Merge branch 'improve_docs2'

This commit is contained in:
Oliver Blanthorn 2018-10-10 17:53:06 +01:00
commit 5c0647242c
No known key found for this signature in database
GPG key ID: 2BB8C36BB504BFF3
8 changed files with 342 additions and 140 deletions

View file

@ -8,6 +8,7 @@ import { BufferAllCompletionSource } from "@src/completions/BufferAll"
import { BufferCompletionSource } from "@src/completions/Buffer" import { BufferCompletionSource } from "@src/completions/Buffer"
import { BmarkCompletionSource } from "@src/completions/Bmark" import { BmarkCompletionSource } from "@src/completions/Bmark"
import { ExcmdCompletionSource } from "@src/completions/Excmd" import { ExcmdCompletionSource } from "@src/completions/Excmd"
import { HelpCompletionSource } from "@src/completions/Help"
import { HistoryCompletionSource } from "@src/completions/History" import { HistoryCompletionSource } from "@src/completions/History"
import { SettingsCompletionSource } from "@src/completions/Settings" import { SettingsCompletionSource } from "@src/completions/Settings"
import * as Messaging from "@src/lib/messaging" import * as Messaging from "@src/lib/messaging"
@ -65,8 +66,9 @@ function enableCompletions() {
new BufferAllCompletionSource(completionsDiv), new BufferAllCompletionSource(completionsDiv),
new BufferCompletionSource(completionsDiv), new BufferCompletionSource(completionsDiv),
new ExcmdCompletionSource(completionsDiv), new ExcmdCompletionSource(completionsDiv),
new SettingsCompletionSource(completionsDiv), new HelpCompletionSource(completionsDiv),
new HistoryCompletionSource(completionsDiv), new HistoryCompletionSource(completionsDiv),
new SettingsCompletionSource(completionsDiv),
] ]
const fragment = document.createDocumentFragment() const fragment = document.createDocumentFragment()

101
src/completions/Help.ts Normal file
View file

@ -0,0 +1,101 @@
import * as Completions from "@src/completions"
import * as Metadata from "@src/.metadata.generated"
import * as aliases from "@src/lib/aliases"
import * as config from "@src/lib/config"
import state from "@src/state"
import { browserBg } from "@src/lib/webext"
import { typeToString } from "@src/lib/metadata"
class HelpCompletionOption extends Completions.CompletionOptionHTML implements Completions.CompletionOptionFuse {
public fuseKeys = []
constructor(
public value: string,
doc: string
) {
super()
this.html = html`<tr class="HelpCompletionOption option">
<td class="name">${value}</td>
<td class="doc">${doc}</td>
</tr>`
}
}
export class HelpCompletionSource extends Completions.CompletionSourceFuse {
public options: HelpCompletionOption[]
constructor(private _parent) {
super(
["help"],
"HelpCompletionSource",
"Help",
)
this._parent.appendChild(this.node)
}
public async filter(exstr: string) {
this.lastExstr = exstr
let [prefix, query] = this.splitOnPrefix(exstr)
let options = ""
// Hide self and stop if prefixes don't match
if (prefix) {
// Show self if prefix and currently hidden
if (this.state === "hidden") {
this.state = "normal"
}
} else {
this.state = "hidden"
return
}
let configmd = Metadata.everything["src/lib/config.ts"].classes.default_config
let fns = Metadata.everything["src/excmds.ts"].functions
let settings = config.get()
let exaliases = settings.exaliases
let bindings = settings.nmaps
// Settings completion
this.options = Object.keys(settings)
.filter(x => x.startsWith(query))
.map(setting => {
let doc = ""
if (configmd[setting]) {
doc = configmd[setting].doc.join(" ")
}
return new HelpCompletionOption(setting, `Setting. ${doc}`)
})
// Excmd completion
.concat(Object.keys(fns)
.filter(fn => fn.startsWith(query))
.map(f => new HelpCompletionOption(f, `Excmd. ${fns[f].doc}`))
)
// Alias completion
.concat(Object.keys(exaliases)
.filter(alias => alias.startsWith(query))
.map(alias => {
let cmd = aliases.expandExstr(alias)
let doc = (fns[cmd] || {}).doc || ""
return new HelpCompletionOption(alias, `Alias for \`${cmd}\`. ${doc}`)
})
)
// Bindings completion
.concat(Object.keys(bindings)
.filter(binding => binding.startsWith(query))
.map(binding => new HelpCompletionOption(binding, `Normal mode binding for \`${bindings[binding]}\``)))
.sort((compopt1, compopt2) => compopt1.value.localeCompare(compopt2.value))
this.updateChain()
}
updateChain() {
// Options are pre-trimmed to the right length.
this.options.forEach(option => (option.state = "normal"))
// Call concrete class
this.updateDisplay()
}
onInput() {}
}

View file

@ -4,25 +4,11 @@
Use `:help <excmd>` or scroll down to show [[help]] for a particular excmd. If you're still stuck, you might consider reading through the [:tutor](/static/clippy/tutor.html) again. Use `:help <excmd>` or scroll down to show [[help]] for a particular excmd. If you're still stuck, you might consider reading through the [:tutor](/static/clippy/tutor.html) again.
The default keybinds can be found [here](/static/docs/classes/_lib_config_.default_config.html) or all active binds can be seen with `:viewconfig nmaps`. The default keybinds and settings can be found [here](/static/docs/classes/_lib_config_.default_config.html) and active binds can be seen with `:viewconfig nmaps` or with [[bind]].
You can also view them with [[bind]]. Try `bind j`.
For more information, and FAQs, check out our [readme][3] on github.
Tridactyl is in a pretty early stage of development. Please report any
issues and make requests for missing features on the GitHub [project page][1].
You can also get in touch using Matrix, Gitter, or IRC chat clients:
[![Matrix Chat][matrix-badge]][matrix-link]
[![Gitter Chat][gitter-badge]][gitter-link]
[![Freenode Chat][freenode-badge]][freenode-link]
All three channels are mirrored together, so it doesn't matter which one you use.
## How to use this help page ## How to use this help page
We've hackily re-purposed TypeDoc which is designed for internal documentation. Every function (excmd) on this page can be called via Tridactyl's command line which we call "ex". There is a slight change in syntax, however. Wherever you see: Every function (excmd) on this page can be called via Tridactyl's command line which we call "ex". There is a slight change in syntax, however. Wherever you see:
`function(arg1,arg2)` `function(arg1,arg2)`
@ -44,36 +30,25 @@
You do not need to worry about types. Return values which are promises will turn into whatever they promise to when used in [[composite]]. You do not need to worry about types. Return values which are promises will turn into whatever they promise to when used in [[composite]].
## Highlighted features: ## Caveats
- Press `b` to bring up a list of open tabs in the current window; you can
type the tab ID or part of the title or URL to choose a tab
- Press `Shift` + `Insert` to enter "ignore mode". Press `Shift` + `Insert`
again to return to "normal mode". `<C-A-backtick>` also works both ways.
- Press `f` to start "hint mode", `F` to open in background (note: hint
characters should be typed in lowercase)
- Press `o` to `:open` a different page
- Press `s` if you want to search for something that looks like a domain
name or URL
- [[bind]] new commands with e.g. `:bind J tabnext`
- Type `:help` to see a list of available excmds
- Use `yy` to copy the current page URL to your clipboard
- `[[`and `]]` to navigate through the pages of comics, paginated
articles, etc
- Pressing `ZZ` will close all tabs and windows, but it will only "save"
them if your about:preferences are set to "show your tabs and windows
from last time"
- Press Ctrl-i in a text box to edit in an external editor (e.g. vim). Requires native messenger.
- Change theme with `colours default|dark|greenmat|shydactyl`
There are some caveats common to all webextension vimperator-alikes: There are some caveats common to all webextension vimperator-alikes:
- To make Tridactyl work on addons.mozilla.org and some other Mozilla domains, you need to open `about:config`, run [[fixamo]] or add a new boolean `privacy.resistFingerprinting.block_mozAddonManager` with the value `true`, and remove the above domains from `extensions.webextensions.restrictedDomains`. - To make Tridactyl work on addons.mozilla.org and some other Mozilla domains, you need to open `about:config`, run [[fixamo]] or add a new boolean `privacy.resistFingerprinting.block_mozAddonManager` with the value `true`, and remove the above domains from `extensions.webextensions.restrictedDomains`.
- Tridactyl can't run on about:\*, some file:\* URIs, view-source:\*, or data:\*, URIs. - Tridactyl can't run on about:\*, some file:\* URIs, view-source:\*, or data:\*, URIs.
- To change/hide the GUI of Firefox from Tridactyl, you can use [[guiset]] - To change/hide the GUI of Firefox from Tridactyl, you can use [[guiset]] with the native messenger installed (see [[native]] and [[installnative]]). Alternatively, you can edit your userChrome yourself. There is an [example file](2) available in our repository.
with the native messenger installed (see [[native]] and
[[installnative]]). Alternatively, you can edit your userChrome yourself. ## Getting help
There is an [example file](2) available in our repository.
For more information, and FAQs, check out our [readme][3] on github.
Tridactyl is in a pretty early stage of development. Please report any issues and make requests for missing features on the GitHub [project page][1]. You can also get in touch using Matrix, Gitter, or IRC chat clients:
[![Matrix Chat][matrix-badge]][matrix-link]
[![Gitter Chat][gitter-badge]][gitter-link]
[![Freenode Chat][freenode-badge]][freenode-link]
All three channels are mirrored together, so it doesn't matter which one you use.
[1]: https://github.com/cmcaine/tridactyl/issues [1]: https://github.com/cmcaine/tridactyl/issues
[2]: https://github.com/cmcaine/tridactyl/blob/master/src/static/userChrome-minimal.css [2]: https://github.com/cmcaine/tridactyl/blob/master/src/static/userChrome-minimal.css
@ -221,13 +196,13 @@ export async function editor() {
} }
/** /**
* Behaves like readline's [delete_char](http://web.mit.edu/gnu/doc/html/rlman_1.html#SEC14). * Behaves like readline's [delete_char](http://web.mit.edu/gnu/doc/html/rlman_1.html#SEC14), i.e. deletes the character to the right of the caret.
**/ **/
//#content //#content
export function im_delete_char() { export function im_delete_char() {
let elem = DOM.getLastUsedInput() as HTMLInputElement let elem = DOM.getLastUsedInput() as HTMLInputElement
let pos = elem.selectionStart let pos = elem.selectionStart
// Abort if we can't find out where the cursor is // Abort if we can't find out where the caret is
if (pos === undefined || pos === null) { if (pos === undefined || pos === null) {
logger.warning("im_delete_char: elem doesn't have a selectionStart") logger.warning("im_delete_char: elem doesn't have a selectionStart")
return return
@ -245,15 +220,15 @@ export function im_delete_char() {
} }
/** /**
* Behaves like readline's [delete_backward_char](http://web.mit.edu/gnu/doc/html/rlman_1.html#SEC14). * Behaves like readline's [delete_backward_char](http://web.mit.edu/gnu/doc/html/rlman_1.html#SEC14), i.e. deletes the character to the left of the caret.
**/ **/
//#content //#content
export function im_delete_backward_char() { export function im_delete_backward_char() {
let elem = DOM.getLastUsedInput() as HTMLInputElement let elem = DOM.getLastUsedInput() as HTMLInputElement
let pos = elem.selectionStart let pos = elem.selectionStart
// Abort if we can't find out where the cursor is or if it is at the beginning of the text // Abort if we can't find out where the caret is or if it is at the beginning of the text
if (!pos) { if (!pos) {
logger.warning("im_delete_backward_char: elem doesn't have a selectionStart or cursor is at beginning of line.") logger.warning("im_delete_backward_char: elem doesn't have a selectionStart or caret is at beginning of line.")
return return
} }
let text = getInput(elem) let text = getInput(elem)
@ -268,7 +243,7 @@ export function im_delete_backward_char() {
} }
/** /**
* Behaves like readline's [tab_insert](http://web.mit.edu/gnu/doc/html/rlman_1.html#SEC14). * Behaves like readline's [tab_insert](http://web.mit.edu/gnu/doc/html/rlman_1.html#SEC14), i.e. inserts a tab character to the left of the caret.
**/ **/
//#content //#content
export function im_tab_insert() { export function im_tab_insert() {
@ -289,7 +264,7 @@ export function im_tab_insert() {
} }
/** /**
* Behaves like readline's [transpose_chars](http://web.mit.edu/gnu/doc/html/rlman_1.html#SEC14). * Behaves like readline's [transpose_chars](http://web.mit.edu/gnu/doc/html/rlman_1.html#SEC14), i.e. transposes the character to the left of the caret with the character to the right of the caret and then moves the caret one character to the right. If there are no characters to the right or to the left of the caret, uses the two characters the closest to the caret.
**/ **/
//#content //#content
export function im_transpose_chars() { export function im_transpose_chars() {
@ -310,7 +285,7 @@ export function im_transpose_chars() {
} }
/** @hidden /** @hidden
* Detects the boundaries of a word in text according to the wordpattern setting. If POSITION is in a word, the boundaries of this word are returned. If POSITION is out of a word and BEFORE is true, the word before POSITION is returned. If BEFORE is false, the word after the cursor is returned. * Detects the boundaries of a word in text according to the wordpattern setting. If POSITION is in a word, the boundaries of this word are returned. If POSITION is out of a word and BEFORE is true, the word before POSITION is returned. If BEFORE is false, the word after the caret is returned.
*/ */
//#content_helper //#content_helper
export function getWordBoundaries(text: string, position: number, before: boolean): [number, number] { export function getWordBoundaries(text: string, position: number, before: boolean): [number, number] {
@ -318,7 +293,7 @@ export function getWordBoundaries(text: string, position: number, before: boolea
let pattern = new RegExp(config.get("wordpattern"), "g") let pattern = new RegExp(config.get("wordpattern"), "g")
let boundary1 = position < text.length ? position : text.length - 1 let boundary1 = position < text.length ? position : text.length - 1
let direction = before ? -1 : 1 let direction = before ? -1 : 1
// if the cursor is not in a word, try to find the word before or after it // if the caret is not in a word, try to find the word before or after it
while (boundary1 >= 0 && boundary1 < text.length && !text[boundary1].match(pattern)) { while (boundary1 >= 0 && boundary1 < text.length && !text[boundary1].match(pattern)) {
boundary1 += direction boundary1 += direction
} }
@ -339,7 +314,7 @@ export function getWordBoundaries(text: string, position: number, before: boolea
throw new Error(`getWordBoundaries: no characters matching wordpattern (${pattern.source}) in text (${text})`) throw new Error(`getWordBoundaries: no characters matching wordpattern (${pattern.source}) in text (${text})`)
} }
// now that we know the cursor is in a word (it could be in the middle depending on POSITION!), try to find its beginning/end // now that we know the caret is in a word (it could be in the middle depending on POSITION!), try to find its beginning/end
while (boundary1 >= 0 && boundary1 < text.length && !!text[boundary1].match(pattern)) { while (boundary1 >= 0 && boundary1 < text.length && !!text[boundary1].match(pattern)) {
boundary1 += direction boundary1 += direction
} }
@ -347,7 +322,7 @@ export function getWordBoundaries(text: string, position: number, before: boolea
boundary1 -= direction boundary1 -= direction
let boundary2 = boundary1 let boundary2 = boundary1
// now that we know the cursor is at the beginning/end of a word, we need to find the other boundary // now that we know the caret is at the beginning/end of a word, we need to find the other boundary
while (boundary2 >= 0 && boundary2 < text.length && !!text[boundary2].match(pattern)) { while (boundary2 >= 0 && boundary2 < text.length && !!text[boundary2].match(pattern)) {
boundary2 -= direction boundary2 -= direction
} }
@ -376,7 +351,7 @@ export function wordAfterPos(text: string, position: number) {
} }
/** /**
* Behaves like readline's [transpose_words](http://web.mit.edu/gnu/doc/html/rlman_1.html#SEC14). * Behaves like readline's [transpose_words](http://web.mit.edu/gnu/doc/html/rlman_1.html#SEC14). Basically equivalent to [[im_transpose_chars]], but using words as defined by the wordpattern setting.
**/ **/
//#content //#content
export function im_transpose_words() { export function im_transpose_words() {
@ -388,14 +363,14 @@ export function im_transpose_words() {
} }
let text = getInput(elem) let text = getInput(elem)
if (text.length == 0) return if (text.length == 0) return
// If the cursor is at the end of the text, move it just before the last character // If the caret is at the end of the text, move it just before the last character
if (pos >= text.length) { if (pos >= text.length) {
pos = text.length - 1 pos = text.length - 1
} }
// Find the word the cursor is in // Find the word the caret is in
let firstBoundaries = getWordBoundaries(text, pos, false) let firstBoundaries = getWordBoundaries(text, pos, false)
let secondBoundaries = firstBoundaries let secondBoundaries = firstBoundaries
// If there is a word after the word the cursor is in, use it for the transposition, otherwise use the word before it // If there is a word after the word the caret is in, use it for the transposition, otherwise use the word before it
let nextWord = wordAfterPos(text, firstBoundaries[1]) let nextWord = wordAfterPos(text, firstBoundaries[1])
if (nextWord > -1) { if (nextWord > -1) {
secondBoundaries = getWordBoundaries(text, nextWord, false) secondBoundaries = getWordBoundaries(text, nextWord, false)
@ -407,12 +382,12 @@ export function im_transpose_words() {
let beginning = text.substring(0, firstBoundaries[0]) + secondWord + text.substring(firstBoundaries[1], secondBoundaries[0]) let beginning = text.substring(0, firstBoundaries[0]) + secondWord + text.substring(firstBoundaries[1], secondBoundaries[0])
pos = beginning.length pos = beginning.length
fillinput(DOM.getSelector(elem), beginning + firstWord + text.substring(secondBoundaries[1])) fillinput(DOM.getSelector(elem), beginning + firstWord + text.substring(secondBoundaries[1]))
// Move cursor just before the word that was transposed // Move caret just before the word that was transposed
elem.selectionStart = elem.selectionEnd = pos elem.selectionStart = elem.selectionEnd = pos
} }
/** @hidden /** @hidden
* Applies a function to the word the cursor is in, or to the next word if the cursor is not in a word, or to the previous word if the current word is empty. * Applies a function to the word the caret is in, or to the next word if the caret is not in a word, or to the previous word if the current word is empty.
*/ */
//#content_helper //#content_helper
function applyWord(fn: (string) => string) { function applyWord(fn: (string) => string) {
@ -424,7 +399,7 @@ function applyWord(fn: (string) => string) {
} }
let text = getInput(elem) let text = getInput(elem)
if (text.length == 0) return if (text.length == 0) return
// If the cursor is at the end of the text, move it just before the last character // If the caret is at the end of the text, move it just before the last character
if (pos >= text.length) { if (pos >= text.length) {
pos = text.length - 1 pos = text.length - 1
} }
@ -435,7 +410,7 @@ function applyWord(fn: (string) => string) {
} }
/** /**
* Behaves like readline's [upcase_word](http://web.mit.edu/gnu/doc/html/rlman_1.html#SEC14). * Behaves like readline's [upcase_word](http://web.mit.edu/gnu/doc/html/rlman_1.html#SEC14). Makes the word the caret is in uppercase.
**/ **/
//#content //#content
export function im_upcase_word() { export function im_upcase_word() {
@ -443,7 +418,7 @@ export function im_upcase_word() {
} }
/** /**
* Behaves like readline's [downcase_word](http://web.mit.edu/gnu/doc/html/rlman_1.html#SEC14). * Behaves like readline's [downcase_word](http://web.mit.edu/gnu/doc/html/rlman_1.html#SEC14). Makes the word the caret is in lowercase.
**/ **/
//#content //#content
export function im_downcase_word() { export function im_downcase_word() {
@ -451,7 +426,7 @@ export function im_downcase_word() {
} }
/** /**
* Behaves like readline's [capitalize_word](http://web.mit.edu/gnu/doc/html/rlman_1.html#SEC14). * Behaves like readline's [capitalize_word](http://web.mit.edu/gnu/doc/html/rlman_1.html#SEC14). Makes the initial character of the word the caret is in uppercase.
**/ **/
//#content //#content
export function im_capitalize_word() { export function im_capitalize_word() {
@ -459,7 +434,7 @@ export function im_capitalize_word() {
} }
/** /**
* Behaves like readline's [kill_line](http://web.mit.edu/gnu/doc/html/rlman_1.html#SEC15). * Behaves like readline's [kill_line](http://web.mit.edu/gnu/doc/html/rlman_1.html#SEC15), i.e. deletes every character to the right of the caret until reaching either the end of the text or the newline character (\n).
**/ **/
//#content //#content
export function im_kill_line() { export function im_kill_line() {
@ -473,7 +448,7 @@ export function im_kill_line() {
if (text.length == 0) return if (text.length == 0) return
let newLine = text.substring(pos).search("\n") let newLine = text.substring(pos).search("\n")
if (newLine != -1) { if (newLine != -1) {
// If the cursor is right before the newline, kill the newline // If the caret is right before the newline, kill the newline
if (newLine == 0) newLine = 1 if (newLine == 0) newLine = 1
text = text.substring(0, pos) + text.substring(pos + newLine) text = text.substring(0, pos) + text.substring(pos + newLine)
} else { } else {
@ -484,7 +459,7 @@ export function im_kill_line() {
} }
/** /**
* Behaves like readline's [backward_kill_line](http://web.mit.edu/gnu/doc/html/rlman_1.html#SEC15). * Behaves like readline's [backward_kill_line](http://web.mit.edu/gnu/doc/html/rlman_1.html#SEC15), i.e. deletes every character to the left of the caret until either the beginning of the text is found or a newline character ("\n") is reached.
**/ **/
//#content //#content
export function im_backward_kill_line() { export function im_backward_kill_line() {
@ -496,7 +471,7 @@ export function im_backward_kill_line() {
} }
let text = getInput(elem) let text = getInput(elem)
if (text.length == 0) return if (text.length == 0) return
// If the cursor is at the beginning of a line, join the lines // If the caret is at the beginning of a line, join the lines
if (text[pos - 1] == "\n") { if (text[pos - 1] == "\n") {
fillinput(DOM.getSelector(elem), text.substring(0, pos - 1) + text.substring(pos)) fillinput(DOM.getSelector(elem), text.substring(0, pos - 1) + text.substring(pos))
elem.selectionStart = elem.selectionEnd = pos - 1 elem.selectionStart = elem.selectionEnd = pos - 1
@ -504,14 +479,14 @@ export function im_backward_kill_line() {
let newLine let newLine
// Find the closest newline // Find the closest newline
for (newLine = pos; newLine > 0 && text[newLine - 1] != "\n"; --newLine) {} for (newLine = pos; newLine > 0 && text[newLine - 1] != "\n"; --newLine) {}
// Remove everything between the newline and the cursor // Remove everything between the newline and the caret
fillinput(DOM.getSelector(elem), text.substring(0, newLine) + text.substring(pos)) fillinput(DOM.getSelector(elem), text.substring(0, newLine) + text.substring(pos))
elem.selectionStart = elem.selectionEnd = newLine elem.selectionStart = elem.selectionEnd = newLine
} }
} }
/** /**
* Behaves like readline's [kill_whole_line](http://web.mit.edu/gnu/doc/html/rlman_1.html#SEC15). * Behaves like readline's [kill_whole_line](http://web.mit.edu/gnu/doc/html/rlman_1.html#SEC15). Deletes every character between the two newlines the caret is in. If a newline can't be found on the left of the caret, everything is deleted until the beginning of the text is reached. If a newline can't be found on the right, everything is deleted until the end of the text is found.
**/ **/
//#content //#content
export function im_kill_whole_line() { export function im_kill_whole_line() {
@ -524,17 +499,17 @@ export function im_kill_whole_line() {
let text = getInput(elem) let text = getInput(elem)
if (text.length == 0) return if (text.length == 0) return
let firstNewLine, secondNewLine let firstNewLine, secondNewLine
// Find the newline before the cursor // Find the newline before the caret
for (firstNewLine = pos; firstNewLine > 0 && text[firstNewLine - 1] != "\n"; --firstNewLine) {} for (firstNewLine = pos; firstNewLine > 0 && text[firstNewLine - 1] != "\n"; --firstNewLine) {}
// Find the newline after the cursor // Find the newline after the caret
for (secondNewLine = pos; secondNewLine < text.length && text[secondNewLine - 1] != "\n"; ++secondNewLine) {} for (secondNewLine = pos; secondNewLine < text.length && text[secondNewLine - 1] != "\n"; ++secondNewLine) {}
// Remove everything between the newline and the cursor // Remove everything between the newline and the caret
fillinput(DOM.getSelector(elem), text.substring(0, firstNewLine) + text.substring(secondNewLine)) fillinput(DOM.getSelector(elem), text.substring(0, firstNewLine) + text.substring(secondNewLine))
elem.selectionStart = elem.selectionEnd = firstNewLine elem.selectionStart = elem.selectionEnd = firstNewLine
} }
/** /**
* Behaves like readline's [kill_word](http://web.mit.edu/gnu/doc/html/rlman_1.html#SEC15). * Behaves like readline's [kill_word](http://web.mit.edu/gnu/doc/html/rlman_1.html#SEC15). Deletes every character from the caret to the end of a word, with words being defined by the wordpattern setting.
**/ **/
//#content //#content
export function im_kill_word() { export function im_kill_word() {
@ -548,13 +523,13 @@ export function im_kill_word() {
if (text.length == 0) return if (text.length == 0) return
let boundaries = getWordBoundaries(text, pos, false) let boundaries = getWordBoundaries(text, pos, false)
if (pos > boundaries[0] && pos < boundaries[1]) boundaries[0] = pos if (pos > boundaries[0] && pos < boundaries[1]) boundaries[0] = pos
// Remove everything between the newline and the cursor // Remove everything between the newline and the caret
fillinput(DOM.getSelector(elem), text.substring(0, boundaries[0]) + text.substring(boundaries[1] + 1)) fillinput(DOM.getSelector(elem), text.substring(0, boundaries[0]) + text.substring(boundaries[1] + 1))
elem.selectionStart = elem.selectionEnd = boundaries[0] elem.selectionStart = elem.selectionEnd = boundaries[0]
} }
/** /**
* Behaves like readline's [backward_kill_word](http://web.mit.edu/gnu/doc/html/rlman_1.html#SEC15). * Behaves like readline's [backward_kill_word](http://web.mit.edu/gnu/doc/html/rlman_1.html#SEC15). Deletes every character from the caret to the beginning of a word with word being defined by the wordpattern setting.
**/ **/
//#content //#content
export function im_backward_kill_word() { export function im_backward_kill_word() {
@ -568,13 +543,13 @@ export function im_backward_kill_word() {
if (text.length == 0) return if (text.length == 0) return
let boundaries = getWordBoundaries(text, pos, true) let boundaries = getWordBoundaries(text, pos, true)
if (pos > boundaries[0] && pos < boundaries[1]) boundaries[1] = pos if (pos > boundaries[0] && pos < boundaries[1]) boundaries[1] = pos
// Remove everything between the newline and the cursor // Remove everything between the newline and the caret
fillinput(DOM.getSelector(elem), text.substring(0, boundaries[0]) + text.substring(boundaries[1])) fillinput(DOM.getSelector(elem), text.substring(0, boundaries[0]) + text.substring(boundaries[1]))
elem.selectionStart = elem.selectionEnd = boundaries[0] elem.selectionStart = elem.selectionEnd = boundaries[0]
} }
/** /**
* Behaves like readline's [beginning_of_line](http://web.mit.edu/gnu/doc/html/rlman_1.html#SEC12). * Behaves like readline's [beginning_of_line](http://web.mit.edu/gnu/doc/html/rlman_1.html#SEC12). Moves the caret to the right of the first newline character found at the left of the caret. If no newline can be found, move the caret to the beginning of the text.
**/ **/
//#content //#content
export function im_beginning_of_line() { export function im_beginning_of_line() {
@ -591,7 +566,7 @@ export function im_beginning_of_line() {
} }
/** /**
* Behaves like readline's [end_of_line](http://web.mit.edu/gnu/doc/html/rlman_1.html#SEC12). * Behaves like readline's [end_of_line](http://web.mit.edu/gnu/doc/html/rlman_1.html#SEC12). Moves the caret to the left of the first newline character found at the right of the caret. If no newline can be found, move the caret to the end of the text.
**/ **/
//#content //#content
export function im_end_of_line() { export function im_end_of_line() {
@ -608,7 +583,7 @@ export function im_end_of_line() {
} }
/** /**
* Behaves like readline's [forward_char](http://web.mit.edu/gnu/doc/html/rlman_1.html#SEC12). * Behaves like readline's [forward_char](http://web.mit.edu/gnu/doc/html/rlman_1.html#SEC12). Moves the caret one character to the right.
**/ **/
//#content //#content
export function im_forward_char() { export function im_forward_char() {
@ -624,7 +599,7 @@ export function im_forward_char() {
} }
/** /**
* Behaves like readline's [backward_char](http://web.mit.edu/gnu/doc/html/rlman_1.html#SEC12). * Behaves like readline's [backward_char](http://web.mit.edu/gnu/doc/html/rlman_1.html#SEC12). Moves the caret one character to the left.
**/ **/
//#content //#content
export function im_backward_char() { export function im_backward_char() {
@ -640,7 +615,7 @@ export function im_backward_char() {
} }
/** /**
* Behaves like readline's [forward_word](http://web.mit.edu/gnu/doc/html/rlman_1.html#SEC12). * Behaves like readline's [forward_word](http://web.mit.edu/gnu/doc/html/rlman_1.html#SEC12). Moves the caret one word to the right, with words being defined by the wordpattern setting.
**/ **/
//#content //#content
export function im_forward_word() { export function im_forward_word() {
@ -658,7 +633,7 @@ export function im_forward_word() {
} }
/** /**
* Behaves like readline's [backward_word](http://web.mit.edu/gnu/doc/html/rlman_1.html#SEC12). * Behaves like readline's [backward_word](http://web.mit.edu/gnu/doc/html/rlman_1.html#SEC12). Moves the caret one word to the right, with words being defined by the wordpattern setting.
**/ **/
//#content //#content
export function im_backward_word() { export function im_backward_word() {
@ -1138,6 +1113,10 @@ export function jumpnext(n = 1) {
} }
/** Similar to Pentadactyl or vim's jump list. /** Similar to Pentadactyl or vim's jump list.
*
* When you scroll on a page, either by using the mouse or Tridactyl's key bindings, your position in the page will be saved after jumpdelay milliseconds (`:get jumpdelay` to know how many milliseconds that is). If you scroll again, you'll be able to go back to your previous position by using `:jumpprev 1`. If you need to go forward in the jumplist, use `:jumpprev -1`.
*
* Known bug: Tridactyl will use the same jumplist for multiple visits to a same website in the same tab, see [github issue 834](https://github.com/tridactyl/tridactyl/issues/834).
*/ */
//#content //#content
export function jumpprev(n = 1) { export function jumpprev(n = 1) {
@ -1447,6 +1426,7 @@ function removeSource() {
sourceElement = undefined sourceElement = undefined
} }
} }
/** Display the (HTML) source of the current page. /** Display the (HTML) source of the current page.
Behaviour can be changed by the 'viewsource' setting. Behaviour can be changed by the 'viewsource' setting.
@ -1500,43 +1480,70 @@ export function home(all: "false" | "true" = "false") {
/** Show this page. /** Show this page.
`:help something` jumps to the entry for something. Something can be an excmd, an alias for an excmd or a binding. `:help something` jumps to the entry for something. Something can be an excmd, an alias for an excmd, a binding or a setting.
The "nmaps" list is a list of all the bindings for the command you're seeing and the "exaliases" list lists all its aliases. On the ex command page, the "nmaps" list is a list of all the bindings for the command you're seeing and the "exaliases" list lists all its aliases.
If there's a conflict (e.g. you have a "go" binding that does something and also a "go" excmd that does something else), the binding has higher priority. If there's a conflict (e.g. you have a "go" binding that does something, a "go" excmd that does something else and a "go" setting that does a third thing), the binding is chosen first, then the setting, then the excmd.
If the keyword you gave to `:help` is actually an alias for a composite command (see [[composite]]) , you will be taken to the help section for the first command of the pipeline. You will be able to see the whole pipeline by hovering your mouse over the alias in the "exaliases" list. Unfortunately there currently is no way to display these HTML tooltips from the keyboard. If the keyword you gave to `:help` is actually an alias for a composite command (see [[composite]]) , you will be taken to the help section for the first command of the pipeline. You will be able to see the whole pipeline by hovering your mouse over the alias in the "exaliases" list. Unfortunately there currently is no way to display these HTML tooltips from the keyboard.
e.g. `:help bind` e.g. `:help bind`
*/ */
//#background //#background
export async function help(excmd?: string) { export async function help(helpItem?: string) {
const docpage = browser.extension.getURL("static/docs/modules/_excmds_.html") let docpage = browser.extension.getURL("static/docs/modules/_excmds_.html")
if (excmd === undefined) excmd = "" if (helpItem === undefined) helpItem = ""
else { else {
let bindings = await config.getAsync("nmaps") let matched = false
// If 'excmd' matches a binding, replace 'excmd' with the command that would be executed when pressing the key sequence referenced by 'excmd' const settings = await config.getAsync()
if (excmd in bindings) {
excmd = bindings[excmd].split(" ") for (let mode of ["nmaps", "imaps", "inputmaps", "ignoremaps"]) {
excmd = ["composite", "fillcmdline"].includes(excmd[0]) ? excmd[1] : excmd[0] let bindings = settings[mode]
// If 'helpItem' matches a binding, replace 'helpItem' with the command that would be executed when pressing the key sequence referenced by 'helpItem' and don't check other modes
if (helpItem in bindings) {
helpItem = bindings[helpItem].split(" ")
helpItem = ["composite", "fillcmdline"].includes(helpItem[0]) ? helpItem[1] : helpItem[0]
matched = true
break
}
} }
let aliases = await config.getAsync("exaliases") let aliases = settings.exaliases
// As long as excmd is an alias, try to resolve this alias to a real excmd // As long as helpItem is an alias, try to resolve this alias to a real helpItem
let resolved = [] let resolved = []
while (aliases[excmd]) { while (aliases[helpItem]) {
resolved.push(excmd) matched = true
excmd = aliases[excmd].split(" ") resolved.push(helpItem)
excmd = excmd[0] == "composite" ? excmd[1] : excmd[0] helpItem = aliases[helpItem].split(" ")
helpItem = helpItem[0] == "composite" ? helpItem[1] : helpItem[0]
// Prevent infinite loops // Prevent infinite loops
if (resolved.includes(excmd)) break if (resolved.includes(helpItem)) break
}
// If we still haven't found either a binding or an alias, try a setting name
if (!matched) {
let subSettings = settings
let settingNames = helpItem.split(".")
let settingHelpAnchor = ""
// Try to match each piece of the path, this is done so that a correct object (e.g. followpagepatterns) with an incorrect key (e.g. nextt) will still match the parent object.
for (let settingName of settingNames) {
if (settingName in subSettings) {
settingHelpAnchor += settingName + "."
subSettings = subSettings[settingName]
}
}
if (settingHelpAnchor != "") {
docpage = browser.extension.getURL("static/docs/classes/_lib_config_.default_config.html")
helpItem = settingHelpAnchor.slice(0,-1)
}
} }
} }
if ((await activeTab()).url.startsWith(docpage)) { if ((await activeTab()).url.startsWith(docpage)) {
open(docpage + "#" + excmd) open(docpage + "#" + helpItem)
} else { } else {
tabopen(docpage + "#" + excmd) tabopen(docpage + "#" + helpItem)
} }
} }
@ -3161,7 +3168,7 @@ export function seturl(pattern: string, key: string, ...values: string[]) {
/** Set a key value pair in config. /** Set a key value pair in config.
Use to set any string values found [here](/static/docs/classes/_config_.default_config.html). Use to set any string values found [here](/static/docs/classes/_lib_config_.default_config.html).
e.g. e.g.
set searchurls.google https://www.google.com/search?q= set searchurls.google https://www.google.com/search?q=

View file

@ -10,11 +10,11 @@ function initTridactylSettingElem(
let bindingNode = elem.getElementsByClassName(`Tridactyl${kind}`)[0] let bindingNode = elem.getElementsByClassName(`Tridactyl${kind}`)[0]
if (bindingNode) { if (bindingNode) {
Array.from(bindingNode.children) Array.from(bindingNode.children)
.filter(e => e.tagName == "LI") .filter(e => e.tagName == "SPAN")
.forEach(e => e.parentNode.removeChild(e)) .forEach(e => e.parentNode.removeChild(e))
} else { } else {
// Otherwise, create it // Otherwise, create it
bindingNode = document.createElement("ul") bindingNode = document.createElement("p")
bindingNode.className = `TridactylSetting Tridactyl${kind}` bindingNode.className = `TridactylSetting Tridactyl${kind}`
bindingNode.textContent = kind + ": " bindingNode.textContent = kind + ": "
elem.insertBefore(bindingNode, elem.children[2]) elem.insertBefore(bindingNode, elem.children[2])
@ -41,7 +41,7 @@ async function addSetting(settingName: string) {
// We're ignoring composite because it combines multiple excmds // We're ignoring composite because it combines multiple excmds
delete commandElems["composite"] delete commandElems["composite"]
// Initialize or reset the <ul> element that will contain settings in each commandElem // Initialize or reset the <p> element that will contain settings in each commandElem
let settingElems = Object.keys(commandElems).reduce( let settingElems = Object.keys(commandElems).reduce(
(settingElems, cmdName) => { (settingElems, cmdName) => {
settingElems[cmdName] = initTridactylSettingElem( settingElems[cmdName] = initTridactylSettingElem(
@ -71,11 +71,12 @@ async function addSetting(settingName: string) {
// If there is an HTML element for settings that correspond to the excmd we just found // If there is an HTML element for settings that correspond to the excmd we just found
if (settingElems[excmd]) { if (settingElems[excmd]) {
let settingLi = document.createElement("li") let settingSpan = document.createElement("span")
settingLi.innerText = setting settingSpan.innerText = setting
settingLi.title = settings[setting] settingSpan.title = settings[setting]
// Add the setting to the element // Add the setting to the element
settingElems[excmd].appendChild(settingLi) settingElems[excmd].appendChild(settingSpan)
settingElems[excmd].appendChild(document.createTextNode(" "))
} }
} }
@ -83,27 +84,27 @@ async function addSetting(settingName: string) {
Object.values(settingElems) Object.values(settingElems)
.filter( .filter(
(e: HTMLElement) => (e: HTMLElement) =>
!Array.from(e.children).find(c => c.tagName == "LI"), !Array.from(e.children).find(c => c.tagName == "SPAN"),
) )
.forEach((e: HTMLElement) => e.parentNode.removeChild(e)) .forEach((e: HTMLElement) => e.parentNode.removeChild(e))
} }
browser.storage.onChanged.addListener((changes, areaname) => { async function onExcmdPageLoad() {
if ("userconfig" in changes) { browser.storage.onChanged.addListener((changes, areaname) => {
// JSON.stringify for comparisons like it's 2012 if ("userconfig" in changes) {
;["nmaps", "imaps", "ignoremaps", "inputmaps", "exaliases"].forEach( // JSON.stringify for comparisons like it's 2012
kind => { ;["nmaps", "imaps", "ignoremaps", "inputmaps", "exaliases"].forEach(
if ( kind => {
JSON.stringify(changes.userconfig.newValue[kind]) != if (
JSON.stringify(changes.userconfig.oldValue[kind]) JSON.stringify(changes.userconfig.newValue[kind]) !=
) JSON.stringify(changes.userconfig.oldValue[kind])
addSetting(kind) )
}, addSetting(kind)
) },
} )
}) }
})
addEventListener("load", async () => {
await Promise.all( await Promise.all(
["nmaps", "imaps", "ignoremaps", "inputmaps", "exaliases"].map( ["nmaps", "imaps", "ignoremaps", "inputmaps", "exaliases"].map(
addSetting, addSetting,
@ -113,4 +114,85 @@ addEventListener("load", async () => {
if (document.location.hash) { if (document.location.hash) {
document.location.hash = document.location.hash document.location.hash = document.location.hash
} }
}) }
async function onSettingsPageLoad() {
const inputClassName = " TridactylSettingInput "
const inputClassNameModified =
inputClassName + " TridactylSettingInputModified "
let getIdForSetting = settingName => "TridactylSettingInput_" + settingName
browser.storage.onChanged.addListener((changes, areaname) => {
if (!("userconfig" in changes)) return
Object.keys(changes.userconfig.newValue).forEach(key => {
let elem = document.getElementById(
getIdForSetting(key),
) as HTMLInputElement
if (!elem) return
elem.value = changes.userconfig.newValue[key]
elem.className = inputClassName
})
})
let onKeyUp = async ev => {
let input = ev.target
if (ev.key == "Enter") {
;(window as any).tri.messaging.message(
"commandline_background",
"recvExStr",
["set " + input.name + " " + input.value],
)
} else {
if (input.value == (await config.getAsync(input.name.split(".")))) {
input.className = inputClassName
} else {
input.className = inputClassNameModified
}
}
}
Promise.all(Array.from(document.querySelectorAll("a.tsd-anchor")).map(
async (a: HTMLAnchorElement) => {
let section = a.parentNode
let settingName = a.name.split(".")
let value = await config.getAsync(settingName)
if (!value) return console.log("Failed to grab value of ", a)
if (!["number", "boolean", "string"].includes(typeof value))
return console.log(
"Not embedding value of ",
a,
value,
" because not easily represented as string",
)
let input = document.createElement("input")
input.name = a.name
input.value = value
input.id = getIdForSetting(a.name)
input.className = inputClassName
input.addEventListener("keyup", onKeyUp)
let div = document.createElement("div")
div.appendChild(document.createTextNode("Current value:"))
div.appendChild(input)
section.appendChild(div)
},
// Adding elements expands sections so if the user wants to see a specific hash, we need to focus it again
)).then(_ => { if (document.location.hash) { document.location.hash = document.location.hash } })
}
addEventListener(
"load",
(() => {
switch (document.location.pathname) {
case "/static/docs/modules/_excmds_.html":
return onExcmdPageLoad
case "/static/docs/classes/_lib_config_.default_config.html":
return onSettingsPageLoad
}
return () => {}
})(),
)

View file

@ -45,8 +45,7 @@ export type LoggingLevel = "never" | "error" | "warning" | "info" | "debug"
/** /**
* This is the default configuration that Tridactyl comes with. * This is the default configuration that Tridactyl comes with.
* *
* You can change anything here using `set key1.key2.key3 value` or specific things any of the various helper commands such as `bind` or `command`. * You can change anything here using `set key1.key2.key3 value` or specific things any of the various helper commands such as `bind` or `command`. You can also jump to the help section of a setting using `:help $settingname`. Some of the settings have an input field containing their current value. You can modify these values and save them by pressing `<Enter>` but using `:set $setting $value` is a good habit to take as it doesn't force you to leave the page you're visiting to change your settings.
*
*/ */
class default_config { class default_config {
/** /**
@ -746,8 +745,7 @@ function setDeepProperty(obj, value, target) {
export function mergeDeep(o1, o2) { export function mergeDeep(o1, o2) {
let r = Array.isArray(o1) ? o1.slice() : Object.create(o1) let r = Array.isArray(o1) ? o1.slice() : Object.create(o1)
Object.assign(r, o1, o2) Object.assign(r, o1, o2)
if (o2 === undefined) if (o2 === undefined) return r
return r
Object.keys(o1) Object.keys(o1)
.filter(key => typeof o1[key] == "object" && typeof o2[key] == "object") .filter(key => typeof o1[key] == "object" && typeof o2[key] == "object")
.forEach(key => Object.assign(r[key], mergeDeep(o1[key], o2[key]))) .forEach(key => Object.assign(r[key], mergeDeep(o1[key], o2[key])))

View file

@ -64,5 +64,9 @@
"id": "tridactyl.vim@cmcaine.co.uk", "id": "tridactyl.vim@cmcaine.co.uk",
"strict_min_version": "54.0" "strict_min_version": "54.0"
} }
},
"options_ui": {
"page": "static/docs/classes/_lib_config_.default_config.html",
"open_in_tab": true
} }
} }

View file

@ -241,3 +241,7 @@ a.url:hover {
width: 15%; width: 15%;
text-overflow: ellipsis; text-overflow: ellipsis;
} }
.HelpCompletionOption td.name {
max-width: 25%;
}

View file

@ -2983,18 +2983,14 @@ ul.tsd-descriptions h4 {
border-radius: 2px; border-radius: 2px;
} }
ul.TridactylSetting { .TridactylSetting {
display: flex;
flex-direction: row;
justify-content: flex-start;
list-style: none;
padding: 0; padding: 0;
white-space: nowrap;
font-weight: bold; font-weight: bold;
font-size: 10pt; font-size: 10pt;
} }
ul.TridactylSetting li { .TridactylSetting span {
display: inline-block;
font-family: Monospace; font-family: Monospace;
font-weight: normal; font-weight: normal;
background: whitesmoke; background: whitesmoke;
@ -3003,6 +2999,14 @@ ul.TridactylSetting li {
border-radius: 3px; border-radius: 3px;
} }
.TridactylSettingInput {
margin-left: 6pt;
}
.TridactylSettingInputModified {
color: blue;
}
/* ul.TridactylSetting li:not(:last-child):after { */ /* ul.TridactylSetting li:not(:last-child):after { */
/* content: ", "; */ /* content: ", "; */
/* font-weight: normal; */ /* font-weight: normal; */