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