Skip to content

Commit 596782a

Browse files
committed
Add option to display amount of occurrences of matches in file
Controlled by new option `showmatchcount`. If enabled shows current/total matches in the main file being edited. For example [2/10] means 10 totals matches and your past match 1 and in or before match 2 in the file. The count is follows the active focussed editor around.
1 parent b28ac49 commit 596782a

File tree

7 files changed

+225
-72
lines changed

7 files changed

+225
-72
lines changed

src/main/java/com/maddyhome/idea/vim/helper/SearchHighlightsHelper.kt

Lines changed: 111 additions & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,7 @@ import com.maddyhome.idea.vim.newapi.ij
2727
import com.maddyhome.idea.vim.newapi.vim
2828
import com.maddyhome.idea.vim.state.mode.inCommandLineModeWithVisual
2929
import com.maddyhome.idea.vim.state.mode.inVisualMode
30+
import com.maddyhome.idea.vim.ui.ex.ExEntryPanel
3031
import org.jetbrains.annotations.Contract
3132
import java.awt.Font
3233
import 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

4345
internal 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+
227290
private 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+
260340
internal fun highlightSearchResults(
261341
editor: Editor,
262342
pattern: String,

src/main/java/com/maddyhome/idea/vim/newapi/IjVimSearchGroup.kt

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -81,8 +81,8 @@ open class IjVimSearchGroup : VimSearchGroupBase(), PersistentStateComponent<Ele
8181
}
8282
}
8383

84-
override fun updateSearchHighlights(force: Boolean) {
85-
updateSearchHighlights(getLastUsedPattern(), lastIgnoreSmartCase, showSearchHighlight, force)
84+
override fun updateSearchHighlights(force: Boolean, newCaretPosition: Int?) {
85+
updateSearchHighlights(getLastUsedPattern(), lastIgnoreSmartCase, showSearchHighlight, force, newCaretPosition)
8686
}
8787

8888
override fun resetIncsearchHighlights() {

0 commit comments

Comments
 (0)