mirror of
https://github.com/chylex/IntelliJ-IdeaVim.git
synced 2025-08-21 07:54:06 +02:00
Compare commits
16 Commits
0.54.2-EAP
...
0.55
Author | SHA1 | Date | |
---|---|---|---|
![]() |
c1ff6e1498 | ||
![]() |
50c04ce71c | ||
![]() |
bc6ff6bc8e | ||
![]() |
93bcf2a7e8 | ||
![]() |
c3b503adff | ||
![]() |
ecdcbdda10 | ||
![]() |
b97c9a5ed0 | ||
![]() |
84a6843a7b | ||
![]() |
17eed7467c | ||
![]() |
310ffc849c | ||
![]() |
3e6756160a | ||
![]() |
563e809a2d | ||
![]() |
86ec3f3bcd | ||
![]() |
b4e0ec282f | ||
![]() |
cbf7dfabcb | ||
![]() |
b8eb55d965 |
10
CHANGES.md
10
CHANGES.md
@@ -16,13 +16,11 @@ It is important to distinguish EAP from traditional pre-release software.
|
|||||||
Please note that the quality of EAP versions may at times be way below even
|
Please note that the quality of EAP versions may at times be way below even
|
||||||
usual beta standards.
|
usual beta standards.
|
||||||
|
|
||||||
[To Be Released]
|
0.55, 2020-01-20
|
||||||
--------------
|
--------------
|
||||||
|
|
||||||
_Available since 0.54.1 EAP:_
|
|
||||||
|
|
||||||
**Features:**
|
**Features:**
|
||||||
* Surround and Commentary extensions can be repeated with a dot command ([VIM-1118](https://youtrack.jetbrains.com/issue/VIM-1118))
|
* Surround and Commentary extensions support repeating with a dot command ([VIM-1118](https://youtrack.jetbrains.com/issue/VIM-1118))
|
||||||
* Support XDG settings standard ([VIM-664](https://youtrack.jetbrains.com/issue/VIM-664))
|
* Support XDG settings standard ([VIM-664](https://youtrack.jetbrains.com/issue/VIM-664))
|
||||||
* Add option to remove the status bar icon ([VIM-1847](https://youtrack.jetbrains.com/issue/VIM-1847))
|
* Add option to remove the status bar icon ([VIM-1847](https://youtrack.jetbrains.com/issue/VIM-1847))
|
||||||
|
|
||||||
@@ -43,10 +41,6 @@ _Available since 0.54.1 EAP:_
|
|||||||
* [VIM-1853](https://youtrack.jetbrains.com/issue/VIM-1853) Fix marks for disposed projects
|
* [VIM-1853](https://youtrack.jetbrains.com/issue/VIM-1853) Fix marks for disposed projects
|
||||||
* [VIM-1858](https://youtrack.jetbrains.com/issue/VIM-1858) Fix imap for autocomplete
|
* [VIM-1858](https://youtrack.jetbrains.com/issue/VIM-1858) Fix imap for autocomplete
|
||||||
* [VIM-1362](https://youtrack.jetbrains.com/issue/VIM-1362) Search with confirm doesn't scroll down far enough
|
* [VIM-1362](https://youtrack.jetbrains.com/issue/VIM-1362) Search with confirm doesn't scroll down far enough
|
||||||
|
|
||||||
_To Be Released:_
|
|
||||||
|
|
||||||
**Fixes:**
|
|
||||||
* [VIM-1875](https://youtrack.jetbrains.com/issue/VIM-1875) Fix `isk` in `~/.ideaivmrc`
|
* [VIM-1875](https://youtrack.jetbrains.com/issue/VIM-1875) Fix `isk` in `~/.ideaivmrc`
|
||||||
* [VIM-1874](https://youtrack.jetbrains.com/issue/VIM-1874) Fix `set clipboard=unnamed` execution from `~/.ideavimrc`
|
* [VIM-1874](https://youtrack.jetbrains.com/issue/VIM-1874) Fix `set clipboard=unnamed` execution from `~/.ideavimrc`
|
||||||
* [VIM-1878](https://youtrack.jetbrains.com/issue/VIM-1878) Fix `c` command after extract method action
|
* [VIM-1878](https://youtrack.jetbrains.com/issue/VIM-1878) Fix `c` command after extract method action
|
||||||
|
@@ -128,7 +128,7 @@ have `-Duser.home=/my/alternate/home` then IdeaVim will source
|
|||||||
`/my/alternate/home/.ideavimrc` instead of `~/.ideavimrc`.
|
`/my/alternate/home/.ideavimrc` instead of `~/.ideavimrc`.
|
||||||
|
|
||||||
Alternatively, you can set up initialization commands using [XDG](https://specifications.freedesktop.org/basedir-spec/basedir-spec-latest.html) standard.
|
Alternatively, you can set up initialization commands using [XDG](https://specifications.freedesktop.org/basedir-spec/basedir-spec-latest.html) standard.
|
||||||
Put your settings to `$XDG_CONFIG_HOME$/ideavim/ideavimrc` file. [To Be Released]
|
Put your settings to `$XDG_CONFIG_HOME$/ideavim/ideavimrc` file.
|
||||||
|
|
||||||
|
|
||||||
Emulated Vim Plugins
|
Emulated Vim Plugins
|
||||||
|
@@ -103,10 +103,20 @@ The following `:set` commands can appear in `~/.ideavimrc` or be set manually in
|
|||||||
If true, join command will be performed via IDE
|
If true, join command will be performed via IDE
|
||||||
See wiki/`ideajoin` examples
|
See wiki/`ideajoin` examples
|
||||||
|
|
||||||
`ideastatusbar` `ideastatusbar` Boolean (default true) [To Be Released]
|
`ideastatusbar` `ideastatusbar` Boolean (default true)
|
||||||
|
|
||||||
If false, IdeaVim icon won't be shown in the status bar.
|
If false, IdeaVim icon won't be shown in the status bar.
|
||||||
Works only from `~/.ideavimrc` after the IDE restart.
|
Works only from `~/.ideavimrc` after the IDE restart.
|
||||||
|
|
||||||
|
`lookupkeys` `lookupkeys` List of strings
|
||||||
|
|
||||||
|
List of keys that should be processed by the IDE during the active lookup (autocompletion).
|
||||||
|
For example, <Tab> and <Enter> are used by the IDE to finish the lookup,
|
||||||
|
but <C-W> should be passed to IdeaVim.
|
||||||
|
Default value:
|
||||||
|
"<Tab>", "<Down>", "<Up>", "<Enter>", "<Left>", "<Right>",
|
||||||
|
"<C-Down>", "<C-Up>", "<PageUp>", "<PageDown>",
|
||||||
|
"<C-J>", "<C-Q>"
|
||||||
|
|
||||||
----------
|
----------
|
||||||
[1] - cursor keys, <End>, <Home>, <PageUp> and <PageDown>
|
[1] - cursor keys, <End>, <Home>, <PageUp> and <PageDown>
|
||||||
|
@@ -3,8 +3,10 @@
|
|||||||
<id>IdeaVIM</id>
|
<id>IdeaVIM</id>
|
||||||
<change-notes><![CDATA[
|
<change-notes><![CDATA[
|
||||||
<ul>
|
<ul>
|
||||||
<li>Fix `imap jk <ESC>` for the active autocompletion lookup</li>
|
<li>Support dot command for Surround and Commentary extensions</li>
|
||||||
<li>Surround and Commentary extensions can be repeated with a dot command</li>
|
<li>Support XDG settings standard</li>
|
||||||
|
<li>Add option to remove the status bar icon</li>
|
||||||
|
<li>Various bug fixes</li>
|
||||||
</ul>
|
</ul>
|
||||||
<p>See also the complete <a href="https://github.com/JetBrains/ideavim/blob/master/CHANGES.md">changelog</a>.</p>
|
<p>See also the complete <a href="https://github.com/JetBrains/ideavim/blob/master/CHANGES.md">changelog</a>.</p>
|
||||||
]]></change-notes>
|
]]></change-notes>
|
||||||
|
@@ -34,7 +34,6 @@ import com.intellij.openapi.util.Key
|
|||||||
import com.intellij.ui.KeyStrokeAdapter
|
import com.intellij.ui.KeyStrokeAdapter
|
||||||
import com.maddyhome.idea.vim.KeyHandler
|
import com.maddyhome.idea.vim.KeyHandler
|
||||||
import com.maddyhome.idea.vim.VimPlugin
|
import com.maddyhome.idea.vim.VimPlugin
|
||||||
import com.maddyhome.idea.vim.handler.EditorActionHandlerBase.Companion.parseKeysSet
|
|
||||||
import com.maddyhome.idea.vim.helper.EditorDataContext
|
import com.maddyhome.idea.vim.helper.EditorDataContext
|
||||||
import com.maddyhome.idea.vim.helper.EditorHelper
|
import com.maddyhome.idea.vim.helper.EditorHelper
|
||||||
import com.maddyhome.idea.vim.helper.StringHelper
|
import com.maddyhome.idea.vim.helper.StringHelper
|
||||||
@@ -42,7 +41,7 @@ import com.maddyhome.idea.vim.helper.inInsertMode
|
|||||||
import com.maddyhome.idea.vim.helper.inNormalMode
|
import com.maddyhome.idea.vim.helper.inNormalMode
|
||||||
import com.maddyhome.idea.vim.key.ShortcutOwner
|
import com.maddyhome.idea.vim.key.ShortcutOwner
|
||||||
import com.maddyhome.idea.vim.listener.IdeaSpecifics.aceJumpActive
|
import com.maddyhome.idea.vim.listener.IdeaSpecifics.aceJumpActive
|
||||||
import com.maddyhome.idea.vim.option.OptionsManager.lookupKeys
|
import com.maddyhome.idea.vim.option.OptionsManager
|
||||||
import java.awt.event.InputEvent
|
import java.awt.event.InputEvent
|
||||||
import java.awt.event.KeyEvent
|
import java.awt.event.KeyEvent
|
||||||
import javax.swing.KeyStroke
|
import javax.swing.KeyStroke
|
||||||
@@ -90,7 +89,7 @@ class VimShortcutKeyAction : AnAction(), DumbAware {
|
|||||||
if (aceJumpActive()) return false
|
if (aceJumpActive()) return false
|
||||||
val keyCode = keyStroke.keyCode
|
val keyCode = keyStroke.keyCode
|
||||||
if (LookupManager.getActiveLookup(editor) != null) {
|
if (LookupManager.getActiveLookup(editor) != null) {
|
||||||
return isEnabledForLookup(keyStroke)
|
return LookupKeys.isEnabledForLookup(keyStroke)
|
||||||
}
|
}
|
||||||
if (keyCode == KeyEvent.VK_ESCAPE) {
|
if (keyCode == KeyEvent.VK_ESCAPE) {
|
||||||
return isEnabledForEscape(editor)
|
return isEnabledForEscape(editor)
|
||||||
@@ -136,29 +135,6 @@ class VimShortcutKeyAction : AnAction(), DumbAware {
|
|||||||
return fileEditorManager.allEditors.any { fileEditor -> editor == EditorUtil.getEditorEx(fileEditor) }
|
return fileEditorManager.allEditors.any { fileEditor -> editor == EditorUtil.getEditorEx(fileEditor) }
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun isEnabledForLookup(keyStroke: KeyStroke): Boolean {
|
|
||||||
val notAllowedKeys = parseKeysSet(
|
|
||||||
"<Tab>", "<Down>", "<Up>", "<Enter>", "<Left>", "<Right>",
|
|
||||||
// New line in vim, but QuickDoc on MacOs
|
|
||||||
"<C-J>"
|
|
||||||
)
|
|
||||||
for (keys in notAllowedKeys) {
|
|
||||||
if (keyStroke == keys[0]) {
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
}
|
|
||||||
// We allow users to set custom keys that will work with lookup in case devs forgot something
|
|
||||||
val popupActions = lookupKeys
|
|
||||||
val values = popupActions.values()
|
|
||||||
for (value in values) {
|
|
||||||
val keys = StringHelper.parseKeys(value)
|
|
||||||
if (keys.size >= 1 && keyStroke == keys[0]) {
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun isShortcutConflict(keyStroke: KeyStroke): Boolean {
|
private fun isShortcutConflict(keyStroke: KeyStroke): Boolean {
|
||||||
return VimPlugin.getKey().getKeymapConflicts(keyStroke).isNotEmpty()
|
return VimPlugin.getKey().getKeymapConflicts(keyStroke).isNotEmpty()
|
||||||
}
|
}
|
||||||
@@ -189,6 +165,29 @@ class VimShortcutKeyAction : AnAction(), DumbAware {
|
|||||||
|
|
||||||
private fun getEditor(e: AnActionEvent): Editor? = e.getData(PlatformDataKeys.EDITOR)
|
private fun getEditor(e: AnActionEvent): Editor? = e.getData(PlatformDataKeys.EDITOR)
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Every time the key pressed with an active lookup, there is a decision:
|
||||||
|
* should this key be processed by IdeaVim, or by IDE. For example, dot and enter should be processed by IDE, but
|
||||||
|
* <C-W> by IdeaVim.
|
||||||
|
*
|
||||||
|
* The list of keys that should be processed by IDE is stored in [OptionsManager.lookupKeys]. So, we should search
|
||||||
|
* if the pressed key is presented in this list. The caches are used to speedup the process.
|
||||||
|
*/
|
||||||
|
private object LookupKeys {
|
||||||
|
private var parsedLookupKeys: Set<KeyStroke> = parseLookupKeys()
|
||||||
|
|
||||||
|
init {
|
||||||
|
OptionsManager.lookupKeys.addOptionChangeListener { _, _ ->
|
||||||
|
parsedLookupKeys = parseLookupKeys()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fun isEnabledForLookup(keyStroke: KeyStroke): Boolean = keyStroke !in parsedLookupKeys
|
||||||
|
|
||||||
|
private fun parseLookupKeys() = OptionsManager.lookupKeys.values()
|
||||||
|
.map { StringHelper.parseKeys(it) }.filter { it.isNotEmpty() }.map { it.first() }.toSet()
|
||||||
|
}
|
||||||
|
|
||||||
companion object {
|
companion object {
|
||||||
@JvmField
|
@JvmField
|
||||||
val VIM_ONLY_EDITOR_KEYS: Set<KeyStroke> = ImmutableSet.builder<KeyStroke>().addAll(getKeyStrokes(KeyEvent.VK_ENTER, 0)).addAll(getKeyStrokes(KeyEvent.VK_ESCAPE, 0))
|
val VIM_ONLY_EDITOR_KEYS: Set<KeyStroke> = ImmutableSet.builder<KeyStroke>().addAll(getKeyStrokes(KeyEvent.VK_ENTER, 0)).addAll(getKeyStrokes(KeyEvent.VK_ESCAPE, 0))
|
||||||
|
@@ -22,7 +22,7 @@ import com.intellij.openapi.editor.Editor
|
|||||||
import com.maddyhome.idea.vim.VimPlugin
|
import com.maddyhome.idea.vim.VimPlugin
|
||||||
import com.maddyhome.idea.vim.command.Argument
|
import com.maddyhome.idea.vim.command.Argument
|
||||||
import com.maddyhome.idea.vim.command.Command
|
import com.maddyhome.idea.vim.command.Command
|
||||||
import com.maddyhome.idea.vim.ex.LineRange
|
import com.maddyhome.idea.vim.ex.ranges.LineRange
|
||||||
import com.maddyhome.idea.vim.handler.ChangeEditorActionHandler
|
import com.maddyhome.idea.vim.handler.ChangeEditorActionHandler
|
||||||
import com.maddyhome.idea.vim.helper.EditorHelper
|
import com.maddyhome.idea.vim.helper.EditorHelper
|
||||||
|
|
||||||
|
@@ -22,7 +22,7 @@ import com.intellij.openapi.editor.Editor
|
|||||||
import com.maddyhome.idea.vim.VimPlugin
|
import com.maddyhome.idea.vim.VimPlugin
|
||||||
import com.maddyhome.idea.vim.command.Argument
|
import com.maddyhome.idea.vim.command.Argument
|
||||||
import com.maddyhome.idea.vim.command.Command
|
import com.maddyhome.idea.vim.command.Command
|
||||||
import com.maddyhome.idea.vim.ex.LineRange
|
import com.maddyhome.idea.vim.ex.ranges.LineRange
|
||||||
import com.maddyhome.idea.vim.handler.ChangeEditorActionHandler
|
import com.maddyhome.idea.vim.handler.ChangeEditorActionHandler
|
||||||
|
|
||||||
class ChangeLastSearchReplaceAction : ChangeEditorActionHandler.SingleExecution() {
|
class ChangeLastSearchReplaceAction : ChangeEditorActionHandler.SingleExecution() {
|
||||||
|
@@ -28,7 +28,8 @@ import com.maddyhome.idea.vim.command.SelectionType;
|
|||||||
import com.maddyhome.idea.vim.common.Register;
|
import com.maddyhome.idea.vim.common.Register;
|
||||||
import com.maddyhome.idea.vim.common.TextRange;
|
import com.maddyhome.idea.vim.common.TextRange;
|
||||||
import com.maddyhome.idea.vim.ex.handler.GotoLineHandler;
|
import com.maddyhome.idea.vim.ex.handler.GotoLineHandler;
|
||||||
import com.maddyhome.idea.vim.ex.range.AbstractRange;
|
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.HistoryGroup;
|
||||||
import com.maddyhome.idea.vim.helper.MessageHelper;
|
import com.maddyhome.idea.vim.helper.MessageHelper;
|
||||||
import com.maddyhome.idea.vim.helper.Msg;
|
import com.maddyhome.idea.vim.helper.Msg;
|
||||||
@@ -412,7 +413,7 @@ public class CommandParser {
|
|||||||
reprocess = false;
|
reprocess = false;
|
||||||
break;
|
break;
|
||||||
case RANGE_DONE: // We have hit the end of a range - process it
|
case RANGE_DONE: // We have hit the end of a range - process it
|
||||||
Range[] range = AbstractRange.createRange(location.toString(), offsetTotal, move);
|
Range[] range = Range.createRange(location.toString(), offsetTotal, move);
|
||||||
if (range == null) {
|
if (range == null) {
|
||||||
error = MessageHelper.message(Msg.e_badrange, Character.toString(ch));
|
error = MessageHelper.message(Msg.e_badrange, Character.toString(ch));
|
||||||
state = State.ERROR;
|
state = State.ERROR;
|
||||||
|
@@ -1,122 +0,0 @@
|
|||||||
/*
|
|
||||||
* IdeaVim - Vim emulator for IDEs based on the IntelliJ platform
|
|
||||||
* Copyright (C) 2003-2019 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 <https://www.gnu.org/licenses/>.
|
|
||||||
*/
|
|
||||||
|
|
||||||
package com.maddyhome.idea.vim.ex;
|
|
||||||
|
|
||||||
import com.intellij.openapi.actionSystem.DataContext;
|
|
||||||
import com.intellij.openapi.editor.Caret;
|
|
||||||
import com.intellij.openapi.editor.Editor;
|
|
||||||
import com.maddyhome.idea.vim.common.TextRange;
|
|
||||||
import org.jetbrains.annotations.NotNull;
|
|
||||||
|
|
||||||
|
|
||||||
public class ExCommand {
|
|
||||||
public ExCommand(@NotNull Ranges ranges, @NotNull String command, @NotNull String argument) {
|
|
||||||
this.ranges = ranges;
|
|
||||||
this.argument = argument;
|
|
||||||
this.command = command;
|
|
||||||
}
|
|
||||||
|
|
||||||
public int getLine(@NotNull Editor editor) {
|
|
||||||
return ranges.getLine(editor);
|
|
||||||
}
|
|
||||||
|
|
||||||
public int getLine(@NotNull Editor editor, @NotNull Caret caret, @NotNull DataContext context) {
|
|
||||||
return ranges.getLine(editor, caret, context);
|
|
||||||
}
|
|
||||||
|
|
||||||
public int getCount(@NotNull Editor editor, DataContext context, int defaultCount, boolean checkCount) {
|
|
||||||
int count = -1;
|
|
||||||
if (checkCount) {
|
|
||||||
count = getCountArgument();
|
|
||||||
}
|
|
||||||
|
|
||||||
int res = ranges.getCount(editor, context, count);
|
|
||||||
if (res == -1) {
|
|
||||||
res = defaultCount;
|
|
||||||
}
|
|
||||||
|
|
||||||
return res;
|
|
||||||
}
|
|
||||||
|
|
||||||
public int getCount(@NotNull Editor editor, @NotNull Caret caret, @NotNull DataContext context, int defaultCount,
|
|
||||||
boolean checkCount) {
|
|
||||||
final int count = ranges.getCount(editor, caret, context, checkCount ? getCountArgument() : -1);
|
|
||||||
if (count == -1) return defaultCount;
|
|
||||||
return count;
|
|
||||||
}
|
|
||||||
|
|
||||||
@NotNull
|
|
||||||
public LineRange getLineRange(@NotNull Editor editor) {
|
|
||||||
return ranges.getLineRange(editor, -1);
|
|
||||||
}
|
|
||||||
|
|
||||||
public LineRange getLineRange(@NotNull Editor editor, @NotNull Caret caret, @NotNull DataContext context) {
|
|
||||||
return ranges.getLineRange(editor, caret, context, -1);
|
|
||||||
}
|
|
||||||
|
|
||||||
@NotNull
|
|
||||||
public TextRange getTextRange(@NotNull Editor editor, DataContext context, boolean checkCount) {
|
|
||||||
int count = -1;
|
|
||||||
if (checkCount) {
|
|
||||||
count = getCountArgument();
|
|
||||||
}
|
|
||||||
|
|
||||||
return ranges.getTextRange(editor, context, count);
|
|
||||||
}
|
|
||||||
|
|
||||||
public TextRange getTextRange(@NotNull Editor editor, @NotNull Caret caret, @NotNull DataContext context,
|
|
||||||
boolean checkCount) {
|
|
||||||
return ranges.getTextRange(editor, caret, context, checkCount ? getCountArgument() : -1);
|
|
||||||
}
|
|
||||||
|
|
||||||
private int getCountArgument() {
|
|
||||||
try {
|
|
||||||
return Integer.parseInt(argument);
|
|
||||||
}
|
|
||||||
catch (NumberFormatException e) {
|
|
||||||
return -1;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@NotNull
|
|
||||||
public String getCommand() {
|
|
||||||
return command;
|
|
||||||
}
|
|
||||||
|
|
||||||
@NotNull
|
|
||||||
public String getArgument() {
|
|
||||||
return argument;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void setArgument(@NotNull String argument) {
|
|
||||||
this.argument = argument;
|
|
||||||
}
|
|
||||||
|
|
||||||
@NotNull
|
|
||||||
public Ranges getRanges() {
|
|
||||||
return ranges;
|
|
||||||
}
|
|
||||||
|
|
||||||
@NotNull
|
|
||||||
private final Ranges ranges;
|
|
||||||
@NotNull
|
|
||||||
private final String command;
|
|
||||||
@NotNull
|
|
||||||
private String argument;
|
|
||||||
}
|
|
62
src/com/maddyhome/idea/vim/ex/ExCommand.kt
Normal file
62
src/com/maddyhome/idea/vim/ex/ExCommand.kt
Normal file
@@ -0,0 +1,62 @@
|
|||||||
|
/*
|
||||||
|
* IdeaVim - Vim emulator for IDEs based on the IntelliJ platform
|
||||||
|
* Copyright (C) 2003-2019 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 <https://www.gnu.org/licenses/>.
|
||||||
|
*/
|
||||||
|
package com.maddyhome.idea.vim.ex
|
||||||
|
|
||||||
|
import com.intellij.openapi.actionSystem.DataContext
|
||||||
|
import com.intellij.openapi.editor.Caret
|
||||||
|
import com.intellij.openapi.editor.Editor
|
||||||
|
import com.maddyhome.idea.vim.common.TextRange
|
||||||
|
import com.maddyhome.idea.vim.ex.ranges.LineRange
|
||||||
|
import com.maddyhome.idea.vim.ex.ranges.Ranges
|
||||||
|
|
||||||
|
class ExCommand(val ranges: Ranges, val command: String, var argument: String) {
|
||||||
|
|
||||||
|
fun getLine(editor: Editor): Int = ranges.getLine(editor)
|
||||||
|
|
||||||
|
fun getLine(editor: Editor, caret: Caret, context: DataContext): Int = ranges.getLine(editor, caret, context)
|
||||||
|
|
||||||
|
fun getCount(editor: Editor, context: DataContext?, defaultCount: Int, checkCount: Boolean): Int {
|
||||||
|
val count = if (checkCount) countArgument else -1
|
||||||
|
|
||||||
|
val res = ranges.getCount(editor, count)
|
||||||
|
return if (res == -1) defaultCount else res
|
||||||
|
}
|
||||||
|
|
||||||
|
fun getCount(editor: Editor, caret: Caret, context: DataContext, defaultCount: Int, checkCount: Boolean): Int {
|
||||||
|
val count = ranges.getCount(editor, caret, context, if (checkCount) countArgument else -1)
|
||||||
|
return if (count == -1) defaultCount else count
|
||||||
|
}
|
||||||
|
|
||||||
|
fun getLineRange(editor: Editor): LineRange = ranges.getLineRange(editor, -1)
|
||||||
|
|
||||||
|
fun getLineRange(editor: Editor, caret: Caret, context: DataContext): LineRange {
|
||||||
|
return ranges.getLineRange(editor, caret, context, -1)
|
||||||
|
}
|
||||||
|
|
||||||
|
fun getTextRange(editor: Editor, context: DataContext?, checkCount: Boolean): TextRange {
|
||||||
|
val count = if (checkCount) countArgument else -1
|
||||||
|
return ranges.getTextRange(editor, context, count)
|
||||||
|
}
|
||||||
|
|
||||||
|
fun getTextRange(editor: Editor, caret: Caret, context: DataContext, checkCount: Boolean): TextRange {
|
||||||
|
return ranges.getTextRange(editor, caret, context, if (checkCount) countArgument else -1)
|
||||||
|
}
|
||||||
|
|
||||||
|
private val countArgument: Int
|
||||||
|
get() = argument.toIntOrNull() ?: -1
|
||||||
|
}
|
@@ -15,25 +15,18 @@
|
|||||||
* You should have received a copy of the GNU General Public License
|
* You should have received a copy of the GNU General Public License
|
||||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||||
*/
|
*/
|
||||||
|
package com.maddyhome.idea.vim.ex
|
||||||
|
|
||||||
package com.maddyhome.idea.vim.ex;
|
open class ExException(s: String? = null) : Exception(s)
|
||||||
|
|
||||||
/**
|
class InvalidCommandException(message: String, cmd: String?) : ExException("$message | $cmd")
|
||||||
* Exception class
|
|
||||||
*/
|
|
||||||
public class ExException extends Exception {
|
|
||||||
/**
|
|
||||||
* Constructs an <code>ExException</code> with no specified detail message.
|
|
||||||
*/
|
|
||||||
public ExException() {
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
class InvalidRangeException(s: String) : ExException(s)
|
||||||
* Constructs an <code>ExException</code> with the specified detail message.
|
|
||||||
*
|
class MissingArgumentException : ExException()
|
||||||
* @param s the detail message.
|
|
||||||
*/
|
class MissingRangeException : ExException()
|
||||||
public ExException(String s) {
|
|
||||||
super(s);
|
class NoArgumentAllowedException : ExException()
|
||||||
}
|
|
||||||
}
|
class NoRangeAllowedException : ExException()
|
@@ -1,67 +0,0 @@
|
|||||||
/*
|
|
||||||
* IdeaVim - Vim emulator for IDEs based on the IntelliJ platform
|
|
||||||
* Copyright (C) 2003-2019 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 <https://www.gnu.org/licenses/>.
|
|
||||||
*/
|
|
||||||
|
|
||||||
package com.maddyhome.idea.vim.ex;
|
|
||||||
|
|
||||||
import com.intellij.openapi.application.ApplicationManager;
|
|
||||||
import com.intellij.openapi.editor.Editor;
|
|
||||||
import com.maddyhome.idea.vim.helper.UserDataManager;
|
|
||||||
import com.maddyhome.idea.vim.ui.ExOutputPanel;
|
|
||||||
import org.jetbrains.annotations.NotNull;
|
|
||||||
import org.jetbrains.annotations.Nullable;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @author vlan
|
|
||||||
*/
|
|
||||||
public class ExOutputModel {
|
|
||||||
@NotNull private final Editor myEditor;
|
|
||||||
@Nullable private String myText;
|
|
||||||
|
|
||||||
private ExOutputModel(@NotNull Editor editor) {
|
|
||||||
myEditor = editor;
|
|
||||||
}
|
|
||||||
|
|
||||||
@NotNull
|
|
||||||
public static ExOutputModel getInstance(@NotNull Editor editor) {
|
|
||||||
ExOutputModel model = UserDataManager.getVimExOutput(editor);
|
|
||||||
if (model == null) {
|
|
||||||
model = new ExOutputModel(editor);
|
|
||||||
UserDataManager.setVimExOutput(editor, model);
|
|
||||||
}
|
|
||||||
return model;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void output(@NotNull String text) {
|
|
||||||
myText = text;
|
|
||||||
if (!ApplicationManager.getApplication().isUnitTestMode()) {
|
|
||||||
ExOutputPanel.getInstance(myEditor).setText(text);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public void clear() {
|
|
||||||
myText = null;
|
|
||||||
if (!ApplicationManager.getApplication().isUnitTestMode()) {
|
|
||||||
ExOutputPanel.getInstance(myEditor).deactivate(false);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Nullable
|
|
||||||
public String getText() {
|
|
||||||
return myText;
|
|
||||||
}
|
|
||||||
}
|
|
57
src/com/maddyhome/idea/vim/ex/ExOutputModel.kt
Normal file
57
src/com/maddyhome/idea/vim/ex/ExOutputModel.kt
Normal file
@@ -0,0 +1,57 @@
|
|||||||
|
/*
|
||||||
|
* IdeaVim - Vim emulator for IDEs based on the IntelliJ platform
|
||||||
|
* Copyright (C) 2003-2019 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 <https://www.gnu.org/licenses/>.
|
||||||
|
*/
|
||||||
|
package com.maddyhome.idea.vim.ex
|
||||||
|
|
||||||
|
import com.intellij.openapi.application.ApplicationManager
|
||||||
|
import com.intellij.openapi.editor.Editor
|
||||||
|
import com.maddyhome.idea.vim.helper.vimExOutput
|
||||||
|
import com.maddyhome.idea.vim.ui.ExOutputPanel
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @author vlan
|
||||||
|
*/
|
||||||
|
class ExOutputModel private constructor(private val myEditor: Editor) {
|
||||||
|
var text: String? = null
|
||||||
|
private set
|
||||||
|
|
||||||
|
fun output(text: String) {
|
||||||
|
this.text = text
|
||||||
|
if (!ApplicationManager.getApplication().isUnitTestMode) {
|
||||||
|
ExOutputPanel.getInstance(myEditor).setText(text)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fun clear() {
|
||||||
|
text = null
|
||||||
|
if (!ApplicationManager.getApplication().isUnitTestMode) {
|
||||||
|
ExOutputPanel.getInstance(myEditor).deactivate(false)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
companion object {
|
||||||
|
@JvmStatic
|
||||||
|
fun getInstance(editor: Editor): ExOutputModel {
|
||||||
|
var model = editor.vimExOutput
|
||||||
|
if (model == null) {
|
||||||
|
model = ExOutputModel(editor)
|
||||||
|
editor.vimExOutput = model
|
||||||
|
}
|
||||||
|
return model
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@@ -1,39 +0,0 @@
|
|||||||
/*
|
|
||||||
* IdeaVim - Vim emulator for IDEs based on the IntelliJ platform
|
|
||||||
* Copyright (C) 2003-2019 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 <https://www.gnu.org/licenses/>.
|
|
||||||
*/
|
|
||||||
|
|
||||||
package com.maddyhome.idea.vim.ex;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Exception class
|
|
||||||
*/
|
|
||||||
public class InvalidArgumentException extends ExException {
|
|
||||||
/**
|
|
||||||
* Constructs an <code>InvalidArgumentException</code> with no specified detail message.
|
|
||||||
*/
|
|
||||||
public InvalidArgumentException() {
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Constructs an <code>InvalidArgumentException</code> with the specified detail message.
|
|
||||||
*
|
|
||||||
* @param s the detail message.
|
|
||||||
*/
|
|
||||||
public InvalidArgumentException(String s) {
|
|
||||||
super(s);
|
|
||||||
}
|
|
||||||
}
|
|
@@ -1,39 +0,0 @@
|
|||||||
/*
|
|
||||||
* IdeaVim - Vim emulator for IDEs based on the IntelliJ platform
|
|
||||||
* Copyright (C) 2003-2019 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 <https://www.gnu.org/licenses/>.
|
|
||||||
*/
|
|
||||||
|
|
||||||
package com.maddyhome.idea.vim.ex;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Exception class
|
|
||||||
*/
|
|
||||||
public class InvalidRangeException extends ExException {
|
|
||||||
/**
|
|
||||||
* Constructs an <code>InvalidRangeException</code> with no specified detail message.
|
|
||||||
*/
|
|
||||||
public InvalidRangeException() {
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Constructs an <code>InvalidRangeException</code> with the specified detail message.
|
|
||||||
*
|
|
||||||
* @param s the detail message.
|
|
||||||
*/
|
|
||||||
public InvalidRangeException(String s) {
|
|
||||||
super(s);
|
|
||||||
}
|
|
||||||
}
|
|
@@ -1,44 +0,0 @@
|
|||||||
/*
|
|
||||||
* IdeaVim - Vim emulator for IDEs based on the IntelliJ platform
|
|
||||||
* Copyright (C) 2003-2019 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 <https://www.gnu.org/licenses/>.
|
|
||||||
*/
|
|
||||||
|
|
||||||
package com.maddyhome.idea.vim.ex;
|
|
||||||
|
|
||||||
|
|
||||||
public class LineRange {
|
|
||||||
public LineRange(int startLine, int endLine) {
|
|
||||||
if (endLine >= startLine) {
|
|
||||||
this.startLine = startLine;
|
|
||||||
this.endLine = endLine;
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
this.startLine = endLine;
|
|
||||||
this.endLine = startLine;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public int getStartLine() {
|
|
||||||
return startLine;
|
|
||||||
}
|
|
||||||
|
|
||||||
public int getEndLine() {
|
|
||||||
return endLine;
|
|
||||||
}
|
|
||||||
|
|
||||||
private final int startLine;
|
|
||||||
private final int endLine;
|
|
||||||
}
|
|
@@ -1,39 +0,0 @@
|
|||||||
/*
|
|
||||||
* IdeaVim - Vim emulator for IDEs based on the IntelliJ platform
|
|
||||||
* Copyright (C) 2003-2019 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 <https://www.gnu.org/licenses/>.
|
|
||||||
*/
|
|
||||||
|
|
||||||
package com.maddyhome.idea.vim.ex;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Exception class
|
|
||||||
*/
|
|
||||||
public class MissingArgumentException extends ExException {
|
|
||||||
/**
|
|
||||||
* Constructs an <code>InvalidArgumentException</code> with no specified detail message.
|
|
||||||
*/
|
|
||||||
public MissingArgumentException() {
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Constructs an <code>InvalidArgumentException</code> with the specified detail message.
|
|
||||||
*
|
|
||||||
* @param s the detail message.
|
|
||||||
*/
|
|
||||||
public MissingArgumentException(String s) {
|
|
||||||
super(s);
|
|
||||||
}
|
|
||||||
}
|
|
@@ -1,39 +0,0 @@
|
|||||||
/*
|
|
||||||
* IdeaVim - Vim emulator for IDEs based on the IntelliJ platform
|
|
||||||
* Copyright (C) 2003-2019 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 <https://www.gnu.org/licenses/>.
|
|
||||||
*/
|
|
||||||
|
|
||||||
package com.maddyhome.idea.vim.ex;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Exception class
|
|
||||||
*/
|
|
||||||
public class MissingRangeException extends ExException {
|
|
||||||
/**
|
|
||||||
* Constructs an <code>InvalidArgumentException</code> with no specified detail message.
|
|
||||||
*/
|
|
||||||
public MissingRangeException() {
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Constructs an <code>InvalidArgumentException</code> with the specified detail message.
|
|
||||||
*
|
|
||||||
* @param s the detail message.
|
|
||||||
*/
|
|
||||||
public MissingRangeException(String s) {
|
|
||||||
super(s);
|
|
||||||
}
|
|
||||||
}
|
|
@@ -1,39 +0,0 @@
|
|||||||
/*
|
|
||||||
* IdeaVim - Vim emulator for IDEs based on the IntelliJ platform
|
|
||||||
* Copyright (C) 2003-2019 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 <https://www.gnu.org/licenses/>.
|
|
||||||
*/
|
|
||||||
|
|
||||||
package com.maddyhome.idea.vim.ex;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Exception class
|
|
||||||
*/
|
|
||||||
public class NoArgumentAllowedException extends ExException {
|
|
||||||
/**
|
|
||||||
* Constructs an <code>InvalidArgumentException</code> with no specified detail message.
|
|
||||||
*/
|
|
||||||
public NoArgumentAllowedException() {
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Constructs an <code>InvalidArgumentException</code> with the specified detail message.
|
|
||||||
*
|
|
||||||
* @param s the detail message.
|
|
||||||
*/
|
|
||||||
public NoArgumentAllowedException(String s) {
|
|
||||||
super(s);
|
|
||||||
}
|
|
||||||
}
|
|
@@ -1,39 +0,0 @@
|
|||||||
/*
|
|
||||||
* IdeaVim - Vim emulator for IDEs based on the IntelliJ platform
|
|
||||||
* Copyright (C) 2003-2019 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 <https://www.gnu.org/licenses/>.
|
|
||||||
*/
|
|
||||||
|
|
||||||
package com.maddyhome.idea.vim.ex;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Exception class
|
|
||||||
*/
|
|
||||||
public class NoRangeAllowedException extends ExException {
|
|
||||||
/**
|
|
||||||
* Constructs an <code>NoRangeAllowedException</code> with no specified detail message.
|
|
||||||
*/
|
|
||||||
public NoRangeAllowedException() {
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Constructs an <code>NoRangeAllowedException</code> with the specified detail message.
|
|
||||||
*
|
|
||||||
* @param s the detail message.
|
|
||||||
*/
|
|
||||||
public NoRangeAllowedException(String s) {
|
|
||||||
super(s);
|
|
||||||
}
|
|
||||||
}
|
|
@@ -1,49 +0,0 @@
|
|||||||
/*
|
|
||||||
* IdeaVim - Vim emulator for IDEs based on the IntelliJ platform
|
|
||||||
* Copyright (C) 2003-2019 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 <https://www.gnu.org/licenses/>.
|
|
||||||
*/
|
|
||||||
|
|
||||||
package com.maddyhome.idea.vim.ex;
|
|
||||||
|
|
||||||
import org.jetbrains.annotations.NotNull;
|
|
||||||
|
|
||||||
|
|
||||||
public class ParseResult {
|
|
||||||
@NotNull private final Ranges ranges;
|
|
||||||
@NotNull private final String command;
|
|
||||||
@NotNull private final String argument;
|
|
||||||
|
|
||||||
public ParseResult(@NotNull Ranges ranges, @NotNull String command, @NotNull String argument) {
|
|
||||||
this.ranges = ranges;
|
|
||||||
this.argument = argument;
|
|
||||||
this.command = command;
|
|
||||||
}
|
|
||||||
|
|
||||||
@NotNull
|
|
||||||
public String getCommand() {
|
|
||||||
return command;
|
|
||||||
}
|
|
||||||
|
|
||||||
@NotNull
|
|
||||||
public String getArgument() {
|
|
||||||
return argument;
|
|
||||||
}
|
|
||||||
|
|
||||||
@NotNull
|
|
||||||
public Ranges getRanges() {
|
|
||||||
return ranges;
|
|
||||||
}
|
|
||||||
}
|
|
@@ -1,46 +0,0 @@
|
|||||||
/*
|
|
||||||
* IdeaVim - Vim emulator for IDEs based on the IntelliJ platform
|
|
||||||
* Copyright (C) 2003-2019 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 <https://www.gnu.org/licenses/>.
|
|
||||||
*/
|
|
||||||
|
|
||||||
package com.maddyhome.idea.vim.ex;
|
|
||||||
|
|
||||||
import com.intellij.openapi.editor.Caret;
|
|
||||||
import com.intellij.openapi.editor.Editor;
|
|
||||||
import org.jetbrains.annotations.NotNull;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Represents an Ex command range
|
|
||||||
*/
|
|
||||||
public interface Range {
|
|
||||||
/**
|
|
||||||
* Get the line number this range represents
|
|
||||||
*
|
|
||||||
* @param editor The editor to get the line for
|
|
||||||
* @param lastZero True if the last line set represents before the start of the false
|
|
||||||
* @return The zero based logical line in the editor that the range represents
|
|
||||||
*/
|
|
||||||
int getLine(Editor editor, boolean lastZero);
|
|
||||||
|
|
||||||
int getLine(@NotNull Editor editor, @NotNull Caret caret, boolean lastZero);
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Should the cursor be moved to this range's line?
|
|
||||||
*
|
|
||||||
* @return True if cursor should be moved, false if not
|
|
||||||
*/
|
|
||||||
boolean isMove();
|
|
||||||
}
|
|
@@ -1,288 +0,0 @@
|
|||||||
/*
|
|
||||||
* IdeaVim - Vim emulator for IDEs based on the IntelliJ platform
|
|
||||||
* Copyright (C) 2003-2019 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 <https://www.gnu.org/licenses/>.
|
|
||||||
*/
|
|
||||||
|
|
||||||
package com.maddyhome.idea.vim.ex;
|
|
||||||
|
|
||||||
import com.intellij.openapi.actionSystem.DataContext;
|
|
||||||
import com.intellij.openapi.editor.Caret;
|
|
||||||
import com.intellij.openapi.editor.Editor;
|
|
||||||
import com.maddyhome.idea.vim.VimPlugin;
|
|
||||||
import com.maddyhome.idea.vim.common.TextRange;
|
|
||||||
import com.maddyhome.idea.vim.ex.range.AbstractRange;
|
|
||||||
import com.maddyhome.idea.vim.group.MotionGroup;
|
|
||||||
import com.maddyhome.idea.vim.helper.EditorHelper;
|
|
||||||
import org.jetbrains.annotations.NotNull;
|
|
||||||
|
|
||||||
import java.util.ArrayList;
|
|
||||||
import java.util.Collections;
|
|
||||||
import java.util.List;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Handles the set of range values entered as part of an Ex command.
|
|
||||||
*/
|
|
||||||
public class Ranges {
|
|
||||||
/**
|
|
||||||
* Create the empty range list
|
|
||||||
*/
|
|
||||||
public Ranges() {
|
|
||||||
ranges = new ArrayList<>();
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Adds a range to the list
|
|
||||||
*
|
|
||||||
* @param range The list of ranges to append to the current list
|
|
||||||
*/
|
|
||||||
public void addRange(@NotNull Range[] range) {
|
|
||||||
Collections.addAll(ranges, range);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Gets the number of ranges in the list
|
|
||||||
*
|
|
||||||
* @return The range count
|
|
||||||
*/
|
|
||||||
public int size() {
|
|
||||||
return ranges.size();
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Sets the default line to be used by this range if no range was actually given by the user. -1 is used to
|
|
||||||
* mean the current line.
|
|
||||||
*
|
|
||||||
* @param line The line or -1 for current line
|
|
||||||
*/
|
|
||||||
public void setDefaultLine(int line) {
|
|
||||||
defaultLine = line;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Gets the line of the last range specified in the range list
|
|
||||||
*
|
|
||||||
* @param editor The editor to get the line for
|
|
||||||
* @return The line number represented by the range
|
|
||||||
*/
|
|
||||||
public int getLine(@NotNull Editor editor) {
|
|
||||||
processRange(editor);
|
|
||||||
|
|
||||||
return endLine;
|
|
||||||
}
|
|
||||||
|
|
||||||
public int getLine(@NotNull Editor editor, @NotNull Caret caret, @NotNull DataContext context) {
|
|
||||||
processRange(editor, caret, context);
|
|
||||||
return endLine;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Gets the start line number the range represents
|
|
||||||
*
|
|
||||||
* @param editor The editor to get the line number for
|
|
||||||
* @return The starting line number
|
|
||||||
*/
|
|
||||||
public int getFirstLine(@NotNull Editor editor) {
|
|
||||||
processRange(editor);
|
|
||||||
|
|
||||||
return startLine;
|
|
||||||
}
|
|
||||||
|
|
||||||
public int getFirstLine(@NotNull Editor editor, @NotNull Caret caret, @NotNull DataContext context) {
|
|
||||||
processRange(editor, caret, context);
|
|
||||||
return startLine;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Gets the count for an Ex command. This is either an explicit count enter at the end of the command or the
|
|
||||||
* end of the specified range.
|
|
||||||
*
|
|
||||||
* @param editor The editor to get the count for
|
|
||||||
* @param context The data context
|
|
||||||
* @param count The count given at the end of the command or -1 if no such count (use end line)
|
|
||||||
* @return count if count != -1, else return end line of range
|
|
||||||
*/
|
|
||||||
public int getCount(@NotNull Editor editor, DataContext context, int count) {
|
|
||||||
if (count == -1) {
|
|
||||||
return getLine(editor);
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
return count;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public int getCount(@NotNull Editor editor, @NotNull Caret caret, @NotNull DataContext context, int count) {
|
|
||||||
if (count == -1) return getLine(editor, caret, context);
|
|
||||||
return count;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Gets the line range represented by this range. If a count is given, the range is the range end line through
|
|
||||||
* count-1 lines. If no count is given (-1), the range is the range given by the user.
|
|
||||||
*
|
|
||||||
* @param editor The editor to get the range for
|
|
||||||
* @param count The count given at the end of the command or -1 if no such count
|
|
||||||
* @return The line range
|
|
||||||
*/
|
|
||||||
@NotNull
|
|
||||||
public LineRange getLineRange(@NotNull Editor editor, int count) {
|
|
||||||
processRange(editor);
|
|
||||||
int end;
|
|
||||||
int start;
|
|
||||||
if (count == -1) {
|
|
||||||
end = endLine;
|
|
||||||
start = startLine;
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
start = endLine;
|
|
||||||
end = start + count - 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
return new LineRange(start, end);
|
|
||||||
}
|
|
||||||
|
|
||||||
public LineRange getLineRange(@NotNull Editor editor, @NotNull Caret caret, @NotNull DataContext context, int count) {
|
|
||||||
processRange(editor, caret, context);
|
|
||||||
if (count == -1) return new LineRange(startLine, endLine);
|
|
||||||
return new LineRange(endLine, endLine + count - 1);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Gets the text range represented by this range. If a count is given, the range is the range end line through
|
|
||||||
* count-1 lines. If no count is given (-1), the range is the range given by the user. The text range is based
|
|
||||||
* on the line range but this is character based from the start of the first line to the end of the last line.
|
|
||||||
*
|
|
||||||
* @param editor The editor to get the range for
|
|
||||||
* @param context The data context
|
|
||||||
* @param count The count given at the end of the command or -1 if no such count
|
|
||||||
* @return The text range
|
|
||||||
*/
|
|
||||||
@NotNull
|
|
||||||
public TextRange getTextRange(@NotNull Editor editor, DataContext context, int count) {
|
|
||||||
LineRange lr = getLineRange(editor, count);
|
|
||||||
int start = EditorHelper.getLineStartOffset(editor, lr.getStartLine());
|
|
||||||
int end = EditorHelper.getLineEndOffset(editor, lr.getEndLine(), true) + 1;
|
|
||||||
|
|
||||||
return new TextRange(start, Math.min(end, EditorHelper.getFileSize(editor)));
|
|
||||||
}
|
|
||||||
|
|
||||||
public TextRange getTextRange(@NotNull Editor editor, @NotNull Caret caret, @NotNull DataContext context, int count) {
|
|
||||||
final LineRange lineRange = getLineRange(editor, caret, context, count);
|
|
||||||
|
|
||||||
final int start = EditorHelper.getLineStartOffset(editor, lineRange.getStartLine());
|
|
||||||
final int end = EditorHelper.getLineEndOffset(editor, lineRange.getEndLine(), true) + 1;
|
|
||||||
|
|
||||||
return new TextRange(start, Math.min(end, EditorHelper.getFileSize(editor)));
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Helper method to get the text range for the current cursor line
|
|
||||||
*
|
|
||||||
* @param editor The editor to get the range for
|
|
||||||
* @param context The data context
|
|
||||||
* @return The range of the current line
|
|
||||||
*/
|
|
||||||
@NotNull
|
|
||||||
public static TextRange getCurrentLineRange(@NotNull Editor editor, DataContext context) {
|
|
||||||
Ranges ranges = new Ranges();
|
|
||||||
|
|
||||||
return ranges.getTextRange(editor, context, -1);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Helper method to get the text range for the current file
|
|
||||||
*
|
|
||||||
* @param editor The editor to get the range for
|
|
||||||
* @param context The data context
|
|
||||||
* @return The range of the current file
|
|
||||||
*/
|
|
||||||
@NotNull
|
|
||||||
public static TextRange getFileTextRange(@NotNull Editor editor, DataContext context) {
|
|
||||||
Ranges ranges = new Ranges();
|
|
||||||
final Range[] range = AbstractRange.createRange("%", 0, false);
|
|
||||||
if (range != null) {
|
|
||||||
ranges.addRange(range);
|
|
||||||
}
|
|
||||||
|
|
||||||
return ranges.getTextRange(editor, context, -1);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Processes the list of ranges and calculates the start and end lines of the range
|
|
||||||
*
|
|
||||||
* @param editor The editor to get the lines for
|
|
||||||
*/
|
|
||||||
private void processRange(@NotNull Editor editor) {
|
|
||||||
// Already done
|
|
||||||
if (done) return;
|
|
||||||
|
|
||||||
// Start with the range being the current line
|
|
||||||
startLine = defaultLine == -1 ? editor.getCaretModel().getLogicalPosition().line : defaultLine;
|
|
||||||
endLine = startLine;
|
|
||||||
boolean lastZero = false;
|
|
||||||
// Now process each range, moving the cursor if appropriate
|
|
||||||
for (Range range : ranges) {
|
|
||||||
startLine = endLine;
|
|
||||||
endLine = range.getLine(editor, lastZero);
|
|
||||||
if (range.isMove()) {
|
|
||||||
MotionGroup.moveCaret(editor, editor.getCaretModel().getPrimaryCaret(),
|
|
||||||
VimPlugin.getMotion().moveCaretToLine(editor, endLine, editor.getCaretModel().getPrimaryCaret()));
|
|
||||||
}
|
|
||||||
// Did that last range represent the start of the file?
|
|
||||||
lastZero = (endLine < 0);
|
|
||||||
count++;
|
|
||||||
}
|
|
||||||
|
|
||||||
// If only one range given, make the start and end the same
|
|
||||||
if (count == 1) {
|
|
||||||
startLine = endLine;
|
|
||||||
}
|
|
||||||
|
|
||||||
done = true;
|
|
||||||
}
|
|
||||||
|
|
||||||
private void processRange(@NotNull Editor editor, @NotNull Caret caret, @NotNull DataContext context) {
|
|
||||||
startLine = defaultLine == -1 ? caret.getLogicalPosition().line : defaultLine;
|
|
||||||
endLine = startLine;
|
|
||||||
boolean lastZero = false;
|
|
||||||
for (Range range : ranges) {
|
|
||||||
startLine = endLine;
|
|
||||||
endLine = range.getLine(editor, caret, lastZero);
|
|
||||||
|
|
||||||
if (range.isMove()) MotionGroup.moveCaret(editor, caret, VimPlugin.getMotion().moveCaretToLine(editor, endLine, editor.getCaretModel().getPrimaryCaret()));
|
|
||||||
|
|
||||||
lastZero = endLine < 0;
|
|
||||||
++count;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (count == 1) startLine = endLine;
|
|
||||||
|
|
||||||
count = 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
@NotNull
|
|
||||||
public String toString() {
|
|
||||||
|
|
||||||
return "Ranges[ranges=" + ranges + "]";
|
|
||||||
}
|
|
||||||
|
|
||||||
private int startLine = 0;
|
|
||||||
private int endLine = 0;
|
|
||||||
private int count = 0;
|
|
||||||
private int defaultLine = -1;
|
|
||||||
private boolean done = false;
|
|
||||||
@NotNull
|
|
||||||
private final List<Range> ranges;
|
|
||||||
}
|
|
@@ -25,6 +25,7 @@ import com.maddyhome.idea.vim.VimPlugin
|
|||||||
import com.maddyhome.idea.vim.command.SelectionType
|
import com.maddyhome.idea.vim.command.SelectionType
|
||||||
import com.maddyhome.idea.vim.common.TextRange
|
import com.maddyhome.idea.vim.common.TextRange
|
||||||
import com.maddyhome.idea.vim.ex.*
|
import com.maddyhome.idea.vim.ex.*
|
||||||
|
import com.maddyhome.idea.vim.ex.ranges.LineRange
|
||||||
import com.maddyhome.idea.vim.group.copy.PutData
|
import com.maddyhome.idea.vim.group.copy.PutData
|
||||||
import com.maddyhome.idea.vim.helper.EditorHelper
|
import com.maddyhome.idea.vim.helper.EditorHelper
|
||||||
import com.maddyhome.idea.vim.helper.MessageHelper
|
import com.maddyhome.idea.vim.helper.MessageHelper
|
||||||
|
@@ -24,6 +24,7 @@ import com.intellij.openapi.editor.Editor
|
|||||||
import com.intellij.openapi.util.text.StringUtil
|
import com.intellij.openapi.util.text.StringUtil
|
||||||
import com.maddyhome.idea.vim.VimPlugin
|
import com.maddyhome.idea.vim.VimPlugin
|
||||||
import com.maddyhome.idea.vim.ex.*
|
import com.maddyhome.idea.vim.ex.*
|
||||||
|
import com.maddyhome.idea.vim.ex.ranges.LineRange
|
||||||
import com.maddyhome.idea.vim.helper.inBlockSubMode
|
import com.maddyhome.idea.vim.helper.inBlockSubMode
|
||||||
import java.util.*
|
import java.util.*
|
||||||
|
|
||||||
|
@@ -1,154 +0,0 @@
|
|||||||
/*
|
|
||||||
* IdeaVim - Vim emulator for IDEs based on the IntelliJ platform
|
|
||||||
* Copyright (C) 2003-2019 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 <https://www.gnu.org/licenses/>.
|
|
||||||
*/
|
|
||||||
|
|
||||||
package com.maddyhome.idea.vim.ex.range;
|
|
||||||
|
|
||||||
import com.intellij.openapi.editor.Caret;
|
|
||||||
import com.intellij.openapi.editor.Editor;
|
|
||||||
import com.maddyhome.idea.vim.ex.Range;
|
|
||||||
import org.jetbrains.annotations.NotNull;
|
|
||||||
import org.jetbrains.annotations.Nullable;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Base for all Ex command ranges
|
|
||||||
*/
|
|
||||||
public abstract class AbstractRange implements Range {
|
|
||||||
/**
|
|
||||||
* Factory method used to create an appropriate range based on the range text
|
|
||||||
*
|
|
||||||
* @param str The range text
|
|
||||||
* @param offset Any offset specified after the range
|
|
||||||
* @param move True if cursor should be moved to range line
|
|
||||||
* @return The ranges appropriate to the text
|
|
||||||
*/
|
|
||||||
@Nullable
|
|
||||||
public static Range[] createRange(@NotNull String str, int offset, boolean move) {
|
|
||||||
// Current line
|
|
||||||
if (str.equals(".") || str.length() == 0) {
|
|
||||||
return new Range[]{new LineNumberRange(offset, move)};
|
|
||||||
}
|
|
||||||
// All lines
|
|
||||||
else if (str.equals("%")) {
|
|
||||||
return new Range[]{
|
|
||||||
new LineNumberRange(0, 0, move),
|
|
||||||
new LineNumberRange(LineNumberRange.LAST_LINE, offset, move)
|
|
||||||
};
|
|
||||||
}
|
|
||||||
// Last line
|
|
||||||
else if (str.equals("$")) {
|
|
||||||
return new Range[]{new LineNumberRange(LineNumberRange.LAST_LINE, offset, move)};
|
|
||||||
}
|
|
||||||
// Mark like
|
|
||||||
else if (str.startsWith("'") && str.length() == 2) {
|
|
||||||
return new Range[]{new MarkRange(str.charAt(1), offset, move)};
|
|
||||||
}
|
|
||||||
// Pattern
|
|
||||||
else if (str.startsWith("/") || str.startsWith("\\/") || str.startsWith("\\&")) {
|
|
||||||
return new Range[]{new SearchRange(str, offset, move)};
|
|
||||||
}
|
|
||||||
// Pattern
|
|
||||||
else if (str.startsWith("?") || str.startsWith("\\?")) {
|
|
||||||
return new Range[]{new SearchRange(str, offset, move)};
|
|
||||||
}
|
|
||||||
// Specific line number (1 based)
|
|
||||||
else {
|
|
||||||
try {
|
|
||||||
int line = Integer.parseInt(str) - 1;
|
|
||||||
|
|
||||||
return new Range[]{new LineNumberRange(line, offset, move)};
|
|
||||||
}
|
|
||||||
catch (NumberFormatException e) {
|
|
||||||
// Ignore - we'll send back bad range later.
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// User entered an invalid range.
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Create the range
|
|
||||||
*
|
|
||||||
* @param offset The line offset
|
|
||||||
* @param move True if cursor moved
|
|
||||||
*/
|
|
||||||
public AbstractRange(int offset, boolean move) {
|
|
||||||
this.offset = offset;
|
|
||||||
this.move = move;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Gets the line offset
|
|
||||||
*
|
|
||||||
* @return The line offset
|
|
||||||
*/
|
|
||||||
protected int getOffset() {
|
|
||||||
return offset;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Should the cursor move
|
|
||||||
*
|
|
||||||
* @return True if cursor should move, false if not
|
|
||||||
*/
|
|
||||||
@Override
|
|
||||||
public boolean isMove() {
|
|
||||||
return move;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Gets the line number (0 based) specificied by this range. Includes the offset.
|
|
||||||
*
|
|
||||||
* @param editor The editor to get the line for
|
|
||||||
* @param lastZero True if last line was set to start of file
|
|
||||||
* @return The zero based line number, -1 if unable to get the line number
|
|
||||||
*/
|
|
||||||
@Override
|
|
||||||
public int getLine(Editor editor, boolean lastZero) {
|
|
||||||
int line = getRangeLine(editor, lastZero);
|
|
||||||
|
|
||||||
return line + offset;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public int getLine(@NotNull Editor editor, @NotNull Caret caret, boolean lastZero) {
|
|
||||||
if (offset == 0) return getRangeLine(editor, lastZero);
|
|
||||||
|
|
||||||
return getRangeLine(editor, caret, lastZero) + offset;
|
|
||||||
}
|
|
||||||
|
|
||||||
@NotNull
|
|
||||||
public String toString() {
|
|
||||||
return "AbstractRange" + "{offset=" + offset + ", move=" + move + '}';
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Gets the line number specified by this range without regard to any offset.
|
|
||||||
*
|
|
||||||
* @param editor The editor to get the line for
|
|
||||||
* @param lastZero True if last line was set to start of file
|
|
||||||
* @return The zero based line number, -1 if inable to get the line number
|
|
||||||
*/
|
|
||||||
protected abstract int getRangeLine(Editor editor, boolean lastZero);
|
|
||||||
|
|
||||||
protected abstract int getRangeLine(@NotNull Editor editor, @NotNull Caret caret,
|
|
||||||
boolean lastZero);
|
|
||||||
|
|
||||||
protected final int offset;
|
|
||||||
protected final boolean move;
|
|
||||||
}
|
|
@@ -1,92 +0,0 @@
|
|||||||
/*
|
|
||||||
* IdeaVim - Vim emulator for IDEs based on the IntelliJ platform
|
|
||||||
* Copyright (C) 2003-2019 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 <https://www.gnu.org/licenses/>.
|
|
||||||
*/
|
|
||||||
|
|
||||||
package com.maddyhome.idea.vim.ex.range;
|
|
||||||
|
|
||||||
import com.intellij.openapi.editor.Caret;
|
|
||||||
import com.intellij.openapi.editor.Editor;
|
|
||||||
import com.maddyhome.idea.vim.helper.EditorHelper;
|
|
||||||
import org.jetbrains.annotations.NotNull;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Represents a specific line, the current line, or the last line of a file
|
|
||||||
*/
|
|
||||||
public class LineNumberRange extends AbstractRange {
|
|
||||||
public static final int CURRENT_LINE = -99999999;
|
|
||||||
public static final int LAST_LINE = -99999998;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Create a range for the current line
|
|
||||||
*
|
|
||||||
* @param offset The range offset
|
|
||||||
* @param move True if cursor should be moved
|
|
||||||
*/
|
|
||||||
public LineNumberRange(int offset, boolean move) {
|
|
||||||
super(offset, move);
|
|
||||||
|
|
||||||
this.line = CURRENT_LINE;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Create a range for the given line
|
|
||||||
*
|
|
||||||
* @param offset The range offset
|
|
||||||
* @param move True if cursor should be moved
|
|
||||||
*/
|
|
||||||
public LineNumberRange(int line, int offset, boolean move) {
|
|
||||||
super(offset, move);
|
|
||||||
|
|
||||||
this.line = line;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Gets the line number specified by this range without regard to any offset.
|
|
||||||
*
|
|
||||||
* @param editor The editor to get the line for
|
|
||||||
* @param lastZero True if last line was set to start of file
|
|
||||||
* @return The zero based line number, -1 for start of file
|
|
||||||
*/
|
|
||||||
@Override
|
|
||||||
protected int getRangeLine(@NotNull Editor editor, boolean lastZero) {
|
|
||||||
if (line == CURRENT_LINE) {
|
|
||||||
line = editor.getCaretModel().getLogicalPosition().line;
|
|
||||||
}
|
|
||||||
else if (line == LAST_LINE) {
|
|
||||||
line = EditorHelper.getLineCount(editor) - 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
return line;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
protected int getRangeLine(@NotNull Editor editor, @NotNull Caret caret,
|
|
||||||
boolean lastZero) {
|
|
||||||
if (line == LAST_LINE) line = EditorHelper.getLineCount(editor) - 1;
|
|
||||||
else line = caret.getLogicalPosition().line;
|
|
||||||
|
|
||||||
return line;
|
|
||||||
}
|
|
||||||
|
|
||||||
@NotNull
|
|
||||||
public String toString() {
|
|
||||||
|
|
||||||
return "LineNumberRange[" + "line=" + line + ", " + super.toString() + "]";
|
|
||||||
}
|
|
||||||
|
|
||||||
private int line;
|
|
||||||
}
|
|
@@ -1,76 +0,0 @@
|
|||||||
/*
|
|
||||||
* IdeaVim - Vim emulator for IDEs based on the IntelliJ platform
|
|
||||||
* Copyright (C) 2003-2019 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 <https://www.gnu.org/licenses/>.
|
|
||||||
*/
|
|
||||||
|
|
||||||
package com.maddyhome.idea.vim.ex.range;
|
|
||||||
|
|
||||||
import com.intellij.openapi.editor.Caret;
|
|
||||||
import com.intellij.openapi.editor.Editor;
|
|
||||||
import com.maddyhome.idea.vim.VimPlugin;
|
|
||||||
import com.maddyhome.idea.vim.common.Mark;
|
|
||||||
import org.jetbrains.annotations.NotNull;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Represents the line specified by a mark
|
|
||||||
*/
|
|
||||||
public class MarkRange extends AbstractRange {
|
|
||||||
/**
|
|
||||||
* Create the mark range
|
|
||||||
*
|
|
||||||
* @param mark The mark name
|
|
||||||
* @param offset The range offset
|
|
||||||
* @param move True if cursor should be moved
|
|
||||||
*/
|
|
||||||
public MarkRange(char mark, int offset, boolean move) {
|
|
||||||
super(offset, move);
|
|
||||||
|
|
||||||
this.mark = mark;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Gets the line number specified by this range without regard to any offset.
|
|
||||||
*
|
|
||||||
* @param editor The editor to get the line for
|
|
||||||
* @param lastZero True if last line was set to start of file
|
|
||||||
* @return The zero based line number, -1 if there is no such mark set in the file
|
|
||||||
*/
|
|
||||||
@Override
|
|
||||||
public int getRangeLine(@NotNull Editor editor, boolean lastZero) {
|
|
||||||
Mark mark = VimPlugin.getMark().getFileMark(editor, this.mark);
|
|
||||||
|
|
||||||
if (mark != null) {
|
|
||||||
return mark.getLogicalLine();
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
return -1;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
protected int getRangeLine(@NotNull Editor editor, @NotNull Caret caret,
|
|
||||||
boolean lastZero) {
|
|
||||||
return getRangeLine(editor, lastZero);
|
|
||||||
}
|
|
||||||
|
|
||||||
@NotNull
|
|
||||||
public String toString() {
|
|
||||||
|
|
||||||
return "MarkRange[" + "mark=" + mark + ", " + super.toString() + "]";
|
|
||||||
}
|
|
||||||
|
|
||||||
private final char mark;
|
|
||||||
}
|
|
@@ -1,170 +0,0 @@
|
|||||||
/*
|
|
||||||
* IdeaVim - Vim emulator for IDEs based on the IntelliJ platform
|
|
||||||
* Copyright (C) 2003-2019 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 <https://www.gnu.org/licenses/>.
|
|
||||||
*/
|
|
||||||
|
|
||||||
package com.maddyhome.idea.vim.ex.range;
|
|
||||||
|
|
||||||
import com.intellij.openapi.diagnostic.Logger;
|
|
||||||
import com.intellij.openapi.editor.Caret;
|
|
||||||
import com.intellij.openapi.editor.Editor;
|
|
||||||
import com.maddyhome.idea.vim.VimPlugin;
|
|
||||||
import com.maddyhome.idea.vim.command.CommandFlags;
|
|
||||||
import org.jetbrains.annotations.NotNull;
|
|
||||||
|
|
||||||
import java.util.ArrayList;
|
|
||||||
import java.util.EnumSet;
|
|
||||||
import java.util.List;
|
|
||||||
import java.util.StringTokenizer;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Represents a range given by a search pattern. The pattern can be '\\/', '\\?', '\\&', /{pattern}/,
|
|
||||||
* or ?{pattern}?. The last two can be repeated 0 or more times after any of the others.
|
|
||||||
*/
|
|
||||||
public class SearchRange extends AbstractRange {
|
|
||||||
/**
|
|
||||||
* Create the pattern range
|
|
||||||
*
|
|
||||||
* @param pattern The text of the pattern. Each subpattern must be separated by the nul character (\\u0000)
|
|
||||||
* @param offset The range offset
|
|
||||||
* @param move True if the cursor should be moved
|
|
||||||
*/
|
|
||||||
public SearchRange(String pattern, int offset, boolean move) {
|
|
||||||
super(offset, move);
|
|
||||||
setPattern(pattern);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Parses the pattern into a list of subpatterns and flags
|
|
||||||
*
|
|
||||||
* @param pattern The full search pattern
|
|
||||||
*/
|
|
||||||
private void setPattern(String pattern) {
|
|
||||||
if (logger.isDebugEnabled()) {
|
|
||||||
logger.debug("pattern=" + pattern);
|
|
||||||
}
|
|
||||||
StringTokenizer tok = new StringTokenizer(pattern, "\u0000");
|
|
||||||
while (tok.hasMoreTokens()) {
|
|
||||||
String pat = tok.nextToken();
|
|
||||||
switch (pat) {
|
|
||||||
case "\\/":
|
|
||||||
patterns.add(VimPlugin.getSearch().getLastSearch());
|
|
||||||
flags.add(EnumSet.of(CommandFlags.FLAG_SEARCH_FWD));
|
|
||||||
break;
|
|
||||||
case "\\?":
|
|
||||||
patterns.add(VimPlugin.getSearch().getLastSearch());
|
|
||||||
flags.add(EnumSet.of(CommandFlags.FLAG_SEARCH_REV));
|
|
||||||
break;
|
|
||||||
case "\\&":
|
|
||||||
patterns.add(VimPlugin.getSearch().getLastPattern());
|
|
||||||
flags.add(EnumSet.of(CommandFlags.FLAG_SEARCH_FWD));
|
|
||||||
break;
|
|
||||||
default:
|
|
||||||
if (pat.charAt(0) == '/') {
|
|
||||||
flags.add(EnumSet.of(CommandFlags.FLAG_SEARCH_FWD));
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
flags.add(EnumSet.of(CommandFlags.FLAG_SEARCH_REV));
|
|
||||||
}
|
|
||||||
|
|
||||||
pat = pat.substring(1);
|
|
||||||
if (pat.charAt(pat.length() - 1) == pat.charAt(0)) {
|
|
||||||
pat = pat.substring(0, pat.length() - 1);
|
|
||||||
}
|
|
||||||
patterns.add(pat);
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Gets the line number specified by this range without regard to any offset.
|
|
||||||
*
|
|
||||||
* @param editor The editor to get the line for
|
|
||||||
* @param lastZero True if last line was set to start of file
|
|
||||||
* @return The zero based line number, -1 if the text was not found
|
|
||||||
*/
|
|
||||||
@Override
|
|
||||||
protected int getRangeLine(@NotNull Editor editor, boolean lastZero) {
|
|
||||||
// Each subsequent pattern is searched for starting in the line after the previous search match
|
|
||||||
int line = editor.getCaretModel().getLogicalPosition().line;
|
|
||||||
int pos = -1;
|
|
||||||
for (int i = 0; i < patterns.size(); i++) {
|
|
||||||
String pattern = patterns.get(i);
|
|
||||||
EnumSet<CommandFlags> flag = flags.get(i);
|
|
||||||
if (flag.contains(CommandFlags.FLAG_SEARCH_FWD) && !lastZero) {
|
|
||||||
pos = VimPlugin.getMotion().moveCaretToLineEnd(editor, line, true);
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
pos = VimPlugin.getMotion().moveCaretToLineStart(editor, line);
|
|
||||||
}
|
|
||||||
|
|
||||||
pos = VimPlugin.getSearch().search(editor, pattern, pos, 1, flag);
|
|
||||||
if (pos == -1) {
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
line = editor.offsetToLogicalPosition(pos).line;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (pos != -1) {
|
|
||||||
return line;
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
return -1;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
protected int getRangeLine(@NotNull Editor editor, @NotNull Caret caret,
|
|
||||||
boolean lastZero) {
|
|
||||||
int line = caret.getLogicalPosition().line;
|
|
||||||
int offset = -1;
|
|
||||||
for (int i = 0; i < patterns.size(); i++) {
|
|
||||||
final String pattern = patterns.get(i);
|
|
||||||
final EnumSet<CommandFlags> flag = flags.get(i);
|
|
||||||
|
|
||||||
offset = VimPlugin.getSearch().search(editor, pattern, getSearchOffset(editor, line, flag, lastZero), 1, flag);
|
|
||||||
if (offset == -1) break;
|
|
||||||
|
|
||||||
line = editor.offsetToLogicalPosition(offset).line;
|
|
||||||
}
|
|
||||||
|
|
||||||
return offset != -1 ? line : -1;
|
|
||||||
}
|
|
||||||
|
|
||||||
private int getSearchOffset(@NotNull Editor editor, int line, EnumSet<CommandFlags> flag, boolean lastZero) {
|
|
||||||
if (flag.contains(CommandFlags.FLAG_SEARCH_FWD) && !lastZero) {
|
|
||||||
return VimPlugin.getMotion().moveCaretToLineEnd(editor, line, true);
|
|
||||||
}
|
|
||||||
|
|
||||||
return VimPlugin.getMotion().moveCaretToLineStart(editor, line);
|
|
||||||
}
|
|
||||||
|
|
||||||
@NotNull
|
|
||||||
public String toString() {
|
|
||||||
|
|
||||||
return "SearchRange[" + "patterns=" + patterns + ", " + super.toString() + "]";
|
|
||||||
}
|
|
||||||
|
|
||||||
@NotNull
|
|
||||||
private final List<String> patterns = new ArrayList<>();
|
|
||||||
@NotNull
|
|
||||||
private final List<EnumSet<CommandFlags>> flags = new ArrayList<>();
|
|
||||||
|
|
||||||
private static final Logger logger = Logger.getInstance(SearchRange.class.getName());
|
|
||||||
}
|
|
296
src/com/maddyhome/idea/vim/ex/ranges/ExRanges.kt
Normal file
296
src/com/maddyhome/idea/vim/ex/ranges/ExRanges.kt
Normal file
@@ -0,0 +1,296 @@
|
|||||||
|
/*
|
||||||
|
* IdeaVim - Vim emulator for IDEs based on the IntelliJ platform
|
||||||
|
* Copyright (C) 2003-2019 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 <https://www.gnu.org/licenses/>.
|
||||||
|
*/
|
||||||
|
package com.maddyhome.idea.vim.ex.ranges
|
||||||
|
|
||||||
|
import com.intellij.openapi.diagnostic.debug
|
||||||
|
import com.intellij.openapi.diagnostic.logger
|
||||||
|
import com.intellij.openapi.editor.Caret
|
||||||
|
import com.intellij.openapi.editor.Editor
|
||||||
|
import com.maddyhome.idea.vim.VimPlugin
|
||||||
|
import com.maddyhome.idea.vim.command.CommandFlags
|
||||||
|
import com.maddyhome.idea.vim.helper.EditorHelper
|
||||||
|
import com.maddyhome.idea.vim.helper.enumSetOf
|
||||||
|
import java.util.*
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Base for all Ex command ranges
|
||||||
|
*/
|
||||||
|
sealed class Range(
|
||||||
|
// Line offset
|
||||||
|
protected val offset: Int,
|
||||||
|
val isMove: Boolean
|
||||||
|
) {
|
||||||
|
/**
|
||||||
|
* Gets the line number (0 based) specificied by this range. Includes the offset.
|
||||||
|
*
|
||||||
|
* @param editor The editor to get the line for
|
||||||
|
* @param lastZero True if last line was set to start of file
|
||||||
|
* @return The zero based line number, -1 if unable to get the line number
|
||||||
|
*/
|
||||||
|
fun getLine(editor: Editor, lastZero: Boolean): Int {
|
||||||
|
val line = getRangeLine(editor, lastZero)
|
||||||
|
return line + offset
|
||||||
|
}
|
||||||
|
|
||||||
|
fun getLine(editor: Editor, caret: Caret, lastZero: Boolean): Int {
|
||||||
|
return if (offset == 0) getRangeLine(editor, lastZero) else getRangeLine(editor, caret, lastZero) + offset
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun toString(): String = "Range{offset=$offset, move=$isMove}"
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Gets the line number specified by this range without regard to any offset.
|
||||||
|
*
|
||||||
|
* @param editor The editor to get the line for
|
||||||
|
* @param lastZero True if last line was set to start of file
|
||||||
|
* @return The zero based line number, -1 if inable to get the line number
|
||||||
|
*/
|
||||||
|
protected abstract fun getRangeLine(editor: Editor, lastZero: Boolean): Int
|
||||||
|
|
||||||
|
protected abstract fun getRangeLine(editor: Editor, caret: Caret, lastZero: Boolean): Int
|
||||||
|
|
||||||
|
companion object {
|
||||||
|
/**
|
||||||
|
* Factory method used to create an appropriate range based on the range text
|
||||||
|
*
|
||||||
|
* @param str The range text
|
||||||
|
* @param offset Any offset specified after the range
|
||||||
|
* @param move True if cursor should be moved to range line
|
||||||
|
* @return The ranges appropriate to the text
|
||||||
|
*/
|
||||||
|
@JvmStatic
|
||||||
|
fun createRange(str: String, offset: Int, move: Boolean): Array<Range>? {
|
||||||
|
// Current line
|
||||||
|
if (str.isEmpty() || str == ".") {
|
||||||
|
return arrayOf(LineNumberRange(offset, move))
|
||||||
|
} else if (str == "%") {
|
||||||
|
return arrayOf(
|
||||||
|
LineNumberRange(0, 0, move),
|
||||||
|
LineNumberRange(LineNumberRange.LAST_LINE, offset, move)
|
||||||
|
)
|
||||||
|
} else if (str == "$") {
|
||||||
|
return arrayOf(LineNumberRange(LineNumberRange.LAST_LINE, offset, move))
|
||||||
|
} else if (str.startsWith("'") && str.length == 2) {
|
||||||
|
return arrayOf(MarkRange(str[1], offset, move))
|
||||||
|
} else if (str.startsWith("/") || str.startsWith("\\/") || str.startsWith("\\&")) {
|
||||||
|
return arrayOf(SearchRange(str, offset, move))
|
||||||
|
} else if (str.startsWith("?") || str.startsWith("\\?")) {
|
||||||
|
return arrayOf(SearchRange(str, offset, move))
|
||||||
|
} else {
|
||||||
|
try {
|
||||||
|
val line = str.toInt() - 1
|
||||||
|
return arrayOf(LineNumberRange(line, offset, move))
|
||||||
|
} catch (e: NumberFormatException) { // Ignore - we'll send back bad range later.
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// User entered an invalid range.
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Represents a specific line, the current line, or the last line of a file
|
||||||
|
*/
|
||||||
|
class LineNumberRange : Range {
|
||||||
|
/**
|
||||||
|
* Create a range for the current line
|
||||||
|
*
|
||||||
|
* @param offset The range offset
|
||||||
|
* @param move True if cursor should be moved
|
||||||
|
*/
|
||||||
|
constructor(offset: Int, move: Boolean) : super(offset, move) {
|
||||||
|
line = CURRENT_LINE
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Create a range for the given line
|
||||||
|
*
|
||||||
|
* @param offset The range offset
|
||||||
|
* @param move True if cursor should be moved
|
||||||
|
*/
|
||||||
|
constructor(line: Int, offset: Int, move: Boolean) : super(offset, move) {
|
||||||
|
this.line = line
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Gets the line number specified by this range without regard to any offset.
|
||||||
|
*
|
||||||
|
* @param editor The editor to get the line for
|
||||||
|
* @param lastZero True if last line was set to start of file
|
||||||
|
* @return The zero based line number, -1 for start of file
|
||||||
|
*/
|
||||||
|
override fun getRangeLine(editor: Editor, lastZero: Boolean): Int {
|
||||||
|
if (line == CURRENT_LINE) {
|
||||||
|
line = editor.caretModel.logicalPosition.line
|
||||||
|
} else if (line == LAST_LINE) {
|
||||||
|
line = EditorHelper.getLineCount(editor) - 1
|
||||||
|
}
|
||||||
|
return line
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun getRangeLine(editor: Editor, caret: Caret,
|
||||||
|
lastZero: Boolean): Int {
|
||||||
|
line = if (line == LAST_LINE) EditorHelper.getLineCount(editor) - 1 else caret.logicalPosition.line
|
||||||
|
return line
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun toString(): String = "LineNumberRange[line=$line, ${super.toString()}]"
|
||||||
|
|
||||||
|
private var line: Int
|
||||||
|
|
||||||
|
companion object {
|
||||||
|
const val CURRENT_LINE = -99999999
|
||||||
|
const val LAST_LINE = -99999998
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Represents the line specified by a mark
|
||||||
|
*/
|
||||||
|
class MarkRange(private val mark: Char, offset: Int, move: Boolean) : Range(offset, move) {
|
||||||
|
/**
|
||||||
|
* Gets the line number specified by this range without regard to any offset.
|
||||||
|
*
|
||||||
|
* @param editor The editor to get the line for
|
||||||
|
* @param lastZero True if last line was set to start of file
|
||||||
|
* @return The zero based line number, -1 if there is no such mark set in the file
|
||||||
|
*/
|
||||||
|
override fun getRangeLine(editor: Editor, lastZero: Boolean): Int {
|
||||||
|
val mark = VimPlugin.getMark().getFileMark(editor, mark)
|
||||||
|
return mark?.logicalLine ?: -1
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun getRangeLine(editor: Editor, caret: Caret, lastZero: Boolean): Int = getRangeLine(editor, lastZero)
|
||||||
|
|
||||||
|
override fun toString(): String = "MarkRange[mark=$mark, ${super.toString()}]"
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Represents a range given by a search pattern. The pattern can be '\\/', '\\?', '\\&', /{pattern}/,
|
||||||
|
* or ?{pattern}?. The last two can be repeated 0 or more times after any of the others.
|
||||||
|
*/
|
||||||
|
class SearchRange(pattern: String, offset: Int, move: Boolean) : Range(offset, move) {
|
||||||
|
/**
|
||||||
|
* Parses the pattern into a list of subpatterns and flags
|
||||||
|
*
|
||||||
|
* @param pattern The full search pattern
|
||||||
|
*/
|
||||||
|
private fun setPattern(pattern: String) {
|
||||||
|
logger.debug { "pattern=$pattern" }
|
||||||
|
val tok = StringTokenizer(pattern, "\u0000")
|
||||||
|
while (tok.hasMoreTokens()) {
|
||||||
|
var pat = tok.nextToken()
|
||||||
|
when (pat) {
|
||||||
|
"\\/" -> {
|
||||||
|
patterns.add(VimPlugin.getSearch().lastSearch)
|
||||||
|
flags.add(enumSetOf(CommandFlags.FLAG_SEARCH_FWD))
|
||||||
|
}
|
||||||
|
"\\?" -> {
|
||||||
|
patterns.add(VimPlugin.getSearch().lastSearch)
|
||||||
|
flags.add(enumSetOf(CommandFlags.FLAG_SEARCH_REV))
|
||||||
|
}
|
||||||
|
"\\&" -> {
|
||||||
|
patterns.add(VimPlugin.getSearch().lastPattern)
|
||||||
|
flags.add(enumSetOf(CommandFlags.FLAG_SEARCH_FWD))
|
||||||
|
}
|
||||||
|
else -> {
|
||||||
|
if (pat[0] == '/') {
|
||||||
|
flags.add(enumSetOf(CommandFlags.FLAG_SEARCH_FWD))
|
||||||
|
} else {
|
||||||
|
flags.add(enumSetOf(CommandFlags.FLAG_SEARCH_REV))
|
||||||
|
}
|
||||||
|
pat = pat.substring(1)
|
||||||
|
if (pat.last() == pat[0]) {
|
||||||
|
pat = pat.substring(0, pat.length - 1)
|
||||||
|
}
|
||||||
|
patterns.add(pat)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Gets the line number specified by this range without regard to any offset.
|
||||||
|
*
|
||||||
|
* @param editor The editor to get the line for
|
||||||
|
* @param lastZero True if last line was set to start of file
|
||||||
|
* @return The zero based line number, -1 if the text was not found
|
||||||
|
*/
|
||||||
|
override fun getRangeLine(editor: Editor, lastZero: Boolean): Int { // Each subsequent pattern is searched for starting in the line after the previous search match
|
||||||
|
var line = editor.caretModel.logicalPosition.line
|
||||||
|
var pos = -1
|
||||||
|
for (i in patterns.indices) {
|
||||||
|
val pattern = patterns[i]
|
||||||
|
val flag = flags[i]
|
||||||
|
pos = if (CommandFlags.FLAG_SEARCH_FWD in flag && !lastZero) {
|
||||||
|
VimPlugin.getMotion().moveCaretToLineEnd(editor, line, true)
|
||||||
|
} else {
|
||||||
|
VimPlugin.getMotion().moveCaretToLineStart(editor, line)
|
||||||
|
}
|
||||||
|
pos = VimPlugin.getSearch().search(editor, pattern!!, pos, 1, flag)
|
||||||
|
line = if (pos == -1) {
|
||||||
|
break
|
||||||
|
} else {
|
||||||
|
editor.offsetToLogicalPosition(pos).line
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return if (pos != -1) line else -1
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun getRangeLine(editor: Editor, caret: Caret,
|
||||||
|
lastZero: Boolean): Int {
|
||||||
|
var line = caret.logicalPosition.line
|
||||||
|
var offset = -1
|
||||||
|
for (i in patterns.indices) {
|
||||||
|
val pattern = patterns[i]
|
||||||
|
val flag = flags[i]
|
||||||
|
offset = VimPlugin.getSearch().search(editor, pattern!!, getSearchOffset(editor, line, flag, lastZero), 1, flag)
|
||||||
|
if (offset == -1) break
|
||||||
|
line = editor.offsetToLogicalPosition(offset).line
|
||||||
|
}
|
||||||
|
return if (offset != -1) line else -1
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun getSearchOffset(editor: Editor, line: Int, flag: EnumSet<CommandFlags>, lastZero: Boolean): Int {
|
||||||
|
return if (flag.contains(CommandFlags.FLAG_SEARCH_FWD) && !lastZero) {
|
||||||
|
VimPlugin.getMotion().moveCaretToLineEnd(editor, line, true)
|
||||||
|
} else VimPlugin.getMotion().moveCaretToLineStart(editor, line)
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun toString(): String = "SearchRange[patterns=$patterns, ${super.toString()}]"
|
||||||
|
|
||||||
|
private val patterns: MutableList<String?> = mutableListOf()
|
||||||
|
private val flags: MutableList<EnumSet<CommandFlags>> = mutableListOf()
|
||||||
|
|
||||||
|
companion object {
|
||||||
|
private val logger = logger<SearchRange>()
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Create the pattern range
|
||||||
|
*
|
||||||
|
* @param pattern The text of the pattern. Each subpattern must be separated by the nul character (\\u0000)
|
||||||
|
* @param offset The range offset
|
||||||
|
* @param move True if the cursor should be moved
|
||||||
|
*/
|
||||||
|
init {
|
||||||
|
setPattern(pattern)
|
||||||
|
}
|
||||||
|
}
|
@@ -15,14 +15,22 @@
|
|||||||
* You should have received a copy of the GNU General Public License
|
* You should have received a copy of the GNU General Public License
|
||||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||||
*/
|
*/
|
||||||
|
package com.maddyhome.idea.vim.ex.ranges
|
||||||
|
|
||||||
package com.maddyhome.idea.vim.ex;
|
class LineRange(startLine: Int, endLine: Int) {
|
||||||
|
|
||||||
/**
|
@JvmField
|
||||||
* Exception class
|
val startLine: Int
|
||||||
*/
|
@JvmField
|
||||||
public class InvalidCommandException extends ExException {
|
val endLine: Int
|
||||||
public InvalidCommandException(String message, String cmd) {
|
|
||||||
super(message);
|
init {
|
||||||
|
if (endLine >= startLine) {
|
||||||
|
this.startLine = startLine
|
||||||
|
this.endLine = endLine
|
||||||
|
} else {
|
||||||
|
this.startLine = endLine
|
||||||
|
this.endLine = startLine
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
191
src/com/maddyhome/idea/vim/ex/ranges/Ranges.kt
Normal file
191
src/com/maddyhome/idea/vim/ex/ranges/Ranges.kt
Normal file
@@ -0,0 +1,191 @@
|
|||||||
|
/*
|
||||||
|
* IdeaVim - Vim emulator for IDEs based on the IntelliJ platform
|
||||||
|
* Copyright (C) 2003-2019 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 <https://www.gnu.org/licenses/>.
|
||||||
|
*/
|
||||||
|
package com.maddyhome.idea.vim.ex.ranges
|
||||||
|
|
||||||
|
import com.intellij.openapi.actionSystem.DataContext
|
||||||
|
import com.intellij.openapi.editor.Caret
|
||||||
|
import com.intellij.openapi.editor.Editor
|
||||||
|
import com.maddyhome.idea.vim.VimPlugin
|
||||||
|
import com.maddyhome.idea.vim.common.TextRange
|
||||||
|
import com.maddyhome.idea.vim.group.MotionGroup
|
||||||
|
import com.maddyhome.idea.vim.helper.EditorHelper
|
||||||
|
import kotlin.math.min
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Handles the set of range values entered as part of an Ex command.
|
||||||
|
*/
|
||||||
|
class Ranges {
|
||||||
|
/** Adds a range to the list */
|
||||||
|
fun addRange(range: Array<Range>) {
|
||||||
|
ranges.addAll(range)
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Gets the number of ranges in the list */
|
||||||
|
fun size(): Int = ranges.size
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Sets the default line to be used by this range if no range was actually given by the user. -1 is used to
|
||||||
|
* mean the current line.
|
||||||
|
*
|
||||||
|
* @param line The line or -1 for current line
|
||||||
|
*/
|
||||||
|
fun setDefaultLine(line: Int) {
|
||||||
|
defaultLine = line
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Gets the line of the last range specified in the range list
|
||||||
|
*
|
||||||
|
* @param editor The editor to get the line for
|
||||||
|
* @return The line number represented by the range
|
||||||
|
*/
|
||||||
|
fun getLine(editor: Editor): Int {
|
||||||
|
processRange(editor)
|
||||||
|
return endLine
|
||||||
|
}
|
||||||
|
|
||||||
|
fun getLine(editor: Editor, caret: Caret, context: DataContext): Int {
|
||||||
|
processRange(editor, caret, context)
|
||||||
|
return endLine
|
||||||
|
}
|
||||||
|
|
||||||
|
fun getFirstLine(editor: Editor, caret: Caret, context: DataContext): Int {
|
||||||
|
processRange(editor, caret, context)
|
||||||
|
return startLine
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Gets the count for an Ex command. This is either an explicit count enter at the end of the command or the
|
||||||
|
* end of the specified range.
|
||||||
|
*
|
||||||
|
* @param editor The editor to get the count for
|
||||||
|
* @param count The count given at the end of the command or -1 if no such count (use end line)
|
||||||
|
* @return count if count != -1, else return end line of range
|
||||||
|
*/
|
||||||
|
fun getCount(editor: Editor, count: Int): Int = if (count == -1) getLine(editor) else count
|
||||||
|
|
||||||
|
fun getCount(editor: Editor, caret: Caret, context: DataContext, count: Int): Int {
|
||||||
|
return if (count == -1) getLine(editor, caret, context) else count
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Gets the line range represented by this range. If a count is given, the range is the range end line through
|
||||||
|
* count-1 lines. If no count is given (-1), the range is the range given by the user.
|
||||||
|
*
|
||||||
|
* @param editor The editor to get the range for
|
||||||
|
* @param count The count given at the end of the command or -1 if no such count
|
||||||
|
* @return The line range
|
||||||
|
*/
|
||||||
|
fun getLineRange(editor: Editor, count: Int): LineRange {
|
||||||
|
processRange(editor)
|
||||||
|
val end: Int
|
||||||
|
val start: Int
|
||||||
|
if (count == -1) {
|
||||||
|
end = endLine
|
||||||
|
start = startLine
|
||||||
|
} else {
|
||||||
|
start = endLine
|
||||||
|
end = start + count - 1
|
||||||
|
}
|
||||||
|
return LineRange(start, end)
|
||||||
|
}
|
||||||
|
|
||||||
|
fun getLineRange(editor: Editor, caret: Caret, context: DataContext, count: Int): LineRange {
|
||||||
|
processRange(editor, caret, context)
|
||||||
|
return if (count == -1) LineRange(startLine, endLine) else LineRange(endLine, endLine + count - 1)
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Gets the text range represented by this range. If a count is given, the range is the range end line through
|
||||||
|
* count-1 lines. If no count is given (-1), the range is the range given by the user. The text range is based
|
||||||
|
* on the line range but this is character based from the start of the first line to the end of the last line.
|
||||||
|
*
|
||||||
|
* @param editor The editor to get the range for
|
||||||
|
* @param context The data context
|
||||||
|
* @param count The count given at the end of the command or -1 if no such count
|
||||||
|
* @return The text range
|
||||||
|
*/
|
||||||
|
fun getTextRange(editor: Editor, context: DataContext?, count: Int): TextRange {
|
||||||
|
val lr = getLineRange(editor, count)
|
||||||
|
val start = EditorHelper.getLineStartOffset(editor, lr.startLine)
|
||||||
|
val end = EditorHelper.getLineEndOffset(editor, lr.endLine, true) + 1
|
||||||
|
return TextRange(start, min(end, EditorHelper.getFileSize(editor)))
|
||||||
|
}
|
||||||
|
|
||||||
|
fun getTextRange(editor: Editor, caret: Caret, context: DataContext, count: Int): TextRange {
|
||||||
|
val lineRange = getLineRange(editor, caret, context, count)
|
||||||
|
val start = EditorHelper.getLineStartOffset(editor, lineRange.startLine)
|
||||||
|
val end = EditorHelper.getLineEndOffset(editor, lineRange.endLine, true) + 1
|
||||||
|
return TextRange(start, min(end, EditorHelper.getFileSize(editor)))
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Processes the list of ranges and calculates the start and end lines of the range
|
||||||
|
*
|
||||||
|
* @param editor The editor to get the lines for
|
||||||
|
*/
|
||||||
|
private fun processRange(editor: Editor) {
|
||||||
|
// Already done
|
||||||
|
if (done) return
|
||||||
|
// Start with the range being the current line
|
||||||
|
startLine = if (defaultLine == -1) editor.caretModel.logicalPosition.line else defaultLine
|
||||||
|
endLine = startLine
|
||||||
|
var lastZero = false
|
||||||
|
// Now process each range, moving the cursor if appropriate
|
||||||
|
for (range in ranges) {
|
||||||
|
startLine = endLine
|
||||||
|
endLine = range.getLine(editor, lastZero)
|
||||||
|
if (range.isMove) {
|
||||||
|
MotionGroup.moveCaret(editor, editor.caretModel.primaryCaret,
|
||||||
|
VimPlugin.getMotion().moveCaretToLine(editor, endLine, editor.caretModel.primaryCaret))
|
||||||
|
}
|
||||||
|
// Did that last range represent the start of the file?
|
||||||
|
lastZero = endLine < 0
|
||||||
|
count++
|
||||||
|
}
|
||||||
|
// If only one range given, make the start and end the same
|
||||||
|
if (count == 1) {
|
||||||
|
startLine = endLine
|
||||||
|
}
|
||||||
|
done = true
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun processRange(editor: Editor, caret: Caret, context: DataContext) {
|
||||||
|
startLine = if (defaultLine == -1) caret.logicalPosition.line else defaultLine
|
||||||
|
endLine = startLine
|
||||||
|
var lastZero = false
|
||||||
|
for (range in ranges) {
|
||||||
|
startLine = endLine
|
||||||
|
endLine = range.getLine(editor, caret, lastZero)
|
||||||
|
if (range.isMove) MotionGroup.moveCaret(editor, caret, VimPlugin.getMotion().moveCaretToLine(editor, endLine, editor.caretModel.primaryCaret))
|
||||||
|
lastZero = endLine < 0
|
||||||
|
++count
|
||||||
|
}
|
||||||
|
if (count == 1) startLine = endLine
|
||||||
|
count = 0
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun toString(): String = "Ranges[ranges=$ranges]"
|
||||||
|
|
||||||
|
private var startLine = 0
|
||||||
|
private var endLine = 0
|
||||||
|
private var count = 0
|
||||||
|
private var defaultLine = -1
|
||||||
|
private var done = false
|
||||||
|
private val ranges: MutableList<Range> = mutableListOf()
|
||||||
|
}
|
@@ -1,357 +0,0 @@
|
|||||||
/*
|
|
||||||
* IdeaVim - Vim emulator for IDEs based on the IntelliJ platform
|
|
||||||
* Copyright (C) 2003-2019 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 <https://www.gnu.org/licenses/>.
|
|
||||||
*/
|
|
||||||
|
|
||||||
package com.maddyhome.idea.vim.extension.surround;
|
|
||||||
|
|
||||||
import com.google.common.collect.ImmutableMap;
|
|
||||||
import com.intellij.openapi.actionSystem.DataContext;
|
|
||||||
import com.intellij.openapi.application.WriteAction;
|
|
||||||
import com.intellij.openapi.editor.Caret;
|
|
||||||
import com.intellij.openapi.editor.Editor;
|
|
||||||
import com.intellij.openapi.util.Pair;
|
|
||||||
import com.maddyhome.idea.vim.VimPlugin;
|
|
||||||
import com.maddyhome.idea.vim.command.CommandState;
|
|
||||||
import com.maddyhome.idea.vim.command.MappingMode;
|
|
||||||
import com.maddyhome.idea.vim.command.SelectionType;
|
|
||||||
import com.maddyhome.idea.vim.common.Mark;
|
|
||||||
import com.maddyhome.idea.vim.common.TextRange;
|
|
||||||
import com.maddyhome.idea.vim.extension.VimExtensionHandler;
|
|
||||||
import com.maddyhome.idea.vim.extension.VimNonDisposableExtension;
|
|
||||||
import com.maddyhome.idea.vim.group.ChangeGroup;
|
|
||||||
import com.maddyhome.idea.vim.helper.EditorHelper;
|
|
||||||
import com.maddyhome.idea.vim.key.OperatorFunction;
|
|
||||||
import com.maddyhome.idea.vim.option.ClipboardOptionsData;
|
|
||||||
import org.jetbrains.annotations.NotNull;
|
|
||||||
import org.jetbrains.annotations.Nullable;
|
|
||||||
|
|
||||||
import javax.swing.*;
|
|
||||||
import java.awt.event.KeyEvent;
|
|
||||||
import java.util.ArrayList;
|
|
||||||
import java.util.List;
|
|
||||||
import java.util.Map;
|
|
||||||
import java.util.regex.Matcher;
|
|
||||||
import java.util.regex.Pattern;
|
|
||||||
|
|
||||||
import static com.maddyhome.idea.vim.extension.VimExtensionFacade.*;
|
|
||||||
import static com.maddyhome.idea.vim.helper.StringHelper.parseKeys;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Port of vim-surround.
|
|
||||||
*
|
|
||||||
* See https://github.com/tpope/vim-surround
|
|
||||||
*
|
|
||||||
* @author dhleong
|
|
||||||
* @author vlan
|
|
||||||
*/
|
|
||||||
public class VimSurroundExtension extends VimNonDisposableExtension {
|
|
||||||
|
|
||||||
private static final char REGISTER = '"';
|
|
||||||
private final static Pattern tagNameAndAttributesCapturePattern = Pattern.compile("(\\w+)([^>]*)>");
|
|
||||||
|
|
||||||
private static final Map<Character, Pair<String, String>> SURROUND_PAIRS = ImmutableMap.<Character, Pair<String, String>>builder()
|
|
||||||
.put('b', Pair.create("(", ")"))
|
|
||||||
.put('(', Pair.create("( ", " )"))
|
|
||||||
.put(')', Pair.create("(", ")"))
|
|
||||||
.put('B', Pair.create("{", "}"))
|
|
||||||
.put('{', Pair.create("{ ", " }"))
|
|
||||||
.put('}', Pair.create("{", "}"))
|
|
||||||
.put('r', Pair.create("[", "]"))
|
|
||||||
.put('[', Pair.create("[ ", " ]"))
|
|
||||||
.put(']', Pair.create("[", "]"))
|
|
||||||
.put('a', Pair.create("<", ">"))
|
|
||||||
.put('>', Pair.create("<", ">"))
|
|
||||||
.put('s', Pair.create(" ", ""))
|
|
||||||
.build();
|
|
||||||
|
|
||||||
@NotNull
|
|
||||||
@Override
|
|
||||||
public String getName() {
|
|
||||||
return "surround";
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
protected void initOnce() {
|
|
||||||
putExtensionHandlerMapping(MappingMode.N, parseKeys("<Plug>YSurround"), new YSurroundHandler(), false);
|
|
||||||
putExtensionHandlerMapping(MappingMode.N, parseKeys("<Plug>CSurround"), new CSurroundHandler(), false);
|
|
||||||
putExtensionHandlerMapping(MappingMode.N, parseKeys("<Plug>DSurround"), new DSurroundHandler(), false);
|
|
||||||
putExtensionHandlerMapping(MappingMode.XO, parseKeys("<Plug>VSurround"), new VSurroundHandler(), false);
|
|
||||||
|
|
||||||
putKeyMapping(MappingMode.N, parseKeys("ys"), parseKeys("<Plug>YSurround"), true);
|
|
||||||
putKeyMapping(MappingMode.N, parseKeys("cs"), parseKeys("<Plug>CSurround"), true);
|
|
||||||
putKeyMapping(MappingMode.N, parseKeys("ds"), parseKeys("<Plug>DSurround"), true);
|
|
||||||
putKeyMapping(MappingMode.XO, parseKeys("S"), parseKeys("<Plug>VSurround"), true);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Nullable
|
|
||||||
private static Pair<String, String> getSurroundPair(char c) {
|
|
||||||
if (SURROUND_PAIRS.containsKey(c)) {
|
|
||||||
return SURROUND_PAIRS.get(c);
|
|
||||||
}
|
|
||||||
else if (!Character.isLetter(c)) {
|
|
||||||
final String s = String.valueOf(c);
|
|
||||||
return Pair.create(s, s);
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Nullable
|
|
||||||
private static Pair<String, String> inputTagPair(@NotNull Editor editor) {
|
|
||||||
final String tagInput = inputString(editor, "<", '>');
|
|
||||||
final Matcher matcher = tagNameAndAttributesCapturePattern.matcher(tagInput);
|
|
||||||
|
|
||||||
if (matcher.find()) {
|
|
||||||
final String tagName = matcher.group(1);
|
|
||||||
final String tagAttributes = matcher.group(2);
|
|
||||||
return Pair.create("<" + tagName + tagAttributes + ">", "</" + tagName + ">");
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Nullable
|
|
||||||
private static Pair<String, String> inputFunctionName(
|
|
||||||
@NotNull Editor editor,
|
|
||||||
boolean withInternalSpaces
|
|
||||||
) {
|
|
||||||
final String functionNameInput = inputString(editor, "function: ", null);
|
|
||||||
|
|
||||||
if (functionNameInput.isEmpty()) {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
return withInternalSpaces
|
|
||||||
? Pair.create(functionNameInput + "( ", " )")
|
|
||||||
: Pair.create(functionNameInput + "(", ")");
|
|
||||||
}
|
|
||||||
|
|
||||||
@Nullable
|
|
||||||
private static Pair<String, String> getOrInputPair(char c, @NotNull Editor editor) {
|
|
||||||
switch (c) {
|
|
||||||
case '<':
|
|
||||||
case 't':
|
|
||||||
return inputTagPair(editor);
|
|
||||||
case 'f':
|
|
||||||
return inputFunctionName(editor, false);
|
|
||||||
case 'F':
|
|
||||||
return inputFunctionName(editor, true);
|
|
||||||
default:
|
|
||||||
return getSurroundPair(c);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private static char getChar(@NotNull Editor editor) {
|
|
||||||
final KeyStroke key = inputKeyStroke(editor);
|
|
||||||
final char keyChar = key.getKeyChar();
|
|
||||||
if (keyChar == KeyEvent.CHAR_UNDEFINED || keyChar == KeyEvent.VK_ESCAPE) {
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
return keyChar;
|
|
||||||
}
|
|
||||||
|
|
||||||
private static class YSurroundHandler implements VimExtensionHandler {
|
|
||||||
@Override
|
|
||||||
public boolean isRepeatable() {
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void execute(@NotNull Editor editor, @NotNull DataContext context) {
|
|
||||||
setOperatorFunction(new Operator());
|
|
||||||
executeNormal(parseKeys("g@"), editor);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private static class VSurroundHandler implements VimExtensionHandler {
|
|
||||||
@Override
|
|
||||||
public void execute(@NotNull Editor editor, @NotNull DataContext context) {
|
|
||||||
int selectionStart = editor.getCaretModel().getPrimaryCaret().getSelectionStart();
|
|
||||||
// NB: Operator ignores SelectionType anyway
|
|
||||||
if (!new Operator().apply(editor, context, SelectionType.CHARACTER_WISE)) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
WriteAction.run(() -> {
|
|
||||||
// Leave visual mode
|
|
||||||
executeNormal(parseKeys("<Esc>"), editor);
|
|
||||||
editor.getCaretModel().moveToOffset(selectionStart);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
private static class CSurroundHandler implements VimExtensionHandler {
|
|
||||||
@Override
|
|
||||||
public boolean isRepeatable() {
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void execute(@NotNull Editor editor, @NotNull DataContext context) {
|
|
||||||
final char charFrom = getChar(editor);
|
|
||||||
if (charFrom == 0) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
final char charTo = getChar(editor);
|
|
||||||
if (charTo == 0) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
Pair<String, String> newSurround = getOrInputPair(charTo, editor);
|
|
||||||
if (newSurround == null) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
WriteAction.run(() -> change(editor, charFrom, newSurround));
|
|
||||||
}
|
|
||||||
|
|
||||||
static void change(@NotNull Editor editor, char charFrom, @Nullable Pair<String, String> newSurround) {
|
|
||||||
// We take over the " register, so preserve it
|
|
||||||
final List<KeyStroke> oldValue = getRegister(REGISTER);
|
|
||||||
|
|
||||||
// Extract the inner value
|
|
||||||
perform("di" + pick(charFrom), editor);
|
|
||||||
List<KeyStroke> innerValue = getRegister(REGISTER);
|
|
||||||
if (innerValue == null) {
|
|
||||||
innerValue = new ArrayList<>();
|
|
||||||
} else {
|
|
||||||
innerValue = new ArrayList<>(innerValue);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Delete the surrounding
|
|
||||||
perform("da" + pick(charFrom), editor);
|
|
||||||
|
|
||||||
// Insert the surrounding characters and paste
|
|
||||||
if (newSurround != null) {
|
|
||||||
innerValue.addAll(0, parseKeys(escape(newSurround.first)));
|
|
||||||
innerValue.addAll(parseKeys(escape(newSurround.second)));
|
|
||||||
}
|
|
||||||
pasteSurround(innerValue, editor);
|
|
||||||
|
|
||||||
// Restore the old value
|
|
||||||
setRegister(REGISTER, oldValue);
|
|
||||||
|
|
||||||
// Jump back to start
|
|
||||||
executeNormal(parseKeys("`["), editor);
|
|
||||||
}
|
|
||||||
|
|
||||||
@NotNull
|
|
||||||
private static String escape(@NotNull String sequence) {
|
|
||||||
return sequence.replace("<", "\\<");
|
|
||||||
}
|
|
||||||
|
|
||||||
private static void perform(@NotNull String sequence, @NotNull Editor editor) {
|
|
||||||
try (ClipboardOptionsData.IdeaputDisabler ignored = new ClipboardOptionsData.IdeaputDisabler()) {
|
|
||||||
executeNormal(parseKeys("\"" + REGISTER + sequence), editor);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private static void pasteSurround(@NotNull List<KeyStroke> innerValue, @NotNull Editor editor) {
|
|
||||||
// This logic is direct from vim-surround
|
|
||||||
final int offset = editor.getCaretModel().getOffset();
|
|
||||||
final int lineEndOffset = EditorHelper.getLineEndForOffset(editor, offset);
|
|
||||||
|
|
||||||
final Mark motionEndMark = VimPlugin.getMark().getMark(editor, ']');
|
|
||||||
final int motionEndOffset;
|
|
||||||
if (motionEndMark != null) {
|
|
||||||
motionEndOffset = EditorHelper.getOffset(editor, motionEndMark.getLogicalLine(), motionEndMark.getCol());
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
motionEndOffset = -1;
|
|
||||||
}
|
|
||||||
final String pasteCommand = motionEndOffset == lineEndOffset && offset + 1 == lineEndOffset ? "p" : "P";
|
|
||||||
setRegister(REGISTER, innerValue);
|
|
||||||
perform(pasteCommand, editor);
|
|
||||||
}
|
|
||||||
|
|
||||||
private static char pick(char charFrom) {
|
|
||||||
switch (charFrom) {
|
|
||||||
case 'a': return '>';
|
|
||||||
case 'r': return ']';
|
|
||||||
default: return charFrom;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private static class DSurroundHandler implements VimExtensionHandler {
|
|
||||||
@Override
|
|
||||||
public boolean isRepeatable() {
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void execute(@NotNull Editor editor, @NotNull DataContext context) {
|
|
||||||
// Deleting surround is just changing the surrounding to "nothing"
|
|
||||||
final char charFrom = getChar(editor);
|
|
||||||
if (charFrom == 0) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
WriteAction.run(() -> CSurroundHandler.change(editor, charFrom, null));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private static class Operator implements OperatorFunction {
|
|
||||||
@Override
|
|
||||||
public boolean apply(@NotNull Editor editor, @NotNull DataContext context, @NotNull SelectionType selectionType) {
|
|
||||||
final char c = getChar(editor);
|
|
||||||
if (c == 0) {
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
final Pair<String, String> pair = getOrInputPair(c, editor);
|
|
||||||
if (pair == null) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
// XXX: Will it work with line-wise or block-wise selections?
|
|
||||||
final TextRange range = getSurroundRange(editor);
|
|
||||||
if (range == null) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
WriteAction.run(() -> {
|
|
||||||
final ChangeGroup change = VimPlugin.getChange();
|
|
||||||
final String leftSurround = pair.getFirst();
|
|
||||||
final Caret primaryCaret = editor.getCaretModel().getPrimaryCaret();
|
|
||||||
primaryCaret.moveToOffset(range.getStartOffset());
|
|
||||||
change.insertText(editor, primaryCaret, leftSurround);
|
|
||||||
primaryCaret.moveToOffset(range.getEndOffset() + leftSurround.length());
|
|
||||||
change.insertText(editor, primaryCaret, pair.getSecond());
|
|
||||||
|
|
||||||
// Jump back to start
|
|
||||||
executeNormal(parseKeys("`["), editor);
|
|
||||||
});
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Nullable
|
|
||||||
private TextRange getSurroundRange(@NotNull Editor editor) {
|
|
||||||
final CommandState.Mode mode = CommandState.getInstance(editor).getMode();
|
|
||||||
switch (mode) {
|
|
||||||
case COMMAND:
|
|
||||||
return VimPlugin.getMark().getChangeMarks(editor);
|
|
||||||
case VISUAL:
|
|
||||||
int selectionStart = editor.getCaretModel().getPrimaryCaret().getSelectionStart();
|
|
||||||
int selectionEnd = editor.getCaretModel().getPrimaryCaret().getSelectionEnd();
|
|
||||||
return new TextRange(selectionStart, selectionEnd);
|
|
||||||
default:
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
@@ -0,0 +1,256 @@
|
|||||||
|
/*
|
||||||
|
* IdeaVim - Vim emulator for IDEs based on the IntelliJ platform
|
||||||
|
* Copyright (C) 2003-2019 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 <https://www.gnu.org/licenses/>.
|
||||||
|
*/
|
||||||
|
package com.maddyhome.idea.vim.extension.surround
|
||||||
|
|
||||||
|
import com.intellij.openapi.actionSystem.DataContext
|
||||||
|
import com.intellij.openapi.application.runWriteAction
|
||||||
|
import com.intellij.openapi.editor.Editor
|
||||||
|
import com.maddyhome.idea.vim.VimPlugin
|
||||||
|
import com.maddyhome.idea.vim.command.CommandState
|
||||||
|
import com.maddyhome.idea.vim.command.MappingMode
|
||||||
|
import com.maddyhome.idea.vim.command.SelectionType
|
||||||
|
import com.maddyhome.idea.vim.common.TextRange
|
||||||
|
import com.maddyhome.idea.vim.extension.VimExtensionFacade.executeNormal
|
||||||
|
import com.maddyhome.idea.vim.extension.VimExtensionFacade.getRegister
|
||||||
|
import com.maddyhome.idea.vim.extension.VimExtensionFacade.inputKeyStroke
|
||||||
|
import com.maddyhome.idea.vim.extension.VimExtensionFacade.inputString
|
||||||
|
import com.maddyhome.idea.vim.extension.VimExtensionFacade.putExtensionHandlerMapping
|
||||||
|
import com.maddyhome.idea.vim.extension.VimExtensionFacade.putKeyMapping
|
||||||
|
import com.maddyhome.idea.vim.extension.VimExtensionFacade.setOperatorFunction
|
||||||
|
import com.maddyhome.idea.vim.extension.VimExtensionFacade.setRegister
|
||||||
|
import com.maddyhome.idea.vim.extension.VimExtensionHandler
|
||||||
|
import com.maddyhome.idea.vim.extension.VimNonDisposableExtension
|
||||||
|
import com.maddyhome.idea.vim.helper.EditorHelper
|
||||||
|
import com.maddyhome.idea.vim.helper.StringHelper
|
||||||
|
import com.maddyhome.idea.vim.helper.mode
|
||||||
|
import com.maddyhome.idea.vim.key.OperatorFunction
|
||||||
|
import com.maddyhome.idea.vim.option.ClipboardOptionsData.IdeaputDisabler
|
||||||
|
import java.awt.event.KeyEvent
|
||||||
|
import javax.swing.KeyStroke
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Port of vim-surround.
|
||||||
|
*
|
||||||
|
* See https://github.com/tpope/vim-surround
|
||||||
|
*
|
||||||
|
* @author dhleong
|
||||||
|
* @author vlan
|
||||||
|
*/
|
||||||
|
class VimSurroundExtension : VimNonDisposableExtension() {
|
||||||
|
override fun getName() = "surround"
|
||||||
|
|
||||||
|
override fun initOnce() {
|
||||||
|
putExtensionHandlerMapping(MappingMode.N, StringHelper.parseKeys("<Plug>YSurround"), YSurroundHandler(), false)
|
||||||
|
putExtensionHandlerMapping(MappingMode.N, StringHelper.parseKeys("<Plug>CSurround"), CSurroundHandler(), false)
|
||||||
|
putExtensionHandlerMapping(MappingMode.N, StringHelper.parseKeys("<Plug>DSurround"), DSurroundHandler(), false)
|
||||||
|
putExtensionHandlerMapping(MappingMode.XO, StringHelper.parseKeys("<Plug>VSurround"), VSurroundHandler(), false)
|
||||||
|
|
||||||
|
putKeyMapping(MappingMode.N, StringHelper.parseKeys("ys"), StringHelper.parseKeys("<Plug>YSurround"), true)
|
||||||
|
putKeyMapping(MappingMode.N, StringHelper.parseKeys("cs"), StringHelper.parseKeys("<Plug>CSurround"), true)
|
||||||
|
putKeyMapping(MappingMode.N, StringHelper.parseKeys("ds"), StringHelper.parseKeys("<Plug>DSurround"), true)
|
||||||
|
putKeyMapping(MappingMode.XO, StringHelper.parseKeys("S"), StringHelper.parseKeys("<Plug>VSurround"), true)
|
||||||
|
}
|
||||||
|
|
||||||
|
private class YSurroundHandler : VimExtensionHandler {
|
||||||
|
override fun isRepeatable() = true
|
||||||
|
|
||||||
|
override fun execute(editor: Editor, context: DataContext) {
|
||||||
|
setOperatorFunction(Operator())
|
||||||
|
executeNormal(StringHelper.parseKeys("g@"), editor)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private class VSurroundHandler : VimExtensionHandler {
|
||||||
|
override fun execute(editor: Editor, context: DataContext) {
|
||||||
|
val selectionStart = editor.caretModel.primaryCaret.selectionStart
|
||||||
|
// NB: Operator ignores SelectionType anyway
|
||||||
|
if (!Operator().apply(editor, context, SelectionType.CHARACTER_WISE)) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
runWriteAction {
|
||||||
|
// Leave visual mode
|
||||||
|
executeNormal(StringHelper.parseKeys("<Esc>"), editor)
|
||||||
|
editor.caretModel.moveToOffset(selectionStart)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private class CSurroundHandler : VimExtensionHandler {
|
||||||
|
override fun isRepeatable() = true
|
||||||
|
|
||||||
|
override fun execute(editor: Editor, context: DataContext) {
|
||||||
|
val charFrom = getChar(editor)
|
||||||
|
if (charFrom.toInt() == 0) return
|
||||||
|
|
||||||
|
val charTo = getChar(editor)
|
||||||
|
if (charTo.toInt() == 0) return
|
||||||
|
|
||||||
|
val newSurround = getOrInputPair(charTo, editor) ?: return
|
||||||
|
runWriteAction { change(editor, charFrom, newSurround) }
|
||||||
|
}
|
||||||
|
|
||||||
|
companion object {
|
||||||
|
fun change(editor: Editor, charFrom: Char, newSurround: Pair<String, String>?) {
|
||||||
|
// We take over the " register, so preserve it
|
||||||
|
val oldValue: List<KeyStroke>? = getRegister(REGISTER)
|
||||||
|
// Extract the inner value
|
||||||
|
perform("di" + pick(charFrom), editor)
|
||||||
|
val innerValue: MutableList<KeyStroke> = getRegister(REGISTER)?.toMutableList() ?: mutableListOf()
|
||||||
|
// Delete the surrounding
|
||||||
|
perform("da" + pick(charFrom), editor)
|
||||||
|
// Insert the surrounding characters and paste
|
||||||
|
if (newSurround != null) {
|
||||||
|
innerValue.addAll(0, StringHelper.parseKeys(escape(newSurround.first)))
|
||||||
|
innerValue.addAll(StringHelper.parseKeys(escape(newSurround.second)))
|
||||||
|
}
|
||||||
|
pasteSurround(innerValue, editor)
|
||||||
|
// Restore the old value
|
||||||
|
setRegister(REGISTER, oldValue)
|
||||||
|
// Jump back to start
|
||||||
|
executeNormal(StringHelper.parseKeys("`["), editor)
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun escape(sequence: String): String = sequence.replace("<", "\\<")
|
||||||
|
|
||||||
|
private fun perform(sequence: String, editor: Editor) {
|
||||||
|
IdeaputDisabler().use { executeNormal(StringHelper.parseKeys("\"" + REGISTER + sequence), editor) }
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun pasteSurround(innerValue: List<KeyStroke?>, editor: Editor) { // This logic is direct from vim-surround
|
||||||
|
val offset = editor.caretModel.offset
|
||||||
|
val lineEndOffset = EditorHelper.getLineEndForOffset(editor, offset)
|
||||||
|
val motionEndMark = VimPlugin.getMark().getMark(editor, ']')
|
||||||
|
val motionEndOffset = if (motionEndMark != null) {
|
||||||
|
EditorHelper.getOffset(editor, motionEndMark.logicalLine, motionEndMark.col)
|
||||||
|
} else -1
|
||||||
|
val pasteCommand = if (motionEndOffset == lineEndOffset && offset + 1 == lineEndOffset) "p" else "P"
|
||||||
|
setRegister(REGISTER, innerValue)
|
||||||
|
perform(pasteCommand, editor)
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun pick(charFrom: Char) = when (charFrom) {
|
||||||
|
'a' -> '>'
|
||||||
|
'r' -> ']'
|
||||||
|
else -> charFrom
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private class DSurroundHandler : VimExtensionHandler {
|
||||||
|
override fun isRepeatable() = true
|
||||||
|
|
||||||
|
override fun execute(editor: Editor, context: DataContext) {
|
||||||
|
// Deleting surround is just changing the surrounding to "nothing"
|
||||||
|
val charFrom = getChar(editor)
|
||||||
|
if (charFrom.toInt() == 0) return
|
||||||
|
|
||||||
|
runWriteAction { CSurroundHandler.change(editor, charFrom, null) }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private class Operator : OperatorFunction {
|
||||||
|
override fun apply(editor: Editor, context: DataContext, selectionType: SelectionType): Boolean {
|
||||||
|
val c = getChar(editor)
|
||||||
|
if (c.toInt() == 0) return true
|
||||||
|
|
||||||
|
val pair = getOrInputPair(c, editor) ?: return false
|
||||||
|
// XXX: Will it work with line-wise or block-wise selections?
|
||||||
|
val range = getSurroundRange(editor) ?: return false
|
||||||
|
runWriteAction {
|
||||||
|
val change = VimPlugin.getChange()
|
||||||
|
val leftSurround = pair.first
|
||||||
|
val primaryCaret = editor.caretModel.primaryCaret
|
||||||
|
primaryCaret.moveToOffset(range.startOffset)
|
||||||
|
change.insertText(editor, primaryCaret, leftSurround)
|
||||||
|
primaryCaret.moveToOffset(range.endOffset + leftSurround.length)
|
||||||
|
change.insertText(editor, primaryCaret, pair.second)
|
||||||
|
// Jump back to start
|
||||||
|
executeNormal(StringHelper.parseKeys("`["), editor)
|
||||||
|
}
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun getSurroundRange(editor: Editor): TextRange? = when (editor.mode) {
|
||||||
|
CommandState.Mode.COMMAND -> VimPlugin.getMark().getChangeMarks(editor)
|
||||||
|
CommandState.Mode.VISUAL -> editor.caretModel.primaryCaret.run { TextRange(selectionStart, selectionEnd) }
|
||||||
|
else -> null
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
companion object {
|
||||||
|
private const val REGISTER = '"'
|
||||||
|
|
||||||
|
private val tagNameAndAttributesCapturePattern = "(\\w+)([^>]*)>".toPattern()
|
||||||
|
|
||||||
|
private val SURROUND_PAIRS = mapOf(
|
||||||
|
'b' to ("(" to ")"),
|
||||||
|
'(' to ("( " to " )"),
|
||||||
|
')' to ("(" to ")"),
|
||||||
|
'B' to ("{" to "}"),
|
||||||
|
'{' to ("{ " to " }"),
|
||||||
|
'}' to ("{" to "}"),
|
||||||
|
'r' to ("[" to "]"),
|
||||||
|
'[' to ("[ " to " ]"),
|
||||||
|
']' to ("[" to "]"),
|
||||||
|
'a' to ("<" to ">"),
|
||||||
|
'>' to ("<" to ">"),
|
||||||
|
's' to (" " to "")
|
||||||
|
)
|
||||||
|
|
||||||
|
private fun getSurroundPair(c: Char): Pair<String, String>? = if (c in SURROUND_PAIRS) {
|
||||||
|
SURROUND_PAIRS[c]
|
||||||
|
} else if (!c.isLetter()) {
|
||||||
|
val s = c.toString()
|
||||||
|
s to s
|
||||||
|
} else null
|
||||||
|
|
||||||
|
private fun inputTagPair(editor: Editor): Pair<String, String>? {
|
||||||
|
val tagInput = inputString(editor, "<", '>')
|
||||||
|
val matcher = tagNameAndAttributesCapturePattern.matcher(tagInput)
|
||||||
|
return if (matcher.find()) {
|
||||||
|
val tagName = matcher.group(1)
|
||||||
|
val tagAttributes = matcher.group(2)
|
||||||
|
"<$tagName$tagAttributes>" to "</$tagName>"
|
||||||
|
} else null
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun inputFunctionName(
|
||||||
|
editor: Editor,
|
||||||
|
withInternalSpaces: Boolean
|
||||||
|
): Pair<String, String>? {
|
||||||
|
val functionNameInput = inputString(editor, "function: ", null)
|
||||||
|
if (functionNameInput.isEmpty()) return null
|
||||||
|
return if (withInternalSpaces) "$functionNameInput( " to " )" else "$functionNameInput(" to ")"
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun getOrInputPair(c: Char, editor: Editor): Pair<String, String>? = when (c) {
|
||||||
|
'<', 't' -> inputTagPair(editor)
|
||||||
|
'f' -> inputFunctionName(editor, false)
|
||||||
|
'F' -> inputFunctionName(editor, true)
|
||||||
|
else -> getSurroundPair(c)
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun getChar(editor: Editor): Char {
|
||||||
|
val key = inputKeyStroke(editor)
|
||||||
|
val keyChar = key.keyChar
|
||||||
|
return if (keyChar == KeyEvent.CHAR_UNDEFINED || keyChar.toInt() == KeyEvent.VK_ESCAPE) {
|
||||||
|
0.toChar()
|
||||||
|
} else keyChar
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@@ -51,7 +51,7 @@ import com.maddyhome.idea.vim.command.*;
|
|||||||
import com.maddyhome.idea.vim.common.IndentConfig;
|
import com.maddyhome.idea.vim.common.IndentConfig;
|
||||||
import com.maddyhome.idea.vim.common.Register;
|
import com.maddyhome.idea.vim.common.Register;
|
||||||
import com.maddyhome.idea.vim.common.TextRange;
|
import com.maddyhome.idea.vim.common.TextRange;
|
||||||
import com.maddyhome.idea.vim.ex.LineRange;
|
import com.maddyhome.idea.vim.ex.ranges.LineRange;
|
||||||
import com.maddyhome.idea.vim.group.visual.VimSelection;
|
import com.maddyhome.idea.vim.group.visual.VimSelection;
|
||||||
import com.maddyhome.idea.vim.group.visual.VisualGroupKt;
|
import com.maddyhome.idea.vim.group.visual.VisualGroupKt;
|
||||||
import com.maddyhome.idea.vim.group.visual.VisualModeHelperKt;
|
import com.maddyhome.idea.vim.group.visual.VisualModeHelperKt;
|
||||||
@@ -1787,8 +1787,8 @@ public class ChangeGroup {
|
|||||||
public boolean sortRange(@NotNull Editor editor,
|
public boolean sortRange(@NotNull Editor editor,
|
||||||
@NotNull LineRange range,
|
@NotNull LineRange range,
|
||||||
@NotNull Comparator<String> lineComparator) {
|
@NotNull Comparator<String> lineComparator) {
|
||||||
final int startLine = range.getStartLine();
|
final int startLine = range.startLine;
|
||||||
final int endLine = range.getEndLine();
|
final int endLine = range.endLine;
|
||||||
final int count = endLine - startLine + 1;
|
final int count = endLine - startLine + 1;
|
||||||
if (count < 2) {
|
if (count < 2) {
|
||||||
return false;
|
return false;
|
||||||
|
@@ -37,7 +37,7 @@ import com.maddyhome.idea.vim.command.CommandFlags;
|
|||||||
import com.maddyhome.idea.vim.command.SelectionType;
|
import com.maddyhome.idea.vim.command.SelectionType;
|
||||||
import com.maddyhome.idea.vim.common.CharacterPosition;
|
import com.maddyhome.idea.vim.common.CharacterPosition;
|
||||||
import com.maddyhome.idea.vim.common.TextRange;
|
import com.maddyhome.idea.vim.common.TextRange;
|
||||||
import com.maddyhome.idea.vim.ex.LineRange;
|
import com.maddyhome.idea.vim.ex.ranges.LineRange;
|
||||||
import com.maddyhome.idea.vim.helper.*;
|
import com.maddyhome.idea.vim.helper.*;
|
||||||
import com.maddyhome.idea.vim.option.ListOption;
|
import com.maddyhome.idea.vim.option.ListOption;
|
||||||
import com.maddyhome.idea.vim.option.OptionChangeListener;
|
import com.maddyhome.idea.vim.option.OptionChangeListener;
|
||||||
@@ -377,7 +377,7 @@ public class SearchGroup {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public int updateIncsearchHighlights(@NotNull Editor editor, @NotNull String pattern, boolean forwards, int caretOffset, @Nullable LineRange searchRange) {
|
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.getStartLine()) : caretOffset;
|
final int searchStartOffset = searchRange != null ? EditorHelper.getLineStartOffset(editor, searchRange.startLine) : caretOffset;
|
||||||
final boolean showHighlights = OptionsManager.INSTANCE.getHlsearch().isSet();
|
final boolean showHighlights = OptionsManager.INSTANCE.getHlsearch().isSet();
|
||||||
return updateSearchHighlights(pattern, false, showHighlights, searchStartOffset, searchRange, forwards, false);
|
return updateSearchHighlights(pattern, false, showHighlights, searchStartOffset, searchRange, forwards, false);
|
||||||
}
|
}
|
||||||
@@ -410,8 +410,8 @@ public class SearchGroup {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (shouldAddAllSearchHighlights(editor, pattern, showHighlights)) {
|
if (shouldAddAllSearchHighlights(editor, pattern, showHighlights)) {
|
||||||
final int startLine = searchRange == null ? 0 : searchRange.getStartLine();
|
final int startLine = searchRange == null ? 0 : searchRange.startLine;
|
||||||
final int endLine = searchRange == null ? -1 : searchRange.getEndLine();
|
final int endLine = searchRange == null ? -1 : searchRange.endLine;
|
||||||
List<TextRange> results = findAll(editor, pattern, startLine, endLine, shouldIgnoreCase(pattern, shouldIgnoreSmartCase));
|
List<TextRange> results = findAll(editor, pattern, startLine, endLine, shouldIgnoreCase(pattern, shouldIgnoreSmartCase));
|
||||||
if (!results.isEmpty()) {
|
if (!results.isEmpty()) {
|
||||||
currentMatchOffset = findClosestMatch(editor, results, initialOffset, forwards);
|
currentMatchOffset = findClosestMatch(editor, results, initialOffset, forwards);
|
||||||
@@ -1114,8 +1114,8 @@ public class SearchGroup {
|
|||||||
cmd.inc();
|
cmd.inc();
|
||||||
}
|
}
|
||||||
|
|
||||||
int line1 = range.getStartLine();
|
int line1 = range.startLine;
|
||||||
int line2 = range.getEndLine();
|
int line2 = range.endLine;
|
||||||
|
|
||||||
if (line1 < 0 || line2 < 0) {
|
if (line1 < 0 || line2 < 0) {
|
||||||
return false;
|
return false;
|
||||||
|
@@ -1,107 +0,0 @@
|
|||||||
/*
|
|
||||||
* IdeaVim - Vim emulator for IDEs based on the IntelliJ platform
|
|
||||||
* Copyright (C) 2003-2019 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 <https://www.gnu.org/licenses/>.
|
|
||||||
*/
|
|
||||||
|
|
||||||
package com.maddyhome.idea.vim.helper;
|
|
||||||
|
|
||||||
import com.maddyhome.idea.vim.option.OptionsManager;
|
|
||||||
import org.jetbrains.annotations.NotNull;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* This helper class is used when working with various character level operations
|
|
||||||
*/
|
|
||||||
public class CharacterHelper {
|
|
||||||
|
|
||||||
public enum CharacterType {
|
|
||||||
KEYWORD,
|
|
||||||
HIRAGANA,
|
|
||||||
KATAKANA,
|
|
||||||
HALF_WIDTH_KATAKANA,
|
|
||||||
PUNCTUATION,
|
|
||||||
WHITESPACE
|
|
||||||
}
|
|
||||||
|
|
||||||
public static final char CASE_TOGGLE = '~';
|
|
||||||
public static final char CASE_UPPER = 'u';
|
|
||||||
public static final char CASE_LOWER = 'l';
|
|
||||||
|
|
||||||
/**
|
|
||||||
* This returns the type of the supplied character. The logic is as follows:<br>
|
|
||||||
* If the character is whitespace, <code>WHITESPACE</code> is returned.<br>
|
|
||||||
* If the punctuation is being skipped or the character is a letter, digit, or underscore, <code>KEYWORD</code>
|
|
||||||
* is returned.<br>
|
|
||||||
* Otherwise <code>PUNCTUATION</code> is returned.
|
|
||||||
*
|
|
||||||
* @param ch The character to analyze
|
|
||||||
* @param punctuationAsLetters True if punctuation is to be ignored, false if not
|
|
||||||
* @return The type of the character
|
|
||||||
*/
|
|
||||||
@NotNull
|
|
||||||
public static CharacterType charType(char ch, boolean punctuationAsLetters) {
|
|
||||||
final Character.UnicodeBlock block = Character.UnicodeBlock.of(ch);
|
|
||||||
if (Character.isWhitespace(ch)) {
|
|
||||||
return CharacterType.WHITESPACE;
|
|
||||||
}
|
|
||||||
else if (block == Character.UnicodeBlock.HIRAGANA) {
|
|
||||||
return CharacterType.HIRAGANA;
|
|
||||||
}
|
|
||||||
else if (block == Character.UnicodeBlock.KATAKANA) {
|
|
||||||
return CharacterType.KATAKANA;
|
|
||||||
}
|
|
||||||
else if (isHalfWidthKatakanaLetter(ch)) {
|
|
||||||
return CharacterType.HALF_WIDTH_KATAKANA;
|
|
||||||
}
|
|
||||||
else if (punctuationAsLetters || OptionsManager.INSTANCE.getIskeyword().isKeyword(ch)) {
|
|
||||||
return CharacterType.KEYWORD;
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
return CharacterType.PUNCTUATION;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private static boolean isHalfWidthKatakanaLetter(char ch) {
|
|
||||||
return ch >= '\uFF66' && ch <= '\uFF9F';
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Changes the case of the supplied character based on the supplied change type
|
|
||||||
*
|
|
||||||
* @param ch The character to change
|
|
||||||
* @param type One of <code>CASE_TOGGLE</code>, <code>CASE_UPPER</code>, or <code>CASE_LOWER</code>
|
|
||||||
* @return The character with changed case or the original if not a letter
|
|
||||||
*/
|
|
||||||
public static char changeCase(char ch, char type) {
|
|
||||||
switch (type) {
|
|
||||||
case CASE_TOGGLE:
|
|
||||||
if (Character.isLowerCase(ch)) {
|
|
||||||
ch = Character.toUpperCase(ch);
|
|
||||||
}
|
|
||||||
else if (Character.isUpperCase(ch)) {
|
|
||||||
ch = Character.toLowerCase(ch);
|
|
||||||
}
|
|
||||||
break;
|
|
||||||
case CASE_LOWER:
|
|
||||||
ch = Character.toLowerCase(ch);
|
|
||||||
break;
|
|
||||||
case CASE_UPPER:
|
|
||||||
ch = Character.toUpperCase(ch);
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
|
|
||||||
return ch;
|
|
||||||
}
|
|
||||||
}
|
|
84
src/com/maddyhome/idea/vim/helper/CharacterHelper.kt
Normal file
84
src/com/maddyhome/idea/vim/helper/CharacterHelper.kt
Normal file
@@ -0,0 +1,84 @@
|
|||||||
|
/*
|
||||||
|
* IdeaVim - Vim emulator for IDEs based on the IntelliJ platform
|
||||||
|
* Copyright (C) 2003-2019 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 <https://www.gnu.org/licenses/>.
|
||||||
|
*/
|
||||||
|
package com.maddyhome.idea.vim.helper
|
||||||
|
|
||||||
|
import com.maddyhome.idea.vim.option.OptionsManager.iskeyword
|
||||||
|
import java.lang.Character.UnicodeBlock
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This helper class is used when working with various character level operations
|
||||||
|
*/
|
||||||
|
object CharacterHelper {
|
||||||
|
const val CASE_TOGGLE = '~'
|
||||||
|
const val CASE_UPPER = 'u'
|
||||||
|
const val CASE_LOWER = 'l'
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This returns the type of the supplied character. The logic is as follows:<br></br>
|
||||||
|
* If the character is whitespace, `WHITESPACE` is returned.<br></br>
|
||||||
|
* If the punctuation is being skipped or the character is a letter, digit, or underscore, `KEYWORD`
|
||||||
|
* is returned.<br></br>
|
||||||
|
* Otherwise `PUNCTUATION` is returned.
|
||||||
|
*
|
||||||
|
* @param ch The character to analyze
|
||||||
|
* @param punctuationAsLetters True if punctuation is to be ignored, false if not
|
||||||
|
* @return The type of the character
|
||||||
|
*/
|
||||||
|
@JvmStatic
|
||||||
|
fun charType(ch: Char, punctuationAsLetters: Boolean): CharacterType {
|
||||||
|
val block = UnicodeBlock.of(ch)
|
||||||
|
return if (Character.isWhitespace(ch)) {
|
||||||
|
CharacterType.WHITESPACE
|
||||||
|
} else if (block === UnicodeBlock.HIRAGANA) {
|
||||||
|
CharacterType.HIRAGANA
|
||||||
|
} else if (block === UnicodeBlock.KATAKANA) {
|
||||||
|
CharacterType.KATAKANA
|
||||||
|
} else if (isHalfWidthKatakanaLetter(ch)) {
|
||||||
|
CharacterType.HALF_WIDTH_KATAKANA
|
||||||
|
} else if (punctuationAsLetters || iskeyword.isKeyword(ch)) {
|
||||||
|
CharacterType.KEYWORD
|
||||||
|
} else {
|
||||||
|
CharacterType.PUNCTUATION
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun isHalfWidthKatakanaLetter(ch: Char): Boolean = ch in '\uFF66'..'\uFF9F'
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Changes the case of the supplied character based on the supplied change type
|
||||||
|
*
|
||||||
|
* @param ch The character to change
|
||||||
|
* @param type One of `CASE_TOGGLE`, `CASE_UPPER`, or `CASE_LOWER`
|
||||||
|
* @return The character with changed case or the original if not a letter
|
||||||
|
*/
|
||||||
|
@JvmStatic
|
||||||
|
fun changeCase(ch: Char, type: Char): Char = when (type) {
|
||||||
|
CASE_TOGGLE -> when {
|
||||||
|
Character.isLowerCase(ch) -> Character.toUpperCase(ch)
|
||||||
|
Character.isUpperCase(ch) -> Character.toLowerCase(ch)
|
||||||
|
else -> ch
|
||||||
|
}
|
||||||
|
CASE_LOWER -> Character.toLowerCase(ch)
|
||||||
|
CASE_UPPER -> Character.toUpperCase(ch)
|
||||||
|
else -> ch
|
||||||
|
}
|
||||||
|
|
||||||
|
enum class CharacterType {
|
||||||
|
KEYWORD, HIRAGANA, KATAKANA, HALF_WIDTH_KATAKANA, PUNCTUATION, WHITESPACE
|
||||||
|
}
|
||||||
|
}
|
@@ -58,7 +58,7 @@ object OptionsManager {
|
|||||||
val incsearch = addOption(ToggleOption("incsearch", "is", false))
|
val incsearch = addOption(ToggleOption("incsearch", "is", false))
|
||||||
val iskeyword = addOption(KeywordOption("iskeyword", "isk", arrayOf("@", "48-57", "_")))
|
val iskeyword = addOption(KeywordOption("iskeyword", "isk", arrayOf("@", "48-57", "_")))
|
||||||
val keymodel = addOption(KeyModelOptionData.option)
|
val keymodel = addOption(KeyModelOptionData.option)
|
||||||
val lookupKeys = addOption(ListOption("lookupkeys", "lookupkeys", arrayOf(), null))
|
val lookupKeys = addOption(ListOption(LookupKeysData.name, LookupKeysData.name, LookupKeysData.defaultValues, null))
|
||||||
val matchpairs = addOption(ListOption("matchpairs", "mps", arrayOf("(:)", "{:}", "[:]"), ".:."))
|
val matchpairs = addOption(ListOption("matchpairs", "mps", arrayOf("(:)", "{:}", "[:]"), ".:."))
|
||||||
val more = addOption(ToggleOption("more", "more", true))
|
val more = addOption(ToggleOption("more", "more", true))
|
||||||
val nrformats = addOption(BoundListOption("nrformats", "nf", arrayOf("octal", "hex"), arrayOf("octal", "hex", "alpha")))
|
val nrformats = addOption(BoundListOption("nrformats", "nf", arrayOf("octal", "hex"), arrayOf("octal", "hex", "alpha")))
|
||||||
@@ -506,3 +506,17 @@ object IdeaRefactorMode {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
object LookupKeysData {
|
||||||
|
val name = "lookupkeys"
|
||||||
|
val defaultValues = arrayOf(
|
||||||
|
"<Tab>", "<Down>", "<Up>", "<Enter>", "<Left>", "<Right>",
|
||||||
|
"<C-Down>", "<C-Up>",
|
||||||
|
"<PageUp>", "<PageDown>",
|
||||||
|
// New line in vim, but QuickDoc on MacOs
|
||||||
|
"<C-J>",
|
||||||
|
// QuickDoc for non-mac layouts.
|
||||||
|
// Vim: Insert next non-digit literally (same as <Ctrl-V>). Not yet supported (19.01.2020)
|
||||||
|
"<C-Q>"
|
||||||
|
)
|
||||||
|
}
|
||||||
|
@@ -31,7 +31,7 @@ import com.intellij.util.IJSwingUtilities;
|
|||||||
import com.maddyhome.idea.vim.VimPlugin;
|
import com.maddyhome.idea.vim.VimPlugin;
|
||||||
import com.maddyhome.idea.vim.ex.CommandParser;
|
import com.maddyhome.idea.vim.ex.CommandParser;
|
||||||
import com.maddyhome.idea.vim.ex.ExCommand;
|
import com.maddyhome.idea.vim.ex.ExCommand;
|
||||||
import com.maddyhome.idea.vim.ex.LineRange;
|
import com.maddyhome.idea.vim.ex.ranges.LineRange;
|
||||||
import com.maddyhome.idea.vim.group.MotionGroup;
|
import com.maddyhome.idea.vim.group.MotionGroup;
|
||||||
import com.maddyhome.idea.vim.helper.UiHelper;
|
import com.maddyhome.idea.vim.helper.UiHelper;
|
||||||
import com.maddyhome.idea.vim.option.OptionsManager;
|
import com.maddyhome.idea.vim.option.OptionsManager;
|
||||||
|
Reference in New Issue
Block a user