// Search searches for a given string/regex in the buffer and selects the next
// match if a match is found
-// This function affects lastSearch and lastSearchRegex (saved searches) for
-// use with FindNext and FindPrevious
+// This function behaves the same way as Find and FindLiteral actions:
+// it affects the buffer's LastSearch and LastSearchRegex (saved searches)
+// for use with FindNext and FindPrevious, and turns HighlightSearch on or off
+// according to hlsearch setting
func (h *BufPane) Search(str string, useRegex bool, searchDown bool) error {
match, found, err := h.Buf.FindNext(str, h.Buf.Start(), h.Buf.End(), h.Cursor.Loc, searchDown, useRegex)
if err != nil {
h.Cursor.OrigSelection[0] = h.Cursor.CurSelection[0]
h.Cursor.OrigSelection[1] = h.Cursor.CurSelection[1]
h.Cursor.GotoLoc(h.Cursor.CurSelection[1])
- h.lastSearch = str
- h.lastSearchRegex = useRegex
+ h.Buf.LastSearch = str
+ h.Buf.LastSearchRegex = useRegex
+ h.Buf.HighlightSearch = h.Buf.Settings["hlsearch"].(bool)
h.Relocate()
} else {
h.Cursor.ResetSelection()
h.Cursor.OrigSelection[0] = h.Cursor.CurSelection[0]
h.Cursor.OrigSelection[1] = h.Cursor.CurSelection[1]
h.Cursor.GotoLoc(h.Cursor.CurSelection[1])
- h.lastSearch = resp
- h.lastSearchRegex = useRegex
+ h.Buf.LastSearch = resp
+ h.Buf.LastSearchRegex = useRegex
+ h.Buf.HighlightSearch = h.Buf.Settings["hlsearch"].(bool)
} else {
h.Cursor.ResetSelection()
InfoBar.Message("No matches found")
return true
}
+// ToggleHighlightSearch toggles highlighting all instances of the last used search term
+func (h *BufPane) ToggleHighlightSearch() bool {
+ h.Buf.HighlightSearch = !h.Buf.HighlightSearch
+ return true
+}
+
+// UnhighlightSearch unhighlights all instances of the last used search term
+func (h *BufPane) UnhighlightSearch() bool {
+ h.Buf.HighlightSearch = false
+ return true
+}
+
// FindNext searches forwards for the last used search term
func (h *BufPane) FindNext() bool {
// If the cursor is at the start of a selection and we search we want
if h.Cursor.HasSelection() {
searchLoc = h.Cursor.CurSelection[1]
}
- match, found, err := h.Buf.FindNext(h.lastSearch, h.Buf.Start(), h.Buf.End(), searchLoc, true, h.lastSearchRegex)
+ match, found, err := h.Buf.FindNext(h.Buf.LastSearch, h.Buf.Start(), h.Buf.End(), searchLoc, true, h.Buf.LastSearchRegex)
if err != nil {
InfoBar.Error(err)
}
if h.Cursor.HasSelection() {
searchLoc = h.Cursor.CurSelection[0]
}
- match, found, err := h.Buf.FindNext(h.lastSearch, h.Buf.Start(), h.Buf.End(), searchLoc, false, h.lastSearchRegex)
+ match, found, err := h.Buf.FindNext(h.Buf.LastSearch, h.Buf.Start(), h.Buf.End(), searchLoc, false, h.Buf.LastSearchRegex)
if err != nil {
InfoBar.Error(err)
}
// Same here, just to keep track for mouse move events
tripleClick bool
- // Last search stores the last successful search for FindNext and FindPrev
- lastSearch string
- lastSearchRegex bool
// Should the current multiple cursor selection search based on word or
// based on selection (false for selection, true for word)
multiWord bool
"ToggleKeyMenu": (*BufPane).ToggleKeyMenu,
"ToggleDiffGutter": (*BufPane).ToggleDiffGutter,
"ToggleRuler": (*BufPane).ToggleRuler,
+ "ToggleHighlightSearch": (*BufPane).ToggleHighlightSearch,
+ "UnhighlightSearch": (*BufPane).UnhighlightSearch,
"ClearStatus": (*BufPane).ClearStatus,
"ShellMode": (*BufPane).ShellMode,
"CommandMode": (*BufPane).CommandMode,
h.Cursor.SetSelectionStart(locs[0])
h.Cursor.SetSelectionEnd(locs[1])
h.Cursor.GotoLoc(locs[0])
+ h.Buf.LastSearch = search
+ h.Buf.LastSearchRegex = true
+ h.Buf.HighlightSearch = h.Buf.Settings["hlsearch"].(bool)
h.Relocate()
"F4": "Quit",
"F7": "Find",
"F10": "Quit",
- "Esc": "Escape,Deselect,ClearInfo,RemoveAllMultiCursors",
+ "Esc": "Escape,Deselect,ClearInfo,RemoveAllMultiCursors,UnhighlightSearch",
// Mouse bindings
"MouseWheelUp": "ScrollUp",
"F4": "Quit",
"F7": "Find",
"F10": "Quit",
- "Esc": "Escape,Deselect,ClearInfo,RemoveAllMultiCursors",
+ "Esc": "Escape,Deselect,ClearInfo,RemoveAllMultiCursors,UnhighlightSearch",
// Mouse bindings
"MouseWheelUp": "ScrollUp",
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
+ 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++ {
- l = util.Max(b.Highlighter.ReHighlightStates(b, i), l)
+ b.LineArray.invalidateSearchMatches(i)
}
- b.Highlighter.HighlightMatches(b, start, l)
}
// DisableReload disables future reloads of this sharedbuffer
// 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
// 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
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)
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 {
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 (
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
+ }
+ }
+}
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 {
"fastdirty": false,
"fileformat": "unix",
"filetype": "unknown",
+ "hlsearch": false,
"incsearch": true,
"ignorecase": true,
"indentchar": " ",
draw := func(r rune, combc []rune, style tcell.Style, highlight bool, showcursor bool) {
if nColsBeforeStart <= 0 && vloc.Y >= 0 {
if highlight {
+ if w.Buf.HighlightSearch && w.Buf.SearchMatch(bloc) {
+ style = config.DefStyle.Reverse(true)
+ if s, ok := config.Colorscheme["hlsearch"]; ok {
+ style = s
+ }
+ }
+
_, origBg, _ := style.Decompose()
_, defBg, _ := config.DefStyle.Decompose()
- // syntax highlighting with non-default background takes precedence
+ // syntax or hlsearch highlighting with non-default background takes precedence
// over cursor-line and color-column
dontOverrideBackground := origBg != defBg
color-link underlined "#D33682,#1D1F21"
color-link error "bold #FF4444,#1D1F21"
color-link todo "bold #FF8844,#1D1F21"
+color-link hlsearch "#000000,#B4EC85"
color-link statusline "#1D1F21,#C5C8C6"
color-link tabbar "#1D1F21,#C5C8C6"
color-link indent-char "#505050,#1D1F21"
color-link error "231, 160"
color-link underlined "underline 241,231"
color-link todo "246,231"
+color-link hlsearch "231,136"
color-link statusline "241,254"
color-link tabbar "241,254"
color-link diff-added "34"
color-link ignore "default"
color-link error "bold ,brightred"
color-link todo "underline black,brightyellow"
+color-link hlsearch "white,darkgreen"
color-link indent-char ",brightgreen"
color-link line-number "green"
color-link line-number.scrollbar "green"
color-link ignore "default"
color-link error "bold ,#e34234"
color-link todo "bold underline #888888,#f26522"
+color-link hlsearch "#b7b7b7,#32593d"
color-link indent-char ",#bdecb6"
color-link line-number "#bdecb6,#36393e"
color-link line-number.scrollbar "#3eb489"
color-link underlined "#D33682,#242424"
color-link error "bold #CB4B16,#242424"
color-link todo "bold #D33682,#242424"
+color-link hlsearch "#CCCCCC,#32593D"
color-link statusline "#242424,#CCCCCC"
color-link tabbar "#242424,#CCCCCC"
color-link indent-char "#4F4F4F,#242424"
color-link underlined "#D33682,#282828"
color-link error "bold #CB4B16,#282828"
color-link todo "bold #D33682,#282828"
+color-link hlsearch "#282828,#E6DB74"
color-link statusline "#282828,#F8F8F2"
color-link tabbar "#282828,#F8F8F2"
color-link indent-char "#505050,#282828"
color-link error "bold #FF5555"
color-link todo "bold #FF79C6"
+color-link hlsearch "#282A36,#50FA7B"
+
color-link diff-added "#50FA7B"
color-link diff-modified "#FFB86C"
color-link diff-deleted "#FF5555"
color-link error "#cb4b16,#001e28"
color-link gutter-error "#cb4b16,#001e28"
color-link gutter-warning "#fce94f,#001e28"
+color-link hlsearch "#ffffff,#005028"
color-link identifier "#00c8a0,#001e28"
color-link identifier.class "#00c8a0,#001e28"
color-link indent-char "#a0a0a0,#001e28"
color-link error "#500000,#f0f0f0"
color-link gutter-error "#500000,#f0f0f0"
color-link gutter-warning "#dcc800,#f0f0f0"
+color-link hlsearch "#000000,#b8d8e8"
color-link identifier "bold #0078a0,#f0f0f0"
color-link identifier.class "bold #0078a0,#f0f0f0"
color-link indent-char "#404040,#f0f0f0"
color-link error "#cb4b16,#2d0023"
color-link gutter-error "#cb4b16,#2d0023"
color-link gutter-warning "#fce94f,#2d0023"
+color-link hlsearch "#ffffff,#005028"
color-link identifier "#00c8a0,#2d0023"
color-link identifier.class "#00c8a0,#2d0023"
color-link indent-char "#a0a0a0,#2d0023"
color-link type.extended "default"
color-link error "red"
color-link todo "bold cyan"
+color-link hlsearch "black,brightcyan"
color-link indent-char "bold black"
color-link line-number ""
color-link current-line-number ""
color-link underlined "#EDB443,#0C1014"
color-link error "bold #C23127,#0C1014"
color-link todo "bold #888CA6,#0C1014"
+color-link hlsearch "#091F2E,#EDB443"
color-link statusline "#091F2E,#599CAB"
color-link indent-char "#505050,#0C1014"
color-link line-number "#245361,#11151C"
color-link special "#d79921,#282828"
color-link underlined "underline #282828"
color-link error "#9d0006,#282828"
+color-link hlsearch "#282828,#fabd2f"
color-link diff-added "#00AF00"
color-link diff-modified "#FFAF00"
color-link diff-deleted "#D70000"
color-link underlined "underline 109,235"
color-link error "235,124"
color-link todo "bold 223,235"
+color-link hlsearch "235,214"
color-link diff-added "34"
color-link diff-modified "214"
color-link diff-deleted "160"
color-link error "bold #263238,#F07178"
color-link gutter-error "#EEFFFF,#F07178"
color-link gutter-warning "#EEFFFF,#FFF176"
+color-link hlsearch "#FFFFFF,#546E7A"
color-link identifier "#82AAFF,#263238"
color-link identifier.macro "#FFCB6B,#263238"
color-link indent-char "#505050,#263238"
color-link underlined "#D33682"
color-link error "bold #CB4B16"
color-link todo "bold #D33682"
+color-link hlsearch "#1D0000,#E6DB74"
color-link statusline "#282828,#F8F8F2"
color-link indent-char "#505050,#282828"
color-link line-number "#AAAAAA,#282828"
color-link underlined "#D33682,#282828"
color-link error "bold #CB4B16,#282828"
color-link todo "bold #D33682,#282828"
+color-link hlsearch "#282828,#E6DB74"
color-link statusline "#282828,#F8F8F2"
color-link tabbar "#282828,#F8F8F2"
color-link indent-char "#505050,#282828"
color-link diff-deleted "#D70000"
color-link gutter-error "#9B859D"
color-link gutter-warning "#9B859D"
+color-link hlsearch "#21252C,#E5C07B"
color-link identifier "#61AFEF"
color-link identifier.class "#C678DD"
color-link identifier.var "#C678DD"
color-link todo "bold #cc7833,#2b2b2b"
color-link error "bold #cc7833,#2b2b2b"
color-link gutter-error "#cc7833,#11151C"
+color-link hlsearch "#e6e1dc,#474d5c"
color-link indent-char "#414141,#2b2b2b"
color-link line-number "#a1a1a1,#232323"
color-link current-line-number "#e6e1dc,#2b2b2b"
color-link ignore "default"
color-link error ",brightred"
color-link todo ",brightyellow"
+color-link hlsearch "black,yellow"
color-link indent-char "black"
color-link line-number "yellow"
color-link current-line-number "red"
color-link underlined "#D33682,#002833"
color-link error "bold #CB4B16,#002833"
color-link todo "bold #D33682,#002833"
+color-link hlsearch "#002833,#B58900"
color-link statusline "#003541,#839496"
color-link tabbar "#003541,#839496"
color-link indent-char "#003541,#002833"
color-link underlined "magenta"
color-link error "bold brightred"
color-link todo "bold magenta"
+color-link hlsearch "black,yellow"
color-link statusline "black,brightblue"
color-link tabbar "black,brightblue"
color-link indent-char "black"
color-link underlined "61,230"
color-link error "88"
color-link todo "210"
+color-link hlsearch "0,253"
color-link statusline "233,229"
color-link tabbar "233,229"
color-link indent-char "229"
color-link diff-deleted "#D70000"
color-link gutter-error "#9B859D"
color-link gutter-warning "#9B859D"
+color-link hlsearch "#141414,#C0C000"
color-link identifier "#9B703F"
color-link identifier.class "#DAD085"
color-link identifier.var "#7587A6"
color-link underlined "188,237"
color-link error "115,236"
color-link todo "bold 254,237"
+color-link hlsearch "230,22"
color-link statusline "186,236"
color-link tabbar "186,236"
color-link indent-char "238,237"
default value: `unknown`. This will be automatically overridden depending
on the file you open.
+* `hlsearch`: highlight all instances of the searched text after a successful
+ search. This highlighting can be turned off via `UnhighlightSearch` action
+ (triggered by Esc key by default) or toggled on/off via `ToggleHighlightSearch`
+ action. Note that these actions don't change `hlsearch` setting.
+ As long as `hlsearch` is set to true, after the next search the highlighting
+ is turned on again.
+
+ default value: `false`
+
* `incsearch`: enable incremental search in "Find" prompt (matching as you type).
default value: `true`