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