CHA-API(7)

๐Ÿ“ฆ
version 0.5.0 ยท 3 versions
, in arch rolling

๐Ÿ’– CHA-API(7) โ€” Chawanโ€™s command API ๐Ÿ’–

okay bestie, welcome to Chawanโ€™s command API โ€” the CHA-API(7) page from the Miscellaneous Information Manual. This is the group-chat lore for how keybindings, JavaScript actions, and Chawanโ€™s built-in objects all vibe together. โœจ

As described in cha-config(5), users can bind keypress combinations to actions. Such an action can be either a JavaScript expression, or a command defined in the [cmd] section of config.toml.

For example, the following works:

'g p n' = 'n => pager.alert(n)' # e.g. 2gpn prints `2' to the status line

Note however, that JavaScript functions must be called with an appropriate this value. So e.g. the following does not work:

'g p n' = 'pager.alert' # broken

To work around this limitation, actions have to wrap the target function in a closure, as above. However, this has poor reusability; for more complex actions, you would have to copy and paste the entire function every time you re-bind it or call it from a different function.

To fix this, it is possible to define a command in the [cmd] section:

[cmd.me]
showNumber = 'n => pager.alert(n)'

me can be anything you want; it exists only to avoid collisions with built-in commands. The only restriction is that the first component (in this case, โ€œmeโ€) must not contain an upper-case letter.

Now you can call cmd.me.showNumber() from any other function, or include it in a keybinding (in that case, cmd. is optional):

'g p n' = 'me.showNumber'
# same as
'g p n' = 'cmd.me.showNumber'

๐Ÿงš Interfaces ๐Ÿงš

Note that there are also many private functions not documented here. Such functions have no stability guarantee and may disappear at any time, so using them is not recommended. fr fr, donโ€™t build your whole setup on them.

๐Ÿ’… Client ๐Ÿ’…

The global object (globalThis) implements the Client interface. Public functions of this are:

  • quit() โ€” Exit the browser.
  • suspend() โ€” Temporarily suspend the browser, by delivering the client process a SIGTSTP signal. Note: this suspends all processes, including buffers, the loader and CGI.
  • readFile(path) โ€” Read a file at path. Returns the fileโ€™s content as a string, or null if the file does not exist.
  • writeFile(path, content) โ€” Write content to the file at path. Throws a TypeError if this failed for whatever reason.
  • getenv(name, fallback = null) โ€” Get an environment variable by name. Returns fallback if the variable does not exist.
  • setenv(name, value) โ€” Set an environment variable by name. Throws a TypeError if the operation failed (e.g. because the variableโ€™s size exceeded an OS-specified limit.)
  • pager โ€” The pager object. Implements Pager, as described below.
  • line โ€” The line editor. Implements LineEdit, as described below.
  • buffer โ€” The currently focused buffer. Implements Buffer, as described below.
  • select โ€” The currently focused select menu. Implements Select, as described below.
  • config โ€” The config object. A currently incomplete interface for retrieving and setting configuration options. In general, names are the same as in config.toml, except all - (ASCII hyphen) characters are stripped and the next character is upper-cased. e.g. external.cgi-dir can be queried as config.external.cgiDir, etc. Setting individual options sometimes works, but sometimes they do not get propagated as expected. Consider this an experimental API. Currently, siteconf and omnirule values are not exposed to JS. The configuration directory itself can be queried as config.dir.

Client is derived from Window, so it indirectly implements various web standards normally available on the Window object on websites, e.g. fetch(). However, it does not give access to JS objects in buffers, so for example, globalThis.document always returns a blank document.

๐ŸŒˆ Pager ๐ŸŒˆ

Pager is a separate interface from Client that gives access to the pager (i.e. browser chrome). It is accessible as globalThis.pager, or simply pager.

Note: there is a quirk of questionable value where some (but not all) functions defined on Buffer are also defined on Pager. For example, pager.url dispatches to buffer.url. This quirk is deprecated and may be removed in the future; users are encouraged to use the buffer variable directly. lowkey, just use buffer.

Following properties (functions/getters) are defined by Pager:

  • load(url = buffer.url) โ€” Opens a prompt to load a resource, with url pre-filled.
  • loadSubmit(url) โ€” Act as if url had been entered to the URL bar. loadSubmit differs from gotoURL in that it also evaluates omni-rules, tries to prepend a scheme for URLs without one/local files, etc.
  • gotoURL(url, options = {replace: null, contentType: null, save: false, charset: null}) โ€” Go to the specified URL immediately (without a prompt). This differs from loadSubmit in that it loads the exact URL as passed (no prepending https, etc.)
    • When replace is set to a Buffer object, the new buffer will replace the old one if it loads successfully.
    • When contentType is set, the new bufferโ€™s content type is forcibly set to that string.
    • When save is true, the user is prompted to save the resource instead of displaying it in a buffer.
    • When charset is not null, the specified charset label is forced instead of regular charset detection.
  • traverse(dir) โ€” Switch to the next buffer in direction dir, interpreted as in Buffer#find.
  • nextBuffer(), prevBuffer() โ€” Same as traverse("next") and traverse("prev").
  • dupeBuffer() โ€” Duplicate the current buffer by loading its source in a new buffer.
  • discardBuffer(buffer = globalThis.buffer, dir = pager.navDirection) โ€” Discard buffer, then move back to the buffer opposite to dir (interpreted as in Buffer#find).
  • discardTree() โ€” Discard all subsequent siblings of the current buffer. This function is deprecated, and may be removed in the future.
  • addTab(target) โ€” Open a new tab. If target is a buffer, it is removed from its current tab and added to the newly created tab. Otherwise, target is interpreted as a URL to open with gotoURL.
  • prevTab(), nextTab() โ€” Switch to the previous/next tab in the tab list.
  • discardTab() โ€” Discard the current tab.
  • reload() โ€” Open a new buffer with the current bufferโ€™s URL, replacing the current buffer.
  • reshape() โ€” Reshape the current buffer (=render the current page anew.)
  • redraw() โ€” Redraw screen contents. Useful if something messed up the display.
  • toggleSource() โ€” If viewing an HTML buffer, open a new buffer with its source. Otherwise, open the current bufferโ€™s contents as HTML.
  • lineInfo() โ€” Display information about the current line.
  • searchForward(), searchBackward() โ€” Search forward/backward for a string in the current buffer.
  • isearchForward(), isearchBackward() โ€” Incremental-search forward/backward for a string, highlighting the first result.
  • gotoLine(n?) โ€” Go to the line passed as the first argument. If no arguments were specified, an input window for entering a line is shown.
  • searchNext(n = 1), searchPrev(n = 1) โ€” Jump to the nth next/previous search result.
  • peek() โ€” Display an alert message of the current URL.
  • peekCursor() โ€” Display an alert message of the URL or title under the cursor. Multiple calls allow cycling through the two. (i.e. by default, press u once -> title, press again -> URL)
  • showFullAlert() โ€” Show the last alert inside the line editor.
  • ask(prompt) โ€” Ask the user for confirmation. Returns a promise which resolves to a boolean value indicating whether the user responded with yes. Can be used to implement an exit prompt like this:
q = 'pager.ask("Do you want to exit Chawan?").then(x => x && pager.quit())'
  • askChar(prompt) โ€” Ask the user for any character. Like pager.ask, but the return value is a character, and (y/n) is not appended to the prompt.
  • clipboardWrite(s) โ€” Write s to the clipboard (copy). By default, it tries using OSC 52; if that fails, it tries to run external.copy-cmd (defaults to xsel). Returns true if the copy succeeded, false otherwise. (There may be false positives in case OSC 52 is used and the terminal doesnโ€™t consume the text, although Chawan will try its best to avoid this.)
  • extern(cmd, options = {env: { ... }, suspend: true, wait: false}) โ€” Run an external command cmd. By default, the $CHA_URL and $CHA_CHARSET variables are set; change this using the env option. options.suspend suspends the pager while the command is being executed, and options.wait makes it so the user must press a key before the pager is resumed. Returns true if the command exited successfully, false otherwise.
  • externCapture(cmd) โ€” Like extern(), but redirect the commandโ€™s stdout string into the result. null is returned if the command wasnโ€™t executed successfully, or if the command returned a non-zero exit value.
  • externInto(cmd, ins) โ€” Like extern(), but redirect ins into the commandโ€™s standard input stream. true is returned if the command exits successfully, false otherwise.
  • externFilterSource(cmd, buffer = null, contentType = null) โ€” Redirects the specified (or if buffer is null, the current) bufferโ€™s source into cmd. Then, it pipes the output into a new buffer, with the content type contentType (or, if contentType is null, the original bufferโ€™s content type). Returns undefined. (It should return a promise; TODO.)
  • openEditor(text) โ€” Open โ€œtextโ€ in the command configured as external.editor (this is typically just $EDITOR.) If the editor signals an error (crash or non-zero exit code), null is returned. Otherwise, the userโ€™s input is returned as a string.
  • openMenu(x?, y?) โ€” Opens the context menu at the specified position.
  • openBufferMenu(x?, y?) โ€” Opens the buffer selection menu at the specified position.
  • openCustomMenu(name, init = {x: buffer.acursorx, y: buffer.acursory}) โ€” pops open a custom menu right where you tell it to ๐Ÿ’… name is the menu you defined in menu.js, and init is an object with the optional fields x/y (both numbers). Peep the Menu section for the deets.
๐Ÿšจ ok so tea: an older version of this function took init as the first parameter and read name out of the object. That still works, but it's deprecated and is getting yeeted in the future. Don't build new stuff on it, bestie.
  • closeMenu() โ€” closes the menu if it's open. bye bestie ๐Ÿ‘‹
  • buffer โ€” same thing as globalThis.buffer (and you can grab that without typing globalThis, i.e. just buffer). but likeโ€ฆ use that one instead, ok? ๐Ÿซถ
  • menu โ€” getter for the menu that's currently on screen. Returns a Select object.
  • navDirection โ€” the direction you last moved in the buffer list using traverse. possible values are prev, next, any โœจ
  • revDirection โ€” literally just Pager.oppositeDir(pager.navDirection). same thing, different font ๐Ÿ’œ

And also, there's this static function chillin' on Pager itself:

  • Pager.oppositeDir(dir) โ€” returns a string for the direction opposite to dir. For "next" it's "prev", for "prev" it's "next", and for "any" it'sโ€ฆ the same. iconic non-behavior ๐ŸŒˆ

๐Ÿฆ‹ Buffer

Every buffer gets exposed as an object implementing the Buffer interface. Want a reference to the buffer that's currently on screen? Just grab buffer. easy ๐Ÿ“

Heads up: remember that deprecated quirk we mentioned earlier, where Pager dispatches some properties onto the current buffer? yeah. still a thing. ๐Ÿ™ˆ

These properties (functions/getters) are defined by Buffer:

  • cursorUp(n = 1), cursorDown(n = 1) โ€” moves the cursor up/down by n lines, or by 1 if you don't say n.
  • cursorLeft(n = 1), cursorRight(n = 1) โ€” moves the cursor left/right by n cells, or by 1 if n is unspecified.
๐Ÿซ  note: n currently means cells, but honestly it should mean characters. (the difference being that a numbered cursorLeft or cursorRight is currently broken for double-width chars. yes really. it's giving bug.)
  • cursorLineBegin(), cursorLineEnd() โ€” moves the cursor to the first/last cell of the line.
  • cursorLineTextStart() โ€” moves the cursor to the first non-blank character of the line.
  • cursorNextWord(), cursorNextViWord(), cursorNextBigWord() โ€” moves the cursor to the beginning of the next word.
  • cursorPrevWord(), cursorPrevViWord(), cursorPrevBigWord() โ€” moves the cursor to the end of the previous word.
  • cursorWordEnd(), cursorViWordEnd(), cursorBigWordEnd() โ€” moves the cursor to the end of the current word, or if it's already there, to the end of the next word.
  • cursorWordBegin(), cursorViWordBegin(), cursorBigWordBegin() โ€” moves the cursor to the beginning of the current word, or if it's already there, to the end of the previous word.
  • async getCurrentWord(x = this.cursorx, y = this.cursory) โ€” returns the word currently under the cursor as a string.
  • cursorNextLink(), cursorPrevLink() โ€” moves the cursor to the beginning of the next/previous clickable element.
  • cursorLinkNavDown(n = 1), cursorLinkNavUp(n = 1) โ€” moves the cursor to the beginning of the *n*th next/previous clickable element. the buffer scrolls pagewise, and wraps to the beginning/end if the content is less than one page long ๐Ÿ”ฅ
  • cursorNextParagraph(n = 1), cursorPrevParagraph(n = 1) โ€” moves the cursor to the beginning/end of the *n*th next/previous paragraph.
  • cursorNthLink(n = 1) โ€” moves the cursor to the *n*th link of the document.
  • cursorRevNthLink(n = 1) โ€” moves the cursor to the *n*th link of the document, but counting backwards from the document's last line. reverse girlie ๐Ÿ’…
  • pageUp(n = 1), pageDown(n = 1), pageLeft(n = 1), pageRight(n = 1) โ€” scroll up/down/left/right by n pages.
  • halfPageUp(n = 1), halfPageDown(n = 1), halfPageLeft(n = 1), halfPageRight(n = 1) โ€” scroll up/down/left/right by n half pages. half the drama, same energy ๐Ÿ’€
  • scrollUp(n = 1), scrollDown(n = 1), scrollLeft(n = 1), scrollRight(n = 1) โ€” scroll up/down/left/right by n lines.
  • click(n = 1) โ€” clicks the HTML element currently under the cursor. n controls the number of clicks, e.g. n = 2 is a double click. (the click count is only relevant in JS apps, fyi ๐Ÿ‘€)
  • cursorFirstLine(), cursorLastLine() โ€” moves to the first/last line in the buffer.
  • cursorTop(), cursorMiddle(), cursorBottom() โ€” moves to the first/middle/bottom line on the screen. (equivalent to H/M/L in vi, iykyk ๐ŸŒธ)
  • lowerPage(n = this.cursory) โ€” moves the cursor to line n, then scrolls up so the cursor sits on the top line of the screen. (zt in vim.)
  • lowerPageBegin(n = this.cursory) โ€” moves the cursor to the first non-blank character of line n, then scrolls up so the cursor sits on the top line of the screen. (z<CR> in vi.)
  • centerLine(n = this.cursory) โ€” centers the screen around line n. (zz in vim.) ๐ŸŽฏ
  • centerLineBegin(n = this.cursory) โ€” centers the screen around line n, and moves the cursor to that line's first non-blank character. (z. in vi.)
  • raisePage(n = this.cursory) โ€” moves the cursor to line n, then scrolls down so the cursor sits on the top line of the screen. (zb in vim.)
  • raisePageBegin(n = this.cursory) โ€” moves the cursor to the first non-blank character of line n, then scrolls up so the cursor sits on the last line of the screen. (z^ in vi.)
  • nextPageBegin(n = this.cursory) โ€” if n was given, moves to the screen before the *n*th line and raises the page. otherwise, goes to the previous screen's last line and raises the page. (z+ in vi.)
  • cursorLeftEdge(), cursorMiddleColumn(), cursorRightEdge() โ€” moves to the first/middle/last column on the screen.
  • centerColumn() โ€” centers the screen around the current column. symmetrical slay โœจ
  • findPrevMatch(regex, x, y, wrap = false, n = 1), findNextMatch(regex, x, y, wrap = false, n = 1) โ€” finds the previous/next match for a regex. regex is a RegExp object (like, from the /this syntax/ thing). x and y are the starting position in the buffer, wrap decides whether the search should wrap over the document or not, and n is how many occurrences to find. Returns an array of elements [x, y, w] where x and y are the matched coordinates and w is the width of the matched text. If nothing matches, the result is [-1, -1, 0]. no match no problem ๐Ÿ˜ญ
  • findNextMark(x = this.cursorx, y = this.cursory), findPrevMark(x = this.cursorx, y = this.cursory) โ€” finds the next/previous mark after/before x, y if there is one, and returns its id (or null if none were found).
  • setMark(id, x = this.cursorx, y = this.cursory) โ€” sets a mark at (x, y) using the name id. Returns true if no other mark already exists with id. If one does already exist, it gets overridden and the function returns false. ๐Ÿ“Œ
  • clearMark(id) โ€” clears the mark with the name id. Returns true if the mark existed, false otherwise.
  • gotoMark(id) โ€” if the mark id exists, jumps to its position and returns true. otherwise does nothing and returns false.
  • gotoMarkY(id) โ€” if the mark id exists, jumps to the beginning of the line at its Y position and returns true. otherwise does nothing and returns false.
  • getMarkPos(id) โ€” if the mark id exists, returns its position as an array where element zero is the X position and element one is the Y position. otherwise returns null. ๐Ÿงš
  • cursorToggleSelection(n = 1, opts = {selectionType: "normal"}) โ€” starts a vim-style visual selection. the cursor gets moved to the right by n cells. selectionType may be "normal" (regular selection), "line" (line-based selection) and "column" (column-based selection). pick your fighter ๐Ÿ’…
  • getSelectionText() โ€” gets the currently selected text. returns a promise, so consumers must await it to actually get the text. no cap, don't forget the await ๐Ÿ™
  • markURL() โ€” converts URL-like strings to anchors on the current page.
  • showLinkHints() โ€” displays link hints on the page. mainly intended for the built-in toggleLinkHints command. returns an array of objects with x representing the x position and y the y position of a link. ๐Ÿ”
  • toggleImages() โ€” toggles display of images in this buffer.
  • saveLink() โ€” saves the URL pointed to by the cursor.
  • saveSource() โ€” saves the source of this buffer. ๐Ÿ’พ
  • setCursorX(x), setCursorY(y), setCursorXY(x, y), setCursorXCenter(x), setCursorYCenter(y), setCursorXYCenter(x, y) โ€” sets the cursor position to x and y respectively, scrolling the view if necessary. the variants ending in "Center" also center the screen around the position if it's outside the screen. bougie but useful ๐Ÿ‘‘
  • setFromX(x), setFromY(y), setFromXY(x, y) โ€” sets the starting position of the displayed area on the screen.
  • find(dir) โ€” finds the next buffer in the list in a specific direction. possible values of dir are "prev", "next", and "any". "next" and "prev" return the next/previous buffer respectively, while "any" returns either "prev", or if it's null, "next". chaotic but valid ๐ŸŒˆ
  • url โ€” getter for the buffer's URL.
โš ๏ธ note: this returns a URL object, not a string. do not come for me when .toUpperCase() doesn't exist on it, bestie ๐Ÿ’€
  • hoverTitle, hoverLink, hoverImage โ€” getter for the string representation of the element title/link/image currently under the cursor. returns the empty string if no title is found.
  • cursorx, cursory โ€” the x/y position of the cursor inside the buffer.
fun lil gotcha: the status line display is 1-indexed, but these values are 0-indexed (i.e. cursory = 0 is the first line). so if your math is off by one, that's why ๐Ÿซ 
  • fromx, fromy โ€” the x/y position of the first line displayed on the screen.
  • acursorx, acursory โ€” the absolute x/y position of the cursor on the screen. same as buffer.cursorx - buffer.fromx and buffer.cursory - buffer.fromy. the math girlies ate ๐Ÿงฎ
  • numLines โ€” the number of lines currently loaded in the buffer.
  • width, height โ€” the width and height of the buffer's window (i.e. the visible part of the canvas). ๐Ÿ“
  • process โ€” the process ID of the buffer.
  • title โ€” text from the title element, or the buffer's URL if there's no title.
  • next โ€” next buffer in the buffer list. may be null.
  • prev โ€” previous buffer in the buffer list. may be null.
  • select โ€” reference to the current select elementโ€™s widget, or null if no select element is open. This object implements the Select interface, which is somewhat compatible with the Buffer interface with some exceptions. (TODO: elaborate)

๐Ÿ’… Select

The currently focused select menu widget can be retrieved using globalThis.select (or, normally, just select). slay.

A select widget โ€œlineโ€ is defined to include both options and separator lines. Keep that in mind, bestie โ€” it matters for the cursor stuff.

Following properties (functions/getters) are defined by Select:

  • click() โ€” Submit the widget with the selected option. go go go.
  • cancel() โ€” Close the widget without submitting anything.
  • cursorFirstLine(), cursorLastLine() โ€” Move to the first/last selectable option.
  • cursorTop(), cursorMiddle(), cursorBottom() โ€” Move to the top/middle/bottom visible option on the screen.
  • cursorUp(n = 1), cursorDown(n = 1) โ€” Move the cursor up/down by n options. Separator lines are skipped.
  • scrollUp(n = 1), scrollDown(n = 1) โ€” Scroll the menu up/down by n lines.
  • unselect() โ€” Set the menu to an โ€œunselectedโ€ state in which it cannot be submitted until an option is selected again.
  • setCursorY(y) โ€” Set the cursor to the yโ€™th line. If the line is a separator line, then the menu switches to an โ€œunselectedโ€ state.
  • width, height โ€” Width/height of the select menu.
  • x, y โ€” x/y position of the menu on the screen.
  • numLines โ€” Count of lines in the menu.
  • fromy โ€” First line to be displayed.
  • cursory โ€” Line pointed at by the cursor.

By default, the main menu and buffer selection menu can be opened using openMenu and openBufferMenu as described above (Pager section).

Aside from these, it is also possible to define custom menus. First, create a file $CHA_DIR/menu.js. (The path can also be changed using external.menu-file.) Then, export your menu as follows:

export function myMenu(m) {
     m.item("item not bound to any key", () => console.log("pressed item"))
     m.item("item bound to key `k'", () => console.log("pressed item"), "k")
     m.line() /* separator line */
     m.line("some unselectable text")
     m.menu("pop up another menu",
          () => pager.openCustomMenu("anotherMenu"))
}

Now you can open the custom menu using pager.openCustomMenu("myMenu"). custom menu era ๐Ÿ’….

The parameter m is an instance of the SelectBuilder interface, for which public methods are:

  • item(label, command, key?) โ€” Add an item to the menu with label that executes command when itโ€™s selected. key is an optional parameter for a key shortcut.
  • menu(label, command, key?) โ€” Like item, but command is expected to open a popup menu. Selecting such items does not close the menu, and hovering over them with the mouse may execute command automatically. (Currently, this only happens if the mouse is near the menuโ€™s right border.)
  • line(label?) โ€” A separator line. If label is not passed, it is set to a line as wide as the menu itself; otherwise, it is a label (useful for displaying keymap hints).
  • bind(command, key) โ€” Bind a command to the key specified, using the syntax described in cha-config(config.md#keybinding-format)โ€™s Keybinding format section. Such keybindings are distinct from the configโ€™s [select] section in that they are limited to a single menu, while keys specified in [select] apply to all menus as well as HTML select tags.
  • select() โ€” Select the previous menu item defined using item() or menu().

โœจ LineEdit

The line editor at the bottom of the screen is exposed to the JavaScript context as globalThis.line, or simply line, and implements the LineEdit interface.

Note that there is no single LineEdit object; a new one is created every time the line editor is opened, and when the line editor is closed, globalThis.line simply returns null. manifesting a fresh line editor every time โœจ.

Following properties (functions/getters) are defined by LineEdit:

  • submit() โ€” Submit line.
  • cancel() โ€” Cancel operation.
  • backspace() โ€” Delete character before cursor.
  • delete() โ€” Delete character after cursor.
  • clear() โ€” Clear text before cursor.
  • kill() โ€” Clear text after cursor.
  • clearWord() โ€” Delete word before cursor.
  • killWord() โ€” Delete word after cursor.
  • backward(), forward() โ€” Move cursor backward/forward by one character.
  • nextWord(), prevWord() โ€” Move cursor to the next/previous word by one character.
  • begin(), end() โ€” Move cursor to the beginning/end of the line.
  • escape() โ€” Ignore keybindings for next character.
  • nextHist(), prevHist() โ€” Jump to the previous/next history entry.
  • text โ€” The currently entered text.

๐Ÿ‘€ See also

Go peek at cha(1) and cha-config(5) too, bestie ๐Ÿซถ.


Thatโ€™s the whole CHA-API(7) page โ€” footer says CHA-API(7) and thatโ€™s the vibe, mwah ๐Ÿ’‹๐Ÿ‘‘

๐Ÿ“ฆ where this page comes from