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