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 }