]> git.lizzy.rs Git - micro.git/blob - cmd/micro/actions.go
Plugin onAction return value should determine relocate
[micro.git] / cmd / micro / actions.go
1 package main
2
3 import (
4         "io/ioutil"
5         "os"
6         "strconv"
7         "strings"
8         "time"
9
10         "github.com/mitchellh/go-homedir"
11         "github.com/yuin/gopher-lua"
12         "github.com/zyedidia/clipboard"
13 )
14
15 // PreActionCall executes the lua pre callback if possible
16 func PreActionCall(funcName string) bool {
17         executeAction := true
18         for _, pl := range loadedPlugins {
19                 ret, err := Call(pl+".pre"+funcName, nil)
20                 if err != nil && !strings.HasPrefix(err.Error(), "function does not exist") {
21                         TermMessage(err)
22                         continue
23                 }
24                 if ret == lua.LFalse {
25                         executeAction = false
26                 }
27         }
28         return executeAction
29 }
30
31 // PostActionCall executes the lua plugin callback if possible
32 func PostActionCall(funcName string) bool {
33         relocate := true
34         for _, pl := range loadedPlugins {
35                 ret, err := Call(pl+".on"+funcName, nil)
36                 if err != nil && !strings.HasPrefix(err.Error(), "function does not exist") {
37                         TermMessage(err)
38                         continue
39                 }
40                 if ret == lua.LFalse {
41                         relocate = false
42                 }
43         }
44         return relocate
45 }
46
47 // CursorUp moves the cursor up
48 func (v *View) CursorUp(usePlugin bool) bool {
49         if usePlugin && !PreActionCall("CursorUp") {
50                 return false
51         }
52
53         if v.Cursor.HasSelection() {
54                 v.Cursor.Loc = v.Cursor.CurSelection[0]
55                 v.Cursor.ResetSelection()
56         }
57         v.Cursor.Up()
58
59         if usePlugin {
60                 return PostActionCall("CursorUp")
61         }
62         return true
63 }
64
65 // CursorDown moves the cursor down
66 func (v *View) CursorDown(usePlugin bool) bool {
67         if usePlugin && !PreActionCall("CursorDown") {
68                 return false
69         }
70
71         if v.Cursor.HasSelection() {
72                 v.Cursor.Loc = v.Cursor.CurSelection[1]
73                 v.Cursor.ResetSelection()
74         }
75         v.Cursor.Down()
76
77         if usePlugin {
78                 return PostActionCall("CursorDown")
79         }
80         return true
81 }
82
83 // CursorLeft moves the cursor left
84 func (v *View) CursorLeft(usePlugin bool) bool {
85         if usePlugin && !PreActionCall("CursorLeft") {
86                 return false
87         }
88
89         if v.Cursor.HasSelection() {
90                 v.Cursor.Loc = v.Cursor.CurSelection[0]
91                 v.Cursor.ResetSelection()
92         } else {
93                 v.Cursor.Left()
94         }
95
96         if usePlugin {
97                 return PostActionCall("CursorLeft")
98         }
99         return true
100 }
101
102 // CursorRight moves the cursor right
103 func (v *View) CursorRight(usePlugin bool) bool {
104         if usePlugin && !PreActionCall("CursorRight") {
105                 return false
106         }
107
108         if v.Cursor.HasSelection() {
109                 v.Cursor.Loc = v.Cursor.CurSelection[1].Move(-1, v.Buf)
110                 v.Cursor.ResetSelection()
111         } else {
112                 v.Cursor.Right()
113         }
114
115         if usePlugin {
116                 return PostActionCall("CursorRight")
117         }
118         return true
119 }
120
121 // WordRight moves the cursor one word to the right
122 func (v *View) WordRight(usePlugin bool) bool {
123         if usePlugin && !PreActionCall("WordRight") {
124                 return false
125         }
126
127         v.Cursor.WordRight()
128
129         if usePlugin {
130                 return PostActionCall("WordRight")
131         }
132         return true
133 }
134
135 // WordLeft moves the cursor one word to the left
136 func (v *View) WordLeft(usePlugin bool) bool {
137         if usePlugin && !PreActionCall("WordLeft") {
138                 return false
139         }
140
141         v.Cursor.WordLeft()
142
143         if usePlugin {
144                 return PostActionCall("WordLeft")
145         }
146         return true
147 }
148
149 // SelectUp selects up one line
150 func (v *View) SelectUp(usePlugin bool) bool {
151         if usePlugin && !PreActionCall("SelectUp") {
152                 return false
153         }
154
155         if !v.Cursor.HasSelection() {
156                 v.Cursor.OrigSelection[0] = v.Cursor.Loc
157         }
158         v.Cursor.Up()
159         v.Cursor.SelectTo(v.Cursor.Loc)
160
161         if usePlugin {
162                 return PostActionCall("SelectUp")
163         }
164         return true
165 }
166
167 // SelectDown selects down one line
168 func (v *View) SelectDown(usePlugin bool) bool {
169         if usePlugin && !PreActionCall("SelectDown") {
170                 return false
171         }
172
173         if !v.Cursor.HasSelection() {
174                 v.Cursor.OrigSelection[0] = v.Cursor.Loc
175         }
176         v.Cursor.Down()
177         v.Cursor.SelectTo(v.Cursor.Loc)
178
179         if usePlugin {
180                 return PostActionCall("SelectDown")
181         }
182         return true
183 }
184
185 // SelectLeft selects the character to the left of the cursor
186 func (v *View) SelectLeft(usePlugin bool) bool {
187         if usePlugin && !PreActionCall("SelectLeft") {
188                 return false
189         }
190
191         loc := v.Cursor.Loc
192         count := v.Buf.End().Move(-1, v.Buf)
193         if loc.GreaterThan(count) {
194                 loc = count
195         }
196         if !v.Cursor.HasSelection() {
197                 v.Cursor.OrigSelection[0] = loc
198         }
199         v.Cursor.Left()
200         v.Cursor.SelectTo(v.Cursor.Loc)
201
202         if usePlugin {
203                 return PostActionCall("SelectLeft")
204         }
205         return true
206 }
207
208 // SelectRight selects the character to the right of the cursor
209 func (v *View) SelectRight(usePlugin bool) bool {
210         if usePlugin && !PreActionCall("SelectRight") {
211                 return false
212         }
213
214         loc := v.Cursor.Loc
215         count := v.Buf.End().Move(-1, v.Buf)
216         if loc.GreaterThan(count) {
217                 loc = count
218         }
219         if !v.Cursor.HasSelection() {
220                 v.Cursor.OrigSelection[0] = loc
221         }
222         v.Cursor.Right()
223         v.Cursor.SelectTo(v.Cursor.Loc)
224
225         if usePlugin {
226                 return PostActionCall("SelectRight")
227         }
228         return true
229 }
230
231 // SelectWordRight selects the word to the right of the cursor
232 func (v *View) SelectWordRight(usePlugin bool) bool {
233         if usePlugin && !PreActionCall("SelectWordRight") {
234                 return false
235         }
236
237         if !v.Cursor.HasSelection() {
238                 v.Cursor.OrigSelection[0] = v.Cursor.Loc
239         }
240         v.Cursor.WordRight()
241         v.Cursor.SelectTo(v.Cursor.Loc)
242
243         if usePlugin {
244                 return PostActionCall("SelectWordRight")
245         }
246         return true
247 }
248
249 // SelectWordLeft selects the word to the left of the cursor
250 func (v *View) SelectWordLeft(usePlugin bool) bool {
251         if usePlugin && !PreActionCall("SelectWordLeft") {
252                 return false
253         }
254
255         if !v.Cursor.HasSelection() {
256                 v.Cursor.OrigSelection[0] = v.Cursor.Loc
257         }
258         v.Cursor.WordLeft()
259         v.Cursor.SelectTo(v.Cursor.Loc)
260
261         if usePlugin {
262                 return PostActionCall("SelectWordLeft")
263         }
264         return true
265 }
266
267 // StartOfLine moves the cursor to the start of the line
268 func (v *View) StartOfLine(usePlugin bool) bool {
269         if usePlugin && !PreActionCall("StartOfLine") {
270                 return false
271         }
272
273         v.Cursor.Start()
274
275         if usePlugin {
276                 return PostActionCall("StartOfLine")
277         }
278         return true
279 }
280
281 // EndOfLine moves the cursor to the end of the line
282 func (v *View) EndOfLine(usePlugin bool) bool {
283         if usePlugin && !PreActionCall("EndOfLine") {
284                 return false
285         }
286
287         v.Cursor.End()
288
289         if usePlugin {
290                 return PostActionCall("EndOfLine")
291         }
292         return true
293 }
294
295 // SelectToStartOfLine selects to the start of the current line
296 func (v *View) SelectToStartOfLine(usePlugin bool) bool {
297         if usePlugin && !PreActionCall("SelectToStartOfLine") {
298                 return false
299         }
300
301         if !v.Cursor.HasSelection() {
302                 v.Cursor.OrigSelection[0] = v.Cursor.Loc
303         }
304         v.Cursor.Start()
305         v.Cursor.SelectTo(v.Cursor.Loc)
306
307         if usePlugin {
308                 return PostActionCall("SelectToStartOfLine")
309         }
310         return true
311 }
312
313 // SelectToEndOfLine selects to the end of the current line
314 func (v *View) SelectToEndOfLine(usePlugin bool) bool {
315         if usePlugin && !PreActionCall("SelectToEndOfLine") {
316                 return false
317         }
318
319         if !v.Cursor.HasSelection() {
320                 v.Cursor.OrigSelection[0] = v.Cursor.Loc
321         }
322         v.Cursor.End()
323         v.Cursor.SelectTo(v.Cursor.Loc)
324
325         if usePlugin {
326                 return PostActionCall("SelectToEndOfLine")
327         }
328         return true
329 }
330
331 // CursorStart moves the cursor to the start of the buffer
332 func (v *View) CursorStart(usePlugin bool) bool {
333         if usePlugin && !PreActionCall("CursorStart") {
334                 return false
335         }
336
337         v.Cursor.X = 0
338         v.Cursor.Y = 0
339
340         if usePlugin {
341                 return PostActionCall("CursorStart")
342         }
343         return true
344 }
345
346 // CursorEnd moves the cursor to the end of the buffer
347 func (v *View) CursorEnd(usePlugin bool) bool {
348         if usePlugin && !PreActionCall("CursorEnd") {
349                 return false
350         }
351
352         v.Cursor.Loc = v.Buf.End()
353
354         if usePlugin {
355                 return PostActionCall("CursorEnd")
356         }
357         return true
358 }
359
360 // SelectToStart selects the text from the cursor to the start of the buffer
361 func (v *View) SelectToStart(usePlugin bool) bool {
362         if usePlugin && !PreActionCall("SelectToStart") {
363                 return false
364         }
365
366         if !v.Cursor.HasSelection() {
367                 v.Cursor.OrigSelection[0] = v.Cursor.Loc
368         }
369         v.CursorStart(false)
370         v.Cursor.SelectTo(v.Buf.Start())
371
372         if usePlugin {
373                 return PostActionCall("SelectToStart")
374         }
375         return true
376 }
377
378 // SelectToEnd selects the text from the cursor to the end of the buffer
379 func (v *View) SelectToEnd(usePlugin bool) bool {
380         if usePlugin && !PreActionCall("SelectToEnd") {
381                 return false
382         }
383
384         if !v.Cursor.HasSelection() {
385                 v.Cursor.OrigSelection[0] = v.Cursor.Loc
386         }
387         v.CursorEnd(false)
388         v.Cursor.SelectTo(v.Buf.End())
389
390         if usePlugin {
391                 return PostActionCall("SelectToEnd")
392         }
393         return true
394 }
395
396 // InsertSpace inserts a space
397 func (v *View) InsertSpace(usePlugin bool) bool {
398         if usePlugin && !PreActionCall("InsertSpace") {
399                 return false
400         }
401
402         if v.Cursor.HasSelection() {
403                 v.Cursor.DeleteSelection()
404                 v.Cursor.ResetSelection()
405         }
406         v.Buf.Insert(v.Cursor.Loc, " ")
407         v.Cursor.Right()
408
409         if usePlugin {
410                 return PostActionCall("InsertSpace")
411         }
412         return true
413 }
414
415 // InsertNewline inserts a newline plus possible some whitespace if autoindent is on
416 func (v *View) InsertNewline(usePlugin bool) bool {
417         if usePlugin && !PreActionCall("InsertNewline") {
418                 return false
419         }
420
421         // Insert a newline
422         if v.Cursor.HasSelection() {
423                 v.Cursor.DeleteSelection()
424                 v.Cursor.ResetSelection()
425         }
426
427         v.Buf.Insert(v.Cursor.Loc, "\n")
428         ws := GetLeadingWhitespace(v.Buf.Line(v.Cursor.Y))
429         v.Cursor.Right()
430
431         if settings["autoindent"].(bool) {
432                 v.Buf.Insert(v.Cursor.Loc, ws)
433                 for i := 0; i < len(ws); i++ {
434                         v.Cursor.Right()
435                 }
436
437                 if IsSpacesOrTabs(v.Buf.Line(v.Cursor.Y - 1)) {
438                         line := v.Buf.Line(v.Cursor.Y - 1)
439                         v.Buf.Remove(Loc{0, v.Cursor.Y - 1}, Loc{Count(line), v.Cursor.Y - 1})
440                 }
441         }
442         v.Cursor.LastVisualX = v.Cursor.GetVisualX()
443
444         if usePlugin {
445                 return PostActionCall("InsertNewline")
446         }
447         return true
448 }
449
450 // Backspace deletes the previous character
451 func (v *View) Backspace(usePlugin bool) bool {
452         if usePlugin && !PreActionCall("Backspace") {
453                 return false
454         }
455
456         // Delete a character
457         if v.Cursor.HasSelection() {
458                 v.Cursor.DeleteSelection()
459                 v.Cursor.ResetSelection()
460         } else if v.Cursor.Loc.GreaterThan(v.Buf.Start()) {
461                 // We have to do something a bit hacky here because we want to
462                 // delete the line by first moving left and then deleting backwards
463                 // but the undo redo would place the cursor in the wrong place
464                 // So instead we move left, save the position, move back, delete
465                 // and restore the position
466
467                 // If the user is using spaces instead of tabs and they are deleting
468                 // whitespace at the start of the line, we should delete as if its a
469                 // tab (tabSize number of spaces)
470                 lineStart := v.Buf.Line(v.Cursor.Y)[:v.Cursor.X]
471                 tabSize := int(settings["tabsize"].(float64))
472                 if settings["tabstospaces"].(bool) && IsSpaces(lineStart) && len(lineStart) != 0 && len(lineStart)%tabSize == 0 {
473                         loc := v.Cursor.Loc
474                         v.Cursor.Loc = loc.Move(-tabSize, v.Buf)
475                         cx, cy := v.Cursor.X, v.Cursor.Y
476                         v.Cursor.Loc = loc
477                         v.Buf.Remove(loc.Move(-tabSize, v.Buf), loc)
478                         v.Cursor.X, v.Cursor.Y = cx, cy
479                 } else {
480                         v.Cursor.Left()
481                         cx, cy := v.Cursor.X, v.Cursor.Y
482                         v.Cursor.Right()
483                         loc := v.Cursor.Loc
484                         v.Buf.Remove(loc.Move(-1, v.Buf), loc)
485                         v.Cursor.X, v.Cursor.Y = cx, cy
486                 }
487         }
488         v.Cursor.LastVisualX = v.Cursor.GetVisualX()
489
490         if usePlugin {
491                 return PostActionCall("Backspace")
492         }
493         return true
494 }
495
496 // DeleteWordRight deletes the word to the right of the cursor
497 func (v *View) DeleteWordRight(usePlugin bool) bool {
498         if usePlugin && !PreActionCall("DeleteWordRight") {
499                 return false
500         }
501
502         v.SelectWordRight(false)
503         if v.Cursor.HasSelection() {
504                 v.Cursor.DeleteSelection()
505                 v.Cursor.ResetSelection()
506         }
507
508         if usePlugin {
509                 return PostActionCall("DeleteWordRight")
510         }
511         return true
512 }
513
514 // DeleteWordLeft deletes the word to the left of the cursor
515 func (v *View) DeleteWordLeft(usePlugin bool) bool {
516         if usePlugin && !PreActionCall("DeleteWordLeft") {
517                 return false
518         }
519
520         v.SelectWordLeft(false)
521         if v.Cursor.HasSelection() {
522                 v.Cursor.DeleteSelection()
523                 v.Cursor.ResetSelection()
524         }
525
526         if usePlugin {
527                 return PostActionCall("DeleteWordLeft")
528         }
529         return true
530 }
531
532 // Delete deletes the next character
533 func (v *View) Delete(usePlugin bool) bool {
534         if usePlugin && !PreActionCall("Delete") {
535                 return false
536         }
537
538         if v.Cursor.HasSelection() {
539                 v.Cursor.DeleteSelection()
540                 v.Cursor.ResetSelection()
541         } else {
542                 loc := v.Cursor.Loc
543                 if loc.LessThan(v.Buf.End()) {
544                         v.Buf.Remove(loc, loc.Move(1, v.Buf))
545                 }
546         }
547
548         if usePlugin {
549                 return PostActionCall("Delete")
550         }
551         return true
552 }
553
554 // IndentSelection indents the current selection
555 func (v *View) IndentSelection(usePlugin bool) bool {
556         if usePlugin && !PreActionCall("IndentSelection") {
557                 return false
558         }
559
560         if v.Cursor.HasSelection() {
561                 start := v.Cursor.CurSelection[0].Y
562                 end := v.Cursor.CurSelection[1].Move(-1, v.Buf).Y
563                 endX := v.Cursor.CurSelection[1].Move(-1, v.Buf).X
564                 for i := start; i <= end; i++ {
565                         if settings["tabstospaces"].(bool) {
566                                 tabsize := int(settings["tabsize"].(float64))
567                                 v.Buf.Insert(Loc{0, i}, Spaces(tabsize))
568                                 if i == start {
569                                         if v.Cursor.CurSelection[0].X > 0 {
570                                                 v.Cursor.CurSelection[0] = v.Cursor.CurSelection[0].Move(tabsize, v.Buf)
571                                         }
572                                 }
573                                 if i == end {
574                                         v.Cursor.CurSelection[1] = Loc{endX + tabsize + 1, end}
575                                 }
576                         } else {
577                                 v.Buf.Insert(Loc{0, i}, "\t")
578                                 if i == start {
579                                         if v.Cursor.CurSelection[0].X > 0 {
580                                                 v.Cursor.CurSelection[0] = v.Cursor.CurSelection[0].Move(1, v.Buf)
581                                         }
582                                 }
583                                 if i == end {
584                                         v.Cursor.CurSelection[1] = Loc{endX + 2, end}
585                                 }
586                         }
587                 }
588                 v.Cursor.Relocate()
589
590                 if usePlugin {
591                         return PostActionCall("IndentSelection")
592                 }
593                 return true
594         }
595         return false
596 }
597
598 // OutdentSelection takes the current selection and moves it back one indent level
599 func (v *View) OutdentSelection(usePlugin bool) bool {
600         if usePlugin && !PreActionCall("OutdentSelection") {
601                 return false
602         }
603
604         if v.Cursor.HasSelection() {
605                 start := v.Cursor.CurSelection[0].Y
606                 end := v.Cursor.CurSelection[1].Move(-1, v.Buf).Y
607                 endX := v.Cursor.CurSelection[1].Move(-1, v.Buf).X
608                 for i := start; i <= end; i++ {
609                         if len(GetLeadingWhitespace(v.Buf.Line(i))) > 0 {
610                                 if settings["tabstospaces"].(bool) {
611                                         tabsize := int(settings["tabsize"].(float64))
612                                         for j := 0; j < tabsize; j++ {
613                                                 if len(GetLeadingWhitespace(v.Buf.Line(i))) == 0 {
614                                                         break
615                                                 }
616                                                 v.Buf.Remove(Loc{0, i}, Loc{1, i})
617                                                 if i == start {
618                                                         if v.Cursor.CurSelection[0].X > 0 {
619                                                                 v.Cursor.CurSelection[0] = v.Cursor.CurSelection[0].Move(-1, v.Buf)
620                                                         }
621                                                 }
622                                                 if i == end {
623                                                         v.Cursor.CurSelection[1] = Loc{endX - j, end}
624                                                 }
625                                         }
626                                 } else {
627                                         v.Buf.Remove(Loc{0, i}, Loc{1, i})
628                                         if i == start {
629                                                 if v.Cursor.CurSelection[0].X > 0 {
630                                                         v.Cursor.CurSelection[0] = v.Cursor.CurSelection[0].Move(-1, v.Buf)
631                                                 }
632                                         }
633                                         if i == end {
634                                                 v.Cursor.CurSelection[1] = Loc{endX, end}
635                                         }
636                                 }
637                         }
638                 }
639                 v.Cursor.Relocate()
640
641                 if usePlugin {
642                         return PostActionCall("OutdentSelection")
643                 }
644                 return true
645         }
646         return false
647 }
648
649 // InsertTab inserts a tab or spaces
650 func (v *View) InsertTab(usePlugin bool) bool {
651         if usePlugin && !PreActionCall("InsertTab") {
652                 return false
653         }
654
655         if v.Cursor.HasSelection() {
656                 return false
657         }
658         // Insert a tab
659         if settings["tabstospaces"].(bool) {
660                 tabSize := int(settings["tabsize"].(float64))
661                 v.Buf.Insert(v.Cursor.Loc, Spaces(tabSize))
662                 for i := 0; i < tabSize; i++ {
663                         v.Cursor.Right()
664                 }
665         } else {
666                 v.Buf.Insert(v.Cursor.Loc, "\t")
667                 v.Cursor.Right()
668         }
669
670         if usePlugin {
671                 return PostActionCall("InsertTab")
672         }
673         return true
674 }
675
676 // Save the buffer to disk
677 func (v *View) Save(usePlugin bool) bool {
678         if usePlugin && !PreActionCall("Save") {
679                 return false
680         }
681
682         if v.Help {
683                 // We can't save the help text
684                 return false
685         }
686         // If this is an empty buffer, ask for a filename
687         if v.Buf.Path == "" {
688                 filename, canceled := messenger.Prompt("Filename: ", "Save", NoCompletion)
689                 if !canceled {
690                         v.Buf.Path = filename
691                         v.Buf.Name = filename
692                 } else {
693                         return false
694                 }
695         }
696         err := v.Buf.Save()
697         if err != nil {
698                 if strings.HasSuffix(err.Error(), "permission denied") {
699                         choice, _ := messenger.YesNoPrompt("Permission denied. Do you want to save this file using sudo? (y,n)")
700                         if choice {
701                                 err = v.Buf.SaveWithSudo()
702                                 if err != nil {
703                                         messenger.Error(err.Error())
704                                         return false
705                                 }
706                                 messenger.Message("Saved " + v.Buf.Path)
707                         }
708                         messenger.Reset()
709                         messenger.Clear()
710                 } else {
711                         messenger.Error(err.Error())
712                 }
713         } else {
714                 messenger.Message("Saved " + v.Buf.Path)
715         }
716
717         if usePlugin {
718                 return PostActionCall("Save")
719         }
720         return false
721 }
722
723 // Find opens a prompt and searches forward for the input
724 func (v *View) Find(usePlugin bool) bool {
725         if usePlugin && !PreActionCall("Find") {
726                 return false
727         }
728
729         if v.Cursor.HasSelection() {
730                 searchStart = ToCharPos(v.Cursor.CurSelection[1], v.Buf)
731         } else {
732                 searchStart = ToCharPos(v.Cursor.Loc, v.Buf)
733         }
734         BeginSearch()
735
736         if usePlugin {
737                 return PostActionCall("Find")
738         }
739         return true
740 }
741
742 // FindNext searches forwards for the last used search term
743 func (v *View) FindNext(usePlugin bool) bool {
744         if usePlugin && !PreActionCall("FindNext") {
745                 return false
746         }
747
748         if v.Cursor.HasSelection() {
749                 searchStart = ToCharPos(v.Cursor.CurSelection[1], v.Buf)
750         } else {
751                 searchStart = ToCharPos(v.Cursor.Loc, v.Buf)
752         }
753         messenger.Message("Finding: " + lastSearch)
754         Search(lastSearch, v, true)
755
756         if usePlugin {
757                 return PostActionCall("FindNext")
758         }
759         return true
760 }
761
762 // FindPrevious searches backwards for the last used search term
763 func (v *View) FindPrevious(usePlugin bool) bool {
764         if usePlugin && !PreActionCall("FindPrevious") {
765                 return false
766         }
767
768         if v.Cursor.HasSelection() {
769                 searchStart = ToCharPos(v.Cursor.CurSelection[0], v.Buf)
770         } else {
771                 searchStart = ToCharPos(v.Cursor.Loc, v.Buf)
772         }
773         messenger.Message("Finding: " + lastSearch)
774         Search(lastSearch, v, false)
775
776         if usePlugin {
777                 return PostActionCall("FindPrevious")
778         }
779         return true
780 }
781
782 // Undo undoes the last action
783 func (v *View) Undo(usePlugin bool) bool {
784         if usePlugin && !PreActionCall("Undo") {
785                 return false
786         }
787
788         v.Buf.Undo()
789         messenger.Message("Undid action")
790
791         if usePlugin {
792                 return PostActionCall("Undo")
793         }
794         return true
795 }
796
797 // Redo redoes the last action
798 func (v *View) Redo(usePlugin bool) bool {
799         if usePlugin && !PreActionCall("Redo") {
800                 return false
801         }
802
803         v.Buf.Redo()
804         messenger.Message("Redid action")
805
806         if usePlugin {
807                 return PostActionCall("Redo")
808         }
809         return true
810 }
811
812 // Copy the selection to the system clipboard
813 func (v *View) Copy(usePlugin bool) bool {
814         if usePlugin && !PreActionCall("Copy") {
815                 return false
816         }
817
818         if v.Cursor.HasSelection() {
819                 clipboard.WriteAll(v.Cursor.GetSelection())
820                 v.freshClip = true
821                 messenger.Message("Copied selection")
822         }
823
824         if usePlugin {
825                 return PostActionCall("Copy")
826         }
827         return true
828 }
829
830 // CutLine cuts the current line to the clipboard
831 func (v *View) CutLine(usePlugin bool) bool {
832         if usePlugin && !PreActionCall("CutLine") {
833                 return false
834         }
835
836         v.Cursor.SelectLine()
837         if !v.Cursor.HasSelection() {
838                 return false
839         }
840         if v.freshClip == true {
841                 if v.Cursor.HasSelection() {
842                         if clip, err := clipboard.ReadAll(); err != nil {
843                                 messenger.Error(err)
844                         } else {
845                                 clipboard.WriteAll(clip + v.Cursor.GetSelection())
846                         }
847                 }
848         } else if time.Since(v.lastCutTime)/time.Second > 10*time.Second || v.freshClip == false {
849                 v.Copy(true)
850         }
851         v.freshClip = true
852         v.lastCutTime = time.Now()
853         v.Cursor.DeleteSelection()
854         v.Cursor.ResetSelection()
855         messenger.Message("Cut line")
856
857         if usePlugin {
858                 return PostActionCall("CutLine")
859         }
860         return true
861 }
862
863 // Cut the selection to the system clipboard
864 func (v *View) Cut(usePlugin bool) bool {
865         if usePlugin && !PreActionCall("Cut") {
866                 return false
867         }
868
869         if v.Cursor.HasSelection() {
870                 clipboard.WriteAll(v.Cursor.GetSelection())
871                 v.Cursor.DeleteSelection()
872                 v.Cursor.ResetSelection()
873                 v.freshClip = true
874                 messenger.Message("Cut selection")
875
876                 if usePlugin {
877                         return PostActionCall("Cut")
878                 }
879                 return true
880         }
881
882         return false
883 }
884
885 // DuplicateLine duplicates the current line
886 func (v *View) DuplicateLine(usePlugin bool) bool {
887         if usePlugin && !PreActionCall("DuplicateLine") {
888                 return false
889         }
890
891         v.Cursor.End()
892         v.Buf.Insert(v.Cursor.Loc, "\n"+v.Buf.Line(v.Cursor.Y))
893         v.Cursor.Right()
894         messenger.Message("Duplicated line")
895
896         if usePlugin {
897                 return PostActionCall("DuplicateLine")
898         }
899         return true
900 }
901
902 // DeleteLine deletes the current line
903 func (v *View) DeleteLine(usePlugin bool) bool {
904         if usePlugin && !PreActionCall("DeleteLine") {
905                 return false
906         }
907
908         v.Cursor.SelectLine()
909         if !v.Cursor.HasSelection() {
910                 return false
911         }
912         v.Cursor.DeleteSelection()
913         v.Cursor.ResetSelection()
914         messenger.Message("Deleted line")
915
916         if usePlugin {
917                 return PostActionCall("DeleteLine")
918         }
919         return true
920 }
921
922 // Paste whatever is in the system clipboard into the buffer
923 // Delete and paste if the user has a selection
924 func (v *View) Paste(usePlugin bool) bool {
925         if usePlugin && !PreActionCall("Paste") {
926                 return false
927         }
928
929         leadingWS := GetLeadingWhitespace(v.Buf.Line(v.Cursor.Y))
930
931         if v.Cursor.HasSelection() {
932                 v.Cursor.DeleteSelection()
933                 v.Cursor.ResetSelection()
934         }
935         clip, _ := clipboard.ReadAll()
936         clip = strings.Replace(clip, "\n", "\n"+leadingWS, -1)
937         v.Buf.Insert(v.Cursor.Loc, clip)
938         v.Cursor.Loc = v.Cursor.Loc.Move(Count(clip), v.Buf)
939         v.freshClip = false
940         messenger.Message("Pasted clipboard")
941
942         if usePlugin {
943                 return PostActionCall("Paste")
944         }
945         return true
946 }
947
948 // SelectAll selects the entire buffer
949 func (v *View) SelectAll(usePlugin bool) bool {
950         if usePlugin && !PreActionCall("SelectAll") {
951                 return false
952         }
953
954         v.Cursor.CurSelection[0] = v.Buf.Start()
955         v.Cursor.CurSelection[1] = v.Buf.End()
956         // Put the cursor at the beginning
957         v.Cursor.X = 0
958         v.Cursor.Y = 0
959
960         if usePlugin {
961                 return PostActionCall("SelectAll")
962         }
963         return true
964 }
965
966 // OpenFile opens a new file in the buffer
967 func (v *View) OpenFile(usePlugin bool) bool {
968         if usePlugin && !PreActionCall("OpenFile") {
969                 return false
970         }
971
972         if v.CanClose("Continue? (yes, no, save) ") {
973                 filename, canceled := messenger.Prompt("File to open: ", "Open", FileCompletion)
974                 if canceled {
975                         return false
976                 }
977                 home, _ := homedir.Dir()
978                 filename = strings.Replace(filename, "~", home, 1)
979                 file, err := ioutil.ReadFile(filename)
980
981                 var buf *Buffer
982                 if err != nil {
983                         // File does not exist -- create an empty buffer with that name
984                         buf = NewBuffer([]byte{}, filename)
985                 } else {
986                         buf = NewBuffer(file, filename)
987                 }
988                 v.OpenBuffer(buf)
989
990                 if usePlugin {
991                         return PostActionCall("OpenFile")
992                 }
993                 return true
994         }
995         return false
996 }
997
998 // Start moves the viewport to the start of the buffer
999 func (v *View) Start(usePlugin bool) bool {
1000         if usePlugin && !PreActionCall("Start") {
1001                 return false
1002         }
1003
1004         v.Topline = 0
1005
1006         if usePlugin {
1007                 return PostActionCall("Start")
1008         }
1009         return false
1010 }
1011
1012 // End moves the viewport to the end of the buffer
1013 func (v *View) End(usePlugin bool) bool {
1014         if usePlugin && !PreActionCall("End") {
1015                 return false
1016         }
1017
1018         if v.height > v.Buf.NumLines {
1019                 v.Topline = 0
1020         } else {
1021                 v.Topline = v.Buf.NumLines - v.height
1022         }
1023
1024         if usePlugin {
1025                 return PostActionCall("End")
1026         }
1027         return false
1028 }
1029
1030 // PageUp scrolls the view up a page
1031 func (v *View) PageUp(usePlugin bool) bool {
1032         if usePlugin && !PreActionCall("PageUp") {
1033                 return false
1034         }
1035
1036         if v.Topline > v.height {
1037                 v.ScrollUp(v.height)
1038         } else {
1039                 v.Topline = 0
1040         }
1041
1042         if usePlugin {
1043                 return PostActionCall("PageUp")
1044         }
1045         return false
1046 }
1047
1048 // PageDown scrolls the view down a page
1049 func (v *View) PageDown(usePlugin bool) bool {
1050         if usePlugin && !PreActionCall("PageDown") {
1051                 return false
1052         }
1053
1054         if v.Buf.NumLines-(v.Topline+v.height) > v.height {
1055                 v.ScrollDown(v.height)
1056         } else if v.Buf.NumLines >= v.height {
1057                 v.Topline = v.Buf.NumLines - v.height
1058         }
1059
1060         if usePlugin {
1061                 return PostActionCall("PageDown")
1062         }
1063         return false
1064 }
1065
1066 // CursorPageUp places the cursor a page up
1067 func (v *View) CursorPageUp(usePlugin bool) bool {
1068         if usePlugin && !PreActionCall("CursorPageUp") {
1069                 return false
1070         }
1071
1072         if v.Cursor.HasSelection() {
1073                 v.Cursor.Loc = v.Cursor.CurSelection[0]
1074                 v.Cursor.ResetSelection()
1075         }
1076         v.Cursor.UpN(v.height)
1077
1078         if usePlugin {
1079                 return PostActionCall("CursorPageUp")
1080         }
1081         return true
1082 }
1083
1084 // CursorPageDown places the cursor a page up
1085 func (v *View) CursorPageDown(usePlugin bool) bool {
1086         if usePlugin && !PreActionCall("CursorPageDown") {
1087                 return false
1088         }
1089
1090         if v.Cursor.HasSelection() {
1091                 v.Cursor.Loc = v.Cursor.CurSelection[1]
1092                 v.Cursor.ResetSelection()
1093         }
1094         v.Cursor.DownN(v.height)
1095
1096         if usePlugin {
1097                 return PostActionCall("CursorPageDown")
1098         }
1099         return true
1100 }
1101
1102 // HalfPageUp scrolls the view up half a page
1103 func (v *View) HalfPageUp(usePlugin bool) bool {
1104         if usePlugin && !PreActionCall("HalfPageUp") {
1105                 return false
1106         }
1107
1108         if v.Topline > v.height/2 {
1109                 v.ScrollUp(v.height / 2)
1110         } else {
1111                 v.Topline = 0
1112         }
1113
1114         if usePlugin {
1115                 return PostActionCall("HalfPageUp")
1116         }
1117         return false
1118 }
1119
1120 // HalfPageDown scrolls the view down half a page
1121 func (v *View) HalfPageDown(usePlugin bool) bool {
1122         if usePlugin && !PreActionCall("HalfPageDown") {
1123                 return false
1124         }
1125
1126         if v.Buf.NumLines-(v.Topline+v.height) > v.height/2 {
1127                 v.ScrollDown(v.height / 2)
1128         } else {
1129                 if v.Buf.NumLines >= v.height {
1130                         v.Topline = v.Buf.NumLines - v.height
1131                 }
1132         }
1133
1134         if usePlugin {
1135                 return PostActionCall("HalfPageDown")
1136         }
1137         return false
1138 }
1139
1140 // ToggleRuler turns line numbers off and on
1141 func (v *View) ToggleRuler(usePlugin bool) bool {
1142         if usePlugin && !PreActionCall("ToggleRuler") {
1143                 return false
1144         }
1145
1146         if settings["ruler"] == false {
1147                 settings["ruler"] = true
1148                 messenger.Message("Enabled ruler")
1149         } else {
1150                 settings["ruler"] = false
1151                 messenger.Message("Disabled ruler")
1152         }
1153
1154         if usePlugin {
1155                 return PostActionCall("ToggleRuler")
1156         }
1157         return false
1158 }
1159
1160 // JumpLine jumps to a line and moves the view accordingly.
1161 func (v *View) JumpLine(usePlugin bool) bool {
1162         if usePlugin && !PreActionCall("JumpLine") {
1163                 return false
1164         }
1165
1166         // Prompt for line number
1167         linestring, canceled := messenger.Prompt("Jump to line # ", "LineNumber", NoCompletion)
1168         if canceled {
1169                 return false
1170         }
1171         lineint, err := strconv.Atoi(linestring)
1172         lineint = lineint - 1 // fix offset
1173         if err != nil {
1174                 messenger.Error(err) // return errors
1175                 return false
1176         }
1177         // Move cursor and view if possible.
1178         if lineint < v.Buf.NumLines && lineint >= 0 {
1179                 v.Cursor.X = 0
1180                 v.Cursor.Y = lineint
1181
1182                 if usePlugin {
1183                         return PostActionCall("JumpLine")
1184                 }
1185                 return true
1186         }
1187         messenger.Error("Only ", v.Buf.NumLines, " lines to jump")
1188         return false
1189 }
1190
1191 // ClearStatus clears the messenger bar
1192 func (v *View) ClearStatus(usePlugin bool) bool {
1193         if usePlugin && !PreActionCall("ClearStatus") {
1194                 return false
1195         }
1196
1197         messenger.Message("")
1198
1199         if usePlugin {
1200                 return PostActionCall("ClearStatus")
1201         }
1202         return false
1203 }
1204
1205 // ToggleHelp toggles the help screen
1206 func (v *View) ToggleHelp(usePlugin bool) bool {
1207         if usePlugin && !PreActionCall("ToggleHelp") {
1208                 return false
1209         }
1210
1211         if !v.Help {
1212                 // Open the default help
1213                 v.openHelp("help")
1214         } else {
1215                 v.Quit(true)
1216         }
1217
1218         if usePlugin {
1219                 return PostActionCall("ToggleHelp")
1220         }
1221         return true
1222 }
1223
1224 // ShellMode opens a terminal to run a shell command
1225 func (v *View) ShellMode(usePlugin bool) bool {
1226         if usePlugin && !PreActionCall("ShellMode") {
1227                 return false
1228         }
1229
1230         input, canceled := messenger.Prompt("$ ", "Shell", NoCompletion)
1231         if !canceled {
1232                 // The true here is for openTerm to make the command interactive
1233                 HandleShellCommand(input, true)
1234                 if usePlugin {
1235                         return PostActionCall("ShellMode")
1236                 }
1237         }
1238         return false
1239 }
1240
1241 // CommandMode lets the user enter a command
1242 func (v *View) CommandMode(usePlugin bool) bool {
1243         if usePlugin && !PreActionCall("CommandMode") {
1244                 return false
1245         }
1246
1247         input, canceled := messenger.Prompt("> ", "Command", CommandCompletion)
1248         if !canceled {
1249                 HandleCommand(input)
1250                 if usePlugin {
1251                         return PostActionCall("CommandMode")
1252                 }
1253         }
1254
1255         return false
1256 }
1257
1258 // Quit quits the editor
1259 // This behavior needs to be changed and should really only quit the editor if this
1260 // is the last view
1261 // However, since micro only supports one view for now, it doesn't really matter
1262 func (v *View) Quit(usePlugin bool) bool {
1263         if usePlugin && !PreActionCall("Quit") {
1264                 return false
1265         }
1266
1267         // Make sure not to quit if there are unsaved changes
1268         if v.CanClose("Quit anyway? (yes, no, save) ") {
1269                 v.CloseBuffer()
1270                 if len(tabs[curTab].views) > 1 {
1271                         v.splitNode.Delete()
1272                         tabs[v.TabNum].Cleanup()
1273                         tabs[v.TabNum].Resize()
1274                 } else if len(tabs) > 1 {
1275                         if len(tabs[v.TabNum].views) == 1 {
1276                                 tabs = tabs[:v.TabNum+copy(tabs[v.TabNum:], tabs[v.TabNum+1:])]
1277                                 for i, t := range tabs {
1278                                         t.SetNum(i)
1279                                 }
1280                                 if curTab >= len(tabs) {
1281                                         curTab--
1282                                 }
1283                                 if curTab == 0 {
1284                                         // CurView().Resize(screen.Size())
1285                                         CurView().ToggleTabbar()
1286                                         CurView().matches = Match(CurView())
1287                                 }
1288                         }
1289                 } else {
1290                         if usePlugin {
1291                                 PostActionCall("Quit")
1292                         }
1293
1294                         screen.Fini()
1295                         os.Exit(0)
1296                 }
1297         }
1298
1299         if usePlugin {
1300                 return PostActionCall("Quit")
1301         }
1302         return false
1303 }
1304
1305 // AddTab adds a new tab with an empty buffer
1306 func (v *View) AddTab(usePlugin bool) bool {
1307         if usePlugin && !PreActionCall("AddTab") {
1308                 return false
1309         }
1310
1311         tab := NewTabFromView(NewView(NewBuffer([]byte{}, "")))
1312         tab.SetNum(len(tabs))
1313         tabs = append(tabs, tab)
1314         curTab++
1315         if len(tabs) == 2 {
1316                 for _, t := range tabs {
1317                         for _, v := range t.views {
1318                                 v.ToggleTabbar()
1319                         }
1320                 }
1321         }
1322
1323         if usePlugin {
1324                 return PostActionCall("AddTab")
1325         }
1326         return true
1327 }
1328
1329 // PreviousTab switches to the previous tab in the tab list
1330 func (v *View) PreviousTab(usePlugin bool) bool {
1331         if usePlugin && !PreActionCall("PreviousTab") {
1332                 return false
1333         }
1334
1335         if curTab > 0 {
1336                 curTab--
1337         } else if curTab == 0 {
1338                 curTab = len(tabs) - 1
1339         }
1340
1341         if usePlugin {
1342                 return PostActionCall("PreviousTab")
1343         }
1344         return false
1345 }
1346
1347 // NextTab switches to the next tab in the tab list
1348 func (v *View) NextTab(usePlugin bool) bool {
1349         if usePlugin && !PreActionCall("NextTab") {
1350                 return false
1351         }
1352
1353         if curTab < len(tabs)-1 {
1354                 curTab++
1355         } else if curTab == len(tabs)-1 {
1356                 curTab = 0
1357         }
1358
1359         if usePlugin {
1360                 return PostActionCall("NextTab")
1361         }
1362         return false
1363 }
1364
1365 // NextSplit changes the view to the next split
1366 func (v *View) NextSplit(usePlugin bool) bool {
1367         if usePlugin && !PreActionCall("NextSplit") {
1368                 return false
1369         }
1370
1371         tab := tabs[curTab]
1372         if tab.curView < len(tab.views)-1 {
1373                 tab.curView++
1374         } else {
1375                 tab.curView = 0
1376         }
1377
1378         if usePlugin {
1379                 return PostActionCall("NextSplit")
1380         }
1381         return false
1382 }
1383
1384 // PreviousSplit changes the view to the previous split
1385 func (v *View) PreviousSplit(usePlugin bool) bool {
1386         if usePlugin && !PreActionCall("PreviousSplit") {
1387                 return false
1388         }
1389
1390         tab := tabs[curTab]
1391         if tab.curView > 0 {
1392                 tab.curView--
1393         } else {
1394                 tab.curView = len(tab.views) - 1
1395         }
1396
1397         if usePlugin {
1398                 return PostActionCall("PreviousSplit")
1399         }
1400         return false
1401 }
1402
1403 // None is no action
1404 func None() bool {
1405         return false
1406 }