mirror of
https://github.com/chylex/IntelliJ-IdeaVim.git
synced 2025-03-05 06:32:51 +01:00
Convert SearchHighlightsHelper to Kotlin
This commit is contained in:
parent
b13acaf823
commit
3c8b7e2de4
src/com/maddyhome/idea/vim
@ -61,6 +61,8 @@ import java.util.EnumSet;
|
||||
import java.util.Iterator;
|
||||
import java.util.List;
|
||||
|
||||
import static com.maddyhome.idea.vim.helper.SearchHelperKtKt.shouldIgnoreCase;
|
||||
|
||||
@State(name = "VimSearchSettings", storages = {
|
||||
@Storage(value = "$APP_CONFIG$/vim_settings_local.xml", roamingType = RoamingType.DISABLED)
|
||||
})
|
||||
@ -367,7 +369,7 @@ public class SearchGroup implements PersistentStateComponent<Element> {
|
||||
private void highlightSearchLines(@NotNull Editor editor, int startLine, int endLine) {
|
||||
if (lastSearch != null) {
|
||||
final List<TextRange> results = findAll(editor, lastSearch, startLine, endLine,
|
||||
SearchHelper.shouldIgnoreCase(lastSearch, lastIgnoreSmartCase));
|
||||
shouldIgnoreCase(lastSearch, lastIgnoreSmartCase));
|
||||
SearchHighlightsHelper.highlightSearchResults(editor, lastSearch, results, -1);
|
||||
}
|
||||
}
|
||||
@ -432,7 +434,7 @@ public class SearchGroup implements PersistentStateComponent<Element> {
|
||||
//RE sp;
|
||||
RegExp sp;
|
||||
RegExp.regmmatch_T regmatch = new RegExp.regmmatch_T();
|
||||
regmatch.rmm_ic = SearchHelper.shouldIgnoreCase(pattern, searchOptions.contains(SearchOptions.IGNORE_SMARTCASE));
|
||||
regmatch.rmm_ic = shouldIgnoreCase(pattern, searchOptions.contains(SearchOptions.IGNORE_SMARTCASE));
|
||||
sp = new RegExp();
|
||||
regmatch.regprog = sp.vim_regcomp(pattern, 1);
|
||||
if (regmatch.regprog == null) {
|
||||
@ -1002,7 +1004,7 @@ public class SearchGroup implements PersistentStateComponent<Element> {
|
||||
}
|
||||
|
||||
/* the 'i' or 'I' flag overrules 'ignorecase' and 'smartcase' */
|
||||
regmatch.rmm_ic = SearchHelper.shouldIgnoreCase(pattern != null ? pattern : "", false);
|
||||
regmatch.rmm_ic = shouldIgnoreCase(pattern != null ? pattern : "", false);
|
||||
if (do_ic == 'i') {
|
||||
regmatch.rmm_ic = true;
|
||||
}
|
||||
@ -1206,6 +1208,7 @@ public class SearchGroup implements PersistentStateComponent<Element> {
|
||||
/**
|
||||
* Updates search highlights when the selected editor changes
|
||||
*/
|
||||
@SuppressWarnings("unused")
|
||||
public static void fileEditorManagerSelectionChangedCallback(@NotNull FileEditorManagerEvent event) {
|
||||
VimPlugin.getSearch().updateSearchHighlights();
|
||||
}
|
||||
|
@ -2289,13 +2289,6 @@ public class SearchHelper {
|
||||
return res.toString();
|
||||
}
|
||||
|
||||
public static boolean shouldIgnoreCase(@NotNull String pattern, boolean ignoreSmartCase) {
|
||||
boolean sc = !ignoreSmartCase && OptionsManager.INSTANCE.getSmartcase().isSet();
|
||||
boolean ic = OptionsManager.INSTANCE.getIgnorecase().isSet();
|
||||
|
||||
return ic && !(sc && StringHelper.containsUpperCase(pattern));
|
||||
}
|
||||
|
||||
public static class CountPosition {
|
||||
public CountPosition(int count, int position) {
|
||||
this.count = count;
|
||||
|
@ -20,6 +20,8 @@ package com.maddyhome.idea.vim.helper
|
||||
|
||||
import com.maddyhome.idea.vim.helper.SearchHelper.Direction
|
||||
import com.maddyhome.idea.vim.helper.SearchHelper.findPositionOfFirstCharacter
|
||||
import com.maddyhome.idea.vim.option.OptionsManager.ignorecase
|
||||
import com.maddyhome.idea.vim.option.OptionsManager.smartcase
|
||||
|
||||
private data class State(val position: Int, val trigger: Char, val inQuote: Boolean?, val lastOpenSingleQuotePos: Int)
|
||||
|
||||
@ -161,3 +163,8 @@ private fun quoteChanges(chars: CharSequence, begin: Int) = sequence {
|
||||
found = findPositionOfFirstCharacter(chars, i + Direction.FORWARD.toInt(), charsToSearch, false, Direction.FORWARD)
|
||||
}
|
||||
}
|
||||
|
||||
fun shouldIgnoreCase(pattern: String, ignoreSmartCase: Boolean): Boolean {
|
||||
val sc = smartcase.isSet && !ignoreSmartCase
|
||||
return ignorecase.isSet && !(sc && StringHelper.containsUpperCase(pattern))
|
||||
}
|
||||
|
@ -16,259 +16,230 @@
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package com.maddyhome.idea.vim.helper;
|
||||
@file:JvmName("SearchHighlightsHelper")
|
||||
|
||||
import com.intellij.openapi.editor.Editor;
|
||||
import com.intellij.openapi.editor.EditorFactory;
|
||||
import com.intellij.openapi.editor.colors.EditorColors;
|
||||
import com.intellij.openapi.editor.colors.EditorColorsScheme;
|
||||
import com.intellij.openapi.editor.markup.*;
|
||||
import com.intellij.openapi.fileEditor.FileEditorManager;
|
||||
import com.intellij.openapi.project.Project;
|
||||
import com.intellij.openapi.project.ProjectManager;
|
||||
import com.intellij.ui.ColorUtil;
|
||||
import com.maddyhome.idea.vim.common.TextRange;
|
||||
import com.maddyhome.idea.vim.ex.ranges.LineRange;
|
||||
import com.maddyhome.idea.vim.group.SearchGroup;
|
||||
import com.maddyhome.idea.vim.option.OptionsManager;
|
||||
import org.jetbrains.annotations.Contract;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
package com.maddyhome.idea.vim.helper
|
||||
|
||||
import java.awt.*;
|
||||
import java.util.*;
|
||||
import java.util.List;
|
||||
import com.intellij.openapi.editor.Editor
|
||||
import com.intellij.openapi.editor.EditorFactory
|
||||
import com.intellij.openapi.editor.colors.EditorColors
|
||||
import com.intellij.openapi.editor.colors.EditorColorsScheme
|
||||
import com.intellij.openapi.editor.markup.*
|
||||
import com.intellij.openapi.fileEditor.FileEditorManager
|
||||
import com.intellij.openapi.project.ProjectManager
|
||||
import com.intellij.ui.ColorUtil
|
||||
import com.maddyhome.idea.vim.common.TextRange
|
||||
import com.maddyhome.idea.vim.ex.ranges.LineRange
|
||||
import com.maddyhome.idea.vim.group.SearchGroup
|
||||
import com.maddyhome.idea.vim.group.SearchGroup.SearchOptions
|
||||
import com.maddyhome.idea.vim.option.OptionsManager.hlsearch
|
||||
import com.maddyhome.idea.vim.option.OptionsManager.wrapscan
|
||||
import org.jetbrains.annotations.Contract
|
||||
import java.awt.Color
|
||||
import java.awt.Font
|
||||
import java.util.*
|
||||
|
||||
public class SearchHighlightsHelper {
|
||||
public static void updateSearchHighlights(@Nullable String pattern, boolean shouldIgnoreSmartCase, boolean showHighlights, boolean forceUpdate) {
|
||||
updateSearchHighlights(pattern, shouldIgnoreSmartCase, showHighlights, -1, null, true, forceUpdate);
|
||||
}
|
||||
fun updateSearchHighlights(pattern: String?, shouldIgnoreSmartCase: Boolean, showHighlights: Boolean, forceUpdate: Boolean) {
|
||||
updateSearchHighlights(pattern, shouldIgnoreSmartCase, showHighlights, -1, null, true, forceUpdate)
|
||||
}
|
||||
|
||||
public static int updateIncsearchHighlights(@NotNull Editor editor, @NotNull String pattern, boolean forwards, int caretOffset, @Nullable LineRange searchRange) {
|
||||
final int searchStartOffset = searchRange != null ? EditorHelper.getLineStartOffset(editor, searchRange.startLine) : caretOffset;
|
||||
final boolean showHighlights = OptionsManager.INSTANCE.getHlsearch().isSet();
|
||||
return updateSearchHighlights(pattern, false, showHighlights, searchStartOffset, searchRange, forwards, false);
|
||||
}
|
||||
fun updateIncsearchHighlights(editor: Editor, pattern: String, forwards: Boolean, caretOffset: Int, searchRange: LineRange?): Int {
|
||||
val searchStartOffset = if (searchRange != null) EditorHelper.getLineStartOffset(editor, searchRange.startLine) else caretOffset
|
||||
val showHighlights = hlsearch.isSet
|
||||
return updateSearchHighlights(pattern, false, showHighlights, searchStartOffset, searchRange, forwards, false)
|
||||
}
|
||||
|
||||
@NotNull
|
||||
public static RangeHighlighter addSubstitutionConfirmationHighlight(@NotNull Editor editor, int start, int end) {
|
||||
TextAttributes color = new TextAttributes(
|
||||
editor.getColorsScheme().getColor(EditorColors.SELECTION_FOREGROUND_COLOR),
|
||||
editor.getColorsScheme().getColor(EditorColors.SELECTION_BACKGROUND_COLOR),
|
||||
editor.getColorsScheme().getColor(EditorColors.CARET_COLOR),
|
||||
EffectType.ROUNDED_BOX, Font.PLAIN
|
||||
);
|
||||
return editor.getMarkupModel().addRangeHighlighter(start, end, HighlighterLayer.SELECTION,
|
||||
color, HighlighterTargetArea.EXACT_RANGE);
|
||||
}
|
||||
fun addSubstitutionConfirmationHighlight(editor: Editor, start: Int, end: Int): RangeHighlighter {
|
||||
val color = TextAttributes(
|
||||
editor.colorsScheme.getColor(EditorColors.SELECTION_FOREGROUND_COLOR),
|
||||
editor.colorsScheme.getColor(EditorColors.SELECTION_BACKGROUND_COLOR),
|
||||
editor.colorsScheme.getColor(EditorColors.CARET_COLOR),
|
||||
EffectType.ROUNDED_BOX, Font.PLAIN
|
||||
)
|
||||
return editor.markupModel.addRangeHighlighter(start, end, HighlighterLayer.SELECTION,
|
||||
color, HighlighterTargetArea.EXACT_RANGE)
|
||||
}
|
||||
|
||||
/**
|
||||
* Refreshes current search highlights for all editors of currently active text editor/document
|
||||
*/
|
||||
private static int updateSearchHighlights(@Nullable String pattern, boolean shouldIgnoreSmartCase, boolean showHighlights,
|
||||
int initialOffset, @Nullable LineRange searchRange, boolean forwards, boolean forceUpdate) {
|
||||
int currentMatchOffset = -1;
|
||||
|
||||
ProjectManager projectManager = ProjectManager.getInstanceIfCreated();
|
||||
if (projectManager == null) return currentMatchOffset;
|
||||
Project[] projects = projectManager.getOpenProjects();
|
||||
for (Project project : projects) {
|
||||
Editor current = FileEditorManager.getInstance(project).getSelectedTextEditor();
|
||||
Editor[] editors = current == null ? null : EditorFactory.getInstance().getEditors(current.getDocument(), project);
|
||||
if (editors == null) {
|
||||
continue;
|
||||
/**
|
||||
* Refreshes current search highlights for all editors of currently active text editor/document
|
||||
*/
|
||||
private fun updateSearchHighlights(pattern: String?, shouldIgnoreSmartCase: Boolean, showHighlights: Boolean,
|
||||
initialOffset: Int, searchRange: LineRange?, forwards: Boolean, forceUpdate: Boolean): Int {
|
||||
var currentMatchOffset = -1
|
||||
val projectManager = ProjectManager.getInstanceIfCreated() ?: return currentMatchOffset
|
||||
for (project in projectManager.openProjects) {
|
||||
val current = FileEditorManager.getInstance(project).selectedTextEditor ?: continue
|
||||
// [VERSION UPDATE] 202+ Use editors
|
||||
val editors = EditorFactory.getInstance().getEditors(current.document, project) ?: continue
|
||||
for (editor in editors) {
|
||||
// Try to keep existing highlights if possible. Update if hlsearch has changed or if the pattern has changed.
|
||||
// Force update for the situations where the text is the same, but the ignore case values have changed.
|
||||
// E.g. Use `*` to search for a word (which ignores smartcase), then use `/<Up>` to search for the same pattern,
|
||||
// which will match smartcase. Or changing the smartcase/ignorecase settings
|
||||
if (shouldRemoveSearchHighlights(editor, pattern, showHighlights) || forceUpdate) {
|
||||
removeSearchHighlights(editor)
|
||||
}
|
||||
|
||||
for (final Editor editor : editors) {
|
||||
// Try to keep existing highlights if possible. Update if hlsearch has changed or if the pattern has changed.
|
||||
// Force update for the situations where the text is the same, but the ignore case values have changed.
|
||||
// E.g. Use `*` to search for a word (which ignores smartcase), then use `/<Up>` to search for the same pattern,
|
||||
// which will match smartcase. Or changing the smartcase/ignorecase settings
|
||||
if (shouldRemoveSearchHighlights(editor, pattern, showHighlights) || forceUpdate) {
|
||||
removeSearchHighlights(editor);
|
||||
}
|
||||
if (pattern == null) continue
|
||||
|
||||
if (shouldAddAllSearchHighlights(editor, pattern, showHighlights)) {
|
||||
// hlsearch (+ incsearch/noincsearch)
|
||||
final int startLine = searchRange == null ? 0 : searchRange.startLine;
|
||||
final int endLine = searchRange == null ? -1 : searchRange.endLine;
|
||||
List<TextRange> results = SearchGroup.findAll(editor, pattern, startLine, endLine,
|
||||
SearchHelper.shouldIgnoreCase(pattern, shouldIgnoreSmartCase));
|
||||
if (!results.isEmpty()) {
|
||||
currentMatchOffset = findClosestMatch(editor, results, initialOffset, forwards);
|
||||
highlightSearchResults(editor, pattern, results, currentMatchOffset);
|
||||
}
|
||||
UserDataManager.setVimLastSearch(editor, pattern);
|
||||
if (shouldAddAllSearchHighlights(editor, pattern, showHighlights)) {
|
||||
// hlsearch (+ incsearch/noincsearch)
|
||||
val startLine = searchRange?.startLine ?: 0
|
||||
val endLine = searchRange?.endLine ?: -1
|
||||
val results = SearchGroup.findAll(editor, pattern, startLine, endLine, shouldIgnoreCase(pattern, shouldIgnoreSmartCase))
|
||||
if (results.isNotEmpty()) {
|
||||
currentMatchOffset = findClosestMatch(editor, results, initialOffset, forwards)
|
||||
highlightSearchResults(editor, pattern, results, currentMatchOffset)
|
||||
}
|
||||
else if (shouldAddCurrentMatchSearchHighlight(pattern, showHighlights, initialOffset)) {
|
||||
// nohlsearch + incsearch
|
||||
final boolean wrap = OptionsManager.INSTANCE.getWrapscan().isSet();
|
||||
final EnumSet<SearchGroup.SearchOptions> searchOptions = EnumSet.of(SearchGroup.SearchOptions.WHOLE_FILE);
|
||||
if (wrap) searchOptions.add(SearchGroup.SearchOptions.WRAP);
|
||||
if (shouldIgnoreSmartCase) searchOptions.add(SearchGroup.SearchOptions.IGNORE_SMARTCASE);
|
||||
if (!forwards) searchOptions.add(SearchGroup.SearchOptions.BACKWARDS);
|
||||
final TextRange result = SearchGroup.findIt(editor, pattern, initialOffset, 1, searchOptions);
|
||||
if (result != null && pattern != null) {
|
||||
currentMatchOffset = result.getStartOffset();
|
||||
final List<TextRange> results = Collections.singletonList(result);
|
||||
highlightSearchResults(editor, pattern, results, currentMatchOffset);
|
||||
}
|
||||
editor.vimLastSearch = pattern
|
||||
} else if (shouldAddCurrentMatchSearchHighlight(pattern, showHighlights, initialOffset)) {
|
||||
// nohlsearch + incsearch
|
||||
val searchOptions = EnumSet.of(SearchOptions.WHOLE_FILE)
|
||||
if (wrapscan.isSet) searchOptions.add(SearchOptions.WRAP)
|
||||
if (shouldIgnoreSmartCase) searchOptions.add(SearchOptions.IGNORE_SMARTCASE)
|
||||
if (!forwards) searchOptions.add(SearchOptions.BACKWARDS)
|
||||
val result = SearchGroup.findIt(editor, pattern, initialOffset, 1, searchOptions)
|
||||
if (result != null) {
|
||||
currentMatchOffset = result.startOffset
|
||||
val results = listOf(result)
|
||||
highlightSearchResults(editor, pattern, results, currentMatchOffset)
|
||||
}
|
||||
else if (shouldMaintainCurrentMatchOffset(pattern, initialOffset)) {
|
||||
// incsearch. If nothing has changed (e.g. we've edited offset values in `/foo/e+2`) make sure we return the
|
||||
// current match offset so the caret remains at the current incsarch match
|
||||
final Integer offset = UserDataManager.getVimIncsearchCurrentMatchOffset(editor);
|
||||
if (offset != null) {
|
||||
currentMatchOffset = offset;
|
||||
}
|
||||
} else if (shouldMaintainCurrentMatchOffset(pattern, initialOffset)) {
|
||||
// incsearch. If nothing has changed (e.g. we've edited offset values in `/foo/e+2`) make sure we return the
|
||||
// current match offset so the caret remains at the current incsarch match
|
||||
val offset = editor.vimIncsearchCurrentMatchOffset
|
||||
if (offset != null) {
|
||||
currentMatchOffset = offset
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return currentMatchOffset;
|
||||
}
|
||||
return currentMatchOffset
|
||||
}
|
||||
|
||||
/**
|
||||
* Remove current search highlights if hlSearch is false, or if the pattern is changed
|
||||
*/
|
||||
@Contract("_, _, false -> true; _, null, true -> false")
|
||||
private static boolean shouldRemoveSearchHighlights(@NotNull Editor editor, @Nullable String newPattern, boolean hlSearch) {
|
||||
return !hlSearch || (newPattern != null && !newPattern.equals(UserDataManager.getVimLastSearch(editor)));
|
||||
/**
|
||||
* Remove current search highlights if hlSearch is false, or if the pattern is changed
|
||||
*/
|
||||
@Contract("_, _, false -> true; _, null, true -> false")
|
||||
private fun shouldRemoveSearchHighlights(editor: Editor, newPattern: String?, hlSearch: Boolean): Boolean {
|
||||
return !hlSearch || newPattern != null && newPattern != editor.vimLastSearch
|
||||
}
|
||||
|
||||
private fun removeSearchHighlights(editor: Editor) {
|
||||
editor.vimLastSearch = null
|
||||
val ehl = editor.vimLastHighlighters ?: return
|
||||
for (rh in ehl) {
|
||||
editor.markupModel.removeHighlighter(rh)
|
||||
}
|
||||
editor.vimLastHighlighters = null
|
||||
}
|
||||
|
||||
private static void removeSearchHighlights(@NotNull Editor editor) {
|
||||
UserDataManager.setVimLastSearch(editor, null);
|
||||
/**
|
||||
* Add search highlights if hlSearch is true and the pattern is changed
|
||||
*/
|
||||
@Contract("_, _, false -> false; _, null, true -> false")
|
||||
private fun shouldAddAllSearchHighlights(editor: Editor, newPattern: String?, hlSearch: Boolean): Boolean {
|
||||
return hlSearch && newPattern != null && newPattern != editor.vimLastSearch && newPattern != ""
|
||||
}
|
||||
|
||||
Collection<RangeHighlighter> ehl = UserDataManager.getVimLastHighlighters(editor);
|
||||
if (ehl == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
for (RangeHighlighter rh : ehl) {
|
||||
editor.getMarkupModel().removeHighlighter(rh);
|
||||
}
|
||||
|
||||
ehl.clear();
|
||||
|
||||
UserDataManager.setVimLastHighlighters(editor, null);
|
||||
private fun findClosestMatch(editor: Editor, results: List<TextRange>, initialOffset: Int, forwards: Boolean): Int {
|
||||
if (results.isEmpty() || initialOffset == -1) {
|
||||
return -1
|
||||
}
|
||||
|
||||
/**
|
||||
* Add search highlights if hlSearch is true and the pattern is changed
|
||||
*/
|
||||
@Contract("_, _, false -> false; _, null, true -> false")
|
||||
private static boolean shouldAddAllSearchHighlights(@NotNull Editor editor, @Nullable String newPattern, boolean hlSearch) {
|
||||
return hlSearch && newPattern != null && !newPattern.equals(UserDataManager.getVimLastSearch(editor)) && !Objects.equals(newPattern, "");
|
||||
val size = editor.fileSize
|
||||
val max = Collections.max(results) { r1: TextRange, r2: TextRange ->
|
||||
val d1 = distance(r1, initialOffset, forwards, size)
|
||||
val d2 = distance(r2, initialOffset, forwards, size)
|
||||
if (d1 < 0 && d2 >= 0) {
|
||||
return@max Int.MAX_VALUE
|
||||
}
|
||||
d2 - d1
|
||||
}
|
||||
|
||||
private static int findClosestMatch(@NotNull Editor editor, @NotNull List<TextRange> results, int initialOffset, boolean forwards) {
|
||||
if (results.isEmpty() || initialOffset == -1) {
|
||||
return -1;
|
||||
}
|
||||
|
||||
final int size = EditorHelperRt.getFileSize(editor);
|
||||
final TextRange max = Collections.max(results, (r1, r2) -> {
|
||||
final int d1 = distance(r1, initialOffset, forwards, size);
|
||||
final int d2 = distance(r2, initialOffset, forwards, size);
|
||||
if (d1 < 0 && d2 >= 0) {
|
||||
return Integer.MAX_VALUE;
|
||||
}
|
||||
return d2 - d1;
|
||||
});
|
||||
|
||||
if (!OptionsManager.INSTANCE.getWrapscan().isSet()) {
|
||||
final int start = max.getStartOffset();
|
||||
if (forwards && start < initialOffset) {
|
||||
return -1;
|
||||
}
|
||||
else if (start >= initialOffset) {
|
||||
return -1;
|
||||
}
|
||||
}
|
||||
|
||||
return max.getStartOffset();
|
||||
}
|
||||
|
||||
private static int distance(@NotNull TextRange range, int pos, boolean forwards, int size) {
|
||||
final int start = range.getStartOffset();
|
||||
if (start <= pos) {
|
||||
return forwards ? size - pos + start : pos - start;
|
||||
}
|
||||
else {
|
||||
return forwards ? start - pos : pos + size - start;
|
||||
if (!wrapscan.isSet) {
|
||||
val start = max.startOffset
|
||||
if (forwards && start < initialOffset) {
|
||||
return -1
|
||||
} else if (start >= initialOffset) {
|
||||
return -1
|
||||
}
|
||||
}
|
||||
return max.startOffset
|
||||
}
|
||||
|
||||
public static void highlightSearchResults(@NotNull Editor editor, @NotNull String pattern, List<TextRange> results,
|
||||
int currentMatchOffset) {
|
||||
Collection<RangeHighlighter> highlighters = UserDataManager.getVimLastHighlighters(editor);
|
||||
if (highlighters == null) {
|
||||
highlighters = new ArrayList<>();
|
||||
UserDataManager.setVimLastHighlighters(editor, highlighters);
|
||||
}
|
||||
|
||||
for (TextRange range : results) {
|
||||
final boolean current = range.getStartOffset() == currentMatchOffset;
|
||||
final RangeHighlighter highlighter = highlightMatch(editor, range.getStartOffset(), range.getEndOffset(), current, pattern);
|
||||
highlighters.add(highlighter);
|
||||
}
|
||||
|
||||
UserDataManager.setVimIncsearchCurrentMatchOffset(editor, currentMatchOffset);
|
||||
}
|
||||
|
||||
private static @NotNull RangeHighlighter highlightMatch(@NotNull Editor editor, int start, int end, boolean current, String tooltip) {
|
||||
TextAttributes attributes = editor.getColorsScheme().getAttributes(EditorColors.TEXT_SEARCH_RESULT_ATTRIBUTES);
|
||||
if (current) {
|
||||
// This mimics what IntelliJ does with the Find live preview
|
||||
attributes = attributes.clone();
|
||||
attributes.setEffectType(EffectType.ROUNDED_BOX);
|
||||
attributes.setEffectColor(editor.getColorsScheme().getColor(EditorColors.CARET_COLOR));
|
||||
}
|
||||
if (attributes.getErrorStripeColor() == null) {
|
||||
attributes.setErrorStripeColor(getFallbackErrorStripeColor(attributes, editor.getColorsScheme()));
|
||||
}
|
||||
final RangeHighlighter highlighter = editor.getMarkupModel().addRangeHighlighter(start, end, HighlighterLayer.SELECTION - 1,
|
||||
attributes, HighlighterTargetArea.EXACT_RANGE);
|
||||
highlighter.setErrorStripeTooltip(tooltip);
|
||||
return highlighter;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return a valid error stripe colour based on editor background
|
||||
*
|
||||
* <p>Based on HighlightManager#addRangeHighlight behaviour, which we can't use because it will hide highlights
|
||||
* when hitting Escape.</p>
|
||||
*/
|
||||
private static @Nullable Color getFallbackErrorStripeColor(TextAttributes attributes, EditorColorsScheme colorsScheme) {
|
||||
if (attributes.getBackgroundColor() != null) {
|
||||
boolean isDark = ColorUtil.isDark(colorsScheme.getDefaultBackground());
|
||||
return isDark ? attributes.getBackgroundColor().brighter() : attributes.getBackgroundColor().darker();
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Add search highlight for current match if hlsearch is false and we're performing incsearch highlights
|
||||
*/
|
||||
@Contract("_, true, _ -> false")
|
||||
private static boolean shouldAddCurrentMatchSearchHighlight(@Nullable String pattern, boolean hlSearch, int initialOffset) {
|
||||
return !hlSearch && isIncrementalSearchHighlights(initialOffset) && pattern != null && pattern.length() > 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* Keep the current match offset if the pattern is still valid and we're performing incremental search highlights
|
||||
* This will keep the caret position when editing the offset in e.g. `/foo/e+1`
|
||||
*/
|
||||
@Contract("null, _ -> false")
|
||||
private static boolean shouldMaintainCurrentMatchOffset(@Nullable String pattern, int initialOffset) {
|
||||
return pattern != null && pattern.length() > 0 && isIncrementalSearchHighlights(initialOffset);
|
||||
}
|
||||
|
||||
/**
|
||||
* initialOffset is only valid if we're highlighting incsearch
|
||||
*/
|
||||
@Contract(pure = true)
|
||||
private static boolean isIncrementalSearchHighlights(int initialOffset) {
|
||||
return initialOffset != -1;
|
||||
private fun distance(range: TextRange, pos: Int, forwards: Boolean, size: Int): Int {
|
||||
val start = range.startOffset
|
||||
return if (start <= pos) {
|
||||
if (forwards) size - pos + start else pos - start
|
||||
} else {
|
||||
if (forwards) start - pos else pos + size - start
|
||||
}
|
||||
}
|
||||
|
||||
fun highlightSearchResults(editor: Editor, pattern: String, results: List<TextRange>, currentMatchOffset: Int) {
|
||||
var highlighters = editor.vimLastHighlighters
|
||||
if (highlighters == null) {
|
||||
highlighters = mutableListOf()
|
||||
editor.vimLastHighlighters = highlighters
|
||||
}
|
||||
for (range in results) {
|
||||
val current = range.startOffset == currentMatchOffset
|
||||
val highlighter = highlightMatch(editor, range.startOffset, range.endOffset, current, pattern)
|
||||
highlighters.add(highlighter)
|
||||
}
|
||||
editor.vimIncsearchCurrentMatchOffset = currentMatchOffset
|
||||
}
|
||||
|
||||
private fun highlightMatch(editor: Editor, start: Int, end: Int, current: Boolean, tooltip: String): RangeHighlighter {
|
||||
var attributes = editor.colorsScheme.getAttributes(EditorColors.TEXT_SEARCH_RESULT_ATTRIBUTES)
|
||||
if (current) {
|
||||
// This mimics what IntelliJ does with the Find live preview
|
||||
attributes = attributes.clone()
|
||||
attributes.effectType = EffectType.ROUNDED_BOX
|
||||
attributes.effectColor = editor.colorsScheme.getColor(EditorColors.CARET_COLOR)
|
||||
}
|
||||
if (attributes.errorStripeColor == null) {
|
||||
attributes.errorStripeColor = getFallbackErrorStripeColor(attributes, editor.colorsScheme)
|
||||
}
|
||||
val highlighter = editor.markupModel.addRangeHighlighter(start, end, HighlighterLayer.SELECTION - 1,
|
||||
attributes, HighlighterTargetArea.EXACT_RANGE)
|
||||
highlighter.errorStripeTooltip = tooltip
|
||||
return highlighter
|
||||
}
|
||||
|
||||
/**
|
||||
* Return a valid error stripe colour based on editor background
|
||||
*
|
||||
*
|
||||
* Based on HighlightManager#addRangeHighlight behaviour, which we can't use because it will hide highlights
|
||||
* when hitting Escape.
|
||||
*/
|
||||
private fun getFallbackErrorStripeColor(attributes: TextAttributes, colorsScheme: EditorColorsScheme): Color? {
|
||||
if (attributes.backgroundColor != null) {
|
||||
val isDark = ColorUtil.isDark(colorsScheme.defaultBackground)
|
||||
return if (isDark) attributes.backgroundColor.brighter() else attributes.backgroundColor.darker()
|
||||
}
|
||||
return null
|
||||
}
|
||||
|
||||
/**
|
||||
* Add search highlight for current match if hlsearch is false and we're performing incsearch highlights
|
||||
*/
|
||||
@Contract("_, true, _ -> false")
|
||||
private fun shouldAddCurrentMatchSearchHighlight(pattern: String?, hlSearch: Boolean, initialOffset: Int): Boolean {
|
||||
return !hlSearch && isIncrementalSearchHighlights(initialOffset) && pattern != null && pattern.isNotEmpty()
|
||||
}
|
||||
|
||||
/**
|
||||
* Keep the current match offset if the pattern is still valid and we're performing incremental search highlights
|
||||
* This will keep the caret position when editing the offset in e.g. `/foo/e+1`
|
||||
*/
|
||||
@Contract("null, _ -> false")
|
||||
private fun shouldMaintainCurrentMatchOffset(pattern: String?, initialOffset: Int): Boolean {
|
||||
return pattern != null && pattern.isNotEmpty() && isIncrementalSearchHighlights(initialOffset)
|
||||
}
|
||||
|
||||
/**
|
||||
* initialOffset is only valid if we're highlighting incsearch
|
||||
*/
|
||||
@Contract(pure = true)
|
||||
private fun isIncrementalSearchHighlights(initialOffset: Int) = initialOffset != -1
|
@ -80,7 +80,7 @@ fun unInitializeEditor(editor: Editor) {
|
||||
}
|
||||
|
||||
var Editor.vimLastSearch: String? by userData()
|
||||
var Editor.vimLastHighlighters: Collection<RangeHighlighter>? by userData()
|
||||
var Editor.vimLastHighlighters: MutableCollection<RangeHighlighter>? by userData()
|
||||
var Editor.vimIncsearchCurrentMatchOffset: Int? by userData()
|
||||
/***
|
||||
* @see :help visualmode()
|
||||
|
Loading…
Reference in New Issue
Block a user