diff --git a/cmd/micro/bindings.go b/cmd/micro/bindings.go index c97c96e0..9b1af052 100644 --- a/cmd/micro/bindings.go +++ b/cmd/micro/bindings.go @@ -73,6 +73,8 @@ var bindingActions = map[string]func(*View) bool{ "ShellMode": (*View).ShellMode, "CommandMode": (*View).CommandMode, "Quit": (*View).Quit, + "LastView": (*View).LastView, + "NextView": (*View).NextView, } var bindingKeys = map[string]tcell.Key{ @@ -902,7 +904,7 @@ func (v *View) OpenFile() bool { if v.CanClose("Continue? (yes, no, save) ") { filename, canceled := messenger.Prompt("File to open: ", "Open") if canceled { - return true + return false } home, _ := homedir.Dir() filename = strings.Replace(filename, "~", home, 1) @@ -910,12 +912,19 @@ func (v *View) OpenFile() bool { if err != nil { messenger.Error(err.Error()) - return true + return false } buf := NewBuffer(file, filename) v.OpenBuffer(buf) + return true } - return true + return false +} + +func (v *View) openInNewView(buf *Buffer) { + views = append(views, NewView(buf)) + mainView++ + views[mainView].Num = mainView } // Start moves the viewport to the start of the buffer @@ -1082,12 +1091,38 @@ func (v *View) Quit() bool { // Make sure not to quit if there are unsaved changes if views[mainView].CanClose("Quit anyway? (yes, no, save) ") { views[mainView].CloseBuffer() - screen.Fini() - os.Exit(0) + if len(views) > 1 { + views = views[:v.Num+copy(views[v.Num:], views[v.Num+1:])] + for i, v := range views { + v.Num = i + } + if v.Num <= mainView { + if !(v.Num == mainView && mainView == 0) { + mainView-- + } + } + } else { + screen.Fini() + os.Exit(0) + } } return false } +func (v *View) LastView() bool { + if mainView > 0 { + mainView-- + } + return true +} + +func (v *View) NextView() bool { + if mainView < len(views)-1 { + mainView++ + } + return true +} + // None is no action func None() bool { return false diff --git a/cmd/micro/micro.go b/cmd/micro/micro.go index e9ed08f4..00e54b3f 100644 --- a/cmd/micro/micro.go +++ b/cmd/micro/micro.go @@ -55,7 +55,7 @@ var ( ) // LoadInput loads the file input for the editor -func LoadInput() (string, []byte, error) { +func LoadInput() []*Buffer { // There are a number of ways micro should start given its input // 1. If it is given a file in os.Args, it should open that @@ -72,23 +72,34 @@ func LoadInput() (string, []byte, error) { var filename string var input []byte var err error + var buffers []*Buffer if len(os.Args) > 1 { // Option 1 - filename = os.Args[1] - // Check that the file exists - if _, e := os.Stat(filename); e == nil { - input, err = ioutil.ReadFile(filename) + for i := 1; i < len(os.Args); i++ { + filename = os.Args[i] + // Check that the file exists + if _, e := os.Stat(filename); e == nil { + input, err = ioutil.ReadFile(filename) + if err != nil { + TermMessage(err) + continue + } + } + buffers = append(buffers, NewBuffer(input, filename)) } } else if !isatty.IsTerminal(os.Stdin.Fd()) { // Option 2 // The input is not a terminal, so something is being piped in // and we should read from stdin input, err = ioutil.ReadAll(os.Stdin) + buffers = append(buffers, NewBuffer(input, filename)) + } else { + // Option 3, just open an empty buffer + buffers = append(buffers, NewBuffer(input, filename)) } - // Option 3, or just return whatever we got - return filename, input, err + return buffers } // InitConfigDir finds the configuration directory for micro according to the XDG spec. @@ -170,9 +181,10 @@ func InitScreen() { // RedrawAll redraws everything -- all the views and the messenger func RedrawAll() { messenger.Clear() - for _, v := range views { - v.Display() - } + // for _, v := range views { + views[mainView].Display() + // } + DisplayTabBar() messenger.Display() screen.Show() } @@ -186,12 +198,6 @@ func main() { os.Exit(0) } - filename, input, err := LoadInput() - if err != nil { - fmt.Println(err) - os.Exit(1) - } - L = lua.NewState() defer L.Close() @@ -210,8 +216,6 @@ func main() { // Load the help files LoadHelp() - buf := NewBuffer(input, filename) - InitScreen() // This is just so if we have an error, we can exit cleanly and not completely @@ -229,8 +233,12 @@ func main() { messenger = new(Messenger) messenger.history = make(map[string][]string) - views = make([]*View, 1) - views[0] = NewView(buf) + // views = make([]*View, 1) + buffers := LoadInput() + for i, buf := range buffers { + views = append(views, NewView(buf)) + views[i].Num = i + } L.SetGlobal("OS", luar.New(L, runtime.GOOS)) L.SetGlobal("views", luar.New(L, views)) diff --git a/cmd/micro/statusline.go b/cmd/micro/statusline.go index ea3e7a4d..8c505195 100644 --- a/cmd/micro/statusline.go +++ b/cmd/micro/statusline.go @@ -15,7 +15,7 @@ type Statusline struct { // Display draws the statusline to the screen func (sline *Statusline) Display() { // We'll draw the line at the lowest line in the view - y := sline.view.height + y := sline.view.height + sline.view.y file := sline.view.Buf.Name // If the name is empty, use 'No name' diff --git a/cmd/micro/tabbar.go b/cmd/micro/tabbar.go new file mode 100644 index 00000000..be5de108 --- /dev/null +++ b/cmd/micro/tabbar.go @@ -0,0 +1,35 @@ +package main + +func DisplayTabBar() { + str := "" + for i, v := range views { + if i == mainView { + str += "[" + } else { + str += " " + } + str += v.Buf.Name + if i == mainView { + str += "]" + } else { + str += " " + } + str += " " + } + + tabBarStyle := defStyle.Reverse(true) + if style, ok := colorscheme["tabbar"]; ok { + tabBarStyle = style + } + + // Maybe there is a unicode filename? + fileRunes := []rune(str) + w, _ := screen.Size() + for x := 0; x < w; x++ { + if x < len(fileRunes) { + screen.SetContent(x, 0, fileRunes[x], nil, tabBarStyle) + } else { + screen.SetContent(x, 0, ' ', nil, tabBarStyle) + } + } +} diff --git a/cmd/micro/view.go b/cmd/micro/view.go index ca1ad2ca..01e134a9 100644 --- a/cmd/micro/view.go +++ b/cmd/micro/view.go @@ -31,6 +31,9 @@ type View struct { width int height int + // Where this view is located + x, y int + // How much to offset because of line numbers lineNumOffset int @@ -40,6 +43,9 @@ type View struct { // Is the help text opened in this view helpOpen bool + // This is the index of this view in the views array + Num int + // Is this view modifiable? Modifiable bool @@ -91,6 +97,8 @@ func NewView(buf *Buffer) *View { func NewViewWidthHeight(buf *Buffer, w, h int) *View { v := new(View) + v.x, v.y = 0, 1 + v.widthPercent = w v.heightPercent = h v.Resize(screen.Size()) @@ -113,6 +121,8 @@ func NewViewWidthHeight(buf *Buffer, w, h int) *View { func (v *View) Resize(w, h int) { // Always include 1 line for the command line at the bottom h-- + // Include one line for the tab bar at the top + h-- v.width = int(float32(w) * float32(v.widthPercent) / 100) // We subtract 1 for the statusline v.height = int(float32(h) * float32(v.heightPercent) / 100) @@ -173,6 +183,7 @@ func (v *View) OpenBuffer(buf *Buffer) { v.Topline = 0 v.leftCol = 0 v.Cursor.ResetSelection() + v.Relocate() v.messages = make(map[string][]GutterMessage) v.matches = Match(v) @@ -459,12 +470,12 @@ func (v *View) DisplayView() { } for lineN := 0; lineN < v.height; lineN++ { - var x int + x := v.x // If the buffer is smaller than the view height if lineN+v.Topline >= v.Buf.NumLines { // We have to clear all this space for i := 0; i < v.width; i++ { - screen.SetContent(i, lineN, ' ', nil, defStyle) + screen.SetContent(i, lineN+v.y, ' ', nil, defStyle) } continue @@ -492,9 +503,9 @@ func (v *View) DisplayView() { gutterStyle = style } } - screen.SetContent(x, lineN, '>', nil, gutterStyle) + screen.SetContent(x, lineN+v.y, '>', nil, gutterStyle) x++ - screen.SetContent(x, lineN, '>', nil, gutterStyle) + screen.SetContent(x, lineN+v.y, '>', nil, gutterStyle) x++ if v.Cursor.Y == lineN+v.Topline { messenger.Message(msg.msg) @@ -504,9 +515,9 @@ func (v *View) DisplayView() { } } if !msgOnLine { - screen.SetContent(x, lineN, ' ', nil, tcell.StyleDefault) + screen.SetContent(x, lineN+v.y, ' ', nil, tcell.StyleDefault) x++ - screen.SetContent(x, lineN, ' ', nil, tcell.StyleDefault) + screen.SetContent(x, lineN+v.y, ' ', nil, tcell.StyleDefault) x++ if v.Cursor.Y == lineN+v.Topline && messenger.gutterMessage { messenger.Reset() @@ -525,18 +536,18 @@ func (v *View) DisplayView() { if settings["ruler"] == true { lineNum = strconv.Itoa(lineN + v.Topline + 1) for i := 0; i < maxLineLength-len(lineNum); i++ { - screen.SetContent(x, lineN, ' ', nil, lineNumStyle) + screen.SetContent(x, lineN+v.y, ' ', nil, lineNumStyle) x++ } // Write the actual line number for _, ch := range lineNum { - screen.SetContent(x, lineN, ch, nil, lineNumStyle) + screen.SetContent(x, lineN+v.y, ch, nil, lineNumStyle) x++ } if settings["ruler"] == true { // Write the extra space - screen.SetContent(x, lineN, ' ', nil, lineNumStyle) + screen.SetContent(x, lineN+v.y, ' ', nil, lineNumStyle) x++ } } @@ -594,11 +605,14 @@ func (v *View) DisplayView() { if x-v.leftCol >= v.lineNumOffset { screen.SetContent(x-v.leftCol, lineN, indentChar[0], nil, lineIndentStyle) } + if x-v.leftCol >= v.lineNumOffset { + screen.SetContent(x-v.leftCol, lineN+v.y, indentChar[0], nil, lineIndentStyle) + } tabSize := int(settings["tabsize"].(float64)) for i := 0; i < tabSize-1; i++ { x++ if x-v.leftCol >= v.lineNumOffset { - screen.SetContent(x-v.leftCol, lineN, ' ', nil, lineStyle) + screen.SetContent(x-v.leftCol, lineN+v.y, ' ', nil, lineStyle) } } } else if runewidth.RuneWidth(ch) > 1 { @@ -613,7 +627,7 @@ func (v *View) DisplayView() { } } else { if x-v.leftCol >= v.lineNumOffset { - screen.SetContent(x-v.leftCol, lineN, ch, nil, lineStyle) + screen.SetContent(x-v.leftCol, lineN+v.y, ch, nil, lineStyle) } } charNum = charNum.Move(1, v.Buf) @@ -632,7 +646,7 @@ func (v *View) DisplayView() { if style, ok := colorscheme["selection"]; ok { selectStyle = style } - screen.SetContent(x-v.leftCol, lineN, ' ', nil, selectStyle) + screen.SetContent(x-v.leftCol, lineN+v.y, ' ', nil, selectStyle) x++ } @@ -647,7 +661,7 @@ func (v *View) DisplayView() { } } if !(x-v.leftCol < v.lineNumOffset) { - screen.SetContent(x-v.leftCol+i, lineN, ' ', nil, lineStyle) + screen.SetContent(x+i, lineN+v.y, ' ', nil, lineStyle) } } } @@ -659,7 +673,7 @@ func (v *View) DisplayCursor() { if (v.Cursor.Y-v.Topline < 0 || v.Cursor.Y-v.Topline > v.height-1) || v.Cursor.HasSelection() { screen.HideCursor() } else { - screen.ShowCursor(v.Cursor.GetVisualX()+v.lineNumOffset-v.leftCol, v.Cursor.Y-v.Topline) + screen.ShowCursor(v.x+v.Cursor.GetVisualX()+v.lineNumOffset-v.leftCol, v.Cursor.Y-v.Topline+v.y) } }