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