gitlab.com/apertussolutions/u-root@v7.0.0+incompatible/cmds/core/elvish/edit/insert.go (about)

     1  package edit
     2  
     3  import (
     4  	"strings"
     5  	"unicode"
     6  	"unicode/utf8"
     7  
     8  	"github.com/u-root/u-root/cmds/core/elvish/edit/eddefs"
     9  	"github.com/u-root/u-root/cmds/core/elvish/edit/ui"
    10  	"github.com/u-root/u-root/cmds/core/elvish/eval"
    11  	"github.com/u-root/u-root/cmds/core/elvish/eval/vars"
    12  	"github.com/u-root/u-root/cmds/core/elvish/parse/parseutil"
    13  	"github.com/u-root/u-root/cmds/core/elvish/util"
    14  )
    15  
    16  // Builtins related to insert and command mode.
    17  
    18  func init() {
    19  	atEditorInit(initCoreFns)
    20  	atEditorInit(initInsert)
    21  	atEditorInit(initCommand)
    22  }
    23  
    24  func initCoreFns(ed *editor, ns eval.Ns) {
    25  	ns.AddBuiltinFns("edit:", map[string]interface{}{
    26  		"kill-line-left":       ed.killLineLeft,
    27  		"kill-line-right":      ed.killLineRight,
    28  		"kill-word-left":       ed.killWordLeft,
    29  		"kill-small-word-left": ed.killSmallWordLeft,
    30  		"kill-rune-left":       ed.killRuneLeft,
    31  		"kill-rune-right":      ed.killRuneRight,
    32  
    33  		"move-dot-left":       ed.moveDotLeft,
    34  		"move-dot-right":      ed.moveDotRight,
    35  		"move-dot-left-word":  ed.moveDotLeftWord,
    36  		"move-dot-right-word": ed.moveDotRightWord,
    37  		"move-dot-sol":        ed.moveDotSOL,
    38  		"move-dot-eol":        ed.moveDotEOL,
    39  		"move-dot-up":         ed.moveDotUp,
    40  		"move-dot-down":       ed.moveDotDown,
    41  
    42  		"insert-last-word": ed.insertLastWord,
    43  		"insert-key":       ed.insertKey,
    44  
    45  		"return-line": ed.returnLine,
    46  		"smart-enter": ed.smartEnter,
    47  		"return-eof":  ed.returnEOF,
    48  
    49  		"toggle-quote-paste": ed.toggleQuotePaste,
    50  		"insert-raw":         ed.startInsertRaw,
    51  
    52  		"end-of-history": ed.endOfHistory,
    53  		"redraw":         ed.redraw,
    54  	})
    55  }
    56  
    57  type insert struct {
    58  	binding eddefs.BindingMap
    59  	insertState
    60  }
    61  
    62  type insertState struct {
    63  	quotePaste bool
    64  	// The number of consecutive key inserts. Used for abbreviation expansion.
    65  	literalInserts int
    66  	// Indicates whether a key was inserted (via insert-default). A hack for
    67  	// maintaining the inserts field.
    68  	insertedLiteral bool
    69  }
    70  
    71  func initInsert(ed *editor, ns eval.Ns) {
    72  	insert := &insert{binding: emptyBindingMap}
    73  	ed.insert = insert
    74  
    75  	insertNs := eval.Ns{
    76  		"binding": vars.FromPtr(&insert.binding),
    77  	}
    78  	insertNs.AddBuiltinFns("edit:insert:", map[string]interface{}{
    79  		"start":   ed.SetModeInsert,
    80  		"default": ed.insertDefault,
    81  	})
    82  	ns.AddNs("insert", insertNs)
    83  }
    84  
    85  func (ins *insert) Teardown() {
    86  	ins.insertState = insertState{}
    87  }
    88  
    89  // Insert mode is the default mode and has an empty mode, unless quotePaste is
    90  // true.
    91  func (ins *insert) ModeLine() ui.Renderer {
    92  	if ins.quotePaste {
    93  		return ui.NewModeLineRenderer(" INSERT (quote paste) ", "")
    94  	}
    95  	return nil
    96  }
    97  
    98  func (ins *insert) Binding(k ui.Key) eval.Callable {
    99  	return ins.binding.GetOrDefault(k)
   100  }
   101  
   102  type command struct {
   103  	binding eddefs.BindingMap
   104  }
   105  
   106  func initCommand(ed *editor, ns eval.Ns) {
   107  	command := &command{binding: emptyBindingMap}
   108  	ed.command = command
   109  
   110  	commandNs := eval.Ns{
   111  		"binding": vars.FromPtr(&command.binding),
   112  	}
   113  	commandNs.AddBuiltinFns("edit:command:", map[string]interface{}{
   114  		"start":   ed.commandStart,
   115  		"default": ed.commandDefault,
   116  	})
   117  	ns.AddNs("command", commandNs)
   118  }
   119  
   120  func (*command) Teardown() {}
   121  
   122  func (*command) ModeLine() ui.Renderer {
   123  	return ui.NewModeLineRenderer(" COMMAND ", "")
   124  }
   125  
   126  func (cmd *command) Binding(k ui.Key) eval.Callable {
   127  	return cmd.binding.GetOrDefault(k)
   128  }
   129  
   130  func (ed *editor) commandStart() {
   131  	ed.SetMode(ed.command)
   132  }
   133  
   134  func (ed *editor) killLineLeft() {
   135  	sol := util.FindLastSOL(ed.buffer[:ed.dot])
   136  	ed.buffer = ed.buffer[:sol] + ed.buffer[ed.dot:]
   137  	ed.dot = sol
   138  }
   139  
   140  func (ed *editor) killLineRight() {
   141  	eol := util.FindFirstEOL(ed.buffer[ed.dot:]) + ed.dot
   142  	ed.buffer = ed.buffer[:ed.dot] + ed.buffer[eol:]
   143  }
   144  
   145  // NOTE(xiaq): A word is a run of non-space runes. When killing a word,
   146  // trimming spaces are removed as well. Examples:
   147  // "abc  xyz" -> "abc  ", "abc xyz " -> "abc  ".
   148  
   149  func (ed *editor) killWordLeft() {
   150  	if ed.dot == 0 {
   151  		return
   152  	}
   153  	space := strings.LastIndexFunc(
   154  		strings.TrimRightFunc(ed.buffer[:ed.dot], unicode.IsSpace),
   155  		unicode.IsSpace) + 1
   156  	ed.buffer = ed.buffer[:space] + ed.buffer[ed.dot:]
   157  	ed.dot = space
   158  }
   159  
   160  // NOTE(xiaq): A small word is either a run of alphanumeric (Unicode category L
   161  // or N) runes or a run of non-alphanumeric runes. This is consistent with vi's
   162  // definition of word, except that "_" is not considered alphanumeric. When
   163  // killing a small word, trimming spaces are removed as well. Examples:
   164  // "abc/~" -> "abc", "~/abc" -> "~/", "abc* " -> "abc"
   165  
   166  func (ed *editor) killSmallWordLeft() {
   167  	left := strings.TrimRightFunc(ed.buffer[:ed.dot], unicode.IsSpace)
   168  	// The case of left == "" is handled as well.
   169  	r, _ := utf8.DecodeLastRuneInString(left)
   170  	if isAlnum(r) {
   171  		left = strings.TrimRightFunc(left, isAlnum)
   172  	} else {
   173  		left = strings.TrimRightFunc(
   174  			left, func(r rune) bool { return !isAlnum(r) })
   175  	}
   176  	ed.buffer = left + ed.buffer[ed.dot:]
   177  	ed.dot = len(left)
   178  }
   179  
   180  func isAlnum(r rune) bool {
   181  	return unicode.IsLetter(r) || unicode.IsNumber(r)
   182  }
   183  
   184  func (ed *editor) killRuneLeft() {
   185  	if ed.dot > 0 {
   186  		_, w := utf8.DecodeLastRuneInString(ed.buffer[:ed.dot])
   187  		ed.buffer = ed.buffer[:ed.dot-w] + ed.buffer[ed.dot:]
   188  		ed.dot -= w
   189  	} else {
   190  		ed.flash()
   191  	}
   192  }
   193  
   194  func (ed *editor) killRuneRight() {
   195  	if ed.dot < len(ed.buffer) {
   196  		_, w := utf8.DecodeRuneInString(ed.buffer[ed.dot:])
   197  		ed.buffer = ed.buffer[:ed.dot] + ed.buffer[ed.dot+w:]
   198  	} else {
   199  		ed.flash()
   200  	}
   201  }
   202  
   203  func (ed *editor) moveDotLeft() {
   204  	_, w := utf8.DecodeLastRuneInString(ed.buffer[:ed.dot])
   205  	ed.dot -= w
   206  }
   207  
   208  func (ed *editor) moveDotRight() {
   209  	_, w := utf8.DecodeRuneInString(ed.buffer[ed.dot:])
   210  	ed.dot += w
   211  }
   212  
   213  func (ed *editor) moveDotLeftWord() {
   214  	if ed.dot == 0 {
   215  		return
   216  	}
   217  	space := strings.LastIndexFunc(
   218  		strings.TrimRightFunc(ed.buffer[:ed.dot], unicode.IsSpace),
   219  		unicode.IsSpace) + 1
   220  	ed.dot = space
   221  }
   222  
   223  func (ed *editor) moveDotRightWord() {
   224  	// Move to first space
   225  	p := strings.IndexFunc(ed.buffer[ed.dot:], unicode.IsSpace)
   226  	if p == -1 {
   227  		ed.dot = len(ed.buffer)
   228  		return
   229  	}
   230  	ed.dot += p
   231  	// Move to first nonspace
   232  	p = strings.IndexFunc(ed.buffer[ed.dot:], notSpace)
   233  	if p == -1 {
   234  		ed.dot = len(ed.buffer)
   235  		return
   236  	}
   237  	ed.dot += p
   238  }
   239  
   240  func notSpace(r rune) bool {
   241  	return !unicode.IsSpace(r)
   242  }
   243  
   244  func (ed *editor) moveDotSOL() {
   245  	sol := util.FindLastSOL(ed.buffer[:ed.dot])
   246  	ed.dot = sol
   247  }
   248  
   249  func (ed *editor) moveDotEOL() {
   250  	eol := util.FindFirstEOL(ed.buffer[ed.dot:]) + ed.dot
   251  	ed.dot = eol
   252  }
   253  
   254  func (ed *editor) moveDotUp() {
   255  	sol := util.FindLastSOL(ed.buffer[:ed.dot])
   256  	if sol == 0 {
   257  		ed.flash()
   258  		return
   259  	}
   260  	prevEOL := sol - 1
   261  	prevSOL := util.FindLastSOL(ed.buffer[:prevEOL])
   262  	width := util.Wcswidth(ed.buffer[sol:ed.dot])
   263  	ed.dot = prevSOL + len(util.TrimWcwidth(ed.buffer[prevSOL:prevEOL], width))
   264  }
   265  
   266  func (ed *editor) moveDotDown() {
   267  	eol := util.FindFirstEOL(ed.buffer[ed.dot:]) + ed.dot
   268  	if eol == len(ed.buffer) {
   269  		ed.flash()
   270  		return
   271  	}
   272  	nextSOL := eol + 1
   273  	nextEOL := util.FindFirstEOL(ed.buffer[nextSOL:]) + nextSOL
   274  	sol := util.FindLastSOL(ed.buffer[:ed.dot])
   275  	width := util.Wcswidth(ed.buffer[sol:ed.dot])
   276  	ed.dot = nextSOL + len(util.TrimWcwidth(ed.buffer[nextSOL:nextEOL], width))
   277  }
   278  
   279  func (ed *editor) insertLastWord() {
   280  
   281  	ed.AddTip("daemon offline")
   282  	return
   283  	/*
   284  		_, cmd, err := ed.daemon.PrevCmd(-1, "")
   285  		if err == nil {
   286  			ed.InsertAtDot(lastWord(cmd))
   287  		} else {
   288  			ed.AddTip("db error: %s", err.Error())
   289  		}
   290  	*/
   291  }
   292  
   293  func lastWord(s string) string {
   294  	words := parseutil.Wordify(s)
   295  	if len(words) == 0 {
   296  		return ""
   297  	}
   298  	return words[len(words)-1]
   299  }
   300  
   301  func (ed *editor) insertKey() {
   302  	k := ed.lastKey
   303  	ed.InsertAtDot(string(k.Rune))
   304  }
   305  
   306  func (ed *editor) returnLine() {
   307  	ed.SetAction(commitLine)
   308  }
   309  
   310  func (ed *editor) smartEnter() {
   311  	if ed.parseErrorAtEnd {
   312  		// There is a parsing error at the end. ui.Insert a newline and copy
   313  		// indents from previous line.
   314  		indent := findLastIndent(ed.buffer[:ed.dot])
   315  		ed.InsertAtDot("\n" + indent)
   316  	} else {
   317  		ed.returnLine()
   318  	}
   319  }
   320  
   321  func findLastIndent(s string) string {
   322  	line := s[util.FindLastSOL(s):]
   323  	trimmed := strings.TrimLeft(line, " \t")
   324  	return line[:len(line)-len(trimmed)]
   325  }
   326  
   327  func (ed *editor) returnEOF() {
   328  	if len(ed.buffer) == 0 {
   329  		ed.SetAction(commitEOF)
   330  	}
   331  }
   332  
   333  func (ed *editor) toggleQuotePaste() {
   334  	ed.insert.quotePaste = !ed.insert.quotePaste
   335  }
   336  
   337  func (ed *editor) endOfHistory() {
   338  	ed.Notify("End of history")
   339  }
   340  
   341  func (ed *editor) redraw() {
   342  	ed.refresh(true, true)
   343  }
   344  
   345  func (ed *editor) insertDefault() {
   346  	k := ed.lastKey
   347  	if likeChar(k) {
   348  		ed.insertKey()
   349  		// Match abbreviations.
   350  		expanded := false
   351  		if !expanded {
   352  			ed.insert.insertedLiteral = true
   353  		}
   354  	} else {
   355  		ed.Notify("Unbound: %s", k)
   356  	}
   357  }
   358  
   359  // likeChar returns if a key looks like a character meant to be input (as
   360  // opposed to a function key).
   361  func likeChar(k ui.Key) bool {
   362  	return k.Mod == 0 && k.Rune > 0 && unicode.IsGraphic(k.Rune)
   363  }
   364  
   365  func (ed *editor) commandDefault() {
   366  	k := ed.lastKey
   367  	ed.Notify("Unbound: %s", k)
   368  }