mirror of
https://github.com/vale981/tridactyl
synced 2025-03-06 01:51:40 -05:00
completions 1
This commit is contained in:
parent
f982233a49
commit
8d771e578c
5 changed files with 326 additions and 158 deletions
|
@ -1,39 +1,55 @@
|
|||
import * as Messaging from './messaging'
|
||||
|
||||
export type onLineCallback = (exStr: string) => void
|
||||
|
||||
/** CommandLine API for inclusion in background script
|
||||
|
||||
Receives messages from commandline_frame
|
||||
*/
|
||||
export namespace onLine {
|
||||
|
||||
export type onLineCallback = (exStr: string) => void
|
||||
|
||||
const listeners = new Set<onLineCallback>()
|
||||
export function addListener(cb: onLineCallback) {
|
||||
export const onLine = {
|
||||
addListener: function (cb: onLineCallback) {
|
||||
listeners.add(cb)
|
||||
return () => { listeners.delete(cb) }
|
||||
}
|
||||
|
||||
/** Receive events from commandline_frame and pass to listeners */
|
||||
function recvExStr(exstr: string) {
|
||||
for (let listener of listeners) {
|
||||
listener(exstr)
|
||||
}
|
||||
}
|
||||
|
||||
/** Helpers for completions */
|
||||
async function currentWindowTabs(): Promise<browser.tabs.Tab[]> {
|
||||
return await browser.tabs.query({currentWindow:true})
|
||||
}
|
||||
|
||||
async function allWindowTabs(): Promise<browser.tabs.Tab[]> {
|
||||
let allTabs: browser.tabs.Tab[] = []
|
||||
for (const window of await browser.windows.getAll()) {
|
||||
const tabs = await browser.tabs.query({windowId:window.id})
|
||||
allTabs = allTabs.concat(tabs)
|
||||
}
|
||||
return allTabs
|
||||
}
|
||||
|
||||
Messaging.addListener("commandline_background", Messaging.attributeCaller({currentWindowTabs, recvExStr}))
|
||||
},
|
||||
}
|
||||
|
||||
const listeners = new Set<onLineCallback>()
|
||||
|
||||
/** Receive events from commandline_frame and pass to listeners */
|
||||
function recvExStr(exstr: string) {
|
||||
for (let listener of listeners) {
|
||||
listener(exstr)
|
||||
}
|
||||
}
|
||||
|
||||
/** Helpers for completions */
|
||||
async function currentWindowTabs(): Promise<browser.tabs.Tab[]> {
|
||||
return await browser.tabs.query({currentWindow:true})
|
||||
}
|
||||
|
||||
async function allWindowTabs(): Promise<browser.tabs.Tab[]> {
|
||||
let allTabs: browser.tabs.Tab[] = []
|
||||
for (const window of await browser.windows.getAll()) {
|
||||
const tabs = await browser.tabs.query({windowId:window.id})
|
||||
allTabs = allTabs.concat(tabs)
|
||||
}
|
||||
return allTabs
|
||||
}
|
||||
|
||||
export async function show() {
|
||||
Messaging.messageActiveTab('commandline_content', 'show')
|
||||
Messaging.messageActiveTab('commandline_content', 'focus')
|
||||
Messaging.messageActiveTab('commandline_frame', 'focus')
|
||||
}
|
||||
|
||||
export async function hide() {
|
||||
Messaging.messageActiveTab('commandline_content', 'hide')
|
||||
Messaging.messageActiveTab('commandline_content', 'blur')
|
||||
}
|
||||
|
||||
Messaging.addListener("commandline_background", Messaging.attributeCaller({
|
||||
currentWindowTabs,
|
||||
recvExStr,
|
||||
show,
|
||||
hide,
|
||||
}))
|
||||
|
|
|
@ -8,8 +8,8 @@ import * as SELF from './commandline_frame'
|
|||
import './number.clamp'
|
||||
import state from './state'
|
||||
|
||||
let completionsrc: Completions.CompletionSource = undefined
|
||||
let completions = window.document.getElementById("completions") as HTMLElement
|
||||
let activeCompletions: Completions.CompletionSource[] = undefined
|
||||
let completionsDiv = window.document.getElementById("completions") as HTMLElement
|
||||
let clInput = window.document.getElementById("tridactyl-input") as HTMLInputElement
|
||||
|
||||
/* This is to handle Escape key which, while the cmdline is focused,
|
||||
|
@ -19,9 +19,23 @@ let clInput = window.document.getElementById("tridactyl-input") as HTMLInputElem
|
|||
* tl;dr TODO: delete this and better resolve race condition
|
||||
*/
|
||||
let isVisible = false
|
||||
function resizeArea() { if (isVisible) sendExstr("showcmdline") }
|
||||
function resizeArea() {
|
||||
if (isVisible) {
|
||||
Messaging.message("commandline_background", "show")
|
||||
}
|
||||
}
|
||||
|
||||
export let focus = () => clInput.focus()
|
||||
export function focus() {
|
||||
clInput.focus()
|
||||
console.log(activeCompletions)
|
||||
if (! activeCompletions) {
|
||||
activeCompletions = [
|
||||
new Completions.BufferCompletionSource(completionsDiv),
|
||||
]
|
||||
|
||||
activeCompletions.forEach(comp => completionsDiv.appendChild(comp.node))
|
||||
}
|
||||
}
|
||||
|
||||
async function sendExstr(exstr) {
|
||||
Messaging.message("commandline_background", "recvExStr", [exstr])
|
||||
|
@ -74,39 +88,28 @@ clInput.addEventListener("keydown", function (keyevent) {
|
|||
}
|
||||
})
|
||||
|
||||
clInput.addEventListener("input", async () => {
|
||||
// TODO: Handle this in parser
|
||||
if (clInput.value.startsWith("buffer ") || clInput.value.startsWith("tabclose ") ||
|
||||
clInput.value.startsWith("tabmove ")) {
|
||||
const tabs: browser.tabs.Tab[] = await Messaging.message("commandline_background", "currentWindowTabs")
|
||||
completionsrc = Completions.BufferCompletionSource.fromTabs(tabs)
|
||||
completionsrc = await completionsrc.filter(clInput.value)
|
||||
completions.innerHTML = ""
|
||||
completions.appendChild(completionsrc.node)
|
||||
resizeArea()
|
||||
}
|
||||
else if (clInput.value.startsWith("bufferall ")) {
|
||||
// TODO
|
||||
}
|
||||
else if (completionsrc) {
|
||||
completionsrc = undefined
|
||||
completions.innerHTML = ""
|
||||
resizeArea()
|
||||
}
|
||||
clInput.addEventListener("input", () => {
|
||||
// Fire each completion and add a callback to resize area
|
||||
console.log(activeCompletions)
|
||||
activeCompletions.forEach(comp =>
|
||||
comp.filter(clInput.value).then(() => resizeArea())
|
||||
)
|
||||
})
|
||||
|
||||
let cmdline_history_position = 0
|
||||
let cmdline_history_current = ""
|
||||
|
||||
function hide_and_clear(){
|
||||
async function hide_and_clear(){
|
||||
/** Bug workaround: clInput cannot be cleared during an "Escape"
|
||||
* keydown event, presumably due to Firefox's internal handler for
|
||||
* Escape. So clear clInput just after :)
|
||||
*/
|
||||
completionsrc = undefined
|
||||
completions.innerHTML = ""
|
||||
setTimeout(()=>{clInput.value = ""}, 0)
|
||||
sendExstr("hidecmdline")
|
||||
await Messaging.message('commandline_background', 'hide')
|
||||
// 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.
|
||||
activeCompletions.forEach(comp => completionsDiv.removeChild(comp.node))
|
||||
activeCompletions = undefined
|
||||
isVisible = false
|
||||
}
|
||||
|
||||
|
@ -118,7 +121,6 @@ function tabcomplete(){
|
|||
}
|
||||
|
||||
function history(n){
|
||||
completions.innerHTML = ""
|
||||
if (cmdline_history_position == 0){
|
||||
cmdline_history_current = clInput.value
|
||||
}
|
||||
|
@ -133,7 +135,6 @@ function history(n){
|
|||
/* Send the commandline to the background script and await response. */
|
||||
function process() {
|
||||
console.log(clInput.value)
|
||||
sendExstr("hidecmdline")
|
||||
sendExstr(clInput.value)
|
||||
|
||||
// Save non-secret commandlines to the history.
|
||||
|
@ -144,10 +145,9 @@ function process() {
|
|||
state.cmdHistory = state.cmdHistory.concat([clInput.value])
|
||||
}
|
||||
console.log(state.cmdHistory)
|
||||
completionsrc = undefined
|
||||
completions.innerHTML = ""
|
||||
clInput.value = ""
|
||||
cmdline_history_position = 0
|
||||
|
||||
hide_and_clear()
|
||||
}
|
||||
|
||||
export function fillcmdline(newcommand?: string, trailspace = true){
|
||||
|
|
|
@ -12,102 +12,237 @@ How to handle cached e.g. buffer information going out of date?
|
|||
import * as Fuse from 'fuse.js'
|
||||
import {enumerate} from './itertools'
|
||||
import {toNumber} from './convert'
|
||||
import * as Messaging from './messaging'
|
||||
|
||||
const DEFAULT_FAVICON = browser.extension.getURL("static/defaultFavicon.svg")
|
||||
|
||||
// {{{ INTERFACES
|
||||
|
||||
interface CompletionOption {
|
||||
// What to fill into cmdline
|
||||
value: string
|
||||
type OptionState = 'focused' | 'hidden' | 'normal'
|
||||
|
||||
// Highlight and blur element,
|
||||
blur(): void
|
||||
focus(): void
|
||||
abstract class CompletionOption {
|
||||
/** What to fill into cmdline */
|
||||
value: string
|
||||
/** Control presentation of the option */
|
||||
state: OptionState
|
||||
}
|
||||
|
||||
export abstract class CompletionSource {
|
||||
private obsolete = false
|
||||
readonly options: CompletionOption[]
|
||||
node: HTMLElement
|
||||
public completion: string
|
||||
|
||||
readonly options = new Array<CompletionOption>()
|
||||
/** Update [[node]] to display completions relevant to exstr */
|
||||
public abstract filter(exstr: string): Promise<void>
|
||||
|
||||
public node: HTMLElement
|
||||
private _state: OptionState
|
||||
|
||||
// Called by updateCompletions on the child that succeeds its parent
|
||||
abstract activate(): void
|
||||
// this.node now belongs to you, update it or something :)
|
||||
// Example: Mutate node or call replaceChild on its parent
|
||||
/** Control presentation of Source */
|
||||
set state(newstate: OptionState) {
|
||||
switch (newstate) {
|
||||
case 'normal':
|
||||
this.node.classList.remove('hidden')
|
||||
this.completion = undefined
|
||||
break
|
||||
case 'hidden':
|
||||
this.node.classList.add('hidden')
|
||||
break;
|
||||
}
|
||||
this._state = newstate
|
||||
}
|
||||
|
||||
abstract async filter(exstr): Promise<CompletionSource>
|
||||
// <Do some async work that doesn't mutate any non-local vars>
|
||||
// Make a new CompletionOptions and return it
|
||||
get state() {
|
||||
return this._state
|
||||
}
|
||||
}
|
||||
|
||||
// Default classes
|
||||
|
||||
abstract class CompletionOptionHTML extends CompletionOption {
|
||||
public html: HTMLElement
|
||||
public value
|
||||
|
||||
private _state: OptionState = 'hidden'
|
||||
|
||||
/** Control presentation of element */
|
||||
set state(newstate: OptionState) {
|
||||
switch (newstate) {
|
||||
case 'focused':
|
||||
this.html.classList.add('focused')
|
||||
this.html.classList.remove('hidden')
|
||||
break
|
||||
case 'normal':
|
||||
this.html.classList.remove('focused')
|
||||
this.html.classList.remove('hidden')
|
||||
break
|
||||
case 'hidden':
|
||||
this.html.classList.remove('focused')
|
||||
this.html.classList.add('hidden')
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
interface CompletionOptionFuse extends CompletionOption {
|
||||
// For fuzzy matching
|
||||
fuseKeys: any[]
|
||||
}
|
||||
|
||||
type ScoredOption = {
|
||||
index: number,
|
||||
option: CompletionOptionFuse,
|
||||
score: number
|
||||
}
|
||||
|
||||
abstract class CompletionSourceFuse extends CompletionSource {
|
||||
public node
|
||||
public options: CompletionOptionFuse[]
|
||||
|
||||
protected optionContainer = html`<div class="optionContainer">`
|
||||
|
||||
constructor(private prefixes, className: string, title?: string) {
|
||||
super()
|
||||
this.node = html
|
||||
`<div class="${className} hidden">
|
||||
<div class="sectionHeader">${title || className}</div>
|
||||
</div>`
|
||||
this.node.appendChild(this.optionContainer)
|
||||
}
|
||||
|
||||
abstract onFilter(query: string, exstr?: string)
|
||||
|
||||
async filter(exstr: string) {
|
||||
let prefix, query
|
||||
for (const pre of this.prefixes) {
|
||||
if (exstr.startsWith(pre)) {
|
||||
prefix = pre
|
||||
query = exstr.replace(pre, '')
|
||||
}
|
||||
}
|
||||
|
||||
// Hide self if prefixes don't match
|
||||
if (prefix) {
|
||||
this.state = 'normal'
|
||||
} else {
|
||||
this.state = 'hidden'
|
||||
return
|
||||
}
|
||||
|
||||
// Call concrete class
|
||||
this.onFilter(query, prefix)
|
||||
}
|
||||
|
||||
/** Rtn sorted array of {option, score} */
|
||||
protected scoredOptions(query: string, options = this.options): ScoredOption[] {
|
||||
const fuseOptions = {
|
||||
keys: ["fuseKeys"],
|
||||
shouldSort: true,
|
||||
id: "index",
|
||||
includeScore: true,
|
||||
}
|
||||
|
||||
// Can't sort the real options array because Fuse loses class information.
|
||||
const searchThis = this.options.map(
|
||||
(elem, index) => {
|
||||
return {index, fuseKeys: elem.fuseKeys}
|
||||
})
|
||||
|
||||
// PERF: Could be expensive not to cache Fuse()
|
||||
const fuse = new Fuse(searchThis, fuseOptions)
|
||||
return fuse.search(query).map(
|
||||
res => {
|
||||
let result = res as any
|
||||
console.log(result, result.item, query)
|
||||
let index = toNumber(result.item)
|
||||
return {
|
||||
index,
|
||||
option: this.options[index],
|
||||
score: result.score as number
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
/** Set option state by score
|
||||
|
||||
For now just displays all scored elements (see threshold in fuse) and
|
||||
focus the best match.
|
||||
*/
|
||||
protected setStateFromScore(scoredOpts: ScoredOption[]) {
|
||||
let matches = scoredOpts.map(res => res.index)
|
||||
|
||||
for (const [index, option] of enumerate(this.options)) {
|
||||
if (matches.includes(index)) option.state = 'normal'
|
||||
else option.state = 'hidden'
|
||||
}
|
||||
|
||||
if (matches.length) {
|
||||
// TODO: use prefix of last exstr.
|
||||
this.completion = "buffer " + this.options[matches[0]].value
|
||||
this.options[matches[0]].state = 'focused'
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// }}}
|
||||
|
||||
// {{{ IMPLEMENTATIONS
|
||||
|
||||
class BufferCompletionOption implements CompletionOption {
|
||||
// keep a reference to our markup so we can highlight it on focus().
|
||||
html: HTMLElement
|
||||
|
||||
// For fuzzy matching
|
||||
matchStrings: string[] = []
|
||||
class BufferCompletionOption extends CompletionOptionHTML implements CompletionOptionFuse {
|
||||
public fuseKeys = []
|
||||
|
||||
constructor(public value: string, tab: browser.tabs.Tab, isAlternative = false) {
|
||||
super()
|
||||
// Two character buffer properties prefix
|
||||
let pre = ""
|
||||
if (tab.active) pre += "%"
|
||||
else if (isAlternative) pre += "#"
|
||||
if (tab.pinned) { pre += "@" }
|
||||
this.matchStrings.push(pre) // before pad so we don't match whitespace
|
||||
pre = pre.padEnd(2)
|
||||
this.matchStrings.push(String(tab.index + 1), tab.title, tab.url)
|
||||
if (tab.pinned) pre += "@"
|
||||
|
||||
// Push prefix before padding so we don't match on whitespace
|
||||
this.fuseKeys.push(pre)
|
||||
|
||||
// Push properties we want to fuzmatch on
|
||||
this.fuseKeys.push(String(tab.index + 1), tab.title, tab.url)
|
||||
|
||||
// Create HTMLElement
|
||||
const favIconUrl = tab.favIconUrl ? tab.favIconUrl : DEFAULT_FAVICON
|
||||
this.html = html`<div class="BufferCompletionOption option">
|
||||
<span>${pre}</span>
|
||||
<span>${pre.padEnd(2)}</span>
|
||||
<img src=${favIconUrl} />
|
||||
<span>${tab.index + 1}: ${tab.title}</span>
|
||||
<a class="url" href=${tab.url}>${tab.url}</a>
|
||||
</div>`
|
||||
}
|
||||
|
||||
blur() { this.html.classList.remove("focused") }
|
||||
focus() { this.html.classList.add("focused"); this.show() }
|
||||
hide() { this.html.classList.add("hidden"); this.blur() }
|
||||
show() { this.html.classList.remove("hidden") }
|
||||
}
|
||||
|
||||
export class BufferCompletionSource extends CompletionSource {
|
||||
private fuse: Fuse
|
||||
public prefixes = [ "buffer " ]
|
||||
export class BufferCompletionSource extends CompletionSourceFuse {
|
||||
public options: BufferCompletionOption[]
|
||||
|
||||
constructor(
|
||||
readonly options: BufferCompletionOption[],
|
||||
public node: HTMLElement,
|
||||
) {
|
||||
super()
|
||||
const fuseOptions = {
|
||||
keys: ["matchStrings"],
|
||||
shouldSort: true,
|
||||
id: "index",
|
||||
}
|
||||
// TODO:
|
||||
// - store the exstr and trigger redraws on user or data input without
|
||||
// callback faffery
|
||||
// - sort out the element redrawing.
|
||||
|
||||
// Can't sort the real options array because Fuse loses class information.
|
||||
const searchThis = options.map((elem, index) => {return {index, matchStrings: elem.matchStrings}})
|
||||
this.fuse = new Fuse(searchThis, fuseOptions)
|
||||
// Callback
|
||||
private waiting
|
||||
|
||||
constructor(private _parent) {
|
||||
super(["buffer ", "tabmove "], "BufferCompletionOption", "Buffers")
|
||||
this.updateOptions()
|
||||
this._parent.appendChild(this.node)
|
||||
}
|
||||
|
||||
static fromTabs(tabs: browser.tabs.Tab[]) {
|
||||
const node = html`<div class="BufferCompletionSource">
|
||||
<div class="sectionHeader">Buffers</div>`
|
||||
private async updateOptions(exstr?: string) {
|
||||
/* console.log('updateOptions', this.optionContainer) */
|
||||
const tabs: browser.tabs.Tab[] =
|
||||
await Messaging.message("commandline_background", "currentWindowTabs")
|
||||
|
||||
const options = []
|
||||
|
||||
// Get alternative tab, defined as last accessed tab.
|
||||
const alt = tabs.sort((a, b) => { return a.lastAccessed < b.lastAccessed ? 1 : -1 })[1]
|
||||
tabs.sort((a, b) => { return a.index < b.index ? -1 : 1 })
|
||||
|
||||
const options: BufferCompletionOption[] = []
|
||||
|
||||
for (const tab of tabs) {
|
||||
options.push(new BufferCompletionOption(
|
||||
(tab.index + 1).toString(),
|
||||
|
@ -116,45 +251,64 @@ export class BufferCompletionSource extends CompletionSource {
|
|||
)
|
||||
}
|
||||
|
||||
for (const option of options) {
|
||||
node.appendChild(option.html)
|
||||
/* console.log('updateOptions end', this.waiting, this.optionContainer) */
|
||||
this.options = options
|
||||
if (this.waiting) this.waiting()
|
||||
}
|
||||
|
||||
/** Call to replace the current display */
|
||||
// TODO: optionContainer.replaceWith and optionContainer.remove don't work.
|
||||
// I don't know why, but it means we can't replace the div in one go. Maybe
|
||||
// an iframe thing.
|
||||
private updateDisplay() {
|
||||
/* const newContainer = html`<div>` */
|
||||
|
||||
while (this.optionContainer.hasChildNodes()) {
|
||||
this.optionContainer.removeChild(this.optionContainer.lastChild)
|
||||
}
|
||||
|
||||
return new BufferCompletionSource(options, node)
|
||||
for (const option of this.options) {
|
||||
/* newContainer.appendChild(option.html) */
|
||||
this.optionContainer.appendChild(option.html)
|
||||
}
|
||||
|
||||
/* console.log('updateDisplay', this.optionContainer, newContainer) */
|
||||
|
||||
/* let result1 = this.optionContainer.remove() */
|
||||
/* let res2 = this.node.appendChild(newContainer) */
|
||||
/* console.log('results', result1, res2) */
|
||||
}
|
||||
|
||||
activate() {
|
||||
// TODO... this bit of the interface isn't super clear to me yet.
|
||||
}
|
||||
|
||||
async filter(exstr: string) {
|
||||
// Remove the `${prefix} ` bit.
|
||||
const query = exstr.slice(exstr.indexOf(' ') + 1)
|
||||
async onFilter(query, exstr) {
|
||||
// Wait if options is not populated yet. It's possible that it will
|
||||
// never resolve if this.waiting is overridden with a different
|
||||
// callback. That's intended behaviour.
|
||||
let needsCommit
|
||||
if (! this.options) {
|
||||
await new Promise(resolve => this.waiting = () => resolve())
|
||||
needsCommit = true
|
||||
}
|
||||
|
||||
// Else filter by query if query is not empty
|
||||
if (query) {
|
||||
let matches = this.fuse.search(query).map(toNumber) as number[]
|
||||
|
||||
for (const [index, option] of enumerate(this.options)) {
|
||||
if (! matches.includes(index)) option.hide()
|
||||
else option.show()
|
||||
}
|
||||
|
||||
if (matches.length) this.options[matches[0]].focus()
|
||||
this.setStateFromScore(this.scoredOptions(query))
|
||||
// Else show all options
|
||||
} else {
|
||||
for (const option of this.options) {
|
||||
option.show()
|
||||
}
|
||||
this.options.forEach(option => option.state = 'normal')
|
||||
}
|
||||
|
||||
return this
|
||||
//
|
||||
this.updateDisplay()
|
||||
|
||||
// Schedule an update, if you like. Not very useful for buffers, but
|
||||
// will be for other things.
|
||||
setTimeout(() => this.updateOptions(), 0)
|
||||
}
|
||||
}
|
||||
|
||||
// }}}
|
||||
|
||||
// {{{ UNUSED: MANAGING ASYNC CHANGES
|
||||
|
||||
/* If first to modify completions, update it. */
|
||||
/** If first to modify epoch, commit change. May want to change epoch after commiting. */
|
||||
async function commitIfCurrent(epochref: any, asyncFunc: Function, commitFunc: Function, ...args: any[]): Promise<any> {
|
||||
// I *think* sync stuff in here is guaranteed to happen immediately after
|
||||
// being called, up to the first await, despite this being an async
|
||||
|
@ -165,7 +319,10 @@ async function commitIfCurrent(epochref: any, asyncFunc: Function, commitFunc: F
|
|||
else console.error(new Error("Update failed: epoch out of date!"))
|
||||
}
|
||||
|
||||
/* Indicate changes to completions we would like. */
|
||||
/** Indicate changes to completions we would like.
|
||||
|
||||
This will probably never be used for original designed purpose.
|
||||
*/
|
||||
function updateCompletions(filter: string, sources: CompletionSource[]) {
|
||||
for (let [index, source] of enumerate(sources)) {
|
||||
// Tell each compOpt to filter, and if they finish fast enough they:
|
||||
|
|
|
@ -41,8 +41,6 @@
|
|||
import * as Messaging from "./messaging"
|
||||
import {l} from './lib/webext'
|
||||
|
||||
//#content_omit_line
|
||||
import * as CommandLineContent from "./commandline_content"
|
||||
//#content_omit_line
|
||||
import "./number.clamp"
|
||||
//#content_helper
|
||||
|
@ -65,6 +63,8 @@ import * as keydown from "./keydown_background"
|
|||
import {activeTab, activeTabId, firefoxVersionAtLeast} from './lib/webext'
|
||||
//#content_helper
|
||||
import {incrementUrl, getUrlRoot, getUrlParent} from "./url_util"
|
||||
//#background_helper
|
||||
import * as CommandLineBackground from './commandline_background'
|
||||
|
||||
/** @hidden */
|
||||
//#background_helper
|
||||
|
@ -146,6 +146,7 @@ function tabSetActive(id: number) {
|
|||
|
||||
// }}}
|
||||
|
||||
|
||||
// {{{ PAGE CONTEXT
|
||||
|
||||
/** Blur (unfocus) the active element */
|
||||
|
@ -545,19 +546,10 @@ export function composite(...cmds: string[]) {
|
|||
cmds.forEach(controller.acceptExCmd)
|
||||
}
|
||||
|
||||
/** Don't use this */
|
||||
// TODO: These two don't really make sense as excmds, they're internal things.
|
||||
//#content
|
||||
/** Please use fillcmdline instead */
|
||||
//#background
|
||||
export function showcmdline() {
|
||||
CommandLineContent.show()
|
||||
CommandLineContent.focus()
|
||||
}
|
||||
|
||||
/** Don't use this */
|
||||
//#content
|
||||
export function hidecmdline() {
|
||||
CommandLineContent.hide()
|
||||
CommandLineContent.blur()
|
||||
CommandLineBackground.show()
|
||||
}
|
||||
|
||||
/** Set the current value of the commandline to string *with* a trailing space */
|
||||
|
@ -619,7 +611,7 @@ export async function clipboard(excmd: "open"|"yank"|"tabopen" = "open", ...toYa
|
|||
// todo: maybe we should have some common error and error handler
|
||||
throw new Error(`[clipboard] unknown excmd: ${excmd}`)
|
||||
}
|
||||
hidecmdline()
|
||||
CommandLineBackground.hide()
|
||||
}
|
||||
|
||||
// {{{ Buffer/completion stuff
|
||||
|
|
|
@ -33,14 +33,17 @@ input {
|
|||
color: black;
|
||||
display: inline-block;
|
||||
font-size: 10pt;
|
||||
max-height: calc(20 * var(--option-height));
|
||||
min-height: calc(10 * var(--option-height));
|
||||
font-family: "monospace";
|
||||
overflow: hidden;
|
||||
width: 100%;
|
||||
border-top: 0.5px solid grey;
|
||||
}
|
||||
|
||||
#completions .BufferCompletionSource {
|
||||
max-height: calc(20 * var(--option-height));
|
||||
min-height: calc(10 * var(--option-height));
|
||||
}
|
||||
|
||||
#completions img {
|
||||
display: inline;
|
||||
vertical-align: middle;
|
||||
|
|
Loading…
Add table
Reference in a new issue