]> git.lizzy.rs Git - micro.git/blob - internal/action/actions.go
c42b6c8a6d2f16522e2ace4d9c2d1544f39c9761
[micro.git] / internal / action / actions.go
1 package action
2
3 import (
4         "regexp"
5         "runtime"
6         "strings"
7         "time"
8         "unicode/utf8"
9
10         shellquote "github.com/kballard/go-shellquote"
11         "github.com/zyedidia/clipboard"
12         "github.com/zyedidia/micro/v2/internal/buffer"
13         "github.com/zyedidia/micro/v2/internal/config"
14         "github.com/zyedidia/micro/v2/internal/screen"
15         "github.com/zyedidia/micro/v2/internal/shell"
16         "github.com/zyedidia/micro/v2/internal/util"
17         "github.com/zyedidia/tcell"
18 )
19
20 // ScrollUp is not an action
21 func (h *BufPane) ScrollUp(n int) {
22         v := h.GetView()
23         if v.StartLine >= n {
24                 v.StartLine -= n
25                 h.SetView(v)
26         } else {
27                 v.StartLine = 0
28         }
29 }
30
31 // ScrollDown is not an action
32 func (h *BufPane) ScrollDown(n int) {
33         v := h.GetView()
34         if v.StartLine <= h.Buf.LinesNum()-1-n {
35                 v.StartLine += n
36                 h.SetView(v)
37         }
38 }
39
40 // MousePress is the event that should happen when a normal click happens
41 // This is almost always bound to left click
42 func (h *BufPane) MousePress(e *tcell.EventMouse) bool {
43         b := h.Buf
44         mx, my := e.Position()
45         mouseLoc := h.LocFromVisual(buffer.Loc{mx, my})
46         h.Cursor.Loc = mouseLoc
47         if h.mouseReleased {
48                 if b.NumCursors() > 1 {
49                         b.ClearCursors()
50                         h.Relocate()
51                         h.Cursor = h.Buf.GetActiveCursor()
52                         h.Cursor.Loc = mouseLoc
53                 }
54                 if time.Since(h.lastClickTime)/time.Millisecond < config.DoubleClickThreshold && (mouseLoc.X == h.lastLoc.X && mouseLoc.Y == h.lastLoc.Y) {
55                         if h.doubleClick {
56                                 // Triple click
57                                 h.lastClickTime = time.Now()
58
59                                 h.tripleClick = true
60                                 h.doubleClick = false
61
62                                 h.Cursor.SelectLine()
63                                 h.Cursor.CopySelection("primary")
64                         } else {
65                                 // Double click
66                                 h.lastClickTime = time.Now()
67
68                                 h.doubleClick = true
69                                 h.tripleClick = false
70
71                                 h.Cursor.SelectWord()
72                                 h.Cursor.CopySelection("primary")
73                         }
74                 } else {
75                         h.doubleClick = false
76                         h.tripleClick = false
77                         h.lastClickTime = time.Now()
78
79                         h.Cursor.OrigSelection[0] = h.Cursor.Loc
80                         h.Cursor.CurSelection[0] = h.Cursor.Loc
81                         h.Cursor.CurSelection[1] = h.Cursor.Loc
82                 }
83                 h.mouseReleased = false
84         } else if !h.mouseReleased {
85                 if h.tripleClick {
86                         h.Cursor.AddLineToSelection()
87                 } else if h.doubleClick {
88                         h.Cursor.AddWordToSelection()
89                 } else {
90                         h.Cursor.SetSelectionEnd(h.Cursor.Loc)
91                 }
92         }
93
94         h.Cursor.StoreVisualX()
95         h.lastLoc = mouseLoc
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 < utf8.RuneCount(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: utf8.RuneCount(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 && utf8.RuneCount(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 !util.IsNonAlphaNumeric(h.Cursor.RuneUnder(h.Cursor.X)) {
668                 // don't autocomplete if cursor is on alpha numeric character (middle of a word)
669                 return false
670         }
671
672         if b.HasSuggestions {
673                 b.CycleAutocomplete(true)
674                 return true
675         }
676         return b.Autocomplete(buffer.BufferComplete)
677 }
678
679 // CycleAutocompleteBack cycles back in the autocomplete suggestion list
680 func (h *BufPane) CycleAutocompleteBack() bool {
681         if h.Cursor.HasSelection() {
682                 return false
683         }
684
685         if h.Buf.HasSuggestions {
686                 h.Buf.CycleAutocomplete(false)
687                 return true
688         }
689         return false
690 }
691
692 // InsertTab inserts a tab or spaces
693 func (h *BufPane) InsertTab() bool {
694         b := h.Buf
695         indent := b.IndentString(util.IntOpt(b.Settings["tabsize"]))
696         tabBytes := len(indent)
697         bytesUntilIndent := tabBytes - (h.Cursor.GetVisualX() % tabBytes)
698         b.Insert(h.Cursor.Loc, indent[:bytesUntilIndent])
699         h.Relocate()
700         return true
701 }
702
703 // SaveAll saves all open buffers
704 func (h *BufPane) SaveAll() bool {
705         for _, b := range buffer.OpenBuffers {
706                 b.Save()
707         }
708         return true
709 }
710
711 // SaveCB performs a save and does a callback at the very end (after all prompts have been resolved)
712 func (h *BufPane) SaveCB(action string, callback func()) bool {
713         // If this is an empty buffer, ask for a filename
714         if h.Buf.Path == "" {
715                 h.SaveAsCB(action, callback)
716         } else {
717                 noPrompt := h.saveBufToFile(h.Buf.Path, action, callback)
718                 if noPrompt {
719                         return true
720                 }
721         }
722         return false
723 }
724
725 // Save the buffer to disk
726 func (h *BufPane) Save() bool {
727         return h.SaveCB("Save", nil)
728 }
729
730 // SaveAsCB performs a save as and does a callback at the very end (after all prompts have been resolved)
731 func (h *BufPane) SaveAsCB(action string, callback func()) bool {
732         InfoBar.Prompt("Filename: ", "", "Save", nil, func(resp string, canceled bool) {
733                 if !canceled {
734                         // the filename might or might not be quoted, so unquote first then join the strings.
735                         args, err := shellquote.Split(resp)
736                         if err != nil {
737                                 InfoBar.Error("Error parsing arguments: ", err)
738                                 return
739                         }
740                         if len(args) == 0 {
741                                 InfoBar.Error("No filename given")
742                                 return
743                         }
744                         filename := strings.Join(args, " ")
745                         noPrompt := h.saveBufToFile(filename, action, callback)
746                         if noPrompt {
747                                 h.completeAction(action)
748                         }
749                 }
750         })
751         return false
752 }
753
754 // SaveAs saves the buffer to disk with the given name
755 func (h *BufPane) SaveAs() bool {
756         return h.SaveAsCB("SaveAs", nil)
757 }
758
759 // This function saves the buffer to `filename` and changes the buffer's path and name
760 // to `filename` if the save is successful
761 func (h *BufPane) saveBufToFile(filename string, action string, callback func()) bool {
762         err := h.Buf.SaveAs(filename)
763         if err != nil {
764                 if strings.HasSuffix(err.Error(), "permission denied") {
765                         saveWithSudo := func() {
766                                 err = h.Buf.SaveAsWithSudo(filename)
767                                 if err != nil {
768                                         InfoBar.Error(err)
769                                 } else {
770                                         h.Buf.Path = filename
771                                         h.Buf.SetName(filename)
772                                         InfoBar.Message("Saved " + filename)
773                                 }
774                         }
775                         if h.Buf.Settings["autosu"].(bool) {
776                                 saveWithSudo()
777                         } else {
778                                 InfoBar.YNPrompt("Permission denied. Do you want to save this file using sudo? (y,n)", func(yes, canceled bool) {
779                                         if yes && !canceled {
780                                                 saveWithSudo()
781                                                 h.completeAction(action)
782                                         }
783                                         if callback != nil {
784                                                 callback()
785                                         }
786                                 })
787                                 return false
788                         }
789                 } else {
790                         InfoBar.Error(err)
791                 }
792         } else {
793                 h.Buf.Path = filename
794                 h.Buf.SetName(filename)
795                 InfoBar.Message("Saved " + filename)
796         }
797         if callback != nil {
798                 callback()
799         }
800         return true
801 }
802
803 // Find opens a prompt and searches forward for the input
804 func (h *BufPane) Find() bool {
805         return h.find(true)
806 }
807
808 // FindLiteral is the same as Find() but does not support regular expressions
809 func (h *BufPane) FindLiteral() bool {
810         return h.find(false)
811 }
812
813 func (h *BufPane) find(useRegex bool) bool {
814         h.searchOrig = h.Cursor.Loc
815         prompt := "Find: "
816         if useRegex {
817                 prompt = "Find (regex): "
818         }
819         InfoBar.Prompt(prompt, "", "Find", func(resp string) {
820                 // Event callback
821                 match, found, _ := h.Buf.FindNext(resp, h.Buf.Start(), h.Buf.End(), h.searchOrig, true, useRegex)
822                 if found {
823                         h.Cursor.SetSelectionStart(match[0])
824                         h.Cursor.SetSelectionEnd(match[1])
825                         h.Cursor.OrigSelection[0] = h.Cursor.CurSelection[0]
826                         h.Cursor.OrigSelection[1] = h.Cursor.CurSelection[1]
827                         h.Cursor.GotoLoc(match[1])
828                 } else {
829                         h.Cursor.GotoLoc(h.searchOrig)
830                         h.Cursor.ResetSelection()
831                 }
832                 h.Relocate()
833         }, func(resp string, canceled bool) {
834                 // Finished callback
835                 if !canceled {
836                         match, found, err := h.Buf.FindNext(resp, h.Buf.Start(), h.Buf.End(), h.searchOrig, true, useRegex)
837                         if err != nil {
838                                 InfoBar.Error(err)
839                         }
840                         if found {
841                                 h.Cursor.SetSelectionStart(match[0])
842                                 h.Cursor.SetSelectionEnd(match[1])
843                                 h.Cursor.OrigSelection[0] = h.Cursor.CurSelection[0]
844                                 h.Cursor.OrigSelection[1] = h.Cursor.CurSelection[1]
845                                 h.Cursor.GotoLoc(h.Cursor.CurSelection[1])
846                                 h.lastSearch = resp
847                                 h.lastSearchRegex = useRegex
848                         } else {
849                                 h.Cursor.ResetSelection()
850                                 InfoBar.Message("No matches found")
851                         }
852                 } else {
853                         h.Cursor.ResetSelection()
854                 }
855                 h.Relocate()
856         })
857
858         return true
859 }
860
861 // FindNext searches forwards for the last used search term
862 func (h *BufPane) FindNext() bool {
863         // If the cursor is at the start of a selection and we search we want
864         // to search from the end of the selection in the case that
865         // the selection is a search result in which case we wouldn't move at
866         // at all which would be bad
867         searchLoc := h.Cursor.Loc
868         if h.Cursor.HasSelection() {
869                 searchLoc = h.Cursor.CurSelection[1]
870         }
871         match, found, err := h.Buf.FindNext(h.lastSearch, h.Buf.Start(), h.Buf.End(), searchLoc, true, h.lastSearchRegex)
872         if err != nil {
873                 InfoBar.Error(err)
874         }
875         if found {
876                 h.Cursor.SetSelectionStart(match[0])
877                 h.Cursor.SetSelectionEnd(match[1])
878                 h.Cursor.OrigSelection[0] = h.Cursor.CurSelection[0]
879                 h.Cursor.OrigSelection[1] = h.Cursor.CurSelection[1]
880                 h.Cursor.Loc = h.Cursor.CurSelection[1]
881         } else {
882                 h.Cursor.ResetSelection()
883         }
884         h.Relocate()
885         return true
886 }
887
888 // FindPrevious searches backwards for the last used search term
889 func (h *BufPane) FindPrevious() bool {
890         // If the cursor is at the end of a selection and we search we want
891         // to search from the beginning of the selection in the case that
892         // the selection is a search result in which case we wouldn't move at
893         // at all which would be bad
894         searchLoc := h.Cursor.Loc
895         if h.Cursor.HasSelection() {
896                 searchLoc = h.Cursor.CurSelection[0]
897         }
898         match, found, err := h.Buf.FindNext(h.lastSearch, h.Buf.Start(), h.Buf.End(), searchLoc, false, h.lastSearchRegex)
899         if err != nil {
900                 InfoBar.Error(err)
901         }
902         if found {
903                 h.Cursor.SetSelectionStart(match[0])
904                 h.Cursor.SetSelectionEnd(match[1])
905                 h.Cursor.OrigSelection[0] = h.Cursor.CurSelection[0]
906                 h.Cursor.OrigSelection[1] = h.Cursor.CurSelection[1]
907                 h.Cursor.Loc = h.Cursor.CurSelection[1]
908         } else {
909                 h.Cursor.ResetSelection()
910         }
911         h.Relocate()
912         return true
913 }
914
915 // Undo undoes the last action
916 func (h *BufPane) Undo() bool {
917         h.Buf.Undo()
918         InfoBar.Message("Undid action")
919         h.Relocate()
920         return true
921 }
922
923 // Redo redoes the last action
924 func (h *BufPane) Redo() bool {
925         h.Buf.Redo()
926         InfoBar.Message("Redid action")
927         h.Relocate()
928         return true
929 }
930
931 // Copy the selection to the system clipboard
932 func (h *BufPane) Copy() bool {
933         if h.Cursor.HasSelection() {
934                 h.Cursor.CopySelection("clipboard")
935                 h.freshClip = true
936                 if clipboard.Unsupported {
937                         InfoBar.Message("Copied selection (install xclip for external clipboard)")
938                 } else {
939                         InfoBar.Message("Copied selection")
940                 }
941         }
942         h.Relocate()
943         return true
944 }
945
946 // Copy the current line to the clipboard
947 func (h *BufPane) CopyLine() bool {
948         if h.Cursor.HasSelection() {
949                 return false
950         } else {
951                 h.Cursor.SelectLine()
952                 h.Cursor.CopySelection("clipboard")
953                 h.freshClip = true
954                 if clipboard.Unsupported {
955                         InfoBar.Message("Copied line (install xclip for external clipboard)")
956                 } else {
957                         InfoBar.Message("Copied line")
958                 }
959         }
960         h.Cursor.Deselect(true)
961         h.Relocate()
962         return true
963 }
964
965 // CutLine cuts the current line to the clipboard
966 func (h *BufPane) CutLine() bool {
967         h.Cursor.SelectLine()
968         if !h.Cursor.HasSelection() {
969                 return false
970         }
971         if h.freshClip == true {
972                 if h.Cursor.HasSelection() {
973                         if clip, err := clipboard.ReadAll("clipboard"); err != nil {
974                                 // messenger.Error(err)
975                         } else {
976                                 clipboard.WriteAll(clip+string(h.Cursor.GetSelection()), "clipboard")
977                         }
978                 }
979         } else if time.Since(h.lastCutTime)/time.Second > 10*time.Second || h.freshClip == false {
980                 h.Copy()
981         }
982         h.freshClip = true
983         h.lastCutTime = time.Now()
984         h.Cursor.DeleteSelection()
985         h.Cursor.ResetSelection()
986         InfoBar.Message("Cut line")
987         h.Relocate()
988         return true
989 }
990
991 // Cut the selection to the system clipboard
992 func (h *BufPane) Cut() bool {
993         if h.Cursor.HasSelection() {
994                 h.Cursor.CopySelection("clipboard")
995                 h.Cursor.DeleteSelection()
996                 h.Cursor.ResetSelection()
997                 h.freshClip = true
998                 InfoBar.Message("Cut selection")
999
1000                 h.Relocate()
1001                 return true
1002         } else {
1003                 return h.CutLine()
1004         }
1005 }
1006
1007 // DuplicateLine duplicates the current line or selection
1008 func (h *BufPane) DuplicateLine() bool {
1009         if h.Cursor.HasSelection() {
1010                 h.Buf.Insert(h.Cursor.CurSelection[1], string(h.Cursor.GetSelection()))
1011         } else {
1012                 h.Cursor.End()
1013                 h.Buf.Insert(h.Cursor.Loc, "\n"+string(h.Buf.LineBytes(h.Cursor.Y)))
1014                 // h.Cursor.Right()
1015         }
1016
1017         InfoBar.Message("Duplicated line")
1018         h.Relocate()
1019         return true
1020 }
1021
1022 // DeleteLine deletes the current line
1023 func (h *BufPane) DeleteLine() bool {
1024         h.Cursor.SelectLine()
1025         if !h.Cursor.HasSelection() {
1026                 return false
1027         }
1028         h.Cursor.DeleteSelection()
1029         h.Cursor.ResetSelection()
1030         InfoBar.Message("Deleted line")
1031         h.Relocate()
1032         return true
1033 }
1034
1035 // MoveLinesUp moves up the current line or selected lines if any
1036 func (h *BufPane) MoveLinesUp() bool {
1037         if h.Cursor.HasSelection() {
1038                 if h.Cursor.CurSelection[0].Y == 0 {
1039                         InfoBar.Message("Cannot move further up")
1040                         return false
1041                 }
1042                 start := h.Cursor.CurSelection[0].Y
1043                 end := h.Cursor.CurSelection[1].Y
1044                 sel := 1
1045                 if start > end {
1046                         end, start = start, end
1047                         sel = 0
1048                 }
1049
1050                 compensate := false
1051                 if h.Cursor.CurSelection[sel].X != 0 {
1052                         end++
1053                 } else {
1054                         compensate = true
1055                 }
1056
1057                 h.Buf.MoveLinesUp(
1058                         start,
1059                         end,
1060                 )
1061                 if compensate {
1062                         h.Cursor.CurSelection[sel].Y -= 1
1063                 }
1064         } else {
1065                 if h.Cursor.Loc.Y == 0 {
1066                         InfoBar.Message("Cannot move further up")
1067                         return false
1068                 }
1069                 h.Buf.MoveLinesUp(
1070                         h.Cursor.Loc.Y,
1071                         h.Cursor.Loc.Y+1,
1072                 )
1073         }
1074
1075         h.Relocate()
1076         return true
1077 }
1078
1079 // MoveLinesDown moves down the current line or selected lines if any
1080 func (h *BufPane) MoveLinesDown() bool {
1081         if h.Cursor.HasSelection() {
1082                 if h.Cursor.CurSelection[1].Y >= h.Buf.LinesNum() {
1083                         InfoBar.Message("Cannot move further down")
1084                         return false
1085                 }
1086                 start := h.Cursor.CurSelection[0].Y
1087                 end := h.Cursor.CurSelection[1].Y
1088                 sel := 1
1089                 if start > end {
1090                         end, start = start, end
1091                         sel = 0
1092                 }
1093
1094                 if h.Cursor.CurSelection[sel].X != 0 {
1095                         end++
1096                 }
1097
1098                 h.Buf.MoveLinesDown(
1099                         start,
1100                         end,
1101                 )
1102         } else {
1103                 if h.Cursor.Loc.Y >= h.Buf.LinesNum()-1 {
1104                         InfoBar.Message("Cannot move further down")
1105                         return false
1106                 }
1107                 h.Buf.MoveLinesDown(
1108                         h.Cursor.Loc.Y,
1109                         h.Cursor.Loc.Y+1,
1110                 )
1111         }
1112
1113         h.Relocate()
1114         return true
1115 }
1116
1117 // Paste whatever is in the system clipboard into the buffer
1118 // Delete and paste if the user has a selection
1119 func (h *BufPane) Paste() bool {
1120         clip, _ := clipboard.ReadAll("clipboard")
1121         h.paste(clip)
1122         h.Relocate()
1123         return true
1124 }
1125
1126 // PastePrimary pastes from the primary clipboard (only use on linux)
1127 func (h *BufPane) PastePrimary() bool {
1128         clip, _ := clipboard.ReadAll("primary")
1129         h.paste(clip)
1130         h.Relocate()
1131         return true
1132 }
1133
1134 func (h *BufPane) paste(clip string) {
1135         if h.Buf.Settings["smartpaste"].(bool) {
1136                 if h.Cursor.X > 0 && len(util.GetLeadingWhitespace([]byte(strings.TrimLeft(clip, "\r\n")))) == 0 {
1137                         leadingWS := util.GetLeadingWhitespace(h.Buf.LineBytes(h.Cursor.Y))
1138                         clip = strings.Replace(clip, "\n", "\n"+string(leadingWS), -1)
1139                 }
1140         }
1141
1142         if h.Cursor.HasSelection() {
1143                 h.Cursor.DeleteSelection()
1144                 h.Cursor.ResetSelection()
1145         }
1146
1147         h.Buf.Insert(h.Cursor.Loc, clip)
1148         // h.Cursor.Loc = h.Cursor.Loc.Move(Count(clip), h.Buf)
1149         h.freshClip = false
1150         if clipboard.Unsupported {
1151                 InfoBar.Message("Pasted clipboard (install xclip for external clipboard)")
1152         } else {
1153                 InfoBar.Message("Pasted clipboard")
1154         }
1155 }
1156
1157 // JumpToMatchingBrace moves the cursor to the matching brace if it is
1158 // currently on a brace
1159 func (h *BufPane) JumpToMatchingBrace() bool {
1160         for _, bp := range buffer.BracePairs {
1161                 r := h.Cursor.RuneUnder(h.Cursor.X)
1162                 rl := h.Cursor.RuneUnder(h.Cursor.X - 1)
1163                 if r == bp[0] || r == bp[1] || rl == bp[0] || rl == bp[1] {
1164                         matchingBrace, left, found := h.Buf.FindMatchingBrace(bp, h.Cursor.Loc)
1165                         if found {
1166                                 if left {
1167                                         h.Cursor.GotoLoc(matchingBrace)
1168                                 } else {
1169                                         h.Cursor.GotoLoc(matchingBrace.Move(1, h.Buf))
1170                                 }
1171                         } else {
1172                                 return false
1173                         }
1174                 }
1175         }
1176
1177         h.Relocate()
1178         return true
1179 }
1180
1181 // SelectAll selects the entire buffer
1182 func (h *BufPane) SelectAll() bool {
1183         h.Cursor.SetSelectionStart(h.Buf.Start())
1184         h.Cursor.SetSelectionEnd(h.Buf.End())
1185         // Put the cursor at the beginning
1186         h.Cursor.X = 0
1187         h.Cursor.Y = 0
1188         h.Relocate()
1189         return true
1190 }
1191
1192 // OpenFile opens a new file in the buffer
1193 func (h *BufPane) OpenFile() bool {
1194         InfoBar.Prompt("> ", "open ", "Open", nil, func(resp string, canceled bool) {
1195                 if !canceled {
1196                         h.HandleCommand(resp)
1197                 }
1198         })
1199         return true
1200 }
1201
1202 // OpenFile opens a new file in the buffer
1203 func (h *BufPane) JumpLine() bool {
1204         InfoBar.Prompt("> ", "goto ", "Command", nil, func(resp string, canceled bool) {
1205                 if !canceled {
1206                         h.HandleCommand(resp)
1207                 }
1208         })
1209         return true
1210 }
1211
1212 // Start moves the viewport to the start of the buffer
1213 func (h *BufPane) Start() bool {
1214         v := h.GetView()
1215         v.StartLine = 0
1216         h.SetView(v)
1217         return true
1218 }
1219
1220 // End moves the viewport to the end of the buffer
1221 func (h *BufPane) End() bool {
1222         // TODO: softwrap problems?
1223         v := h.GetView()
1224         if v.Height > h.Buf.LinesNum() {
1225                 v.StartLine = 0
1226                 h.SetView(v)
1227         } else {
1228                 v.StartLine = h.Buf.LinesNum() - v.Height
1229                 h.SetView(v)
1230         }
1231         return true
1232 }
1233
1234 // PageUp scrolls the view up a page
1235 func (h *BufPane) PageUp() bool {
1236         v := h.GetView()
1237         if v.StartLine > v.Height {
1238                 h.ScrollUp(v.Height)
1239         } else {
1240                 v.StartLine = 0
1241         }
1242         h.SetView(v)
1243         return true
1244 }
1245
1246 // PageDown scrolls the view down a page
1247 func (h *BufPane) PageDown() bool {
1248         v := h.GetView()
1249         if h.Buf.LinesNum()-(v.StartLine+v.Height) > v.Height {
1250                 h.ScrollDown(v.Height)
1251         } else if h.Buf.LinesNum() >= v.Height {
1252                 v.StartLine = h.Buf.LinesNum() - v.Height
1253         }
1254         return true
1255 }
1256
1257 // SelectPageUp selects up one page
1258 func (h *BufPane) SelectPageUp() bool {
1259         if !h.Cursor.HasSelection() {
1260                 h.Cursor.OrigSelection[0] = h.Cursor.Loc
1261         }
1262         h.Cursor.UpN(h.GetView().Height)
1263         h.Cursor.SelectTo(h.Cursor.Loc)
1264         h.Relocate()
1265         return true
1266 }
1267
1268 // SelectPageDown selects down one page
1269 func (h *BufPane) SelectPageDown() bool {
1270         if !h.Cursor.HasSelection() {
1271                 h.Cursor.OrigSelection[0] = h.Cursor.Loc
1272         }
1273         h.Cursor.DownN(h.GetView().Height)
1274         h.Cursor.SelectTo(h.Cursor.Loc)
1275         h.Relocate()
1276         return true
1277 }
1278
1279 // CursorPageUp places the cursor a page up
1280 func (h *BufPane) CursorPageUp() bool {
1281         h.Cursor.Deselect(true)
1282
1283         if h.Cursor.HasSelection() {
1284                 h.Cursor.Loc = h.Cursor.CurSelection[0]
1285                 h.Cursor.ResetSelection()
1286                 h.Cursor.StoreVisualX()
1287         }
1288         h.Cursor.UpN(h.GetView().Height)
1289         h.Relocate()
1290         return true
1291 }
1292
1293 // CursorPageDown places the cursor a page up
1294 func (h *BufPane) CursorPageDown() bool {
1295         h.Cursor.Deselect(false)
1296
1297         if h.Cursor.HasSelection() {
1298                 h.Cursor.Loc = h.Cursor.CurSelection[1]
1299                 h.Cursor.ResetSelection()
1300                 h.Cursor.StoreVisualX()
1301         }
1302         h.Cursor.DownN(h.GetView().Height)
1303         h.Relocate()
1304         return true
1305 }
1306
1307 // HalfPageUp scrolls the view up half a page
1308 func (h *BufPane) HalfPageUp() bool {
1309         v := h.GetView()
1310         if v.StartLine > v.Height/2 {
1311                 h.ScrollUp(v.Height / 2)
1312         } else {
1313                 v.StartLine = 0
1314         }
1315         h.SetView(v)
1316         return true
1317 }
1318
1319 // HalfPageDown scrolls the view down half a page
1320 func (h *BufPane) HalfPageDown() bool {
1321         v := h.GetView()
1322         if h.Buf.LinesNum()-(v.StartLine+v.Height) > v.Height/2 {
1323                 h.ScrollDown(v.Height / 2)
1324         } else {
1325                 if h.Buf.LinesNum() >= v.Height {
1326                         v.StartLine = h.Buf.LinesNum() - v.Height
1327                 }
1328         }
1329         h.SetView(v)
1330         return true
1331 }
1332
1333 // ToggleDiffGutter turns the diff gutter off and on
1334 func (h *BufPane) ToggleDiffGutter() bool {
1335         if !h.Buf.Settings["diffgutter"].(bool) {
1336                 h.Buf.Settings["diffgutter"] = true
1337                 h.Buf.UpdateDiff(func(synchronous bool) {
1338                         screen.Redraw()
1339                 })
1340                 InfoBar.Message("Enabled diff gutter")
1341         } else {
1342                 h.Buf.Settings["diffgutter"] = false
1343                 InfoBar.Message("Disabled diff gutter")
1344         }
1345         return true
1346 }
1347
1348 // ToggleRuler turns line numbers off and on
1349 func (h *BufPane) ToggleRuler() bool {
1350         if !h.Buf.Settings["ruler"].(bool) {
1351                 h.Buf.Settings["ruler"] = true
1352                 InfoBar.Message("Enabled ruler")
1353         } else {
1354                 h.Buf.Settings["ruler"] = false
1355                 InfoBar.Message("Disabled ruler")
1356         }
1357         return true
1358 }
1359
1360 // ClearStatus clears the messenger bar
1361 func (h *BufPane) ClearStatus() bool {
1362         InfoBar.Message("")
1363         return true
1364 }
1365
1366 // ToggleHelp toggles the help screen
1367 func (h *BufPane) ToggleHelp() bool {
1368         if h.Buf.Type == buffer.BTHelp {
1369                 h.Quit()
1370         } else {
1371                 h.openHelp("help")
1372         }
1373         return true
1374 }
1375
1376 // ToggleKeyMenu toggles the keymenu option and resizes all tabs
1377 func (h *BufPane) ToggleKeyMenu() bool {
1378         config.GlobalSettings["keymenu"] = !config.GetGlobalOption("keymenu").(bool)
1379         Tabs.Resize()
1380         return true
1381 }
1382
1383 // ShellMode opens a terminal to run a shell command
1384 func (h *BufPane) ShellMode() bool {
1385         InfoBar.Prompt("$ ", "", "Shell", nil, func(resp string, canceled bool) {
1386                 if !canceled {
1387                         // The true here is for openTerm to make the command interactive
1388                         shell.RunInteractiveShell(resp, true, false)
1389                 }
1390         })
1391
1392         return true
1393 }
1394
1395 // CommandMode lets the user enter a command
1396 func (h *BufPane) CommandMode() bool {
1397         InfoBar.Prompt("> ", "", "Command", nil, func(resp string, canceled bool) {
1398                 if !canceled {
1399                         h.HandleCommand(resp)
1400                 }
1401         })
1402         return true
1403 }
1404
1405 // ToggleOverwriteMode lets the user toggle the text overwrite mode
1406 func (h *BufPane) ToggleOverwriteMode() bool {
1407         h.isOverwriteMode = !h.isOverwriteMode
1408         return true
1409 }
1410
1411 // Escape leaves current mode
1412 func (h *BufPane) Escape() bool {
1413         return true
1414 }
1415
1416 // Quit this will close the current tab or view that is open
1417 func (h *BufPane) Quit() bool {
1418         quit := func() {
1419                 h.Buf.Close()
1420                 if len(MainTab().Panes) > 1 {
1421                         h.Unsplit()
1422                 } else if len(Tabs.List) > 1 {
1423                         Tabs.RemoveTab(h.splitID)
1424                 } else {
1425                         screen.Screen.Fini()
1426                         InfoBar.Close()
1427                         runtime.Goexit()
1428                 }
1429         }
1430         if h.Buf.Modified() {
1431                 if config.GlobalSettings["autosave"].(float64) > 0 {
1432                         // autosave on means we automatically save when quitting
1433                         h.SaveCB("Quit", func() {
1434                                 quit()
1435                         })
1436                 } else {
1437                         InfoBar.YNPrompt("Save changes to "+h.Buf.GetName()+" before closing? (y,n,esc)", func(yes, canceled bool) {
1438                                 if !canceled && !yes {
1439                                         quit()
1440                                 } else if !canceled && yes {
1441                                         h.SaveCB("Quit", func() {
1442                                                 quit()
1443                                         })
1444                                 }
1445                         })
1446                 }
1447         } else {
1448                 quit()
1449         }
1450         return true
1451 }
1452
1453 // QuitAll quits the whole editor; all splits and tabs
1454 func (h *BufPane) QuitAll() bool {
1455         anyModified := false
1456         for _, b := range buffer.OpenBuffers {
1457                 if b.Modified() {
1458                         anyModified = true
1459                         break
1460                 }
1461         }
1462
1463         quit := func() {
1464                 for _, b := range buffer.OpenBuffers {
1465                         b.Close()
1466                 }
1467                 screen.Screen.Fini()
1468                 InfoBar.Close()
1469                 runtime.Goexit()
1470         }
1471
1472         if anyModified {
1473                 InfoBar.YNPrompt("Quit micro? (all open buffers will be closed without saving)", func(yes, canceled bool) {
1474                         if !canceled && yes {
1475                                 quit()
1476                         }
1477                 })
1478         } else {
1479                 quit()
1480         }
1481
1482         return true
1483 }
1484
1485 // AddTab adds a new tab with an empty buffer
1486 func (h *BufPane) AddTab() bool {
1487         width, height := screen.Screen.Size()
1488         iOffset := config.GetInfoBarOffset()
1489         b := buffer.NewBufferFromString("", "", buffer.BTDefault)
1490         tp := NewTabFromBuffer(0, 0, width, height-iOffset, b)
1491         Tabs.AddTab(tp)
1492         Tabs.SetActive(len(Tabs.List) - 1)
1493
1494         return true
1495 }
1496
1497 // PreviousTab switches to the previous tab in the tab list
1498 func (h *BufPane) PreviousTab() bool {
1499         tabsLen := len(Tabs.List)
1500         a := Tabs.Active() + tabsLen
1501         Tabs.SetActive((a - 1) % tabsLen)
1502
1503         return true
1504 }
1505
1506 // NextTab switches to the next tab in the tab list
1507 func (h *BufPane) NextTab() bool {
1508         a := Tabs.Active()
1509         Tabs.SetActive((a + 1) % len(Tabs.List))
1510
1511         return true
1512 }
1513
1514 // VSplitAction opens an empty vertical split
1515 func (h *BufPane) VSplitAction() bool {
1516         h.VSplitBuf(buffer.NewBufferFromString("", "", buffer.BTDefault))
1517
1518         return true
1519 }
1520
1521 // HSplitAction opens an empty horizontal split
1522 func (h *BufPane) HSplitAction() bool {
1523         h.HSplitBuf(buffer.NewBufferFromString("", "", buffer.BTDefault))
1524
1525         return true
1526 }
1527
1528 // Unsplit closes all splits in the current tab except the active one
1529 func (h *BufPane) Unsplit() bool {
1530         tab := h.tab
1531         n := tab.GetNode(h.splitID)
1532         ok := n.Unsplit()
1533         if ok {
1534                 tab.RemovePane(tab.GetPane(h.splitID))
1535                 tab.Resize()
1536                 tab.SetActive(len(tab.Panes) - 1)
1537
1538                 return true
1539         }
1540         return false
1541 }
1542
1543 // NextSplit changes the view to the next split
1544 func (h *BufPane) NextSplit() bool {
1545         a := h.tab.active
1546         if a < len(h.tab.Panes)-1 {
1547                 a++
1548         } else {
1549                 a = 0
1550         }
1551
1552         h.tab.SetActive(a)
1553
1554         return true
1555 }
1556
1557 // PreviousSplit changes the view to the previous split
1558 func (h *BufPane) PreviousSplit() bool {
1559         a := h.tab.active
1560         if a > 0 {
1561                 a--
1562         } else {
1563                 a = len(h.tab.Panes) - 1
1564         }
1565         h.tab.SetActive(a)
1566
1567         return true
1568 }
1569
1570 var curmacro []interface{}
1571 var recording_macro bool
1572
1573 // ToggleMacro toggles recording of a macro
1574 func (h *BufPane) ToggleMacro() bool {
1575         recording_macro = !recording_macro
1576         if recording_macro {
1577                 curmacro = []interface{}{}
1578                 InfoBar.Message("Recording")
1579         } else {
1580                 InfoBar.Message("Stopped recording")
1581         }
1582         h.Relocate()
1583         return true
1584 }
1585
1586 // PlayMacro plays back the most recently recorded macro
1587 func (h *BufPane) PlayMacro() bool {
1588         if recording_macro {
1589                 return false
1590         }
1591         for _, action := range curmacro {
1592                 switch t := action.(type) {
1593                 case rune:
1594                         h.DoRuneInsert(t)
1595                 case func(*BufPane) bool:
1596                         t(h)
1597                 }
1598         }
1599         h.Relocate()
1600         return true
1601 }
1602
1603 // SpawnMultiCursor creates a new multiple cursor at the next occurrence of the current selection or current word
1604 func (h *BufPane) SpawnMultiCursor() bool {
1605         spawner := h.Buf.GetCursor(h.Buf.NumCursors() - 1)
1606         if !spawner.HasSelection() {
1607                 spawner.SelectWord()
1608                 h.multiWord = true
1609                 h.Relocate()
1610                 return true
1611         }
1612
1613         sel := spawner.GetSelection()
1614         searchStart := spawner.CurSelection[1]
1615
1616         search := string(sel)
1617         search = regexp.QuoteMeta(search)
1618         if h.multiWord {
1619                 search = "\\b" + search + "\\b"
1620         }
1621         match, found, err := h.Buf.FindNext(search, h.Buf.Start(), h.Buf.End(), searchStart, true, true)
1622         if err != nil {
1623                 InfoBar.Error(err)
1624         }
1625         if found {
1626                 c := buffer.NewCursor(h.Buf, buffer.Loc{})
1627                 c.SetSelectionStart(match[0])
1628                 c.SetSelectionEnd(match[1])
1629                 c.OrigSelection[0] = c.CurSelection[0]
1630                 c.OrigSelection[1] = c.CurSelection[1]
1631                 c.Loc = c.CurSelection[1]
1632
1633                 h.Buf.AddCursor(c)
1634                 h.Buf.SetCurCursor(h.Buf.NumCursors() - 1)
1635                 h.Buf.MergeCursors()
1636         } else {
1637                 InfoBar.Message("No matches found")
1638         }
1639
1640         h.Relocate()
1641         return true
1642 }
1643
1644 // SpawnMultiCursorUp creates additional cursor, at the same X (if possible), one Y less.
1645 func (h *BufPane) SpawnMultiCursorUp() bool {
1646         if h.Cursor.Y == 0 {
1647                 return false
1648         } else {
1649                 h.Cursor.GotoLoc(buffer.Loc{h.Cursor.X, h.Cursor.Y - 1})
1650                 h.Cursor.Relocate()
1651         }
1652
1653         c := buffer.NewCursor(h.Buf, buffer.Loc{h.Cursor.X, h.Cursor.Y + 1})
1654         h.Buf.AddCursor(c)
1655         h.Buf.SetCurCursor(h.Buf.NumCursors() - 1)
1656         h.Buf.MergeCursors()
1657
1658         h.Relocate()
1659         return true
1660 }
1661
1662 // SpawnMultiCursorDown creates additional cursor, at the same X (if possible), one Y more.
1663 func (h *BufPane) SpawnMultiCursorDown() bool {
1664         if h.Cursor.Y+1 == h.Buf.LinesNum() {
1665                 return false
1666         } else {
1667                 h.Cursor.GotoLoc(buffer.Loc{h.Cursor.X, h.Cursor.Y + 1})
1668                 h.Cursor.Relocate()
1669         }
1670
1671         c := buffer.NewCursor(h.Buf, buffer.Loc{h.Cursor.X, h.Cursor.Y - 1})
1672         h.Buf.AddCursor(c)
1673         h.Buf.SetCurCursor(h.Buf.NumCursors() - 1)
1674         h.Buf.MergeCursors()
1675         h.Relocate()
1676         return true
1677 }
1678
1679 // SpawnMultiCursorSelect adds a cursor at the beginning of each line of a selection
1680 func (h *BufPane) SpawnMultiCursorSelect() bool {
1681         // Avoid cases where multiple cursors already exist, that would create problems
1682         if h.Buf.NumCursors() > 1 {
1683                 return false
1684         }
1685
1686         var startLine int
1687         var endLine int
1688
1689         a, b := h.Cursor.CurSelection[0].Y, h.Cursor.CurSelection[1].Y
1690         if a > b {
1691                 startLine, endLine = b, a
1692         } else {
1693                 startLine, endLine = a, b
1694         }
1695
1696         if h.Cursor.HasSelection() {
1697                 h.Cursor.ResetSelection()
1698                 h.Cursor.GotoLoc(buffer.Loc{0, startLine})
1699
1700                 for i := startLine; i <= endLine; i++ {
1701                         c := buffer.NewCursor(h.Buf, buffer.Loc{0, i})
1702                         c.StoreVisualX()
1703                         h.Buf.AddCursor(c)
1704                 }
1705                 h.Buf.MergeCursors()
1706         } else {
1707                 return false
1708         }
1709         InfoBar.Message("Added cursors from selection")
1710         return true
1711 }
1712
1713 // MouseMultiCursor is a mouse action which puts a new cursor at the mouse position
1714 func (h *BufPane) MouseMultiCursor(e *tcell.EventMouse) bool {
1715         b := h.Buf
1716         mx, my := e.Position()
1717         mouseLoc := h.LocFromVisual(buffer.Loc{X: mx, Y: my})
1718         c := buffer.NewCursor(b, mouseLoc)
1719         b.AddCursor(c)
1720         b.MergeCursors()
1721
1722         return true
1723 }
1724
1725 // SkipMultiCursor moves the current multiple cursor to the next available position
1726 func (h *BufPane) SkipMultiCursor() bool {
1727         lastC := h.Buf.GetCursor(h.Buf.NumCursors() - 1)
1728         sel := lastC.GetSelection()
1729         searchStart := lastC.CurSelection[1]
1730
1731         search := string(sel)
1732         search = regexp.QuoteMeta(search)
1733         if h.multiWord {
1734                 search = "\\b" + search + "\\b"
1735         }
1736
1737         match, found, err := h.Buf.FindNext(search, h.Buf.Start(), h.Buf.End(), searchStart, true, true)
1738         if err != nil {
1739                 InfoBar.Error(err)
1740         }
1741         if found {
1742                 lastC.SetSelectionStart(match[0])
1743                 lastC.SetSelectionEnd(match[1])
1744                 lastC.OrigSelection[0] = lastC.CurSelection[0]
1745                 lastC.OrigSelection[1] = lastC.CurSelection[1]
1746                 lastC.Loc = lastC.CurSelection[1]
1747
1748                 h.Buf.MergeCursors()
1749                 h.Buf.SetCurCursor(h.Buf.NumCursors() - 1)
1750         } else {
1751                 InfoBar.Message("No matches found")
1752         }
1753         h.Relocate()
1754         return true
1755 }
1756
1757 // RemoveMultiCursor removes the latest multiple cursor
1758 func (h *BufPane) RemoveMultiCursor() bool {
1759         if h.Buf.NumCursors() > 1 {
1760                 h.Buf.RemoveCursor(h.Buf.NumCursors() - 1)
1761                 h.Buf.SetCurCursor(h.Buf.NumCursors() - 1)
1762                 h.Buf.UpdateCursors()
1763         } else {
1764                 h.multiWord = false
1765         }
1766         h.Relocate()
1767         return true
1768 }
1769
1770 // RemoveAllMultiCursors removes all cursors except the base cursor
1771 func (h *BufPane) RemoveAllMultiCursors() bool {
1772         h.Buf.ClearCursors()
1773         h.multiWord = false
1774         h.Relocate()
1775         return true
1776 }
1777
1778 // None is an action that does nothing
1779 func (h *BufPane) None() bool {
1780         return true
1781 }