refactored tst specifics into separate class

This commit is contained in:
Vsevolod Chernetskyi 2025-02-25 16:29:52 +02:00
parent 9e6b18cf2f
commit 0f58004816
2 changed files with 134 additions and 66 deletions

View file

@ -35,7 +35,10 @@ import { PreferenceCompletionSource } from "@src/completions/Preferences"
import { RssCompletionSource } from "@src/completions/Rss"
import { SessionsCompletionSource } from "@src/completions/Sessions"
import { SettingsCompletionSource } from "@src/completions/Settings"
import { BufferCompletionSource } from "@src/completions/Tab"
import {
LinearBufferCompletionSource,
BufferTreeCompletionSource,
} from "@src/completions/Tab"
import { TabAllCompletionSource } from "@src/completions/TabAll"
import { ThemeCompletionSource } from "@src/completions/Theme"
import { TabHistoryCompletionSource } from "@src/completions/TabHistory"
@ -120,7 +123,8 @@ export function enableCompletions() {
BindingsCompletionSource,
BmarkCompletionSource,
TabAllCompletionSource,
BufferCompletionSource,
LinearBufferCompletionSource,
BufferTreeCompletionSource,
ExcmdCompletionSource,
ThemeCompletionSource,
TabHistoryCompletionSource,

View file

@ -6,9 +6,6 @@ import * as Completions from "@src/completions"
import * as config from "@src/lib/config"
import * as Messaging from "@src/lib/messaging"
// TODO: fix # value for TST
// TODO: extract TST to a separate class
class BufferCompletionOption
extends Completions.CompletionOptionHTML
implements Completions.CompletionOptionFuse {
@ -21,7 +18,6 @@ class BufferCompletionOption
public isAlternative = false,
container: browser.contextualIdentities.ContextualIdentity,
public tabIndex: number,
searchId: number,
titlePrefix: number,
) {
super()
@ -56,7 +52,7 @@ class BufferCompletionOption
this.fuseKeys.push(preplain)
// Push properties we want to fuzmatch on
this.fuseKeys.push(String(searchId), tab.title, tab.url)
this.fuseKeys.push(String(titlePrefix), tab.title, tab.url)
// Create HTMLElement
const favIconUrl = tab.favIconUrl
@ -78,7 +74,7 @@ class BufferCompletionOption
}
}
export class BufferCompletionSource extends Completions.CompletionSourceFuse {
abstract class BufferCompletionSource extends Completions.CompletionSourceFuse {
public options: BufferCompletionOption[]
private shouldSetStateFromScore = true
@ -87,29 +83,13 @@ export class BufferCompletionSource extends Completions.CompletionSourceFuse {
// callback faffery
// - sort out the element redrawing.
constructor(private _parent) {
super(
[
"tab",
"tabclose",
"tabdetach",
"tabduplicate",
"tabmove",
"tabrename",
"tabdiscard",
"pin",
"tstmove",
"tstmoveafter",
"tstattach",
],
"BufferCompletionSource",
"Tabs",
)
constructor(_parent, prefixes: string[], className: string) {
super(prefixes, className, "Tabs")
this.sortScoredOptions = true
this.shouldSetStateFromScore =
config.get("completions", "Tab", "autoselect") === "true"
this.updateOptions()
this._parent.appendChild(this.node)
_parent.appendChild(this.node)
Messaging.addListener("tab_changes", () => this.reactToTabChanges())
}
@ -133,30 +113,19 @@ export class BufferCompletionSource extends Completions.CompletionSourceFuse {
/** Score with fuse unless query is a single # or looks like a tab index */
scoredOptions(
exstr: string,
query: string,
options = this.options,
): Completions.ScoredOption[] {
const [prefix, query] = this.splitOnPrefix(exstr)
const args = query.trim().split(/\s+/gu)
if (args.length === 1) {
if (args[0] === "#") {
const arg = args[0]
if (arg === "#") {
return this.optionsLike(option => option.isAlternative, options)
}
const arg = Number(args[0])
// In TST case argument is tabId
if (Number.isInteger(arg) && this.isTstCommand(prefix)) {
return this.optionsLike(option => option.tabId === arg, options)
}
// if query is an integer n and |n| < options.length
if (Number.isInteger(arg)) {
let index = arg - 1
if (Math.abs(index) < options.length) {
index = index.mod(options.length)
// options order might change by scored sorting
return this.TabscoredOptionsStartsWithN(index, options)
}
const searchId = Number(arg)
if (Number.isInteger(searchId)) {
return this.optionsBySearchId(searchId, options)
}
}
@ -164,20 +133,7 @@ export class BufferCompletionSource extends Completions.CompletionSourceFuse {
return super.scoredOptions(query)
}
/** Return the scoredOption[] result for the tab index startswith n */
private TabscoredOptionsStartsWithN(
n: number,
options: BufferCompletionOption[],
): Completions.ScoredOption[] {
const nstr = (n + 1).toString()
options.sort((a, b) => a.tabIndex - b.tabIndex)
return this.optionsLike(
option => (option.tabIndex + 1).toString().startsWith(nstr),
options,
)
}
private optionsLike(
protected optionsLike(
predicate: (o: BufferCompletionOption) => boolean,
options: BufferCompletionOption[],
): Completions.ScoredOption[] {
@ -218,17 +174,14 @@ export class BufferCompletionSource extends Completions.CompletionSourceFuse {
tab_container = Containers.DefaultContainer
}
const isAlternative = tab.index === altTab.index
const searchId = this.isTstCommand(prefix) ? tab.id : tab.index + 1
const titlePrefix = this.isTstCommand(prefix) ? tab.id : index + 1
const completionValue = isAlternative ? "#" : String(titlePrefix)
const titlePrefix = this.titlePrefix(index, tab)
options.push(
new BufferCompletionOption(
completionValue,
this.completionValue(titlePrefix, isAlternative),
tab,
isAlternative,
tab_container,
index,
searchId,
titlePrefix,
),
)
@ -264,7 +217,7 @@ export class BufferCompletionSource extends Completions.CompletionSourceFuse {
/* console.log('updateOptions', this.optionContainer) */
if (query && query.trim().length > 0) {
this.setStateFromScore(this.scoredOptions(exstr))
this.setStateFromScore(this.scoredOptions(query))
} else {
this.options.forEach(option => (option.state = "normal"))
}
@ -314,7 +267,118 @@ export class BufferCompletionSource extends Completions.CompletionSourceFuse {
return this.options[option.tabIndex]
}
private isTstCommand(prefix: string): boolean {
return prefix.startsWith("tst")
/**
* Provide identifier, which will be used before tab title in the completion option.
* @param index of a tab calculated by Tridactyl
* @param tab Tab
*/
protected abstract titlePrefix(index: number, tab: browser.tabs.Tab): number
/**
* Provide value, which will be used on tab completion (i.e. when user selects tab option using <Space> or <Enter>).
* @param index of a tab calculated by Tridactyl
* @param tab Tab
*/
protected abstract completionValue(
titlePrefix: number,
isAlternative: boolean,
): string
/**
* Filter list of options by option identifier.
* @param searchId identifier of the option
* @param options list of options to search through
*/
protected abstract optionsBySearchId(
searchId: number,
options: BufferCompletionOption[],
): Completions.ScoredOption[]
}
export class LinearBufferCompletionSource extends BufferCompletionSource {
constructor(_parent) {
super(
_parent,
[
"tab",
"tabclose",
"tabdetach",
"tabduplicate",
"tabmove",
"tabrename",
"tabdiscard",
"pin",
],
"LinearBufferCompletionSource",
)
}
protected titlePrefix(index: number, _tab: browser.tabs.Tab): number {
return index + 1
}
protected completionValue(
titlePrefix: number,
isAlternative: boolean,
): string {
if (isAlternative) {
return "#"
}
return String(titlePrefix)
}
protected optionsBySearchId(
searchId: number,
options: BufferCompletionOption[],
): Completions.ScoredOption[] {
const index = (searchId - 1).mod(options.length)
options.sort((a, b) => a.tabIndex - b.tabIndex)
return this.tabScoredOptionsStartsWithN(index, options)
}
/** Return the scoredOption[] result for the tab index startswith n */
private tabScoredOptionsStartsWithN(
n: number,
options: BufferCompletionOption[],
): Completions.ScoredOption[] {
const nstr = (n + 1).toString()
return this.optionsLike(
option => (option.tabIndex + 1).toString().startsWith(nstr),
options,
)
}
}
/**
* TST specifics for tab completion.
*
* At the moment the only difference to linear tabs is that TST source uses tab
* ID in place of tab index for identification.
*/
export class BufferTreeCompletionSource extends BufferCompletionSource {
constructor(_parent) {
super(
_parent,
["tstmove", "tstmoveafter", "tstattach"],
"BufferTreeCompletionSource",
)
}
protected titlePrefix(_index: number, tab: browser.tabs.Tab): number {
return tab.id
}
protected completionValue(
titlePrefix: number,
_isAlternative: boolean,
): string {
return String(titlePrefix)
}
protected optionsBySearchId(
searchId: number,
options: BufferCompletionOption[],
): Completions.ScoredOption[] {
return this.optionsLike(option => option.tabId === searchId, options)
}
}