diff --git a/src/commandline_frame.ts b/src/commandline_frame.ts index 68ced756..010e7878 100644 --- a/src/commandline_frame.ts +++ b/src/commandline_frame.ts @@ -26,6 +26,7 @@ import { CompositeCompletionSource } from "@src/completions/Composite" import { ExcmdCompletionSource } from "@src/completions/Excmd" import { ExtensionsCompletionSource } from "@src/completions/Extensions" import { FileSystemCompletionSource } from "@src/completions/FileSystem" +import { GotoCompletionSource } from "@src/completions/Goto" import { GuisetCompletionSource } from "@src/completions/Guiset" import { HelpCompletionSource } from "@src/completions/Help" import { HistoryCompletionSource } from "@src/completions/History" @@ -119,6 +120,7 @@ export function enableCompletions() { ThemeCompletionSource, CompositeCompletionSource, FileSystemCompletionSource, + GotoCompletionSource, GuisetCompletionSource, HelpCompletionSource, AproposCompletionSource, diff --git a/src/completions/Goto.ts b/src/completions/Goto.ts new file mode 100644 index 00000000..eb3ef7a0 --- /dev/null +++ b/src/completions/Goto.ts @@ -0,0 +1,78 @@ +import * as Messaging from "@src/lib/messaging" +import * as Completions from "@src/completions" +import * as config from "@src/lib/config" + +class GotoCompletionOption + extends Completions.CompletionOptionHTML + implements Completions.CompletionOptionFuse { + public fuseKeys = [] + + constructor(public level, public y, public title, public value) { + super() + this.fuseKeys.push(title) + + this.html = html` + ${title} + ` + } +} + +export class GotoCompletionSource extends Completions.CompletionSourceFuse { + public options: GotoCompletionOption[] = [] + private shouldSetStateFromScore = true + + constructor(private _parent) { + super(["goto"], "GotoCompletionSource", "Headings") + + this.updateOptions() + this.shouldSetStateFromScore = + config.get("completions", "Goto", "autoselect") === "true" + this._parent.appendChild(this.node) + } + + setStateFromScore(scoredOpts: Completions.ScoredOption[]) { + super.setStateFromScore(scoredOpts, this.shouldSetStateFromScore) + } + + onInput(...whatever) { + return this.updateOptions(...whatever) + } + + private async updateOptions(exstr = "") { + this.lastExstr = exstr + const [prefix] = this.splitOnPrefix(exstr) + + // 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 + } + + if (this.options.length < 1) { + this.options = ( + await Messaging.messageOwnTab( + "excmd_content", + "getGotoSelectors", + [], + ) + ) + .sort((a, b) => a.y - b.y) + .map(heading => { + const opt = new GotoCompletionOption( + heading.level, + heading.y, + heading.title, + heading.selector, + ) + opt.state = "normal" + return opt + }) + } + return this.updateChain() + } +} diff --git a/src/excmds.ts b/src/excmds.ts index 423c5cd4..62a23dea 100644 --- a/src/excmds.ts +++ b/src/excmds.ts @@ -4769,6 +4769,36 @@ export async function gobble(nChars: number, endCmd: string) { // }}} +/** @hidden + * This function is used by goto completions. + */ +//#content +export async function getGotoSelectors(): Promise> { + const result = [] + let level = 1 + for (const selector of config.get("gotoselector").split(",")) { + result.push( + ...(Array.from(document.querySelectorAll(selector)) as HTMLElement[]) + .filter(e => e.innerText) + .map(e => ({ level, y: e.getClientRects()[0]?.y, title: e.innerText, selector: DOM.getSelector(e) })) + .filter(e => e.y !== undefined), + ) + level += 1 + } + return result +} + +/** + * Jump to selector. + */ +//#content +export async function goto(...selector: string[]) { + const element = document.querySelector(selector.join(" ")) + if (element) { + element.scrollIntoView() + } +} + /** * Initialize n [mode] mode. * diff --git a/src/lib/config.ts b/src/lib/config.ts index 18aae8e8..73527f6e 100644 --- a/src/lib/config.ts +++ b/src/lib/config.ts @@ -1029,10 +1029,18 @@ export class default_config { */ bmarkweight = 100 + /** + * Default selector for :goto command. + */ + gotoselector = "h1, h2, h3, h4, h5, h6" + /** * General completions options - NB: options are set according to our internal completion source name - see - `src/completions/[name].ts` in the Tridactyl source. */ completions = { + Goto: { + autoselect: "true", + }, Tab: { /** * Whether to automatically select the closest matching completion