mirror of
https://github.com/tridactyl/tridactyl.git
synced 2026-09-10 23:36:22 -04:00
Merge branch 'tstmove' into develop
This commit is contained in:
commit
ca0f516613
|
|
@ -38,7 +38,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"
|
||||
|
|
@ -130,7 +133,8 @@ export function enableCompletions() {
|
|||
BmarkCompletionSource,
|
||||
BookmarkFolderCompletionSource,
|
||||
TabAllCompletionSource,
|
||||
BufferCompletionSource,
|
||||
LinearBufferCompletionSource,
|
||||
BufferTreeCompletionSource,
|
||||
ExcmdCompletionSource,
|
||||
ThemeCompletionSource,
|
||||
TabHistoryCompletionSource,
|
||||
|
|
|
|||
|
|
@ -18,6 +18,7 @@ class BufferCompletionOption
|
|||
public isAlternative = false,
|
||||
container: browser.contextualIdentities.ContextualIdentity,
|
||||
public tabIndex: number,
|
||||
titlePrefix: number,
|
||||
) {
|
||||
super()
|
||||
|
||||
|
|
@ -30,7 +31,6 @@ class BufferCompletionOption
|
|||
if (tab.active) preplain += "%"
|
||||
else if (isAlternative) {
|
||||
preplain += "#"
|
||||
this.value = "#"
|
||||
}
|
||||
let pre = preplain
|
||||
if (tab.pinned) preplain += "P"
|
||||
|
|
@ -52,7 +52,7 @@ class BufferCompletionOption
|
|||
this.fuseKeys.push(preplain)
|
||||
|
||||
// Push properties we want to fuzmatch on
|
||||
this.fuseKeys.push(String(tab.index + 1), tab.title, tab.url)
|
||||
this.fuseKeys.push(String(titlePrefix), tab.title, tab.url)
|
||||
|
||||
// Create HTMLElement
|
||||
const favIconUrl = tab.favIconUrl
|
||||
|
|
@ -66,9 +66,7 @@ class BufferCompletionOption
|
|||
<td class="prefixplain" hidden>${preplain}</td>
|
||||
<td class="container"></td>
|
||||
<td class="icon"><img loading="lazy" src="${favIconUrl}" /></td>
|
||||
<td class="title">
|
||||
${this.tabIndex + 1}: ${indicator} ${tab.title}
|
||||
</td>
|
||||
<td class="title">${titlePrefix}: ${indicator} ${tab.title}</td>
|
||||
<td class="content">
|
||||
<a class="url" target="_blank" href=${tab.url}>${tab.url}</a>
|
||||
</td>
|
||||
|
|
@ -76,7 +74,7 @@ class BufferCompletionOption
|
|||
}
|
||||
}
|
||||
|
||||
export class BufferCompletionSource extends Completions.CompletionSourceFuse {
|
||||
abstract class BufferCompletionSource extends Completions.CompletionSourceFuse {
|
||||
public options: BufferCompletionOption[]
|
||||
private shouldSetStateFromScore = true
|
||||
|
||||
|
|
@ -85,26 +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",
|
||||
],
|
||||
"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,26 +118,14 @@ export class BufferCompletionSource extends Completions.CompletionSourceFuse {
|
|||
): Completions.ScoredOption[] {
|
||||
const args = query.trim().split(/\s+/gu)
|
||||
if (args.length === 1) {
|
||||
// if query is an integer n and |n| < options.length
|
||||
if (Number.isInteger(Number(args[0]))) {
|
||||
let index = Number(args[0]) - 1
|
||||
if (Math.abs(index) < options.length) {
|
||||
index = index.mod(options.length)
|
||||
// options order might change by scored sorting
|
||||
return this.TabscoredOptionsStartsWithN(index, options)
|
||||
}
|
||||
} else if (args[0] === "#") {
|
||||
for (const [index, option] of enumerate(options)) {
|
||||
if (option.isAlternative) {
|
||||
return [
|
||||
{
|
||||
index,
|
||||
option,
|
||||
score: 0,
|
||||
},
|
||||
]
|
||||
}
|
||||
}
|
||||
const arg = args[0]
|
||||
if (arg === "#") {
|
||||
return this.optionsLike(option => option.isAlternative, options)
|
||||
}
|
||||
|
||||
const searchId = Number(arg)
|
||||
if (Number.isInteger(searchId)) {
|
||||
return this.optionsBySearchId(searchId, options)
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -160,26 +133,21 @@ export class BufferCompletionSource extends Completions.CompletionSourceFuse {
|
|||
return super.scoredOptions(query)
|
||||
}
|
||||
|
||||
/** Return the scoredOption[] result for the tab index startswith n */
|
||||
private TabscoredOptionsStartsWithN(
|
||||
n: number,
|
||||
protected optionsLike(
|
||||
predicate: (o: BufferCompletionOption) => boolean,
|
||||
options: BufferCompletionOption[],
|
||||
): Completions.ScoredOption[] {
|
||||
const nstr = (n + 1).toString()
|
||||
const res = []
|
||||
const result = []
|
||||
for (const [index, option] of enumerate(options)) {
|
||||
if ((option.tabIndex + 1).toString().startsWith(nstr)) {
|
||||
res.push({
|
||||
index, // index is not tabIndex, changed by score
|
||||
if (predicate(option)) {
|
||||
result.push({
|
||||
index,
|
||||
option,
|
||||
score: 0,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
// old input will change order: 12 => 123 => 12
|
||||
res.sort((a, b) => a.option.tabIndex - b.option.tabIndex)
|
||||
return res
|
||||
return result
|
||||
}
|
||||
|
||||
private async fillOptions(prefix: string) {
|
||||
|
|
@ -205,13 +173,16 @@ export class BufferCompletionSource extends Completions.CompletionSourceFuse {
|
|||
if (!tab_container) {
|
||||
tab_container = Containers.DefaultContainer
|
||||
}
|
||||
const isAlternative = tab.index === altTab.index
|
||||
const titlePrefix = this.titlePrefix(index, tab)
|
||||
options.push(
|
||||
new BufferCompletionOption(
|
||||
(index + 1).toString(),
|
||||
this.completionValue(titlePrefix, isAlternative),
|
||||
tab,
|
||||
tab.index === altTab.index,
|
||||
isAlternative,
|
||||
tab_container,
|
||||
index,
|
||||
titlePrefix,
|
||||
),
|
||||
)
|
||||
}
|
||||
|
|
@ -295,4 +266,119 @@ export class BufferCompletionSource extends Completions.CompletionSourceFuse {
|
|||
}
|
||||
return this.options[option.tabIndex]
|
||||
}
|
||||
|
||||
/**
|
||||
* 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)
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -98,6 +98,7 @@ import { OpenMode } from "@src/lib/hint_util"
|
|||
import * as Proxy from "@src/lib/proxy"
|
||||
import * as arg from "@src/lib/arg_util"
|
||||
import * as R from "ramda"
|
||||
import * as treestyletab from "@src/interop/tst"
|
||||
|
||||
/**
|
||||
* This is used to drive some excmd handling in `composite`.
|
||||
|
|
@ -2859,8 +2860,8 @@ export async function tabopen_helper({ addressarr = [], waitForDom = false }): P
|
|||
// and browser.search.search() seems to fix that problem.
|
||||
// See https://github.com/tridactyl/tridactyl/pull/4791.
|
||||
return openInNewTab(null, args, waitForDom)
|
||||
.then(tab => browser.tabs.get(tab.id))
|
||||
.then(tab => browser.search.search({tabId: tab.id, ...maybeURL}))
|
||||
.then(tab => browser.tabs.get(tab.id))
|
||||
.then(tab => browser.search.search({ tabId: tab.id, ...maybeURL }))
|
||||
}
|
||||
|
||||
// Fall back to about:newtab
|
||||
|
|
@ -3055,7 +3056,7 @@ export async function tabcloseallto(side: string) {
|
|||
export async function tabdiscard(index: string) {
|
||||
let id: number
|
||||
if (index === "--all") {
|
||||
return browser.tabs.query({}).then(ts => browser.tabs.discard(ts.map(t=>t.id)))
|
||||
return browser.tabs.query({}).then(ts => browser.tabs.discard(ts.map(t => t.id)))
|
||||
} else if (index === undefined) {
|
||||
id = (await activeTab()).id
|
||||
} else {
|
||||
|
|
@ -3088,16 +3089,16 @@ export async function undo(item = "recent"): Promise<number> {
|
|||
item === "recent"
|
||||
? s => s.window || (s.tab && s.tab.windowId === current_win_id)
|
||||
: item === "tab"
|
||||
? s => s.tab
|
||||
: item === "tab_strict"
|
||||
? s => s.tab && s.tab.windowId === current_win_id
|
||||
: item === "window"
|
||||
? s => s.window
|
||||
: !isNaN(parseInt(item, 10))
|
||||
? s => (s.tab || s.window).sessionId === item
|
||||
: () => {
|
||||
throw new Error(`[undo] Invalid argument: ${item}. Must be one of "recent, "tab", "tab_strict", "window" or a sessionId (by selecting a session using the undo completion).`)
|
||||
} // this won't throw an error if there isn't anything in the session list, but I don't think that matters
|
||||
? s => s.tab
|
||||
: item === "tab_strict"
|
||||
? s => s.tab && s.tab.windowId === current_win_id
|
||||
: item === "window"
|
||||
? s => s.window
|
||||
: !isNaN(parseInt(item, 10))
|
||||
? s => (s.tab || s.window).sessionId === item
|
||||
: () => {
|
||||
throw new Error(`[undo] Invalid argument: ${item}. Must be one of "recent, "tab", "tab_strict", "window" or a sessionId (by selecting a session using the undo completion).`)
|
||||
} // this won't throw an error if there isn't anything in the session list, but I don't think that matters
|
||||
const session = sessions.find(predicate)
|
||||
|
||||
if (session) {
|
||||
|
|
@ -3372,7 +3373,7 @@ export async function qall() {
|
|||
//#background
|
||||
export async function sidebaropen(...urllike: string[]) {
|
||||
const url = await queryAndURLwrangler(urllike)
|
||||
if (typeof url === "string") return browser.sidebarAction.setPanel({panel: url})
|
||||
if (typeof url === "string") return browser.sidebarAction.setPanel({ panel: url })
|
||||
throw new Error("Unsupported URL for sidebar. If it was a search term try `:set searchengine google` first")
|
||||
}
|
||||
|
||||
|
|
@ -3382,7 +3383,7 @@ export async function sidebaropen(...urllike: string[]) {
|
|||
* `:bind --mode=browser <C-.> jsua browser.sidebarAction.open(); tri.excmds.sidebaropen("https://mail.google.com/mail/mu")`
|
||||
*/
|
||||
//#background
|
||||
export async function jsua(){
|
||||
export async function jsua() {
|
||||
throw new Error(":jsua can only be called through `bind --mode=browser` binds, see `:help jsua`")
|
||||
}
|
||||
|
||||
|
|
@ -3392,7 +3393,7 @@ export async function jsua(){
|
|||
* `:bind --mode=browser <C-.> sidebartoggle`
|
||||
*/
|
||||
//#background
|
||||
export async function sidebartoggle(){
|
||||
export async function sidebartoggle() {
|
||||
throw new Error(":sidebartoggle can only be called through `bind --mode=browser` binds, see `:help sidebartoggle`")
|
||||
}
|
||||
|
||||
|
|
@ -3947,7 +3948,7 @@ export function fillcmdline(...strarr: string[]) {
|
|||
const str = strarr.join(" ")
|
||||
showcmdline(false)
|
||||
logger.debug("excmds fillcmdline sending fillcmdline to commandline_frame")
|
||||
return Messaging.messageOwnTab("commandline_frame", "fillcmdline", [str, true/*trailspace*/, true/*focus*/])
|
||||
return Messaging.messageOwnTab("commandline_frame", "fillcmdline", [str, true /*trailspace*/, true /*focus*/])
|
||||
}
|
||||
|
||||
/** Set the current value of the commandline to string *without* a trailing space */
|
||||
|
|
@ -3955,7 +3956,7 @@ export function fillcmdline(...strarr: string[]) {
|
|||
export function fillcmdline_notrail(...strarr: string[]) {
|
||||
const str = strarr.join(" ")
|
||||
showcmdline(false)
|
||||
return Messaging.messageOwnTab("commandline_frame", "fillcmdline", [str, false/*trailspace*/, true/*focus*/])
|
||||
return Messaging.messageOwnTab("commandline_frame", "fillcmdline", [str, false /*trailspace*/, true /*focus*/])
|
||||
}
|
||||
|
||||
/** Show and fill the command line without focusing it */
|
||||
|
|
@ -6208,3 +6209,27 @@ export async function elementunhide() {
|
|||
elem.className = elem.className.replace("TridactylKilledElem", "")
|
||||
}
|
||||
// vim: tabstop=4 shiftwidth=4 expandtab
|
||||
|
||||
/**
|
||||
* Move the current TST tree to be just in front of the tab specified.
|
||||
*/
|
||||
//#background
|
||||
export async function tstmove(tabId: string) {
|
||||
treestyletab.moveTreeBefore(Number(tabId))
|
||||
}
|
||||
|
||||
/**
|
||||
* Move the current TST tree to be right after the tab specified.
|
||||
*/
|
||||
//#background
|
||||
export async function tstmoveafter(tabId: string) {
|
||||
treestyletab.moveTreeAfter(Number(tabId))
|
||||
}
|
||||
|
||||
/**
|
||||
* Attach current tree as a child to the selected parent.
|
||||
*/
|
||||
//#background
|
||||
export async function tstattach(tabId: string) {
|
||||
treestyletab.attachTree(Number(tabId))
|
||||
}
|
||||
|
|
|
|||
31
src/interop/tst.ts
Normal file
31
src/interop/tst.ts
Normal file
|
|
@ -0,0 +1,31 @@
|
|||
import * as ExtensionInfo from "@src/lib/extension_info"
|
||||
import { browserBg } from "@src/lib/webext"
|
||||
|
||||
export async function moveTreeBefore(tabId: number) {
|
||||
await ExtensionInfo.messageExtension("tree_style_tab", {
|
||||
type: "move-before",
|
||||
tab: "current",
|
||||
referenceTabId: tabId,
|
||||
followChildren: true,
|
||||
})
|
||||
}
|
||||
|
||||
export async function moveTreeAfter(tabId: number) {
|
||||
await ExtensionInfo.messageExtension("tree_style_tab", {
|
||||
type: "move-after",
|
||||
tab: "current",
|
||||
referenceTabId: tabId,
|
||||
followChildren: true,
|
||||
})
|
||||
}
|
||||
|
||||
export async function attachTree(parentTabId: number) {
|
||||
const currentTab = (
|
||||
await browserBg.tabs.query({ currentWindow: true, active: true })
|
||||
)[0]
|
||||
await ExtensionInfo.messageExtension("tree_style_tab", {
|
||||
type: "attach",
|
||||
child: currentTab.id,
|
||||
parent: parentTabId,
|
||||
})
|
||||
}
|
||||
|
|
@ -5,14 +5,21 @@
|
|||
|
||||
*/
|
||||
|
||||
import Logger from "./logging"
|
||||
|
||||
/** Friendly-names of extensions that are used in different places so
|
||||
that we can refer to them with more readable and less magic ids.
|
||||
*/
|
||||
export const KNOWN_EXTENSIONS: { [name: string]: string } = {
|
||||
temp_containers: "{c607c8df-14a7-4f28-894f-29e8722976af}",
|
||||
multi_account_containers: "@testpilot-containers",
|
||||
tree_style_tab: "treestyletab@piro.sakura.ne.jp",
|
||||
}
|
||||
|
||||
type KnownExtensionId = keyof typeof KNOWN_EXTENSIONS
|
||||
|
||||
const logger = new Logger("extensions")
|
||||
|
||||
/** List of currently installed extensions.
|
||||
*/
|
||||
const installedExtensions: {
|
||||
|
|
@ -84,3 +91,11 @@ export async function listExtensions() {
|
|||
.map(key => installedExtensions[key])
|
||||
.filter(obj => obj.optionsUrl.length > 0)
|
||||
}
|
||||
|
||||
export async function messageExtension(id: KnownExtensionId, message: any) {
|
||||
try {
|
||||
return await browser.runtime.sendMessage(KNOWN_EXTENSIONS[id], message)
|
||||
} catch (e) {
|
||||
logger.error("Failed to communicate with extension ", id, e)
|
||||
}
|
||||
}
|
||||
|
|
|
|||
Loading…
Reference in a new issue