]> git.lizzy.rs Git - micro.git/blob - internal/action/actions.go
Merge branch 'python-highlight-zero' of https://github.com/a11ce/micro into a11ce...
[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         InfoBar.Prompt(prompt, "", "Find", func(resp string) {
851                 // Event callback
852                 match, found, _ := h.Buf.FindNext(resp, h.Buf.Start(), h.Buf.End(), h.searchOrig, true, useRegex)
853                 if found {
854                         h.Cursor.SetSelectionStart(match[0])
855                         h.Cursor.SetSelectionEnd(match[1])
856                         h.Cursor.OrigSelection[0] = h.Cursor.CurSelection[0]
857                         h.Cursor.OrigSelection[1] = h.Cursor.CurSelection[1]
858                         h.Cursor.GotoLoc(match[1])
859                 } else {
860                         h.Cursor.GotoLoc(h.searchOrig)
861                         h.Cursor.ResetSelection()
862                 }
863                 h.Relocate()
864         }, func(resp string, canceled bool) {
865                 // Finished callback
866                 if !canceled {
867                         match, found, err := h.Buf.FindNext(resp, h.Buf.Start(), h.Buf.End(), h.searchOrig, true, useRegex)
868                         if err != nil {
869                                 InfoBar.Error(err)
870                         }
871                         if found {
872                                 h.Cursor.SetSelectionStart(match[0])
873                                 h.Cursor.SetSelectionEnd(match[1])
874                                 h.Cursor.OrigSelection[0] = h.Cursor.CurSelection[0]
875                                 h.Cursor.OrigSelection[1] = h.Cursor.CurSelection[1]
876                                 h.Cursor.GotoLoc(h.Cursor.CurSelection[1])
877                                 h.lastSearch = resp
878                                 h.lastSearchRegex = useRegex
879                         } else {
880                                 h.Cursor.ResetSelection()
881                                 InfoBar.Message("No matches found")
882                         }
883                 } else {
884                         h.Cursor.ResetSelection()
885                 }
886                 h.Relocate()
887         })
888
889         return true
890 }
891
892 // FindNext searches forwards for the last used search term
893 func (h *BufPane) FindNext() bool {
894         // If the cursor is at the start of a selection and we search we want
895         // to search from the end of the selection in the case that
896         // the selection is a search result in which case we wouldn't move at
897         // at all which would be bad
898         searchLoc := h.Cursor.Loc
899         if h.Cursor.HasSelection() {
900                 searchLoc = h.Cursor.CurSelection[1]
901         }
902         match, found, err := h.Buf.FindNext(h.lastSearch, h.Buf.Start(), h.Buf.End(), searchLoc, true, h.lastSearchRegex)
903         if err != nil {
904                 InfoBar.Error(err)
905         }
906         if found {
907                 h.Cursor.SetSelectionStart(match[0])
908                 h.Cursor.SetSelectionEnd(match[1])
909                 h.Cursor.OrigSelection[0] = h.Cursor.CurSelection[0]
910                 h.Cursor.OrigSelection[1] = h.Cursor.CurSelection[1]
911                 h.Cursor.Loc = h.Cursor.CurSelection[1]
912         } else {
913                 h.Cursor.ResetSelection()
914         }
915         h.Relocate()
916         return true
917 }
918
919 // FindPrevious searches backwards for the last used search term
920 func (h *BufPane) FindPrevious() bool {
921         // If the cursor is at the end of a selection and we search we want
922         // to search from the beginning of the selection in the case that
923         // the selection is a search result in which case we wouldn't move at
924         // at all which would be bad
925         searchLoc := h.Cursor.Loc
926         if h.Cursor.HasSelection() {
927                 searchLoc = h.Cursor.CurSelection[0]
928         }
929         match, found, err := h.Buf.FindNext(h.lastSearch, h.Buf.Start(), h.Buf.End(), searchLoc, false, h.lastSearchRegex)
930         if err != nil {
931                 InfoBar.Error(err)
932         }
933         if found {
934                 h.Cursor.SetSelectionStart(match[0])
935                 h.Cursor.SetSelectionEnd(match[1])
936                 h.Cursor.OrigSelection[0] = h.Cursor.CurSelection[0]
937                 h.Cursor.OrigSelection[1] = h.Cursor.CurSelection[1]
938                 h.Cursor.Loc = h.Cursor.CurSelection[1]
939         } else {
940                 h.Cursor.ResetSelection()
941         }
942         h.Relocate()
943         return true
944 }
945
946 // Undo undoes the last action
947 func (h *BufPane) Undo() bool {
948         h.Buf.Undo()
949         InfoBar.Message("Undid action")
950         h.Relocate()
951         return true
952 }
953
954 // Redo redoes the last action
955 func (h *BufPane) Redo() bool {
956         h.Buf.Redo()
957         InfoBar.Message("Redid action")
958         h.Relocate()
959         return true
960 }
961
962 // Copy the selection to the system clipboard
963 func (h *BufPane) Copy() bool {
964         if h.Cursor.HasSelection() {
965                 h.Cursor.CopySelection(clipboard.ClipboardReg)
966                 h.freshClip = true
967                 InfoBar.Message("Copied selection")
968         }
969         h.Relocate()
970         return true
971 }
972
973 // Copy the current line to the clipboard
974 func (h *BufPane) CopyLine() bool {
975         if h.Cursor.HasSelection() {
976                 return false
977         } else {
978                 h.Cursor.SelectLine()
979                 h.Cursor.CopySelection(clipboard.ClipboardReg)
980                 h.freshClip = true
981                 InfoBar.Message("Copied line")
982         }
983         h.Cursor.Deselect(true)
984         h.Relocate()
985         return true
986 }
987
988 // CutLine cuts the current line to the clipboard
989 func (h *BufPane) CutLine() bool {
990         h.Cursor.SelectLine()
991         if !h.Cursor.HasSelection() {
992                 return false
993         }
994         if h.freshClip {
995                 if h.Cursor.HasSelection() {
996                         if clip, err := clipboard.Read(clipboard.ClipboardReg); err != nil {
997                                 InfoBar.Error(err)
998                         } else {
999                                 clipboard.WriteMulti(clip+string(h.Cursor.GetSelection()), clipboard.ClipboardReg, h.Cursor.Num, h.Buf.NumCursors())
1000                         }
1001                 }
1002         } else if time.Since(h.lastCutTime)/time.Second > 10*time.Second || !h.freshClip {
1003                 h.Copy()
1004         }
1005         h.freshClip = true
1006         h.lastCutTime = time.Now()
1007         h.Cursor.DeleteSelection()
1008         h.Cursor.ResetSelection()
1009         InfoBar.Message("Cut line")
1010         h.Relocate()
1011         return true
1012 }
1013
1014 // Cut the selection to the system clipboard
1015 func (h *BufPane) Cut() bool {
1016         if h.Cursor.HasSelection() {
1017                 h.Cursor.CopySelection(clipboard.ClipboardReg)
1018                 h.Cursor.DeleteSelection()
1019                 h.Cursor.ResetSelection()
1020                 h.freshClip = true
1021                 InfoBar.Message("Cut selection")
1022
1023                 h.Relocate()
1024                 return true
1025         } else {
1026                 return h.CutLine()
1027         }
1028 }
1029
1030 // DuplicateLine duplicates the current line or selection
1031 func (h *BufPane) DuplicateLine() bool {
1032         if h.Cursor.HasSelection() {
1033                 h.Buf.Insert(h.Cursor.CurSelection[1], string(h.Cursor.GetSelection()))
1034         } else {
1035                 h.Cursor.End()
1036                 h.Buf.Insert(h.Cursor.Loc, "\n"+string(h.Buf.LineBytes(h.Cursor.Y)))
1037                 // h.Cursor.Right()
1038         }
1039
1040         InfoBar.Message("Duplicated line")
1041         h.Relocate()
1042         return true
1043 }
1044
1045 // DeleteLine deletes the current line
1046 func (h *BufPane) DeleteLine() bool {
1047         h.Cursor.SelectLine()
1048         if !h.Cursor.HasSelection() {
1049                 return false
1050         }
1051         h.Cursor.DeleteSelection()
1052         h.Cursor.ResetSelection()
1053         InfoBar.Message("Deleted line")
1054         h.Relocate()
1055         return true
1056 }
1057
1058 // MoveLinesUp moves up the current line or selected lines if any
1059 func (h *BufPane) MoveLinesUp() bool {
1060         if h.Cursor.HasSelection() {
1061                 if h.Cursor.CurSelection[0].Y == 0 {
1062                         InfoBar.Message("Cannot move further up")
1063                         return false
1064                 }
1065                 start := h.Cursor.CurSelection[0].Y
1066                 end := h.Cursor.CurSelection[1].Y
1067                 sel := 1
1068                 if start > end {
1069                         end, start = start, end
1070                         sel = 0
1071                 }
1072
1073                 compensate := false
1074                 if h.Cursor.CurSelection[sel].X != 0 {
1075                         end++
1076                 } else {
1077                         compensate = true
1078                 }
1079
1080                 h.Buf.MoveLinesUp(
1081                         start,
1082                         end,
1083                 )
1084                 if compensate {
1085                         h.Cursor.CurSelection[sel].Y -= 1
1086                 }
1087         } else {
1088                 if h.Cursor.Loc.Y == 0 {
1089                         InfoBar.Message("Cannot move further up")
1090                         return false
1091                 }
1092                 h.Buf.MoveLinesUp(
1093                         h.Cursor.Loc.Y,
1094                         h.Cursor.Loc.Y+1,
1095                 )
1096         }
1097
1098         h.Relocate()
1099         return true
1100 }
1101
1102 // MoveLinesDown moves down the current line or selected lines if any
1103 func (h *BufPane) MoveLinesDown() bool {
1104         if h.Cursor.HasSelection() {
1105                 if h.Cursor.CurSelection[1].Y >= h.Buf.LinesNum() {
1106                         InfoBar.Message("Cannot move further down")
1107                         return false
1108                 }
1109                 start := h.Cursor.CurSelection[0].Y
1110                 end := h.Cursor.CurSelection[1].Y
1111                 sel := 1
1112                 if start > end {
1113                         end, start = start, end
1114                         sel = 0
1115                 }
1116
1117                 if h.Cursor.CurSelection[sel].X != 0 {
1118                         end++
1119                 }
1120
1121                 h.Buf.MoveLinesDown(
1122                         start,
1123                         end,
1124                 )
1125         } else {
1126                 if h.Cursor.Loc.Y >= h.Buf.LinesNum()-1 {
1127                         InfoBar.Message("Cannot move further down")
1128                         return false
1129                 }
1130                 h.Buf.MoveLinesDown(
1131                         h.Cursor.Loc.Y,
1132                         h.Cursor.Loc.Y+1,
1133                 )
1134         }
1135
1136         h.Relocate()
1137         return true
1138 }
1139
1140 // Paste whatever is in the system clipboard into the buffer
1141 // Delete and paste if the user has a selection
1142 func (h *BufPane) Paste() bool {
1143         clip, err := clipboard.ReadMulti(clipboard.ClipboardReg, h.Cursor.Num, h.Buf.NumCursors())
1144         if err != nil {
1145                 InfoBar.Error(err)
1146         } else {
1147                 h.paste(clip)
1148         }
1149         h.Relocate()
1150         return true
1151 }
1152
1153 // PastePrimary pastes from the primary clipboard (only use on linux)
1154 func (h *BufPane) PastePrimary() bool {
1155         clip, err := clipboard.ReadMulti(clipboard.PrimaryReg, h.Cursor.Num, h.Buf.NumCursors())
1156         if err != nil {
1157                 InfoBar.Error(err)
1158         } else {
1159                 h.paste(clip)
1160         }
1161         h.Relocate()
1162         return true
1163 }
1164
1165 func (h *BufPane) paste(clip string) {
1166         if h.Buf.Settings["smartpaste"].(bool) {
1167                 if h.Cursor.X > 0 && len(util.GetLeadingWhitespace([]byte(strings.TrimLeft(clip, "\r\n")))) == 0 {
1168                         leadingWS := util.GetLeadingWhitespace(h.Buf.LineBytes(h.Cursor.Y))
1169                         clip = strings.Replace(clip, "\n", "\n"+string(leadingWS), -1)
1170                 }
1171         }
1172
1173         if h.Cursor.HasSelection() {
1174                 h.Cursor.DeleteSelection()
1175                 h.Cursor.ResetSelection()
1176         }
1177
1178         h.Buf.Insert(h.Cursor.Loc, clip)
1179         // h.Cursor.Loc = h.Cursor.Loc.Move(Count(clip), h.Buf)
1180         h.freshClip = false
1181         InfoBar.Message("Pasted clipboard")
1182 }
1183
1184 // JumpToMatchingBrace moves the cursor to the matching brace if it is
1185 // currently on a brace
1186 func (h *BufPane) JumpToMatchingBrace() bool {
1187         for _, bp := range buffer.BracePairs {
1188                 r := h.Cursor.RuneUnder(h.Cursor.X)
1189                 rl := h.Cursor.RuneUnder(h.Cursor.X - 1)
1190                 if r == bp[0] || r == bp[1] || rl == bp[0] || rl == bp[1] {
1191                         matchingBrace, left, found := h.Buf.FindMatchingBrace(bp, h.Cursor.Loc)
1192                         if found {
1193                                 if left {
1194                                         h.Cursor.GotoLoc(matchingBrace)
1195                                 } else {
1196                                         h.Cursor.GotoLoc(matchingBrace.Move(1, h.Buf))
1197                                 }
1198                                 break
1199                         } else {
1200                                 return false
1201                         }
1202                 }
1203         }
1204
1205         h.Relocate()
1206         return true
1207 }
1208
1209 // SelectAll selects the entire buffer
1210 func (h *BufPane) SelectAll() bool {
1211         h.Cursor.SetSelectionStart(h.Buf.Start())
1212         h.Cursor.SetSelectionEnd(h.Buf.End())
1213         // Put the cursor at the beginning
1214         h.Cursor.X = 0
1215         h.Cursor.Y = 0
1216         h.Relocate()
1217         return true
1218 }
1219
1220 // OpenFile opens a new file in the buffer
1221 func (h *BufPane) OpenFile() bool {
1222         InfoBar.Prompt("> ", "open ", "Open", nil, func(resp string, canceled bool) {
1223                 if !canceled {
1224                         h.HandleCommand(resp)
1225                 }
1226         })
1227         return true
1228 }
1229
1230 // OpenFile opens a new file in the buffer
1231 func (h *BufPane) JumpLine() bool {
1232         InfoBar.Prompt("> ", "goto ", "Command", nil, func(resp string, canceled bool) {
1233                 if !canceled {
1234                         h.HandleCommand(resp)
1235                 }
1236         })
1237         return true
1238 }
1239
1240 // Start moves the viewport to the start of the buffer
1241 func (h *BufPane) Start() bool {
1242         v := h.GetView()
1243         v.StartLine = 0
1244         h.SetView(v)
1245         return true
1246 }
1247
1248 // End moves the viewport to the end of the buffer
1249 func (h *BufPane) End() bool {
1250         // TODO: softwrap problems?
1251         v := h.GetView()
1252         if v.Height > h.Buf.LinesNum() {
1253                 v.StartLine = 0
1254                 h.SetView(v)
1255         } else {
1256                 v.StartLine = h.Buf.LinesNum() - v.Height
1257                 h.SetView(v)
1258         }
1259         return true
1260 }
1261
1262 // PageUp scrolls the view up a page
1263 func (h *BufPane) PageUp() bool {
1264         v := h.GetView()
1265         if v.StartLine > v.Height {
1266                 h.ScrollUp(v.Height)
1267         } else {
1268                 v.StartLine = 0
1269         }
1270         h.SetView(v)
1271         return true
1272 }
1273
1274 // PageDown scrolls the view down a page
1275 func (h *BufPane) PageDown() bool {
1276         v := h.GetView()
1277         if h.Buf.LinesNum()-(v.StartLine+v.Height) > v.Height {
1278                 h.ScrollDown(v.Height)
1279         } else if h.Buf.LinesNum() >= v.Height {
1280                 v.StartLine = h.Buf.LinesNum() - v.Height
1281         }
1282         return true
1283 }
1284
1285 // SelectPageUp selects up one page
1286 func (h *BufPane) SelectPageUp() bool {
1287         if !h.Cursor.HasSelection() {
1288                 h.Cursor.OrigSelection[0] = h.Cursor.Loc
1289         }
1290         h.Cursor.UpN(h.GetView().Height)
1291         h.Cursor.SelectTo(h.Cursor.Loc)
1292         h.Relocate()
1293         return true
1294 }
1295
1296 // SelectPageDown selects down one page
1297 func (h *BufPane) SelectPageDown() bool {
1298         if !h.Cursor.HasSelection() {
1299                 h.Cursor.OrigSelection[0] = h.Cursor.Loc
1300         }
1301         h.Cursor.DownN(h.GetView().Height)
1302         h.Cursor.SelectTo(h.Cursor.Loc)
1303         h.Relocate()
1304         return true
1305 }
1306
1307 // CursorPageUp places the cursor a page up
1308 func (h *BufPane) CursorPageUp() bool {
1309         h.Cursor.Deselect(true)
1310
1311         if h.Cursor.HasSelection() {
1312                 h.Cursor.Loc = h.Cursor.CurSelection[0]
1313                 h.Cursor.ResetSelection()
1314                 h.Cursor.StoreVisualX()
1315         }
1316         h.Cursor.UpN(h.GetView().Height)
1317         h.Relocate()
1318         return true
1319 }
1320
1321 // CursorPageDown places the cursor a page up
1322 func (h *BufPane) CursorPageDown() bool {
1323         h.Cursor.Deselect(false)
1324
1325         if h.Cursor.HasSelection() {
1326                 h.Cursor.Loc = h.Cursor.CurSelection[1]
1327                 h.Cursor.ResetSelection()
1328                 h.Cursor.StoreVisualX()
1329         }
1330         h.Cursor.DownN(h.GetView().Height)
1331         h.Relocate()
1332         return true
1333 }
1334
1335 // HalfPageUp scrolls the view up half a page
1336 func (h *BufPane) HalfPageUp() bool {
1337         v := h.GetView()
1338         if v.StartLine > v.Height/2 {
1339                 h.ScrollUp(v.Height / 2)
1340         } else {
1341                 v.StartLine = 0
1342         }
1343         h.SetView(v)
1344         return true
1345 }
1346
1347 // HalfPageDown scrolls the view down half a page
1348 func (h *BufPane) HalfPageDown() bool {
1349         v := h.GetView()
1350         if h.Buf.LinesNum()-(v.StartLine+v.Height) > v.Height/2 {
1351                 h.ScrollDown(v.Height / 2)
1352         } else {
1353                 if h.Buf.LinesNum() >= v.Height {
1354                         v.StartLine = h.Buf.LinesNum() - v.Height
1355                 }
1356         }
1357         h.SetView(v)
1358         return true
1359 }
1360
1361 // ToggleDiffGutter turns the diff gutter off and on
1362 func (h *BufPane) ToggleDiffGutter() bool {
1363         if !h.Buf.Settings["diffgutter"].(bool) {
1364                 h.Buf.Settings["diffgutter"] = true
1365                 h.Buf.UpdateDiff(func(synchronous bool) {
1366                         screen.Redraw()
1367                 })
1368                 InfoBar.Message("Enabled diff gutter")
1369         } else {
1370                 h.Buf.Settings["diffgutter"] = false
1371                 InfoBar.Message("Disabled diff gutter")
1372         }
1373         return true
1374 }
1375
1376 // ToggleRuler turns line numbers off and on
1377 func (h *BufPane) ToggleRuler() bool {
1378         if !h.Buf.Settings["ruler"].(bool) {
1379                 h.Buf.Settings["ruler"] = true
1380                 InfoBar.Message("Enabled ruler")
1381         } else {
1382                 h.Buf.Settings["ruler"] = false
1383                 InfoBar.Message("Disabled ruler")
1384         }
1385         return true
1386 }
1387
1388 // ClearStatus clears the messenger bar
1389 func (h *BufPane) ClearStatus() bool {
1390         InfoBar.Message("")
1391         return true
1392 }
1393
1394 // ToggleHelp toggles the help screen
1395 func (h *BufPane) ToggleHelp() bool {
1396         if h.Buf.Type == buffer.BTHelp {
1397                 h.Quit()
1398         } else {
1399                 h.openHelp("help")
1400         }
1401         return true
1402 }
1403
1404 // ToggleKeyMenu toggles the keymenu option and resizes all tabs
1405 func (h *BufPane) ToggleKeyMenu() bool {
1406         config.GlobalSettings["keymenu"] = !config.GetGlobalOption("keymenu").(bool)
1407         Tabs.Resize()
1408         return true
1409 }
1410
1411 // ShellMode opens a terminal to run a shell command
1412 func (h *BufPane) ShellMode() bool {
1413         InfoBar.Prompt("$ ", "", "Shell", nil, func(resp string, canceled bool) {
1414                 if !canceled {
1415                         // The true here is for openTerm to make the command interactive
1416                         shell.RunInteractiveShell(resp, true, false)
1417                 }
1418         })
1419
1420         return true
1421 }
1422
1423 // CommandMode lets the user enter a command
1424 func (h *BufPane) CommandMode() bool {
1425         InfoBar.Prompt("> ", "", "Command", nil, func(resp string, canceled bool) {
1426                 if !canceled {
1427                         h.HandleCommand(resp)
1428                 }
1429         })
1430         return true
1431 }
1432
1433 // ToggleOverwriteMode lets the user toggle the text overwrite mode
1434 func (h *BufPane) ToggleOverwriteMode() bool {
1435         h.isOverwriteMode = !h.isOverwriteMode
1436         return true
1437 }
1438
1439 // Escape leaves current mode
1440 func (h *BufPane) Escape() bool {
1441         return true
1442 }
1443
1444 // Deselect deselects on the current cursor
1445 func (h *BufPane) Deselect() bool {
1446         h.Cursor.Deselect(true)
1447         return true
1448 }
1449
1450 // ClearInfo clears the infobar
1451 func (h *BufPane) ClearInfo() bool {
1452         InfoBar.Message("")
1453         return true
1454 }
1455
1456 // Quit this will close the current tab or view that is open
1457 func (h *BufPane) Quit() bool {
1458         quit := func() {
1459                 h.Buf.Close()
1460                 if len(MainTab().Panes) > 1 {
1461                         h.Unsplit()
1462                 } else if len(Tabs.List) > 1 {
1463                         Tabs.RemoveTab(h.splitID)
1464                 } else {
1465                         screen.Screen.Fini()
1466                         InfoBar.Close()
1467                         runtime.Goexit()
1468                 }
1469         }
1470         if h.Buf.Modified() {
1471                 if config.GlobalSettings["autosave"].(float64) > 0 {
1472                         // autosave on means we automatically save when quitting
1473                         h.SaveCB("Quit", func() {
1474                                 quit()
1475                         })
1476                 } else {
1477                         InfoBar.YNPrompt("Save changes to "+h.Buf.GetName()+" before closing? (y,n,esc)", func(yes, canceled bool) {
1478                                 if !canceled && !yes {
1479                                         quit()
1480                                 } else if !canceled && yes {
1481                                         h.SaveCB("Quit", func() {
1482                                                 quit()
1483                                         })
1484                                 }
1485                         })
1486                 }
1487         } else {
1488                 quit()
1489         }
1490         return true
1491 }
1492
1493 // QuitAll quits the whole editor; all splits and tabs
1494 func (h *BufPane) QuitAll() bool {
1495         anyModified := false
1496         for _, b := range buffer.OpenBuffers {
1497                 if b.Modified() {
1498                         anyModified = true
1499                         break
1500                 }
1501         }
1502
1503         quit := func() {
1504                 for _, b := range buffer.OpenBuffers {
1505                         b.Close()
1506                 }
1507                 screen.Screen.Fini()
1508                 InfoBar.Close()
1509                 runtime.Goexit()
1510         }
1511
1512         if anyModified {
1513                 InfoBar.YNPrompt("Quit micro? (all open buffers will be closed without saving)", func(yes, canceled bool) {
1514                         if !canceled && yes {
1515                                 quit()
1516                         }
1517                 })
1518         } else {
1519                 quit()
1520         }
1521
1522         return true
1523 }
1524
1525 // AddTab adds a new tab with an empty buffer
1526 func (h *BufPane) AddTab() bool {
1527         width, height := screen.Screen.Size()
1528         iOffset := config.GetInfoBarOffset()
1529         b := buffer.NewBufferFromString("", "", buffer.BTDefault)
1530         tp := NewTabFromBuffer(0, 0, width, height-iOffset, b)
1531         Tabs.AddTab(tp)
1532         Tabs.SetActive(len(Tabs.List) - 1)
1533
1534         return true
1535 }
1536
1537 // PreviousTab switches to the previous tab in the tab list
1538 func (h *BufPane) PreviousTab() bool {
1539         tabsLen := len(Tabs.List)
1540         a := Tabs.Active() + tabsLen
1541         Tabs.SetActive((a - 1) % tabsLen)
1542
1543         return true
1544 }
1545
1546 // NextTab switches to the next tab in the tab list
1547 func (h *BufPane) NextTab() bool {
1548         a := Tabs.Active()
1549         Tabs.SetActive((a + 1) % len(Tabs.List))
1550
1551         return true
1552 }
1553
1554 // VSplitAction opens an empty vertical split
1555 func (h *BufPane) VSplitAction() bool {
1556         h.VSplitBuf(buffer.NewBufferFromString("", "", buffer.BTDefault))
1557
1558         return true
1559 }
1560
1561 // HSplitAction opens an empty horizontal split
1562 func (h *BufPane) HSplitAction() bool {
1563         h.HSplitBuf(buffer.NewBufferFromString("", "", buffer.BTDefault))
1564
1565         return true
1566 }
1567
1568 // Unsplit closes all splits in the current tab except the active one
1569 func (h *BufPane) Unsplit() bool {
1570         tab := h.tab
1571         n := tab.GetNode(h.splitID)
1572         ok := n.Unsplit()
1573         if ok {
1574                 tab.RemovePane(tab.GetPane(h.splitID))
1575                 tab.Resize()
1576                 tab.SetActive(len(tab.Panes) - 1)
1577
1578                 return true
1579         }
1580         return false
1581 }
1582
1583 // NextSplit changes the view to the next split
1584 func (h *BufPane) NextSplit() bool {
1585         a := h.tab.active
1586         if a < len(h.tab.Panes)-1 {
1587                 a++
1588         } else {
1589                 a = 0
1590         }
1591
1592         h.tab.SetActive(a)
1593
1594         return true
1595 }
1596
1597 // PreviousSplit changes the view to the previous split
1598 func (h *BufPane) PreviousSplit() bool {
1599         a := h.tab.active
1600         if a > 0 {
1601                 a--
1602         } else {
1603                 a = len(h.tab.Panes) - 1
1604         }
1605         h.tab.SetActive(a)
1606
1607         return true
1608 }
1609
1610 var curmacro []interface{}
1611 var recording_macro bool
1612
1613 // ToggleMacro toggles recording of a macro
1614 func (h *BufPane) ToggleMacro() bool {
1615         recording_macro = !recording_macro
1616         if recording_macro {
1617                 curmacro = []interface{}{}
1618                 InfoBar.Message("Recording")
1619         } else {
1620                 InfoBar.Message("Stopped recording")
1621         }
1622         h.Relocate()
1623         return true
1624 }
1625
1626 // PlayMacro plays back the most recently recorded macro
1627 func (h *BufPane) PlayMacro() bool {
1628         if recording_macro {
1629                 return false
1630         }
1631         for _, action := range curmacro {
1632                 switch t := action.(type) {
1633                 case rune:
1634                         h.DoRuneInsert(t)
1635                 case func(*BufPane) bool:
1636                         t(h)
1637                 }
1638         }
1639         h.Relocate()
1640         return true
1641 }
1642
1643 // SpawnMultiCursor creates a new multiple cursor at the next occurrence of the current selection or current word
1644 func (h *BufPane) SpawnMultiCursor() bool {
1645         spawner := h.Buf.GetCursor(h.Buf.NumCursors() - 1)
1646         if !spawner.HasSelection() {
1647                 spawner.SelectWord()
1648                 h.multiWord = true
1649                 h.Relocate()
1650                 return true
1651         }
1652
1653         sel := spawner.GetSelection()
1654         searchStart := spawner.CurSelection[1]
1655
1656         search := string(sel)
1657         search = regexp.QuoteMeta(search)
1658         if h.multiWord {
1659                 search = "\\b" + search + "\\b"
1660         }
1661         match, found, err := h.Buf.FindNext(search, h.Buf.Start(), h.Buf.End(), searchStart, true, true)
1662         if err != nil {
1663                 InfoBar.Error(err)
1664         }
1665         if found {
1666                 c := buffer.NewCursor(h.Buf, buffer.Loc{})
1667                 c.SetSelectionStart(match[0])
1668                 c.SetSelectionEnd(match[1])
1669                 c.OrigSelection[0] = c.CurSelection[0]
1670                 c.OrigSelection[1] = c.CurSelection[1]
1671                 c.Loc = c.CurSelection[1]
1672
1673                 h.Buf.AddCursor(c)
1674                 h.Buf.SetCurCursor(h.Buf.NumCursors() - 1)
1675                 h.Buf.MergeCursors()
1676         } else {
1677                 InfoBar.Message("No matches found")
1678         }
1679
1680         h.Relocate()
1681         return true
1682 }
1683
1684 // SpawnMultiCursorUp creates additional cursor, at the same X (if possible), one Y less.
1685 func (h *BufPane) SpawnMultiCursorUp() bool {
1686         if h.Cursor.Y == 0 {
1687                 return false
1688         } else {
1689                 h.Cursor.GotoLoc(buffer.Loc{h.Cursor.X, h.Cursor.Y - 1})
1690                 h.Cursor.Relocate()
1691         }
1692
1693         c := buffer.NewCursor(h.Buf, buffer.Loc{h.Cursor.X, h.Cursor.Y + 1})
1694         h.Buf.AddCursor(c)
1695         h.Buf.SetCurCursor(h.Buf.NumCursors() - 1)
1696         h.Buf.MergeCursors()
1697
1698         h.Relocate()
1699         return true
1700 }
1701
1702 // SpawnMultiCursorDown creates additional cursor, at the same X (if possible), one Y more.
1703 func (h *BufPane) SpawnMultiCursorDown() bool {
1704         if h.Cursor.Y+1 == h.Buf.LinesNum() {
1705                 return false
1706         } else {
1707                 h.Cursor.GotoLoc(buffer.Loc{h.Cursor.X, h.Cursor.Y + 1})
1708                 h.Cursor.Relocate()
1709         }
1710
1711         c := buffer.NewCursor(h.Buf, buffer.Loc{h.Cursor.X, h.Cursor.Y - 1})
1712         h.Buf.AddCursor(c)
1713         h.Buf.SetCurCursor(h.Buf.NumCursors() - 1)
1714         h.Buf.MergeCursors()
1715         h.Relocate()
1716         return true
1717 }
1718
1719 // SpawnMultiCursorSelect adds a cursor at the beginning of each line of a selection
1720 func (h *BufPane) SpawnMultiCursorSelect() bool {
1721         // Avoid cases where multiple cursors already exist, that would create problems
1722         if h.Buf.NumCursors() > 1 {
1723                 return false
1724         }
1725
1726         var startLine int
1727         var endLine int
1728
1729         a, b := h.Cursor.CurSelection[0].Y, h.Cursor.CurSelection[1].Y
1730         if a > b {
1731                 startLine, endLine = b, a
1732         } else {
1733                 startLine, endLine = a, b
1734         }
1735
1736         if h.Cursor.HasSelection() {
1737                 h.Cursor.ResetSelection()
1738                 h.Cursor.GotoLoc(buffer.Loc{0, startLine})
1739
1740                 for i := startLine; i <= endLine; i++ {
1741                         c := buffer.NewCursor(h.Buf, buffer.Loc{0, i})
1742                         c.StoreVisualX()
1743                         h.Buf.AddCursor(c)
1744                 }
1745                 h.Buf.MergeCursors()
1746         } else {
1747                 return false
1748         }
1749         InfoBar.Message("Added cursors from selection")
1750         return true
1751 }
1752
1753 // MouseMultiCursor is a mouse action which puts a new cursor at the mouse position
1754 func (h *BufPane) MouseMultiCursor(e *tcell.EventMouse) bool {
1755         b := h.Buf
1756         mx, my := e.Position()
1757         mouseLoc := h.LocFromVisual(buffer.Loc{X: mx, Y: my})
1758         c := buffer.NewCursor(b, mouseLoc)
1759         b.AddCursor(c)
1760         b.MergeCursors()
1761
1762         return true
1763 }
1764
1765 // SkipMultiCursor moves the current multiple cursor to the next available position
1766 func (h *BufPane) SkipMultiCursor() bool {
1767         lastC := h.Buf.GetCursor(h.Buf.NumCursors() - 1)
1768         sel := lastC.GetSelection()
1769         searchStart := lastC.CurSelection[1]
1770
1771         search := string(sel)
1772         search = regexp.QuoteMeta(search)
1773         if h.multiWord {
1774                 search = "\\b" + search + "\\b"
1775         }
1776
1777         match, found, err := h.Buf.FindNext(search, h.Buf.Start(), h.Buf.End(), searchStart, true, true)
1778         if err != nil {
1779                 InfoBar.Error(err)
1780         }
1781         if found {
1782                 lastC.SetSelectionStart(match[0])
1783                 lastC.SetSelectionEnd(match[1])
1784                 lastC.OrigSelection[0] = lastC.CurSelection[0]
1785                 lastC.OrigSelection[1] = lastC.CurSelection[1]
1786                 lastC.Loc = lastC.CurSelection[1]
1787
1788                 h.Buf.MergeCursors()
1789                 h.Buf.SetCurCursor(h.Buf.NumCursors() - 1)
1790         } else {
1791                 InfoBar.Message("No matches found")
1792         }
1793         h.Relocate()
1794         return true
1795 }
1796
1797 // RemoveMultiCursor removes the latest multiple cursor
1798 func (h *BufPane) RemoveMultiCursor() bool {
1799         if h.Buf.NumCursors() > 1 {
1800                 h.Buf.RemoveCursor(h.Buf.NumCursors() - 1)
1801                 h.Buf.SetCurCursor(h.Buf.NumCursors() - 1)
1802                 h.Buf.UpdateCursors()
1803         } else {
1804                 h.multiWord = false
1805         }
1806         h.Relocate()
1807         return true
1808 }
1809
1810 // RemoveAllMultiCursors removes all cursors except the base cursor
1811 func (h *BufPane) RemoveAllMultiCursors() bool {
1812         h.Buf.ClearCursors()
1813         h.multiWord = false
1814         h.Relocate()
1815         return true
1816 }
1817
1818 // None is an action that does nothing
1819 func (h *BufPane) None() bool {
1820         return true
1821 }