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