mirror of
https://github.com/chylex/IntelliJ-IdeaVim.git
synced 2025-05-29 19:34:09 +02:00
Support incsearch highlighting for global command
Fixes VIM-2891
This commit is contained in:
parent
f552e43c5b
commit
f64c99c406
src
main/java/com/maddyhome/idea/vim
test/java/org/jetbrains/plugins/ideavim/group
testFixtures/kotlin/org/jetbrains/plugins/ideavim
vim-engine/src/main/kotlin/com/maddyhome/idea/vim/vimscript/model/commands
@ -34,6 +34,7 @@ import com.maddyhome.idea.vim.regexp.CharPointer;
|
||||
import com.maddyhome.idea.vim.regexp.RegExp;
|
||||
import com.maddyhome.idea.vim.ui.ExPanelBorder;
|
||||
import com.maddyhome.idea.vim.vimscript.model.commands.Command;
|
||||
import com.maddyhome.idea.vim.vimscript.model.commands.GlobalCommand;
|
||||
import com.maddyhome.idea.vim.vimscript.model.commands.SubstituteCommand;
|
||||
import com.maddyhome.idea.vim.vimscript.parser.VimscriptParser;
|
||||
import org.jetbrains.annotations.Contract;
|
||||
@ -270,11 +271,13 @@ public class ExEntryPanel extends JPanel implements VimCommandLine {
|
||||
try {
|
||||
final Editor editor = entry.getEditor();
|
||||
|
||||
final String labelText = label.getText(); // Either '/', '?' or ':'boolean searchCommand = false;
|
||||
|
||||
boolean searchCommand = false;
|
||||
LineRange searchRange = null;
|
||||
char separator = label.getText().charAt(0);
|
||||
char separator = labelText.charAt(0);
|
||||
String searchText = entry.getActualText();
|
||||
if (label.getText().equals(":")) {
|
||||
if (labelText.equals(":")) {
|
||||
if (searchText.isEmpty()) return;
|
||||
final Command command = getIncsearchCommand(searchText);
|
||||
if (command == null) {
|
||||
@ -287,14 +290,18 @@ public class ExEntryPanel extends JPanel implements VimCommandLine {
|
||||
separator = argument.charAt(0);
|
||||
searchText = argument.substring(1);
|
||||
}
|
||||
if (searchText.length() == 0) {
|
||||
// Reset back to the original search highlights after deleting a search from a substitution command.
|
||||
if (!searchText.isEmpty()) {
|
||||
searchRange = command.getLineRangeSafe(new IjVimEditor(editor));
|
||||
}
|
||||
if (searchText.isEmpty() || searchRange == null) {
|
||||
// Reset back to the original search highlights after deleting a search from a substitution command.Or if
|
||||
// there is no search range (because the user entered an invalid range, e.g. mark not set).
|
||||
// E.g. Highlight `whatever`, type `:%s/foo` + highlight `foo`, delete back to `:%s/` and reset highlights
|
||||
// back to `whatever`
|
||||
VimPlugin.getSearch().resetIncsearchHighlights();
|
||||
resetCaretOffset(editor);
|
||||
return;
|
||||
}
|
||||
searchRange = command.getLineRange(new IjVimEditor(editor));
|
||||
}
|
||||
|
||||
// If we're showing highlights for the search command `/`, then the command builder will have a count already
|
||||
@ -302,7 +309,6 @@ public class ExEntryPanel extends JPanel implements VimCommandLine {
|
||||
// obviously won't be a count.
|
||||
int count1 = Math.max(1, KeyHandler.getInstance().getKeyHandlerState().getEditorCommandBuilder().getCount());
|
||||
|
||||
final String labelText = label.getText();
|
||||
if (labelText.equals("/") || labelText.equals("?") || searchCommand) {
|
||||
final boolean forwards = !labelText.equals("?"); // :s, :g, :v are treated as forwards
|
||||
final String pattern;
|
||||
@ -334,8 +340,8 @@ public class ExEntryPanel extends JPanel implements VimCommandLine {
|
||||
if (commandText == null) return null;
|
||||
try {
|
||||
final Command exCommand = VimscriptParser.INSTANCE.parseCommand(commandText);
|
||||
// TODO: Add global, vglobal, smagic and snomagic here when the commands are supported
|
||||
if (exCommand instanceof SubstituteCommand) {
|
||||
// TODO: Add smagic and snomagic here if/when the commands are supported
|
||||
if (exCommand instanceof SubstituteCommand || exCommand instanceof GlobalCommand) {
|
||||
return exCommand;
|
||||
}
|
||||
}
|
||||
|
@ -39,19 +39,19 @@ import com.maddyhome.idea.vim.vimscript.model.ExecutionResult
|
||||
*/
|
||||
@ExCommand(command = "g[lobal],v[global]")
|
||||
internal data class GlobalCommand(val range: Range, val argument: String, val invert: Boolean) : Command.SingleExecution(range, argument) {
|
||||
|
||||
init {
|
||||
// Most commands have a default range of the current line ("."). Global has a default range of the whole file
|
||||
defaultRange = "%"
|
||||
}
|
||||
|
||||
override val argFlags = flags(RangeFlag.RANGE_OPTIONAL, ArgumentFlag.ARGUMENT_OPTIONAL, Access.SELF_SYNCHRONIZED)
|
||||
|
||||
override fun processCommand(editor: VimEditor, context: ExecutionContext, operatorArguments: OperatorArguments): ExecutionResult {
|
||||
var result: ExecutionResult = ExecutionResult.Success
|
||||
editor.removeSecondaryCarets()
|
||||
val caret = editor.currentCaret()
|
||||
|
||||
// For :g command the default range is %
|
||||
val lineRange: LineRange = if (range.size() == 0) {
|
||||
LineRange(0, editor.lineCount() - 1)
|
||||
} else {
|
||||
getLineRange(editor, caret)
|
||||
}
|
||||
val lineRange = getLineRange(editor, caret)
|
||||
if (!processGlobalCommand(editor, context, lineRange)) {
|
||||
result = ExecutionResult.Error
|
||||
}
|
||||
|
@ -1657,7 +1657,7 @@ class SearchGroupTest : VimTestCase() {
|
||||
)
|
||||
enterCommand("set hlsearch incsearch")
|
||||
|
||||
enterSearch("and")
|
||||
enterSearch("and") // Moves the caret to "and" on the second line: (1, 10)
|
||||
typeText(":", "%s/roc", "<BS><BS><BS>")
|
||||
|
||||
assertSearchHighlights(
|
||||
@ -1669,7 +1669,8 @@ class SearchGroupTest : VimTestCase() {
|
||||
""".trimMargin(),
|
||||
)
|
||||
|
||||
// TODO: Check caret position
|
||||
// Make sure the caret is reset too
|
||||
assertPosition(1, 10)
|
||||
}
|
||||
|
||||
@Test
|
||||
@ -1683,7 +1684,7 @@ class SearchGroupTest : VimTestCase() {
|
||||
)
|
||||
enterCommand("set hlsearch incsearch")
|
||||
|
||||
enterSearch("and")
|
||||
enterSearch("and") // Moves the care to "and" on second line: (1, 10)
|
||||
typeText(":", "%s/ass", "<Esc>")
|
||||
|
||||
assertSearchHighlights(
|
||||
@ -1695,9 +1696,456 @@ class SearchGroupTest : VimTestCase() {
|
||||
""".trimMargin(),
|
||||
)
|
||||
|
||||
// TODO: Check caret position
|
||||
// Make sure the caret is reset too
|
||||
assertPosition(1, 10)
|
||||
}
|
||||
|
||||
// global
|
||||
@Test
|
||||
fun `test incsearch highlights for global command with range`() {
|
||||
configureByText(
|
||||
"""I found it in a legendary land
|
||||
|${c}all rocks and lavender and tufted grass,
|
||||
|where it was settled on some sodden sand
|
||||
|hard by the torrent of a mountain pass.
|
||||
""".trimMargin(),
|
||||
)
|
||||
enterCommand("set hlsearch incsearch")
|
||||
|
||||
typeText(":", "%g/and")
|
||||
|
||||
assertSearchHighlights(
|
||||
"and",
|
||||
"""I found it in a legendary l‷and‴
|
||||
|all rocks «and» lavender «and» tufted grass,
|
||||
|where it was settled on some sodden s«and»
|
||||
|hard by the torrent of a mountain pass.
|
||||
""".trimMargin(),
|
||||
)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `test incsearch highlights for global command in whole file with default range`() {
|
||||
configureByText(
|
||||
"""I found it in a legendary land
|
||||
|${c}all rocks and lavender and tufted grass,
|
||||
|where it was settled on some sodden sand
|
||||
|hard by the torrent of a mountain pass.
|
||||
""".trimMargin(),
|
||||
)
|
||||
enterCommand("set hlsearch incsearch")
|
||||
|
||||
typeText(":", "g/and")
|
||||
|
||||
assertSearchHighlights(
|
||||
"and",
|
||||
"""I found it in a legendary l‷and‴
|
||||
|all rocks «and» lavender «and» tufted grass,
|
||||
|where it was settled on some sodden s«and»
|
||||
|hard by the torrent of a mountain pass.
|
||||
""".trimMargin(),
|
||||
)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `test incsearch highlights for global-bang command`() {
|
||||
configureByText(
|
||||
"""I found it in a legendary land
|
||||
|${c}all rocks and lavender and tufted grass,
|
||||
|where it was settled on some sodden sand
|
||||
|hard by the torrent of a mountain pass.
|
||||
""".trimMargin(),
|
||||
)
|
||||
enterCommand("set hlsearch incsearch")
|
||||
|
||||
typeText(":", "%g!/and")
|
||||
|
||||
assertSearchHighlights(
|
||||
"and",
|
||||
"""I found it in a legendary l‷and‴
|
||||
|all rocks «and» lavender «and» tufted grass,
|
||||
|where it was settled on some sodden s«and»
|
||||
|hard by the torrent of a mountain pass.
|
||||
""".trimMargin(),
|
||||
)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `test incsearch only highlights for global command after valid argument`() {
|
||||
configureByText(
|
||||
"""I found it in a legendary land
|
||||
|${c}all rocks and lavender and tufted grass,
|
||||
|where it was settled on some sodden sand
|
||||
|hard by the torrent of a mountain pass.
|
||||
""".trimMargin(),
|
||||
)
|
||||
enterCommand("set hlsearch incsearch")
|
||||
|
||||
// E.g. don't remove highlights when trying to type :goto
|
||||
enterSearch("and")
|
||||
typeText(":g")
|
||||
|
||||
assertSearchHighlights(
|
||||
"and",
|
||||
"""I found it in a legendary l«and»
|
||||
|all rocks «and» lavender «and» tufted grass,
|
||||
|where it was settled on some sodden s«and»
|
||||
|hard by the torrent of a mountain pass.
|
||||
""".trimMargin(),
|
||||
)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `test incsearch highlights for global command only highlights in range`() {
|
||||
configureByText(
|
||||
"""I found it in a legendary land
|
||||
|all rocks and lavender and tufted grass,
|
||||
|where it was settled on some sodden sand
|
||||
|${c}hard by the torrent and rush of a mountain pass.
|
||||
""".trimMargin(),
|
||||
)
|
||||
enterCommand("set hlsearch incsearch")
|
||||
|
||||
typeText(":", "2,3g/and")
|
||||
|
||||
assertSearchHighlights(
|
||||
"and",
|
||||
"""I found it in a legendary land
|
||||
|all rocks ‷and‴ lavender «and» tufted grass,
|
||||
|where it was settled on some sodden s«and»
|
||||
|hard by the torrent and rush of a mountain pass.
|
||||
""".trimMargin(),
|
||||
)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `test incsearch for global command starts at beginning of range not caret position`() {
|
||||
configureByText(
|
||||
"""I found it in a legendary land
|
||||
|all rocks and lavender and tufted grass,
|
||||
|where it was settled on some sodden sand
|
||||
|hard by the torrent of a mountain pass.
|
||||
|${c}I found it in a legendary land
|
||||
|all rocks and lavender and tufted grass,
|
||||
|where it was settled on some sodden sand
|
||||
|hard by the torrent of a mountain pass.
|
||||
|I found it in a legendary land
|
||||
|all rocks and lavender and tufted grass,
|
||||
|where it was settled on some sodden sand
|
||||
|hard by the torrent of a mountain pass.
|
||||
""".trimMargin(),
|
||||
)
|
||||
enterCommand("set hlsearch incsearch")
|
||||
|
||||
typeText(":", "2,8g/and")
|
||||
|
||||
assertSearchHighlights(
|
||||
"and",
|
||||
"""I found it in a legendary land
|
||||
|all rocks ‷and‴ lavender «and» tufted grass,
|
||||
|where it was settled on some sodden s«and»
|
||||
|hard by the torrent of a mountain pass.
|
||||
|I found it in a legendary l«and»
|
||||
|all rocks «and» lavender «and» tufted grass,
|
||||
|where it was settled on some sodden s«and»
|
||||
|hard by the torrent of a mountain pass.
|
||||
|I found it in a legendary land
|
||||
|all rocks and lavender and tufted grass,
|
||||
|where it was settled on some sodden sand
|
||||
|hard by the torrent of a mountain pass.
|
||||
""".trimMargin(),
|
||||
)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `test incsearch highlights for global command clears highlights on backspace`() {
|
||||
configureByText(
|
||||
"""I found it in a legendary land
|
||||
|${c}all rocks and lavender and tufted grass,
|
||||
|where it was settled on some sodden sand
|
||||
|hard by the torrent of a mountain pass.
|
||||
""".trimMargin(),
|
||||
)
|
||||
enterCommand("set hlsearch incsearch")
|
||||
|
||||
typeText(":", "g/and", "<BS><BS><BS>")
|
||||
|
||||
assertSearchHighlights(
|
||||
"and",
|
||||
"""I found it in a legendary land
|
||||
|all rocks and lavender and tufted grass,
|
||||
|where it was settled on some sodden sand
|
||||
|hard by the torrent of a mountain pass.
|
||||
""".trimMargin(),
|
||||
)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `test incsearch highlights for global command resets highlights on backspace`() {
|
||||
configureByText(
|
||||
"""I found it in a legendary land
|
||||
|${c}all rocks and lavender and tufted grass,
|
||||
|where it was settled on some sodden sand
|
||||
|hard by the torrent of a mountain pass.
|
||||
""".trimMargin(),
|
||||
)
|
||||
enterCommand("set hlsearch incsearch")
|
||||
|
||||
enterSearch("and") // Moves the caret to "and" on the second line: (1, 10)
|
||||
typeText(":", "g/roc", "<BS><BS><BS>")
|
||||
|
||||
assertSearchHighlights(
|
||||
"and",
|
||||
"""I found it in a legendary l«and»
|
||||
|all rocks «and» lavender «and» tufted grass,
|
||||
|where it was settled on some sodden s«and»
|
||||
|hard by the torrent of a mountain pass.
|
||||
""".trimMargin(),
|
||||
)
|
||||
|
||||
// Make sure the caret is reset too
|
||||
assertPosition(1, 10)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `test cancelling incsearch highlights for global command shows previous highlights`() {
|
||||
configureByText(
|
||||
"""I found it in a legendary land
|
||||
|${c}all rocks and lavender and tufted grass,
|
||||
|where it was settled on some sodden sand
|
||||
|hard by the torrent of a mountain pass.
|
||||
""".trimMargin(),
|
||||
)
|
||||
enterCommand("set hlsearch incsearch")
|
||||
|
||||
enterSearch("and") // Moves the caret to "and" on the second line (1, 10)
|
||||
typeText(":", "g/ass", "<Esc>")
|
||||
|
||||
assertSearchHighlights(
|
||||
"and",
|
||||
"""I found it in a legendary l«and»
|
||||
|all rocks «and» lavender «and» tufted grass,
|
||||
|where it was settled on some sodden s«and»
|
||||
|hard by the torrent of a mountain pass.
|
||||
""".trimMargin(),
|
||||
)
|
||||
|
||||
// Make sure the caret is reset too
|
||||
assertPosition(1, 10)
|
||||
}
|
||||
|
||||
// vglobal
|
||||
@Test
|
||||
fun `test incsearch highlights for vglobal command with range`() {
|
||||
configureByText(
|
||||
"""I found it in a legendary land
|
||||
|${c}all rocks and lavender and tufted grass,
|
||||
|where it was settled on some sodden sand
|
||||
|hard by the torrent of a mountain pass.
|
||||
""".trimMargin(),
|
||||
)
|
||||
enterCommand("set hlsearch incsearch")
|
||||
|
||||
typeText(":", "%v/and")
|
||||
|
||||
assertSearchHighlights(
|
||||
"and",
|
||||
"""I found it in a legendary l‷and‴
|
||||
|all rocks «and» lavender «and» tufted grass,
|
||||
|where it was settled on some sodden s«and»
|
||||
|hard by the torrent of a mountain pass.
|
||||
""".trimMargin(),
|
||||
)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `test incsearch highlights for vglobal command in whole file with default range`() {
|
||||
configureByText(
|
||||
"""I found it in a legendary land
|
||||
|${c}all rocks and lavender and tufted grass,
|
||||
|where it was settled on some sodden sand
|
||||
|hard by the torrent of a mountain pass.
|
||||
""".trimMargin(),
|
||||
)
|
||||
enterCommand("set hlsearch incsearch")
|
||||
|
||||
typeText(":", "v/and")
|
||||
|
||||
assertSearchHighlights(
|
||||
"and",
|
||||
"""I found it in a legendary l‷and‴
|
||||
|all rocks «and» lavender «and» tufted grass,
|
||||
|where it was settled on some sodden s«and»
|
||||
|hard by the torrent of a mountain pass.
|
||||
""".trimMargin(),
|
||||
)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `test incsearch only highlights for vglobal command after valid argument`() {
|
||||
configureByText(
|
||||
"""I found it in a legendary land
|
||||
|${c}all rocks and lavender and tufted grass,
|
||||
|where it was settled on some sodden sand
|
||||
|hard by the torrent of a mountain pass.
|
||||
""".trimMargin(),
|
||||
)
|
||||
enterCommand("set hlsearch incsearch")
|
||||
|
||||
// E.g. don't remove highlights when trying to type :vmap
|
||||
enterSearch("and")
|
||||
typeText(":v")
|
||||
|
||||
assertSearchHighlights(
|
||||
"and",
|
||||
"""I found it in a legendary l«and»
|
||||
|all rocks «and» lavender «and» tufted grass,
|
||||
|where it was settled on some sodden s«and»
|
||||
|hard by the torrent of a mountain pass.
|
||||
""".trimMargin(),
|
||||
)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `test incsearch highlights for vglobal command only highlights in range`() {
|
||||
configureByText(
|
||||
"""I found it in a legendary land
|
||||
|all rocks and lavender and tufted grass,
|
||||
|where it was settled on some sodden sand
|
||||
|${c}hard by the torrent and rush of a mountain pass.
|
||||
""".trimMargin(),
|
||||
)
|
||||
enterCommand("set hlsearch incsearch")
|
||||
|
||||
typeText(":", "2,3v/and")
|
||||
|
||||
assertSearchHighlights(
|
||||
"and",
|
||||
"""I found it in a legendary land
|
||||
|all rocks ‷and‴ lavender «and» tufted grass,
|
||||
|where it was settled on some sodden s«and»
|
||||
|hard by the torrent and rush of a mountain pass.
|
||||
""".trimMargin(),
|
||||
)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `test incsearch for vglobal command starts at beginning of range not caret position`() {
|
||||
configureByText(
|
||||
"""I found it in a legendary land
|
||||
|all rocks and lavender and tufted grass,
|
||||
|where it was settled on some sodden sand
|
||||
|hard by the torrent of a mountain pass.
|
||||
|${c}I found it in a legendary land
|
||||
|all rocks and lavender and tufted grass,
|
||||
|where it was settled on some sodden sand
|
||||
|hard by the torrent of a mountain pass.
|
||||
|I found it in a legendary land
|
||||
|all rocks and lavender and tufted grass,
|
||||
|where it was settled on some sodden sand
|
||||
|hard by the torrent of a mountain pass.
|
||||
""".trimMargin(),
|
||||
)
|
||||
enterCommand("set hlsearch incsearch")
|
||||
|
||||
typeText(":", "2,8v/and")
|
||||
|
||||
assertSearchHighlights(
|
||||
"and",
|
||||
"""I found it in a legendary land
|
||||
|all rocks ‷and‴ lavender «and» tufted grass,
|
||||
|where it was settled on some sodden s«and»
|
||||
|hard by the torrent of a mountain pass.
|
||||
|I found it in a legendary l«and»
|
||||
|all rocks «and» lavender «and» tufted grass,
|
||||
|where it was settled on some sodden s«and»
|
||||
|hard by the torrent of a mountain pass.
|
||||
|I found it in a legendary land
|
||||
|all rocks and lavender and tufted grass,
|
||||
|where it was settled on some sodden sand
|
||||
|hard by the torrent of a mountain pass.
|
||||
""".trimMargin(),
|
||||
)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `test incsearch highlights for vglobal command clears highlights on backspace`() {
|
||||
configureByText(
|
||||
"""I found it in a legendary land
|
||||
|${c}all rocks and lavender and tufted grass,
|
||||
|where it was settled on some sodden sand
|
||||
|hard by the torrent of a mountain pass.
|
||||
""".trimMargin(),
|
||||
)
|
||||
enterCommand("set hlsearch incsearch")
|
||||
|
||||
typeText(":", "v/and", "<BS><BS><BS>")
|
||||
|
||||
assertSearchHighlights(
|
||||
"and",
|
||||
"""I found it in a legendary land
|
||||
|all rocks and lavender and tufted grass,
|
||||
|where it was settled on some sodden sand
|
||||
|hard by the torrent of a mountain pass.
|
||||
""".trimMargin(),
|
||||
)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `test incsearch highlights for vglobal command resets highlights on backspace`() {
|
||||
configureByText(
|
||||
"""I found it in a legendary land
|
||||
|${c}all rocks and lavender and tufted grass,
|
||||
|where it was settled on some sodden sand
|
||||
|hard by the torrent of a mountain pass.
|
||||
""".trimMargin(),
|
||||
)
|
||||
enterCommand("set hlsearch incsearch")
|
||||
|
||||
enterSearch("and") // Moves the caret to "and" on the second line: (1, 10)
|
||||
typeText(":", "v/roc", "<BS><BS><BS>")
|
||||
|
||||
assertSearchHighlights(
|
||||
"and",
|
||||
"""I found it in a legendary l«and»
|
||||
|all rocks «and» lavender «and» tufted grass,
|
||||
|where it was settled on some sodden s«and»
|
||||
|hard by the torrent of a mountain pass.
|
||||
""".trimMargin(),
|
||||
)
|
||||
|
||||
// Make sure the caret is reset too
|
||||
assertPosition(1, 10)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `test cancelling incsearch highlights for vglobal command shows previous highlights`() {
|
||||
configureByText(
|
||||
"""I found it in a legendary land
|
||||
|${c}all rocks and lavender and tufted grass,
|
||||
|where it was settled on some sodden sand
|
||||
|hard by the torrent of a mountain pass.
|
||||
""".trimMargin(),
|
||||
)
|
||||
enterCommand("set hlsearch incsearch")
|
||||
|
||||
enterSearch("and") // Moves the caret to "and" on the second line: (1, 10)
|
||||
typeText(":", "v/ass", "<Esc>")
|
||||
|
||||
assertSearchHighlights(
|
||||
"and",
|
||||
"""I found it in a legendary l«and»
|
||||
|all rocks «and» lavender «and» tufted grass,
|
||||
|where it was settled on some sodden s«and»
|
||||
|hard by the torrent of a mountain pass.
|
||||
""".trimMargin(),
|
||||
)
|
||||
|
||||
// Make sure the caret is reset too
|
||||
assertPosition(1, 10)
|
||||
}
|
||||
|
||||
|
||||
@Test
|
||||
fun `test incsearch updates selection when started in Visual mode`() {
|
||||
doTest(
|
||||
|
@ -59,7 +59,7 @@ import com.maddyhome.idea.vim.api.setToggleOption
|
||||
import com.maddyhome.idea.vim.api.visualLineToBufferLine
|
||||
import com.maddyhome.idea.vim.command.MappingMode
|
||||
import com.maddyhome.idea.vim.ex.ExException
|
||||
import com.maddyhome.idea.vim.ex.ExOutputModel.Companion.getInstance
|
||||
import com.maddyhome.idea.vim.ex.ExOutputModel
|
||||
import com.maddyhome.idea.vim.group.EffectiveIjOptions
|
||||
import com.maddyhome.idea.vim.group.GlobalIjOptions
|
||||
import com.maddyhome.idea.vim.group.IjOptions
|
||||
@ -658,14 +658,14 @@ abstract class VimTestCase {
|
||||
}
|
||||
|
||||
fun assertExOutput(expected: String) {
|
||||
val actual = getInstance(fixture.editor).text
|
||||
val actual = ExOutputModel.getInstance(fixture.editor).text
|
||||
assertNotNull(actual, "No Ex output")
|
||||
assertEquals(expected, actual)
|
||||
NeovimTesting.typeCommand("<esc>", testInfo, fixture.editor)
|
||||
}
|
||||
|
||||
fun assertNoExOutput() {
|
||||
val actual = getInstance(fixture.editor).text
|
||||
val actual = ExOutputModel.getInstance(fixture.editor).text
|
||||
assertNull(actual, "Ex output not null")
|
||||
}
|
||||
|
||||
|
@ -27,11 +27,14 @@ import com.maddyhome.idea.vim.vimscript.model.ExecutionResult
|
||||
import com.maddyhome.idea.vim.vimscript.model.VimLContext
|
||||
import java.util.*
|
||||
|
||||
public sealed class Command(protected var commandRange: Range, public val commandArgument: String) : Executable {
|
||||
public sealed class Command(private val commandRange: Range, public val commandArgument: String) : Executable {
|
||||
override lateinit var vimContext: VimLContext
|
||||
override lateinit var rangeInScript: TextRange
|
||||
|
||||
protected abstract val argFlags: CommandHandlerFlags
|
||||
|
||||
protected var defaultRange: String = "."
|
||||
|
||||
private var nextArgumentTokenOffset = 0
|
||||
private val logger = vimLogger<Command>()
|
||||
|
||||
@ -54,12 +57,13 @@ public sealed class Command(protected var commandRange: Range, public val comman
|
||||
|
||||
@Throws(ExException::class)
|
||||
override fun execute(editor: VimEditor, context: ExecutionContext): ExecutionResult {
|
||||
checkRanges(editor)
|
||||
checkArgument()
|
||||
validate(editor)
|
||||
|
||||
if (editor.nativeCarets().any { it.hasSelection() } && Flag.SAVE_VISUAL !in argFlags.flags) {
|
||||
editor.removeSelection()
|
||||
editor.removeSecondaryCarets()
|
||||
}
|
||||
|
||||
if (argFlags.access == Access.WRITABLE && !editor.isDocumentWritable()) {
|
||||
logger.info("Trying to modify readonly document")
|
||||
return ExecutionResult.Error
|
||||
@ -101,6 +105,11 @@ public sealed class Command(protected var commandRange: Range, public val comman
|
||||
return result
|
||||
}
|
||||
|
||||
private fun validate(editor: VimEditor) {
|
||||
checkRanges(editor)
|
||||
checkArgument()
|
||||
}
|
||||
|
||||
private fun checkRanges(editor: VimEditor) {
|
||||
if (RangeFlag.RANGE_FORBIDDEN == argFlags.rangeFlag && commandRange.size() != 0) {
|
||||
// Some commands (e.g. `:file`) throw "E474: Invalid argument" instead, while e.g. `:3ascii` throws E481
|
||||
@ -115,9 +124,16 @@ public sealed class Command(protected var commandRange: Range, public val comman
|
||||
|
||||
// If a range isn't specified, the default range for most commands is the current line ("."). If the command is
|
||||
// expecting a count instead of a range, the default would be a count of 1, represented as the range "1".
|
||||
// GlobalCommand is the only other command that has a different default. We could introduce another RangeFlag
|
||||
// (and maybe make them an enum set so it can be optional and whole-file-range), or set it
|
||||
// TODO: This is initialisation, not validation
|
||||
// It would be nice to do this in the constructor, but argFlags is abstract, so we can't access it
|
||||
if (RangeFlag.RANGE_IS_COUNT == argFlags.rangeFlag) {
|
||||
commandRange.defaultRange = "1"
|
||||
}
|
||||
else {
|
||||
commandRange.defaultRange = defaultRange
|
||||
}
|
||||
}
|
||||
|
||||
private fun checkArgument() {
|
||||
@ -218,6 +234,8 @@ public sealed class Command(protected var commandRange: Range, public val comman
|
||||
|
||||
private fun getNextArgumentToken() = commandArgument.substring(nextArgumentTokenOffset).trimStart()
|
||||
|
||||
protected fun isRangeSpecified(): Boolean = commandRange.size() > 0
|
||||
|
||||
/**
|
||||
* Return the last line of the range as a count, one-based
|
||||
*/
|
||||
@ -276,11 +294,30 @@ public sealed class Command(protected var commandRange: Range, public val comman
|
||||
?: throw exExceptionMessage(Msg.e_invrange) // E16: Invalid range
|
||||
}
|
||||
|
||||
public fun getLine(editor: VimEditor): Int = getLine(editor, editor.currentCaret())
|
||||
public fun getLine(editor: VimEditor, caret: VimCaret): Int = commandRange.getLine(editor, caret)
|
||||
protected fun getLine(editor: VimEditor): Int = getLine(editor, editor.currentCaret())
|
||||
protected fun getLine(editor: VimEditor, caret: VimCaret): Int = commandRange.getLine(editor, caret)
|
||||
|
||||
public fun getLineRange(editor: VimEditor): LineRange = getLineRange(editor, editor.currentCaret())
|
||||
public fun getLineRange(editor: VimEditor, caret: VimCaret): LineRange = commandRange.getLineRange(editor, caret)
|
||||
protected fun getLineRange(editor: VimEditor): LineRange = getLineRange(editor, editor.currentCaret())
|
||||
protected fun getLineRange(editor: VimEditor, caret: VimCaret): LineRange = commandRange.getLineRange(editor, caret)
|
||||
|
||||
/**
|
||||
* Accessor method purely for incsearch
|
||||
*
|
||||
* Ensures that the range and argument have been correctly initialised and validated, specifically that the default
|
||||
* range has been set. Any validation errors are swallowed and ignored.
|
||||
*
|
||||
* It would be cleaner to move incsearch handling into the search Command instances, which could access this data
|
||||
* safely.
|
||||
*/
|
||||
public fun getLineRangeSafe(editor: VimEditor): LineRange? {
|
||||
try {
|
||||
validate(editor)
|
||||
}
|
||||
catch (t: Throwable) {
|
||||
return null
|
||||
}
|
||||
return getLineRange(editor)
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the line range using the optional count argument
|
||||
@ -291,8 +328,8 @@ public sealed class Command(protected var commandRange: Range, public val comman
|
||||
* The `{count}` argument must be a simple integer, with no trailing characters. This function will fail with "E488:
|
||||
* Trailing characters" otherwise.
|
||||
*/
|
||||
public fun getLineRangeWithCount(editor: VimEditor, caret: VimCaret): LineRange {
|
||||
val lineRange = commandRange.getLineRange(editor, caret)
|
||||
protected fun getLineRangeWithCount(editor: VimEditor, caret: VimCaret): LineRange {
|
||||
val lineRange = getLineRange(editor, caret)
|
||||
return getCountFromArgument()?.let { count ->
|
||||
LineRange(lineRange.endLine, lineRange.endLine + count - 1)
|
||||
} ?: lineRange
|
||||
|
@ -28,7 +28,7 @@ public data class FileCommand(val range: Range, val argument: String) : Command.
|
||||
// TODO: Support the `:file {name}` argument to set the name of the current file
|
||||
// Note that `:file` doesn't really support a range or count. But `:0file` is support to remove the current file
|
||||
// name. We don't support either of these features, but by accepting a range/count, we can report the right error
|
||||
if (commandRange.size() != 0) {
|
||||
if (isRangeSpecified()) {
|
||||
throw ExException("E474: Invalid argument")
|
||||
}
|
||||
|
||||
|
Loading…
Reference in New Issue
Block a user