diff --git a/src/com/maddyhome/idea/vim/action/macro/PlaybackRegisterAction.kt b/src/com/maddyhome/idea/vim/action/macro/PlaybackRegisterAction.kt index 5a5d18a2ad..dd0113b6fe 100644 --- a/src/com/maddyhome/idea/vim/action/macro/PlaybackRegisterAction.kt +++ b/src/com/maddyhome/idea/vim/action/macro/PlaybackRegisterAction.kt @@ -27,6 +27,7 @@ import com.maddyhome.idea.vim.command.Argument import com.maddyhome.idea.vim.command.Command import com.maddyhome.idea.vim.ex.CommandParser import com.maddyhome.idea.vim.ex.ExException +import com.maddyhome.idea.vim.group.RegisterGroup import com.maddyhome.idea.vim.handler.VimActionHandler class PlaybackRegisterAction : VimActionHandler.SingleExecution() { @@ -44,7 +45,7 @@ class PlaybackRegisterAction : VimActionHandler.SingleExecution() { '@' -> { application.runWriteAction { res.set(VimPlugin.getMacro().playbackLastRegister(editor, context, project, cmd.count)) } } - ':' -> { // No write action + RegisterGroup.LAST_COMMAND_REGISTER -> { // No write action try { res.set(CommandParser.getInstance().processLastCommand(editor, context, cmd.count)) } catch (e: ExException) { diff --git a/src/com/maddyhome/idea/vim/action/motion/search/SearchWholeWordBackwardAction.kt b/src/com/maddyhome/idea/vim/action/motion/search/SearchWholeWordBackwardAction.kt index bce83ac451..866911b872 100644 --- a/src/com/maddyhome/idea/vim/action/motion/search/SearchWholeWordBackwardAction.kt +++ b/src/com/maddyhome/idea/vim/action/motion/search/SearchWholeWordBackwardAction.kt @@ -25,6 +25,7 @@ import com.maddyhome.idea.vim.command.Argument import com.maddyhome.idea.vim.command.CommandFlags import com.maddyhome.idea.vim.command.MotionType import com.maddyhome.idea.vim.handler.MotionActionHandler +import com.maddyhome.idea.vim.helper.Direction import com.maddyhome.idea.vim.helper.enumSetOf import java.util.* @@ -37,7 +38,7 @@ class SearchWholeWordBackwardAction : MotionActionHandler.ForEachCaret() { count: Int, rawCount: Int, argument: Argument?): Int { - return VimPlugin.getSearch().searchWord(editor, caret, count, true, -1) + return VimPlugin.getSearch().searchWord(editor, caret, count, true, Direction.BACKWARDS) } override val motionType: MotionType = MotionType.EXCLUSIVE diff --git a/src/com/maddyhome/idea/vim/action/motion/search/SearchWholeWordForwardAction.kt b/src/com/maddyhome/idea/vim/action/motion/search/SearchWholeWordForwardAction.kt index 758c45419a..ef20c31d6e 100644 --- a/src/com/maddyhome/idea/vim/action/motion/search/SearchWholeWordForwardAction.kt +++ b/src/com/maddyhome/idea/vim/action/motion/search/SearchWholeWordForwardAction.kt @@ -25,6 +25,7 @@ import com.maddyhome.idea.vim.command.Argument import com.maddyhome.idea.vim.command.CommandFlags import com.maddyhome.idea.vim.command.MotionType import com.maddyhome.idea.vim.handler.MotionActionHandler +import com.maddyhome.idea.vim.helper.Direction import com.maddyhome.idea.vim.helper.enumSetOf import java.util.* @@ -37,7 +38,7 @@ class SearchWholeWordForwardAction : MotionActionHandler.ForEachCaret() { count: Int, rawCount: Int, argument: Argument?): Int { - return VimPlugin.getSearch().searchWord(editor, caret, count, true, 1) + return VimPlugin.getSearch().searchWord(editor, caret, count, true, Direction.FORWARDS) } override val motionType: MotionType = MotionType.EXCLUSIVE diff --git a/src/com/maddyhome/idea/vim/action/motion/search/SearchWordBackwardAction.kt b/src/com/maddyhome/idea/vim/action/motion/search/SearchWordBackwardAction.kt index 16922a6779..3457749d68 100644 --- a/src/com/maddyhome/idea/vim/action/motion/search/SearchWordBackwardAction.kt +++ b/src/com/maddyhome/idea/vim/action/motion/search/SearchWordBackwardAction.kt @@ -25,6 +25,7 @@ import com.maddyhome.idea.vim.command.Argument import com.maddyhome.idea.vim.command.CommandFlags import com.maddyhome.idea.vim.command.MotionType import com.maddyhome.idea.vim.handler.MotionActionHandler +import com.maddyhome.idea.vim.helper.Direction import com.maddyhome.idea.vim.helper.enumSetOf import java.util.* @@ -37,7 +38,7 @@ class SearchWordBackwardAction : MotionActionHandler.ForEachCaret() { count: Int, rawCount: Int, argument: Argument?): Int { - return VimPlugin.getSearch().searchWord(editor, caret, count, false, -1) + return VimPlugin.getSearch().searchWord(editor, caret, count, false, Direction.BACKWARDS) } override val motionType: MotionType = MotionType.EXCLUSIVE diff --git a/src/com/maddyhome/idea/vim/action/motion/search/SearchWordForwardAction.kt b/src/com/maddyhome/idea/vim/action/motion/search/SearchWordForwardAction.kt index e6cade0050..66b89f8493 100644 --- a/src/com/maddyhome/idea/vim/action/motion/search/SearchWordForwardAction.kt +++ b/src/com/maddyhome/idea/vim/action/motion/search/SearchWordForwardAction.kt @@ -25,6 +25,7 @@ import com.maddyhome.idea.vim.command.Argument import com.maddyhome.idea.vim.command.CommandFlags import com.maddyhome.idea.vim.command.MotionType import com.maddyhome.idea.vim.handler.MotionActionHandler +import com.maddyhome.idea.vim.helper.Direction import com.maddyhome.idea.vim.helper.enumSetOf import java.util.* @@ -37,7 +38,7 @@ class SearchWordForwardAction : MotionActionHandler.ForEachCaret() { count: Int, rawCount: Int, argument: Argument?): Int { - return VimPlugin.getSearch().searchWord(editor, caret, count, false, 1) + return VimPlugin.getSearch().searchWord(editor, caret, count, false, Direction.FORWARDS) } override val motionType: MotionType = MotionType.EXCLUSIVE diff --git a/src/com/maddyhome/idea/vim/ex/CommandParser.java b/src/com/maddyhome/idea/vim/ex/CommandParser.java index 2fe0751082..6833f63703 100644 --- a/src/com/maddyhome/idea/vim/ex/CommandParser.java +++ b/src/com/maddyhome/idea/vim/ex/CommandParser.java @@ -26,11 +26,11 @@ import com.maddyhome.idea.vim.VimPlugin; import com.maddyhome.idea.vim.command.SelectionType; import com.maddyhome.idea.vim.common.Register; -import com.maddyhome.idea.vim.common.TextRange; import com.maddyhome.idea.vim.ex.handler.GotoLineHandler; import com.maddyhome.idea.vim.ex.ranges.Range; import com.maddyhome.idea.vim.ex.ranges.Ranges; import com.maddyhome.idea.vim.group.HistoryGroup; +import com.maddyhome.idea.vim.group.RegisterGroup; import com.maddyhome.idea.vim.helper.MessageHelper; import com.maddyhome.idea.vim.helper.Msg; import org.jetbrains.annotations.NotNull; @@ -177,8 +177,7 @@ private void processCommand(@NotNull Editor editor, @NotNull DataContext context ThrowableComputable runCommand = () -> { boolean ok = handler.process(editor, context, command, count); if (ok && !handler.getArgFlags().getFlags().contains(CommandHandler.Flag.DONT_SAVE_LAST)) { - VimPlugin.getRegister().storeTextInternal(editor, new TextRange(-1, -1), cmd, - SelectionType.CHARACTER_WISE, ':', false); + VimPlugin.getRegister().storeTextSpecial(RegisterGroup.LAST_COMMAND_REGISTER, cmd); } return null; }; diff --git a/src/com/maddyhome/idea/vim/extension/multiplecursors/VimMultipleCursorsExtension.kt b/src/com/maddyhome/idea/vim/extension/multiplecursors/VimMultipleCursorsExtension.kt index 96538913c6..cc5a9bb650 100644 --- a/src/com/maddyhome/idea/vim/extension/multiplecursors/VimMultipleCursorsExtension.kt +++ b/src/com/maddyhome/idea/vim/extension/multiplecursors/VimMultipleCursorsExtension.kt @@ -33,13 +33,9 @@ import com.maddyhome.idea.vim.extension.VimExtensionFacade.putKeyMapping import com.maddyhome.idea.vim.extension.VimExtensionHandler import com.maddyhome.idea.vim.group.MotionGroup import com.maddyhome.idea.vim.group.visual.vimSetSelection -import com.maddyhome.idea.vim.helper.EditorHelper -import com.maddyhome.idea.vim.helper.MessageHelper +import com.maddyhome.idea.vim.helper.* import com.maddyhome.idea.vim.helper.SearchHelper.findWordUnderCursor import com.maddyhome.idea.vim.helper.StringHelper.parseKeys -import com.maddyhome.idea.vim.helper.endOffsetInclusive -import com.maddyhome.idea.vim.helper.exitVisualMode -import com.maddyhome.idea.vim.helper.inVisualMode import com.maddyhome.idea.vim.option.OptionsManager import org.jetbrains.annotations.NonNls import java.lang.Integer.min @@ -242,7 +238,7 @@ class VimMultipleCursorsExtension : VimExtension { val wordRange = VimPlugin.getMotion().getWordRange(editor, caret, 1, false, false) caret.vimSetSelection(wordRange.startOffset, wordRange.endOffsetInclusive, true) - val offset = VimPlugin.getSearch().searchWord(editor, caret, 1, whole, 1) + val offset = VimPlugin.getSearch().searchWord(editor, caret, 1, whole, Direction.FORWARDS) MotionGroup.moveCaret(editor, caret, range.endOffset - 1) return offset diff --git a/src/com/maddyhome/idea/vim/group/RegisterGroup.java b/src/com/maddyhome/idea/vim/group/RegisterGroup.java index 321469f12e..d8cd34e440 100644 --- a/src/com/maddyhome/idea/vim/group/RegisterGroup.java +++ b/src/com/maddyhome/idea/vim/group/RegisterGroup.java @@ -18,7 +18,6 @@ package com.maddyhome.idea.vim.group; -import com.google.common.collect.ImmutableList; import com.intellij.codeInsight.editorActions.CopyPastePostProcessor; import com.intellij.codeInsight.editorActions.CopyPastePreProcessor; import com.intellij.codeInsight.editorActions.TextBlockTransferable; @@ -81,18 +80,33 @@ @Storage(value = "$APP_CONFIG$/vim_settings_local.xml", roamingType = RoamingType.DISABLED) }) public class RegisterGroup implements PersistentStateComponent { - private static final @NonNls String WRITABLE_REGISTERS = "0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ-*+_/\""; - private static final String READONLY_REGISTERS = ":.%#=/"; - private static final @NonNls String RECORDABLE_REGISTER = "0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ"; - private static final String PLAYBACK_REGISTER = RECORDABLE_REGISTER + "\".*+"; + public static final char UNNAMED_REGISTER = '"'; + public static final char LAST_SEARCH_REGISTER = '/'; // IdeaVim does not supporting writing to this register + public static final char LAST_COMMAND_REGISTER = ':'; + private static final char LAST_INSERTED_TEXT_REGISTER = '.'; + public static final char SMALL_DELETION_REGISTER = '-'; + private static final char BLACK_HOLE_REGISTER = '_'; + private static final char ALTERNATE_BUFFER_REGISTER = '#'; // Not supported + private static final char EXPRESSION_BUFFER_REGISTER = '='; // Not supported + private static final char CURRENT_FILENAME_REGISTER = '%'; // Not supported + private static final @NonNls String CLIPBOARD_REGISTERS = "*+"; + private static final @NonNls String NUMBERED_REGISTERS = "0123456789"; + private static final @NonNls String NAMED_REGISTERS = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ"; + + private static final @NonNls String WRITABLE_REGISTERS = NUMBERED_REGISTERS + NAMED_REGISTERS + CLIPBOARD_REGISTERS + + SMALL_DELETION_REGISTER + BLACK_HOLE_REGISTER + UNNAMED_REGISTER + LAST_SEARCH_REGISTER; + private static final String READONLY_REGISTERS = "" + + CURRENT_FILENAME_REGISTER + LAST_COMMAND_REGISTER + LAST_INSERTED_TEXT_REGISTER + ALTERNATE_BUFFER_REGISTER + + EXPRESSION_BUFFER_REGISTER; // Expression buffer is not actually readonly + private static final @NonNls String RECORDABLE_REGISTERS = NUMBERED_REGISTERS + NAMED_REGISTERS; + private static final String PLAYBACK_REGISTERS = RECORDABLE_REGISTERS + UNNAMED_REGISTER + CLIPBOARD_REGISTERS + LAST_INSERTED_TEXT_REGISTER; private static final String VALID_REGISTERS = WRITABLE_REGISTERS + READONLY_REGISTERS; - private static final List CLIPBOARD_REGISTERS = ImmutableList.of('*', '+'); + private static final Logger logger = Logger.getInstance(RegisterGroup.class.getName()); - public static final char UNNAMED_REGISTER = '"'; + private final @NotNull HashMap registers = new HashMap<>(); private char defaultRegister = UNNAMED_REGISTER; private char lastRegister = defaultRegister; - private final @NotNull HashMap registers = new HashMap<>(); private char recordRegister = 0; private @Nullable List recordList = null; @@ -173,10 +187,35 @@ public boolean storeText(@NotNull Editor editor, @NotNull TextRange range, @NotN return false; } - public boolean storeTextInternal(@NotNull Editor editor, @NotNull TextRange range, @NotNull String text, - @NotNull SelectionType type, char register, boolean isDelete) { - // Null register doesn't get saved - if (lastRegister == '_') return true; + /** + * Stores text, character wise, in the given special register + * + *

This method is intended to support writing to registers when the text cannot be yanked from an editor. This is + * expected to only be used to update the search and command registers. It will not update named registers.

+ * + *

While this method allows setting the unnamed register, this should only be done from tests, and only when it's + * not possible to yank or cut from the fixture editor. This method will skip additional text processing, and won't + * update other registers such as the small delete register or reorder the numbered registers. It is much more + * preferable to yank from the fixture editor.

+ * + * @param register The register to use for storing the text. Cannot be a normal text register + * @param text The text to store, without further processing + * @return True if the text is stored, false if the passed register is not supported + */ + public boolean storeTextSpecial(char register, @NotNull String text) { + if (READONLY_REGISTERS.indexOf(register) == -1 && register != LAST_SEARCH_REGISTER + && register != UNNAMED_REGISTER) { + return false; + } + registers.put(register, new Register(register, SelectionType.CHARACTER_WISE, text, new ArrayList<>())); + if (logger.isDebugEnabled()) logger.debug("register '" + register + "' contains: \"" + text + "\""); + return true; + } + + private boolean storeTextInternal(@NotNull Editor editor, @NotNull TextRange range, @NotNull String text, + @NotNull SelectionType type, char register, boolean isDelete) { + // Null register doesn't get saved, but acts like it was + if (lastRegister == BLACK_HOLE_REGISTER) return true; int start = range.getStartOffset(); int end = range.getEndOffset(); @@ -219,7 +258,7 @@ public boolean storeTextInternal(@NotNull Editor editor, @NotNull TextRange rang if (logger.isDebugEnabled()) logger.debug("register '" + register + "' contains: \"" + processedText + "\""); } - if (CLIPBOARD_REGISTERS.contains(register)) { + if (CLIPBOARD_REGISTERS.indexOf(register) >= 0) { ClipboardHandler.setClipboardText(processedText, new ArrayList<>(transferableData), text); } @@ -248,7 +287,7 @@ public boolean storeTextInternal(@NotNull Editor editor, @NotNull TextRange rang // Deletes smaller than one line and without specified register go the the "-" register if (smallInlineDeletion && register == defaultRegister) { - registers.put('-', new Register('-', type, processedText, new ArrayList<>(transferableData))); + registers.put(SMALL_DELETION_REGISTER, new Register(SMALL_DELETION_REGISTER, type, processedText, new ArrayList<>(transferableData))); } } // Yanks also go to register 0 if the default register was used @@ -338,7 +377,7 @@ private boolean isSmallDeletionSpecialCase(Editor editor) { } public @Nullable Register getPlaybackRegister(char r) { - if (PLAYBACK_REGISTER.indexOf(r) != 0) { + if (PLAYBACK_REGISTERS.indexOf(r) != 0) { return getRegister(r); } else { @@ -351,7 +390,7 @@ private boolean isSmallDeletionSpecialCase(Editor editor) { if (Character.isUpperCase(r)) { r = Character.toLowerCase(r); } - return CLIPBOARD_REGISTERS.contains(r) ? refreshClipboardRegister(r) : registers.get(r); + return CLIPBOARD_REGISTERS.indexOf(r) >= 0 ? refreshClipboardRegister(r) : registers.get(r); } public void saveRegister(char r, Register register) { @@ -359,7 +398,7 @@ public void saveRegister(char r, Register register) { if (Character.isUpperCase(r)) { r = Character.toLowerCase(r); } - if (CLIPBOARD_REGISTERS.contains(r)) { + if (CLIPBOARD_REGISTERS.indexOf(r) >= 0) { ClipboardHandler.setClipboardText(register.getText(), new ArrayList<>(register.getTransferableData()), register.getRawText()); } registers.put(r, register); @@ -383,7 +422,8 @@ public char getDefaultRegister() { public @NotNull List getRegisters() { final List res = new ArrayList<>(registers.values()); - for (Character r : CLIPBOARD_REGISTERS) { + for (int i = 0; i < CLIPBOARD_REGISTERS.length(); i++) { + final char r = CLIPBOARD_REGISTERS.charAt(i); final Register register = refreshClipboardRegister(r); if (register != null) { res.add(register); @@ -394,7 +434,7 @@ public char getDefaultRegister() { } public boolean startRecording(Editor editor, char register) { - if (RECORDABLE_REGISTER.indexOf(register) != -1) { + if (RECORDABLE_REGISTERS.indexOf(register) != -1) { CommandState.getInstance(editor).setRecording(true); recordRegister = register; recordList = new ArrayList<>(); diff --git a/src/com/maddyhome/idea/vim/group/SearchGroup.java b/src/com/maddyhome/idea/vim/group/SearchGroup.java index 97a8c65ab0..a47201499e 100644 --- a/src/com/maddyhome/idea/vim/group/SearchGroup.java +++ b/src/com/maddyhome/idea/vim/group/SearchGroup.java @@ -25,20 +25,15 @@ import com.intellij.openapi.components.Storage; import com.intellij.openapi.diagnostic.Logger; import com.intellij.openapi.editor.*; -import com.intellij.openapi.editor.colors.EditorColors; -import com.intellij.openapi.editor.colors.EditorColorsScheme; import com.intellij.openapi.editor.event.DocumentEvent; import com.intellij.openapi.editor.event.DocumentListener; import com.intellij.openapi.editor.markup.*; -import com.intellij.openapi.fileEditor.FileEditorManager; import com.intellij.openapi.fileEditor.FileEditorManagerEvent; import com.intellij.openapi.project.Project; import com.intellij.openapi.project.ProjectManager; import com.intellij.openapi.util.Ref; -import com.intellij.ui.ColorUtil; import com.maddyhome.idea.vim.VimPlugin; import com.maddyhome.idea.vim.command.CommandFlags; -import com.maddyhome.idea.vim.command.SelectionType; import com.maddyhome.idea.vim.common.CharacterPosition; import com.maddyhome.idea.vim.common.TextRange; import com.maddyhome.idea.vim.ex.ranges.LineRange; @@ -49,8 +44,8 @@ import com.maddyhome.idea.vim.regexp.CharPointer; import com.maddyhome.idea.vim.regexp.CharacterClasses; import com.maddyhome.idea.vim.regexp.RegExp; -import com.maddyhome.idea.vim.ui.ex.ExEntryPanel; import com.maddyhome.idea.vim.ui.ModalEntry; +import com.maddyhome.idea.vim.ui.ex.ExEntryPanel; import kotlin.jvm.functions.Function1; import org.jdom.Element; import org.jetbrains.annotations.Contract; @@ -59,11 +54,14 @@ import org.jetbrains.annotations.Nullable; import javax.swing.*; -import java.awt.*; import java.text.NumberFormat; import java.text.ParsePosition; +import java.util.Collection; +import java.util.EnumSet; +import java.util.Iterator; import java.util.List; -import java.util.*; + +import static com.maddyhome.idea.vim.helper.SearchHelperKtKt.shouldIgnoreCase; @State(name = "VimSearchSettings", storages = { @Storage(value = "$APP_CONFIG$/vim_settings_local.xml", roamingType = RoamingType.DISABLED) @@ -82,9 +80,6 @@ public SearchGroup() { } }; options.getIgnorecase().addOptionChangeListener(updateHighlightsIfVisible); - - // It appears that when changing smartcase, Vim only redraws the highlights when the screen is redrawn. We can't - // reliably copy that, so do the most intuitive thing options.getSmartcase().addOptionChangeListener(updateHighlightsIfVisible); } @@ -105,7 +100,7 @@ public void turnOff() { // This method is used in AceJump integration plugin @SuppressWarnings("unused") public int getLastDir() { - return lastDir; + return lastDir.toInt(); } public @Nullable String getLastPattern() { @@ -115,15 +110,13 @@ public int getLastDir() { public void resetState() { lastSearch = lastPattern = lastSubstitute = lastReplace = lastOffset = null; lastIgnoreSmartCase = false; - lastDir = 0; + lastDir = Direction.UNSET; resetShowSearchHighlight(); } - private void setLastPattern(@NotNull Editor editor, @NotNull String lastPattern) { + private void setLastPattern(@NotNull String lastPattern) { this.lastPattern = lastPattern; - VimPlugin.getRegister().storeTextInternal(editor, new TextRange(-1, -1), - lastPattern, SelectionType.CHARACTER_WISE, '/', false); - + VimPlugin.getRegister().storeTextSpecial(RegisterGroup.LAST_SEARCH_REGISTER, lastPattern); VimPlugin.getHistory().addEntry(HistoryGroup.SEARCH, lastPattern); } @@ -218,13 +211,6 @@ private void setLastPattern(@NotNull Editor editor, @NotNull String lastPattern) return result.get(); } - private 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 int search(@NotNull Editor editor, @NotNull String command, int count, EnumSet flags, boolean moveCursor) { return search(editor, editor.getCaretModel().getPrimaryCaret(), command, count, flags, moveCursor); } @@ -242,12 +228,12 @@ public int search(@NotNull Editor editor, @NotNull Caret caret, @NotNull String } public int search(@NotNull Editor editor, @NotNull String command, int startOffset, int count, @NotNull EnumSet flags) { - int dir = DIR_FORWARDS; + Direction dir = Direction.FORWARDS; char type = '/'; String pattern = lastSearch; String offset = lastOffset; if (flags.contains(CommandFlags.FLAG_SEARCH_REV)) { - dir = DIR_BACKWARDS; + dir = Direction.BACKWARDS; type = '?'; } @@ -284,7 +270,7 @@ else if (command.length() == 1) { lastSearch = pattern; lastIgnoreSmartCase = false; if (pattern != null) { - setLastPattern(editor, pattern); + setLastPattern(pattern); } lastOffset = offset; lastDir = dir; @@ -301,7 +287,7 @@ else if (command.length() == 1) { return findItOffset(editor, startOffset, count, lastDir); } - public int searchWord(@NotNull Editor editor, @NotNull Caret caret, int count, boolean whole, int dir) { + public int searchWord(@NotNull Editor editor, @NotNull Caret caret, int count, boolean whole, Direction dir) { TextRange range = SearchHelper.findWordUnderCursor(editor, caret); if (range == null) { logger.warn("No range was found"); @@ -321,7 +307,7 @@ public int searchWord(@NotNull Editor editor, @NotNull Caret caret, int count, b lastSearch = pattern.toString(); lastIgnoreSmartCase = true; - setLastPattern(editor, lastSearch); + setLastPattern(lastSearch); lastOffset = ""; lastDir = dir; @@ -336,16 +322,16 @@ public int searchNext(@NotNull Editor editor, @NotNull Caret caret, int count) { } public int searchPrevious(@NotNull Editor editor, @NotNull Caret caret, int count) { - return searchNextWithDirection(editor, caret, count, -lastDir); + return searchNextWithDirection(editor, caret, count, lastDir.reverse()); } public int searchNextFromOffset(@NotNull Editor editor, int offset, int count) { resetShowSearchHighlight(); updateSearchHighlights(); - return findItOffset(editor, offset, count, 1); + return findItOffset(editor, offset, count, Direction.FORWARDS); } - private int searchNextWithDirection(@NotNull Editor editor, @NotNull Caret caret, int count, int dir) { + private int searchNextWithDirection(@NotNull Editor editor, @NotNull Caret caret, int count, Direction dir) { resetShowSearchHighlight(); updateSearchHighlights(); final int startOffset = caret.getOffset(); @@ -369,163 +355,25 @@ public void clearSearchHighlight() { } private void forceUpdateSearchHighlights() { - updateSearchHighlights(lastSearch, lastIgnoreSmartCase, showSearchHighlight, true); + SearchHighlightsHelper.updateSearchHighlights(lastSearch, lastIgnoreSmartCase, showSearchHighlight, true); } private void updateSearchHighlights() { - updateSearchHighlights(lastSearch, lastIgnoreSmartCase, showSearchHighlight, false); + SearchHighlightsHelper.updateSearchHighlights(lastSearch, lastIgnoreSmartCase, showSearchHighlight, false); } public void resetIncsearchHighlights() { - updateSearchHighlights(lastSearch, lastIgnoreSmartCase, showSearchHighlight, true); - } - - public 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); - } - - private void updateSearchHighlights(@Nullable String pattern, boolean shouldIgnoreSmartCase, boolean showHighlights, boolean forceUpdate) { - updateSearchHighlights(pattern, shouldIgnoreSmartCase, showHighlights, -1, null, true, forceUpdate); - } - - /** - * Refreshes current search highlights for all editors of currently active text editor/document - */ - private 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; - } - - for (final Editor editor : editors) { - // 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 `/` to search for the same pattern, - // which will match smartcase. Or changing the smartcase/ignorecase settings - if (shouldRemoveSearchHighlight(editor, pattern, showHighlights) || forceUpdate) { - removeSearchHighlight(editor); - } - - if (shouldAddAllSearchHighlights(editor, pattern, showHighlights)) { - final int startLine = searchRange == null ? 0 : searchRange.startLine; - final int endLine = searchRange == null ? -1 : searchRange.endLine; - List results = findAll(editor, pattern, startLine, endLine, shouldIgnoreCase(pattern, shouldIgnoreSmartCase)); - if (!results.isEmpty()) { - currentMatchOffset = findClosestMatch(editor, results, initialOffset, forwards); - highlightSearchResults(editor, pattern, results, currentMatchOffset); - } - UserDataManager.setVimLastSearch(editor, pattern); - } - else if (shouldAddCurrentMatchSearchHighlight(pattern, showHighlights, initialOffset)) { - final boolean wrap = OptionsManager.INSTANCE.getWrapscan().isSet(); - final EnumSet searchOptions = EnumSet.of(SearchOptions.WHOLE_FILE); - if (wrap) searchOptions.add(SearchOptions.WRAP); - if (shouldIgnoreSmartCase) searchOptions.add(SearchOptions.IGNORE_SMARTCASE); - if (!forwards) searchOptions.add(SearchOptions.BACKWARDS); - final TextRange result = findIt(editor, pattern, initialOffset, 1, searchOptions); - if (result != null && pattern != null) { - currentMatchOffset = result.getStartOffset(); - final List results = Collections.singletonList(result); - highlightSearchResults(editor, pattern, results, currentMatchOffset); - } - } - else if (shouldMaintainCurrentMatchOffset(pattern, initialOffset)) { - final Integer offset = UserDataManager.getVimIncsearchCurrentMatchOffset(editor); - if (offset != null) { - currentMatchOffset = offset; - } - } - } - } - - return currentMatchOffset; - } - - /** - * Remove current search highlights if hlSearch is false, or if the pattern is changed - */ - @Contract("_, _, false -> true; _, null, true -> false") - private boolean shouldRemoveSearchHighlight(@NotNull Editor editor, String newPattern, boolean hlSearch) { - return !hlSearch || (newPattern != null && !newPattern.equals(UserDataManager.getVimLastSearch(editor))); - } - - /** - * Add search highlights if hlSearch is true and the pattern is changed - */ - @Contract("_, _, false -> false; _, null, true -> false") - private boolean shouldAddAllSearchHighlights(@NotNull Editor editor, @Nullable String newPattern, boolean hlSearch) { - return hlSearch && newPattern != null && !newPattern.equals(UserDataManager.getVimLastSearch(editor)) && !Objects.equals(newPattern, ""); - } - - /** - * Add search highlight for current match if hlsearch is false and we're performing incsearch highlights - */ - @Contract("_, true, _ -> false") - private 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 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 boolean isIncrementalSearchHighlights(int initialOffset) { - return initialOffset != -1; + SearchHighlightsHelper.updateSearchHighlights(lastSearch, lastIgnoreSmartCase, showSearchHighlight, true); } private void highlightSearchLines(@NotNull Editor editor, int startLine, int endLine) { if (lastSearch != null) { - final List results = findAll(editor, lastSearch, startLine, endLine, shouldIgnoreCase(lastSearch, lastIgnoreSmartCase)); - highlightSearchResults(editor, lastSearch, results, -1); + final List results = findAll(editor, lastSearch, startLine, endLine, + shouldIgnoreCase(lastSearch, lastIgnoreSmartCase)); + SearchHighlightsHelper.highlightSearchResults(editor, lastSearch, results, -1); } } - private int findClosestMatch(@NotNull Editor editor, @NotNull List 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(); - } - public @Nullable TextRange getNextSearchRange(@NotNull Editor editor, int count, boolean forwards) { editor.getCaretModel().removeSecondaryCarets(); TextRange current = findUnderCaret(editor); @@ -575,23 +423,13 @@ private boolean atEdgeOfGnRange(@NotNull TextRange nextRange, @NotNull Editor ed return findIt(editor, lastSearch, startOffset, 1, searchOptions); } - 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; - } - } - - private static TextRange findIt(@NotNull Editor editor, @Nullable String pattern, int startOffset, int count, EnumSet searchOptions) { + public static TextRange findIt(@NotNull Editor editor, @Nullable String pattern, int startOffset, int count, EnumSet searchOptions) { if (pattern == null || pattern.length() == 0) { logger.warn("Pattern is null or empty. Cannot perform search"); return null; } - int dir = searchOptions.contains(SearchOptions.BACKWARDS) ? DIR_BACKWARDS : DIR_FORWARDS; + Direction dir = searchOptions.contains(SearchOptions.BACKWARDS) ? Direction.BACKWARDS : Direction.FORWARDS; //RE sp; RegExp sp; @@ -629,7 +467,7 @@ private static TextRange findIt(@NotNull Editor editor, @Nullable String pattern int loop; RegExp.lpos_T start_pos; boolean at_first_line; - int extra_col = dir == DIR_FORWARDS ? 1 : 0; + int extra_col = dir == Direction.FORWARDS ? 1 : 0; boolean match_ok; long nmatched; //int submatch = 0; @@ -653,7 +491,7 @@ private static TextRange findIt(@NotNull Editor editor, @Nullable String pattern * Start searching in current line, unless searching backwards and * we're in column 0. */ - if (dir == DIR_BACKWARDS && start_pos.col == 0) { + if (dir == Direction.BACKWARDS && start_pos.col == 0) { lnum = pos.lnum - 1; at_first_line = false; } @@ -667,7 +505,7 @@ private static TextRange findIt(@NotNull Editor editor, @Nullable String pattern startLine = lnum; endLine = lnum + 1; } - for (; lnum >= startLine && lnum < endLine; lnum += dir, at_first_line = false) { + for (; lnum >= startLine && lnum < endLine; lnum += dir.toInt(), at_first_line = false) { /* * Look for a match somewhere in the line. */ @@ -684,7 +522,7 @@ private static TextRange findIt(@NotNull Editor editor, @Nullable String pattern * the start position. If not, continue at the end of the * match (this is vi compatible) or on the next char. */ - if (dir == DIR_FORWARDS && at_first_line) { + if (dir == Direction.FORWARDS && at_first_line) { match_ok = true; /* * When match lands on a NUL the cursor will be put @@ -722,7 +560,7 @@ private static TextRange findIt(@NotNull Editor editor, @Nullable String pattern continue; } } - if (dir == DIR_BACKWARDS) { + if (dir == Direction.BACKWARDS) { /* * Now, if there are multiple matches on this line, * we have to get the last one. Or the last one before @@ -816,7 +654,7 @@ private static TextRange findIt(@NotNull Editor editor, @Nullable String pattern * is redrawn. The keep_msg is cleared whenever another message is * written. */ - if (dir == DIR_BACKWARDS) /* start second loop at the other end */ { + if (dir == Direction.BACKWARDS) /* start second loop at the other end */ { lnum = lineCount - 1; //if (!shortmess(SHM_SEARCH) && (options & SEARCH_MSG)) // give_warning((char_u *)_(top_bot_msg), TRUE); @@ -856,24 +694,7 @@ else if (lnum <= 0) { new CharacterPosition(endpos.lnum, endpos.col).toOffset(editor)); } - private static void highlightSearchResults(@NotNull Editor editor, @NotNull String pattern, List results, - int currentMatchOffset) { - Collection 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 int findItOffset(@NotNull Editor editor, int startOffset, int count, int dir) { + private int findItOffset(@NotNull Editor editor, int startOffset, int count, Direction dir) { boolean wrap = OptionsManager.INSTANCE.getWrapscan().isSet(); logger.debug("Perform search. Direction: " + dir + " wrap: " + wrap); @@ -936,7 +757,7 @@ private int findItOffset(@NotNull Editor editor, int startOffset, int count, int } EnumSet searchOptions = EnumSet.of(SearchOptions.SHOW_MESSAGES, SearchOptions.WHOLE_FILE); - if (dir == DIR_BACKWARDS) searchOptions.add(SearchOptions.BACKWARDS); + if (dir == Direction.BACKWARDS) searchOptions.add(SearchOptions.BACKWARDS); if (lastIgnoreSmartCase) searchOptions.add(SearchOptions.IGNORE_SMARTCASE); if (wrap) searchOptions.add(SearchOptions.WRAP); if (hasEndOffset) searchOptions.add(SearchOptions.WANT_ENDPOS); @@ -1165,7 +986,7 @@ else if (cmd.charAt() != 'p') { lastSubstitute = pattern; lastSearch = pattern; if (pattern != null) { - setLastPattern(editor, pattern); + setLastPattern(pattern); } int start = editor.getDocument().getLineStartOffset(line1); @@ -1248,7 +1069,7 @@ else if (do_ic == 'I') { if (do_all || line != lastLine) { boolean doReplace = true; if (do_ask) { - RangeHighlighter hl = highlightConfirm(editor, startoff, endoff); + RangeHighlighter hl = SearchHighlightsHelper.addSubstitutionConfirmationHighlight(editor, startoff, endoff); final ReplaceConfirmationChoice choice = confirmChoice(editor, match, caret, startoff); editor.getMarkupModel().removeHighlighter(hl); switch (choice) { @@ -1319,65 +1140,6 @@ else if (do_ic == 'I') { return true; } - private @NotNull RangeHighlighter highlightConfirm(@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); - } - - 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 - * - * Based on HighlightManager#addRangeHighlight behaviour, which we can't use because it will hide highlights when - * hitting Escape - */ - 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; - } - - private static void removeSearchHighlight(@NotNull Editor editor) { - UserDataManager.setVimLastSearch(editor, null); - - Collection ehl = UserDataManager.getVimLastHighlighters(editor); - if (ehl == null) { - return; - } - - for (RangeHighlighter rh : ehl) { - editor.getMarkupModel().removeHighlighter(rh); - } - - ehl.clear(); - - UserDataManager.setVimLastHighlighters(editor, null); - } - public void saveData(@NotNull Element element) { logger.debug("saveData"); Element search = new Element("search"); @@ -1397,7 +1159,7 @@ public void saveData(@NotNull Element element) { search.addContent(createElementWithText("last-substitute", lastSubstitute)); } Element text = new Element("last-dir"); - text.addContent(Integer.toString(lastDir)); + text.addContent(Integer.toString(lastDir.toInt())); search.addContent(text); text = new Element("show-last"); @@ -1426,7 +1188,7 @@ public void readData(@NotNull Element element) { lastSubstitute = getSafeChildText(search, "last-substitute"); Element dir = search.getChild("last-dir"); - lastDir = Integer.parseInt(dir.getText()); + lastDir = Direction.Companion.fromInt(Integer.parseInt(dir.getText())); Element show = search.getChild("show-last"); final ListOption vimInfo = OptionsManager.INSTANCE.getViminfo(); @@ -1523,7 +1285,7 @@ private enum ReplaceConfirmationChoice { SUBSTITUTE_ALL, } - private enum SearchOptions { + public enum SearchOptions { BACKWARDS, WANT_ENDPOS, WRAP, @@ -1538,7 +1300,7 @@ private enum SearchOptions { private @Nullable String lastReplace; private @Nullable String lastOffset; private boolean lastIgnoreSmartCase; - private int lastDir; + private Direction lastDir; private boolean showSearchHighlight = OptionsManager.INSTANCE.getHlsearch().isSet(); private boolean do_all = false; /* do multiple substitutions per line */ @@ -1551,8 +1313,5 @@ private enum SearchOptions { private static final int RE_SEARCH = 2; private static final int RE_SUBST = 3; - private static final int DIR_FORWARDS = 1; - private static final int DIR_BACKWARDS = -1; - private static final Logger logger = Logger.getInstance(SearchGroup.class.getName()); } diff --git a/src/com/maddyhome/idea/vim/helper/SearchHelper.java b/src/com/maddyhome/idea/vim/helper/SearchHelper.java index 1926f755fd..474876f23d 100644 --- a/src/com/maddyhome/idea/vim/helper/SearchHelper.java +++ b/src/com/maddyhome/idea/vim/helper/SearchHelper.java @@ -103,7 +103,7 @@ public static int findUnmatchedBlock(@NotNull Editor editor, @NotNull Caret care int pos = caret.getOffset(); int loc = blockChars.indexOf(type); // What direction should we go now (-1 is backward, 1 is forward) - Direction dir = loc % 2 == 0 ? Direction.BACK : Direction.FORWARD; + Direction dir = loc % 2 == 0 ? Direction.BACKWARDS : Direction.FORWARDS; // Which character did we find and which should we now search for char match = blockChars.charAt(loc); char found = blockChars.charAt(loc - dir.toInt()); @@ -163,10 +163,10 @@ public static int findUnmatchedBlock(@NotNull Editor editor, @NotNull Caret care int endOffset = quoteRange.getEndOffset(); CharSequence subSequence = chars.subSequence(startOffset, endOffset); int inQuotePos = pos - startOffset; - int inQuoteStart = findBlockLocation(subSequence, close, type, Direction.BACK, inQuotePos, count, false); + int inQuoteStart = findBlockLocation(subSequence, close, type, Direction.BACKWARDS, inQuotePos, count, false); if (inQuoteStart != -1) { startPosInStringFound = true; - int inQuoteEnd = findBlockLocation(subSequence, type, close, Direction.FORWARD, inQuoteStart, 1, false); + int inQuoteEnd = findBlockLocation(subSequence, type, close, Direction.FORWARDS, inQuoteStart, 1, false); if (inQuoteEnd != -1) { bstart = inQuoteStart + startOffset; bend = inQuoteEnd + startOffset; @@ -176,9 +176,9 @@ public static int findUnmatchedBlock(@NotNull Editor editor, @NotNull Caret care } if (!startPosInStringFound) { - bstart = findBlockLocation(chars, close, type, Direction.BACK, pos, count, false); + bstart = findBlockLocation(chars, close, type, Direction.BACKWARDS, pos, count, false); if (bstart != -1) { - bend = findBlockLocation(chars, type, close, Direction.FORWARD, bstart, 1, false); + bend = findBlockLocation(chars, type, close, Direction.FORWARDS, bstart, 1, false); } } @@ -294,7 +294,7 @@ public static int findMatchingPairOnCurrentLine(@NotNull Editor editor, @NotNull // If we found one ... if (loc >= 0) { // What direction should we go now (-1 is backward, 1 is forward) - Direction dir = loc % 2 == 0 ? Direction.FORWARD : Direction.BACK; + Direction dir = loc % 2 == 0 ? Direction.FORWARDS : Direction.BACKWARDS; // Which character did we find and which should we now search for char found = getPairChars().charAt(loc); char match = getPairChars().charAt(loc + dir.toInt()); @@ -327,7 +327,7 @@ private static int findBlockLocation(@NotNull CharSequence chars, boolean allowInString) { int res = -1; int initialPos = pos; - Function inCheckPosF = x -> dir == Direction.BACK && x > 0 ? x - 1 : x + 1; + Function inCheckPosF = x -> dir == Direction.BACKWARDS && x > 0 ? x - 1 : x + 1; final int inCheckPos = inCheckPosF.apply(pos); boolean inString = checkInString(chars, inCheckPos, true); boolean initialInString = inString; @@ -391,30 +391,16 @@ private static boolean isQuoteWithoutEscape(@NotNull CharSequence chars, int pos return backslashCounter % 2 == 0; } - public enum Direction { - BACK(-1), FORWARD(1); - - private final int value; - - Direction(int i) { - value = i; - } - - public int toInt() { - return value; - } - } - public enum NumberType { BIN, OCT, DEC, HEX, ALPHA } private static int findNextQuoteInLine(@NotNull CharSequence chars, int pos, char quote) { - return findQuoteInLine(chars, pos, quote, Direction.FORWARD); + return findQuoteInLine(chars, pos, quote, Direction.FORWARDS); } private static int findPreviousQuoteInLine(@NotNull CharSequence chars, int pos, char quote) { - return findQuoteInLine(chars, pos, quote, Direction.BACK); + return findQuoteInLine(chars, pos, quote, Direction.BACKWARDS); } private static int findFirstQuoteInLine(@NotNull Editor editor, int pos, char quote) { @@ -428,8 +414,8 @@ private static int findQuoteInLine(@NotNull CharSequence chars, int pos, char qu private static int countCharactersInLine(@NotNull CharSequence chars, int pos, char c) { int cnt = 0; - while (pos > 0 && (chars.charAt(pos + Direction.BACK.toInt()) != '\n')) { - pos = findCharacterPosition(chars, pos + Direction.BACK.toInt(), c, false, true, Direction.BACK); + while (pos > 0 && (chars.charAt(pos + Direction.BACKWARDS.toInt()) != '\n')) { + pos = findCharacterPosition(chars, pos + Direction.BACKWARDS.toInt(), c, false, true, Direction.BACKWARDS); if (pos != -1) { cnt++; } diff --git a/src/com/maddyhome/idea/vim/helper/SearchHelperKt.kt b/src/com/maddyhome/idea/vim/helper/SearchHelperKt.kt index 2183633265..2ec76b5c31 100644 --- a/src/com/maddyhome/idea/vim/helper/SearchHelperKt.kt +++ b/src/com/maddyhome/idea/vim/helper/SearchHelperKt.kt @@ -18,14 +18,34 @@ 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 + +enum class Direction(private val value: Int) { + BACKWARDS(-1), FORWARDS(1), UNSET(0); + + fun toInt(): Int = value + fun reverse(): Direction = when (this) { + BACKWARDS -> FORWARDS + FORWARDS -> BACKWARDS + UNSET -> UNSET + } + + companion object { + fun fromInt(value: Int) = when (value) { + BACKWARDS.value -> BACKWARDS + FORWARDS.value -> FORWARDS + else -> UNSET + } + } +} private data class State(val position: Int, val trigger: Char, val inQuote: Boolean?, val lastOpenSingleQuotePos: Int) // bounds are considered inside corresponding quotes fun checkInString(chars: CharSequence, currentPos: Int, str: Boolean): Boolean { - val begin = findPositionOfFirstCharacter(chars, currentPos, setOf('\n'), false, Direction.BACK)?.second ?: 0 + val begin = findPositionOfFirstCharacter(chars, currentPos, setOf('\n'), false, Direction.BACKWARDS)?.second ?: 0 val changes = quoteChanges(chars, begin) // TODO: here we need to keep only the latest element in beforePos (if any) and // don't need atAndAfterPos to be eagerly collected @@ -105,7 +125,7 @@ private fun quoteChanges(chars: CharSequence, begin: Int) = sequence { // in that situation it may be double quote inside single quotes, so we cannot threat it as double quote pair open/close var inQuote: Boolean? = false val charsToSearch = setOf('\'', '"', '\n') - var found = findPositionOfFirstCharacter(chars, begin, charsToSearch, false, Direction.FORWARD) + var found = findPositionOfFirstCharacter(chars, begin, charsToSearch, false, Direction.FORWARDS) while (found != null && found.first != '\n') { val i = found.second @@ -158,6 +178,11 @@ private fun quoteChanges(chars: CharSequence, begin: Int) = sequence { } } yield(State(i, c, inQuote, lastOpenSingleQuotePos)) - found = findPositionOfFirstCharacter(chars, i + Direction.FORWARD.toInt(), charsToSearch, false, Direction.FORWARD) + found = findPositionOfFirstCharacter(chars, i + Direction.FORWARDS.toInt(), charsToSearch, false, Direction.FORWARDS) } } + +fun shouldIgnoreCase(pattern: String, ignoreSmartCase: Boolean): Boolean { + val sc = smartcase.isSet && !ignoreSmartCase + return ignorecase.isSet && !(sc && StringHelper.containsUpperCase(pattern)) +} diff --git a/src/com/maddyhome/idea/vim/helper/SearchHighlightsHelper.kt b/src/com/maddyhome/idea/vim/helper/SearchHighlightsHelper.kt new file mode 100644 index 0000000000..34dc792323 --- /dev/null +++ b/src/com/maddyhome/idea/vim/helper/SearchHighlightsHelper.kt @@ -0,0 +1,245 @@ +/* + * IdeaVim - Vim emulator for IDEs based on the IntelliJ platform + * Copyright (C) 2003-2020 The IdeaVim authors + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +@file:JvmName("SearchHighlightsHelper") + +package com.maddyhome.idea.vim.helper + +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.* + +fun updateSearchHighlights(pattern: String?, shouldIgnoreSmartCase: Boolean, showHighlights: Boolean, forceUpdate: Boolean) { + updateSearchHighlights(pattern, shouldIgnoreSmartCase, showHighlights, -1, null, true, forceUpdate) +} + +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) +} + +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 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 `/` 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) + 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) + } + 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 + val offset = editor.vimIncsearchCurrentMatchOffset + if (offset != null) { + currentMatchOffset = offset + } + } + } + } + return currentMatchOffset +} + +/** + * 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 +} + +/** + * 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 != "" +} + +private fun findClosestMatch(editor: Editor, results: List, initialOffset: Int, forwards: Boolean): Int { + if (results.isEmpty() || initialOffset == -1) { + return -1 + } + 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 + } + if (!wrapscan.isSet) { + val start = max.startOffset + if (forwards && start < initialOffset) { + return -1 + } else if (start >= initialOffset) { + return -1 + } + } + return max.startOffset +} + +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, 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 \ No newline at end of file diff --git a/src/com/maddyhome/idea/vim/helper/UserDataManager.kt b/src/com/maddyhome/idea/vim/helper/UserDataManager.kt index 5b5ca27db5..7aa31931ed 100644 --- a/src/com/maddyhome/idea/vim/helper/UserDataManager.kt +++ b/src/com/maddyhome/idea/vim/helper/UserDataManager.kt @@ -80,7 +80,7 @@ fun unInitializeEditor(editor: Editor) { } var Editor.vimLastSearch: String? by userData() -var Editor.vimLastHighlighters: Collection? by userData() +var Editor.vimLastHighlighters: MutableCollection? by userData() var Editor.vimIncsearchCurrentMatchOffset: Int? by userData() /*** * @see :help visualmode() diff --git a/src/com/maddyhome/idea/vim/option/OptionsManager.kt b/src/com/maddyhome/idea/vim/option/OptionsManager.kt index 60f8a6b83b..d1759bac97 100644 --- a/src/com/maddyhome/idea/vim/option/OptionsManager.kt +++ b/src/com/maddyhome/idea/vim/option/OptionsManager.kt @@ -52,7 +52,7 @@ object OptionsManager { val clipboard = addOption(ListOption(ClipboardOptionsData.name, ClipboardOptionsData.abbr, arrayOf(ClipboardOptionsData.ideaput, "autoselect,exclude:cons\\|linux"), null)) val digraph = addOption(ToggleOption("digraph", "dg", false)) val gdefault = addOption(ToggleOption("gdefault", "gd", false)) - val history = addOption(NumberOption("history", "hi", 20, 1, Int.MAX_VALUE)) + val history = addOption(NumberOption("history", "hi", 50, 1, Int.MAX_VALUE)) val hlsearch = addOption(ToggleOption("hlsearch", "hls", false)) val ideamarks = addOption(IdeaMarkskOptionsData.option) val ignorecase = addOption(ToggleOption(IgnoreCaseOptionsData.name, IgnoreCaseOptionsData.abbr, false)) diff --git a/src/com/maddyhome/idea/vim/ui/ex/ExEntryPanel.java b/src/com/maddyhome/idea/vim/ui/ex/ExEntryPanel.java index 5da98e83ce..240c3856e9 100644 --- a/src/com/maddyhome/idea/vim/ui/ex/ExEntryPanel.java +++ b/src/com/maddyhome/idea/vim/ui/ex/ExEntryPanel.java @@ -33,6 +33,7 @@ import com.maddyhome.idea.vim.ex.ExCommand; import com.maddyhome.idea.vim.ex.ranges.LineRange; import com.maddyhome.idea.vim.group.MotionGroup; +import com.maddyhome.idea.vim.helper.SearchHighlightsHelper; import com.maddyhome.idea.vim.helper.UiHelper; import com.maddyhome.idea.vim.option.OptionsManager; import com.maddyhome.idea.vim.regexp.CharPointer; @@ -279,6 +280,9 @@ protected void textChanged(@NotNull DocumentEvent e) { searchText = argument.substring(1); } if (searchText.length() == 0) { + // Reset back to the original search highlights after deleting a search from a substitution command. + // E.g. Highlight `whatever`, type `:%s/foo` + highlight `foo`, delete back to `:%s/` and reset highlights + // back to `whatever` VimPlugin.getSearch().resetIncsearchHighlights(); return; } @@ -294,7 +298,7 @@ protected void textChanged(@NotNull DocumentEvent e) { pattern = p.substring(end.pointer() - p.pointer()); VimPlugin.getEditor().closeEditorSearchSession(editor); - final int matchOffset = VimPlugin.getSearch().updateIncsearchHighlights(editor, pattern, forwards, caretOffset, searchRange); + final int matchOffset = SearchHighlightsHelper.updateIncsearchHighlights(editor, pattern, forwards, caretOffset, searchRange); if (matchOffset != -1) { MotionGroup.moveCaret(editor, editor.getCaretModel().getPrimaryCaret(), matchOffset); } diff --git a/test/org/jetbrains/plugins/ideavim/action/SpecialRegistersTest.java b/test/org/jetbrains/plugins/ideavim/action/SpecialRegistersTest.java index d3ade7528a..5999b046ea 100644 --- a/test/org/jetbrains/plugins/ideavim/action/SpecialRegistersTest.java +++ b/test/org/jetbrains/plugins/ideavim/action/SpecialRegistersTest.java @@ -40,7 +40,7 @@ protected void setUp() throws Exception { final RegisterGroup registerGroup = VimPlugin.getRegister(); registerGroup.setKeys('a', stringToKeys(DUMMY_TEXT)); - registerGroup.setKeys('-', stringToKeys(DUMMY_TEXT)); + registerGroup.setKeys(RegisterGroup.SMALL_DELETION_REGISTER, stringToKeys(DUMMY_TEXT)); for (char c = '0'; c <= '9'; c++) { registerGroup.setKeys(c, stringToKeys(DUMMY_TEXT)); } @@ -50,7 +50,7 @@ protected void setUp() throws Exception { public void testSmallDelete() { typeTextInFile(parseKeys("de"), "one two three\n"); - assertEquals("two", getRegisterText('-')); + assertEquals("two", getRegisterText(RegisterGroup.SMALL_DELETION_REGISTER)); // Text smaller than line doesn't go to numbered registers (except special cases) assertRegisterNotChanged('1'); } @@ -60,7 +60,7 @@ public void testSmallDeleteWithPercent() { typeTextInFile(parseKeys("d%"), "(one two) three\n"); assertRegisterChanged('1'); - assertRegisterChanged('-'); + assertRegisterChanged(RegisterGroup.SMALL_DELETION_REGISTER); } // |d| |(| Special case for small delete @@ -68,7 +68,7 @@ public void testSmallDeleteTillPrevSentence() { typeTextInFile(parseKeys("d("), "One. Two. Three.\n"); assertRegisterChanged('1'); - assertRegisterChanged('-'); + assertRegisterChanged(RegisterGroup.SMALL_DELETION_REGISTER); } // |d| |)| Special case for small delete @@ -76,7 +76,7 @@ public void testSmallDeleteTillNextSentence() { typeTextInFile(parseKeys("d)"), "One. Two. Three.\n"); assertRegisterChanged('1'); - assertRegisterChanged('-'); + assertRegisterChanged(RegisterGroup.SMALL_DELETION_REGISTER); } // |d| |`| Special case for small delete @@ -84,7 +84,7 @@ public void testSmallDeleteWithMark() { typeTextInFile(parseKeys("ma", "b", "d`a"), "one two three\n"); assertRegisterChanged('1'); - assertRegisterChanged('-'); + assertRegisterChanged(RegisterGroup.SMALL_DELETION_REGISTER); } // |d| |/| Special case for small delete @@ -92,7 +92,7 @@ public void testSmallDeleteWithSearch() { typeTextInFile(parseKeys("d/", "o", ""), "one two three\n"); assertRegisterChanged('1'); - assertRegisterChanged('-'); + assertRegisterChanged(RegisterGroup.SMALL_DELETION_REGISTER); } // |d| |?| Special case for small delete @@ -100,7 +100,7 @@ public void testSmallDeleteWithBackSearch() { typeTextInFile(parseKeys("d?", "t", ""), "one two three\n"); assertRegisterChanged('1'); - assertRegisterChanged('-'); + assertRegisterChanged(RegisterGroup.SMALL_DELETION_REGISTER); } // |d| |n| Special case for small delete @@ -108,7 +108,7 @@ public void testSmallDeleteWithSearchRepeat() { typeTextInFile(parseKeys("/", "t", "", "dn"), "one two three\n"); assertRegisterChanged('1'); - assertRegisterChanged('-'); + assertRegisterChanged(RegisterGroup.SMALL_DELETION_REGISTER); } // |d| |N| Special case for small delete @@ -116,7 +116,7 @@ public void testSmallDeleteWithBackSearchRepeat() { typeTextInFile(parseKeys("/", "t", "", "dN"), "one two three\n"); assertRegisterChanged('1'); - assertRegisterChanged('-'); + assertRegisterChanged(RegisterGroup.SMALL_DELETION_REGISTER); } // |d| |{| Special case for small delete @@ -124,7 +124,7 @@ public void testSmallDeleteTillPrevParagraph() { typeTextInFile(parseKeys("d{"), "one two three"); assertRegisterChanged('1'); - assertRegisterChanged('-'); + assertRegisterChanged(RegisterGroup.SMALL_DELETION_REGISTER); } // |d| |}| Special case for small delete @@ -132,7 +132,7 @@ public void testSmallDeleteTillNextParagraph() { typeTextInFile(parseKeys("d}"), "one two three"); assertRegisterChanged('1'); - assertRegisterChanged('-'); + assertRegisterChanged(RegisterGroup.SMALL_DELETION_REGISTER); } public void testSmallDeleteInRegister() { @@ -141,14 +141,14 @@ public void testSmallDeleteInRegister() { // Small deletes (less than a line) with register specified go to that register and to numbered registers assertRegisterChanged('a'); assertRegisterChanged('1'); - assertRegisterNotChanged('-'); + assertRegisterNotChanged(RegisterGroup.SMALL_DELETION_REGISTER); } public void testLineDelete() { typeTextInFile(parseKeys("dd"), "one two three\n"); assertRegisterChanged('1'); - assertRegisterNotChanged('-'); + assertRegisterNotChanged(RegisterGroup.SMALL_DELETION_REGISTER); } public void testLineDeleteInRegister() { @@ -190,5 +190,4 @@ private String getRegisterText(char registerName) { return register.getText(); } - } diff --git a/test/org/jetbrains/plugins/ideavim/extension/replacewithregister/ReplaceWithRegisterTest.kt b/test/org/jetbrains/plugins/ideavim/extension/replacewithregister/ReplaceWithRegisterTest.kt index 0aaeba1041..30b825640d 100644 --- a/test/org/jetbrains/plugins/ideavim/extension/replacewithregister/ReplaceWithRegisterTest.kt +++ b/test/org/jetbrains/plugins/ideavim/extension/replacewithregister/ReplaceWithRegisterTest.kt @@ -22,6 +22,7 @@ import com.maddyhome.idea.vim.VimPlugin import com.maddyhome.idea.vim.command.CommandState import com.maddyhome.idea.vim.command.SelectionType import com.maddyhome.idea.vim.common.TextRange +import com.maddyhome.idea.vim.group.RegisterGroup import com.maddyhome.idea.vim.helper.StringHelper.parseKeys import com.maddyhome.idea.vim.helper.VimBehaviorDiffers import org.jetbrains.plugins.ideavim.VimTestCase @@ -57,7 +58,7 @@ class ReplaceWithRegisterTest : VimTestCase() { val text = "" configureByText(text) - VimPlugin.getRegister().storeTextInternal(myFixture.editor, TextRange(0, 0), "one", SelectionType.CHARACTER_WISE, '"', false) + VimPlugin.getRegister().storeTextSpecial(RegisterGroup.UNNAMED_REGISTER, "one") typeText(parseKeys("griw")) myFixture.checkResult("on${c}e") } @@ -66,7 +67,7 @@ class ReplaceWithRegisterTest : VimTestCase() { val text = "${c}one" configureByText(text) - VimPlugin.getRegister().storeText(myFixture.editor, "" rangeOf "", SelectionType.CHARACTER_WISE, false) + VimPlugin.getRegister().storeTextSpecial(RegisterGroup.UNNAMED_REGISTER, "") typeText(parseKeys("griw")) myFixture.checkResult(c) }