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