]> git.lizzy.rs Git - micro.git/blob - cmd/micro/actions.go
5f64c39ba1af2cc05807dad8dbbe051c41976a73
[micro.git] / cmd / micro / actions.go
1 package main
2
3 import (
4         "os"
5         "strconv"
6         "strings"
7         "time"
8
9         "github.com/yuin/gopher-lua"
10         "github.com/zyedidia/clipboard"
11 )
12
13 // PreActionCall executes the lua pre callback if possible
14 func PreActionCall(funcName string, view *View) bool {
15         executeAction := true
16         for pl := range loadedPlugins {
17                 ret, err := Call(pl+".pre"+funcName, view)
18                 if err != nil && !strings.HasPrefix(err.Error(), "function does not exist") {
19                         TermMessage(err)
20                         continue
21                 }
22                 if ret == lua.LFalse {
23                         executeAction = false
24                 }
25         }
26         return executeAction
27 }
28
29 // PostActionCall executes the lua plugin callback if possible
30 func PostActionCall(funcName string, view *View) bool {
31         relocate := true
32         for pl := range loadedPlugins {
33                 ret, err := Call(pl+".on"+funcName, view)
34                 if err != nil && !strings.HasPrefix(err.Error(), "function does not exist") {
35                         TermMessage(err)
36                         continue
37                 }
38                 if ret == lua.LFalse {
39                         relocate = false
40                 }
41         }
42         return relocate
43 }
44
45 func (v *View) deselect(index int) bool {
46         if v.Cursor.HasSelection() {
47                 v.Cursor.Loc = v.Cursor.CurSelection[index]
48                 v.Cursor.ResetSelection()
49                 return true
50         }
51         return false
52 }
53
54 // Center centers the view on the cursor
55 func (v *View) Center(usePlugin bool) bool {
56         if usePlugin && !PreActionCall("Center", v) {
57                 return false
58         }
59
60         v.Topline = v.Cursor.Y - v.Height/2
61         if v.Topline+v.Height > v.Buf.NumLines {
62                 v.Topline = v.Buf.NumLines - v.Height
63         }
64         if v.Topline < 0 {
65                 v.Topline = 0
66         }
67
68         if usePlugin {
69                 return PostActionCall("Center", v)
70         }
71         return true
72 }
73
74 // CursorUp moves the cursor up
75 func (v *View) CursorUp(usePlugin bool) bool {
76         if usePlugin && !PreActionCall("CursorUp", v) {
77                 return false
78         }
79
80         v.deselect(0)
81         v.Cursor.Up()
82
83         if usePlugin {
84                 return PostActionCall("CursorUp", v)
85         }
86         return true
87 }
88
89 // CursorDown moves the cursor down
90 func (v *View) CursorDown(usePlugin bool) bool {
91         if usePlugin && !PreActionCall("CursorDown", v) {
92                 return false
93         }
94
95         v.deselect(1)
96         v.Cursor.Down()
97
98         if usePlugin {
99                 return PostActionCall("CursorDown", v)
100         }
101         return true
102 }
103
104 // CursorLeft moves the cursor left
105 func (v *View) CursorLeft(usePlugin bool) bool {
106         if usePlugin && !PreActionCall("CursorLeft", v) {
107                 return false
108         }
109
110         if v.Cursor.HasSelection() {
111                 v.Cursor.Loc = v.Cursor.CurSelection[0]
112                 v.Cursor.ResetSelection()
113         } else {
114                 tabstospaces := v.Buf.Settings["tabstospaces"].(bool)
115                 tabmovement := v.Buf.Settings["tabmovement"].(bool)
116                 if tabstospaces && tabmovement {
117                         tabsize := int(v.Buf.Settings["tabsize"].(float64))
118                         line := v.Buf.Line(v.Cursor.Y)
119                         if v.Cursor.X-tabsize >= 0 && line[v.Cursor.X-tabsize:v.Cursor.X] == Spaces(tabsize) && IsStrWhitespace(line[0:v.Cursor.X-tabsize]) {
120                                 for i := 0; i < tabsize; i++ {
121                                         v.Cursor.Left()
122                                 }
123                         } else {
124                                 v.Cursor.Left()
125                         }
126                 } else {
127                         v.Cursor.Left()
128                 }
129         }
130
131         if usePlugin {
132                 return PostActionCall("CursorLeft", v)
133         }
134         return true
135 }
136
137 // CursorRight moves the cursor right
138 func (v *View) CursorRight(usePlugin bool) bool {
139         if usePlugin && !PreActionCall("CursorRight", v) {
140                 return false
141         }
142
143         if v.Cursor.HasSelection() {
144                 v.Cursor.Loc = v.Cursor.CurSelection[1].Move(-1, v.Buf)
145                 v.Cursor.ResetSelection()
146         } else {
147                 tabstospaces := v.Buf.Settings["tabstospaces"].(bool)
148                 tabmovement := v.Buf.Settings["tabmovement"].(bool)
149                 if tabstospaces && tabmovement {
150                         tabsize := int(v.Buf.Settings["tabsize"].(float64))
151                         line := v.Buf.Line(v.Cursor.Y)
152                         if v.Cursor.X+tabsize < Count(line) && line[v.Cursor.X:v.Cursor.X+tabsize] == Spaces(tabsize) && IsStrWhitespace(line[0:v.Cursor.X]) {
153                                 for i := 0; i < tabsize; i++ {
154                                         v.Cursor.Right()
155                                 }
156                         } else {
157                                 v.Cursor.Right()
158                         }
159                 } else {
160                         v.Cursor.Right()
161                 }
162         }
163
164         if usePlugin {
165                 return PostActionCall("CursorRight", v)
166         }
167         return true
168 }
169
170 // WordRight moves the cursor one word to the right
171 func (v *View) WordRight(usePlugin bool) bool {
172         if usePlugin && !PreActionCall("WordRight", v) {
173                 return false
174         }
175
176         v.Cursor.WordRight()
177
178         if usePlugin {
179                 return PostActionCall("WordRight", v)
180         }
181         return true
182 }
183
184 // WordLeft moves the cursor one word to the left
185 func (v *View) WordLeft(usePlugin bool) bool {
186         if usePlugin && !PreActionCall("WordLeft", v) {
187                 return false
188         }
189
190         v.Cursor.WordLeft()
191
192         if usePlugin {
193                 return PostActionCall("WordLeft", v)
194         }
195         return true
196 }
197
198 // SelectUp selects up one line
199 func (v *View) SelectUp(usePlugin bool) bool {
200         if usePlugin && !PreActionCall("SelectUp", v) {
201                 return false
202         }
203
204         if !v.Cursor.HasSelection() {
205                 v.Cursor.OrigSelection[0] = v.Cursor.Loc
206         }
207         v.Cursor.Up()
208         v.Cursor.SelectTo(v.Cursor.Loc)
209
210         if usePlugin {
211                 return PostActionCall("SelectUp", v)
212         }
213         return true
214 }
215
216 // SelectDown selects down one line
217 func (v *View) SelectDown(usePlugin bool) bool {
218         if usePlugin && !PreActionCall("SelectDown", v) {
219                 return false
220         }
221
222         if !v.Cursor.HasSelection() {
223                 v.Cursor.OrigSelection[0] = v.Cursor.Loc
224         }
225         v.Cursor.Down()
226         v.Cursor.SelectTo(v.Cursor.Loc)
227
228         if usePlugin {
229                 return PostActionCall("SelectDown", v)
230         }
231         return true
232 }
233
234 // SelectLeft selects the character to the left of the cursor
235 func (v *View) SelectLeft(usePlugin bool) bool {
236         if usePlugin && !PreActionCall("SelectLeft", v) {
237                 return false
238         }
239
240         loc := v.Cursor.Loc
241         count := v.Buf.End().Move(-1, v.Buf)
242         if loc.GreaterThan(count) {
243                 loc = count
244         }
245         if !v.Cursor.HasSelection() {
246                 v.Cursor.OrigSelection[0] = loc
247         }
248         v.Cursor.Left()
249         v.Cursor.SelectTo(v.Cursor.Loc)
250
251         if usePlugin {
252                 return PostActionCall("SelectLeft", v)
253         }
254         return true
255 }
256
257 // SelectRight selects the character to the right of the cursor
258 func (v *View) SelectRight(usePlugin bool) bool {
259         if usePlugin && !PreActionCall("SelectRight", v) {
260                 return false
261         }
262
263         loc := v.Cursor.Loc
264         count := v.Buf.End().Move(-1, v.Buf)
265         if loc.GreaterThan(count) {
266                 loc = count
267         }
268         if !v.Cursor.HasSelection() {
269                 v.Cursor.OrigSelection[0] = loc
270         }
271         v.Cursor.Right()
272         v.Cursor.SelectTo(v.Cursor.Loc)
273
274         if usePlugin {
275                 return PostActionCall("SelectRight", v)
276         }
277         return true
278 }
279
280 // SelectWordRight selects the word to the right of the cursor
281 func (v *View) SelectWordRight(usePlugin bool) bool {
282         if usePlugin && !PreActionCall("SelectWordRight", v) {
283                 return false
284         }
285
286         if !v.Cursor.HasSelection() {
287                 v.Cursor.OrigSelection[0] = v.Cursor.Loc
288         }
289         v.Cursor.WordRight()
290         v.Cursor.SelectTo(v.Cursor.Loc)
291
292         if usePlugin {
293                 return PostActionCall("SelectWordRight", v)
294         }
295         return true
296 }
297
298 // SelectWordLeft selects the word to the left of the cursor
299 func (v *View) SelectWordLeft(usePlugin bool) bool {
300         if usePlugin && !PreActionCall("SelectWordLeft", v) {
301                 return false
302         }
303
304         if !v.Cursor.HasSelection() {
305                 v.Cursor.OrigSelection[0] = v.Cursor.Loc
306         }
307         v.Cursor.WordLeft()
308         v.Cursor.SelectTo(v.Cursor.Loc)
309
310         if usePlugin {
311                 return PostActionCall("SelectWordLeft", v)
312         }
313         return true
314 }
315
316 // StartOfLine moves the cursor to the start of the line
317 func (v *View) StartOfLine(usePlugin bool) bool {
318         if usePlugin && !PreActionCall("StartOfLine", v) {
319                 return false
320         }
321
322         v.deselect(0)
323
324         v.Cursor.Start()
325
326         if usePlugin {
327                 return PostActionCall("StartOfLine", v)
328         }
329         return true
330 }
331
332 // EndOfLine moves the cursor to the end of the line
333 func (v *View) EndOfLine(usePlugin bool) bool {
334         if usePlugin && !PreActionCall("EndOfLine", v) {
335                 return false
336         }
337
338         v.deselect(0)
339
340         v.Cursor.End()
341
342         if usePlugin {
343                 return PostActionCall("EndOfLine", v)
344         }
345         return true
346 }
347
348 // SelectToStartOfLine selects to the start of the current line
349 func (v *View) SelectToStartOfLine(usePlugin bool) bool {
350         if usePlugin && !PreActionCall("SelectToStartOfLine", v) {
351                 return false
352         }
353
354         if !v.Cursor.HasSelection() {
355                 v.Cursor.OrigSelection[0] = v.Cursor.Loc
356         }
357         v.Cursor.Start()
358         v.Cursor.SelectTo(v.Cursor.Loc)
359
360         if usePlugin {
361                 return PostActionCall("SelectToStartOfLine", v)
362         }
363         return true
364 }
365
366 // SelectToEndOfLine selects to the end of the current line
367 func (v *View) SelectToEndOfLine(usePlugin bool) bool {
368         if usePlugin && !PreActionCall("SelectToEndOfLine", v) {
369                 return false
370         }
371
372         if !v.Cursor.HasSelection() {
373                 v.Cursor.OrigSelection[0] = v.Cursor.Loc
374         }
375         v.Cursor.End()
376         v.Cursor.SelectTo(v.Cursor.Loc)
377
378         if usePlugin {
379                 return PostActionCall("SelectToEndOfLine", v)
380         }
381         return true
382 }
383
384 // CursorStart moves the cursor to the start of the buffer
385 func (v *View) CursorStart(usePlugin bool) bool {
386         if usePlugin && !PreActionCall("CursorStart", v) {
387                 return false
388         }
389
390         v.deselect(0)
391
392         v.Cursor.X = 0
393         v.Cursor.Y = 0
394
395         if usePlugin {
396                 return PostActionCall("CursorStart", v)
397         }
398         return true
399 }
400
401 // CursorEnd moves the cursor to the end of the buffer
402 func (v *View) CursorEnd(usePlugin bool) bool {
403         if usePlugin && !PreActionCall("CursorEnd", v) {
404                 return false
405         }
406
407         v.deselect(0)
408
409         v.Cursor.Loc = v.Buf.End()
410
411         if usePlugin {
412                 return PostActionCall("CursorEnd", v)
413         }
414         return true
415 }
416
417 // SelectToStart selects the text from the cursor to the start of the buffer
418 func (v *View) SelectToStart(usePlugin bool) bool {
419         if usePlugin && !PreActionCall("SelectToStart", v) {
420                 return false
421         }
422
423         if !v.Cursor.HasSelection() {
424                 v.Cursor.OrigSelection[0] = v.Cursor.Loc
425         }
426         v.CursorStart(false)
427         v.Cursor.SelectTo(v.Buf.Start())
428
429         if usePlugin {
430                 return PostActionCall("SelectToStart", v)
431         }
432         return true
433 }
434
435 // SelectToEnd selects the text from the cursor to the end of the buffer
436 func (v *View) SelectToEnd(usePlugin bool) bool {
437         if usePlugin && !PreActionCall("SelectToEnd", v) {
438                 return false
439         }
440
441         if !v.Cursor.HasSelection() {
442                 v.Cursor.OrigSelection[0] = v.Cursor.Loc
443         }
444         v.CursorEnd(false)
445         v.Cursor.SelectTo(v.Buf.End())
446
447         if usePlugin {
448                 return PostActionCall("SelectToEnd", v)
449         }
450         return true
451 }
452
453 // InsertSpace inserts a space
454 func (v *View) InsertSpace(usePlugin bool) bool {
455         if usePlugin && !PreActionCall("InsertSpace", v) {
456                 return false
457         }
458
459         if v.Cursor.HasSelection() {
460                 v.Cursor.DeleteSelection()
461                 v.Cursor.ResetSelection()
462         }
463         v.Buf.Insert(v.Cursor.Loc, " ")
464         v.Cursor.Right()
465
466         if usePlugin {
467                 return PostActionCall("InsertSpace", v)
468         }
469         return true
470 }
471
472 // InsertNewline inserts a newline plus possible some whitespace if autoindent is on
473 func (v *View) InsertNewline(usePlugin bool) bool {
474         if usePlugin && !PreActionCall("InsertNewline", v) {
475                 return false
476         }
477
478         // Insert a newline
479         if v.Cursor.HasSelection() {
480                 v.Cursor.DeleteSelection()
481                 v.Cursor.ResetSelection()
482         }
483
484         v.Buf.Insert(v.Cursor.Loc, "\n")
485         ws := GetLeadingWhitespace(v.Buf.Line(v.Cursor.Y))
486         v.Cursor.Right()
487
488         if v.Buf.Settings["autoindent"].(bool) {
489                 v.Buf.Insert(v.Cursor.Loc, ws)
490                 for i := 0; i < len(ws); i++ {
491                         v.Cursor.Right()
492                 }
493
494                 // Remove the whitespaces if keepautoindent setting is off
495                 if IsSpacesOrTabs(v.Buf.Line(v.Cursor.Y-1)) && !v.Buf.Settings["keepautoindent"].(bool) {
496                         line := v.Buf.Line(v.Cursor.Y - 1)
497                         v.Buf.Remove(Loc{0, v.Cursor.Y - 1}, Loc{Count(line), v.Cursor.Y - 1})
498                 }
499         }
500         v.Cursor.LastVisualX = v.Cursor.GetVisualX()
501
502         if usePlugin {
503                 return PostActionCall("InsertNewline", v)
504         }
505         return true
506 }
507
508 // Backspace deletes the previous character
509 func (v *View) Backspace(usePlugin bool) bool {
510         if usePlugin && !PreActionCall("Backspace", v) {
511                 return false
512         }
513
514         // Delete a character
515         if v.Cursor.HasSelection() {
516                 v.Cursor.DeleteSelection()
517                 v.Cursor.ResetSelection()
518         } else if v.Cursor.Loc.GreaterThan(v.Buf.Start()) {
519                 // We have to do something a bit hacky here because we want to
520                 // delete the line by first moving left and then deleting backwards
521                 // but the undo redo would place the cursor in the wrong place
522                 // So instead we move left, save the position, move back, delete
523                 // and restore the position
524
525                 // If the user is using spaces instead of tabs and they are deleting
526                 // whitespace at the start of the line, we should delete as if it's a
527                 // tab (tabSize number of spaces)
528                 lineStart := v.Buf.Line(v.Cursor.Y)[:v.Cursor.X]
529                 tabSize := int(v.Buf.Settings["tabsize"].(float64))
530                 if v.Buf.Settings["tabstospaces"].(bool) && IsSpaces(lineStart) && len(lineStart) != 0 && len(lineStart)%tabSize == 0 {
531                         loc := v.Cursor.Loc
532                         v.Cursor.Loc = loc.Move(-tabSize, v.Buf)
533                         cx, cy := v.Cursor.X, v.Cursor.Y
534                         v.Cursor.Loc = loc
535                         v.Buf.Remove(loc.Move(-tabSize, v.Buf), loc)
536                         v.Cursor.X, v.Cursor.Y = cx, cy
537                 } else {
538                         v.Cursor.Left()
539                         cx, cy := v.Cursor.X, v.Cursor.Y
540                         v.Cursor.Right()
541                         loc := v.Cursor.Loc
542                         v.Buf.Remove(loc.Move(-1, v.Buf), loc)
543                         v.Cursor.X, v.Cursor.Y = cx, cy
544                 }
545         }
546         v.Cursor.LastVisualX = v.Cursor.GetVisualX()
547
548         if usePlugin {
549                 return PostActionCall("Backspace", v)
550         }
551         return true
552 }
553
554 // DeleteWordRight deletes the word to the right of the cursor
555 func (v *View) DeleteWordRight(usePlugin bool) bool {
556         if usePlugin && !PreActionCall("DeleteWordRight", v) {
557                 return false
558         }
559
560         v.SelectWordRight(false)
561         if v.Cursor.HasSelection() {
562                 v.Cursor.DeleteSelection()
563                 v.Cursor.ResetSelection()
564         }
565
566         if usePlugin {
567                 return PostActionCall("DeleteWordRight", v)
568         }
569         return true
570 }
571
572 // DeleteWordLeft deletes the word to the left of the cursor
573 func (v *View) DeleteWordLeft(usePlugin bool) bool {
574         if usePlugin && !PreActionCall("DeleteWordLeft", v) {
575                 return false
576         }
577
578         v.SelectWordLeft(false)
579         if v.Cursor.HasSelection() {
580                 v.Cursor.DeleteSelection()
581                 v.Cursor.ResetSelection()
582         }
583
584         if usePlugin {
585                 return PostActionCall("DeleteWordLeft", v)
586         }
587         return true
588 }
589
590 // Delete deletes the next character
591 func (v *View) Delete(usePlugin bool) bool {
592         if usePlugin && !PreActionCall("Delete", v) {
593                 return false
594         }
595
596         if v.Cursor.HasSelection() {
597                 v.Cursor.DeleteSelection()
598                 v.Cursor.ResetSelection()
599         } else {
600                 loc := v.Cursor.Loc
601                 if loc.LessThan(v.Buf.End()) {
602                         v.Buf.Remove(loc, loc.Move(1, v.Buf))
603                 }
604         }
605
606         if usePlugin {
607                 return PostActionCall("Delete", v)
608         }
609         return true
610 }
611
612 // IndentSelection indents the current selection
613 func (v *View) IndentSelection(usePlugin bool) bool {
614         if usePlugin && !PreActionCall("IndentSelection", v) {
615                 return false
616         }
617
618         if v.Cursor.HasSelection() {
619                 start := v.Cursor.CurSelection[0]
620                 end := v.Cursor.CurSelection[1]
621                 if end.Y < start.Y {
622                         start, end = end, start
623                 }
624
625                 startY := start.Y
626                 endY := end.Move(-1, v.Buf).Y
627                 endX := end.Move(-1, v.Buf).X
628                 for y := startY; y <= endY; y++ {
629                         tabsize := len(v.Buf.IndentString())
630                         v.Buf.Insert(Loc{0, y}, v.Buf.IndentString())
631                         if y == startY && start.X > 0 {
632                                 v.Cursor.SetSelectionStart(start.Move(tabsize, v.Buf))
633                         }
634                         if y == endY {
635                                 v.Cursor.SetSelectionEnd(Loc{endX + tabsize + 1, endY})
636                         }
637                 }
638                 v.Cursor.Relocate()
639
640                 if usePlugin {
641                         return PostActionCall("IndentSelection", v)
642                 }
643                 return true
644         }
645         return false
646 }
647
648 // OutdentLine moves the current line back one indentation
649 func (v *View) OutdentLine(usePlugin bool) bool {
650         if usePlugin && !PreActionCall("OutdentLine", v) {
651                 return false
652         }
653
654         if v.Cursor.HasSelection() {
655                 return false
656         }
657
658         for x := 0; x < len(v.Buf.IndentString()); x++ {
659                 if len(GetLeadingWhitespace(v.Buf.Line(v.Cursor.Y))) == 0 {
660                         break
661                 }
662                 v.Buf.Remove(Loc{0, v.Cursor.Y}, Loc{1, v.Cursor.Y})
663                 v.Cursor.X -= 1
664         }
665         v.Cursor.Relocate()
666
667         if usePlugin {
668                 return PostActionCall("OutdentLine", v)
669         }
670         return true
671 }
672
673 // OutdentSelection takes the current selection and moves it back one indent level
674 func (v *View) OutdentSelection(usePlugin bool) bool {
675         if usePlugin && !PreActionCall("OutdentSelection", v) {
676                 return false
677         }
678
679         if v.Cursor.HasSelection() {
680                 start := v.Cursor.CurSelection[0]
681                 end := v.Cursor.CurSelection[1]
682                 if end.Y < start.Y {
683                         start, end = end, start
684                 }
685
686                 startY := start.Y
687                 endY := end.Move(-1, v.Buf).Y
688                 endX := end.Move(-1, v.Buf).X
689                 for y := startY; y <= endY; y++ {
690                         for x := 0; x < len(v.Buf.IndentString()); x++ {
691                                 if len(GetLeadingWhitespace(v.Buf.Line(y))) == 0 {
692                                         break
693                                 }
694                                 v.Buf.Remove(Loc{0, y}, Loc{1, y})
695                                 if y == startY && start.X > 0 {
696                                         v.Cursor.SetSelectionStart(start.Move(-1, v.Buf))
697                                 }
698                                 if y == endY {
699                                         v.Cursor.SetSelectionEnd(Loc{endX - x, endY})
700                                 }
701                         }
702                 }
703                 v.Cursor.Relocate()
704
705                 if usePlugin {
706                         return PostActionCall("OutdentSelection", v)
707                 }
708                 return true
709         }
710         return false
711 }
712
713 // InsertTab inserts a tab or spaces
714 func (v *View) InsertTab(usePlugin bool) bool {
715         if usePlugin && !PreActionCall("InsertTab", v) {
716                 return false
717         }
718
719         if v.Cursor.HasSelection() {
720                 return false
721         }
722
723         tabBytes := len(v.Buf.IndentString())
724         bytesUntilIndent := tabBytes - (v.Cursor.GetVisualX() % tabBytes)
725         v.Buf.Insert(v.Cursor.Loc, v.Buf.IndentString()[:bytesUntilIndent])
726         for i := 0; i < bytesUntilIndent; i++ {
727                 v.Cursor.Right()
728         }
729
730         if usePlugin {
731                 return PostActionCall("InsertTab", v)
732         }
733         return true
734 }
735
736 // SaveAll saves all open buffers
737 func (v *View) SaveAll(usePlugin bool) bool {
738         if usePlugin && !PreActionCall("SaveAll", v) {
739                 return false
740         }
741
742         for _, t := range tabs {
743                 for _, v := range t.views {
744                         v.Save(false)
745                 }
746         }
747
748         if usePlugin {
749                 return PostActionCall("SaveAll", v)
750         }
751         return false
752 }
753
754 // Save the buffer to disk
755 func (v *View) Save(usePlugin bool) bool {
756         if usePlugin && !PreActionCall("Save", v) {
757                 return false
758         }
759
760         if v.Type == vtHelp {
761                 // We can't save the help text
762                 return false
763         }
764         // If this is an empty buffer, ask for a filename
765         if v.Buf.Path == "" {
766                 v.SaveAs(false)
767         } else {
768                 v.saveToFile(v.Buf.Path)
769         }
770
771         if usePlugin {
772                 return PostActionCall("Save", v)
773         }
774         return false
775 }
776
777 // This function saves the buffer to `filename` and changes the buffer's path and name
778 // to `filename` if the save is successful
779 func (v *View) saveToFile(filename string) {
780         err := v.Buf.SaveAs(filename)
781         if err != nil {
782                 if strings.HasSuffix(err.Error(), "permission denied") {
783                         choice, _ := messenger.YesNoPrompt("Permission denied. Do you want to save this file using sudo? (y,n)")
784                         if choice {
785                                 err = v.Buf.SaveAsWithSudo(filename)
786                                 if err != nil {
787                                         messenger.Error(err.Error())
788                                 } else {
789                                         v.Buf.Path = filename
790                                         v.Buf.name = filename
791                                         messenger.Message("Saved " + filename)
792                                 }
793                         }
794                         messenger.Reset()
795                         messenger.Clear()
796                 } else {
797                         messenger.Error(err.Error())
798                 }
799         } else {
800                 v.Buf.Path = filename
801                 v.Buf.name = filename
802                 messenger.Message("Saved " + filename)
803         }
804 }
805
806 // SaveAs saves the buffer to disk with the given name
807 func (v *View) SaveAs(usePlugin bool) bool {
808         filename, canceled := messenger.Prompt("Filename: ", "", "Save", NoCompletion)
809         if !canceled {
810                 // the filename might or might not be quoted, so unquote first then join the strings.
811                 filename = strings.Join(SplitCommandArgs(filename), " ")
812                 v.saveToFile(filename)
813         }
814
815         return false
816 }
817
818 // Find opens a prompt and searches forward for the input
819 func (v *View) Find(usePlugin bool) bool {
820         if usePlugin && !PreActionCall("Find", v) {
821                 return false
822         }
823
824         searchStr := ""
825         if v.Cursor.HasSelection() {
826                 searchStart = ToCharPos(v.Cursor.CurSelection[1], v.Buf)
827                 searchStart = ToCharPos(v.Cursor.CurSelection[1], v.Buf)
828                 searchStr = v.Cursor.GetSelection()
829         } else {
830                 searchStart = ToCharPos(v.Cursor.Loc, v.Buf)
831         }
832         BeginSearch(searchStr)
833
834         if usePlugin {
835                 return PostActionCall("Find", v)
836         }
837         return true
838 }
839
840 // FindNext searches forwards for the last used search term
841 func (v *View) FindNext(usePlugin bool) bool {
842         if usePlugin && !PreActionCall("FindNext", v) {
843                 return false
844         }
845
846         if v.Cursor.HasSelection() {
847                 searchStart = ToCharPos(v.Cursor.CurSelection[1], v.Buf)
848                 // lastSearch = v.Cursor.GetSelection()
849         } else {
850                 searchStart = ToCharPos(v.Cursor.Loc, v.Buf)
851         }
852         if lastSearch == "" {
853                 return true
854         }
855         messenger.Message("Finding: " + lastSearch)
856         Search(lastSearch, v, true)
857
858         if usePlugin {
859                 return PostActionCall("FindNext", v)
860         }
861         return true
862 }
863
864 // FindPrevious searches backwards for the last used search term
865 func (v *View) FindPrevious(usePlugin bool) bool {
866         if usePlugin && !PreActionCall("FindPrevious", v) {
867                 return false
868         }
869
870         if v.Cursor.HasSelection() {
871                 searchStart = ToCharPos(v.Cursor.CurSelection[0], v.Buf)
872         } else {
873                 searchStart = ToCharPos(v.Cursor.Loc, v.Buf)
874         }
875         messenger.Message("Finding: " + lastSearch)
876         Search(lastSearch, v, false)
877
878         if usePlugin {
879                 return PostActionCall("FindPrevious", v)
880         }
881         return true
882 }
883
884 // Undo undoes the last action
885 func (v *View) Undo(usePlugin bool) bool {
886         if usePlugin && !PreActionCall("Undo", v) {
887                 return false
888         }
889
890         v.Buf.Undo()
891         messenger.Message("Undid action")
892
893         if usePlugin {
894                 return PostActionCall("Undo", v)
895         }
896         return true
897 }
898
899 // Redo redoes the last action
900 func (v *View) Redo(usePlugin bool) bool {
901         if usePlugin && !PreActionCall("Redo", v) {
902                 return false
903         }
904
905         v.Buf.Redo()
906         messenger.Message("Redid action")
907
908         if usePlugin {
909                 return PostActionCall("Redo", v)
910         }
911         return true
912 }
913
914 // Copy the selection to the system clipboard
915 func (v *View) Copy(usePlugin bool) bool {
916         if usePlugin && !PreActionCall("Copy", v) {
917                 return false
918         }
919
920         if v.Cursor.HasSelection() {
921                 v.Cursor.CopySelection("clipboard")
922                 v.freshClip = true
923                 messenger.Message("Copied selection")
924         }
925
926         if usePlugin {
927                 return PostActionCall("Copy", v)
928         }
929         return true
930 }
931
932 // CutLine cuts the current line to the clipboard
933 func (v *View) CutLine(usePlugin bool) bool {
934         if usePlugin && !PreActionCall("CutLine", v) {
935                 return false
936         }
937
938         v.Cursor.SelectLine()
939         if !v.Cursor.HasSelection() {
940                 return false
941         }
942         if v.freshClip == true {
943                 if v.Cursor.HasSelection() {
944                         if clip, err := clipboard.ReadAll("clipboard"); err != nil {
945                                 messenger.Error(err)
946                         } else {
947                                 clipboard.WriteAll(clip+v.Cursor.GetSelection(), "clipboard")
948                         }
949                 }
950         } else if time.Since(v.lastCutTime)/time.Second > 10*time.Second || v.freshClip == false {
951                 v.Copy(true)
952         }
953         v.freshClip = true
954         v.lastCutTime = time.Now()
955         v.Cursor.DeleteSelection()
956         v.Cursor.ResetSelection()
957         messenger.Message("Cut line")
958
959         if usePlugin {
960                 return PostActionCall("CutLine", v)
961         }
962         return true
963 }
964
965 // Cut the selection to the system clipboard
966 func (v *View) Cut(usePlugin bool) bool {
967         if usePlugin && !PreActionCall("Cut", v) {
968                 return false
969         }
970
971         if v.Cursor.HasSelection() {
972                 v.Cursor.CopySelection("clipboard")
973                 v.Cursor.DeleteSelection()
974                 v.Cursor.ResetSelection()
975                 v.freshClip = true
976                 messenger.Message("Cut selection")
977
978                 if usePlugin {
979                         return PostActionCall("Cut", v)
980                 }
981                 return true
982         }
983
984         return false
985 }
986
987 // DuplicateLine duplicates the current line or selection
988 func (v *View) DuplicateLine(usePlugin bool) bool {
989         if usePlugin && !PreActionCall("DuplicateLine", v) {
990                 return false
991         }
992
993         if v.Cursor.HasSelection() {
994                 v.Buf.Insert(v.Cursor.CurSelection[1], v.Cursor.GetSelection())
995         } else {
996                 v.Cursor.End()
997                 v.Buf.Insert(v.Cursor.Loc, "\n"+v.Buf.Line(v.Cursor.Y))
998                 v.Cursor.Right()
999         }
1000
1001         messenger.Message("Duplicated line")
1002
1003         if usePlugin {
1004                 return PostActionCall("DuplicateLine", v)
1005         }
1006         return true
1007 }
1008
1009 // DeleteLine deletes the current line
1010 func (v *View) DeleteLine(usePlugin bool) bool {
1011         if usePlugin && !PreActionCall("DeleteLine", v) {
1012                 return false
1013         }
1014
1015         v.Cursor.SelectLine()
1016         if !v.Cursor.HasSelection() {
1017                 return false
1018         }
1019         v.Cursor.DeleteSelection()
1020         v.Cursor.ResetSelection()
1021         messenger.Message("Deleted line")
1022
1023         if usePlugin {
1024                 return PostActionCall("DeleteLine", v)
1025         }
1026         return true
1027 }
1028
1029 // MoveLinesUp moves up the current line or selected lines if any
1030 func (v *View) MoveLinesUp(usePlugin bool) bool {
1031         if usePlugin && !PreActionCall("MoveLinesUp", v) {
1032                 return false
1033         }
1034
1035         if v.Cursor.HasSelection() {
1036                 if v.Cursor.CurSelection[0].Y == 0 {
1037                         messenger.Message("Can not move further up")
1038                         return true
1039                 }
1040                 v.Buf.MoveLinesUp(
1041                         v.Cursor.CurSelection[0].Y,
1042                         v.Cursor.CurSelection[1].Y,
1043                 )
1044                 v.Cursor.UpN(1)
1045                 v.Cursor.CurSelection[0].Y -= 1
1046                 v.Cursor.CurSelection[1].Y -= 1
1047                 messenger.Message("Moved up selected line(s)")
1048         } else {
1049                 if v.Cursor.Loc.Y == 0 {
1050                         messenger.Message("Can not move further up")
1051                         return true
1052                 }
1053                 v.Buf.MoveLinesUp(
1054                         v.Cursor.Loc.Y,
1055                         v.Cursor.Loc.Y+1,
1056                 )
1057                 v.Cursor.UpN(1)
1058                 messenger.Message("Moved up current line")
1059         }
1060         v.Buf.IsModified = true
1061
1062         if usePlugin {
1063                 return PostActionCall("MoveLinesUp", v)
1064         }
1065         return true
1066 }
1067
1068 // MoveLinesDown moves down the current line or selected lines if any
1069 func (v *View) MoveLinesDown(usePlugin bool) bool {
1070         if usePlugin && !PreActionCall("MoveLinesDown", v) {
1071                 return false
1072         }
1073
1074         if v.Cursor.HasSelection() {
1075                 if v.Cursor.CurSelection[1].Y >= len(v.Buf.lines) {
1076                         messenger.Message("Can not move further down")
1077                         return true
1078                 }
1079                 v.Buf.MoveLinesDown(
1080                         v.Cursor.CurSelection[0].Y,
1081                         v.Cursor.CurSelection[1].Y,
1082                 )
1083                 v.Cursor.DownN(1)
1084                 v.Cursor.CurSelection[0].Y += 1
1085                 v.Cursor.CurSelection[1].Y += 1
1086                 messenger.Message("Moved down selected line(s)")
1087         } else {
1088                 if v.Cursor.Loc.Y >= len(v.Buf.lines)-1 {
1089                         messenger.Message("Can not move further down")
1090                         return true
1091                 }
1092                 v.Buf.MoveLinesDown(
1093                         v.Cursor.Loc.Y,
1094                         v.Cursor.Loc.Y+1,
1095                 )
1096                 v.Cursor.DownN(1)
1097                 messenger.Message("Moved down current line")
1098         }
1099         v.Buf.IsModified = true
1100
1101         if usePlugin {
1102                 return PostActionCall("MoveLinesDown", v)
1103         }
1104         return true
1105 }
1106
1107 // Paste whatever is in the system clipboard into the buffer
1108 // Delete and paste if the user has a selection
1109 func (v *View) Paste(usePlugin bool) bool {
1110         if usePlugin && !PreActionCall("Paste", v) {
1111                 return false
1112         }
1113
1114         clip, _ := clipboard.ReadAll("clipboard")
1115         v.paste(clip)
1116
1117         if usePlugin {
1118                 return PostActionCall("Paste", v)
1119         }
1120         return true
1121 }
1122
1123 // PastePrimary pastes from the primary clipboard (only use on linux)
1124 func (v *View) PastePrimary(usePlugin bool) bool {
1125         if usePlugin && !PreActionCall("Paste", v) {
1126                 return false
1127         }
1128
1129         clip, _ := clipboard.ReadAll("primary")
1130         v.paste(clip)
1131
1132         if usePlugin {
1133                 return PostActionCall("Paste", v)
1134         }
1135         return true
1136 }
1137
1138 // SelectAll selects the entire buffer
1139 func (v *View) SelectAll(usePlugin bool) bool {
1140         if usePlugin && !PreActionCall("SelectAll", v) {
1141                 return false
1142         }
1143
1144         v.Cursor.SetSelectionStart(v.Buf.Start())
1145         v.Cursor.SetSelectionEnd(v.Buf.End())
1146         // Put the cursor at the beginning
1147         v.Cursor.X = 0
1148         v.Cursor.Y = 0
1149
1150         if usePlugin {
1151                 return PostActionCall("SelectAll", v)
1152         }
1153         return true
1154 }
1155
1156 // OpenFile opens a new file in the buffer
1157 func (v *View) OpenFile(usePlugin bool) bool {
1158         if usePlugin && !PreActionCall("OpenFile", v) {
1159                 return false
1160         }
1161
1162         if v.CanClose() {
1163                 input, canceled := messenger.Prompt("> ", "open ", "Open", CommandCompletion)
1164                 if !canceled {
1165                         HandleCommand(input)
1166                         if usePlugin {
1167                                 return PostActionCall("OpenFile", v)
1168                         }
1169                 }
1170         }
1171         return false
1172 }
1173
1174 // Start moves the viewport to the start of the buffer
1175 func (v *View) Start(usePlugin bool) bool {
1176         if usePlugin && !PreActionCall("Start", v) {
1177                 return false
1178         }
1179
1180         v.Topline = 0
1181
1182         if usePlugin {
1183                 return PostActionCall("Start", v)
1184         }
1185         return false
1186 }
1187
1188 // End moves the viewport to the end of the buffer
1189 func (v *View) End(usePlugin bool) bool {
1190         if usePlugin && !PreActionCall("End", v) {
1191                 return false
1192         }
1193
1194         if v.Height > v.Buf.NumLines {
1195                 v.Topline = 0
1196         } else {
1197                 v.Topline = v.Buf.NumLines - v.Height
1198         }
1199
1200         if usePlugin {
1201                 return PostActionCall("End", v)
1202         }
1203         return false
1204 }
1205
1206 // PageUp scrolls the view up a page
1207 func (v *View) PageUp(usePlugin bool) bool {
1208         if usePlugin && !PreActionCall("PageUp", v) {
1209                 return false
1210         }
1211
1212         if v.Topline > v.Height {
1213                 v.ScrollUp(v.Height)
1214         } else {
1215                 v.Topline = 0
1216         }
1217
1218         if usePlugin {
1219                 return PostActionCall("PageUp", v)
1220         }
1221         return false
1222 }
1223
1224 // PageDown scrolls the view down a page
1225 func (v *View) PageDown(usePlugin bool) bool {
1226         if usePlugin && !PreActionCall("PageDown", v) {
1227                 return false
1228         }
1229
1230         if v.Buf.NumLines-(v.Topline+v.Height) > v.Height {
1231                 v.ScrollDown(v.Height)
1232         } else if v.Buf.NumLines >= v.Height {
1233                 v.Topline = v.Buf.NumLines - v.Height
1234         }
1235
1236         if usePlugin {
1237                 return PostActionCall("PageDown", v)
1238         }
1239         return false
1240 }
1241
1242 // CursorPageUp places the cursor a page up
1243 func (v *View) CursorPageUp(usePlugin bool) bool {
1244         if usePlugin && !PreActionCall("CursorPageUp", v) {
1245                 return false
1246         }
1247
1248         v.deselect(0)
1249
1250         if v.Cursor.HasSelection() {
1251                 v.Cursor.Loc = v.Cursor.CurSelection[0]
1252                 v.Cursor.ResetSelection()
1253         }
1254         v.Cursor.UpN(v.Height)
1255
1256         if usePlugin {
1257                 return PostActionCall("CursorPageUp", v)
1258         }
1259         return true
1260 }
1261
1262 // CursorPageDown places the cursor a page up
1263 func (v *View) CursorPageDown(usePlugin bool) bool {
1264         if usePlugin && !PreActionCall("CursorPageDown", v) {
1265                 return false
1266         }
1267
1268         v.deselect(0)
1269
1270         if v.Cursor.HasSelection() {
1271                 v.Cursor.Loc = v.Cursor.CurSelection[1]
1272                 v.Cursor.ResetSelection()
1273         }
1274         v.Cursor.DownN(v.Height)
1275
1276         if usePlugin {
1277                 return PostActionCall("CursorPageDown", v)
1278         }
1279         return true
1280 }
1281
1282 // HalfPageUp scrolls the view up half a page
1283 func (v *View) HalfPageUp(usePlugin bool) bool {
1284         if usePlugin && !PreActionCall("HalfPageUp", v) {
1285                 return false
1286         }
1287
1288         if v.Topline > v.Height/2 {
1289                 v.ScrollUp(v.Height / 2)
1290         } else {
1291                 v.Topline = 0
1292         }
1293
1294         if usePlugin {
1295                 return PostActionCall("HalfPageUp", v)
1296         }
1297         return false
1298 }
1299
1300 // HalfPageDown scrolls the view down half a page
1301 func (v *View) HalfPageDown(usePlugin bool) bool {
1302         if usePlugin && !PreActionCall("HalfPageDown", v) {
1303                 return false
1304         }
1305
1306         if v.Buf.NumLines-(v.Topline+v.Height) > v.Height/2 {
1307                 v.ScrollDown(v.Height / 2)
1308         } else {
1309                 if v.Buf.NumLines >= v.Height {
1310                         v.Topline = v.Buf.NumLines - v.Height
1311                 }
1312         }
1313
1314         if usePlugin {
1315                 return PostActionCall("HalfPageDown", v)
1316         }
1317         return false
1318 }
1319
1320 // ToggleRuler turns line numbers off and on
1321 func (v *View) ToggleRuler(usePlugin bool) bool {
1322         if usePlugin && !PreActionCall("ToggleRuler", v) {
1323                 return false
1324         }
1325
1326         if v.Buf.Settings["ruler"] == false {
1327                 v.Buf.Settings["ruler"] = true
1328                 messenger.Message("Enabled ruler")
1329         } else {
1330                 v.Buf.Settings["ruler"] = false
1331                 messenger.Message("Disabled ruler")
1332         }
1333
1334         if usePlugin {
1335                 return PostActionCall("ToggleRuler", v)
1336         }
1337         return false
1338 }
1339
1340 // JumpLine jumps to a line and moves the view accordingly.
1341 func (v *View) JumpLine(usePlugin bool) bool {
1342         if usePlugin && !PreActionCall("JumpLine", v) {
1343                 return false
1344         }
1345
1346         // Prompt for line number
1347         linestring, canceled := messenger.Prompt("Jump to line # ", "", "LineNumber", NoCompletion)
1348         if canceled {
1349                 return false
1350         }
1351         lineint, err := strconv.Atoi(linestring)
1352         lineint = lineint - 1 // fix offset
1353         if err != nil {
1354                 messenger.Error(err) // return errors
1355                 return false
1356         }
1357         // Move cursor and view if possible.
1358         if lineint < v.Buf.NumLines && lineint >= 0 {
1359                 v.Cursor.X = 0
1360                 v.Cursor.Y = lineint
1361
1362                 if usePlugin {
1363                         return PostActionCall("JumpLine", v)
1364                 }
1365                 return true
1366         }
1367         messenger.Error("Only ", v.Buf.NumLines, " lines to jump")
1368         return false
1369 }
1370
1371 // ClearStatus clears the messenger bar
1372 func (v *View) ClearStatus(usePlugin bool) bool {
1373         if usePlugin && !PreActionCall("ClearStatus", v) {
1374                 return false
1375         }
1376
1377         messenger.Message("")
1378
1379         if usePlugin {
1380                 return PostActionCall("ClearStatus", v)
1381         }
1382         return false
1383 }
1384
1385 // ToggleHelp toggles the help screen
1386 func (v *View) ToggleHelp(usePlugin bool) bool {
1387         if usePlugin && !PreActionCall("ToggleHelp", v) {
1388                 return false
1389         }
1390
1391         if v.Type != vtHelp {
1392                 // Open the default help
1393                 v.openHelp("help")
1394         } else {
1395                 v.Quit(true)
1396         }
1397
1398         if usePlugin {
1399                 return PostActionCall("ToggleHelp", v)
1400         }
1401         return true
1402 }
1403
1404 // ShellMode opens a terminal to run a shell command
1405 func (v *View) ShellMode(usePlugin bool) bool {
1406         if usePlugin && !PreActionCall("ShellMode", v) {
1407                 return false
1408         }
1409
1410         input, canceled := messenger.Prompt("$ ", "", "Shell", NoCompletion)
1411         if !canceled {
1412                 // The true here is for openTerm to make the command interactive
1413                 HandleShellCommand(input, true, true)
1414                 if usePlugin {
1415                         return PostActionCall("ShellMode", v)
1416                 }
1417         }
1418         return false
1419 }
1420
1421 // CommandMode lets the user enter a command
1422 func (v *View) CommandMode(usePlugin bool) bool {
1423         if usePlugin && !PreActionCall("CommandMode", v) {
1424                 return false
1425         }
1426
1427         input, canceled := messenger.Prompt("> ", "", "Command", CommandCompletion)
1428         if !canceled {
1429                 HandleCommand(input)
1430                 if usePlugin {
1431                         return PostActionCall("CommandMode", v)
1432                 }
1433         }
1434
1435         return false
1436 }
1437
1438 // Escape leaves current mode
1439 func (v *View) Escape(usePlugin bool) bool {
1440         // check if user is searching, or the last search is still active
1441         if searching || lastSearch != "" {
1442                 ExitSearch(v)
1443                 return true
1444         }
1445         // check if a prompt is shown, hide it and don't quit
1446         if messenger.hasPrompt {
1447                 messenger.Reset() // FIXME
1448                 return true
1449         }
1450
1451         return false
1452 }
1453
1454 // Quit this will close the current tab or view that is open
1455 func (v *View) Quit(usePlugin bool) bool {
1456         if usePlugin && !PreActionCall("Quit", v) {
1457                 return false
1458         }
1459
1460         // Make sure not to quit if there are unsaved changes
1461         if v.CanClose() {
1462                 v.CloseBuffer()
1463                 if len(tabs[curTab].views) > 1 {
1464                         v.splitNode.Delete()
1465                         tabs[v.TabNum].Cleanup()
1466                         tabs[v.TabNum].Resize()
1467                 } else if len(tabs) > 1 {
1468                         if len(tabs[v.TabNum].views) == 1 {
1469                                 tabs = tabs[:v.TabNum+copy(tabs[v.TabNum:], tabs[v.TabNum+1:])]
1470                                 for i, t := range tabs {
1471                                         t.SetNum(i)
1472                                 }
1473                                 if curTab >= len(tabs) {
1474                                         curTab--
1475                                 }
1476                                 if curTab == 0 {
1477                                         CurView().ToggleTabbar()
1478                                 }
1479                         }
1480                 } else {
1481                         if usePlugin {
1482                                 PostActionCall("Quit", v)
1483                         }
1484
1485                         screen.Fini()
1486                         os.Exit(0)
1487                 }
1488         }
1489
1490         if usePlugin {
1491                 return PostActionCall("Quit", v)
1492         }
1493         return false
1494 }
1495
1496 // QuitAll quits the whole editor; all splits and tabs
1497 func (v *View) QuitAll(usePlugin bool) bool {
1498         if usePlugin && !PreActionCall("QuitAll", v) {
1499                 return false
1500         }
1501
1502         closeAll := true
1503         for _, tab := range tabs {
1504                 for _, v := range tab.views {
1505                         if !v.CanClose() {
1506                                 closeAll = false
1507                         }
1508                 }
1509         }
1510
1511         if closeAll {
1512                 // only quit if all of the buffers can be closed and the user confirms that they actually want to quit everything
1513                 shouldQuit, _ := messenger.YesNoPrompt("Do you want to quit micro (all open files will be closed)?")
1514
1515                 if shouldQuit {
1516                         for _, tab := range tabs {
1517                                 for _, v := range tab.views {
1518                                         v.CloseBuffer()
1519                                 }
1520                         }
1521
1522                         if usePlugin {
1523                                 PostActionCall("QuitAll", v)
1524                         }
1525
1526                         screen.Fini()
1527                         os.Exit(0)
1528                 }
1529         }
1530
1531         return false
1532 }
1533
1534 // AddTab adds a new tab with an empty buffer
1535 func (v *View) AddTab(usePlugin bool) bool {
1536         if usePlugin && !PreActionCall("AddTab", v) {
1537                 return false
1538         }
1539
1540         tab := NewTabFromView(NewView(NewBufferFromString("", "")))
1541         tab.SetNum(len(tabs))
1542         tabs = append(tabs, tab)
1543         curTab = len(tabs) - 1
1544         if len(tabs) == 2 {
1545                 for _, t := range tabs {
1546                         for _, v := range t.views {
1547                                 v.ToggleTabbar()
1548                         }
1549                 }
1550         }
1551
1552         if usePlugin {
1553                 return PostActionCall("AddTab", v)
1554         }
1555         return true
1556 }
1557
1558 // PreviousTab switches to the previous tab in the tab list
1559 func (v *View) PreviousTab(usePlugin bool) bool {
1560         if usePlugin && !PreActionCall("PreviousTab", v) {
1561                 return false
1562         }
1563
1564         if curTab > 0 {
1565                 curTab--
1566         } else if curTab == 0 {
1567                 curTab = len(tabs) - 1
1568         }
1569
1570         if usePlugin {
1571                 return PostActionCall("PreviousTab", v)
1572         }
1573         return false
1574 }
1575
1576 // NextTab switches to the next tab in the tab list
1577 func (v *View) NextTab(usePlugin bool) bool {
1578         if usePlugin && !PreActionCall("NextTab", v) {
1579                 return false
1580         }
1581
1582         if curTab < len(tabs)-1 {
1583                 curTab++
1584         } else if curTab == len(tabs)-1 {
1585                 curTab = 0
1586         }
1587
1588         if usePlugin {
1589                 return PostActionCall("NextTab", v)
1590         }
1591         return false
1592 }
1593
1594 // VSplitBinding opens an empty vertical split
1595 func (v *View) VSplitBinding(usePlugin bool) bool {
1596         if usePlugin && !PreActionCall("VSplit", v) {
1597                 return false
1598         }
1599
1600         v.VSplit(NewBufferFromString("", ""))
1601
1602         if usePlugin {
1603                 return PostActionCall("VSplit", v)
1604         }
1605         return false
1606 }
1607
1608 // HSplitBinding opens an empty horizontal split
1609 func (v *View) HSplitBinding(usePlugin bool) bool {
1610         if usePlugin && !PreActionCall("HSplit", v) {
1611                 return false
1612         }
1613
1614         v.HSplit(NewBufferFromString("", ""))
1615
1616         if usePlugin {
1617                 return PostActionCall("HSplit", v)
1618         }
1619         return false
1620 }
1621
1622 // Unsplit closes all splits in the current tab except the active one
1623 func (v *View) Unsplit(usePlugin bool) bool {
1624         if usePlugin && !PreActionCall("Unsplit", v) {
1625                 return false
1626         }
1627
1628         curView := tabs[curTab].CurView
1629         for i := len(tabs[curTab].views) - 1; i >= 0; i-- {
1630                 view := tabs[curTab].views[i]
1631                 if view != nil && view.Num != curView {
1632                         view.Quit(true)
1633                         // messenger.Message("Quit ", view.Buf.Path)
1634                 }
1635         }
1636
1637         if usePlugin {
1638                 return PostActionCall("Unsplit", v)
1639         }
1640         return false
1641 }
1642
1643 // NextSplit changes the view to the next split
1644 func (v *View) NextSplit(usePlugin bool) bool {
1645         if usePlugin && !PreActionCall("NextSplit", v) {
1646                 return false
1647         }
1648
1649         tab := tabs[curTab]
1650         if tab.CurView < len(tab.views)-1 {
1651                 tab.CurView++
1652         } else {
1653                 tab.CurView = 0
1654         }
1655
1656         if usePlugin {
1657                 return PostActionCall("NextSplit", v)
1658         }
1659         return false
1660 }
1661
1662 // PreviousSplit changes the view to the previous split
1663 func (v *View) PreviousSplit(usePlugin bool) bool {
1664         if usePlugin && !PreActionCall("PreviousSplit", v) {
1665                 return false
1666         }
1667
1668         tab := tabs[curTab]
1669         if tab.CurView > 0 {
1670                 tab.CurView--
1671         } else {
1672                 tab.CurView = len(tab.views) - 1
1673         }
1674
1675         if usePlugin {
1676                 return PostActionCall("PreviousSplit", v)
1677         }
1678         return false
1679 }
1680
1681 var curMacro []interface{}
1682 var recordingMacro bool
1683
1684 // ToggleMacro toggles recording of a macro
1685 func (v *View) ToggleMacro(usePlugin bool) bool {
1686         if usePlugin && !PreActionCall("ToggleMacro", v) {
1687                 return false
1688         }
1689
1690         recordingMacro = !recordingMacro
1691
1692         if recordingMacro {
1693                 curMacro = []interface{}{}
1694                 messenger.Message("Recording")
1695         } else {
1696                 messenger.Message("Stopped recording")
1697         }
1698
1699         if usePlugin {
1700                 return PostActionCall("ToggleMacro", v)
1701         }
1702         return true
1703 }
1704
1705 // PlayMacro plays back the most recently recorded macro
1706 func (v *View) PlayMacro(usePlugin bool) bool {
1707         if usePlugin && !PreActionCall("PlayMacro", v) {
1708                 return false
1709         }
1710
1711         for _, action := range curMacro {
1712                 switch t := action.(type) {
1713                 case rune:
1714                         // Insert a character
1715                         if v.Cursor.HasSelection() {
1716                                 v.Cursor.DeleteSelection()
1717                                 v.Cursor.ResetSelection()
1718                         }
1719                         v.Buf.Insert(v.Cursor.Loc, string(t))
1720                         v.Cursor.Right()
1721
1722                         for pl := range loadedPlugins {
1723                                 _, err := Call(pl+".onRune", string(t), v)
1724                                 if err != nil && !strings.HasPrefix(err.Error(), "function does not exist") {
1725                                         TermMessage(err)
1726                                 }
1727                         }
1728                 case func(*View, bool) bool:
1729                         t(v, true)
1730                 }
1731         }
1732
1733         if usePlugin {
1734                 return PostActionCall("PlayMacro", v)
1735         }
1736         return true
1737 }
1738
1739 // None is no action
1740 func None() bool {
1741         return false
1742 }