From 7d87e6db9985fdbb4a144b49ca66b75cc27ed56a Mon Sep 17 00:00:00 2001 From: Zachary Yedidia Date: Tue, 28 Aug 2018 18:44:52 -0400 Subject: [PATCH] More actions and window organization --- cmd/micro/action/actions.go | 309 +++++++++++++++++--------- cmd/micro/action/bufhandler.go | 41 ++-- cmd/micro/action/handler.go | 5 + cmd/micro/buffer/cursor.go | 149 +++++++++++++ cmd/micro/{ => display}/infobar.go | 2 +- cmd/micro/{ => display}/statusline.go | 6 +- cmd/micro/{ => display}/window.go | 86 ++++--- cmd/micro/editpane.go | 20 ++ cmd/micro/lua/lua.go | 2 +- cmd/micro/micro.go | 87 ++++++-- cmd/micro/util/util.go | 9 +- 11 files changed, 539 insertions(+), 177 deletions(-) rename cmd/micro/{ => display}/infobar.go (99%) rename cmd/micro/{ => display}/statusline.go (97%) rename cmd/micro/{ => display}/window.go (71%) create mode 100644 cmd/micro/editpane.go diff --git a/cmd/micro/action/actions.go b/cmd/micro/action/actions.go index db56279a..ec6c07bc 100644 --- a/cmd/micro/action/actions.go +++ b/cmd/micro/action/actions.go @@ -9,450 +9,553 @@ import ( // MousePress is the event that should happen when a normal click happens // This is almost always bound to left click -func (a *BufHandler) MousePress(e *tcell.EventMouse) bool { +func (h *BufHandler) MousePress(e *tcell.EventMouse) bool { return false } // ScrollUpAction scrolls the view up -func (a *BufHandler) ScrollUpAction() bool { +func (h *BufHandler) ScrollUpAction() bool { return false } // ScrollDownAction scrolls the view up -func (a *BufHandler) ScrollDownAction() bool { +func (h *BufHandler) ScrollDownAction() bool { return false } // Center centers the view on the cursor -func (a *BufHandler) Center() bool { +func (h *BufHandler) Center() bool { return true } // CursorUp moves the cursor up -func (a *BufHandler) CursorUp() bool { - a.Cursor.Deselect(true) - a.Cursor.Up() +func (h *BufHandler) CursorUp() bool { + h.Cursor.Deselect(true) + h.Cursor.Up() return true } // CursorDown moves the cursor down -func (a *BufHandler) CursorDown() bool { - a.Cursor.Deselect(true) - a.Cursor.Down() +func (h *BufHandler) CursorDown() bool { + h.Cursor.Deselect(true) + h.Cursor.Down() return true } // CursorLeft moves the cursor left -func (a *BufHandler) CursorLeft() bool { - a.Cursor.Deselect(true) - a.Cursor.Left() +func (h *BufHandler) CursorLeft() bool { + h.Cursor.Deselect(true) + h.Cursor.Left() return true } // CursorRight moves the cursor right -func (a *BufHandler) CursorRight() bool { - a.Cursor.Deselect(true) - a.Cursor.Right() +func (h *BufHandler) CursorRight() bool { + h.Cursor.Deselect(true) + h.Cursor.Right() return true } // WordRight moves the cursor one word to the right -func (a *BufHandler) WordRight() bool { +func (h *BufHandler) WordRight() bool { + h.Cursor.Deselect(true) + h.Cursor.WordRight() return true } // WordLeft moves the cursor one word to the left -func (a *BufHandler) WordLeft() bool { +func (h *BufHandler) WordLeft() bool { + h.Cursor.Deselect(true) + h.Cursor.WordLeft() return true } // SelectUp selects up one line -func (a *BufHandler) SelectUp() bool { +func (h *BufHandler) SelectUp() bool { + if !h.Cursor.HasSelection() { + h.Cursor.OrigSelection[0] = h.Cursor.Loc + } + h.Cursor.Up() + h.Cursor.SelectTo(h.Cursor.Loc) return true } // SelectDown selects down one line -func (a *BufHandler) SelectDown() bool { +func (h *BufHandler) SelectDown() bool { + if !h.Cursor.HasSelection() { + h.Cursor.OrigSelection[0] = h.Cursor.Loc + } + h.Cursor.Down() + h.Cursor.SelectTo(h.Cursor.Loc) return true } // SelectLeft selects the character to the left of the cursor -func (a *BufHandler) SelectLeft() bool { +func (h *BufHandler) SelectLeft() bool { + loc := h.Cursor.Loc + count := h.Buf.End() + if loc.GreaterThan(count) { + loc = count + } + if !h.Cursor.HasSelection() { + h.Cursor.OrigSelection[0] = loc + } + h.Cursor.Left() + h.Cursor.SelectTo(h.Cursor.Loc) return true } // SelectRight selects the character to the right of the cursor -func (a *BufHandler) SelectRight() bool { +func (h *BufHandler) SelectRight() bool { + loc := h.Cursor.Loc + count := h.Buf.End() + if loc.GreaterThan(count) { + loc = count + } + if !h.Cursor.HasSelection() { + h.Cursor.OrigSelection[0] = loc + } + h.Cursor.Right() + h.Cursor.SelectTo(h.Cursor.Loc) return true } // SelectWordRight selects the word to the right of the cursor -func (a *BufHandler) SelectWordRight() bool { +func (h *BufHandler) SelectWordRight() bool { + if !h.Cursor.HasSelection() { + h.Cursor.OrigSelection[0] = h.Cursor.Loc + } + h.Cursor.WordRight() + h.Cursor.SelectTo(h.Cursor.Loc) return true } // SelectWordLeft selects the word to the left of the cursor -func (a *BufHandler) SelectWordLeft() bool { +func (h *BufHandler) SelectWordLeft() bool { + if !h.Cursor.HasSelection() { + h.Cursor.OrigSelection[0] = h.Cursor.Loc + } + h.Cursor.WordLeft() + h.Cursor.SelectTo(h.Cursor.Loc) return true } // StartOfLine moves the cursor to the start of the line -func (a *BufHandler) StartOfLine() bool { +func (h *BufHandler) StartOfLine() bool { + h.Cursor.Deselect(true) + if h.Cursor.X != 0 { + h.Cursor.Start() + } else { + h.Cursor.StartOfText() + } return true } // EndOfLine moves the cursor to the end of the line -func (a *BufHandler) EndOfLine() bool { +func (h *BufHandler) EndOfLine() bool { + h.Cursor.Deselect(true) + h.Cursor.End() return true } // SelectLine selects the entire current line -func (a *BufHandler) SelectLine() bool { +func (h *BufHandler) SelectLine() bool { + h.Cursor.SelectLine() return true } // SelectToStartOfLine selects to the start of the current line -func (a *BufHandler) SelectToStartOfLine() bool { +func (h *BufHandler) SelectToStartOfLine() bool { + if !h.Cursor.HasSelection() { + h.Cursor.OrigSelection[0] = h.Cursor.Loc + } + h.Cursor.Start() + h.Cursor.SelectTo(h.Cursor.Loc) return true } // SelectToEndOfLine selects to the end of the current line -func (a *BufHandler) SelectToEndOfLine() bool { +func (h *BufHandler) SelectToEndOfLine() bool { + if !h.Cursor.HasSelection() { + h.Cursor.OrigSelection[0] = h.Cursor.Loc + } + h.Cursor.End() + h.Cursor.SelectTo(h.Cursor.Loc) return true } // ParagraphPrevious moves the cursor to the previous empty line, or beginning of the buffer if there's none -func (a *BufHandler) ParagraphPrevious() bool { +func (h *BufHandler) ParagraphPrevious() bool { + var line int + for line = h.Cursor.Y; line > 0; line-- { + if len(h.Buf.LineBytes(line)) == 0 && line != h.Cursor.Y { + h.Cursor.X = 0 + h.Cursor.Y = line + break + } + } + // If no empty line found. move cursor to end of buffer + if line == 0 { + h.Cursor.Loc = h.Buf.Start() + } return true } // ParagraphNext moves the cursor to the next empty line, or end of the buffer if there's none -func (a *BufHandler) ParagraphNext() bool { +func (h *BufHandler) ParagraphNext() bool { + var line int + for line = h.Cursor.Y; line < h.Buf.LinesNum(); line++ { + if len(h.Buf.LineBytes(line)) == 0 && line != h.Cursor.Y { + h.Cursor.X = 0 + h.Cursor.Y = line + break + } + } + // If no empty line found. move cursor to end of buffer + if line == h.Buf.LinesNum() { + h.Cursor.Loc = h.Buf.End() + } return true } // Retab changes all tabs to spaces or all spaces to tabs depending // on the user's settings -func (a *BufHandler) Retab() bool { +func (h *BufHandler) Retab() bool { return true } // CursorStart moves the cursor to the start of the buffer -func (a *BufHandler) CursorStart() bool { +func (h *BufHandler) CursorStart() bool { + h.Cursor.Deselect(true) + h.Cursor.X = 0 + h.Cursor.Y = 0 return true } // CursorEnd moves the cursor to the end of the buffer -func (a *BufHandler) CursorEnd() bool { +func (h *BufHandler) CursorEnd() bool { + h.Cursor.Deselect(true) + h.Cursor.Loc = h.Buf.End() + h.Cursor.StoreVisualX() return true } // SelectToStart selects the text from the cursor to the start of the buffer -func (a *BufHandler) SelectToStart() bool { +func (h *BufHandler) SelectToStart() bool { + if !h.Cursor.HasSelection() { + h.Cursor.OrigSelection[0] = h.Cursor.Loc + } + h.CursorStart() + h.Cursor.SelectTo(h.Buf.Start()) return true } // SelectToEnd selects the text from the cursor to the end of the buffer -func (a *BufHandler) SelectToEnd() bool { +func (h *BufHandler) SelectToEnd() bool { + if !h.Cursor.HasSelection() { + h.Cursor.OrigSelection[0] = h.Cursor.Loc + } + h.CursorEnd() + h.Cursor.SelectTo(h.Buf.End()) return true } // InsertSpace inserts a space -func (a *BufHandler) InsertSpace() bool { +func (h *BufHandler) InsertSpace() bool { return true } // InsertNewline inserts a newline plus possible some whitespace if autoindent is on -func (a *BufHandler) InsertNewline() bool { +func (h *BufHandler) InsertNewline() bool { return true } // Backspace deletes the previous character -func (a *BufHandler) Backspace() bool { +func (h *BufHandler) Backspace() bool { return true } // DeleteWordRight deletes the word to the right of the cursor -func (a *BufHandler) DeleteWordRight() bool { +func (h *BufHandler) DeleteWordRight() bool { return true } // DeleteWordLeft deletes the word to the left of the cursor -func (a *BufHandler) DeleteWordLeft() bool { +func (h *BufHandler) DeleteWordLeft() bool { return true } // Delete deletes the next character -func (a *BufHandler) Delete() bool { +func (h *BufHandler) Delete() bool { return true } // IndentSelection indents the current selection -func (a *BufHandler) IndentSelection() bool { +func (h *BufHandler) IndentSelection() bool { return false } // OutdentLine moves the current line back one indentation -func (a *BufHandler) OutdentLine() bool { +func (h *BufHandler) OutdentLine() bool { return true } // OutdentSelection takes the current selection and moves it back one indent level -func (a *BufHandler) OutdentSelection() bool { +func (h *BufHandler) OutdentSelection() bool { return false } // InsertTab inserts a tab or spaces -func (a *BufHandler) InsertTab() bool { +func (h *BufHandler) InsertTab() bool { return true } // SaveAll saves all open buffers -func (a *BufHandler) SaveAll() bool { +func (h *BufHandler) SaveAll() bool { return false } // Save the buffer to disk -func (a *BufHandler) Save() bool { +func (h *BufHandler) Save() bool { return false } // SaveAs saves the buffer to disk with the given name -func (a *BufHandler) SaveAs() bool { +func (h *BufHandler) SaveAs() bool { return false } // Find opens a prompt and searches forward for the input -func (a *BufHandler) Find() bool { +func (h *BufHandler) Find() bool { return true } // FindNext searches forwards for the last used search term -func (a *BufHandler) FindNext() bool { +func (h *BufHandler) FindNext() bool { return true } // FindPrevious searches backwards for the last used search term -func (a *BufHandler) FindPrevious() bool { +func (h *BufHandler) FindPrevious() bool { return true } // Undo undoes the last action -func (a *BufHandler) Undo() bool { +func (h *BufHandler) Undo() bool { return true } // Redo redoes the last action -func (a *BufHandler) Redo() bool { +func (h *BufHandler) Redo() bool { return true } // Copy the selection to the system clipboard -func (a *BufHandler) Copy() bool { +func (h *BufHandler) Copy() bool { return true } // CutLine cuts the current line to the clipboard -func (a *BufHandler) CutLine() bool { +func (h *BufHandler) CutLine() bool { return true } // Cut the selection to the system clipboard -func (a *BufHandler) Cut() bool { +func (h *BufHandler) Cut() bool { return true } // DuplicateLine duplicates the current line or selection -func (a *BufHandler) DuplicateLine() bool { +func (h *BufHandler) DuplicateLine() bool { return true } // DeleteLine deletes the current line -func (a *BufHandler) DeleteLine() bool { +func (h *BufHandler) DeleteLine() bool { return true } // MoveLinesUp moves up the current line or selected lines if any -func (a *BufHandler) MoveLinesUp() bool { +func (h *BufHandler) MoveLinesUp() bool { return true } // MoveLinesDown moves down the current line or selected lines if any -func (a *BufHandler) MoveLinesDown() bool { +func (h *BufHandler) MoveLinesDown() bool { return true } // Paste whatever is in the system clipboard into the buffer // Delete and paste if the user has a selection -func (a *BufHandler) Paste() bool { +func (h *BufHandler) Paste() bool { return true } // PastePrimary pastes from the primary clipboard (only use on linux) -func (a *BufHandler) PastePrimary() bool { +func (h *BufHandler) PastePrimary() bool { return true } // JumpToMatchingBrace moves the cursor to the matching brace if it is // currently on a brace -func (a *BufHandler) JumpToMatchingBrace() bool { +func (h *BufHandler) JumpToMatchingBrace() bool { return true } // SelectAll selects the entire buffer -func (a *BufHandler) SelectAll() bool { +func (h *BufHandler) SelectAll() bool { return true } // OpenFile opens a new file in the buffer -func (a *BufHandler) OpenFile() bool { +func (h *BufHandler) OpenFile() bool { return false } // Start moves the viewport to the start of the buffer -func (a *BufHandler) Start() bool { +func (h *BufHandler) Start() bool { return false } // End moves the viewport to the end of the buffer -func (a *BufHandler) End() bool { +func (h *BufHandler) End() bool { return false } // PageUp scrolls the view up a page -func (a *BufHandler) PageUp() bool { +func (h *BufHandler) PageUp() bool { return false } // PageDown scrolls the view down a page -func (a *BufHandler) PageDown() bool { +func (h *BufHandler) PageDown() bool { return false } // SelectPageUp selects up one page -func (a *BufHandler) SelectPageUp() bool { +func (h *BufHandler) SelectPageUp() bool { return true } // SelectPageDown selects down one page -func (a *BufHandler) SelectPageDown() bool { +func (h *BufHandler) SelectPageDown() bool { return true } // CursorPageUp places the cursor a page up -func (a *BufHandler) CursorPageUp() bool { +func (h *BufHandler) CursorPageUp() bool { return true } // CursorPageDown places the cursor a page up -func (a *BufHandler) CursorPageDown() bool { +func (h *BufHandler) CursorPageDown() bool { return true } // HalfPageUp scrolls the view up half a page -func (a *BufHandler) HalfPageUp() bool { +func (h *BufHandler) HalfPageUp() bool { return false } // HalfPageDown scrolls the view down half a page -func (a *BufHandler) HalfPageDown() bool { +func (h *BufHandler) HalfPageDown() bool { return false } // ToggleRuler turns line numbers off and on -func (a *BufHandler) ToggleRuler() bool { +func (h *BufHandler) ToggleRuler() bool { return false } // JumpLine jumps to a line and moves the view accordingly. -func (a *BufHandler) JumpLine() bool { +func (h *BufHandler) JumpLine() bool { return false } // ClearStatus clears the messenger bar -func (a *BufHandler) ClearStatus() bool { +func (h *BufHandler) ClearStatus() bool { return false } // ToggleHelp toggles the help screen -func (a *BufHandler) ToggleHelp() bool { +func (h *BufHandler) ToggleHelp() bool { return true } // ToggleKeyMenu toggles the keymenu option and resizes all tabs -func (a *BufHandler) ToggleKeyMenu() bool { +func (h *BufHandler) ToggleKeyMenu() bool { return true } // ShellMode opens a terminal to run a shell command -func (a *BufHandler) ShellMode() bool { +func (h *BufHandler) ShellMode() bool { return false } // CommandMode lets the user enter a command -func (a *BufHandler) CommandMode() bool { +func (h *BufHandler) CommandMode() bool { return false } // ToggleOverwriteMode lets the user toggle the text overwrite mode -func (a *BufHandler) ToggleOverwriteMode() bool { +func (h *BufHandler) ToggleOverwriteMode() bool { return false } // Escape leaves current mode -func (a *BufHandler) Escape() bool { +func (h *BufHandler) Escape() bool { return false } // Quit this will close the current tab or view that is open -func (a *BufHandler) Quit() bool { +func (h *BufHandler) Quit() bool { screen.Screen.Fini() os.Exit(0) return false } // QuitAll quits the whole editor; all splits and tabs -func (a *BufHandler) QuitAll() bool { +func (h *BufHandler) QuitAll() bool { return false } // AddTab adds a new tab with an empty buffer -func (a *BufHandler) AddTab() bool { +func (h *BufHandler) AddTab() bool { return true } // PreviousTab switches to the previous tab in the tab list -func (a *BufHandler) PreviousTab() bool { +func (h *BufHandler) PreviousTab() bool { return false } // NextTab switches to the next tab in the tab list -func (a *BufHandler) NextTab() bool { +func (h *BufHandler) NextTab() bool { return false } // VSplitBinding opens an empty vertical split -func (a *BufHandler) VSplitBinding() bool { +func (h *BufHandler) VSplitBinding() bool { return false } // HSplitBinding opens an empty horizontal split -func (a *BufHandler) HSplitBinding() bool { +func (h *BufHandler) HSplitBinding() bool { return false } // Unsplit closes all splits in the current tab except the active one -func (a *BufHandler) Unsplit() bool { +func (h *BufHandler) Unsplit() bool { return false } // NextSplit changes the view to the next split -func (a *BufHandler) NextSplit() bool { +func (h *BufHandler) NextSplit() bool { return false } // PreviousSplit changes the view to the previous split -func (a *BufHandler) PreviousSplit() bool { +func (h *BufHandler) PreviousSplit() bool { return false } @@ -460,41 +563,41 @@ var curMacro []interface{} var recordingMacro bool // ToggleMacro toggles recording of a macro -func (a *BufHandler) ToggleMacro() bool { +func (h *BufHandler) ToggleMacro() bool { return true } // PlayMacro plays back the most recently recorded macro -func (a *BufHandler) PlayMacro() bool { +func (h *BufHandler) PlayMacro() bool { return true } // SpawnMultiCursor creates a new multiple cursor at the next occurrence of the current selection or current word -func (a *BufHandler) SpawnMultiCursor() bool { +func (h *BufHandler) SpawnMultiCursor() bool { return false } // SpawnMultiCursorSelect adds a cursor at the beginning of each line of a selection -func (a *BufHandler) SpawnMultiCursorSelect() bool { +func (h *BufHandler) SpawnMultiCursorSelect() bool { return false } // MouseMultiCursor is a mouse action which puts a new cursor at the mouse position -func (a *BufHandler) MouseMultiCursor(e *tcell.EventMouse) bool { +func (h *BufHandler) MouseMultiCursor(e *tcell.EventMouse) bool { return false } // SkipMultiCursor moves the current multiple cursor to the next available position -func (a *BufHandler) SkipMultiCursor() bool { +func (h *BufHandler) SkipMultiCursor() bool { return false } // RemoveMultiCursor removes the latest multiple cursor -func (a *BufHandler) RemoveMultiCursor() bool { +func (h *BufHandler) RemoveMultiCursor() bool { return false } // RemoveAllMultiCursors removes all cursors except the base cursor -func (a *BufHandler) RemoveAllMultiCursors() bool { +func (h *BufHandler) RemoveAllMultiCursors() bool { return false } diff --git a/cmd/micro/action/bufhandler.go b/cmd/micro/action/bufhandler.go index e6951a2f..4fea0e64 100644 --- a/cmd/micro/action/bufhandler.go +++ b/cmd/micro/action/bufhandler.go @@ -36,6 +36,9 @@ type BufHandler struct { cursors []*buffer.Cursor Cursor *buffer.Cursor // the active cursor + StartLine int // Vertical scrolling + StartCol int // Horizontal scrolling + // Since tcell doesn't differentiate between a mouse release event // and a mouse move event with no keys pressed, we need to keep // track of whether or not the mouse was pressed (or not released) last event to determine @@ -65,22 +68,22 @@ type BufHandler struct { } func NewBufHandler(buf *buffer.Buffer) *BufHandler { - a := new(BufHandler) - a.Buf = buf + h := new(BufHandler) + h.Buf = buf - a.cursors = []*buffer.Cursor{&buffer.Cursor{ + h.cursors = []*buffer.Cursor{&buffer.Cursor{ Buf: buf, Loc: buf.StartCursor, }} - a.Cursor = a.cursors[0] + h.Cursor = h.cursors[0] - buf.SetCursors(a.cursors) - return a + buf.SetCursors(h.cursors) + return h } // HandleEvent executes the tcell event properly // TODO: multiple actions bound to one key -func (a *BufHandler) HandleEvent(event tcell.Event) { +func (h *BufHandler) HandleEvent(event tcell.Event) { switch e := event.(type) { case *tcell.EventKey: ke := KeyEvent{ @@ -88,20 +91,32 @@ func (a *BufHandler) HandleEvent(event tcell.Event) { mod: e.Modifiers(), r: e.Rune(), } - if action, ok := BufKeyBindings[ke]; ok { - action(a) - } + h.DoKeyEvent(ke) case *tcell.EventMouse: me := MouseEvent{ btn: e.Buttons(), mod: e.Modifiers(), } - if action, ok := BufMouseBindings[me]; ok { - action(a, e) - } + h.DoMouseEvent(me, e) } } +func (h *BufHandler) DoKeyEvent(e KeyEvent) bool { + if action, ok := BufKeyBindings[e]; ok { + action(h) + return true + } + return false +} + +func (h *BufHandler) DoMouseEvent(e MouseEvent, te *tcell.EventMouse) bool { + if action, ok := BufMouseBindings[e]; ok { + action(h, te) + return true + } + return false +} + var BufKeyActions = map[string]BufKeyAction{ "CursorUp": (*BufHandler).CursorUp, "CursorDown": (*BufHandler).CursorDown, diff --git a/cmd/micro/action/handler.go b/cmd/micro/action/handler.go index 447fe79e..2a366c1d 100644 --- a/cmd/micro/action/handler.go +++ b/cmd/micro/action/handler.go @@ -31,8 +31,13 @@ type MouseEvent struct { mod tcell.ModMask } +type KeyAction func(Handler) bool +type MouseAction func(Handler, tcell.EventMouse) bool + // A Handler will take a tcell event and execute it // appropriately type Handler interface { + // DoKeyEvent(KeyEvent) bool + // DoMouseEvent(MouseEvent, *tcell.EventMouse) (MouseAction, bool) HandleEvent(tcell.Event) } diff --git a/cmd/micro/buffer/cursor.go b/cmd/micro/buffer/cursor.go index 78faf45b..ce257244 100644 --- a/cmd/micro/buffer/cursor.go +++ b/cmd/micro/buffer/cursor.go @@ -107,6 +107,18 @@ func (c *Cursor) Start() { c.LastVisualX = c.GetVisualX() } +// StartOfText moves the cursor to the first non-whitespace rune of +// the line it is on +func (c *Cursor) StartOfText() { + c.Start() + for util.IsWhitespace(c.RuneUnder(c.X)) { + if c.X == utf8.RuneCount(c.Buf.LineBytes(c.Y)) { + break + } + c.Right() + } +} + // End moves the cursor to the end of the line it is on func (c *Cursor) End() { c.X = utf8.RuneCount(c.Buf.LineBytes(c.Y)) @@ -296,6 +308,143 @@ func (c *Cursor) Relocate() { } } +// SelectWord selects the word the cursor is currently on +func (c *Cursor) SelectWord() { + if len(c.Buf.LineBytes(c.Y)) == 0 { + return + } + + if !util.IsWordChar(c.RuneUnder(c.X)) { + c.SetSelectionStart(c.Loc) + c.SetSelectionEnd(c.Loc.Move(1, c.Buf)) + c.OrigSelection = c.CurSelection + return + } + + forward, backward := c.X, c.X + + for backward > 0 && util.IsWordChar(c.RuneUnder(backward-1)) { + backward-- + } + + c.SetSelectionStart(Loc{backward, c.Y}) + c.OrigSelection[0] = c.CurSelection[0] + + lineLen := utf8.RuneCount(c.Buf.LineBytes(c.Y)) - 1 + for forward < lineLen && util.IsWordChar(c.RuneUnder(forward+1)) { + forward++ + } + + c.SetSelectionEnd(Loc{forward, c.Y}.Move(1, c.Buf)) + c.OrigSelection[1] = c.CurSelection[1] + c.Loc = c.CurSelection[1] +} + +// AddWordToSelection adds the word the cursor is currently on +// to the selection +func (c *Cursor) AddWordToSelection() { + if c.Loc.GreaterThan(c.OrigSelection[0]) && c.Loc.LessThan(c.OrigSelection[1]) { + c.CurSelection = c.OrigSelection + return + } + + if c.Loc.LessThan(c.OrigSelection[0]) { + backward := c.X + + for backward > 0 && util.IsWordChar(c.RuneUnder(backward-1)) { + backward-- + } + + c.SetSelectionStart(Loc{backward, c.Y}) + c.SetSelectionEnd(c.OrigSelection[1]) + } + + if c.Loc.GreaterThan(c.OrigSelection[1]) { + forward := c.X + + lineLen := utf8.RuneCount(c.Buf.LineBytes(c.Y)) - 1 + for forward < lineLen && util.IsWordChar(c.RuneUnder(forward+1)) { + forward++ + } + + c.SetSelectionEnd(Loc{forward, c.Y}.Move(1, c.Buf)) + c.SetSelectionStart(c.OrigSelection[0]) + } + + c.Loc = c.CurSelection[1] +} + +// SelectTo selects from the current cursor location to the given +// location +func (c *Cursor) SelectTo(loc Loc) { + if loc.GreaterThan(c.OrigSelection[0]) { + c.SetSelectionStart(c.OrigSelection[0]) + c.SetSelectionEnd(loc) + } else { + c.SetSelectionStart(loc) + c.SetSelectionEnd(c.OrigSelection[0]) + } +} + +// WordRight moves the cursor one word to the right +func (c *Cursor) WordRight() { + for util.IsWhitespace(c.RuneUnder(c.X)) { + if c.X == utf8.RuneCount(c.Buf.LineBytes(c.Y)) { + c.Right() + return + } + c.Right() + } + c.Right() + for util.IsWordChar(c.RuneUnder(c.X)) { + if c.X == utf8.RuneCount(c.Buf.LineBytes(c.Y)) { + return + } + c.Right() + } +} + +// WordLeft moves the cursor one word to the left +func (c *Cursor) WordLeft() { + c.Left() + for util.IsWhitespace(c.RuneUnder(c.X)) { + if c.X == 0 { + return + } + c.Left() + } + c.Left() + for util.IsWordChar(c.RuneUnder(c.X)) { + if c.X == 0 { + return + } + c.Left() + } + c.Right() +} + +// RuneUnder returns the rune under the given x position +func (c *Cursor) RuneUnder(x int) rune { + line := c.Buf.LineBytes(c.Y) + if len(line) == 0 || x >= utf8.RuneCount(line) { + return '\n' + } else if x < 0 { + x = 0 + } + i := 0 + for len(line) > 0 { + r, size := utf8.DecodeRune(line) + line = line[size:] + + if i == x { + return r + } + + i++ + } + return '\n' +} + func (c *Cursor) StoreVisualX() { c.LastVisualX = c.GetVisualX() } diff --git a/cmd/micro/infobar.go b/cmd/micro/display/infobar.go similarity index 99% rename from cmd/micro/infobar.go rename to cmd/micro/display/infobar.go index 4e231646..15049413 100644 --- a/cmd/micro/infobar.go +++ b/cmd/micro/display/infobar.go @@ -1,4 +1,4 @@ -package main +package display import ( "fmt" diff --git a/cmd/micro/statusline.go b/cmd/micro/display/statusline.go similarity index 97% rename from cmd/micro/statusline.go rename to cmd/micro/display/statusline.go index 0b8f7cab..c98950d1 100644 --- a/cmd/micro/statusline.go +++ b/cmd/micro/display/statusline.go @@ -1,4 +1,4 @@ -package main +package display import ( "bytes" @@ -23,13 +23,13 @@ type StatusLine struct { FormatRight string Info map[string]func(*buffer.Buffer) string - win *Window + win *BufWindow } // TODO: plugin modify status line formatter // NewStatusLine returns a statusline bound to a window -func NewStatusLine(win *Window) *StatusLine { +func NewStatusLine(win *BufWindow) *StatusLine { s := new(StatusLine) s.FormatLeft = "$(filename) $(modified)($(line),$(col)) $(opt:filetype) $(opt:fileformat)" // s.FormatLeft = "$(filename) $(modified)(line,col) $(opt:filetype) $(opt:fileformat)" diff --git a/cmd/micro/window.go b/cmd/micro/display/window.go similarity index 71% rename from cmd/micro/window.go rename to cmd/micro/display/window.go index b9cddaae..404733f5 100644 --- a/cmd/micro/window.go +++ b/cmd/micro/display/window.go @@ -1,4 +1,4 @@ -package main +package display import ( "strconv" @@ -12,7 +12,12 @@ import ( "github.com/zyedidia/tcell" ) -type Window struct { +type Window interface { + Display() + Clear() +} + +type BufWindow struct { // X and Y coordinates for the top left of the window X int Y int @@ -32,8 +37,8 @@ type Window struct { sline *StatusLine } -func NewWindow(x, y, width, height int, buf *buffer.Buffer) *Window { - w := new(Window) +func NewBufWindow(x, y, width, height int, buf *buffer.Buffer) *BufWindow { + w := new(BufWindow) w.X, w.Y, w.Width, w.Height, w.Buf = x, y, width, height, buf w.sline = NewStatusLine(w) @@ -41,7 +46,7 @@ func NewWindow(x, y, width, height int, buf *buffer.Buffer) *Window { return w } -func (w *Window) Clear() { +func (w *BufWindow) Clear() { for y := 0; y < w.Height; y++ { for x := 0; x < w.Width; x++ { screen.Screen.SetContent(w.X+x, w.Y+y, ' ', nil, config.DefStyle) @@ -49,7 +54,7 @@ func (w *Window) Clear() { } } -func (w *Window) DrawLineNum(lineNumStyle tcell.Style, softwrapped bool, maxLineNumLength int, vloc *buffer.Loc, bloc *buffer.Loc) { +func (w *BufWindow) drawLineNum(lineNumStyle tcell.Style, softwrapped bool, maxLineNumLength int, vloc *buffer.Loc, bloc *buffer.Loc) { lineNum := strconv.Itoa(bloc.Y + 1) // Write the spaces before the line number if necessary @@ -72,9 +77,9 @@ func (w *Window) DrawLineNum(lineNumStyle tcell.Style, softwrapped bool, maxLine vloc.X++ } -// GetStyle returns the highlight style for the given character position +// getStyle returns the highlight style for the given character position // If there is no change to the current highlight style it just returns that -func (w *Window) GetStyle(style tcell.Style, bloc buffer.Loc, r rune) tcell.Style { +func (w *BufWindow) getStyle(style tcell.Style, bloc buffer.Loc, r rune) tcell.Style { if group, ok := w.Buf.Match(bloc.Y)[bloc.X]; ok { s := config.GetColor(group.String()) return s @@ -82,7 +87,7 @@ func (w *Window) GetStyle(style tcell.Style, bloc buffer.Loc, r rune) tcell.Styl return style } -func (w *Window) ShowCursor(x, y int, main bool) { +func (w *BufWindow) showCursor(x, y int, main bool) { if main { screen.Screen.ShowCursor(x, y) } else { @@ -91,8 +96,8 @@ func (w *Window) ShowCursor(x, y int, main bool) { } } -// DisplayBuffer draws the buffer being shown in this window on the screen.Screen -func (w *Window) DisplayBuffer() { +// displayBuffer draws the buffer being shown in this window on the screen.Screen +func (w *BufWindow) displayBuffer() { b := w.Buf bufHeight := w.Height @@ -132,30 +137,48 @@ func (w *Window) DisplayBuffer() { // this represents the current draw position in the buffer (char positions) bloc := buffer.Loc{w.StartCol, w.StartLine} + activeC := w.Buf.GetActiveCursor() + curStyle := config.DefStyle for vloc.Y = 0; vloc.Y < bufHeight; vloc.Y++ { vloc.X = 0 if b.Settings["ruler"].(bool) { - w.DrawLineNum(lineNumStyle, false, maxLineNumLength, &vloc, &bloc) + w.drawLineNum(lineNumStyle, false, maxLineNumLength, &vloc, &bloc) } line := b.LineBytes(bloc.Y) line, nColsBeforeStart := util.SliceVisualEnd(line, bloc.X, tabsize) - totalwidth := bloc.X - nColsBeforeStart - for len(line) > 0 { - if w.Buf.GetActiveCursor().X == bloc.X && w.Buf.GetActiveCursor().Y == bloc.Y { - w.ShowCursor(vloc.X, vloc.Y, true) - } - - r, size := utf8.DecodeRune(line) - - curStyle = w.GetStyle(curStyle, bloc, r) + draw := func(r rune, style tcell.Style) { if nColsBeforeStart <= 0 { - screen.Screen.SetContent(w.X+vloc.X, w.Y+vloc.Y, r, nil, curStyle) + if activeC.HasSelection() && + (bloc.GreaterEqual(activeC.CurSelection[0]) && bloc.LessThan(activeC.CurSelection[1]) || + bloc.LessThan(activeC.CurSelection[0]) && bloc.GreaterEqual(activeC.CurSelection[1])) { + // The current character is selected + style = config.DefStyle.Reverse(true) + + if s, ok := config.Colorscheme["selection"]; ok { + style = s + } + + } + + screen.Screen.SetContent(w.X+vloc.X, w.Y+vloc.Y, r, nil, style) vloc.X++ } nColsBeforeStart-- + } + + totalwidth := bloc.X - nColsBeforeStart + for len(line) > 0 { + if activeC.X == bloc.X && activeC.Y == bloc.Y { + w.showCursor(vloc.X, vloc.Y, true) + } + + r, size := utf8.DecodeRune(line) + curStyle = w.getStyle(curStyle, bloc, r) + + draw(r, curStyle) width := 0 @@ -175,11 +198,7 @@ func (w *Window) DisplayBuffer() { // Draw any extra characters either spaces for tabs or @ for incomplete wide runes if width > 1 { for i := 1; i < width; i++ { - if nColsBeforeStart <= 0 { - screen.Screen.SetContent(w.X+vloc.X, w.Y+vloc.Y, char, nil, curStyle) - vloc.X++ - } - nColsBeforeStart-- + draw(char, curStyle) } } totalwidth += width @@ -195,12 +214,12 @@ func (w *Window) DisplayBuffer() { } vloc.X = 0 // This will draw an empty line number because the current line is wrapped - w.DrawLineNum(lineNumStyle, true, maxLineNumLength, &vloc, &bloc) + w.drawLineNum(lineNumStyle, true, maxLineNumLength, &vloc, &bloc) } } } - if w.Buf.GetActiveCursor().X == bloc.X && w.Buf.GetActiveCursor().Y == bloc.Y { - w.ShowCursor(vloc.X, vloc.Y, true) + if activeC.X == bloc.X && activeC.Y == bloc.Y { + w.showCursor(vloc.X, vloc.Y, true) } bloc.X = w.StartCol bloc.Y++ @@ -210,6 +229,11 @@ func (w *Window) DisplayBuffer() { } } -func (w *Window) DisplayStatusLine() { +func (w *BufWindow) displayStatusLine() { w.sline.Display() } + +func (w *BufWindow) Display() { + w.displayBuffer() + w.displayStatusLine() +} diff --git a/cmd/micro/editpane.go b/cmd/micro/editpane.go new file mode 100644 index 00000000..90739fba --- /dev/null +++ b/cmd/micro/editpane.go @@ -0,0 +1,20 @@ +package main + +import ( + "github.com/zyedidia/micro/cmd/micro/action" + "github.com/zyedidia/micro/cmd/micro/buffer" + "github.com/zyedidia/micro/cmd/micro/display" +) + +type EditPane struct { + display.Window + action.Handler +} + +func NewBufEditPane(x, y, width, height int, b *buffer.Buffer) *EditPane { + e := new(EditPane) + e.Window = display.NewBufWindow(x, y, width, height, b) + e.Handler = action.NewBufHandler(b) + + return e +} diff --git a/cmd/micro/lua/lua.go b/cmd/micro/lua/lua.go index a87f4880..7fa44a80 100644 --- a/cmd/micro/lua/lua.go +++ b/cmd/micro/lua/lua.go @@ -78,7 +78,7 @@ func Import(pkg string) *lua.LTable { func importFmt() *lua.LTable { pkg := L.NewTable() - L.SetField(pkg, "tErrorf", luar.New(L, fmt.Errorf)) + L.SetField(pkg, "Errorf", luar.New(L, fmt.Errorf)) L.SetField(pkg, "Fprint", luar.New(L, fmt.Fprint)) L.SetField(pkg, "Fprintf", luar.New(L, fmt.Fprintf)) L.SetField(pkg, "Fprintln", luar.New(L, fmt.Fprintln)) diff --git a/cmd/micro/micro.go b/cmd/micro/micro.go index 05d720ce..86374bac 100644 --- a/cmd/micro/micro.go +++ b/cmd/micro/micro.go @@ -3,9 +3,12 @@ package main import ( "flag" "fmt" + "io/ioutil" "os" + "strings" "github.com/go-errors/errors" + isatty "github.com/mattn/go-isatty" "github.com/zyedidia/micro/cmd/micro/action" "github.com/zyedidia/micro/cmd/micro/buffer" "github.com/zyedidia/micro/cmd/micro/config" @@ -20,8 +23,9 @@ const ( ) var ( - // Version is the version number or commit hash // These variables should be set by the linker when compiling + + // Version is the version number or commit hash Version = "0.0.0-unknown" // CommitHash is the commit this version was built on CommitHash = "Unknown" @@ -34,9 +38,6 @@ var ( events chan tcell.Event autosave chan bool - // How many redraws have happened - numRedraw uint - // Command line flags flagVersion = flag.Bool("version", false, "Show the version number and information") flagStartPos = flag.String("startpos", "", "LINE,COL to start the cursor at when opening a buffer.") @@ -91,6 +92,66 @@ func InitFlags() { } } +// LoadInput determines which files should be loaded into buffers +// based on the input stored in flag.Args() +func LoadInput() []*buffer.Buffer { + // There are a number of ways micro should start given its input + + // 1. If it is given a files in flag.Args(), it should open those + + // 2. If there is no input file and the input is not a terminal, that means + // something is being piped in and the stdin should be opened in an + // empty buffer + + // 3. If there is no input file and the input is a terminal, an empty buffer + // should be opened + + var filename string + var input []byte + var err error + args := flag.Args() + buffers := make([]*buffer.Buffer, 0, len(args)) + + if len(args) > 0 { + // Option 1 + // We go through each file and load it + for i := 0; i < len(args); i++ { + if strings.HasPrefix(args[i], "+") { + if strings.Contains(args[i], ":") { + split := strings.Split(args[i], ":") + *flagStartPos = split[0][1:] + "," + split[1] + } else { + *flagStartPos = args[i][1:] + ",0" + } + continue + } + + buf, err := buffer.NewBufferFromFile(args[i]) + if err != nil { + util.TermMessage(err) + continue + } + // If the file didn't exist, input will be empty, and we'll open an empty buffer + buffers = append(buffers, buf) + } + } 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) + if err != nil { + util.TermMessage("Error reading from stdin: ", err) + input = []byte{} + } + buffers = append(buffers, buffer.NewBufferFromString(string(input), filename)) + } else { + // Option 3, just open an empty buffer + buffers = append(buffers, buffer.NewBufferFromString(string(input), filename)) + } + + return buffers +} + func main() { var err error @@ -127,18 +188,9 @@ func main() { } }() - action.TryBindKey("Ctrl-z", "Undo", true) - - b, err := buffer.NewBufferFromFile(os.Args[1]) - - if err != nil { - util.TermMessage(err) - } - + b := LoadInput()[0] width, height := screen.Screen.Size() - w := NewWindow(0, 0, width, height-1, b) - - a := action.NewBufHandler(b) + ep := NewBufEditPane(0, 0, width, height-1, b) // Here is the event loop which runs in a separate thread go func() { @@ -153,8 +205,7 @@ func main() { for { // Display everything screen.Screen.Fill(' ', config.DefStyle) - w.DisplayBuffer() - w.DisplayStatusLine() + ep.Display() screen.Screen.Show() var event tcell.Event @@ -165,7 +216,7 @@ func main() { } if event != nil { - a.HandleEvent(event) + ep.HandleEvent(event) } } diff --git a/cmd/micro/util/util.go b/cmd/micro/util/util.go index b4dda124..62a20908 100644 --- a/cmd/micro/util/util.go +++ b/cmd/micro/util/util.go @@ -135,13 +135,8 @@ func FSize(f *os.File) int64 { // IsWordChar returns whether or not the string is a 'word character' // If it is a unicode character, then it does not match // Word characters are defined as [A-Za-z0-9_] -func IsWordChar(str string) bool { - if len(str) > 1 { - // Unicode - return true - } - c := str[0] - return (c >= '0' && c <= '9') || (c >= 'A' && c <= 'Z') || (c >= 'a' && c <= 'z') || (c == '_') +func IsWordChar(r rune) bool { + return (r >= '0' && r <= '9') || (r >= 'A' && r <= 'Z') || (r >= 'a' && r <= 'z') || (r == '_') } // IsWhitespace returns true if the given rune is a space, tab, or newline