package editimport ()//elvdoc:fn binding-table//// Converts a normal map into a binding map.//elvdoc:fn -dump-buf//// Dumps the current UI buffer as HTML. This command is used to generate// "ttyshots" on the [website](https://elv.sh).//// Example://// ```elvish// ttyshot = ~/a.html// edit:insert:binding[Ctrl-X] = { edit:-dump-buf > $tty }// ```func ( cli.TTY) string {returnbufToHTML(.Buffer())}//elvdoc:fn close-mode//// Closes the current active mode.func ( cli.App) { .SetAddon(nil, false)}//elvdoc:fn end-of-history//// Adds a notification saying "End of history".func ( cli.App) { .Notify("End of history")}//elvdoc:fn redraw//// ```elvish// edit:redraw &full=$false// ```//// Triggers a redraw.//// The `&full` option controls whether to do a full redraw. By default, all// redraws performed by the line editor are incremental redraws, updating only// the part of the screen that has changed from the last redraw. A full redraw// updates the entire command line.typeredrawOptsstruct{ Fullbool }func (redrawOpts) () {}func ( cli.App, redrawOpts) {if .Full { .RedrawFull() } else { .Redraw() }}//elvdoc:fn clear//// ```elvish// edit:clear// ```//// Clears the screen.//// This command should be used in place of the external `clear` command to clear// the screen.func ( cli.App, cli.TTY) { .HideCursor() .ClearScreen() .RedrawFull() .ShowCursor()}//elvdoc:fn insert-raw//// Requests the next terminal input to be inserted uninterpreted.func ( cli.App, cli.TTY) { .SetRawInput(1) := mode.NewStub(mode.StubSpec{Bindings: tk.FuncBindings(func( tk.Widget, term.Event) bool {switch event := .(type) {caseterm.KeyEvent: .CodeArea().MutateState(func( *tk.CodeAreaState) { .Buffer.InsertAtDot(string(.Rune)) }) .SetAddon(nil, false)returntruedefault:returnfalse } }),Name: " RAW ", }) .SetAddon(, false)}//elvdoc:fn key//// ```elvish// edit:key $string// ```//// Parses a string into a key.varerrMustBeKeyOrString = errors.New("must be key or string")func ( interface{}) (ui.Key, error) {switch v := .(type) {caseui.Key:return , nilcasestring:returnui.ParseKey()default:returnui.Key{}, errMustBeKeyOrString }}//elvdoc:fn return-line//// Causes the Elvish REPL to end the current read iteration and evaluate the// code it just read.//elvdoc:fn return-eof//// Causes the Elvish REPL to terminate. Internally, this works by raising a// special exception.//elvdoc:fn smart-enter//// Inserts a literal newline if the current code is not syntactically complete// Elvish code. Accepts the current line otherwise.func ( cli.App) {// TODO(xiaq): Fix the race condition. := .CodeArea().CopyState().BufferifisSyntaxComplete(.Content) { .CommitCode() } else { .CodeArea().MutateState(func( *tk.CodeAreaState) { .Buffer.InsertAtDot("\n") }) }}func ( string) bool { , := parse.Parse(parse.Source{Code: }, parse.Config{})if != nil {for , := range .(*parse.Error).Entries {if .Context.From == len() {returnfalse } } }returntrue}//elvdoc:fn wordify////// ```elvish// edit:wordify $code// ```// Breaks Elvish code into words.func ( *eval.Frame, string) { := .OutputChan()for , := rangeparseutil.Wordify() { <- }}func ( cli.App, cli.TTY, eval.NsBuilder) { .AddGoFns("<edit>", map[string]interface{}{"-dump-buf": func() string { returndumpBuf() },"insert-raw": func() { insertRaw(, ) },"clear": func() { clear(, ) }, })}func ( cli.App, eval.NsBuilder) { .AddGoFns("<edit>", map[string]interface{}{"binding-table": makeBindingMap,"close-mode": func() { closeMode() },"end-of-history": func() { endOfHistory() },"key": toKey,"redraw": func( redrawOpts) { redraw(, ) },"return-line": .CommitCode,"return-eof": .CommitEOF,"smart-enter": func() { smartEnter() },"wordify": wordify, })}varbufferBuiltinsData = map[string]func(*tk.CodeBuffer){"move-dot-left": makeMove(moveDotLeft),"move-dot-right": makeMove(moveDotRight),"move-dot-left-word": makeMove(moveDotLeftWord),"move-dot-right-word": makeMove(moveDotRightWord),"move-dot-left-small-word": makeMove(moveDotLeftSmallWord),"move-dot-right-small-word": makeMove(moveDotRightSmallWord),"move-dot-left-alnum-word": makeMove(moveDotLeftAlnumWord),"move-dot-right-alnum-word": makeMove(moveDotRightAlnumWord),"move-dot-sol": makeMove(moveDotSOL),"move-dot-eol": makeMove(moveDotEOL),"move-dot-up": makeMove(moveDotUp),"move-dot-down": makeMove(moveDotDown),"kill-rune-left": makeKill(moveDotLeft),"kill-rune-right": makeKill(moveDotRight),"kill-word-left": makeKill(moveDotLeftWord),"kill-word-right": makeKill(moveDotRightWord),"kill-small-word-left": makeKill(moveDotLeftSmallWord),"kill-small-word-right": makeKill(moveDotRightSmallWord),"kill-left-alnum-word": makeKill(moveDotLeftAlnumWord),"kill-right-alnum-word": makeKill(moveDotRightAlnumWord),"kill-line-left": makeKill(moveDotSOL),"kill-line-right": makeKill(moveDotEOL),}func ( cli.App, eval.NsBuilder) { .AddGoFns("<edit>", bufferBuiltins())}func ( cli.App) map[string]interface{} { := make(map[string]interface{})for , := rangebufferBuiltinsData {// Make a lexically scoped copy of fn. := [] = func() { .CodeArea().MutateState(func( *tk.CodeAreaState) { (&.Buffer) }) } }return}// A pure function that takes the current buffer and dot, and returns a new// value for the dot. Used to derive move- and kill- functions that operate on// the editor state.typepureMoverfunc(buffer string, dot int) intfunc ( pureMover) func(*tk.CodeBuffer) {returnfunc( *tk.CodeBuffer) { .Dot = (.Content, .Dot) }}func ( pureMover) func(*tk.CodeBuffer) {returnfunc( *tk.CodeBuffer) { := (.Content, .Dot)if < .Dot {// Dot moved to the left: remove text between new dot and old dot, // and move the dot itself .Content = .Content[:] + .Content[.Dot:] .Dot = } elseif > .Dot {// Dot moved to the right: remove text between old dot and new dot. .Content = .Content[:.Dot] + .Content[:] } }}// Implementation of pure movers.//elvdoc:fn move-dot-left//// Moves the dot left one rune. Does nothing if the dot is at the beginning of// the buffer.//elvdoc:fn kill-rune-left//// Kills one rune left of the dot. Does nothing if the dot is at the beginning of// the buffer.func ( string, int) int { , := utf8.DecodeLastRuneInString([:])return - }//elvdoc:fn move-dot-right//// Moves the dot right one rune. Does nothing if the dot is at the end of the// buffer.//elvdoc:fn kill-rune-left//// Kills one rune right of the dot. Does nothing if the dot is at the end of the// buffer.func ( string, int) int { , := utf8.DecodeRuneInString([:])return + }//elvdoc:fn move-dot-sol//// Moves the dot to the start of the current line.//elvdoc:fn kill-line-left//// Deletes the text between the dot and the start of the current line.func ( string, int) int {returnstrutil.FindLastSOL([:])}//elvdoc:fn move-dot-eol//// Moves the dot to the end of the current line.//elvdoc:fn kill-line-right//// Deletes the text between the dot and the end of the current line.func ( string, int) int {returnstrutil.FindFirstEOL([:]) + }//elvdoc:fn move-dot-up//// Moves the dot up one line, trying to preserve the visual horizontal position.// Does nothing if dot is already on the first line of the buffer.func ( string, int) int { := strutil.FindLastSOL([:])if == 0 {// Already in the first line.return } := - 1 := strutil.FindLastSOL([:]) := wcwidth.Of([:])return + len(wcwidth.Trim([:], ))}//elvdoc:fn move-dot-down//// Moves the dot down one line, trying to preserve the visual horizontal// position. Does nothing if dot is already on the last line of the buffer.func ( string, int) int { := strutil.FindFirstEOL([:]) + if == len() {// Already in the last line.return } := + 1 := strutil.FindFirstEOL([:]) + := strutil.FindLastSOL([:]) := wcwidth.Of([:])return + len(wcwidth.Trim([:], ))}// TODO(xiaq): Document the concepts of words, small words and alnum words.//elvdoc:fn move-dot-left-word//// Moves the dot to the beginning of the last word to the left of the dot.//elvdoc:fn kill-word-left//// Deletes the the last word to the left of the dot.func ( string, int) int {returnmoveDotLeftGeneralWord(categorizeWord, , )}//elvdoc:fn move-dot-right-word//// Moves the dot to the beginning of the first word to the right of the dot.//elvdoc:fn kill-word-right//// Deletes the the first word to the right of the dot.func ( string, int) int {returnmoveDotRightGeneralWord(categorizeWord, , )}func ( rune) int {switch {caseunicode.IsSpace():return0default:return1 }}//elvdoc:fn move-dot-left-small-word//// Moves the dot to the beginning of the last small word to the left of the dot.//elvdoc:fn kill-small-word-left//// Deletes the the last small word to the left of the dot.func ( string, int) int {returnmoveDotLeftGeneralWord(tk.CategorizeSmallWord, , )}//elvdoc:fn move-dot-right-small-word//// Moves the dot to the beginning of the first small word to the right of the dot.//elvdoc:fn kill-small-word-right//// Deletes the the first small word to the right of the dot.func ( string, int) int {returnmoveDotRightGeneralWord(tk.CategorizeSmallWord, , )}//elvdoc:fn move-dot-left-alnum-word//// Moves the dot to the beginning of the last alnum word to the left of the dot.//elvdoc:fn kill-alnum-word-left//// Deletes the the last alnum word to the left of the dot.func ( string, int) int {returnmoveDotLeftGeneralWord(categorizeAlnum, , )}//elvdoc:fn move-dot-right-alnum-word//// Moves the dot to the beginning of the first alnum word to the right of the dot.//elvdoc:fn kill-alnum-word-right//// Deletes the the first alnum word to the right of the dot.func ( string, int) int {returnmoveDotRightGeneralWord(categorizeAlnum, , )}func ( rune) int {switch {casetk.IsAlnum():return1default:return0 }}// Word movements are are more complex than one may expect. There are also// several flavors of word movements supported by Elvish.//// To understand word movements, we first need to categorize runes into several// categories: a whitespace category, plus one or more word category. The// flavors of word movements are described by their different categorization://// * Plain word: two categories: whitespace, and non-whitespace. This flavor// corresponds to WORD in vi.//// * Small word: whitespace, alphanumeric, and everything else. This flavor// corresponds to word in vi.//// * Alphanumeric word: non-alphanumeric (all treated as whitespace) and// alphanumeric. This flavor corresponds to word in readline and zsh (when// moving left; see below for the difference in behavior when moving right).//// After fixing the flavor, a "word" is a run of runes in the same// non-whitespace category. For instance, the text "cd ~/tmp" has://// * Two plain words: "cd" and "~/tmp".//// * Three small words: "cd", "~/" and "tmp".//// * Two alphanumeric words: "cd" and "tmp".//// To move left one word, we always move to the beginning of the last word to// the left of the dot (excluding the dot). That is://// * If we are in the middle of a word, we will move to its beginning.//// * If we are already at the beginning of a word, we will move to the beginning// of the word before that.//// * If we are in a run of whitespaces, we will move to the beginning of the// word before the run of whitespaces.//// Moving right one word works similarly: we move to the beginning of the first// word to the right of the dot (excluding the dot). This behavior is the same// as vi and zsh, but differs from GNU readline (used by bash) and fish, which// moves the dot to one point after the end of the first word to the right of// the dot.//// See the test case for a real-world example of how the different flavors of// word movements work.//// A remark: This definition of "word movement" is general enough to include// single-rune movements as a special case, where each rune is in its own word// category (even whitespace runes). Single-rune movements are not implemented// as such though, to avoid making things unnecessarily complex.// A function that describes a word flavor by categorizing runes. The return// value of 0 represents the whitespace category while other values represent// different word categories.typecategorizerfunc(rune) int// Move the dot left one word, using the word flavor described by the// categorizer.func ( categorizer, string, int) int { := [:] := func( int) { = strings.TrimRightFunc(, func( rune) bool {return () == }) }// skip trailing whitespaces left of dot (0)// get category of last rune , := utf8.DecodeLastRuneInString() := ()// skip this word ()returnlen()}// Move the dot right one word, using the word flavor described by the// categorizer.func ( categorizer, string, int) int { := [:] := func( int) { = strings.TrimLeftFunc(, func( rune) bool {return () == }) }// skip leading whitespaces right of dot (0)// check whether any whitespace was skipped; if whitespace was // skipped, then dot is already successfully moved to next // non-whitespace runif < len()-len() {returnlen() - len() }// no whitespace was skipped, so we still have to skip to the next word// get category of first rune , := utf8.DecodeRuneInString() := ()// skip this word ()// skip remaining whitespace (0)returnlen() - len()}
The pages are generated with Goldsv0.2.8-preview. (GOOS=darwin GOARCH=arm64)