mirror of
https://github.com/tridactyl/tridactyl.git
synced 2026-09-10 07:16:33 -04:00
Restore incsearch
This commit is contained in:
parent
0ab3f5bf62
commit
475ae04e79
|
|
@ -33,6 +33,7 @@ import { DialogCompletionSource } from "@src/completions/Dialog"
|
|||
import { ExcmdCompletionSource } from "@src/completions/Excmd"
|
||||
import { ExtensionsCompletionSource } from "@src/completions/Extensions"
|
||||
import { FileSystemCompletionSource } from "@src/completions/FileSystem"
|
||||
import { FindCompletionSource } from "@src/completions/Find"
|
||||
import { GotoCompletionSource } from "@src/completions/Goto"
|
||||
import { GuisetCompletionSource } from "@src/completions/Guiset"
|
||||
import { GlossaryCompletionSource } from "@src/completions/Glossary"
|
||||
|
|
@ -139,7 +140,7 @@ export function enableCompletions() {
|
|||
if (!commandline_state.activeCompletions) {
|
||||
commandline_state.activeCompletions = [
|
||||
AutocmdCompletionSource,
|
||||
// FindCompletionSource,
|
||||
FindCompletionSource,
|
||||
BindingsCompletionSource,
|
||||
BmarkCompletionSource,
|
||||
BookmarkFolderCompletionSource,
|
||||
|
|
@ -342,7 +343,10 @@ commandline_state.clInput.addEventListener(
|
|||
|
||||
let refreshQueue: Promise<unknown> = Promise.resolve()
|
||||
export function refresh_completions(exstr) {
|
||||
const result = refreshQueue.then(() => refreshCompletions(exstr))
|
||||
const session = commandSession
|
||||
const result = refreshQueue.then(() =>
|
||||
session === commandSession ? refreshCompletions(exstr) : undefined,
|
||||
)
|
||||
refreshQueue = result.catch(() => undefined)
|
||||
return result
|
||||
}
|
||||
|
|
@ -366,32 +370,51 @@ function refreshCompletions(exstr) {
|
|||
|
||||
/** @hidden **/
|
||||
let onInputPromise: Promise<void | void[]> = Promise.resolve()
|
||||
const COMPLETION_THROTTLE_MS = 100
|
||||
let completionTimer
|
||||
let lastCompletionStarted = -Infinity
|
||||
/** @hidden **/
|
||||
commandline_state.clInput.addEventListener("input", () => {
|
||||
logger.debug("commandline_frame clInput input event listener")
|
||||
clInputValueChanged()
|
||||
})
|
||||
|
||||
/** @hidden **/
|
||||
async function updateCompletions(exstr: string, session = commandSession) {
|
||||
lastCompletionStarted = performance.now()
|
||||
await onInputPromise
|
||||
if (session !== commandSession) return
|
||||
if (exstr !== commandline_state.clInput.value) {
|
||||
contentState.cmdline_filter = exstr
|
||||
return
|
||||
}
|
||||
|
||||
onInputPromise = refresh_completions(exstr)
|
||||
onInputPromise.then(() => {
|
||||
contentState.cmdline_filter = exstr
|
||||
})
|
||||
}
|
||||
|
||||
/** @hidden **/
|
||||
function clInputValueChanged() {
|
||||
const exstr = commandline_state.clInput.value
|
||||
const session = commandSession
|
||||
contentState.current_cmdline = exstr
|
||||
contentState.cmdline_filter = ""
|
||||
// Schedule completion computation. We do not start computing immediately because this would incur a slow down on quickly repeated input events (e.g. maintaining <Backspace> pressed)
|
||||
setTimeout(async () => {
|
||||
// Make sure the previous computation has ended
|
||||
await onInputPromise
|
||||
// If we're not the current completion computation anymore, stop
|
||||
if (exstr !== commandline_state.clInput.value) {
|
||||
contentState.cmdline_filter = exstr
|
||||
return
|
||||
}
|
||||
|
||||
onInputPromise = refresh_completions(exstr)
|
||||
onInputPromise.then(() => {
|
||||
contentState.cmdline_filter = exstr
|
||||
})
|
||||
}, 100)
|
||||
// Run immediately when idle, otherwise retain one trailing refresh.
|
||||
clearTimeout(completionTimer)
|
||||
const delay =
|
||||
COMPLETION_THROTTLE_MS - (performance.now() - lastCompletionStarted)
|
||||
if (delay <= 0) void updateCompletions(exstr, session)
|
||||
else
|
||||
completionTimer = setTimeout(
|
||||
() =>
|
||||
void updateCompletions(
|
||||
commandline_state.clInput.value,
|
||||
session,
|
||||
),
|
||||
delay,
|
||||
)
|
||||
}
|
||||
|
||||
/** @hidden **/
|
||||
|
|
@ -403,7 +426,11 @@ let cmdline_history_current = ""
|
|||
* Otherwise, no need to pass an argument.
|
||||
*/
|
||||
export function clear(evlistener = false) {
|
||||
if (evlistener) commandSession = { pending: 0, queue: [Promise.resolve()] }
|
||||
if (evlistener) {
|
||||
commandSession = { pending: 0, queue: [Promise.resolve()] }
|
||||
clearTimeout(completionTimer)
|
||||
lastCompletionStarted = -Infinity
|
||||
}
|
||||
if (evlistener) prev_cmd_called_history = false
|
||||
if (evlistener)
|
||||
commandline_state.clInput.removeEventListener("blur", noblur)
|
||||
|
|
|
|||
|
|
@ -1,116 +1,46 @@
|
|||
import { activeTabId } from "@src/lib/webext"
|
||||
import * as Messaging from "@src/lib/messaging"
|
||||
import * as Completions from "../completions"
|
||||
import * as config from "@src/lib/config"
|
||||
|
||||
class FindCompletionOption
|
||||
extends Completions.CompletionOptionHTML
|
||||
implements Completions.CompletionOptionFuse {
|
||||
public fuseKeys = []
|
||||
constructor(m, reverse = false) {
|
||||
super()
|
||||
this.value =
|
||||
(reverse ? "-? " : "") + ("-: " + m.index) + " " + m.rangeData.text
|
||||
this.fuseKeys.push(m.rangeData.text)
|
||||
|
||||
// Create HTMLElement
|
||||
this.html = html`<tr class="FindCompletionOption option">
|
||||
<td class="content">
|
||||
${m.precontext}<span class="match">${m.rangeData.text}</span
|
||||
>${m.postcontext}
|
||||
</td>
|
||||
</tr>`
|
||||
}
|
||||
}
|
||||
import * as Messaging from "@src/lib/messaging"
|
||||
import { ownTabId } from "@src/lib/webext"
|
||||
|
||||
export class FindCompletionSource extends Completions.CompletionSourceFuse {
|
||||
public options: FindCompletionOption[]
|
||||
public prevCompletion = null
|
||||
public completionCount = 0
|
||||
public options = []
|
||||
private session = Math.random()
|
||||
private tabId = ownTabId()
|
||||
private active = false
|
||||
|
||||
constructor(private _parent) {
|
||||
super(["find "], "FindCompletionSource", "Matches")
|
||||
|
||||
this._parent.appendChild(this.node)
|
||||
constructor(_parent?) {
|
||||
super(["find"], "FindCompletionSource")
|
||||
}
|
||||
|
||||
async onInput(exstr) {
|
||||
const id = this.completionCount++
|
||||
// If there's already a promise being executed, wait for it to finish
|
||||
await this.prevCompletion
|
||||
// Since we might have awaited for this.prevCompletion, we don't have a guarantee we're the last completion the user asked for anymore
|
||||
if (id === this.completionCount - 1) {
|
||||
// If we are the last completion
|
||||
this.prevCompletion = this.updateOptions(exstr)
|
||||
await this.prevCompletion
|
||||
}
|
||||
filter(exstr: string) {
|
||||
const [, argstr] = this.splitOnPrefix(exstr)
|
||||
if (argstr === undefined) return this.cancel()
|
||||
this.active = true
|
||||
void this.send(
|
||||
{ session: this.session },
|
||||
...argstr.trim().split(/\s+/),
|
||||
).catch(() => undefined)
|
||||
return Promise.resolve()
|
||||
}
|
||||
|
||||
// Overriding this function is important, the default one has a tendency to hide options when you don't expect it
|
||||
setStateFromScore() {
|
||||
this.options.forEach(o => (o.state = "normal"))
|
||||
destroy() {
|
||||
return this.cancel()
|
||||
}
|
||||
|
||||
private async updateOptions(exstr?: string) {
|
||||
if (!exstr) return
|
||||
|
||||
// Flag parsing because -? should reverse completions
|
||||
const tokens = exstr.split(" ")
|
||||
const flagpos = tokens.indexOf("-?")
|
||||
const reverse = flagpos >= 0
|
||||
if (reverse) {
|
||||
tokens.splice(flagpos, 1)
|
||||
}
|
||||
|
||||
const query = tokens.slice(1).join(" ")
|
||||
const minincsearchlen = await config.getAsync("minincsearchlen")
|
||||
// No point if continuing if the user hasn't started searching yet
|
||||
if (query.length < minincsearchlen) return
|
||||
|
||||
let findresults = await config.getAsync("findresults")
|
||||
const incsearch = (await config.getAsync("incsearch")) === "true"
|
||||
if (findresults === 0 && !incsearch) return
|
||||
|
||||
let incsearchonly = false
|
||||
if (findresults === 0) {
|
||||
findresults = 1
|
||||
incsearchonly = true
|
||||
}
|
||||
|
||||
// Note: the use of activeTabId here might break completions if the user starts searching for a pattern in a really big page and then switches to another tab.
|
||||
// Getting the tabId should probably be done in the constructor but you can't have async constructors.
|
||||
const tabId = await activeTabId()
|
||||
const findings = await Messaging.messageTab(
|
||||
tabId,
|
||||
"finding_content",
|
||||
"find",
|
||||
[query, findresults, reverse],
|
||||
private cancel() {
|
||||
if (!this.active) return Promise.resolve()
|
||||
this.active = false
|
||||
return this.send({ session: this.session, cancel: true }).catch(
|
||||
() => undefined,
|
||||
)
|
||||
}
|
||||
|
||||
// If the search was successful
|
||||
if (findings.length > 0) {
|
||||
// Get match context
|
||||
const len = await config.getAsync("findcontextlen")
|
||||
const matches = await Messaging.messageTab(
|
||||
tabId,
|
||||
"finding_content",
|
||||
"getMatches",
|
||||
[findings, len],
|
||||
)
|
||||
|
||||
if (incsearch)
|
||||
Messaging.messageTab(tabId, "finding_content", "jumpToMatch", [
|
||||
query,
|
||||
false,
|
||||
0,
|
||||
])
|
||||
|
||||
if (!incsearchonly) {
|
||||
this.options = matches.map(
|
||||
m => new FindCompletionOption(m, reverse),
|
||||
)
|
||||
this.updateChain(exstr, this.options)
|
||||
}
|
||||
}
|
||||
private send(preview, ...args: string[]) {
|
||||
return this.tabId.then(tabId =>
|
||||
Messaging.messageTab(tabId, "excmd_content", "find", [
|
||||
preview,
|
||||
...args,
|
||||
]),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
import * as config from "@src/lib/config"
|
||||
import * as DOM from "@src/lib/dom"
|
||||
import { browserBg, activeTabId } from "@src/lib/webext"
|
||||
import { browserBg, ownTabId } from "@src/lib/webext"
|
||||
import state from "@src/state"
|
||||
import * as State from "@src/state"
|
||||
import { compute as scrollCompute } from "compute-scroll-into-view"
|
||||
|
|
@ -126,16 +126,20 @@ class FindHighlight extends HTMLSpanElement {
|
|||
|
||||
const actions = scrollCompute(fakeNode as HTMLElement, option)
|
||||
for (const { el: element, top, left } of actions) {
|
||||
if (preview && !preview.scrolls.has(element))
|
||||
preview.scrolls.set(element, [
|
||||
element.scrollLeft,
|
||||
element.scrollTop,
|
||||
])
|
||||
element.scrollTo({ top, left, behavior: "instant" })
|
||||
}
|
||||
}
|
||||
focus() {
|
||||
if (!isHighlightVisible(this)) {
|
||||
focusMatch(focusElement = true, scroll = true) {
|
||||
if (scroll && !isHighlightVisible(this)) {
|
||||
this.scrollIntoView({ block: "center", inline: "center" })
|
||||
}
|
||||
const focusable = this.queryInRange("a,input,button,details")
|
||||
if (focusable) focusable.focus()
|
||||
|
||||
if (focusElement && focusable) focusable.focus()
|
||||
setNativeFocus(this.nativeRange, true)
|
||||
this.background = `var(--tridactyl-search-highlight-active-color)`
|
||||
for (const node of this.children) {
|
||||
|
|
@ -222,11 +226,19 @@ function highlightsDrawn() {
|
|||
let lastHighlights
|
||||
// Which element of `lastSearch` was last selected
|
||||
let selected = 0
|
||||
let preview
|
||||
let searchGeneration = 0
|
||||
|
||||
let HIGHLIGHT_TIMER
|
||||
let REPOSITION_TIMER
|
||||
const POSITION_OBSERVER = new MutationObserver(scheduleReposition)
|
||||
|
||||
function resetHighlightTimer() {
|
||||
clearTimeout(HIGHLIGHT_TIMER)
|
||||
const timeout = config.get("findhighlighttimeout")
|
||||
if (timeout > 0) HIGHLIGHT_TIMER = setTimeout(removeHighlighting, timeout)
|
||||
}
|
||||
|
||||
function scheduleReposition() {
|
||||
if (!host?.firstChild) return
|
||||
clearTimeout(REPOSITION_TIMER)
|
||||
|
|
@ -239,11 +251,11 @@ window.addEventListener("resize", scheduleReposition)
|
|||
window.addEventListener("scroll", scheduleReposition, true)
|
||||
|
||||
export async function jumpToMatch(searchQuery, option) {
|
||||
const timeout = config.get("findhighlighttimeout")
|
||||
if (timeout > 0) {
|
||||
clearTimeout(HIGHLIGHT_TIMER)
|
||||
HIGHLIGHT_TIMER = setTimeout(removeHighlighting, timeout)
|
||||
}
|
||||
const previewing = option["preview"] === true
|
||||
const generation = ++searchGeneration
|
||||
if (!previewing) preview = undefined
|
||||
if (previewing) clearTimeout(HIGHLIGHT_TIMER)
|
||||
else resetHighlightTimer()
|
||||
// First, search for the query
|
||||
const literal = option["regex"] && searchQuery.match(/^\/(.*)\/([^/]*)$/s)
|
||||
let [source, flags] = literal ? literal.slice(1) : [searchQuery, ""]
|
||||
|
|
@ -260,15 +272,18 @@ export async function jumpToMatch(searchQuery, option) {
|
|||
let results: any = { count: 0 }
|
||||
if (!regex)
|
||||
results = await browserBg.find.find(searchQuery, {
|
||||
tabId: await activeTabId(),
|
||||
tabId: await ownTabId(),
|
||||
caseSensitive: sensitive,
|
||||
entireWord: false,
|
||||
includeRangeData: true,
|
||||
})
|
||||
state.lastSearchQuery = searchQuery
|
||||
state.lastSearchRegex = regex?.flags
|
||||
if (generation !== searchGeneration) return
|
||||
if (!previewing) {
|
||||
state.lastSearchQuery = searchQuery
|
||||
state.lastSearchRegex = regex?.flags
|
||||
}
|
||||
lastHighlights = []
|
||||
removeHighlighting()
|
||||
clearHighlighting()
|
||||
|
||||
const documents = [document]
|
||||
if (!regex)
|
||||
|
|
@ -321,14 +336,14 @@ export async function jumpToMatch(searchQuery, option) {
|
|||
if ("jumpTo" in option) {
|
||||
selected =
|
||||
(option["jumpTo"] + lastHighlights.length) % lastHighlights.length
|
||||
focusHighlight(selected)
|
||||
focusHighlight(selected, !previewing)
|
||||
return
|
||||
}
|
||||
|
||||
// Just reuse the code to find the first match in the view
|
||||
selected = 0
|
||||
if (isHighlightVisible(lastHighlights[selected])) {
|
||||
focusHighlight(selected)
|
||||
focusHighlight(selected, !previewing)
|
||||
} else {
|
||||
const searchFromView = true
|
||||
await jumpToNextMatch(1, searchFromView)
|
||||
|
|
@ -354,15 +369,67 @@ function drawHighlights(highlights) {
|
|||
highlights.forEach(elem => host.appendChild(elem))
|
||||
}
|
||||
|
||||
export function removeHighlighting() {
|
||||
function clearHighlighting() {
|
||||
POSITION_OBSERVER.disconnect()
|
||||
clearTimeout(REPOSITION_TIMER)
|
||||
clearNativeHighlights()
|
||||
while (host?.firstChild) host.removeChild(host.firstChild)
|
||||
}
|
||||
|
||||
export function focusHighlight(index) {
|
||||
lastHighlights[index].focus()
|
||||
function restorePreviewScrolls(snapshot = preview) {
|
||||
for (const [element, [left, top]] of snapshot?.scrolls || [])
|
||||
element.scrollTo({ left, top, behavior: "instant" })
|
||||
}
|
||||
|
||||
export async function previewMatch(session: number, searchQuery, option) {
|
||||
if (preview?.session !== session) {
|
||||
if (preview) cancelPreview(preview.session)
|
||||
preview = {
|
||||
session,
|
||||
highlights: lastHighlights,
|
||||
selected,
|
||||
drawn: highlightsDrawn(),
|
||||
scrolls: new Map(),
|
||||
}
|
||||
}
|
||||
restorePreviewScrolls()
|
||||
const generation = searchGeneration + 1
|
||||
try {
|
||||
await jumpToMatch(searchQuery, { ...option, preview: true })
|
||||
} catch (_) {
|
||||
if (preview?.session === session && generation === searchGeneration) {
|
||||
clearHighlighting()
|
||||
lastHighlights = []
|
||||
restorePreviewScrolls()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export function cancelPreview(session: number) {
|
||||
if (preview?.session !== session) return
|
||||
++searchGeneration
|
||||
clearHighlighting()
|
||||
const snapshot = preview
|
||||
preview = undefined
|
||||
lastHighlights = snapshot.highlights
|
||||
selected = snapshot.selected
|
||||
if (snapshot.drawn && lastHighlights?.length) {
|
||||
drawHighlights(lastHighlights)
|
||||
focusHighlight(selected, false, false)
|
||||
resetHighlightTimer()
|
||||
}
|
||||
restorePreviewScrolls(snapshot)
|
||||
}
|
||||
|
||||
export function removeHighlighting() {
|
||||
if (preview) cancelPreview(preview.session)
|
||||
else ++searchGeneration
|
||||
clearTimeout(HIGHLIGHT_TIMER)
|
||||
clearHighlighting()
|
||||
}
|
||||
|
||||
export function focusHighlight(index, focusElement = true, scroll = true) {
|
||||
lastHighlights[index].focusMatch(focusElement, scroll)
|
||||
if (nativeHighlights) return
|
||||
repositionHighlight()
|
||||
POSITION_OBSERVER.observe(document, {
|
||||
|
|
@ -380,25 +447,25 @@ export function repositionHighlight() {
|
|||
}
|
||||
|
||||
export async function jumpToNextMatch(n: number, searchFromView = false) {
|
||||
const generation = searchGeneration
|
||||
const lastSearchQuery = await State.getAsync("lastSearchQuery")
|
||||
const lastRegex = await State.getAsync("lastSearchRegex")
|
||||
if (generation !== searchGeneration) return
|
||||
if (!lastSearchQuery) return
|
||||
if (!lastHighlights) {
|
||||
const rebuildGeneration = searchGeneration + 1
|
||||
await jumpToMatch(lastSearchQuery, {
|
||||
reverse: n < 0,
|
||||
regex: !!lastRegex,
|
||||
caseSensitive: lastRegex ? !lastRegex.includes("i") : undefined,
|
||||
})
|
||||
if (rebuildGeneration !== searchGeneration) return
|
||||
if (Math.abs(n) === 1) return
|
||||
n = n - n / Math.abs(n)
|
||||
searchFromView = false
|
||||
}
|
||||
if (!highlightsDrawn()) {
|
||||
const timeout = config.get("findhighlighttimeout")
|
||||
if (timeout > 0) {
|
||||
clearTimeout(HIGHLIGHT_TIMER)
|
||||
HIGHLIGHT_TIMER = setTimeout(removeHighlighting, timeout)
|
||||
}
|
||||
resetHighlightTimer()
|
||||
drawHighlights(lastHighlights)
|
||||
}
|
||||
if (lastHighlights[selected] === undefined) {
|
||||
|
|
@ -431,7 +498,7 @@ export async function jumpToNextMatch(n: number, searchFromView = false) {
|
|||
}
|
||||
}
|
||||
|
||||
focusHighlight(selected)
|
||||
focusHighlight(selected, !preview)
|
||||
}
|
||||
|
||||
export function currentMatchRange(): Range {
|
||||
|
|
|
|||
|
|
@ -1505,7 +1505,7 @@ export function scrollpage(n = 1, count = 1) {
|
|||
}
|
||||
|
||||
/**
|
||||
* Rudimentary find mode, left unbound by default as we don't currently support `incsearch`. Suggested binds:
|
||||
* Find mode is left unbound by default. Suggested binds:
|
||||
*
|
||||
* ```text
|
||||
* bind / fillcmdline find
|
||||
|
|
@ -1529,6 +1529,9 @@ export function scrollpage(n = 1, count = 1) {
|
|||
*/
|
||||
//#content
|
||||
export function find(...args: string[]) {
|
||||
// Completion previews pass session metadata as a non-user argument.
|
||||
const preview =
|
||||
typeof (args[0] as any) === "object" ? (args.shift() as any) : undefined
|
||||
const parsed = arg.lib(
|
||||
{
|
||||
"--jump-to": Number,
|
||||
|
|
@ -1561,6 +1564,13 @@ export function find(...args: string[]) {
|
|||
option["caseSensitive"] = argOpt["--case-sensitive"]
|
||||
option["regex"] = argOpt["--regex"]
|
||||
const searchQuery = argOpt._.join(" ")
|
||||
if (preview) {
|
||||
const { session } = preview
|
||||
if (preview.cancel) return finding.cancelPreview(session)
|
||||
if (config.get("incsearch") === "true" && searchQuery.length > 0)
|
||||
return finding.previewMatch(session, searchQuery, option)
|
||||
return finding.cancelPreview(session)
|
||||
}
|
||||
return finding.jumpToMatch(searchQuery, option)
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -153,7 +153,7 @@ export function getCommandlineFns(cmdline_state: {
|
|||
},
|
||||
|
||||
/** Hide the command line and cmdline_state.clear its content without executing it. **/
|
||||
hide_and_clear: () => {
|
||||
hide_and_clear: async () => {
|
||||
cmdline_state.clear(true)
|
||||
cmdline_state.keyEvents = []
|
||||
|
||||
|
|
@ -162,14 +162,13 @@ export function getCommandlineFns(cmdline_state: {
|
|||
messageOwnTab("commandline_content", "blur")
|
||||
// Delete all completion sources - I don't think this is required, but this
|
||||
// way if there is a transient bug in completions it shouldn't persist.
|
||||
if (cmdline_state.activeCompletions)
|
||||
cmdline_state.activeCompletions.forEach(comp => {
|
||||
comp.destroy?.()
|
||||
cmdline_state.completionsDiv.removeChild(comp.node)
|
||||
})
|
||||
const completions = cmdline_state.activeCompletions || []
|
||||
cmdline_state.activeCompletions = undefined
|
||||
const destroying = Promise.all(completions.map(comp => comp.destroy?.()))
|
||||
completions.forEach(comp => comp.node.remove())
|
||||
cmdline_state.isVisible = false
|
||||
cmdline_state.resolveCloseWaiters?.()
|
||||
await destroying
|
||||
},
|
||||
|
||||
/**
|
||||
|
|
@ -223,7 +222,7 @@ export function getCommandlineFns(cmdline_state: {
|
|||
const command =
|
||||
cmdline_state.getCompletion() || cmdline_state.clInput.value
|
||||
|
||||
cmdline_state.fns.hide_and_clear()
|
||||
await cmdline_state.fns.hide_and_clear()
|
||||
|
||||
if (cmdline_state.fns.is_valid_commandline(command) === false)
|
||||
return
|
||||
|
|
@ -250,9 +249,9 @@ export function getCommandlineFns(cmdline_state: {
|
|||
execute_ex_on_all_completions: (excmd: string) =>
|
||||
execute_ex_on_all(cmdline_state, excmd),
|
||||
|
||||
copy_completion: () => {
|
||||
copy_completion: async () => {
|
||||
const command = cmdline_state.getCompletion()
|
||||
cmdline_state.fns.hide_and_clear()
|
||||
await cmdline_state.fns.hide_and_clear()
|
||||
return messageOwnTab("controller_content", "acceptExCmd", [
|
||||
"clipboard yank " + command,
|
||||
])
|
||||
|
|
|
|||
|
|
@ -1387,13 +1387,11 @@ export class default_config {
|
|||
findhighlighttimeout = 0
|
||||
|
||||
/**
|
||||
* Whether Tridactyl should jump to the first match when using `:find`
|
||||
* Whether Tridactyl should preview matches while typing `:find`
|
||||
*/
|
||||
incsearch: "true" | "false" = "false"
|
||||
incsearch: "true" | "false" = "true"
|
||||
|
||||
/**
|
||||
* How many characters should be typed before triggering incsearch/completions
|
||||
*/
|
||||
/** @deprecated Retained for compatibility; this setting has no effect. */
|
||||
minincsearchlen = 3
|
||||
|
||||
/**
|
||||
|
|
|
|||
Loading…
Reference in a new issue