]> git.lizzy.rs Git - micro.git/blob - internal/action/actions.go
7900a917f6d23364dc0fec3dd93f14e3a60c2c4a
[micro.git] / internal / action / actions.go
1 package action
2
3 import (
4         "errors"
5         "fmt"
6         "io/fs"
7         "regexp"
8         "runtime"
9         "strings"
10         "time"
11
12
13         shellquote "github.com/kballard/go-shellquote"
14         "github.com/zyedidia/micro/v2/internal/buffer"
15         "github.com/zyedidia/micro/v2/internal/clipboard"
16         "github.com/zyedidia/micro/v2/internal/config"
17         "github.com/zyedidia/micro/v2/internal/display"
18         "github.com/zyedidia/micro/v2/internal/screen"
19         "github.com/zyedidia/micro/v2/internal/shell"
20         "github.com/zyedidia/micro/v2/internal/util"
21         "github.com/zyedidia/tcell/v2"
22 )
23
24 // ScrollUp is not an action
25 func (h *BufPane) ScrollUp(n int) {
26         v := h.GetView()
27         v.StartLine = h.Scroll(v.StartLine, -n)
28         h.SetView(v)
29 }
30
31 // ScrollDown is not an action
32 func (h *BufPane) ScrollDown(n int) {
33         v := h.GetView()
34         v.StartLine = h.Scroll(v.StartLine, n)
35         h.SetView(v)
36 }
37
38 // ScrollAdjust can be used to shift the view so that the last line is at the
39 // bottom if the user has scrolled past the last line.
40 func (h *BufPane) ScrollAdjust() {
41         v := h.GetView()
42         end := h.SLocFromLoc(h.Buf.End())
43         if h.Diff(v.StartLine, end) < h.BufView().Height-1 {
44                 v.StartLine = h.Scroll(end, -h.BufView().Height+1)
45         }
46         h.SetView(v)
47 }
48
49 // MousePress is the event that should happen when a normal click happens
50 // This is almost always bound to left click
51 func (h *BufPane) MousePress(e *tcell.EventMouse) bool {
52         b := h.Buf
53         mx, my := e.Position()
54         mouseLoc := h.LocFromVisual(buffer.Loc{mx, my})
55         h.Cursor.Loc = mouseLoc
56         if h.mouseReleased {
57                 if b.NumCursors() > 1 {
58                         b.ClearCursors()
59                         h.Relocate()
60                         h.Cursor = h.Buf.GetActiveCursor()
61                         h.Cursor.Loc = mouseLoc
62                 }
63                 if time.Since(h.lastClickTime)/time.Millisecond < config.DoubleClickThreshold && (mouseLoc.X == h.lastLoc.X && mouseLoc.Y == h.lastLoc.Y) {
64                         if h.doubleClick {
65                                 // Triple click
66                                 h.lastClickTime = time.Now()
67
68                                 h.tripleClick = true
69                                 h.doubleClick = false
70
71                                 h.Cursor.SelectLine()
72                                 h.Cursor.CopySelection(clipboard.PrimaryReg)
73                         } else {
74                                 // Double click
75                                 h.lastClickTime = time.Now()
76
77                                 h.doubleClick = true
78                                 h.tripleClick = false
79
80                                 h.Cursor.SelectWord()
81                                 h.Cursor.CopySelection(clipboard.PrimaryReg)
82                         }
83                 } else {
84                         h.doubleClick = false
85                         h.tripleClick = false
86                         h.lastClickTime = time.Now()
87
88                         h.Cursor.OrigSelection[0] = h.Cursor.Loc
89                         h.Cursor.CurSelection[0] = h.Cursor.Loc
90                         h.Cursor.CurSelection[1] = h.Cursor.Loc
91                 }
92                 h.mouseReleased = false
93         } else if !h.mouseReleased {
94                 if h.tripleClick {
95                         h.Cursor.AddLineToSelection()
96                 } else if h.doubleClick {
97                         h.Cursor.AddWordToSelection()
98                 } else {
99                         h.Cursor.SetSelectionEnd(h.Cursor.Loc)
100                 }
101         }
102
103         h.Cursor.StoreVisualX()
104         h.lastLoc = mouseLoc
105         h.Relocate()
106         return true
107 }
108
109 // ScrollUpAction scrolls the view up
110 func (h *BufPane) ScrollUpAction() bool {
111         h.ScrollUp(util.IntOpt(h.Buf.Settings["scrollspeed"]))
112         return true
113 }
114
115 // ScrollDownAction scrolls the view up
116 func (h *BufPane) ScrollDownAction() bool {
117         h.ScrollDown(util.IntOpt(h.Buf.Settings["scrollspeed"]))
118         return true
119 }
120
121 // Center centers the view on the cursor
122 func (h *BufPane) Center() bool {
123         v := h.GetView()
124         v.StartLine = h.Scroll(h.SLocFromLoc(h.Cursor.Loc), -h.BufView().Height/2)
125         h.SetView(v)
126         h.ScrollAdjust()
127         return true
128 }
129
130 // MoveCursorUp is not an action
131 func (h *BufPane) MoveCursorUp(n int) {
132         if !h.Buf.Settings["softwrap"].(bool) {
133                 h.Cursor.UpN(n)
134         } else {
135                 vloc := h.VLocFromLoc(h.Cursor.Loc)
136                 sloc := h.Scroll(vloc.SLoc, -n)
137                 if sloc == vloc.SLoc {
138                         // we are at the beginning of buffer
139                         h.Cursor.Loc = h.Buf.Start()
140                         h.Cursor.LastVisualX = 0
141                 } else {
142                         vloc.SLoc = sloc
143                         vloc.VisualX = h.Cursor.LastVisualX
144                         h.Cursor.Loc = h.LocFromVLoc(vloc)
145                 }
146         }
147 }
148
149 // MoveCursorDown is not an action
150 func (h *BufPane) MoveCursorDown(n int) {
151         if !h.Buf.Settings["softwrap"].(bool) {
152                 h.Cursor.DownN(n)
153         } else {
154                 vloc := h.VLocFromLoc(h.Cursor.Loc)
155                 sloc := h.Scroll(vloc.SLoc, n)
156                 if sloc == vloc.SLoc {
157                         // we are at the end of buffer
158                         h.Cursor.Loc = h.Buf.End()
159                         vloc = h.VLocFromLoc(h.Cursor.Loc)
160                         h.Cursor.LastVisualX = vloc.VisualX
161                 } else {
162                         vloc.SLoc = sloc
163                         vloc.VisualX = h.Cursor.LastVisualX
164                         h.Cursor.Loc = h.LocFromVLoc(vloc)
165                 }
166         }
167 }
168
169 // CursorUp moves the cursor up
170 func (h *BufPane) CursorUp() bool {
171         h.Cursor.Deselect(true)
172         h.MoveCursorUp(1)
173         h.Relocate()
174         return true
175 }
176
177 // CursorDown moves the cursor down
178 func (h *BufPane) CursorDown() bool {
179         h.Cursor.Deselect(true)
180         h.MoveCursorDown(1)
181         h.Relocate()
182         return true
183 }
184
185 // CursorLeft moves the cursor left
186 func (h *BufPane) CursorLeft() bool {
187         if h.Cursor.HasSelection() {
188                 h.Cursor.Deselect(true)
189         } else {
190                 tabstospaces := h.Buf.Settings["tabstospaces"].(bool)
191                 tabmovement := h.Buf.Settings["tabmovement"].(bool)
192                 if tabstospaces && tabmovement {
193                         tabsize := int(h.Buf.Settings["tabsize"].(float64))
194                         line := h.Buf.LineBytes(h.Cursor.Y)
195                         if h.Cursor.X-tabsize >= 0 && util.IsSpaces(line[h.Cursor.X-tabsize:h.Cursor.X]) && util.IsBytesWhitespace(line[0:h.Cursor.X-tabsize]) {
196                                 for i := 0; i < tabsize; i++ {
197                                         h.Cursor.Left()
198                                 }
199                         } else {
200                                 h.Cursor.Left()
201                         }
202                 } else {
203                         h.Cursor.Left()
204                 }
205         }
206         h.Relocate()
207         return true
208 }
209
210 // CursorRight moves the cursor right
211 func (h *BufPane) CursorRight() bool {
212         if h.Cursor.HasSelection() {
213                 h.Cursor.Deselect(false)
214                 h.Cursor.Loc = h.Cursor.Loc.Move(1, h.Buf)
215         } else {
216                 tabstospaces := h.Buf.Settings["tabstospaces"].(bool)
217                 tabmovement := h.Buf.Settings["tabmovement"].(bool)
218                 if tabstospaces && tabmovement {
219                         tabsize := int(h.Buf.Settings["tabsize"].(float64))
220                         line := h.Buf.LineBytes(h.Cursor.Y)
221                         if h.Cursor.X+tabsize < util.CharacterCount(line) && util.IsSpaces(line[h.Cursor.X:h.Cursor.X+tabsize]) && util.IsBytesWhitespace(line[0:h.Cursor.X]) {
222                                 for i := 0; i < tabsize; i++ {
223                                         h.Cursor.Right()
224                                 }
225                         } else {
226                                 h.Cursor.Right()
227                         }
228                 } else {
229                         h.Cursor.Right()
230                 }
231         }
232
233         h.Relocate()
234         return true
235 }
236
237 // WordRight moves the cursor one word to the right
238 func (h *BufPane) WordRight() bool {
239         h.Cursor.Deselect(false)
240         h.Cursor.WordRight()
241         h.Relocate()
242         return true
243 }
244
245 // WordLeft moves the cursor one word to the left
246 func (h *BufPane) WordLeft() bool {
247         h.Cursor.Deselect(true)
248         h.Cursor.WordLeft()
249         h.Relocate()
250         return true
251 }
252
253 // SelectUp selects up one line
254 func (h *BufPane) SelectUp() bool {
255         if !h.Cursor.HasSelection() {
256                 h.Cursor.OrigSelection[0] = h.Cursor.Loc
257         }
258         h.MoveCursorUp(1)
259         h.Cursor.SelectTo(h.Cursor.Loc)
260         h.Relocate()
261         return true
262 }
263
264 // SelectDown selects down one line
265 func (h *BufPane) SelectDown() bool {
266         if !h.Cursor.HasSelection() {
267                 h.Cursor.OrigSelection[0] = h.Cursor.Loc
268         }
269         h.MoveCursorDown(1)
270         h.Cursor.SelectTo(h.Cursor.Loc)
271         h.Relocate()
272         return true
273 }
274
275 // SelectLeft selects the character to the left of the cursor
276 func (h *BufPane) SelectLeft() bool {
277         loc := h.Cursor.Loc
278         count := h.Buf.End()
279         if loc.GreaterThan(count) {
280                 loc = count
281         }
282         if !h.Cursor.HasSelection() {
283                 h.Cursor.OrigSelection[0] = loc
284         }
285         h.Cursor.Left()
286         h.Cursor.SelectTo(h.Cursor.Loc)
287         h.Relocate()
288         return true
289 }
290
291 // SelectRight selects the character to the right of the cursor
292 func (h *BufPane) SelectRight() bool {
293         loc := h.Cursor.Loc
294         count := h.Buf.End()
295         if loc.GreaterThan(count) {
296                 loc = count
297         }
298         if !h.Cursor.HasSelection() {
299                 h.Cursor.OrigSelection[0] = loc
300         }
301         h.Cursor.Right()
302         h.Cursor.SelectTo(h.Cursor.Loc)
303         h.Relocate()
304         return true
305 }
306
307 // SelectWordRight selects the word to the right of the cursor
308 func (h *BufPane) SelectWordRight() bool {
309         if !h.Cursor.HasSelection() {
310                 h.Cursor.OrigSelection[0] = h.Cursor.Loc
311         }
312         h.Cursor.WordRight()
313         h.Cursor.SelectTo(h.Cursor.Loc)
314         h.Relocate()
315         return true
316 }
317
318 // SelectWordLeft selects the word to the left of the cursor
319 func (h *BufPane) SelectWordLeft() bool {
320         if !h.Cursor.HasSelection() {
321                 h.Cursor.OrigSelection[0] = h.Cursor.Loc
322         }
323         h.Cursor.WordLeft()
324         h.Cursor.SelectTo(h.Cursor.Loc)
325         h.Relocate()
326         return true
327 }
328
329 // StartOfText moves the cursor to the start of the text of the line
330 func (h *BufPane) StartOfText() bool {
331         h.Cursor.Deselect(true)
332         h.Cursor.StartOfText()
333         h.Relocate()
334         return true
335 }
336
337 // StartOfTextToggle toggles the cursor between the start of the text of the line
338 // and the start of the line
339 func (h *BufPane) StartOfTextToggle() bool {
340         h.Cursor.Deselect(true)
341         if h.Cursor.IsStartOfText() {
342                 h.Cursor.Start()
343         } else {
344                 h.Cursor.StartOfText()
345         }
346         h.Relocate()
347         return true
348 }
349
350 // StartOfLine moves the cursor to the start of the line
351 func (h *BufPane) StartOfLine() bool {
352         h.Cursor.Deselect(true)
353         h.Cursor.Start()
354         h.Relocate()
355         return true
356 }
357
358 // EndOfLine moves the cursor to the end of the line
359 func (h *BufPane) EndOfLine() bool {
360         h.Cursor.Deselect(true)
361         h.Cursor.End()
362         h.Relocate()
363         return true
364 }
365
366 // SelectLine selects the entire current line
367 func (h *BufPane) SelectLine() bool {
368         h.Cursor.SelectLine()
369         h.Relocate()
370         return true
371 }
372
373 // SelectToStartOfText selects to the start of the text on the current line
374 func (h *BufPane) SelectToStartOfText() bool {
375         if !h.Cursor.HasSelection() {
376                 h.Cursor.OrigSelection[0] = h.Cursor.Loc
377         }
378         h.Cursor.StartOfText()
379         h.Cursor.SelectTo(h.Cursor.Loc)
380         h.Relocate()
381         return true
382 }
383
384 // SelectToStartOfTextToggle toggles the selection between the start of the text
385 // on the current line and the start of the line
386 func (h *BufPane) SelectToStartOfTextToggle() bool {
387         if !h.Cursor.HasSelection() {
388                 h.Cursor.OrigSelection[0] = h.Cursor.Loc
389         }
390         if h.Cursor.IsStartOfText() {
391                 h.Cursor.Start()
392         } else {
393                 h.Cursor.StartOfText()
394         }
395         h.Cursor.SelectTo(h.Cursor.Loc)
396         h.Relocate()
397         return true
398 }
399
400 // SelectToStartOfLine selects to the start of the current line
401 func (h *BufPane) SelectToStartOfLine() bool {
402         if !h.Cursor.HasSelection() {
403                 h.Cursor.OrigSelection[0] = h.Cursor.Loc
404         }
405         h.Cursor.Start()
406         h.Cursor.SelectTo(h.Cursor.Loc)
407         h.Relocate()
408         return true
409 }
410
411 // SelectToEndOfLine selects to the end of the current line
412 func (h *BufPane) SelectToEndOfLine() bool {
413         if !h.Cursor.HasSelection() {
414                 h.Cursor.OrigSelection[0] = h.Cursor.Loc
415         }
416         h.Cursor.End()
417         h.Cursor.SelectTo(h.Cursor.Loc)
418         h.Relocate()
419         return true
420 }
421
422 // ParagraphPrevious moves the cursor to the previous empty line, or beginning of the buffer if there's none
423 func (h *BufPane) ParagraphPrevious() bool {
424         var line int
425         for line = h.Cursor.Y; line > 0; line-- {
426                 if len(h.Buf.LineBytes(line)) == 0 && line != h.Cursor.Y {
427                         h.Cursor.X = 0
428                         h.Cursor.Y = line
429                         break
430                 }
431         }
432         // If no empty line found. move cursor to end of buffer
433         if line == 0 {
434                 h.Cursor.Loc = h.Buf.Start()
435         }
436         h.Relocate()
437         return true
438 }
439
440 // ParagraphNext moves the cursor to the next empty line, or end of the buffer if there's none
441 func (h *BufPane) ParagraphNext() bool {
442         var line int
443         for line = h.Cursor.Y; line < h.Buf.LinesNum(); line++ {
444                 if len(h.Buf.LineBytes(line)) == 0 && line != h.Cursor.Y {
445                         h.Cursor.X = 0
446                         h.Cursor.Y = line
447                         break
448                 }
449         }
450         // If no empty line found. move cursor to end of buffer
451         if line == h.Buf.LinesNum() {
452                 h.Cursor.Loc = h.Buf.End()
453         }
454         h.Relocate()
455         return true
456 }
457
458 // Retab changes all tabs to spaces or all spaces to tabs depending
459 // on the user's settings
460 func (h *BufPane) Retab() bool {
461         h.Buf.Retab()
462         h.Relocate()
463         return true
464 }
465
466 // CursorStart moves the cursor to the start of the buffer
467 func (h *BufPane) CursorStart() bool {
468         h.Cursor.Deselect(true)
469         h.Cursor.X = 0
470         h.Cursor.Y = 0
471         h.Cursor.StoreVisualX()
472         h.Relocate()
473         return true
474 }
475
476 // CursorEnd moves the cursor to the end of the buffer
477 func (h *BufPane) CursorEnd() bool {
478         h.Cursor.Deselect(true)
479         h.Cursor.Loc = h.Buf.End()
480         h.Cursor.StoreVisualX()
481         h.Relocate()
482         return true
483 }
484
485 // SelectToStart selects the text from the cursor to the start of the buffer
486 func (h *BufPane) SelectToStart() bool {
487         if !h.Cursor.HasSelection() {
488                 h.Cursor.OrigSelection[0] = h.Cursor.Loc
489         }
490         h.CursorStart()
491         h.Cursor.SelectTo(h.Buf.Start())
492         h.Relocate()
493         return true
494 }
495
496 // SelectToEnd selects the text from the cursor to the end of the buffer
497 func (h *BufPane) SelectToEnd() bool {
498         if !h.Cursor.HasSelection() {
499                 h.Cursor.OrigSelection[0] = h.Cursor.Loc
500         }
501         h.CursorEnd()
502         h.Cursor.SelectTo(h.Buf.End())
503         h.Relocate()
504         return true
505 }
506
507 // InsertNewline inserts a newline plus possible some whitespace if autoindent is on
508 func (h *BufPane) InsertNewline() bool {
509         // Insert a newline
510         if h.Cursor.HasSelection() {
511                 h.Cursor.DeleteSelection()
512                 h.Cursor.ResetSelection()
513         }
514
515         ws := util.GetLeadingWhitespace(h.Buf.LineBytes(h.Cursor.Y))
516         cx := h.Cursor.X
517         h.Buf.Insert(h.Cursor.Loc, "\n")
518         // h.Cursor.Right()
519
520         if h.Buf.Settings["autoindent"].(bool) {
521                 if cx < len(ws) {
522                         ws = ws[0:cx]
523                 }
524                 h.Buf.Insert(h.Cursor.Loc, string(ws))
525                 // for i := 0; i < len(ws); i++ {
526                 //      h.Cursor.Right()
527                 // }
528
529                 // Remove the whitespaces if keepautoindent setting is off
530                 if util.IsSpacesOrTabs(h.Buf.LineBytes(h.Cursor.Y-1)) && !h.Buf.Settings["keepautoindent"].(bool) {
531                         line := h.Buf.LineBytes(h.Cursor.Y - 1)
532                         h.Buf.Remove(buffer.Loc{X: 0, Y: h.Cursor.Y - 1}, buffer.Loc{X: util.CharacterCount(line), Y: h.Cursor.Y - 1})
533                 }
534         }
535         h.Cursor.LastVisualX = h.Cursor.GetVisualX()
536         h.Relocate()
537         return true
538 }
539
540 // Backspace deletes the previous character
541 func (h *BufPane) Backspace() bool {
542         if h.Cursor.HasSelection() {
543                 h.Cursor.DeleteSelection()
544                 h.Cursor.ResetSelection()
545         } else if h.Cursor.Loc.GreaterThan(h.Buf.Start()) {
546                 // We have to do something a bit hacky here because we want to
547                 // delete the line by first moving left and then deleting backwards
548                 // but the undo redo would place the cursor in the wrong place
549                 // So instead we move left, save the position, move back, delete
550                 // and restore the position
551
552                 // If the user is using spaces instead of tabs and they are deleting
553                 // whitespace at the start of the line, we should delete as if it's a
554                 // tab (tabSize number of spaces)
555                 lineStart := util.SliceStart(h.Buf.LineBytes(h.Cursor.Y), h.Cursor.X)
556                 tabSize := int(h.Buf.Settings["tabsize"].(float64))
557                 if h.Buf.Settings["tabstospaces"].(bool) && util.IsSpaces(lineStart) && len(lineStart) != 0 && util.CharacterCount(lineStart)%tabSize == 0 {
558                         loc := h.Cursor.Loc
559                         h.Buf.Remove(loc.Move(-tabSize, h.Buf), loc)
560                 } else {
561                         loc := h.Cursor.Loc
562                         h.Buf.Remove(loc.Move(-1, h.Buf), loc)
563                 }
564         }
565         h.Cursor.LastVisualX = h.Cursor.GetVisualX()
566         h.Relocate()
567         return true
568 }
569
570 // DeleteWordRight deletes the word to the right of the cursor
571 func (h *BufPane) DeleteWordRight() bool {
572         h.SelectWordRight()
573         if h.Cursor.HasSelection() {
574                 h.Cursor.DeleteSelection()
575                 h.Cursor.ResetSelection()
576         }
577         h.Relocate()
578         return true
579 }
580
581 // DeleteWordLeft deletes the word to the left of the cursor
582 func (h *BufPane) DeleteWordLeft() bool {
583         h.SelectWordLeft()
584         if h.Cursor.HasSelection() {
585                 h.Cursor.DeleteSelection()
586                 h.Cursor.ResetSelection()
587         }
588         h.Relocate()
589         return true
590 }
591
592 // Delete deletes the next character
593 func (h *BufPane) Delete() bool {
594         if h.Cursor.HasSelection() {
595                 h.Cursor.DeleteSelection()
596                 h.Cursor.ResetSelection()
597         } else {
598                 loc := h.Cursor.Loc
599                 if loc.LessThan(h.Buf.End()) {
600                         h.Buf.Remove(loc, loc.Move(1, h.Buf))
601                 }
602         }
603         h.Relocate()
604         return true
605 }
606
607 // IndentSelection indents the current selection
608 func (h *BufPane) IndentSelection() bool {
609         if h.Cursor.HasSelection() {
610                 start := h.Cursor.CurSelection[0]
611                 end := h.Cursor.CurSelection[1]
612                 if end.Y < start.Y {
613                         start, end = end, start
614                         h.Cursor.SetSelectionStart(start)
615                         h.Cursor.SetSelectionEnd(end)
616                 }
617
618                 startY := start.Y
619                 endY := end.Move(-1, h.Buf).Y
620                 endX := end.Move(-1, h.Buf).X
621                 tabsize := int(h.Buf.Settings["tabsize"].(float64))
622                 indentsize := len(h.Buf.IndentString(tabsize))
623                 for y := startY; y <= endY; y++ {
624                         if len(h.Buf.LineBytes(y)) > 0 {
625                                 h.Buf.Insert(buffer.Loc{X: 0, Y: y}, h.Buf.IndentString(tabsize))
626                                 if y == startY && start.X > 0 {
627                                         h.Cursor.SetSelectionStart(start.Move(indentsize, h.Buf))
628                                 }
629                                 if y == endY {
630                                         h.Cursor.SetSelectionEnd(buffer.Loc{X: endX + indentsize + 1, Y: endY})
631                                 }
632                         }
633                 }
634                 h.Buf.RelocateCursors()
635
636                 h.Relocate()
637                 return true
638         }
639         return false
640 }
641
642 // IndentLine moves the current line forward one indentation
643 func (h *BufPane) IndentLine() bool {
644         if h.Cursor.HasSelection() {
645                 return false
646         }
647
648         tabsize := int(h.Buf.Settings["tabsize"].(float64))
649         indentstr := h.Buf.IndentString(tabsize)
650         h.Buf.Insert(buffer.Loc{X: 0, Y: h.Cursor.Y}, indentstr)
651         h.Buf.RelocateCursors()
652         h.Relocate()
653         return true
654 }
655
656 // OutdentLine moves the current line back one indentation
657 func (h *BufPane) OutdentLine() bool {
658         if h.Cursor.HasSelection() {
659                 return false
660         }
661
662         for x := 0; x < len(h.Buf.IndentString(util.IntOpt(h.Buf.Settings["tabsize"]))); x++ {
663                 if len(util.GetLeadingWhitespace(h.Buf.LineBytes(h.Cursor.Y))) == 0 {
664                         break
665                 }
666                 h.Buf.Remove(buffer.Loc{X: 0, Y: h.Cursor.Y}, buffer.Loc{X: 1, Y: h.Cursor.Y})
667         }
668         h.Buf.RelocateCursors()
669         h.Relocate()
670         return true
671 }
672
673 // OutdentSelection takes the current selection and moves it back one indent level
674 func (h *BufPane) OutdentSelection() bool {
675         if h.Cursor.HasSelection() {
676                 start := h.Cursor.CurSelection[0]
677                 end := h.Cursor.CurSelection[1]
678                 if end.Y < start.Y {
679                         start, end = end, start
680                         h.Cursor.SetSelectionStart(start)
681                         h.Cursor.SetSelectionEnd(end)
682                 }
683
684                 startY := start.Y
685                 endY := end.Move(-1, h.Buf).Y
686                 for y := startY; y <= endY; y++ {
687                         for x := 0; x < len(h.Buf.IndentString(util.IntOpt(h.Buf.Settings["tabsize"]))); x++ {
688                                 if len(util.GetLeadingWhitespace(h.Buf.LineBytes(y))) == 0 {
689                                         break
690                                 }
691                                 h.Buf.Remove(buffer.Loc{X: 0, Y: y}, buffer.Loc{X: 1, Y: y})
692                         }
693                 }
694                 h.Buf.RelocateCursors()
695
696                 h.Relocate()
697                 return true
698         }
699         return false
700 }
701
702 // Autocomplete cycles the suggestions and performs autocompletion if there are suggestions
703 func (h *BufPane) Autocomplete() bool {
704         b := h.Buf
705
706         if h.Cursor.HasSelection() {
707                 return false
708         }
709
710         if h.Cursor.X == 0 {
711                 return false
712         }
713         r := h.Cursor.RuneUnder(h.Cursor.X)
714         prev := h.Cursor.RuneUnder(h.Cursor.X - 1)
715         if !util.IsAutocomplete(prev) || !util.IsNonAlphaNumeric(r) {
716                 // don't autocomplete if cursor is on alpha numeric character (middle of a word)
717                 return false
718         }
719
720         if b.HasSuggestions {
721                 b.CycleAutocomplete(true)
722                 return true
723         }
724         return b.Autocomplete(buffer.BufferComplete)
725 }
726
727 // CycleAutocompleteBack cycles back in the autocomplete suggestion list
728 func (h *BufPane) CycleAutocompleteBack() bool {
729         if h.Cursor.HasSelection() {
730                 return false
731         }
732
733         if h.Buf.HasSuggestions {
734                 h.Buf.CycleAutocomplete(false)
735                 return true
736         }
737         return false
738 }
739
740 // InsertTab inserts a tab or spaces
741 func (h *BufPane) InsertTab() bool {
742         b := h.Buf
743         indent := b.IndentString(util.IntOpt(b.Settings["tabsize"]))
744         tabBytes := len(indent)
745         bytesUntilIndent := tabBytes - (h.Cursor.GetVisualX() % tabBytes)
746         b.Insert(h.Cursor.Loc, indent[:bytesUntilIndent])
747         h.Relocate()
748         return true
749 }
750
751 // SaveAll saves all open buffers
752 func (h *BufPane) SaveAll() bool {
753         for _, b := range buffer.OpenBuffers {
754                 b.Save()
755         }
756         return true
757 }
758
759 // SaveCB performs a save and does a callback at the very end (after all prompts have been resolved)
760 func (h *BufPane) SaveCB(action string, callback func()) bool {
761         // If this is an empty buffer, ask for a filename
762         if h.Buf.Path == "" {
763                 h.SaveAsCB(action, callback)
764         } else {
765                 noPrompt := h.saveBufToFile(h.Buf.Path, action, callback)
766                 if noPrompt {
767                         return true
768                 }
769         }
770         return false
771 }
772
773 // Save the buffer to disk
774 func (h *BufPane) Save() bool {
775         return h.SaveCB("Save", nil)
776 }
777
778 // SaveAsCB performs a save as and does a callback at the very end (after all prompts have been resolved)
779 // The callback is only called if the save was successful
780 func (h *BufPane) SaveAsCB(action string, callback func()) bool {
781         InfoBar.Prompt("Filename: ", "", "Save", nil, func(resp string, canceled bool) {
782                 if !canceled {
783                         // the filename might or might not be quoted, so unquote first then join the strings.
784                         args, err := shellquote.Split(resp)
785                         if err != nil {
786                                 InfoBar.Error("Error parsing arguments: ", err)
787                                 return
788                         }
789                         if len(args) == 0 {
790                                 InfoBar.Error("No filename given")
791                                 return
792                         }
793                         filename := strings.Join(args, " ")
794                         noPrompt := h.saveBufToFile(filename, action, callback)
795                         if noPrompt {
796                                 h.completeAction(action)
797                         }
798                 }
799         })
800         return false
801 }
802
803 // SaveAs saves the buffer to disk with the given name
804 func (h *BufPane) SaveAs() bool {
805         return h.SaveAsCB("SaveAs", nil)
806 }
807
808 // This function saves the buffer to `filename` and changes the buffer's path and name
809 // to `filename` if the save is successful
810 // The callback is only called if the save was successful
811 func (h *BufPane) saveBufToFile(filename string, action string, callback func()) bool {
812         err := h.Buf.SaveAs(filename)
813         if err != nil {
814                 if errors.Is(err, fs.ErrPermission) {
815                         saveWithSudo := func() {
816                                 err = h.Buf.SaveAsWithSudo(filename)
817                                 if err != nil {
818                                         InfoBar.Error(err)
819                                 } else {
820                                         h.Buf.Path = filename
821                                         h.Buf.SetName(filename)
822                                         InfoBar.Message("Saved " + filename)
823                                         if callback != nil {
824                                                 callback()
825                                         }
826                                 }
827                         }
828                         if h.Buf.Settings["autosu"].(bool) {
829                                 saveWithSudo()
830                         } else {
831                                 InfoBar.YNPrompt(
832                                         fmt.Sprintf("Permission denied. Do you want to save this file using %s? (y,n)", config.GlobalSettings["sucmd"].(string)),
833                                         func(yes, canceled bool) {
834                                                 if yes && !canceled {
835                                                         saveWithSudo()
836                                                         h.completeAction(action)
837                                                 }
838                                         },
839                                 )
840                                 return false
841                         }
842                 } else {
843                         InfoBar.Error(err)
844                 }
845         } else {
846                 h.Buf.Path = filename
847                 h.Buf.SetName(filename)
848                 InfoBar.Message("Saved " + filename)
849                 if callback != nil {
850                         callback()
851                 }
852         }
853         return true
854 }
855
856 // Find opens a prompt and searches forward for the input
857 func (h *BufPane) Find() bool {
858         return h.find(true)
859 }
860
861 // FindLiteral is the same as Find() but does not support regular expressions
862 func (h *BufPane) FindLiteral() bool {
863         return h.find(false)
864 }
865
866 // Search searches for a given string/regex in the buffer and selects the next
867 // match if a match is found
868 // This function behaves the same way as Find and FindLiteral actions:
869 // it affects the buffer's LastSearch and LastSearchRegex (saved searches)
870 // for use with FindNext and FindPrevious, and turns HighlightSearch on or off
871 // according to hlsearch setting
872 func (h *BufPane) Search(str string, useRegex bool, searchDown bool) error {
873         match, found, err := h.Buf.FindNext(str, h.Buf.Start(), h.Buf.End(), h.Cursor.Loc, searchDown, useRegex)
874         if err != nil {
875                 return err
876         }
877         if found {
878                 h.Cursor.SetSelectionStart(match[0])
879                 h.Cursor.SetSelectionEnd(match[1])
880                 h.Cursor.OrigSelection[0] = h.Cursor.CurSelection[0]
881                 h.Cursor.OrigSelection[1] = h.Cursor.CurSelection[1]
882                 h.Cursor.GotoLoc(h.Cursor.CurSelection[1])
883                 h.Buf.LastSearch = str
884                 h.Buf.LastSearchRegex = useRegex
885                 h.Buf.HighlightSearch = h.Buf.Settings["hlsearch"].(bool)
886                 h.Relocate()
887         } else {
888                 h.Cursor.ResetSelection()
889         }
890         return nil
891 }
892
893 func (h *BufPane) find(useRegex bool) bool {
894         h.searchOrig = h.Cursor.Loc
895         prompt := "Find: "
896         if useRegex {
897                 prompt = "Find (regex): "
898         }
899         var eventCallback func(resp string)
900         if h.Buf.Settings["incsearch"].(bool) {
901                 eventCallback = func(resp string) {
902                         match, found, _ := h.Buf.FindNext(resp, h.Buf.Start(), h.Buf.End(), h.searchOrig, true, useRegex)
903                         if found {
904                                 h.Cursor.SetSelectionStart(match[0])
905                                 h.Cursor.SetSelectionEnd(match[1])
906                                 h.Cursor.OrigSelection[0] = h.Cursor.CurSelection[0]
907                                 h.Cursor.OrigSelection[1] = h.Cursor.CurSelection[1]
908                                 h.Cursor.GotoLoc(match[1])
909                         } else {
910                                 h.Cursor.GotoLoc(h.searchOrig)
911                                 h.Cursor.ResetSelection()
912                         }
913                         h.Relocate()
914                 }
915         }
916         findCallback := func(resp string, canceled bool) {
917                 // Finished callback
918                 if !canceled {
919                         match, found, err := h.Buf.FindNext(resp, h.Buf.Start(), h.Buf.End(), h.searchOrig, true, useRegex)
920                         if err != nil {
921                                 InfoBar.Error(err)
922                         }
923                         if found {
924                                 h.Cursor.SetSelectionStart(match[0])
925                                 h.Cursor.SetSelectionEnd(match[1])
926                                 h.Cursor.OrigSelection[0] = h.Cursor.CurSelection[0]
927                                 h.Cursor.OrigSelection[1] = h.Cursor.CurSelection[1]
928                                 h.Cursor.GotoLoc(h.Cursor.CurSelection[1])
929                                 h.Buf.LastSearch = resp
930                                 h.Buf.LastSearchRegex = useRegex
931                                 h.Buf.HighlightSearch = h.Buf.Settings["hlsearch"].(bool)
932                         } else {
933                                 h.Cursor.ResetSelection()
934                                 InfoBar.Message("No matches found")
935                         }
936                 } else {
937                         h.Cursor.ResetSelection()
938                 }
939                 h.Relocate()
940         }
941         pattern := string(h.Cursor.GetSelection())
942         if eventCallback != nil && pattern != "" {
943                 eventCallback(pattern)
944         }
945         InfoBar.Prompt(prompt, pattern, "Find", eventCallback, findCallback)
946         if pattern != "" {
947                 InfoBar.SelectAll()
948         }
949         return true
950 }
951
952 // ToggleHighlightSearch toggles highlighting all instances of the last used search term
953 func (h *BufPane) ToggleHighlightSearch() bool {
954         h.Buf.HighlightSearch = !h.Buf.HighlightSearch
955         return true
956 }
957
958 // UnhighlightSearch unhighlights all instances of the last used search term
959 func (h *BufPane) UnhighlightSearch() bool {
960         h.Buf.HighlightSearch = false
961         return true
962 }
963
964 // FindNext searches forwards for the last used search term
965 func (h *BufPane) FindNext() bool {
966         // If the cursor is at the start of a selection and we search we want
967         // to search from the end of the selection in the case that
968         // the selection is a search result in which case we wouldn't move at
969         // at all which would be bad
970         searchLoc := h.Cursor.Loc
971         if h.Cursor.HasSelection() {
972                 searchLoc = h.Cursor.CurSelection[1]
973         }
974         match, found, err := h.Buf.FindNext(h.Buf.LastSearch, h.Buf.Start(), h.Buf.End(), searchLoc, true, h.Buf.LastSearchRegex)
975         if err != nil {
976                 InfoBar.Error(err)
977         }
978         if found {
979                 h.Cursor.SetSelectionStart(match[0])
980                 h.Cursor.SetSelectionEnd(match[1])
981                 h.Cursor.OrigSelection[0] = h.Cursor.CurSelection[0]
982                 h.Cursor.OrigSelection[1] = h.Cursor.CurSelection[1]
983                 h.Cursor.Loc = h.Cursor.CurSelection[1]
984         } else {
985                 h.Cursor.ResetSelection()
986         }
987         h.Relocate()
988         return true
989 }
990
991 // FindPrevious searches backwards for the last used search term
992 func (h *BufPane) FindPrevious() bool {
993         // If the cursor is at the end of a selection and we search we want
994         // to search from the beginning of the selection in the case that
995         // the selection is a search result in which case we wouldn't move at
996         // at all which would be bad
997         searchLoc := h.Cursor.Loc
998         if h.Cursor.HasSelection() {
999                 searchLoc = h.Cursor.CurSelection[0]
1000         }
1001         match, found, err := h.Buf.FindNext(h.Buf.LastSearch, h.Buf.Start(), h.Buf.End(), searchLoc, false, h.Buf.LastSearchRegex)
1002         if err != nil {
1003                 InfoBar.Error(err)
1004         }
1005         if found {
1006                 h.Cursor.SetSelectionStart(match[0])
1007                 h.Cursor.SetSelectionEnd(match[1])
1008                 h.Cursor.OrigSelection[0] = h.Cursor.CurSelection[0]
1009                 h.Cursor.OrigSelection[1] = h.Cursor.CurSelection[1]
1010                 h.Cursor.Loc = h.Cursor.CurSelection[1]
1011         } else {
1012                 h.Cursor.ResetSelection()
1013         }
1014         h.Relocate()
1015         return true
1016 }
1017
1018 // Undo undoes the last action
1019 func (h *BufPane) Undo() bool {
1020         h.Buf.Undo()
1021         InfoBar.Message("Undid action")
1022         h.Relocate()
1023         return true
1024 }
1025
1026 // Redo redoes the last action
1027 func (h *BufPane) Redo() bool {
1028         h.Buf.Redo()
1029         InfoBar.Message("Redid action")
1030         h.Relocate()
1031         return true
1032 }
1033
1034 // Copy the selection to the system clipboard
1035 func (h *BufPane) Copy() bool {
1036         if h.Cursor.HasSelection() {
1037                 h.Cursor.CopySelection(clipboard.ClipboardReg)
1038                 h.freshClip = true
1039                 InfoBar.Message("Copied selection")
1040         }
1041         h.Relocate()
1042         return true
1043 }
1044
1045 // CopyLine copies the current line to the clipboard
1046 func (h *BufPane) CopyLine() bool {
1047         if h.Cursor.HasSelection() {
1048                 return false
1049         }
1050         h.Cursor.SelectLine()
1051         h.Cursor.CopySelection(clipboard.ClipboardReg)
1052         h.freshClip = true
1053         InfoBar.Message("Copied line")
1054
1055         h.Cursor.Deselect(true)
1056         h.Relocate()
1057         return true
1058 }
1059
1060 // CutLine cuts the current line to the clipboard
1061 func (h *BufPane) CutLine() bool {
1062         h.Cursor.SelectLine()
1063         if !h.Cursor.HasSelection() {
1064                 return false
1065         }
1066         if h.freshClip {
1067                 if h.Cursor.HasSelection() {
1068                         if clip, err := clipboard.Read(clipboard.ClipboardReg); err != nil {
1069                                 InfoBar.Error(err)
1070                         } else {
1071                                 clipboard.WriteMulti(clip+string(h.Cursor.GetSelection()), clipboard.ClipboardReg, h.Cursor.Num, h.Buf.NumCursors())
1072                         }
1073                 }
1074         } else if time.Since(h.lastCutTime)/time.Second > 10*time.Second || !h.freshClip {
1075                 h.Copy()
1076         }
1077         h.freshClip = true
1078         h.lastCutTime = time.Now()
1079         h.Cursor.DeleteSelection()
1080         h.Cursor.ResetSelection()
1081         InfoBar.Message("Cut line")
1082         h.Relocate()
1083         return true
1084 }
1085
1086 // Cut the selection to the system clipboard
1087 func (h *BufPane) Cut() bool {
1088         if h.Cursor.HasSelection() {
1089                 h.Cursor.CopySelection(clipboard.ClipboardReg)
1090                 h.Cursor.DeleteSelection()
1091                 h.Cursor.ResetSelection()
1092                 h.freshClip = true
1093                 InfoBar.Message("Cut selection")
1094
1095                 h.Relocate()
1096                 return true
1097         }
1098         return h.CutLine()
1099 }
1100
1101 // DuplicateLine duplicates the current line or selection
1102 func (h *BufPane) DuplicateLine() bool {
1103         var infoMessage = "Duplicated line"
1104         if h.Cursor.HasSelection() {
1105                 infoMessage = "Duplicated selection"
1106                 h.Buf.Insert(h.Cursor.CurSelection[1], string(h.Cursor.GetSelection()))
1107         } else {
1108                 h.Cursor.End()
1109                 h.Buf.Insert(h.Cursor.Loc, "\n"+string(h.Buf.LineBytes(h.Cursor.Y)))
1110                 // h.Cursor.Right()
1111         }
1112
1113         InfoBar.Message(infoMessage)
1114         h.Relocate()
1115         return true
1116 }
1117
1118 // DeleteLine deletes the current line
1119 func (h *BufPane) DeleteLine() bool {
1120         h.Cursor.SelectLine()
1121         if !h.Cursor.HasSelection() {
1122                 return false
1123         }
1124         h.Cursor.DeleteSelection()
1125         h.Cursor.ResetSelection()
1126         InfoBar.Message("Deleted line")
1127         h.Relocate()
1128         return true
1129 }
1130
1131 // MoveLinesUp moves up the current line or selected lines if any
1132 func (h *BufPane) MoveLinesUp() bool {
1133         if h.Cursor.HasSelection() {
1134                 if h.Cursor.CurSelection[0].Y == 0 {
1135                         InfoBar.Message("Cannot move further up")
1136                         return false
1137                 }
1138                 start := h.Cursor.CurSelection[0].Y
1139                 end := h.Cursor.CurSelection[1].Y
1140                 sel := 1
1141                 if start > end {
1142                         end, start = start, end
1143                         sel = 0
1144                 }
1145
1146                 compensate := false
1147                 if h.Cursor.CurSelection[sel].X != 0 {
1148                         end++
1149                 } else {
1150                         compensate = true
1151                 }
1152
1153                 h.Buf.MoveLinesUp(
1154                         start,
1155                         end,
1156                 )
1157                 if compensate {
1158                         h.Cursor.CurSelection[sel].Y -= 1
1159                 }
1160         } else {
1161                 if h.Cursor.Loc.Y == 0 {
1162                         InfoBar.Message("Cannot move further up")
1163                         return false
1164                 }
1165                 h.Buf.MoveLinesUp(
1166                         h.Cursor.Loc.Y,
1167                         h.Cursor.Loc.Y+1,
1168                 )
1169         }
1170
1171         h.Relocate()
1172         return true
1173 }
1174
1175 // MoveLinesDown moves down the current line or selected lines if any
1176 func (h *BufPane) MoveLinesDown() bool {
1177         if h.Cursor.HasSelection() {
1178                 if h.Cursor.CurSelection[1].Y >= h.Buf.LinesNum() {
1179                         InfoBar.Message("Cannot move further down")
1180                         return false
1181                 }
1182                 start := h.Cursor.CurSelection[0].Y
1183                 end := h.Cursor.CurSelection[1].Y
1184                 sel := 1
1185                 if start > end {
1186                         end, start = start, end
1187                         sel = 0
1188                 }
1189
1190                 if h.Cursor.CurSelection[sel].X != 0 {
1191                         end++
1192                 }
1193
1194                 h.Buf.MoveLinesDown(
1195                         start,
1196                         end,
1197                 )
1198         } else {
1199                 if h.Cursor.Loc.Y >= h.Buf.LinesNum()-1 {
1200                         InfoBar.Message("Cannot move further down")
1201                         return false
1202                 }
1203                 h.Buf.MoveLinesDown(
1204                         h.Cursor.Loc.Y,
1205                         h.Cursor.Loc.Y+1,
1206                 )
1207         }
1208
1209         h.Relocate()
1210         return true
1211 }
1212
1213 // Paste whatever is in the system clipboard into the buffer
1214 // Delete and paste if the user has a selection
1215 func (h *BufPane) Paste() bool {
1216         clip, err := clipboard.ReadMulti(clipboard.ClipboardReg, h.Cursor.Num, h.Buf.NumCursors())
1217         if err != nil {
1218                 InfoBar.Error(err)
1219         } else {
1220                 h.paste(clip)
1221         }
1222         h.Relocate()
1223         return true
1224 }
1225
1226 // PastePrimary pastes from the primary clipboard (only use on linux)
1227 func (h *BufPane) PastePrimary() bool {
1228         clip, err := clipboard.ReadMulti(clipboard.PrimaryReg, h.Cursor.Num, h.Buf.NumCursors())
1229         if err != nil {
1230                 InfoBar.Error(err)
1231         } else {
1232                 h.paste(clip)
1233         }
1234         h.Relocate()
1235         return true
1236 }
1237
1238 func (h *BufPane) paste(clip string) {
1239         if h.Buf.Settings["smartpaste"].(bool) {
1240                 if h.Cursor.X > 0 && len(util.GetLeadingWhitespace([]byte(strings.TrimLeft(clip, "\r\n")))) == 0 {
1241                         leadingWS := util.GetLeadingWhitespace(h.Buf.LineBytes(h.Cursor.Y))
1242                         clip = strings.ReplaceAll(clip, "\n", "\n"+string(leadingWS))
1243                 }
1244         }
1245
1246         if h.Cursor.HasSelection() {
1247                 h.Cursor.DeleteSelection()
1248                 h.Cursor.ResetSelection()
1249         }
1250
1251         h.Buf.Insert(h.Cursor.Loc, clip)
1252         // h.Cursor.Loc = h.Cursor.Loc.Move(Count(clip), h.Buf)
1253         h.freshClip = false
1254         InfoBar.Message("Pasted clipboard")
1255 }
1256
1257 // JumpToMatchingBrace moves the cursor to the matching brace if it is
1258 // currently on a brace
1259 func (h *BufPane) JumpToMatchingBrace() bool {
1260         for _, bp := range buffer.BracePairs {
1261                 r := h.Cursor.RuneUnder(h.Cursor.X)
1262                 rl := h.Cursor.RuneUnder(h.Cursor.X - 1)
1263                 if r == bp[0] || r == bp[1] || rl == bp[0] || rl == bp[1] {
1264                         matchingBrace, left, found := h.Buf.FindMatchingBrace(bp, h.Cursor.Loc)
1265                         if found {
1266                                 if left {
1267                                         h.Cursor.GotoLoc(matchingBrace)
1268                                 } else {
1269                                         h.Cursor.GotoLoc(matchingBrace.Move(1, h.Buf))
1270                                 }
1271                                 h.Relocate()
1272                                 return true
1273                         }
1274                 }
1275         }
1276         return false
1277 }
1278
1279 // SelectAll selects the entire buffer
1280 func (h *BufPane) SelectAll() bool {
1281         h.Cursor.SetSelectionStart(h.Buf.Start())
1282         h.Cursor.SetSelectionEnd(h.Buf.End())
1283         // Put the cursor at the beginning
1284         h.Cursor.X = 0
1285         h.Cursor.Y = 0
1286         h.Relocate()
1287         return true
1288 }
1289
1290 // OpenFile opens a new file in the buffer
1291 func (h *BufPane) OpenFile() bool {
1292         InfoBar.Prompt("> ", "open ", "Open", nil, func(resp string, canceled bool) {
1293                 if !canceled {
1294                         h.HandleCommand(resp)
1295                 }
1296         })
1297         return true
1298 }
1299
1300 // OpenFile opens a new file in the buffer
1301 func (h *BufPane) JumpLine() bool {
1302         InfoBar.Prompt("> ", "goto ", "Command", nil, func(resp string, canceled bool) {
1303                 if !canceled {
1304                         h.HandleCommand(resp)
1305                 }
1306         })
1307         return true
1308 }
1309
1310 // Start moves the viewport to the start of the buffer
1311 func (h *BufPane) Start() bool {
1312         v := h.GetView()
1313         v.StartLine = display.SLoc{0, 0}
1314         h.SetView(v)
1315         return true
1316 }
1317
1318 // End moves the viewport to the end of the buffer
1319 func (h *BufPane) End() bool {
1320         v := h.GetView()
1321         v.StartLine = h.Scroll(h.SLocFromLoc(h.Buf.End()), -h.BufView().Height+1)
1322         h.SetView(v)
1323         return true
1324 }
1325
1326 // PageUp scrolls the view up a page
1327 func (h *BufPane) PageUp() bool {
1328         h.ScrollUp(h.BufView().Height)
1329         return true
1330 }
1331
1332 // PageDown scrolls the view down a page
1333 func (h *BufPane) PageDown() bool {
1334         h.ScrollDown(h.BufView().Height)
1335         h.ScrollAdjust()
1336         return true
1337 }
1338
1339 // SelectPageUp selects up one page
1340 func (h *BufPane) SelectPageUp() bool {
1341         if !h.Cursor.HasSelection() {
1342                 h.Cursor.OrigSelection[0] = h.Cursor.Loc
1343         }
1344         h.MoveCursorUp(h.BufView().Height)
1345         h.Cursor.SelectTo(h.Cursor.Loc)
1346         h.Relocate()
1347         return true
1348 }
1349
1350 // SelectPageDown selects down one page
1351 func (h *BufPane) SelectPageDown() bool {
1352         if !h.Cursor.HasSelection() {
1353                 h.Cursor.OrigSelection[0] = h.Cursor.Loc
1354         }
1355         h.MoveCursorDown(h.BufView().Height)
1356         h.Cursor.SelectTo(h.Cursor.Loc)
1357         h.Relocate()
1358         return true
1359 }
1360
1361 // CursorPageUp places the cursor a page up
1362 func (h *BufPane) CursorPageUp() bool {
1363         h.Cursor.Deselect(true)
1364
1365         if h.Cursor.HasSelection() {
1366                 h.Cursor.Loc = h.Cursor.CurSelection[0]
1367                 h.Cursor.ResetSelection()
1368                 h.Cursor.StoreVisualX()
1369         }
1370         h.MoveCursorUp(h.BufView().Height)
1371         h.Relocate()
1372         return true
1373 }
1374
1375 // CursorPageDown places the cursor a page up
1376 func (h *BufPane) CursorPageDown() bool {
1377         h.Cursor.Deselect(false)
1378
1379         if h.Cursor.HasSelection() {
1380                 h.Cursor.Loc = h.Cursor.CurSelection[1]
1381                 h.Cursor.ResetSelection()
1382                 h.Cursor.StoreVisualX()
1383         }
1384         h.MoveCursorDown(h.BufView().Height)
1385         h.Relocate()
1386         return true
1387 }
1388
1389 // HalfPageUp scrolls the view up half a page
1390 func (h *BufPane) HalfPageUp() bool {
1391         h.ScrollUp(h.BufView().Height / 2)
1392         return true
1393 }
1394
1395 // HalfPageDown scrolls the view down half a page
1396 func (h *BufPane) HalfPageDown() bool {
1397         h.ScrollDown(h.BufView().Height / 2)
1398         h.ScrollAdjust()
1399         return true
1400 }
1401
1402 // ToggleDiffGutter turns the diff gutter off and on
1403 func (h *BufPane) ToggleDiffGutter() bool {
1404         if !h.Buf.Settings["diffgutter"].(bool) {
1405                 h.Buf.Settings["diffgutter"] = true
1406                 h.Buf.UpdateDiff(func(synchronous bool) {
1407                         screen.Redraw()
1408                 })
1409                 InfoBar.Message("Enabled diff gutter")
1410         } else {
1411                 h.Buf.Settings["diffgutter"] = false
1412                 InfoBar.Message("Disabled diff gutter")
1413         }
1414         return true
1415 }
1416
1417 // ToggleRuler turns line numbers off and on
1418 func (h *BufPane) ToggleRuler() bool {
1419         if !h.Buf.Settings["ruler"].(bool) {
1420                 h.Buf.Settings["ruler"] = true
1421                 InfoBar.Message("Enabled ruler")
1422         } else {
1423                 h.Buf.Settings["ruler"] = false
1424                 InfoBar.Message("Disabled ruler")
1425         }
1426         return true
1427 }
1428
1429 // ClearStatus clears the messenger bar
1430 func (h *BufPane) ClearStatus() bool {
1431         InfoBar.Message("")
1432         return true
1433 }
1434
1435 // ToggleHelp toggles the help screen
1436 func (h *BufPane) ToggleHelp() bool {
1437         if h.Buf.Type == buffer.BTHelp {
1438                 h.Quit()
1439         } else {
1440                 h.openHelp("help")
1441         }
1442         return true
1443 }
1444
1445 // ToggleKeyMenu toggles the keymenu option and resizes all tabs
1446 func (h *BufPane) ToggleKeyMenu() bool {
1447         config.GlobalSettings["keymenu"] = !config.GetGlobalOption("keymenu").(bool)
1448         Tabs.Resize()
1449         return true
1450 }
1451
1452 // ShellMode opens a terminal to run a shell command
1453 func (h *BufPane) ShellMode() bool {
1454         InfoBar.Prompt("$ ", "", "Shell", nil, func(resp string, canceled bool) {
1455                 if !canceled {
1456                         // The true here is for openTerm to make the command interactive
1457                         shell.RunInteractiveShell(resp, true, false)
1458                 }
1459         })
1460
1461         return true
1462 }
1463
1464 // CommandMode lets the user enter a command
1465 func (h *BufPane) CommandMode() bool {
1466         InfoBar.Prompt("> ", "", "Command", nil, func(resp string, canceled bool) {
1467                 if !canceled {
1468                         h.HandleCommand(resp)
1469                 }
1470         })
1471         return true
1472 }
1473
1474 // ToggleOverwriteMode lets the user toggle the text overwrite mode
1475 func (h *BufPane) ToggleOverwriteMode() bool {
1476         h.isOverwriteMode = !h.isOverwriteMode
1477         return true
1478 }
1479
1480 // Escape leaves current mode
1481 func (h *BufPane) Escape() bool {
1482         return true
1483 }
1484
1485 // Deselect deselects on the current cursor
1486 func (h *BufPane) Deselect() bool {
1487         h.Cursor.Deselect(true)
1488         return true
1489 }
1490
1491 // ClearInfo clears the infobar
1492 func (h *BufPane) ClearInfo() bool {
1493         InfoBar.Message("")
1494         return true
1495 }
1496
1497 // ForceQuit closes the current tab or view even if there are unsaved changes
1498 // (no prompt)
1499 func (h *BufPane) ForceQuit() bool {
1500         h.Buf.Close()
1501         if len(MainTab().Panes) > 1 {
1502                 h.Unsplit()
1503         } else if len(Tabs.List) > 1 {
1504                 Tabs.RemoveTab(h.splitID)
1505         } else {
1506                 screen.Screen.Fini()
1507                 InfoBar.Close()
1508                 runtime.Goexit()
1509         }
1510         return true
1511 }
1512
1513 // Quit this will close the current tab or view that is open
1514 func (h *BufPane) Quit() bool {
1515         if h.Buf.Modified() {
1516                 if config.GlobalSettings["autosave"].(float64) > 0 {
1517                         // autosave on means we automatically save when quitting
1518                         h.SaveCB("Quit", func() {
1519                                 h.ForceQuit()
1520                         })
1521                 } else {
1522                         InfoBar.YNPrompt("Save changes to "+h.Buf.GetName()+" before closing? (y,n,esc)", func(yes, canceled bool) {
1523                                 if !canceled && !yes {
1524                                         h.ForceQuit()
1525                                 } else if !canceled && yes {
1526                                         h.SaveCB("Quit", func() {
1527                                                 h.ForceQuit()
1528                                         })
1529                                 }
1530                         })
1531                 }
1532         } else {
1533                 h.ForceQuit()
1534         }
1535         return true
1536 }
1537
1538 // QuitAll quits the whole editor; all splits and tabs
1539 func (h *BufPane) QuitAll() bool {
1540         anyModified := false
1541         for _, b := range buffer.OpenBuffers {
1542                 if b.Modified() {
1543                         anyModified = true
1544                         break
1545                 }
1546         }
1547
1548         quit := func() {
1549                 for _, b := range buffer.OpenBuffers {
1550                         b.Close()
1551                 }
1552                 screen.Screen.Fini()
1553                 InfoBar.Close()
1554                 runtime.Goexit()
1555         }
1556
1557         if anyModified {
1558                 InfoBar.YNPrompt("Quit micro? (all open buffers will be closed without saving)", func(yes, canceled bool) {
1559                         if !canceled && yes {
1560                                 quit()
1561                         }
1562                 })
1563         } else {
1564                 quit()
1565         }
1566
1567         return true
1568 }
1569
1570 // AddTab adds a new tab with an empty buffer
1571 func (h *BufPane) AddTab() bool {
1572         width, height := screen.Screen.Size()
1573         iOffset := config.GetInfoBarOffset()
1574         b := buffer.NewBufferFromString("", "", buffer.BTDefault)
1575         tp := NewTabFromBuffer(0, 0, width, height-iOffset, b)
1576         Tabs.AddTab(tp)
1577         Tabs.SetActive(len(Tabs.List) - 1)
1578
1579         return true
1580 }
1581
1582 // PreviousTab switches to the previous tab in the tab list
1583 func (h *BufPane) PreviousTab() bool {
1584         tabsLen := len(Tabs.List)
1585         a := Tabs.Active() + tabsLen
1586         Tabs.SetActive((a - 1) % tabsLen)
1587
1588         return true
1589 }
1590
1591 // NextTab switches to the next tab in the tab list
1592 func (h *BufPane) NextTab() bool {
1593         a := Tabs.Active()
1594         Tabs.SetActive((a + 1) % len(Tabs.List))
1595
1596         return true
1597 }
1598
1599 // VSplitAction opens an empty vertical split
1600 func (h *BufPane) VSplitAction() bool {
1601         h.VSplitBuf(buffer.NewBufferFromString("", "", buffer.BTDefault))
1602
1603         return true
1604 }
1605
1606 // HSplitAction opens an empty horizontal split
1607 func (h *BufPane) HSplitAction() bool {
1608         h.HSplitBuf(buffer.NewBufferFromString("", "", buffer.BTDefault))
1609
1610         return true
1611 }
1612
1613 // Unsplit closes all splits in the current tab except the active one
1614 func (h *BufPane) Unsplit() bool {
1615         tab := h.tab
1616         n := tab.GetNode(h.splitID)
1617         ok := n.Unsplit()
1618         if ok {
1619                 tab.RemovePane(tab.GetPane(h.splitID))
1620                 tab.Resize()
1621                 tab.SetActive(len(tab.Panes) - 1)
1622
1623                 return true
1624         }
1625         return false
1626 }
1627
1628 // NextSplit changes the view to the next split
1629 func (h *BufPane) NextSplit() bool {
1630         a := h.tab.active
1631         if a < len(h.tab.Panes)-1 {
1632                 a++
1633         } else {
1634                 a = 0
1635         }
1636
1637         h.tab.SetActive(a)
1638
1639         return true
1640 }
1641
1642 // PreviousSplit changes the view to the previous split
1643 func (h *BufPane) PreviousSplit() bool {
1644         a := h.tab.active
1645         if a > 0 {
1646                 a--
1647         } else {
1648                 a = len(h.tab.Panes) - 1
1649         }
1650         h.tab.SetActive(a)
1651
1652         return true
1653 }
1654
1655 var curmacro []interface{}
1656 var recordingMacro bool
1657
1658 // ToggleMacro toggles recording of a macro
1659 func (h *BufPane) ToggleMacro() bool {
1660         recordingMacro = !recordingMacro
1661         if recordingMacro {
1662                 curmacro = []interface{}{}
1663                 InfoBar.Message("Recording")
1664         } else {
1665                 InfoBar.Message("Stopped recording")
1666         }
1667         h.Relocate()
1668         return true
1669 }
1670
1671 // PlayMacro plays back the most recently recorded macro
1672 func (h *BufPane) PlayMacro() bool {
1673         if recordingMacro {
1674                 return false
1675         }
1676         for _, action := range curmacro {
1677                 switch t := action.(type) {
1678                 case rune:
1679                         h.DoRuneInsert(t)
1680                 case func(*BufPane) bool:
1681                         t(h)
1682                 }
1683         }
1684         h.Relocate()
1685         return true
1686 }
1687
1688 // SpawnMultiCursor creates a new multiple cursor at the next occurrence of the current selection or current word
1689 func (h *BufPane) SpawnMultiCursor() bool {
1690         spawner := h.Buf.GetCursor(h.Buf.NumCursors() - 1)
1691         if !spawner.HasSelection() {
1692                 spawner.SelectWord()
1693                 h.multiWord = true
1694                 h.Relocate()
1695                 return true
1696         }
1697
1698         sel := spawner.GetSelection()
1699         searchStart := spawner.CurSelection[1]
1700
1701         search := string(sel)
1702         search = regexp.QuoteMeta(search)
1703         if h.multiWord {
1704                 search = "\\b" + search + "\\b"
1705         }
1706         match, found, err := h.Buf.FindNext(search, h.Buf.Start(), h.Buf.End(), searchStart, true, true)
1707         if err != nil {
1708                 InfoBar.Error(err)
1709         }
1710         if found {
1711                 c := buffer.NewCursor(h.Buf, buffer.Loc{})
1712                 c.SetSelectionStart(match[0])
1713                 c.SetSelectionEnd(match[1])
1714                 c.OrigSelection[0] = c.CurSelection[0]
1715                 c.OrigSelection[1] = c.CurSelection[1]
1716                 c.Loc = c.CurSelection[1]
1717
1718                 h.Buf.AddCursor(c)
1719                 h.Buf.SetCurCursor(h.Buf.NumCursors() - 1)
1720                 h.Buf.MergeCursors()
1721         } else {
1722                 InfoBar.Message("No matches found")
1723         }
1724
1725         h.Relocate()
1726         return true
1727 }
1728
1729 // SpawnMultiCursorUp creates additional cursor, at the same X (if possible), one Y less.
1730 func (h *BufPane) SpawnMultiCursorUp() bool {
1731         if h.Cursor.Y == 0 {
1732                 return false
1733         }
1734         h.Cursor.GotoLoc(buffer.Loc{h.Cursor.X, h.Cursor.Y - 1})
1735         h.Cursor.Relocate()
1736
1737         c := buffer.NewCursor(h.Buf, buffer.Loc{h.Cursor.X, h.Cursor.Y + 1})
1738         h.Buf.AddCursor(c)
1739         h.Buf.SetCurCursor(h.Buf.NumCursors() - 1)
1740         h.Buf.MergeCursors()
1741
1742         h.Relocate()
1743         return true
1744 }
1745
1746 // SpawnMultiCursorDown creates additional cursor, at the same X (if possible), one Y more.
1747 func (h *BufPane) SpawnMultiCursorDown() bool {
1748         if h.Cursor.Y+1 == h.Buf.LinesNum() {
1749                 return false
1750         }
1751         h.Cursor.GotoLoc(buffer.Loc{h.Cursor.X, h.Cursor.Y + 1})
1752         h.Cursor.Relocate()
1753
1754         c := buffer.NewCursor(h.Buf, buffer.Loc{h.Cursor.X, h.Cursor.Y - 1})
1755         h.Buf.AddCursor(c)
1756         h.Buf.SetCurCursor(h.Buf.NumCursors() - 1)
1757         h.Buf.MergeCursors()
1758         h.Relocate()
1759         return true
1760 }
1761
1762 // SpawnMultiCursorSelect adds a cursor at the beginning of each line of a selection
1763 func (h *BufPane) SpawnMultiCursorSelect() bool {
1764         // Avoid cases where multiple cursors already exist, that would create problems
1765         if h.Buf.NumCursors() > 1 {
1766                 return false
1767         }
1768
1769         var startLine int
1770         var endLine int
1771
1772         a, b := h.Cursor.CurSelection[0].Y, h.Cursor.CurSelection[1].Y
1773         if a > b {
1774                 startLine, endLine = b, a
1775         } else {
1776                 startLine, endLine = a, b
1777         }
1778
1779         if h.Cursor.HasSelection() {
1780                 h.Cursor.ResetSelection()
1781                 h.Cursor.GotoLoc(buffer.Loc{0, startLine})
1782
1783                 for i := startLine; i <= endLine; i++ {
1784                         c := buffer.NewCursor(h.Buf, buffer.Loc{0, i})
1785                         c.StoreVisualX()
1786                         h.Buf.AddCursor(c)
1787                 }
1788                 h.Buf.MergeCursors()
1789         } else {
1790                 return false
1791         }
1792         InfoBar.Message("Added cursors from selection")
1793         return true
1794 }
1795
1796 // MouseMultiCursor is a mouse action which puts a new cursor at the mouse position
1797 func (h *BufPane) MouseMultiCursor(e *tcell.EventMouse) bool {
1798         b := h.Buf
1799         mx, my := e.Position()
1800         mouseLoc := h.LocFromVisual(buffer.Loc{X: mx, Y: my})
1801         c := buffer.NewCursor(b, mouseLoc)
1802         b.AddCursor(c)
1803         b.MergeCursors()
1804
1805         return true
1806 }
1807
1808 // SkipMultiCursor moves the current multiple cursor to the next available position
1809 func (h *BufPane) SkipMultiCursor() bool {
1810         lastC := h.Buf.GetCursor(h.Buf.NumCursors() - 1)
1811         sel := lastC.GetSelection()
1812         searchStart := lastC.CurSelection[1]
1813
1814         search := string(sel)
1815         search = regexp.QuoteMeta(search)
1816         if h.multiWord {
1817                 search = "\\b" + search + "\\b"
1818         }
1819
1820         match, found, err := h.Buf.FindNext(search, h.Buf.Start(), h.Buf.End(), searchStart, true, true)
1821         if err != nil {
1822                 InfoBar.Error(err)
1823         }
1824         if found {
1825                 lastC.SetSelectionStart(match[0])
1826                 lastC.SetSelectionEnd(match[1])
1827                 lastC.OrigSelection[0] = lastC.CurSelection[0]
1828                 lastC.OrigSelection[1] = lastC.CurSelection[1]
1829                 lastC.Loc = lastC.CurSelection[1]
1830
1831                 h.Buf.MergeCursors()
1832                 h.Buf.SetCurCursor(h.Buf.NumCursors() - 1)
1833         } else {
1834                 InfoBar.Message("No matches found")
1835         }
1836         h.Relocate()
1837         return true
1838 }
1839
1840 // RemoveMultiCursor removes the latest multiple cursor
1841 func (h *BufPane) RemoveMultiCursor() bool {
1842         if h.Buf.NumCursors() > 1 {
1843                 h.Buf.RemoveCursor(h.Buf.NumCursors() - 1)
1844                 h.Buf.SetCurCursor(h.Buf.NumCursors() - 1)
1845                 h.Buf.UpdateCursors()
1846         } else {
1847                 h.multiWord = false
1848         }
1849         h.Relocate()
1850         return true
1851 }
1852
1853 // RemoveAllMultiCursors removes all cursors except the base cursor
1854 func (h *BufPane) RemoveAllMultiCursors() bool {
1855         h.Buf.ClearCursors()
1856         h.multiWord = false
1857         h.Relocate()
1858         return true
1859 }
1860
1861 // None is an action that does nothing
1862 func (h *BufPane) None() bool {
1863         return true
1864 }