@@ -27,6 +27,7 @@ import com.maddyhome.idea.vim.newapi.ij
2727import com.maddyhome.idea.vim.newapi.vim
2828import com.maddyhome.idea.vim.state.mode.inCommandLineModeWithVisual
2929import com.maddyhome.idea.vim.state.mode.inVisualMode
30+ import com.maddyhome.idea.vim.ui.ex.ExEntryPanel
3031import org.jetbrains.annotations.Contract
3132import java.awt.Font
3233import java.util.*
@@ -36,8 +37,9 @@ internal fun updateSearchHighlights(
3637 shouldIgnoreSmartCase : Boolean ,
3738 showHighlights : Boolean ,
3839 forceUpdate : Boolean ,
40+ newCaretPosition : Int? = null,
3941) {
40- updateSearchHighlights(null , pattern, 1 , shouldIgnoreSmartCase, showHighlights, - 1 , null , true , forceUpdate)
42+ updateSearchHighlights(null , pattern, 1 , shouldIgnoreSmartCase, showHighlights, - 1 , null , true , forceUpdate, newCaretPosition )
4143}
4244
4345internal fun updateIncsearchHighlights (
@@ -63,7 +65,8 @@ internal fun updateIncsearchHighlights(
6365 searchStartOffset,
6466 searchRange,
6567 forwards,
66- false
68+ false ,
69+ null
6770 )
6871}
6972
@@ -84,6 +87,12 @@ internal fun addSubstitutionConfirmationHighlight(editor: Editor, start: Int, en
8487 )
8588}
8689
90+ enum class CountMatchesState {
91+ NoCountMatches ,
92+ ShowCount ,
93+ MaybeClearCount
94+ }
95+
8796/* *
8897 * Refreshes current search highlights for all visible editors
8998 */
@@ -97,6 +106,7 @@ private fun updateSearchHighlights(
97106 searchRange : LineRange ? ,
98107 forwards : Boolean ,
99108 forceUpdate : Boolean ,
109+ newCaretPosition : Int?
100110): Int {
101111 var currentEditorCurrentMatchOffset = - 1
102112
@@ -107,6 +117,18 @@ private fun updateSearchHighlights(
107117 && (currentEditor == null || it.projectId == currentEditor.projectId)
108118 }
109119
120+ val countMatchesSetting = injector.globalOptions().showmatchcount
121+ var editorWithSearchMatches = if (countMatchesSetting) { currentEditor?.ij } else { null }
122+ if (countMatchesSetting && editorWithSearchMatches == null ) {
123+ editorWithSearchMatches = injector.editorGroup.getFocusedEditor()?.ij
124+ }
125+
126+ if (! countMatchesSetting) {
127+ // In case we were counting matches before clear it, the function
128+ // itself ensures nothing happens if it is cleared already
129+ ExEntryPanel .getOrCreatePanelInstance().clearStatusText()
130+ }
131+
110132 val shouldIgnoreCase = pattern == null || shouldIgnoreCase(pattern, shouldIgnoreSmartCase)
111133
112134 var maxhlduringincsearch = injector.globalOptions().maxhlduringincsearch
@@ -115,13 +137,17 @@ private fun updateSearchHighlights(
115137
116138 editors.forEach {
117139 val editor = it.ij
140+ val isCurrentEditor = editor == editorWithSearchMatches
141+ var countMatches = if (! isCurrentEditor || ! countMatchesSetting) CountMatchesState .NoCountMatches else CountMatchesState .ShowCount
142+
118143 var currentMatchOffset = - 1
119144
120145 // Try to keep existing highlights if possible. Update if hlsearch has changed or if the pattern has changed.
121146 // Force update for the situations where the text is the same, but the ignore case values have changed.
122147 // E.g., Use `*` to search for a word (which ignores smartcase), then use `/<Up>` to search for the same pattern,
123148 // which will match smartcase. Or changing the smartcase/ignorecase settings
124- if (shouldRemoveSearchHighlights(editor, pattern, showHighlights) || forceUpdate) {
149+ val clearHighlights = shouldRemoveSearchHighlights(editor, pattern, showHighlights)
150+ if (clearHighlights || forceUpdate) {
125151 removeSearchHighlights(editor)
126152 }
127153
@@ -131,35 +157,22 @@ private fun updateSearchHighlights(
131157 // hlsearch (+ incsearch/noincsearch)
132158 // Make sure the range fits this editor. Note that Vim will use the same range for all windows. E.g., given
133159 // `:1,5s/foo`, Vim will highlight all occurrences of `foo` in the first five lines of all visible windows
134- val vimEditor = editor.vim
135- val editorLastLine = vimEditor.lineCount() - 1
136- val searchStartLine = searchRange?.startLine ? : 0
137- val searchEndLine = (searchRange?.endLine ? : - 1 ).coerceAtMost(editorLastLine)
138- if (searchStartLine <= editorLastLine) {
139- val results =
140- injector.searchHelper.findAll(
141- vimEditor,
142- pattern,
143- searchStartLine,
144- searchEndLine,
145- shouldIgnoreCase
146- )
147- if (results.isNotEmpty()) {
148- // Only in incsearch is current editor not null, then check result size
149- val showHighlightsInEditor = currentEditor == null || results.size < maxhlduringincsearch
150- if (editor == currentEditor?.ij) {
151- val currentMatchIndex = findClosestMatch(results, initialOffset, count1, forwards)
152- currentMatchOffset = if (currentMatchIndex == - 1 ) - 1 else results[currentMatchIndex].startOffset
153-
154- if (! showHighlightsInEditor) {
155- // Always highlight at least the "current" match in the active editor
156- highlightSearchResults(editor, pattern, listOf (results[currentMatchIndex]), currentMatchOffset)
157- }
160+ val results = findAllMatches(pattern, editor.vim, searchRange, shouldIgnoreCase)
161+ if (results.isNotEmpty()) {
162+ // Only in incsearch is current editor not null, then check result size
163+ val showHighlightsInEditor = currentEditor == null || results.size < maxhlduringincsearch
164+ if (editor == currentEditor?.ij) {
165+ val currentMatchIndex = findClosestMatch(results, initialOffset, count1, forwards)
166+ currentMatchOffset = if (currentMatchIndex == - 1 ) - 1 else results[currentMatchIndex].startOffset
167+
168+ if (! showHighlightsInEditor) {
169+ // Always highlight at least the "current" match in the active editor
170+ highlightSearchResults(editor, pattern, listOf (results[currentMatchIndex]), currentMatchOffset)
158171 }
172+ }
159173
160- if (showHighlightsInEditor) {
161- highlightSearchResults(editor, pattern, results, currentMatchOffset)
162- }
174+ if (showHighlightsInEditor) {
175+ highlightSearchResults(editor, pattern, results, currentMatchOffset)
163176 }
164177 }
165178 editor.vimLastSearch = pattern
@@ -189,11 +202,44 @@ private fun updateSearchHighlights(
189202 if (offset != null && editor == = currentEditor?.ij) {
190203 currentMatchOffset = offset
191204 }
205+ } else {
206+ countMatches = CountMatchesState .MaybeClearCount
192207 }
193208
194- if (editor == = currentEditor?.ij) {
209+ if (! isCurrentEditor)
210+ return @forEach
211+
212+ var editorCaretOffset = editor.vim.primaryCaret().offset
213+ if (currentEditor?.ij != null ) {
195214 currentEditorCurrentMatchOffset = currentMatchOffset
215+ editorCaretOffset = currentEditorCurrentMatchOffset
216+ } else if (newCaretPosition != null ) {
217+ editorCaretOffset = newCaretPosition
218+ }
219+
220+ if (countMatches == CountMatchesState .NoCountMatches ) {
221+ return @forEach
222+ }
223+
224+ // If any of the following hold we are still searching:
225+ // - We just highlighted some search results (countMatches is not MaybeClear
226+ // - We did not clear highlights (
227+ // - We are moving towards to a new position
228+ if (countMatches == CountMatchesState .MaybeClearCount && clearHighlights && newCaretPosition == null ) {
229+ ExEntryPanel .getOrCreatePanelInstance().clearStatusText()
230+ return @forEach
231+ }
232+
233+ // Search file for pattern, and determine total and position in results
234+ val results = findAllMatches(pattern, editor.vim, searchRange, shouldIgnoreCase)
235+ val patternIndex = if (results.isEmpty()) {
236+ - 1
237+ } else {
238+ findClosestOrCurrentMatch(results, editorCaretOffset)
196239 }
240+
241+ val countMessage = " [" + (patternIndex + 1 ) + " /" + results.size + " ]"
242+ ExEntryPanel .getOrCreatePanelInstance().setStatusText(editor, countMessage)
197243 }
198244
199245 return currentEditorCurrentMatchOffset
@@ -224,6 +270,23 @@ private fun shouldAddAllSearchHighlights(editor: Editor, newPattern: String?, hl
224270 return hlSearch && newPattern != null && newPattern != editor.vimLastSearch && newPattern != " "
225271}
226272
273+ private fun findAllMatches (pattern : String , vimEditor : VimEditor , searchRange : LineRange ? , shouldIgnoreCase : Boolean ): List <TextRange > {
274+ val editorLastLine = vimEditor.lineCount() - 1
275+ val searchStartLine = searchRange?.startLine ? : 0
276+ val searchEndLine = (searchRange?.endLine ? : - 1 ).coerceAtMost(editorLastLine)
277+ if (searchStartLine > editorLastLine) {
278+ return listOf ()
279+ }
280+
281+ return injector.searchHelper.findAll(
282+ vimEditor,
283+ pattern,
284+ searchStartLine,
285+ searchEndLine,
286+ shouldIgnoreCase
287+ )
288+ }
289+
227290private fun findClosestMatch (
228291 results : List <TextRange >,
229292 initialOffset : Int ,
@@ -257,6 +320,23 @@ private fun findClosestMatch(
257320 return nextIndex % results.size
258321}
259322
323+ private fun findClosestOrCurrentMatch (
324+ results : List <TextRange >,
325+ initialOffset : Int ,
326+ ): Int {
327+ if (results.isEmpty() || initialOffset == - 1 ) {
328+ return - 1
329+ }
330+
331+ val firstMatch = results.filter { it.endOffset >= initialOffset }.minByOrNull { it.endOffset }
332+ if (firstMatch == null ) {
333+ // Results is not empty but there is no match before offset, we must be past the last match
334+ return results.size - 1
335+ }
336+ // Note that wrapping for the count does not make sense
337+ return results.indexOfFirst { it.endOffset == firstMatch.endOffset }
338+ }
339+
260340internal fun highlightSearchResults (
261341 editor : Editor ,
262342 pattern : String ,
0 commit comments