Cache tab/taball options for faster filtering

This commit is contained in:
Oliver Blanthorn 2026-07-28 10:39:58 +02:00
parent 912b718755
commit 20372d6a7d
No known key found for this signature in database
GPG key ID: 2BB8C36BB504BFF3
3 changed files with 48 additions and 6 deletions

View file

@ -78,6 +78,8 @@ class BufferCompletionOption
export class BufferCompletionSource extends TabCompletionSource {
public options: BufferCompletionOption[]
private optionSet: string
private unfilteredOptions: BufferCompletionOption[]
private shouldSetStateFromScore = true
private navigationAnchorTabId: number
@ -257,6 +259,7 @@ export class BufferCompletionSource extends TabCompletionSource {
!(prefix === "tabdiscard" && /^\s*--all(?:\s|$)/u.test(query))
// Hide self and stop if prefixes don't match
const wasHidden = this.state === "hidden"
if (prefix) {
// Show self if prefix and currently hidden
if (this.state === "hidden") {
@ -267,13 +270,24 @@ export class BufferCompletionSource extends TabCompletionSource {
return
}
const options = await this.fillOptions(prefix, generation)
if (!options || !this.isCurrentUpdate(generation)) return
const optionSet = prefix === "tabmove" ? prefix : "default"
const cacheHit =
!this.optionsDirty && !wasHidden && this.optionSet === optionSet
let options: BufferCompletionOption[]
if (!cacheHit) {
options = await this.fillOptions(prefix, generation)
if (!options || !this.isCurrentUpdate(generation)) return
}
const lastFocused =
!setInitialPosition && this.lastFocused?.state === "focused"
? (this.lastFocused as BufferCompletionOption)
: undefined
this.options = options
if (options) {
this.unfilteredOptions = options
this.optionSet = optionSet
this.optionsDirty = false
}
this.options = this.unfilteredOptions
this.completion = undefined
/* console.log('updateOptions', this.optionContainer) */

View file

@ -90,6 +90,7 @@ class TabAllCompletionOption
export class TabAllCompletionSource extends TabCompletionSource {
public options: TabAllCompletionOption[]
private optionSet: string
private shouldSetStateFromScore = true
constructor(private _parent) {
@ -137,6 +138,7 @@ export class TabAllCompletionSource extends TabCompletionSource {
const [prefix] = this.splitOnPrefix(exstr)
// Hide self and stop if prefixes don't match
const wasHidden = this.state === "hidden"
if (prefix) {
// Show self if prefix and currently hidden
if (this.state === "hidden") {
@ -147,6 +149,14 @@ export class TabAllCompletionSource extends TabCompletionSource {
return
}
const optionSet =
this.canonicalisePrefix(prefix) === "tabgrab" ? "tabgrab" : "taball"
if (!this.optionsDirty && !wasHidden && this.optionSet === optionSet) {
this.completion = undefined
this.updateChain()
return
}
const mru = config.get("tabsort") == "mru"
const [tabs, altTab, currentWindow, containerList] =
await Promise.all([
@ -166,7 +176,7 @@ export class TabAllCompletionSource extends TabCompletionSource {
// Check to see if this is a command that needs to exclude the current
// window
const excludeCurrentWindow = this.canonicalisePrefix(prefix) === "tabgrab"
const excludeCurrentWindow = optionSet === "tabgrab"
const windowIndices = new Map(
[...new Set(tabs.map(tab => tab.windowId))]
.sort((a, b) => a - b)
@ -191,8 +201,7 @@ export class TabAllCompletionSource extends TabCompletionSource {
tab.id.toString(),
tab,
tab.id === altTab?.id,
tab.active &&
tab.windowId === currentWindow.id,
tab.active && tab.windowId === currentWindow.id,
windowIndices.get(tab.windowId),
containerMap.get(tab.cookieStoreId) ||
Containers.DefaultContainer,
@ -210,6 +219,8 @@ export class TabAllCompletionSource extends TabCompletionSource {
: -1
this.completion = undefined
this.options = options
this.optionSet = optionSet
this.optionsDirty = false
this.updateChain()
if (wasFocused) {
const visibleOptions = this.options.filter(o => o.state !== "hidden")

View file

@ -2,6 +2,7 @@ import * as Completions from "@src/completions"
import * as Messaging from "@src/lib/messaging"
export abstract class TabCompletionSource extends Completions.CompletionSourceFuse {
protected optionsDirty = true
private removeTabChangesListener: () => void
private handlingTabChanges = false
private tabChangesQueued = false
@ -34,6 +35,21 @@ export abstract class TabCompletionSource extends Completions.CompletionSourceFu
protected abstract refreshForTabChanges(): Promise<void>
updateDisplay() {
const visible = this.options
.filter(option => option.state !== "hidden")
.map(option => option.html)
const visibleSet = new Set(visible)
for (const child of [...this.optionContainer.children])
if (!visibleSet.has(child as HTMLElement)) child.remove()
for (const [index, option] of visible.entries()) {
const child = this.optionContainer.children[index]
if (child !== option)
this.optionContainer.insertBefore(option, child || null)
}
this.next(0)
}
private queueTabChanges(message: Messaging.Message) {
if (this.state === "hidden") return
const priority = (message.args?.[0] || []).some(([command]) =>
@ -69,6 +85,7 @@ export abstract class TabCompletionSource extends Completions.CompletionSourceFu
try {
do {
this.tabChangesQueued = false
this.optionsDirty = true
await this.refreshForTabChanges()
} while (this.tabChangesQueued)
} finally {