diff --git a/src/commandline_frame.ts b/src/commandline_frame.ts index 6190fb4e..fed7224c 100644 --- a/src/commandline_frame.ts +++ b/src/commandline_frame.ts @@ -21,6 +21,7 @@ import * as perf from "@src/perf" import "@src/lib/number.clamp" import "@src/lib/html-tagged-template" import { TabAllCompletionSource } from "@src/completions/TabAll" +import { BindingsCompletionSource } from "@src/completions/Bindings" import { BufferCompletionSource } from "@src/completions/Tab" import { BmarkCompletionSource } from "@src/completions/Bmark" import { ExcmdCompletionSource } from "@src/completions/Excmd" @@ -102,6 +103,7 @@ export function enableCompletions() { if (!commandline_state.activeCompletions) { commandline_state.activeCompletions = [ // FindCompletionSource, + BindingsCompletionSource, BmarkCompletionSource, TabAllCompletionSource, BufferCompletionSource, diff --git a/src/completions/Bindings.ts b/src/completions/Bindings.ts new file mode 100644 index 00000000..fb895b34 --- /dev/null +++ b/src/completions/Bindings.ts @@ -0,0 +1,152 @@ +import * as Completions from "@src/completions" +import * as config from "@src/lib/config" + +class BindingsCompletionOption extends Completions.CompletionOptionHTML + implements Completions.CompletionOptionFuse { + public fuseKeys = [] + + constructor( + public value: string, + binding: { name: string; value: string; mode: string }, + ) { + super() + this.html = html` + ${binding.name} + ${binding.value} + ${binding.mode} + ` + } +} + +export class BindingsCompletionSource extends Completions.CompletionSourceFuse { + public options: BindingsCompletionOption[] + + constructor(private _parent) { + super( + ["bind", "unbind", "bindurl", "unbindurl", "reset", "reseturl"], + "BindingsCompletionSource", + "Bindings", + ) + + this._parent.appendChild(this.node) + } + + public async filter(exstr: string) { + this.lastExstr = exstr + let options = "" + let [prefix, query] = this.splitOnPrefix(exstr) + const args = query ? query.split(/\s+/) : [] + let configName: string = "nmaps" + let modeName = "normal" + let urlPattern: string = null + + // Hide self and stop if prefixes don't match + if (prefix) { + // Show self if prefix and currently hidden + if (this.state === "hidden") { + this.state = "normal" + } + } else { + this.state = "hidden" + return + } + + // url pattern is mandatory: bindurl, unbindurl, reseturl + if (prefix.trim().endsWith("url")) { + urlPattern = args.length > 0 ? args.shift() : "" + options += urlPattern ? urlPattern + " " : "" + + if (args.length === 0) { + const patterns = config.get("subconfigs") + this.options = Object.keys(patterns) + .filter(pattern => pattern.startsWith(urlPattern)) + .sort() + .map(pattern => { + return new BindingsCompletionOption( + pattern, { + name: pattern, + value: "", + mode: "URL Pattern", + }) + }) + + return this.updateChain() + } + } + + const mode2maps = new Map([ + ["normal", "nmaps"], ["ignore", "ignoremaps"], + ["insert", "imaps"], ["input", "inputmaps"], ["ex", "exmaps"], + ["hint", "hintmaps"], ["visual", "vmaps"]]) + const maps2mode = new Map( + Array.from(mode2maps.keys()).map(k => [mode2maps.get(k), k])) + + // completion maps mode + if (args.length === 1 && args[0].startsWith("--m")) { + const margs = args[0].split("=") + if ("--mode".includes(margs[0])) { + const modeStr = margs.length > 1 ? margs[1] : "" + this.options = Array.from(mode2maps.keys()) + .filter(k => k.startsWith(modeStr)) + .map(name => { + return new BindingsCompletionOption( + options + "--mode=" + name, { + name, + value: "", + mode: "Mode Name", + }) + }) + return this.updateChain() + } + } + + if (args.length > 0 && args[0].startsWith("--mode=")) { + const modeStr = args.shift() + const mode = modeStr.replace("--mode=", "") + + modeName = mode + if (maps2mode.has(mode + "maps")) { + modeName = maps2mode.get(mode + "maps") + } + configName = mode2maps.get(modeName) + options += `--mode=${modeName} ` + } + + if (!configName) { + this.options = [] + return this.updateChain() + } + + const bindings = urlPattern ? config.getURL(urlPattern, [configName]) : config.get(configName as any) + + if (bindings === undefined) { + this.options = [] + return this.updateChain() + } + + query = args.join(" ").toLowerCase() + this.options = Object.keys(bindings) + .filter(x => x.toLowerCase().startsWith(query) ) + .sort() + .map(keystr => { + return new BindingsCompletionOption( + options + keystr + " " + bindings[keystr], { + name: keystr, + value: JSON.stringify(bindings[keystr]), + mode: `${configName} (${modeName})`, + }) + }) + + return this.updateChain() + } + + updateChain() { + // Options are pre-trimmed to the right length. + this.options.forEach(option => (option.state = "normal")) + + // Call concrete class + return this.updateDisplay() + } + + onInput() {} +}