]> git.lizzy.rs Git - micro.git/blob - cmd/micro/actions.go
Fix draw ordering
[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/atotto/clipboard"
11         "github.com/mitchellh/go-homedir"
12 )
13
14 // CursorUp moves the cursor up
15 func (v *View) CursorUp() bool {
16         if v.Cursor.HasSelection() {
17                 v.Cursor.Loc = v.Cursor.CurSelection[0]
18                 v.Cursor.ResetSelection()
19         }
20         v.Cursor.Up()
21         return true
22 }
23
24 // CursorDown moves the cursor down
25 func (v *View) CursorDown() bool {
26         if v.Cursor.HasSelection() {
27                 v.Cursor.Loc = v.Cursor.CurSelection[1]
28                 v.Cursor.ResetSelection()
29         }
30         v.Cursor.Down()
31         return true
32 }
33
34 // CursorLeft moves the cursor left
35 func (v *View) CursorLeft() bool {
36         if v.Cursor.HasSelection() {
37                 v.Cursor.Loc = v.Cursor.CurSelection[0]
38                 v.Cursor.ResetSelection()
39         } else {
40                 v.Cursor.Left()
41         }
42         return true
43 }
44
45 // CursorRight moves the cursor right
46 func (v *View) CursorRight() bool {
47         if v.Cursor.HasSelection() {
48                 v.Cursor.Loc = v.Cursor.CurSelection[1].Move(-1, v.Buf)
49                 v.Cursor.ResetSelection()
50         } else {
51                 v.Cursor.Right()
52         }
53         return true
54 }
55
56 // WordRight moves the cursor one word to the right
57 func (v *View) WordRight() bool {
58         v.Cursor.WordRight()
59         return true
60 }
61
62 // WordLeft moves the cursor one word to the left
63 func (v *View) WordLeft() bool {
64         v.Cursor.WordLeft()
65         return true
66 }
67
68 // SelectUp selects up one line
69 func (v *View) SelectUp() bool {
70         if !v.Cursor.HasSelection() {
71                 v.Cursor.OrigSelection[0] = v.Cursor.Loc
72         }
73         v.Cursor.Up()
74         v.Cursor.SelectTo(v.Cursor.Loc)
75         return true
76 }
77
78 // SelectDown selects down one line
79 func (v *View) SelectDown() bool {
80         if !v.Cursor.HasSelection() {
81                 v.Cursor.OrigSelection[0] = v.Cursor.Loc
82         }
83         v.Cursor.Down()
84         v.Cursor.SelectTo(v.Cursor.Loc)
85         return true
86 }
87
88 // SelectLeft selects the character to the left of the cursor
89 func (v *View) SelectLeft() bool {
90         loc := v.Cursor.Loc
91         count := v.Buf.End().Move(-1, v.Buf)
92         if loc.GreaterThan(count) {
93                 loc = count
94         }
95         if !v.Cursor.HasSelection() {
96                 v.Cursor.OrigSelection[0] = loc
97         }
98         v.Cursor.Left()
99         v.Cursor.SelectTo(v.Cursor.Loc)
100         return true
101 }
102
103 // SelectRight selects the character to the right of the cursor
104 func (v *View) SelectRight() bool {
105         loc := v.Cursor.Loc
106         count := v.Buf.End().Move(-1, v.Buf)
107         if loc.GreaterThan(count) {
108                 loc = count
109         }
110         if !v.Cursor.HasSelection() {
111                 v.Cursor.OrigSelection[0] = loc
112         }
113         v.Cursor.Right()
114         v.Cursor.SelectTo(v.Cursor.Loc)
115         return true
116 }
117
118 // SelectWordRight selects the word to the right of the cursor
119 func (v *View) SelectWordRight() bool {
120         if !v.Cursor.HasSelection() {
121                 v.Cursor.OrigSelection[0] = v.Cursor.Loc
122         }
123         v.Cursor.WordRight()
124         v.Cursor.SelectTo(v.Cursor.Loc)
125         return true
126 }
127
128 // SelectWordLeft selects the word to the left of the cursor
129 func (v *View) SelectWordLeft() bool {
130         if !v.Cursor.HasSelection() {
131                 v.Cursor.OrigSelection[0] = v.Cursor.Loc
132         }
133         v.Cursor.WordLeft()
134         v.Cursor.SelectTo(v.Cursor.Loc)
135         return true
136 }
137
138 // StartOfLine moves the cursor to the start of the line
139 func (v *View) StartOfLine() bool {
140         v.Cursor.Start()
141         return true
142 }
143
144 // EndOfLine moves the cursor to the end of the line
145 func (v *View) EndOfLine() bool {
146         v.Cursor.End()
147         return true
148 }
149
150 // SelectToStartOfLine selects to the start of the current line
151 func (v *View) SelectToStartOfLine() bool {
152         if !v.Cursor.HasSelection() {
153                 v.Cursor.OrigSelection[0] = v.Cursor.Loc
154         }
155         v.Cursor.Start()
156         v.Cursor.SelectTo(v.Cursor.Loc)
157         return true
158 }
159
160 // SelectToEndOfLine selects to the end of the current line
161 func (v *View) SelectToEndOfLine() bool {
162         if !v.Cursor.HasSelection() {
163                 v.Cursor.OrigSelection[0] = v.Cursor.Loc
164         }
165         v.Cursor.End()
166         v.Cursor.SelectTo(v.Cursor.Loc)
167         return true
168 }
169
170 // CursorStart moves the cursor to the start of the buffer
171 func (v *View) CursorStart() bool {
172         v.Cursor.X = 0
173         v.Cursor.Y = 0
174         return true
175 }
176
177 // CursorEnd moves the cursor to the end of the buffer
178 func (v *View) CursorEnd() bool {
179         v.Cursor.Loc = v.Buf.End()
180         return true
181 }
182
183 // SelectToStart selects the text from the cursor to the start of the buffer
184 func (v *View) SelectToStart() bool {
185         if !v.Cursor.HasSelection() {
186                 v.Cursor.OrigSelection[0] = v.Cursor.Loc
187         }
188         v.CursorStart()
189         v.Cursor.SelectTo(v.Buf.Start())
190         return true
191 }
192
193 // SelectToEnd selects the text from the cursor to the end of the buffer
194 func (v *View) SelectToEnd() bool {
195         if !v.Cursor.HasSelection() {
196                 v.Cursor.OrigSelection[0] = v.Cursor.Loc
197         }
198         v.CursorEnd()
199         v.Cursor.SelectTo(v.Buf.End())
200         return true
201 }
202
203 // InsertSpace inserts a space
204 func (v *View) InsertSpace() bool {
205         if v.Cursor.HasSelection() {
206                 v.Cursor.DeleteSelection()
207                 v.Cursor.ResetSelection()
208         }
209         v.Buf.Insert(v.Cursor.Loc, " ")
210         v.Cursor.Right()
211         return true
212 }
213
214 // InsertEnter inserts a newline plus possible some whitespace if autoindent is on
215 func (v *View) InsertEnter() bool {
216         // Insert a newline
217         if v.Cursor.HasSelection() {
218                 v.Cursor.DeleteSelection()
219                 v.Cursor.ResetSelection()
220         }
221
222         v.Buf.Insert(v.Cursor.Loc, "\n")
223         ws := GetLeadingWhitespace(v.Buf.Line(v.Cursor.Y))
224         v.Cursor.Right()
225
226         if settings["autoindent"].(bool) {
227                 v.Buf.Insert(v.Cursor.Loc, ws)
228                 for i := 0; i < len(ws); i++ {
229                         v.Cursor.Right()
230                 }
231         }
232         v.Cursor.LastVisualX = v.Cursor.GetVisualX()
233         return true
234 }
235
236 // Backspace deletes the previous character
237 func (v *View) Backspace() bool {
238         // Delete a character
239         if v.Cursor.HasSelection() {
240                 v.Cursor.DeleteSelection()
241                 v.Cursor.ResetSelection()
242         } else if v.Cursor.Loc.GreaterThan(v.Buf.Start()) {
243                 // We have to do something a bit hacky here because we want to
244                 // delete the line by first moving left and then deleting backwards
245                 // but the undo redo would place the cursor in the wrong place
246                 // So instead we move left, save the position, move back, delete
247                 // and restore the position
248
249                 // If the user is using spaces instead of tabs and they are deleting
250                 // whitespace at the start of the line, we should delete as if its a
251                 // tab (tabSize number of spaces)
252                 lineStart := v.Buf.Line(v.Cursor.Y)[:v.Cursor.X]
253                 tabSize := int(settings["tabsize"].(float64))
254                 if settings["tabstospaces"].(bool) && IsSpaces(lineStart) && len(lineStart) != 0 && len(lineStart)%tabSize == 0 {
255                         loc := v.Cursor.Loc
256                         v.Cursor.Loc = loc.Move(-tabSize, v.Buf)
257                         cx, cy := v.Cursor.X, v.Cursor.Y
258                         v.Cursor.Loc = loc
259                         v.Buf.Remove(loc.Move(-tabSize, v.Buf), loc)
260                         v.Cursor.X, v.Cursor.Y = cx, cy
261                 } else {
262                         v.Cursor.Left()
263                         cx, cy := v.Cursor.X, v.Cursor.Y
264                         v.Cursor.Right()
265                         loc := v.Cursor.Loc
266                         v.Buf.Remove(loc.Move(-1, v.Buf), loc)
267                         v.Cursor.X, v.Cursor.Y = cx, cy
268                 }
269         }
270         v.Cursor.LastVisualX = v.Cursor.GetVisualX()
271         return true
272 }
273
274 // DeleteWordRight deletes the word to the right of the cursor
275 func (v *View) DeleteWordRight() bool {
276         v.SelectWordRight()
277         if v.Cursor.HasSelection() {
278                 v.Cursor.DeleteSelection()
279                 v.Cursor.ResetSelection()
280         }
281         return true
282 }
283
284 // DeleteWordLeft deletes the word to the left of the cursor
285 func (v *View) DeleteWordLeft() bool {
286         v.SelectWordLeft()
287         if v.Cursor.HasSelection() {
288                 v.Cursor.DeleteSelection()
289                 v.Cursor.ResetSelection()
290         }
291         return true
292 }
293
294 // Delete deletes the next character
295 func (v *View) Delete() bool {
296         if v.Cursor.HasSelection() {
297                 v.Cursor.DeleteSelection()
298                 v.Cursor.ResetSelection()
299         } else {
300                 loc := v.Cursor.Loc
301                 if loc.LessThan(v.Buf.End()) {
302                         v.Buf.Remove(loc, loc.Move(1, v.Buf))
303                 }
304         }
305         return true
306 }
307
308 // InsertTab inserts a tab or spaces
309 func (v *View) InsertTab() bool {
310         // Insert a tab
311         if v.Cursor.HasSelection() {
312                 v.Cursor.DeleteSelection()
313                 v.Cursor.ResetSelection()
314         }
315         if settings["tabstospaces"].(bool) {
316                 tabSize := int(settings["tabsize"].(float64))
317                 v.Buf.Insert(v.Cursor.Loc, Spaces(tabSize))
318                 for i := 0; i < tabSize; i++ {
319                         v.Cursor.Right()
320                 }
321         } else {
322                 v.Buf.Insert(v.Cursor.Loc, "\t")
323                 v.Cursor.Right()
324         }
325         return true
326 }
327
328 // Save the buffer to disk
329 func (v *View) Save() bool {
330         if v.helpOpen {
331                 // We can't save the help text
332                 return false
333         }
334         // If this is an empty buffer, ask for a filename
335         if v.Buf.Path == "" {
336                 filename, canceled := messenger.Prompt("Filename: ", "Save", NoCompletion)
337                 if !canceled {
338                         v.Buf.Path = filename
339                         v.Buf.Name = filename
340                 } else {
341                         return false
342                 }
343         }
344         err := v.Buf.Save()
345         if err != nil {
346                 if strings.HasSuffix(err.Error(), "permission denied") {
347                         choice, _ := messenger.YesNoPrompt("Permission denied. Do you want to save this file using sudo? (y,n)")
348                         if choice {
349                                 err = v.Buf.SaveWithSudo()
350                                 if err != nil {
351                                         messenger.Error(err.Error())
352                                         return false
353                                 }
354                                 messenger.Message("Saved " + v.Buf.Path)
355                         }
356                         messenger.Reset()
357                         messenger.Clear()
358                 } else {
359                         messenger.Error(err.Error())
360                 }
361         } else {
362                 messenger.Message("Saved " + v.Buf.Path)
363         }
364         return false
365 }
366
367 // Find opens a prompt and searches forward for the input
368 func (v *View) Find() bool {
369         if v.Cursor.HasSelection() {
370                 searchStart = ToCharPos(v.Cursor.CurSelection[1], v.Buf)
371         } else {
372                 searchStart = ToCharPos(v.Cursor.Loc, v.Buf)
373         }
374         BeginSearch()
375         return true
376 }
377
378 // FindNext searches forwards for the last used search term
379 func (v *View) FindNext() bool {
380         if v.Cursor.HasSelection() {
381                 searchStart = ToCharPos(v.Cursor.CurSelection[1], v.Buf)
382         } else {
383                 searchStart = ToCharPos(v.Cursor.Loc, v.Buf)
384         }
385         messenger.Message("Finding: " + lastSearch)
386         Search(lastSearch, v, true)
387         return true
388 }
389
390 // FindPrevious searches backwards for the last used search term
391 func (v *View) FindPrevious() bool {
392         if v.Cursor.HasSelection() {
393                 searchStart = ToCharPos(v.Cursor.CurSelection[0], v.Buf)
394         } else {
395                 searchStart = ToCharPos(v.Cursor.Loc, v.Buf)
396         }
397         messenger.Message("Finding: " + lastSearch)
398         Search(lastSearch, v, false)
399         return true
400 }
401
402 // Undo undoes the last action
403 func (v *View) Undo() bool {
404         v.Buf.Undo()
405         messenger.Message("Undid action")
406         return true
407 }
408
409 // Redo redoes the last action
410 func (v *View) Redo() bool {
411         v.Buf.Redo()
412         messenger.Message("Redid action")
413         return true
414 }
415
416 // Copy the selection to the system clipboard
417 func (v *View) Copy() bool {
418         if v.Cursor.HasSelection() {
419                 clipboard.WriteAll(v.Cursor.GetSelection())
420                 v.freshClip = true
421                 messenger.Message("Copied selection")
422         }
423         return true
424 }
425
426 // CutLine cuts the current line to the clipboard
427 func (v *View) CutLine() bool {
428         v.Cursor.SelectLine()
429         if !v.Cursor.HasSelection() {
430                 return false
431         }
432         if v.freshClip == true {
433                 if v.Cursor.HasSelection() {
434                         if clip, err := clipboard.ReadAll(); err != nil {
435                                 messenger.Error(err)
436                         } else {
437                                 clipboard.WriteAll(clip + v.Cursor.GetSelection())
438                         }
439                 }
440         } else if time.Since(v.lastCutTime)/time.Second > 10*time.Second || v.freshClip == false {
441                 v.Copy()
442         }
443         v.freshClip = true
444         v.lastCutTime = time.Now()
445         v.Cursor.DeleteSelection()
446         v.Cursor.ResetSelection()
447         messenger.Message("Cut line")
448         return true
449 }
450
451 // Cut the selection to the system clipboard
452 func (v *View) Cut() bool {
453         if v.Cursor.HasSelection() {
454                 clipboard.WriteAll(v.Cursor.GetSelection())
455                 v.Cursor.DeleteSelection()
456                 v.Cursor.ResetSelection()
457                 v.freshClip = true
458                 messenger.Message("Cut selection")
459         }
460         return true
461 }
462
463 // DuplicateLine duplicates the current line
464 func (v *View) DuplicateLine() bool {
465         v.Cursor.End()
466         v.Buf.Insert(v.Cursor.Loc, "\n"+v.Buf.Line(v.Cursor.Y))
467         v.Cursor.Right()
468         messenger.Message("Duplicated line")
469         return true
470 }
471
472 // DeleteLine deletes the current line
473 func (v *View) DeleteLine() bool {
474         v.Cursor.SelectLine()
475         if !v.Cursor.HasSelection() {
476                 return false
477         }
478         v.Cursor.DeleteSelection()
479         v.Cursor.ResetSelection()
480         messenger.Message("Deleted line")
481         return true
482 }
483
484 // Paste whatever is in the system clipboard into the buffer
485 // Delete and paste if the user has a selection
486 func (v *View) Paste() bool {
487         if v.Cursor.HasSelection() {
488                 v.Cursor.DeleteSelection()
489                 v.Cursor.ResetSelection()
490         }
491         clip, _ := clipboard.ReadAll()
492         v.Buf.Insert(v.Cursor.Loc, clip)
493         v.Cursor.Loc = v.Cursor.Loc.Move(Count(clip), v.Buf)
494         v.freshClip = false
495         messenger.Message("Pasted clipboard")
496         return true
497 }
498
499 // SelectAll selects the entire buffer
500 func (v *View) SelectAll() bool {
501         v.Cursor.CurSelection[0] = v.Buf.Start()
502         v.Cursor.CurSelection[1] = v.Buf.End()
503         // Put the cursor at the beginning
504         v.Cursor.X = 0
505         v.Cursor.Y = 0
506         return true
507 }
508
509 // OpenFile opens a new file in the buffer
510 func (v *View) OpenFile() bool {
511         if v.CanClose("Continue? (yes, no, save) ") {
512                 filename, canceled := messenger.Prompt("File to open: ", "Open", FileCompletion)
513                 if canceled {
514                         return false
515                 }
516                 home, _ := homedir.Dir()
517                 filename = strings.Replace(filename, "~", home, 1)
518                 file, err := ioutil.ReadFile(filename)
519
520                 var buf *Buffer
521                 if err != nil {
522                         // File does not exist -- create an empty buffer with that name
523                         buf = NewBuffer([]byte{}, filename)
524                 } else {
525                         buf = NewBuffer(file, filename)
526                 }
527                 v.OpenBuffer(buf)
528                 return true
529         }
530         return false
531 }
532
533 // Start moves the viewport to the start of the buffer
534 func (v *View) Start() bool {
535         v.Topline = 0
536         return false
537 }
538
539 // End moves the viewport to the end of the buffer
540 func (v *View) End() bool {
541         if v.height > v.Buf.NumLines {
542                 v.Topline = 0
543         } else {
544                 v.Topline = v.Buf.NumLines - v.height
545         }
546         return false
547 }
548
549 // PageUp scrolls the view up a page
550 func (v *View) PageUp() bool {
551         if v.Topline > v.height {
552                 v.ScrollUp(v.height)
553         } else {
554                 v.Topline = 0
555         }
556         return false
557 }
558
559 // PageDown scrolls the view down a page
560 func (v *View) PageDown() bool {
561         if v.Buf.NumLines-(v.Topline+v.height) > v.height {
562                 v.ScrollDown(v.height)
563         } else if v.Buf.NumLines >= v.height {
564                 v.Topline = v.Buf.NumLines - v.height
565         }
566         return false
567 }
568
569 // CursorPageUp places the cursor a page up
570 func (v *View) CursorPageUp() bool {
571         if v.Cursor.HasSelection() {
572                 v.Cursor.Loc = v.Cursor.CurSelection[0]
573                 v.Cursor.ResetSelection()
574         }
575         v.Cursor.UpN(v.height)
576         return true
577 }
578
579 // CursorPageDown places the cursor a page up
580 func (v *View) CursorPageDown() bool {
581         if v.Cursor.HasSelection() {
582                 v.Cursor.Loc = v.Cursor.CurSelection[1]
583                 v.Cursor.ResetSelection()
584         }
585         v.Cursor.DownN(v.height)
586         return true
587 }
588
589 // HalfPageUp scrolls the view up half a page
590 func (v *View) HalfPageUp() bool {
591         if v.Topline > v.height/2 {
592                 v.ScrollUp(v.height / 2)
593         } else {
594                 v.Topline = 0
595         }
596         return false
597 }
598
599 // HalfPageDown scrolls the view down half a page
600 func (v *View) HalfPageDown() bool {
601         if v.Buf.NumLines-(v.Topline+v.height) > v.height/2 {
602                 v.ScrollDown(v.height / 2)
603         } else {
604                 if v.Buf.NumLines >= v.height {
605                         v.Topline = v.Buf.NumLines - v.height
606                 }
607         }
608         return false
609 }
610
611 // ToggleRuler turns line numbers off and on
612 func (v *View) ToggleRuler() bool {
613         if settings["ruler"] == false {
614                 settings["ruler"] = true
615                 messenger.Message("Enabled ruler")
616         } else {
617                 settings["ruler"] = false
618                 messenger.Message("Disabled ruler")
619         }
620         return false
621 }
622
623 // JumpLine jumps to a line and moves the view accordingly.
624 func (v *View) JumpLine() bool {
625         // Prompt for line number
626         linestring, canceled := messenger.Prompt("Jump to line # ", "LineNumber", NoCompletion)
627         if canceled {
628                 return false
629         }
630         lineint, err := strconv.Atoi(linestring)
631         lineint = lineint - 1 // fix offset
632         if err != nil {
633                 messenger.Error(err) // return errors
634                 return false
635         }
636         // Move cursor and view if possible.
637         if lineint < v.Buf.NumLines && lineint >= 0 {
638                 v.Cursor.X = 0
639                 v.Cursor.Y = lineint
640                 return true
641         }
642         messenger.Error("Only ", v.Buf.NumLines, " lines to jump")
643         return false
644 }
645
646 // ClearStatus clears the messenger bar
647 func (v *View) ClearStatus() bool {
648         messenger.Message("")
649         return false
650 }
651
652 // ToggleHelp toggles the help screen
653 func (v *View) ToggleHelp() bool {
654         if !v.helpOpen {
655                 v.lastBuffer = v.Buf
656                 helpBuffer := NewBuffer([]byte(helpTxt), "help.md")
657                 helpBuffer.Name = "Help"
658                 v.helpOpen = true
659                 v.OpenBuffer(helpBuffer)
660         } else {
661                 v.OpenBuffer(v.lastBuffer)
662                 v.helpOpen = false
663         }
664         return true
665 }
666
667 // ShellMode opens a terminal to run a shell command
668 func (v *View) ShellMode() bool {
669         input, canceled := messenger.Prompt("$ ", "Shell", NoCompletion)
670         if !canceled {
671                 // The true here is for openTerm to make the command interactive
672                 HandleShellCommand(input, true)
673         }
674         return false
675 }
676
677 // CommandMode lets the user enter a command
678 func (v *View) CommandMode() bool {
679         input, canceled := messenger.Prompt("> ", "Command", NoCompletion)
680         if !canceled {
681                 HandleCommand(input)
682         }
683         return false
684 }
685
686 // Quit quits the editor
687 // This behavior needs to be changed and should really only quit the editor if this
688 // is the last view
689 // However, since micro only supports one view for now, it doesn't really matter
690 func (v *View) Quit() bool {
691         if v.helpOpen {
692                 return v.ToggleHelp()
693         }
694         // Make sure not to quit if there are unsaved changes
695         if v.CanClose("Quit anyway? (yes, no, save) ") {
696                 v.CloseBuffer()
697                 if len(tabs[curTab].views) > 1 {
698                         var view *View
699                         if v.splitChild != nil {
700                                 view = v.splitChild
701                                 view.splitParent = v.splitParent
702                         } else if v.splitParent != nil {
703                                 view = v.splitParent
704                                 v.splitParent.splitChild = nil
705                         }
706                         view.x, view.y = view.splitOrigPos[0], view.splitOrigPos[1]
707                         view.widthPercent, view.heightPercent = view.splitOrigDimensions[0], view.splitOrigDimensions[1]
708                         view.Resize(screen.Size())
709                         if settings["syntax"].(bool) {
710                                 view.matches = Match(view)
711                         }
712                         tabs[curTab].views = tabs[curTab].views[:v.Num+copy(tabs[curTab].views[v.Num:], tabs[curTab].views[v.Num+1:])]
713                         for i, v := range tabs[curTab].views {
714                                 v.Num = i
715                         }
716                         tabs[curTab].curView = view.Num
717                 } else if len(tabs) > 1 {
718                         if len(tabs[v.TabNum].views) == 1 {
719                                 tabs = tabs[:v.TabNum+copy(tabs[v.TabNum:], tabs[v.TabNum+1:])]
720                                 for i, t := range tabs {
721                                         t.SetNum(i)
722                                 }
723                                 if curTab >= len(tabs) {
724                                         curTab--
725                                 }
726                                 if curTab == 0 {
727                                         CurView().Resize(screen.Size())
728                                         CurView().matches = Match(CurView())
729                                 }
730                         }
731                 } else {
732                         screen.Fini()
733                         os.Exit(0)
734                 }
735         }
736         return false
737 }
738
739 // AddTab adds a new tab with an empty buffer
740 func (v *View) AddTab() bool {
741         tab := NewTabFromView(NewView(NewBuffer([]byte{}, "")))
742         tab.SetNum(len(tabs))
743         tabs = append(tabs, tab)
744         curTab++
745         if len(tabs) == 2 {
746                 for _, t := range tabs {
747                         for _, v := range t.views {
748                                 v.Resize(screen.Size())
749                         }
750                 }
751         }
752         return true
753 }
754
755 // PreviousTab switches to the previous tab in the tab list
756 func (v *View) PreviousTab() bool {
757         if curTab > 0 {
758                 curTab--
759         } else if curTab == 0 {
760                 curTab = len(tabs) - 1
761         }
762         return false
763 }
764
765 // NextTab switches to the next tab in the tab list
766 func (v *View) NextTab() bool {
767         if curTab < len(tabs)-1 {
768                 curTab++
769         } else if curTab == len(tabs)-1 {
770                 curTab = 0
771         }
772         return false
773 }
774
775 // Changes the view to the next split
776 func (v *View) NextSplit() bool {
777         tab := tabs[curTab]
778         if tab.curView < len(tab.views)-1 {
779                 tab.curView++
780         } else {
781                 tab.curView = 0
782         }
783         return false
784 }
785
786 // Changes the view to the previous split
787 func (v *View) PreviousSplit() bool {
788         tab := tabs[curTab]
789         if tab.curView > 0 {
790                 tab.curView--
791         } else {
792                 tab.curView = len(tab.views) - 1
793         }
794         return false
795 }
796
797 // None is no action
798 func None() bool {
799         return false
800 }