mirror of
https://github.com/vale981/tridactyl
synced 2025-03-05 17:41:40 -05:00
Merge pull request #2194 from mozbugbox/bind-completion
Binding completion
This commit is contained in:
commit
3b38f093b7
7 changed files with 222 additions and 55 deletions
|
@ -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"
|
||||
|
@ -103,6 +104,7 @@ export function enableCompletions() {
|
|||
if (!commandline_state.activeCompletions) {
|
||||
commandline_state.activeCompletions = [
|
||||
// FindCompletionSource,
|
||||
BindingsCompletionSource,
|
||||
BmarkCompletionSource,
|
||||
TabAllCompletionSource,
|
||||
BufferCompletionSource,
|
||||
|
|
|
@ -108,8 +108,16 @@ export abstract class CompletionOptionHTML extends CompletionOption {
|
|||
switch (newstate) {
|
||||
case "focused":
|
||||
this.html.classList.add("focused")
|
||||
this.html.scrollIntoView()
|
||||
this.html.classList.remove("hidden")
|
||||
const myRect = this.html.getClientRects()[0]
|
||||
if (myRect) {
|
||||
const container = document.getElementById("completions")
|
||||
const boxRect = container.getClientRects()[0]
|
||||
if (myRect.bottom > boxRect.bottom)
|
||||
this.html.scrollIntoView()
|
||||
else if (myRect.top < boxRect.top)
|
||||
this.html.scrollIntoView(false)
|
||||
}
|
||||
break
|
||||
case "normal":
|
||||
this.html.classList.remove("focused")
|
||||
|
|
148
src/completions/Bindings.ts
Normal file
148
src/completions/Bindings.ts
Normal file
|
@ -0,0 +1,148 @@
|
|||
import * as Completions from "@src/completions"
|
||||
import * as config from "@src/lib/config"
|
||||
import * as Binding from "@src/lib/binding"
|
||||
|
||||
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`<tr class="BindingsCompletionOption option">
|
||||
<td class="name">${binding.name}</td>
|
||||
<td class="content">${binding.value}</td>
|
||||
<td class="type">${binding.mode}</td>
|
||||
</tr>`
|
||||
}
|
||||
}
|
||||
|
||||
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
|
||||
}
|
||||
|
||||
this.deselect()
|
||||
|
||||
// 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()
|
||||
}
|
||||
}
|
||||
|
||||
// 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 = Binding.modes
|
||||
.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 (Binding.maps2mode.has(mode + "maps")) {
|
||||
modeName = Binding.maps2mode.get(mode + "maps")
|
||||
}
|
||||
configName = Binding.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() {}
|
||||
}
|
|
@ -90,6 +90,15 @@ export class ExcmdCompletionSource extends Completions.CompletionSourceFuse {
|
|||
}
|
||||
}
|
||||
|
||||
// Add partial matched funcs like: 'conf' ~= 'viewconfig'
|
||||
const seen = new Set(this.options.map(o => o.value))
|
||||
const partial_options = this.scoreOptions(
|
||||
fns
|
||||
.filter(([name, fn]) => !fn.hidden && name.includes(exstr) && !seen.has(name))
|
||||
.map(([name, fn]) => new ExcmdCompletionOption(name, fn.doc)),
|
||||
)
|
||||
this.options = this.options.concat(partial_options)
|
||||
|
||||
this.options.forEach(o => (o.state = "normal"))
|
||||
return this.updateChain()
|
||||
}
|
||||
|
|
|
@ -156,6 +156,7 @@ import { EditorCmds as BgEditorCmds } from "@src/background/editor"
|
|||
import { messageActiveTab } from "@src/lib/messaging"
|
||||
import { EditorCmds } from "@src/background/editor"
|
||||
import { firefoxVersionAtLeast } from "@src/lib/webext"
|
||||
import { parse_bind_args, modeMaps } from "@src/lib/binding"
|
||||
import * as rc from "@src/background/config_rc"
|
||||
import * as css_util from "@src/lib/css_util"
|
||||
import * as Updates from "@src/lib/updates"
|
||||
|
@ -1398,7 +1399,7 @@ export async function help(...helpItems: string[]) {
|
|||
},
|
||||
// -b: look for a binding
|
||||
"-b": (settings, helpItem) => {
|
||||
for (const mode of ["nmaps", "imaps", "inputmaps", "ignoremaps"]) {
|
||||
for (const mode of modeMaps) {
|
||||
const bindings = settings[mode]
|
||||
// If 'helpItem' matches a binding, replace 'helpItem' with
|
||||
// the command that would be executed when pressing the key
|
||||
|
@ -3120,48 +3121,6 @@ export function comclear(name: string) {
|
|||
config.unset("exaliases", name)
|
||||
}
|
||||
|
||||
/** @hidden */
|
||||
//#background_helper
|
||||
interface bind_args {
|
||||
mode: string
|
||||
configName: string
|
||||
key: string
|
||||
excmd: string
|
||||
}
|
||||
|
||||
/** @hidden */
|
||||
//#background_helper
|
||||
function parse_bind_args(...args: string[]): bind_args {
|
||||
if (args.length === 0) throw new Error("Invalid bind/unbind arguments.")
|
||||
|
||||
const result = {} as bind_args
|
||||
result.mode = "normal"
|
||||
|
||||
// TODO: This mapping is copy-pasted in controller_content.ts,
|
||||
// where it constructs the list of parsers. it should be
|
||||
// centralized, possibly as part of rewrite for content-local maps
|
||||
// and similar.
|
||||
const mode2maps = new Map([["normal", "nmaps"], ["ignore", "ignoremaps"], ["insert", "imaps"], ["input", "inputmaps"], ["ex", "exmaps"], ["hint", "hintmaps"], ["visual", "vmaps"]])
|
||||
if (args[0].startsWith("--mode=")) {
|
||||
result.mode = args.shift().replace("--mode=", "")
|
||||
}
|
||||
if (!mode2maps.has(result.mode)) {
|
||||
result.configName = result.mode + "maps"
|
||||
} else {
|
||||
result.configName = mode2maps.get(result.mode)
|
||||
}
|
||||
|
||||
const key = args.shift()
|
||||
// Convert key to internal representation
|
||||
result.key = mapstrToKeyseq(key)
|
||||
.map(k => k.toMapstr())
|
||||
.join("")
|
||||
|
||||
result.excmd = args.join(" ")
|
||||
|
||||
return result
|
||||
}
|
||||
|
||||
/** Bind a sequence of keys to an excmd or view bound sequence.
|
||||
|
||||
This is an easier-to-implement bodge while we work on vim-style maps.
|
||||
|
|
14
src/help.ts
14
src/help.ts
|
@ -1,6 +1,7 @@
|
|||
// This file is only loaded in tridacyl's help pages
|
||||
|
||||
import * as config from "@src/lib/config"
|
||||
import { modeMaps } from "@src/lib/binding"
|
||||
|
||||
/** Create the element that should contain keybinding information */
|
||||
function initTridactylSettingElem(
|
||||
|
@ -93,7 +94,7 @@ async function onExcmdPageLoad() {
|
|||
browser.storage.onChanged.addListener((changes, areaname) => {
|
||||
if ("userconfig" in changes) {
|
||||
// JSON.stringify for comparisons like it's 2012
|
||||
["nmaps", "imaps", "ignoremaps", "inputmaps", "exaliases"].forEach(
|
||||
[...modeMaps, "exaliases"].forEach(
|
||||
kind => {
|
||||
if (
|
||||
JSON.stringify(changes.userconfig.newValue[kind]) !==
|
||||
|
@ -105,16 +106,7 @@ async function onExcmdPageLoad() {
|
|||
}
|
||||
})
|
||||
|
||||
await Promise.all(
|
||||
[
|
||||
"nmaps",
|
||||
"imaps",
|
||||
"ignoremaps",
|
||||
"inputmaps",
|
||||
"exaliases",
|
||||
"exmaps",
|
||||
].map(addSetting),
|
||||
)
|
||||
await Promise.all([...modeMaps, "exaliases"].map(addSetting))
|
||||
// setCommandSetting() can change the height of nodes in the page so we need to scroll to the right place again
|
||||
if (document.location.hash) {
|
||||
/* tslint:disable:no-self-assignment */
|
||||
|
|
49
src/lib/binding.ts
Normal file
49
src/lib/binding.ts
Normal file
|
@ -0,0 +1,49 @@
|
|||
/** # Binding Functions
|
||||
*
|
||||
*/
|
||||
|
||||
import { mapstrToKeyseq } from "@src/lib/keyseq"
|
||||
|
||||
export const mode2maps = new Map([
|
||||
["normal", "nmaps"], ["ignore", "ignoremaps"],
|
||||
["insert", "imaps"], ["input", "inputmaps"], ["ex", "exmaps"],
|
||||
["hint", "hintmaps"], ["visual", "vmaps"]])
|
||||
|
||||
export const maps2mode = new Map(
|
||||
Array.from(mode2maps.keys()).map(k => [mode2maps.get(k), k]))
|
||||
|
||||
export const modes = Array.from(mode2maps.keys())
|
||||
export const modeMaps = Array.from(maps2mode.keys())
|
||||
|
||||
interface bind_args {
|
||||
mode: string
|
||||
configName: string
|
||||
key: string
|
||||
excmd: string
|
||||
}
|
||||
|
||||
export function parse_bind_args(...args: string[]): bind_args {
|
||||
if (args.length === 0) throw new Error("Invalid bind/unbind arguments.")
|
||||
|
||||
const result = {} as bind_args
|
||||
result.mode = "normal"
|
||||
|
||||
if (args[0].startsWith("--mode=")) {
|
||||
result.mode = args.shift().replace("--mode=", "")
|
||||
}
|
||||
if (!mode2maps.has(result.mode)) {
|
||||
result.configName = result.mode + "maps"
|
||||
} else {
|
||||
result.configName = mode2maps.get(result.mode)
|
||||
}
|
||||
|
||||
const key = args.shift()
|
||||
// Convert key to internal representation
|
||||
result.key = mapstrToKeyseq(key)
|
||||
.map(k => k.toMapstr())
|
||||
.join("")
|
||||
|
||||
result.excmd = args.join(" ")
|
||||
|
||||
return result
|
||||
}
|
Loading…
Add table
Reference in a new issue