mirror of
https://github.com/chylex/IntelliJ-IdeaVim.git
synced 2025-08-17 16:31:45 +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
|
||||
usual beta standards.
|
||||
|
||||
[To Be Released]
|
||||
0.55, 2020-01-20
|
||||
--------------
|
||||
|
||||
_Available since 0.54.1 EAP:_
|
||||
|
||||
**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))
|
||||
* 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-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
|
||||
|
||||
_To Be Released:_
|
||||
|
||||
**Fixes:**
|
||||
* [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-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`.
|
||||
|
||||
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
|
||||
|
@@ -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
|
||||
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.
|
||||
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>
|
||||
|
@@ -3,8 +3,10 @@
|
||||
<id>IdeaVIM</id>
|
||||
<change-notes><![CDATA[
|
||||
<ul>
|
||||
<li>Fix `imap jk <ESC>` for the active autocompletion lookup</li>
|
||||
<li>Surround and Commentary extensions can be repeated with a dot command</li>
|
||||
<li>Support dot command for Surround and Commentary extensions</li>
|
||||
<li>Support XDG settings standard</li>
|
||||
<li>Add option to remove the status bar icon</li>
|
||||
<li>Various bug fixes</li>
|
||||
</ul>
|
||||
<p>See also the complete <a href="https://github.com/JetBrains/ideavim/blob/master/CHANGES.md">changelog</a>.</p>
|
||||
]]></change-notes>
|
||||
|
@@ -34,7 +34,6 @@ import com.intellij.openapi.util.Key
|
||||
import com.intellij.ui.KeyStrokeAdapter
|
||||
import com.maddyhome.idea.vim.KeyHandler
|
||||
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.EditorHelper
|
||||
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.key.ShortcutOwner
|
||||
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.KeyEvent
|
||||
import javax.swing.KeyStroke
|
||||
@@ -90,7 +89,7 @@ class VimShortcutKeyAction : AnAction(), DumbAware {
|
||||
if (aceJumpActive()) return false
|
||||
val keyCode = keyStroke.keyCode
|
||||
if (LookupManager.getActiveLookup(editor) != null) {
|
||||
return isEnabledForLookup(keyStroke)
|
||||
return LookupKeys.isEnabledForLookup(keyStroke)
|
||||
}
|
||||
if (keyCode == KeyEvent.VK_ESCAPE) {
|
||||
return isEnabledForEscape(editor)
|
||||
@@ -136,29 +135,6 @@ class VimShortcutKeyAction : AnAction(), DumbAware {
|
||||
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 {
|
||||
return VimPlugin.getKey().getKeymapConflicts(keyStroke).isNotEmpty()
|
||||
}
|
||||
@@ -189,6 +165,29 @@ class VimShortcutKeyAction : AnAction(), DumbAware {
|
||||
|
||||
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 {
|
||||
@JvmField
|
||||
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.command.Argument
|
||||
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.helper.EditorHelper
|
||||
|
||||
|
@@ -22,7 +22,7 @@ import com.intellij.openapi.editor.Editor
|
||||
import com.maddyhome.idea.vim.VimPlugin
|
||||
import com.maddyhome.idea.vim.command.Argument
|
||||
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
|
||||
|
||||
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.TextRange;
|
||||
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.helper.MessageHelper;
|
||||
import com.maddyhome.idea.vim.helper.Msg;
|
||||
@@ -412,7 +413,7 @@ public class CommandParser {
|
||||
reprocess = false;
|
||||
break;
|
||||
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) {
|
||||
error = MessageHelper.message(Msg.e_badrange, Character.toString(ch));
|
||||
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
|
||||
* 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)
|
||||
|
||||
/**
|
||||
* Exception class
|
||||
*/
|
||||
public class ExException extends Exception {
|
||||
/**
|
||||
* Constructs an <code>ExException</code> with no specified detail message.
|
||||
*/
|
||||
public ExException() {
|
||||
}
|
||||
class InvalidCommandException(message: String, cmd: String?) : ExException("$message | $cmd")
|
||||
|
||||
/**
|
||||
* Constructs an <code>ExException</code> with the specified detail message.
|
||||
*
|
||||
* @param s the detail message.
|
||||
*/
|
||||
public ExException(String s) {
|
||||
super(s);
|
||||
}
|
||||
}
|
||||
class InvalidRangeException(s: String) : ExException(s)
|
||||
|
||||
class MissingArgumentException : ExException()
|
||||
|
||||
class MissingRangeException : ExException()
|
||||
|
||||
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.common.TextRange
|
||||
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.helper.EditorHelper
|
||||
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.maddyhome.idea.vim.VimPlugin
|
||||
import com.maddyhome.idea.vim.ex.*
|
||||
import com.maddyhome.idea.vim.ex.ranges.LineRange
|
||||
import com.maddyhome.idea.vim.helper.inBlockSubMode
|
||||
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
|
||||
* 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) {
|
||||
|
||||
/**
|
||||
* Exception class
|
||||
*/
|
||||
public class InvalidCommandException extends ExException {
|
||||
public InvalidCommandException(String message, String cmd) {
|
||||
super(message);
|
||||
@JvmField
|
||||
val startLine: Int
|
||||
@JvmField
|
||||
val endLine: Int
|
||||
|
||||
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.Register;
|
||||
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.VisualGroupKt;
|
||||
import com.maddyhome.idea.vim.group.visual.VisualModeHelperKt;
|
||||
@@ -1787,8 +1787,8 @@ public class ChangeGroup {
|
||||
public boolean sortRange(@NotNull Editor editor,
|
||||
@NotNull LineRange range,
|
||||
@NotNull Comparator<String> lineComparator) {
|
||||
final int startLine = range.getStartLine();
|
||||
final int endLine = range.getEndLine();
|
||||
final int startLine = range.startLine;
|
||||
final int endLine = range.endLine;
|
||||
final int count = endLine - startLine + 1;
|
||||
if (count < 2) {
|
||||
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.common.CharacterPosition;
|
||||
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.option.ListOption;
|
||||
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) {
|
||||
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();
|
||||
return updateSearchHighlights(pattern, false, showHighlights, searchStartOffset, searchRange, forwards, false);
|
||||
}
|
||||
@@ -410,8 +410,8 @@ public class SearchGroup {
|
||||
}
|
||||
|
||||
if (shouldAddAllSearchHighlights(editor, pattern, showHighlights)) {
|
||||
final int startLine = searchRange == null ? 0 : searchRange.getStartLine();
|
||||
final int endLine = searchRange == null ? -1 : searchRange.getEndLine();
|
||||
final int startLine = searchRange == null ? 0 : searchRange.startLine;
|
||||
final int endLine = searchRange == null ? -1 : searchRange.endLine;
|
||||
List<TextRange> results = findAll(editor, pattern, startLine, endLine, shouldIgnoreCase(pattern, shouldIgnoreSmartCase));
|
||||
if (!results.isEmpty()) {
|
||||
currentMatchOffset = findClosestMatch(editor, results, initialOffset, forwards);
|
||||
@@ -1114,8 +1114,8 @@ public class SearchGroup {
|
||||
cmd.inc();
|
||||
}
|
||||
|
||||
int line1 = range.getStartLine();
|
||||
int line2 = range.getEndLine();
|
||||
int line1 = range.startLine;
|
||||
int line2 = range.endLine;
|
||||
|
||||
if (line1 < 0 || line2 < 0) {
|
||||
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 iskeyword = addOption(KeywordOption("iskeyword", "isk", arrayOf("@", "48-57", "_")))
|
||||
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 more = addOption(ToggleOption("more", "more", true))
|
||||
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.ex.CommandParser;
|
||||
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.helper.UiHelper;
|
||||
import com.maddyhome.idea.vim.option.OptionsManager;
|
||||
|
Reference in New Issue
Block a user