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