From 30344968507bc23ec6eba2c9cff52ef3d50c66d3 Mon Sep 17 00:00:00 2001 From: Zachary Yedidia Date: Sat, 16 Apr 2016 09:55:40 -0400 Subject: [PATCH] Improve the search functionality and improve selection --- src/cursor.go | 20 +++++++------- src/messenger.go | 13 +++------ src/micro.go | 63 +++++++++---------------------------------- src/search.go | 70 ++++++++++++++++++++++++++++++++++++++++++++++++ src/view.go | 43 ++++++++++------------------- 5 files changed, 110 insertions(+), 99 deletions(-) create mode 100644 src/search.go diff --git a/src/cursor.go b/src/cursor.go index 05c1a09f..a9229039 100644 --- a/src/cursor.go +++ b/src/cursor.go @@ -76,22 +76,22 @@ func (c *Cursor) Loc() int { // ResetSelection resets the user's selection func (c *Cursor) ResetSelection() { - c.curSelection[0] = -1 - c.curSelection[1] = -1 + c.curSelection[0] = 0 + c.curSelection[1] = 0 } // HasSelection returns whether or not the user has selected anything func (c *Cursor) HasSelection() bool { - return c.curSelection[1] != -1 || c.curSelection[0] != -1 + return c.curSelection[0] != c.curSelection[1] } // DeleteSelection deletes the currently selected text func (c *Cursor) DeleteSelection() { if c.curSelection[0] > c.curSelection[1] { - c.v.eh.Remove(c.curSelection[1], c.curSelection[0]+1) + c.v.eh.Remove(c.curSelection[1], c.curSelection[0]) c.SetLoc(c.curSelection[1]) } else { - c.v.eh.Remove(c.curSelection[0], c.curSelection[1]+1) + c.v.eh.Remove(c.curSelection[0], c.curSelection[1]) c.SetLoc(c.curSelection[0]) } } @@ -99,9 +99,9 @@ func (c *Cursor) DeleteSelection() { // GetSelection returns the cursor's selection func (c *Cursor) GetSelection() string { if c.curSelection[0] > c.curSelection[1] { - return string([]rune(c.v.buf.text)[c.curSelection[1] : c.curSelection[0]+1]) + return string([]rune(c.v.buf.text)[c.curSelection[1]:c.curSelection[0]]) } - return string([]rune(c.v.buf.text)[c.curSelection[0] : c.curSelection[1]+1]) + return string([]rune(c.v.buf.text)[c.curSelection[0]:c.curSelection[1]]) } // SelectLine selects the current line @@ -143,7 +143,7 @@ func (c *Cursor) SelectWord() { if !IsWordChar(string(c.RuneUnder(c.x))) { loc := c.Loc() c.curSelection[0] = loc - c.curSelection[1] = loc + c.curSelection[1] = loc + 1 c.origSelection = c.curSelection return } @@ -161,7 +161,7 @@ func (c *Cursor) SelectWord() { forward++ } - c.curSelection[1] = ToCharPos(forward, c.y, c.v.buf) + c.curSelection[1] = ToCharPos(forward, c.y, c.v.buf) + 1 c.origSelection[1] = c.curSelection[1] } @@ -192,7 +192,7 @@ func (c *Cursor) AddWordToSelection() { forward++ } - c.curSelection[1] = ToCharPos(forward, c.y, c.v.buf) + c.curSelection[1] = ToCharPos(forward, c.y, c.v.buf) + 1 c.curSelection[0] = c.origSelection[0] } } diff --git a/src/messenger.go b/src/messenger.go index ea3ddbdb..df9f3f21 100644 --- a/src/messenger.go +++ b/src/messenger.go @@ -43,8 +43,6 @@ type Messenger struct { // style to use when drawing the message style tcell.Style - realtimePrompt bool - // We have to keep track of the cursor for prompting cursorx int } @@ -112,16 +110,11 @@ func (m *Messenger) Prompt(prompt string) (string, bool) { switch e := event.(type) { case *tcell.EventKey: - if e.Key() == tcell.KeyEscape { + switch e.Key() { + case tcell.KeyCtrlQ, tcell.KeyCtrlC, tcell.KeyEscape: // Cancel m.hasPrompt = false - } else if e.Key() == tcell.KeyCtrlC { - // Cancel - m.hasPrompt = false - } else if e.Key() == tcell.KeyCtrlQ { - // Cancel - m.hasPrompt = false - } else if e.Key() == tcell.KeyEnter { + case tcell.KeyEnter: // User is done entering their response m.hasPrompt = false response, canceled = m.response, false diff --git a/src/micro.go b/src/micro.go index e08aeec4..89025981 100644 --- a/src/micro.go +++ b/src/micro.go @@ -16,13 +16,16 @@ const ( undoThreshold = 500 // If two events are less than n milliseconds apart, undo both of them ) -// The main screen -var screen tcell.Screen +var ( + // The main screen + screen tcell.Screen -// Object to send messages and prompts to the user -var messenger *Messenger + // Object to send messages and prompts to the user + messenger *Messenger -var redrawStatus int + redrawStatus int + searching bool +) // LoadInput loads the file input for the editor func LoadInput() (string, []byte, error) { @@ -138,48 +141,8 @@ func main() { // Wait for the user's action event := screen.PollEvent() - if messenger.realtimePrompt { - switch e := event.(type) { - case *tcell.EventKey: - if e.Key() == tcell.KeyEscape { - // Cancel - messenger.hasPrompt = false - messenger.realtimePrompt = false - messenger.Clear() - messenger.Reset() - continue - } else if e.Key() == tcell.KeyCtrlC { - // Cancel - messenger.hasPrompt = false - messenger.realtimePrompt = false - messenger.Clear() - messenger.Reset() - continue - } else if e.Key() == tcell.KeyCtrlQ { - // Cancel - messenger.hasPrompt = false - messenger.realtimePrompt = false - messenger.Clear() - messenger.Reset() - continue - } else if e.Key() == tcell.KeyEnter { - // User is done entering their response - messenger.hasPrompt = false - messenger.realtimePrompt = false - messenger.Clear() - messenger.Reset() - continue - } - } - if messenger.cursorx < 0 { - // Cancel - messenger.realtimePrompt = false - messenger.hasPrompt = false - messenger.Clear() - messenger.Reset() - continue - } - messenger.HandleEvent(event) + if searching { + HandleSearchEvent(event, view) } else { // Check if we should quit switch e := event.(type) { @@ -202,9 +165,9 @@ func main() { view.Resize(screen.Size()) } } - } - // Send it to the view - view.HandleEvent(event) + // Send it to the view + view.HandleEvent(event) + } } } diff --git a/src/search.go b/src/search.go new file mode 100644 index 00000000..de96b6b5 --- /dev/null +++ b/src/search.go @@ -0,0 +1,70 @@ +package main + +import ( + "github.com/gdamore/tcell" + "regexp" + "strings" +) + +func BeginSearch() { + searching = true + messenger.hasPrompt = true + messenger.Message("Find: ") +} + +func EndSearch() { + searching = false + messenger.hasPrompt = false + messenger.Clear() + messenger.Reset() +} + +func HandleSearchEvent(event tcell.Event, v *View) { + switch e := event.(type) { + case *tcell.EventKey: + switch e.Key() { + case tcell.KeyCtrlQ, tcell.KeyCtrlC, tcell.KeyEscape, tcell.KeyEnter: + // Done + EndSearch() + return + } + } + + messenger.HandleEvent(event) + + if messenger.cursorx < 0 { + // Done + EndSearch() + return + } + + if messenger.response == "" { + v.cursor.ResetSelection() + // We don't end the search though + return + } + + str := strings.Join(v.buf.lines[v.cursor.y:], "\n") + charPos := ToCharPos(0, v.cursor.y, v.buf) + r, err := regexp.Compile(messenger.response) + if err != nil { + return + } + match := r.FindStringIndex(str) + if match == nil { + str = strings.Join(v.buf.lines[:v.cursor.y], "\n") + match = r.FindStringIndex(str) + charPos = 0 + if match == nil { + v.cursor.ResetSelection() + return + } + } + v.cursor.curSelection[0] = charPos + match[0] + v.cursor.curSelection[1] = charPos + match[1] + v.cursor.x, v.cursor.y = FromCharPos(charPos+match[1]-1, v.buf) + if v.Relocate() { + v.matches = Match(v) + } + return +} diff --git a/src/view.go b/src/view.go index fbdd596e..dfc1a31a 100644 --- a/src/view.go +++ b/src/view.go @@ -4,7 +4,6 @@ import ( "github.com/atotto/clipboard" "github.com/gdamore/tcell" "io/ioutil" - "regexp" "strconv" "strings" "time" @@ -89,6 +88,7 @@ func NewViewWidthHeight(buf *Buffer, w, h int) *View { y: 0, v: v, } + v.cursor.ResetSelection() v.eh = NewEventHandler(v) @@ -293,22 +293,28 @@ func (v *View) OpenFile() { // Relocate moves the view window so that the cursor is in view // This is useful if the user has scrolled far away, and then starts typing -func (v *View) Relocate() { +func (v *View) Relocate() bool { + ret := false cy := v.cursor.y if cy < v.topline { v.topline = cy + ret = true } if cy > v.topline+v.height-1 { v.topline = cy - v.height + 1 + ret = true } cx := v.cursor.GetVisualX() if cx < v.leftCol { v.leftCol = cx + ret = true } if cx+v.lineNumOffset+1 > v.leftCol+v.width { v.leftCol = cx - v.width + v.lineNumOffset + 1 + ret = true } + return ret } // MoveToMouseClick moves the cursor to location x, y assuming x, y were given @@ -340,25 +346,6 @@ func (v *View) HandleEvent(event tcell.Event) { // By default it's true because most events should cause a relocate relocate := true - if messenger.realtimePrompt { - str := strings.Join(v.buf.lines[v.cursor.y:], "\n") - charPos := ToCharPos(0, v.cursor.y, v.buf) - r, err := regexp.Compile(messenger.response) - if err != nil { - return - } - match := r.FindStringIndex(str) - if match == nil { - v.cursor.ResetSelection() - return - } - v.cursor.curSelection[0] = charPos + match[0] - v.cursor.curSelection[1] = charPos + match[1] - 1 - v.cursor.x, v.cursor.y = FromCharPos(charPos+match[1]-1, v.buf) - v.Relocate() - return - } - // By default we don't update and syntax highlighting v.UpdateLines(-2, 0) switch e := event.(type) { @@ -424,9 +411,7 @@ func (v *View) HandleEvent(event tcell.Event) { case tcell.KeyCtrlS: v.Save() case tcell.KeyCtrlF: - messenger.realtimePrompt = true - messenger.hasPrompt = true - messenger.Message("Find: ") + BeginSearch() case tcell.KeyCtrlZ: v.eh.Undo() // Rehighlight the entire buffer @@ -529,7 +514,7 @@ func (v *View) HandleEvent(event tcell.Event) { } else if v.doubleClick { v.cursor.AddWordToSelection() } else { - v.cursor.curSelection[1] = v.cursor.Loc() - 1 + v.cursor.curSelection[1] = v.cursor.Loc() } } v.mouseReleased = false @@ -656,8 +641,8 @@ func (v *View) DisplayView() { // } if v.cursor.HasSelection() && - (charNum >= v.cursor.curSelection[0] && charNum <= v.cursor.curSelection[1] || - charNum <= v.cursor.curSelection[0] && charNum >= v.cursor.curSelection[1]) { + (charNum >= v.cursor.curSelection[0] && charNum < v.cursor.curSelection[1] || + charNum < v.cursor.curSelection[0] && charNum >= v.cursor.curSelection[1]) { lineStyle = tcell.StyleDefault.Reverse(true) @@ -691,8 +676,8 @@ func (v *View) DisplayView() { // The newline may be selected, in which case we should draw the selection style // with a space to represent it if v.cursor.HasSelection() && - (charNum >= v.cursor.curSelection[0] && charNum <= v.cursor.curSelection[1] || - charNum <= v.cursor.curSelection[0] && charNum >= v.cursor.curSelection[1]) { + (charNum >= v.cursor.curSelection[0] && charNum < v.cursor.curSelection[1] || + charNum < v.cursor.curSelection[0] && charNum >= v.cursor.curSelection[1]) { selectStyle := tcell.StyleDefault.Reverse(true)