Improve the search functionality and improve selection

This commit is contained in:
Zachary Yedidia
2016-04-16 09:55:40 -04:00
parent 139bc3cda2
commit 3034496850
5 changed files with 110 additions and 99 deletions

View File

@@ -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]
}
}

View File

@@ -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

View File

@@ -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)
}
}
}

70
src/search.go Normal file
View File

@@ -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
}

View File

@@ -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)