Add search and replace
This commit is contained in:
parent
df968db5a3
commit
a3885bfb12
5 changed files with 212 additions and 27 deletions
|
@ -552,7 +552,7 @@ func (h *BufHandler) SaveAs() bool {
|
|||
func (h *BufHandler) Find() bool {
|
||||
InfoBar.Prompt("Find: ", "", "Find", func(resp string) {
|
||||
// Event callback
|
||||
match, found, _ := h.Buf.FindNext(resp, h.Cursor.Loc, true)
|
||||
match, found, _ := h.Buf.FindNext(resp, h.Buf.Start(), h.Buf.End(), h.Cursor.Loc, true, true)
|
||||
if found {
|
||||
h.Cursor.SetSelectionStart(match[0])
|
||||
h.Cursor.SetSelectionEnd(match[1])
|
||||
|
@ -564,7 +564,7 @@ func (h *BufHandler) Find() bool {
|
|||
}, func(resp string, canceled bool) {
|
||||
// Finished callback
|
||||
if !canceled {
|
||||
match, found, err := h.Buf.FindNext(resp, h.Cursor.Loc, true)
|
||||
match, found, err := h.Buf.FindNext(resp, h.Buf.Start(), h.Buf.End(), h.Cursor.Loc, true, true)
|
||||
if err != nil {
|
||||
InfoBar.Error(err)
|
||||
}
|
||||
|
@ -597,7 +597,7 @@ func (h *BufHandler) FindNext() bool {
|
|||
if h.Cursor.HasSelection() {
|
||||
searchLoc = h.Cursor.CurSelection[1]
|
||||
}
|
||||
match, found, err := h.Buf.FindNext(h.lastSearch, searchLoc, true)
|
||||
match, found, err := h.Buf.FindNext(h.lastSearch, h.Buf.Start(), h.Buf.End(), searchLoc, true, true)
|
||||
if err != nil {
|
||||
InfoBar.Error(err)
|
||||
}
|
||||
|
@ -623,7 +623,7 @@ func (h *BufHandler) FindPrevious() bool {
|
|||
if h.Cursor.HasSelection() {
|
||||
searchLoc = h.Cursor.CurSelection[0]
|
||||
}
|
||||
match, found, err := h.Buf.FindNext(h.lastSearch, searchLoc, false)
|
||||
match, found, err := h.Buf.FindNext(h.lastSearch, h.Buf.Start(), h.Buf.End(), searchLoc, false, true)
|
||||
if err != nil {
|
||||
InfoBar.Error(err)
|
||||
}
|
||||
|
@ -1189,7 +1189,7 @@ func (h *BufHandler) SpawnMultiCursor() bool {
|
|||
if h.multiWord {
|
||||
search = "\\b" + search + "\\b"
|
||||
}
|
||||
match, found, err := h.Buf.FindNext(search, searchStart, true)
|
||||
match, found, err := h.Buf.FindNext(search, h.Buf.Start(), h.Buf.End(), searchStart, true, false)
|
||||
if err != nil {
|
||||
InfoBar.Error(err)
|
||||
}
|
||||
|
@ -1262,7 +1262,7 @@ func (h *BufHandler) SkipMultiCursor() bool {
|
|||
sel := lastC.GetSelection()
|
||||
searchStart := lastC.CurSelection[1]
|
||||
|
||||
match, found, err := h.Buf.FindNext(string(sel), searchStart, true)
|
||||
match, found, err := h.Buf.FindNext(string(sel), h.Buf.Start(), h.Buf.End(), searchStart, true, false)
|
||||
if err != nil {
|
||||
InfoBar.Error(err)
|
||||
}
|
||||
|
|
|
@ -5,8 +5,10 @@ import (
|
|||
"fmt"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"regexp"
|
||||
"strconv"
|
||||
"strings"
|
||||
"unicode/utf8"
|
||||
|
||||
"github.com/zyedidia/micro/cmd/micro/buffer"
|
||||
"github.com/zyedidia/micro/cmd/micro/config"
|
||||
|
@ -540,6 +542,115 @@ func (h *BufHandler) SaveCmd(args []string) {
|
|||
|
||||
// ReplaceCmd runs search and replace
|
||||
func (h *BufHandler) ReplaceCmd(args []string) {
|
||||
if len(args) < 2 || len(args) > 4 {
|
||||
// We need to find both a search and replace expression
|
||||
InfoBar.Error("Invalid replace statement: " + strings.Join(args, " "))
|
||||
return
|
||||
}
|
||||
|
||||
all := false
|
||||
noRegex := false
|
||||
|
||||
if len(args) > 2 {
|
||||
for _, arg := range args[2:] {
|
||||
switch arg {
|
||||
case "-a":
|
||||
all = true
|
||||
case "-l":
|
||||
noRegex = true
|
||||
default:
|
||||
InfoBar.Error("Invalid flag: " + arg)
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
search := args[0]
|
||||
|
||||
if noRegex {
|
||||
search = regexp.QuoteMeta(search)
|
||||
}
|
||||
|
||||
replace := []byte(args[1])
|
||||
|
||||
var regex *regexp.Regexp
|
||||
var err error
|
||||
if h.Buf.Settings["ignorecase"].(bool) {
|
||||
regex, err = regexp.Compile("(?im)" + search)
|
||||
} else {
|
||||
regex, err = regexp.Compile("(?m)" + search)
|
||||
}
|
||||
if err != nil {
|
||||
// There was an error with the user's regex
|
||||
InfoBar.Error(err)
|
||||
return
|
||||
}
|
||||
|
||||
nreplaced := 0
|
||||
start := h.Buf.Start()
|
||||
end := h.Buf.End()
|
||||
if h.Cursor.HasSelection() {
|
||||
start = h.Cursor.CurSelection[0]
|
||||
end = h.Cursor.CurSelection[1]
|
||||
}
|
||||
if all {
|
||||
nreplaced = h.Buf.ReplaceRegex(start, end, regex, replace)
|
||||
} else {
|
||||
inRange := func(l buffer.Loc) bool {
|
||||
return l.GreaterEqual(start) && l.LessThan(end)
|
||||
}
|
||||
|
||||
searchLoc := start
|
||||
searching := true
|
||||
var doReplacement func()
|
||||
doReplacement = func() {
|
||||
locs, found, err := h.Buf.FindNext(search, start, end, searchLoc, true, !noRegex)
|
||||
if err != nil {
|
||||
InfoBar.Error(err)
|
||||
return
|
||||
}
|
||||
if !found || !inRange(locs[0]) || !inRange(locs[1]) {
|
||||
h.Cursor.ResetSelection()
|
||||
h.Cursor.Relocate()
|
||||
return
|
||||
}
|
||||
|
||||
h.Cursor.SetSelectionStart(locs[0])
|
||||
h.Cursor.SetSelectionEnd(locs[1])
|
||||
|
||||
InfoBar.YNPrompt("Perform replacement (y,n,esc)", func(yes, canceled bool) {
|
||||
if !canceled && yes {
|
||||
h.Buf.Replace(locs[0], locs[1], replace)
|
||||
searchLoc = locs[0]
|
||||
searchLoc.X += utf8.RuneCount(replace)
|
||||
h.Cursor.Loc = searchLoc
|
||||
nreplaced++
|
||||
} else if !canceled && !yes {
|
||||
searchLoc = locs[0]
|
||||
searchLoc.X += utf8.RuneCount(replace)
|
||||
} else if canceled {
|
||||
h.Cursor.ResetSelection()
|
||||
h.Cursor.Relocate()
|
||||
return
|
||||
}
|
||||
if searching {
|
||||
doReplacement()
|
||||
}
|
||||
})
|
||||
}
|
||||
doReplacement()
|
||||
}
|
||||
|
||||
// TODO: relocate all cursors?
|
||||
h.Cursor.Relocate()
|
||||
|
||||
if nreplaced > 1 {
|
||||
InfoBar.Message("Replaced ", nreplaced, " occurrences of ", search)
|
||||
} else if nreplaced == 1 {
|
||||
InfoBar.Message("Replaced ", nreplaced, " occurrence of ", search)
|
||||
} else {
|
||||
InfoBar.Message("Nothing matched ", search)
|
||||
}
|
||||
}
|
||||
|
||||
// ReplaceAllCmd replaces search term all at once
|
||||
|
|
|
@ -33,20 +33,21 @@ func (h *InfoHandler) HandleEvent(event tcell.Event) {
|
|||
}
|
||||
|
||||
done := h.DoKeyEvent(ke)
|
||||
if e.Key() == tcell.KeyRune && h.HasYN {
|
||||
if e.Rune() == 'y' && h.HasYN {
|
||||
hasYN := h.HasYN
|
||||
if e.Key() == tcell.KeyRune && hasYN {
|
||||
if e.Rune() == 'y' && hasYN {
|
||||
h.YNResp = true
|
||||
h.DonePrompt(false)
|
||||
} else if e.Rune() == 'n' && h.HasYN {
|
||||
} else if e.Rune() == 'n' && hasYN {
|
||||
h.YNResp = false
|
||||
h.DonePrompt(false)
|
||||
}
|
||||
}
|
||||
if e.Key() == tcell.KeyRune && !done && !h.HasYN {
|
||||
if e.Key() == tcell.KeyRune && !done && !hasYN {
|
||||
h.DoRuneInsert(e.Rune())
|
||||
done = true
|
||||
}
|
||||
if done && h.HasPrompt && !h.HasYN {
|
||||
if done && h.HasPrompt && !hasYN {
|
||||
resp := strings.TrimSpace(string(h.LineBytes(0)))
|
||||
hist := h.History[h.PromptType]
|
||||
hist[h.HistoryNum] = resp
|
||||
|
|
|
@ -9,16 +9,32 @@ import (
|
|||
|
||||
func (b *Buffer) findDown(r *regexp.Regexp, start, end Loc) ([2]Loc, bool) {
|
||||
start.Y = util.Clamp(start.Y, 0, b.LinesNum()-1)
|
||||
end.Y = util.Clamp(end.Y, 0, b.LinesNum()-1)
|
||||
|
||||
if start.GreaterThan(end) {
|
||||
start, end = end, start
|
||||
}
|
||||
|
||||
for i := start.Y; i <= end.Y; i++ {
|
||||
l := b.LineBytes(i)
|
||||
charpos := 0
|
||||
|
||||
if i == start.Y {
|
||||
if i == start.Y && start.Y == end.Y {
|
||||
nchars := utf8.RuneCount(l)
|
||||
start.X = util.Clamp(start.X, 0, nchars-1)
|
||||
end.X = util.Clamp(end.X, 0, nchars-1)
|
||||
l = util.SliceStart(l, end.X)
|
||||
l = util.SliceEnd(l, start.X)
|
||||
charpos = start.X
|
||||
} else 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
|
||||
} else if i == end.Y {
|
||||
nchars := utf8.RuneCount(l)
|
||||
end.X = util.Clamp(end.X, 0, nchars-1)
|
||||
l = util.SliceStart(l, end.X)
|
||||
}
|
||||
|
||||
match := r.FindIndex(l)
|
||||
|
@ -34,21 +50,39 @@ func (b *Buffer) findDown(r *regexp.Regexp, start, end Loc) ([2]Loc, bool) {
|
|||
|
||||
func (b *Buffer) findUp(r *regexp.Regexp, start, end Loc) ([2]Loc, bool) {
|
||||
start.Y = util.Clamp(start.Y, 0, b.LinesNum()-1)
|
||||
end.Y = util.Clamp(end.Y, 0, b.LinesNum()-1)
|
||||
|
||||
for i := start.Y; i >= end.Y; i-- {
|
||||
if start.GreaterThan(end) {
|
||||
start, end = end, start
|
||||
}
|
||||
|
||||
for i := end.Y; i >= start.Y; i-- {
|
||||
l := b.LineBytes(i)
|
||||
charpos := 0
|
||||
|
||||
if i == start.Y {
|
||||
if i == start.Y && start.Y == end.Y {
|
||||
nchars := utf8.RuneCount(l)
|
||||
start.X = util.Clamp(start.X, 0, nchars-1)
|
||||
l = util.SliceStart(l, start.X)
|
||||
end.X = util.Clamp(end.X, 0, nchars-1)
|
||||
l = util.SliceStart(l, end.X)
|
||||
l = util.SliceEnd(l, start.X)
|
||||
charpos = start.X
|
||||
} else 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
|
||||
} else if i == end.Y {
|
||||
nchars := utf8.RuneCount(l)
|
||||
end.X = util.Clamp(end.X, 0, nchars-1)
|
||||
l = util.SliceStart(l, end.X)
|
||||
}
|
||||
|
||||
match := r.FindIndex(l)
|
||||
|
||||
if match != nil {
|
||||
start := Loc{util.RunePos(l, match[0]), i}
|
||||
end := Loc{util.RunePos(l, match[1]), i}
|
||||
start := Loc{charpos + util.RunePos(l, match[0]), i}
|
||||
end := Loc{charpos + util.RunePos(l, match[1]), i}
|
||||
return [2]Loc{start, end}, true
|
||||
}
|
||||
}
|
||||
|
@ -59,13 +93,18 @@ func (b *Buffer) findUp(r *regexp.Regexp, start, end Loc) ([2]Loc, bool) {
|
|||
// 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) {
|
||||
func (b *Buffer) FindNext(s string, start, end, from Loc, down bool, useRegex bool) ([2]Loc, bool, error) {
|
||||
if s == "" {
|
||||
return [2]Loc{}, false, nil
|
||||
}
|
||||
|
||||
var r *regexp.Regexp
|
||||
var err error
|
||||
|
||||
if !useRegex {
|
||||
s = regexp.QuoteMeta(s)
|
||||
}
|
||||
|
||||
if b.Settings["ignorecase"].(bool) {
|
||||
r, err = regexp.Compile("(?i)" + s)
|
||||
} else {
|
||||
|
@ -79,15 +118,54 @@ func (b *Buffer) FindNext(s string, from Loc, down bool) ([2]Loc, bool, error) {
|
|||
found := false
|
||||
var l [2]Loc
|
||||
if down {
|
||||
l, found = b.findDown(r, from, b.End())
|
||||
l, found = b.findDown(r, from, end)
|
||||
if !found {
|
||||
l, found = b.findDown(r, b.Start(), from)
|
||||
l, found = b.findDown(r, start, from)
|
||||
}
|
||||
} else {
|
||||
l, found = b.findUp(r, from, b.Start())
|
||||
l, found = b.findUp(r, from, start)
|
||||
if !found {
|
||||
l, found = b.findUp(r, b.End(), from)
|
||||
l, found = b.findUp(r, end, from)
|
||||
}
|
||||
}
|
||||
return l, found, nil
|
||||
}
|
||||
|
||||
// ReplaceRegex replaces all occurrences of 'search' with 'replace' in the given area
|
||||
// and returns the number of replacements made
|
||||
func (b *Buffer) ReplaceRegex(start, end Loc, search *regexp.Regexp, replace []byte) int {
|
||||
if start.GreaterThan(end) {
|
||||
start, end = end, start
|
||||
}
|
||||
|
||||
found := 0
|
||||
var deltas []Delta
|
||||
for i := start.Y; i <= end.Y; i++ {
|
||||
l := b.lines[i].data
|
||||
charpos := 0
|
||||
|
||||
// TODO: replace within X coords of selection
|
||||
if start.Y == end.Y && i == start.Y {
|
||||
l = util.SliceStart(l, end.X)
|
||||
l = util.SliceEnd(l, start.X)
|
||||
charpos = start.X
|
||||
} else if i == start.Y {
|
||||
l = util.SliceEnd(l, start.X)
|
||||
charpos = start.X
|
||||
} else if i == end.Y {
|
||||
l = util.SliceStart(l, end.X)
|
||||
}
|
||||
newText := search.ReplaceAllFunc(l, func(in []byte) []byte {
|
||||
found++
|
||||
return replace
|
||||
})
|
||||
|
||||
from := Loc{charpos, i}
|
||||
to := Loc{charpos + utf8.RuneCount(l), i}
|
||||
|
||||
deltas = append(deltas, Delta{newText, from, to})
|
||||
}
|
||||
b.MultipleReplace(deltas)
|
||||
|
||||
return found
|
||||
}
|
||||
|
|
|
@ -146,16 +146,11 @@ func (i *InfoBuf) DonePrompt(canceled bool) {
|
|||
h[len(h)-1] = resp
|
||||
}
|
||||
i.PromptCallback = nil
|
||||
i.EventCallback = nil
|
||||
}
|
||||
if i.EventCallback != nil {
|
||||
i.EventCallback = nil
|
||||
}
|
||||
i.Replace(i.Start(), i.End(), []byte{})
|
||||
}
|
||||
if i.YNCallback != nil && hadYN {
|
||||
i.YNCallback(i.YNResp, canceled)
|
||||
i.YNCallback = nil
|
||||
}
|
||||
}
|
||||
|
||||
|
|
Loading…
Reference in a new issue