mirror of
https://github.com/zyedidia/micro.git
synced 2026-03-17 06:17:12 +09:00
Support for highlighting all search matches (hlsearch) (#1762)
* Support for highlighting all search matches (hlsearch) hlsearch is implemented efficiently using the buffer's line array, somewhat similarly to the syntax highlighting. Unlike the syntax highlighter which highlights the entire file, hlsearch searches for matches for the displayed lines only. Matches are searched when the given line is displayed first time or after it was modified. Otherwise the previously found matches are used. * Add UnhighlightSearch action and add it to the list of actions triggered by Esc key by default. * Add comment explaining the purpose of search map * Add hlsearch colors to colorschemes Mostly just copied from the corresponding original (mostly vim) colorschemes. * Highlight matches during/after replace as well As a side effect it also changes the last search value, i.e. affects FindNext and FindPrevious, but it's probably fine. In vim it works the same way. * Improve hlsearch option description
This commit is contained in:
@@ -146,18 +146,20 @@ func (b *SharedBuffer) remove(start, end Loc) []byte {
|
||||
func (b *SharedBuffer) MarkModified(start, end int) {
|
||||
b.ModifiedThisFrame = true
|
||||
|
||||
if !b.Settings["syntax"].(bool) || b.SyntaxDef == nil {
|
||||
return
|
||||
}
|
||||
|
||||
start = util.Clamp(start, 0, len(b.lines)-1)
|
||||
end = util.Clamp(end, 0, len(b.lines)-1)
|
||||
|
||||
l := -1
|
||||
for i := start; i <= end; i++ {
|
||||
l = util.Max(b.Highlighter.ReHighlightStates(b, i), l)
|
||||
if b.Settings["syntax"].(bool) && b.SyntaxDef != nil {
|
||||
l := -1
|
||||
for i := start; i <= end; i++ {
|
||||
l = util.Max(b.Highlighter.ReHighlightStates(b, i), l)
|
||||
}
|
||||
b.Highlighter.HighlightMatches(b, start, l)
|
||||
}
|
||||
|
||||
for i := start; i <= end; i++ {
|
||||
b.LineArray.invalidateSearchMatches(i)
|
||||
}
|
||||
b.Highlighter.HighlightMatches(b, start, l)
|
||||
}
|
||||
|
||||
// DisableReload disables future reloads of this sharedbuffer
|
||||
@@ -181,6 +183,7 @@ type DiffStatus byte
|
||||
// The syntax highlighting info must be stored with the buffer because the syntax
|
||||
// highlighter attaches information to each line of the buffer for optimization
|
||||
// purposes so it doesn't have to rehighlight everything on every update.
|
||||
// Likewise for the search highlighting.
|
||||
type Buffer struct {
|
||||
*EventHandler
|
||||
*SharedBuffer
|
||||
@@ -202,6 +205,12 @@ type Buffer struct {
|
||||
// This is hacky. Maybe it would be better to move all the visual x logic
|
||||
// from buffer to display, but it would require rewriting a lot of code.
|
||||
GetVisualX func(loc Loc) int
|
||||
|
||||
// Last search stores the last successful search
|
||||
LastSearch string
|
||||
LastSearchRegex bool
|
||||
// HighlightSearch enables highlighting all instances of the last successful search
|
||||
HighlightSearch bool
|
||||
}
|
||||
|
||||
// NewBufferFromFileAtLoc opens a new buffer with a given cursor location
|
||||
@@ -1199,6 +1208,12 @@ func (b *Buffer) DiffStatus(lineN int) DiffStatus {
|
||||
return b.diff[lineN]
|
||||
}
|
||||
|
||||
// SearchMatch returns true if the given location is within a match of the last search.
|
||||
// It is used for search highlighting
|
||||
func (b *Buffer) SearchMatch(pos Loc) bool {
|
||||
return b.LineArray.SearchMatch(b, pos)
|
||||
}
|
||||
|
||||
// WriteLog writes a string to the log buffer
|
||||
func WriteLog(s string) {
|
||||
LogBuf.EventHandler.Insert(LogBuf.End(), s)
|
||||
|
||||
@@ -32,6 +32,15 @@ func runeToByteIndex(n int, txt []byte) int {
|
||||
return count
|
||||
}
|
||||
|
||||
// A searchState contains the search match info for a single line
|
||||
type searchState struct {
|
||||
search string
|
||||
useRegex bool
|
||||
ignorecase bool
|
||||
match [][2]int
|
||||
done bool
|
||||
}
|
||||
|
||||
// A Line contains the data in bytes as well as a highlight state, match
|
||||
// and a flag for whether the highlighting needs to be updated
|
||||
type Line struct {
|
||||
@@ -41,6 +50,14 @@ type Line struct {
|
||||
match highlight.LineMatch
|
||||
rehighlight bool
|
||||
lock sync.Mutex
|
||||
|
||||
// The search states for the line, used for highlighting of search matches,
|
||||
// separately from the syntax highlighting.
|
||||
// A map is used because the line array may be shared between multiple buffers
|
||||
// (multiple instances of the same file opened in different edit panes)
|
||||
// which have distinct searches, so in the general case there are multiple
|
||||
// searches per a line, one search per a Buffer containing this line.
|
||||
search map[*Buffer]*searchState
|
||||
}
|
||||
|
||||
const (
|
||||
@@ -356,3 +373,79 @@ func (la *LineArray) SetRehighlight(lineN int, on bool) {
|
||||
defer la.lines[lineN].lock.Unlock()
|
||||
la.lines[lineN].rehighlight = on
|
||||
}
|
||||
|
||||
// SearchMatch returns true if the location `pos` is within a match
|
||||
// of the last search for the buffer `b`.
|
||||
// It is used for efficient highlighting of search matches (separately
|
||||
// from the syntax highlighting).
|
||||
// SearchMatch searches for the matches if it is called first time
|
||||
// for the given line or if the line was modified. Otherwise the
|
||||
// previously found matches are used.
|
||||
//
|
||||
// The buffer `b` needs to be passed because the line array may be shared
|
||||
// between multiple buffers (multiple instances of the same file opened
|
||||
// in different edit panes) which have distinct searches, so SearchMatch
|
||||
// needs to know which search to match against.
|
||||
func (la *LineArray) SearchMatch(b *Buffer, pos Loc) bool {
|
||||
if b.LastSearch == "" {
|
||||
return false
|
||||
}
|
||||
|
||||
lineN := pos.Y
|
||||
if la.lines[lineN].search == nil {
|
||||
la.lines[lineN].search = make(map[*Buffer]*searchState)
|
||||
}
|
||||
s, ok := la.lines[lineN].search[b]
|
||||
if !ok {
|
||||
// Note: here is a small harmless leak: when the buffer `b` is closed,
|
||||
// `s` is not deleted from the map. It means that the buffer
|
||||
// will not be garbage-collected until the line array is garbage-collected,
|
||||
// i.e. until all the buffers sharing this file are closed.
|
||||
s = new(searchState)
|
||||
la.lines[lineN].search[b] = s
|
||||
}
|
||||
if !ok || s.search != b.LastSearch || s.useRegex != b.LastSearchRegex ||
|
||||
s.ignorecase != b.Settings["ignorecase"].(bool) {
|
||||
s.search = b.LastSearch
|
||||
s.useRegex = b.LastSearchRegex
|
||||
s.ignorecase = b.Settings["ignorecase"].(bool)
|
||||
s.done = false
|
||||
}
|
||||
|
||||
if !s.done {
|
||||
s.match = nil
|
||||
start := Loc{0, lineN}
|
||||
end := Loc{util.CharacterCount(la.lines[lineN].data), lineN}
|
||||
for start.X < end.X {
|
||||
m, found, _ := b.FindNext(b.LastSearch, start, end, start, true, b.LastSearchRegex)
|
||||
if !found {
|
||||
break
|
||||
}
|
||||
s.match = append(s.match, [2]int{m[0].X, m[1].X})
|
||||
|
||||
start.X = m[1].X
|
||||
if m[1].X == m[0].X {
|
||||
start.X = m[1].X + 1
|
||||
}
|
||||
}
|
||||
|
||||
s.done = true
|
||||
}
|
||||
|
||||
for _, m := range s.match {
|
||||
if pos.X >= m[0] && pos.X < m[1] {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
// invalidateSearchMatches marks search matches for the given line as outdated.
|
||||
// It is called when the line is modified.
|
||||
func (la *LineArray) invalidateSearchMatches(lineN int) {
|
||||
if la.lines[lineN].search != nil {
|
||||
for _, s := range la.lines[lineN].search {
|
||||
s.done = false
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -39,6 +39,12 @@ func (b *Buffer) SetOptionNative(option string, nativeValue interface{}) error {
|
||||
b.isModified = true
|
||||
} else if option == "readonly" && b.Type.Kind == BTDefault.Kind {
|
||||
b.Type.Readonly = nativeValue.(bool)
|
||||
} else if option == "hlsearch" {
|
||||
for _, buf := range OpenBuffers {
|
||||
if b.SharedBuffer == buf.SharedBuffer {
|
||||
buf.HighlightSearch = nativeValue.(bool)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if b.OptionCallback != nil {
|
||||
|
||||
Reference in New Issue
Block a user