Implement searching

This commit is contained in:
Zachary Yedidia
2019-01-03 15:27:43 -05:00
parent f63c72c50d
commit e63a3c8917
10 changed files with 348 additions and 25 deletions

View File

@@ -1,6 +1,7 @@
package action
import (
"log"
"os"
"strings"
"time"
@@ -560,6 +561,37 @@ func (h *BufHandler) SaveAs() bool {
// Find opens a prompt and searches forward for the input
func (h *BufHandler) Find() bool {
InfoBar.Prompt("Find: ", "", func(resp string) {
match, found, _ := h.Buf.FindNext(resp, h.Cursor.Loc, true)
if found {
h.Cursor.SetSelectionStart(match[0])
h.Cursor.SetSelectionEnd(match[1])
h.Cursor.OrigSelection[0] = h.Cursor.CurSelection[0]
h.Cursor.OrigSelection[1] = h.Cursor.CurSelection[1]
} else {
log.Println("RESET")
h.Cursor.ResetSelection()
}
}, func(resp string, canceled bool) {
if !canceled {
match, found, err := h.Buf.FindNext(resp, h.Cursor.Loc, true)
if err != nil {
InfoBar.Error(err)
}
if found {
h.Cursor.SetSelectionStart(match[0])
h.Cursor.SetSelectionEnd(match[1])
h.Cursor.OrigSelection[0] = h.Cursor.CurSelection[0]
h.Cursor.OrigSelection[1] = h.Cursor.CurSelection[1]
h.Cursor.Loc = h.Cursor.CurSelection[1]
} else {
h.Cursor.ResetSelection()
}
} else {
h.Cursor.ResetSelection()
}
})
return true
}
@@ -725,7 +757,7 @@ func (h *BufHandler) SelectAll() bool {
// OpenFile opens a new file in the buffer
func (h *BufHandler) OpenFile() bool {
InfoBar.Prompt("> ", "open ", func(resp string, canceled bool) {
InfoBar.Prompt("> ", "open ", nil, func(resp string, canceled bool) {
if !canceled {
HandleCommand(resp)
}
@@ -889,7 +921,7 @@ func (h *BufHandler) ShellMode() bool {
// CommandMode lets the user enter a command
func (h *BufHandler) CommandMode() bool {
InfoBar.Prompt("> ", "", func(resp string, canceled bool) {
InfoBar.Prompt("> ", "", nil, func(resp string, canceled bool) {
if !canceled {
HandleCommand(resp)
}

View File

@@ -103,7 +103,8 @@ modSearch:
k = string(unicode.ToUpper(rune(k[0]))) + k[1:]
if code, ok := keyEvents["Ctrl"+k]; ok {
var r tcell.Key
if code < 256 {
// Special case for escape, for some reason tcell doesn't send it with the esc character
if code < 256 && code != 27 {
r = code
}
// It is, we're done.
@@ -118,7 +119,8 @@ modSearch:
// See if we can find the key in bindingKeys
if code, ok := keyEvents[k]; ok {
var r tcell.Key
if code < 256 {
// Special case for escape, for some reason tcell doesn't send it with the esc character
if code < 256 && code != 27 {
r = code
}
return KeyEvent{

View File

@@ -22,6 +22,7 @@ func init() {
BufMouseBindings = make(map[MouseEvent]BufMouseAction)
}
// BufMapKey maps a key event to an action
func BufMapKey(k KeyEvent, action string) {
if f, ok := BufKeyActions[action]; ok {
BufKeyStrings[k] = action
@@ -30,6 +31,8 @@ func BufMapKey(k KeyEvent, action string) {
util.TermMessage("Error:", action, "does not exist")
}
}
// BufMapMouse maps a mouse event to an action
func BufMapMouse(k MouseEvent, action string) {
if f, ok := BufMouseActions[action]; ok {
BufMouseBindings[k] = f
@@ -147,10 +150,13 @@ func (h *BufHandler) HandleEvent(event tcell.Event) {
}
}
// DoKeyEvent executes a key event by finding the action it is bound
// to and executing it (possibly multiple times for multiple cursors)
func (h *BufHandler) DoKeyEvent(e KeyEvent) bool {
if action, ok := BufKeyBindings[e]; ok {
for _, a := range MultiActions {
if a == BufKeyStrings[e] {
estr := BufKeyStrings[e]
for _, s := range MultiActions {
if s == estr {
cursors := h.Buf.GetCursors()
for _, c := range cursors {
h.Buf.SetCurCursor(c.Num)
@@ -170,6 +176,8 @@ func (h *BufHandler) DoKeyEvent(e KeyEvent) bool {
return false
}
// DoMouseEvent executes a mouse event by finding the action it is bound
// to and executing it
func (h *BufHandler) DoMouseEvent(e MouseEvent, te *tcell.EventMouse) bool {
if action, ok := BufMouseBindings[e]; ok {
if action(h, te) {
@@ -180,6 +188,8 @@ func (h *BufHandler) DoMouseEvent(e MouseEvent, te *tcell.EventMouse) bool {
return false
}
// DoRuneInsert inserts a given rune into the current buffer
// (possibly multiple times for multiple cursors)
func (h *BufHandler) DoRuneInsert(r rune) {
cursors := h.Buf.GetCursors()
for _, c := range cursors {
@@ -199,6 +209,7 @@ func (h *BufHandler) DoRuneInsert(r rune) {
}
}
// BufKeyActions contains the list of all possible key actions the bufhandler could execute
var BufKeyActions = map[string]BufKeyAction{
"CursorUp": (*BufHandler).CursorUp,
"CursorDown": (*BufHandler).CursorDown,
@@ -297,13 +308,13 @@ var BufKeyActions = map[string]BufKeyAction{
// This was changed to InsertNewline but I don't want to break backwards compatibility
"InsertEnter": (*BufHandler).InsertNewline,
}
// BufMouseActions contains the list of all possible mouse actions the bufhandler could execute
var BufMouseActions = map[string]BufMouseAction{
"MousePress": (*BufHandler).MousePress,
"MouseMultiCursor": (*BufHandler).MouseMultiCursor,
}
const funcPrefixLen = 21 // length of "action.(*BufHandler)."
// MultiActions is a list of actions that should be executed multiple
// times if there are multiple cursors (one per cursor)
// Generally actions that modify global editor state like quitting or

View File

@@ -119,7 +119,7 @@ func DefaultCommands() map[string]StrCommand {
// enter
func CommandEditAction(prompt string) BufKeyAction {
return func(h *BufHandler) bool {
InfoBar.Prompt("> ", prompt, func(resp string, canceled bool) {
InfoBar.Prompt("> ", prompt, nil, func(resp string, canceled bool) {
if !canceled {
HandleCommand(resp)
}

View File

@@ -8,12 +8,12 @@ import (
type EditPane struct {
display.Window
Handler
*BufHandler
}
type InfoPane struct {
display.Window
Handler
*InfoHandler
*info.InfoBuf
}
@@ -22,7 +22,7 @@ func NewBufEditPane(x, y, width, height int, b *buffer.Buffer) *EditPane {
// TODO: can probably replace editpane with bufhandler entirely
w := display.NewBufWindow(x, y, width, height, b)
e.Window = w
e.Handler = NewBufHandler(b, w)
e.BufHandler = NewBufHandler(b, w)
return e
}
@@ -32,7 +32,7 @@ func NewInfoBar() *InfoPane {
ib := info.NewBuffer()
w := display.NewInfoWindow(ib)
e.Window = w
e.Handler = NewBufHandler(ib.Buffer, w)
e.InfoHandler = NewInfoHandler(ib, w)
e.InfoBuf = ib
return e

View File

@@ -0,0 +1,158 @@
package action
import (
"strings"
"github.com/zyedidia/micro/cmd/micro/display"
"github.com/zyedidia/micro/cmd/micro/info"
"github.com/zyedidia/tcell"
)
type InfoKeyAction func(*InfoHandler)
type InfoHandler struct {
*BufHandler
*info.InfoBuf
}
func NewInfoHandler(ib *info.InfoBuf, w display.Window) *InfoHandler {
ih := new(InfoHandler)
ih.InfoBuf = ib
ih.BufHandler = NewBufHandler(ib.Buffer, w)
return ih
}
func (h *InfoHandler) HandleEvent(event tcell.Event) {
switch e := event.(type) {
case *tcell.EventKey:
ke := KeyEvent{
code: e.Key(),
mod: e.Modifiers(),
r: e.Rune(),
}
done := h.DoKeyEvent(ke)
if !done && e.Key() == tcell.KeyRune {
h.DoRuneInsert(e.Rune())
}
case *tcell.EventMouse:
h.BufHandler.HandleEvent(event)
}
}
func (h *InfoHandler) DoKeyEvent(e KeyEvent) bool {
done := false
if action, ok := BufKeyBindings[e]; ok {
estr := BufKeyStrings[e]
for _, s := range InfoNones {
if s == estr {
return false
}
}
for s, a := range InfoOverrides {
if s == estr {
done = true
a(h)
break
}
}
if !done {
done = action(h.BufHandler)
}
}
if done && h.EventCallback != nil {
h.EventCallback(strings.TrimSpace(string(h.LineBytes(0))))
}
return done
}
func (h *InfoHandler) DoRuneInsert(r rune) {
h.BufHandler.DoRuneInsert(r)
if h.EventCallback != nil {
h.EventCallback(strings.TrimSpace(string(h.LineBytes(0))))
}
}
// InfoNones is a list of actions that should have no effect when executed
// by an infohandler
var InfoNones = []string{
"Save",
"SaveAll",
"SaveAs",
"Find",
"FindNext",
"FindPrevious",
"Center",
"DuplicateLine",
"MoveLinesUp",
"MoveLinesDown",
"OpenFile",
"Start",
"End",
"PageUp",
"PageDown",
"SelectPageUp",
"SelectPageDown",
"HalfPageUp",
"HalfPageDown",
"ToggleHelp",
"ToggleKeyMenu",
"ToggleRuler",
"JumpLine",
"ClearStatus",
"ShellMode",
"CommandMode",
"AddTab",
"PreviousTab",
"NextTab",
"NextSplit",
"PreviousSplit",
"Unsplit",
"VSplit",
"HSplit",
"ToggleMacro",
"PlayMacro",
"Suspend",
"ScrollUp",
"ScrollDown",
"SpawnMultiCursor",
"SpawnMultiCursorSelect",
"RemoveMultiCursor",
"RemoveAllMultiCursors",
"SkipMultiCursor",
}
// InfoOverrides is the list of actions which have been overriden
// by the infohandler
var InfoOverrides = map[string]InfoKeyAction{
"CursorUp": (*InfoHandler).CursorUp,
"CursorDown": (*InfoHandler).CursorDown,
"InsertNewline": (*InfoHandler).InsertNewline,
"InsertTab": (*InfoHandler).InsertTab,
"Escape": (*InfoHandler).Escape,
"Quit": (*InfoHandler).Quit,
"QuitAll": (*InfoHandler).QuitAll,
}
func (h *InfoHandler) CursorUp() {
// TODO: history
}
func (h *InfoHandler) CursorDown() {
// TODO: history
}
func (h *InfoHandler) InsertTab() {
// TODO: autocomplete
}
func (h *InfoHandler) InsertNewline() {
h.DonePrompt(false)
}
func (h *InfoHandler) Quit() {
h.DonePrompt(true)
}
func (h *InfoHandler) QuitAll() {
h.DonePrompt(true)
}
func (h *InfoHandler) Escape() {
h.DonePrompt(true)
}

View File

@@ -0,0 +1,93 @@
package buffer
import (
"regexp"
"unicode/utf8"
"github.com/zyedidia/micro/cmd/micro/util"
)
func (b *Buffer) findDown(r *regexp.Regexp, start, end Loc) ([2]Loc, bool) {
start.Y = util.Clamp(start.Y, 0, b.LinesNum()-1)
for i := start.Y; i <= end.Y; i++ {
l := b.LineBytes(i)
charpos := 0
if i == start.Y {
nchars := utf8.RuneCount(l)
start.X = util.Clamp(start.X, 0, nchars-1)
l = util.SliceEnd(l, start.X)
charpos = start.X
}
match := r.FindIndex(l)
if match != nil {
start := Loc{charpos + util.RunePos(l, match[0]), i}
end := Loc{charpos + util.RunePos(l, match[1]), i}
return [2]Loc{start, end}, true
}
}
return [2]Loc{}, false
}
func (b *Buffer) findUp(r *regexp.Regexp, start, end Loc) ([2]Loc, bool) {
start.Y = util.Clamp(start.Y, 0, b.LinesNum()-1)
for i := start.Y; i >= end.Y; i-- {
l := b.LineBytes(i)
if i == start.Y {
nchars := utf8.RuneCount(l)
start.X = util.Clamp(start.X, 0, nchars-1)
l = util.SliceStart(l, start.X)
}
match := r.FindIndex(l)
if match != nil {
start := Loc{util.RunePos(l, match[0]), i}
end := Loc{util.RunePos(l, match[1]), i}
return [2]Loc{start, end}, true
}
}
return [2]Loc{}, false
}
// FindNext finds the next occurrence of a given string in the buffer
// It returns the start and end location of the match (if found) and
// a boolean indicating if it was found
// May also return an error if the search regex is invalid
func (b *Buffer) FindNext(s string, from Loc, down bool) ([2]Loc, bool, error) {
if s == "" {
return [2]Loc{}, false, nil
}
var r *regexp.Regexp
var err error
if b.Settings["ignorecase"].(bool) {
r, err = regexp.Compile("(?i)" + s)
} else {
r, err = regexp.Compile(s)
}
if err != nil {
return [2]Loc{}, false, err
}
found := false
var l [2]Loc
if down {
l, found = b.findDown(r, from, b.End())
if !found {
l, found = b.findDown(r, b.Start(), from)
}
} else {
l, found = b.findUp(r, from, b.Start())
if !found {
l, found = b.findUp(r, b.End(), from)
}
}
return l, found, nil
}

View File

@@ -317,7 +317,7 @@ func (w *BufWindow) getStyle(style tcell.Style, bloc buffer.Loc, r rune) (tcell.
}
func (w *BufWindow) showCursor(x, y int, main bool) {
if !main {
if main {
screen.Screen.ShowCursor(x, y)
} else {
r, _, _, _ := screen.Screen.GetContent(x, y)
@@ -421,8 +421,8 @@ func (w *BufWindow) displayBuffer() {
if showcursor {
for _, c := range cursors {
if c.X == bloc.X && c.Y == bloc.Y {
w.showCursor(w.X+vloc.X, w.Y+vloc.Y, true)
if c.X == bloc.X && c.Y == bloc.Y && !c.HasSelection() {
w.showCursor(w.X+vloc.X, w.Y+vloc.Y, c.Num == 0)
}
}
}
@@ -495,8 +495,8 @@ func (w *BufWindow) displayBuffer() {
}
for _, c := range cursors {
if c.X == bloc.X && c.Y == bloc.Y {
w.showCursor(w.X+vloc.X, w.Y+vloc.Y, true)
if c.X == bloc.X && c.Y == bloc.Y && !c.HasSelection() {
w.showCursor(w.X+vloc.X, w.Y+vloc.Y, c.Num == 0)
}
}

View File

@@ -27,8 +27,10 @@ type InfoBuf struct {
GutterMessage bool
PromptCallback func(resp string, canceled bool)
EventCallback func(resp string)
}
// NewBuffer returns a new infobuffer
func NewBuffer() *InfoBuf {
ib := new(InfoBuf)
ib.History = make(map[string][]string)
@@ -62,7 +64,11 @@ func (i *InfoBuf) Error(msg ...interface{}) {
// TODO: add to log?
}
func (i *InfoBuf) Prompt(prompt string, msg string, callback func(string, bool)) {
// Prompt starts a prompt for the user, it takes a prompt, a possibly partially filled in msg
// and callbacks executed when the user executes an event and when the user finishes the prompt
// The eventcb passes the current user response as the argument and donecb passes the user's message
// and a boolean indicating if the prompt was canceled
func (i *InfoBuf) Prompt(prompt string, msg string, eventcb func(string), donecb func(string, bool)) {
// If we get another prompt mid-prompt we cancel the one getting overwritten
if i.HasPrompt {
i.DonePrompt(true)
@@ -71,21 +77,27 @@ func (i *InfoBuf) Prompt(prompt string, msg string, callback func(string, bool))
i.Msg = prompt
i.HasPrompt = true
i.HasMessage, i.HasError = false, false
i.PromptCallback = callback
i.PromptCallback = donecb
i.EventCallback = eventcb
i.Buffer.Insert(i.Buffer.Start(), msg)
}
// DonePrompt finishes the current prompt and indicates whether or not it was canceled
func (i *InfoBuf) DonePrompt(canceled bool) {
i.HasPrompt = false
if canceled {
i.PromptCallback("", true)
} else {
i.PromptCallback(strings.TrimSpace(string(i.LineBytes(0))), false)
if i.PromptCallback != nil {
if canceled {
i.PromptCallback("", true)
} else {
i.PromptCallback(strings.TrimSpace(string(i.LineBytes(0))), false)
}
}
i.PromptCallback = nil
i.EventCallback = nil
i.Replace(i.Start(), i.End(), "")
}
// Reset resets the messenger's cursor, message and response
// Reset resets the infobuffer's msg and info
func (i *InfoBuf) Reset() {
i.Msg = ""
i.HasPrompt, i.HasMessage, i.HasError = false, false, false

View File

@@ -188,6 +188,12 @@ func IsStrWhitespace(str string) bool {
return true
}
// RunePos returns the rune index of a given byte index
// Make sure the byte index is not between code points
func RunePos(b []byte, i int) int {
return utf8.RuneCount(b[:i])
}
// TODO: consider changing because of snap segfault
// ReplaceHome takes a path as input and replaces ~ at the start of the path with the user's
// home directory. Does nothing if the path does not start with '~'.
@@ -302,3 +308,12 @@ func GetCharPosInLine(b []byte, visualPos int, tabsize int) int {
return i
}
func Clamp(val, min, max int) int {
if val < min {
val = min
} else if val > max {
val = max
}
return val
}