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