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