1
0
mirror of https://github.com/chylex/IntelliJ-IdeaVim.git synced 2025-08-17 16:31:45 +02:00

Compare commits

..

31 Commits

Author SHA1 Message Date
Alex Plate
c1ff6e1498 Prepare to the 0.55 release 2020-01-20 20:15:36 +03:00
Alex Plate
50c04ce71c Change LookupKeys implementation 2020-01-20 15:29:40 +03:00
Alex Plate
bc6ff6bc8e Convert characterHelper to kt 2020-01-19 17:55:15 +03:00
Alex Plate
93bcf2a7e8 Rename .java to .kt 2020-01-19 17:55:14 +03:00
Alex Plate
c3b503adff Set up a mechanism to define the KeyStrokes that should work with active lookup 2020-01-19 17:34:12 +03:00
Alex Plate
ecdcbdda10 Put all range files into the ranges directory 2020-01-16 15:13:56 +03:00
Alex Plate
b97c9a5ed0 Rename .java to .kt 2020-01-16 15:13:55 +03:00
Alex Plate
84a6843a7b Convert ExOutputModel to kt 2020-01-16 12:26:39 +03:00
Alex Plate
17eed7467c Rename .java to .kt 2020-01-16 12:26:39 +03:00
Alex Plate
310ffc849c Convert ExCommand to kt 2020-01-16 12:23:43 +03:00
Alex Plate
3e6756160a Rename .java to .kt 2020-01-16 12:23:43 +03:00
Alex Plate
563e809a2d Put all exceptions to single kt file 2020-01-16 12:18:20 +03:00
Alex Plate
86ec3f3bcd Rename .java to .kt 2020-01-16 12:17:54 +03:00
Alex Plate
b4e0ec282f Convert vim surround plugin to kt 2019-12-26 16:45:42 +03:00
Alex Plate
cbf7dfabcb Rename .java to .kt 2019-12-26 16:45:42 +03:00
Alex Plate
b8eb55d965 Update changes 2019-12-24 11:00:17 +03:00
Alex Plate
30662a8110 Comment out incompatible test 2019-12-24 10:48:55 +03:00
Alex Plate
69a273982f Fix 183 capability 2019-12-24 10:34:57 +03:00
Alex Plate
eef1b25e9f Convert common package to kt 2019-12-23 17:20:15 +03:00
Alex Plate
622de851fe Rename .java to .kt 2019-12-23 17:20:15 +03:00
Alex Plate
8cecb61d28 Convert selection type to kt 2019-12-23 16:21:39 +03:00
Alex Plate
77d8d27dfa Rename .java to .kt 2019-12-23 16:21:39 +03:00
Alex Plate
340f259b17 [VIM-1884] Add CTRL-J to the list of keys working with lookup 2019-12-23 15:54:52 +03:00
Alex Plate
5cf68a7f06 [VIM-1878] Update submode in case of active template 2019-12-23 12:54:57 +03:00
Alex Plate
0ae5abcd0c [VIM-1874] Add possibility to execute option listener after adding 2019-12-19 18:54:51 +03:00
Alex Plate
b6cecb2125 Refactor options change listener 2019-12-19 18:54:50 +03:00
Alex Plate
3aa3a9c9a8 Get rid of unused name sorter 2019-12-19 18:13:51 +03:00
Alex Plate
2c11ed43e4 [VIM-1875] Fix isk loading on startup 2019-12-19 17:12:29 +03:00
Alex Plate
be0bee672e Merge pull request #213 from hajdamak/VIM-664-fix
VIM-664 Fix usage of invalid path separator
2019-12-19 11:26:53 +03:00
Rafał Hajdacki
b695e3c646 Fix usage of invalid path separator 2019-12-18 14:09:40 +01:00
Alex Plate
33840dc5cd Add info about [Version Update] YouTrack tag 2019-12-18 10:58:31 +03:00
64 changed files with 1511 additions and 2428 deletions

View File

@@ -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,7 +41,10 @@ _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
* [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
* [VIM-1884](https://youtrack.jetbrains.com/issue/VIM-1884) Show quickDoc during popup with `CTRL-J`
0.54, 2019-11-20
--------------

View File

@@ -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

View File

@@ -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>

View File

@@ -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>
@@ -21,6 +23,7 @@
<vendor>JetBrains</vendor>
<!-- Please search for "[VERSION UPDATE]" in project in case you update the since-build version -->
<!-- Check for [Version Update] tag in YouTrack as well -->
<idea-version since-build="183.4284.148"/>
<!-- Mark the plugin as compatible with RubyMine and other products based on the IntelliJ platform -->

View File

@@ -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,27 +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>"
)
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()
}
@@ -187,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))

View File

@@ -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

View File

@@ -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() {

View File

@@ -1,91 +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.command;
import org.jetbrains.annotations.NotNull;
import java.util.EnumSet;
/**
* @author vlan
*/
public enum SelectionType {
// Integer values for registers serialization in RegisterGroup.readData()
LINE_WISE(1 << 1),
CHARACTER_WISE(1 << 2),
BLOCK_WISE(1 << 3);
SelectionType(int value) {
this.value = value;
}
private final int value;
public int getValue() {
return value;
}
@NotNull
public static SelectionType fromValue(int value) {
for (SelectionType type : SelectionType.values()) {
if (type.getValue() == value) {
return type;
}
}
return CHARACTER_WISE;
}
@NotNull
public static SelectionType fromSubMode(@NotNull CommandState.SubMode subMode) {
switch (subMode) {
case VISUAL_LINE:
return LINE_WISE;
case VISUAL_BLOCK:
return BLOCK_WISE;
default:
return CHARACTER_WISE;
}
}
@NotNull
public CommandState.SubMode toSubMode() {
switch (this) {
case LINE_WISE:
return CommandState.SubMode.VISUAL_LINE;
case CHARACTER_WISE:
return CommandState.SubMode.VISUAL_CHARACTER;
case BLOCK_WISE:
return CommandState.SubMode.VISUAL_BLOCK;
default:
return CommandState.SubMode.VISUAL_CHARACTER;
}
}
public static SelectionType fromCommandFlags(EnumSet<CommandFlags> flags) {
if (flags.contains(CommandFlags.FLAG_MOT_LINEWISE)) {
return SelectionType.LINE_WISE;
}
else if (flags.contains(CommandFlags.FLAG_MOT_BLOCKWISE)) {
return SelectionType.BLOCK_WISE;
}
else {
return SelectionType.CHARACTER_WISE;
}
}
}

View File

@@ -0,0 +1,63 @@
/*
* 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.command
import com.maddyhome.idea.vim.command.CommandState.SubMode
import java.util.*
/**
* @author vlan
*/
enum class SelectionType(val value: Int) {
// Integer values for registers serialization in RegisterGroup.readData()
LINE_WISE(1 shl 1),
CHARACTER_WISE(1 shl 2),
BLOCK_WISE(1 shl 3);
fun toSubMode() = when (this) {
LINE_WISE -> SubMode.VISUAL_LINE
CHARACTER_WISE -> SubMode.VISUAL_CHARACTER
BLOCK_WISE -> SubMode.VISUAL_BLOCK
}
companion object {
@JvmStatic
fun fromValue(value: Int): SelectionType {
for (type in values()) {
if (type.value == value) {
return type
}
}
return CHARACTER_WISE
}
@JvmStatic
fun fromSubMode(subMode: SubMode): SelectionType = when (subMode) {
SubMode.VISUAL_LINE -> LINE_WISE
SubMode.VISUAL_BLOCK -> BLOCK_WISE
else -> CHARACTER_WISE
}
@JvmStatic
fun fromCommandFlags(flags: EnumSet<CommandFlags>) = when {
CommandFlags.FLAG_MOT_LINEWISE in flags -> LINE_WISE
CommandFlags.FLAG_MOT_BLOCKWISE in flags -> BLOCK_WISE
else -> CHARACTER_WISE
}
}
}

View File

@@ -1,76 +0,0 @@
package com.maddyhome.idea.vim.common;
import com.intellij.application.options.CodeStyle;
import com.intellij.openapi.actionSystem.DataContext;
import com.intellij.openapi.actionSystem.PlatformDataKeys;
import com.intellij.openapi.editor.Editor;
import com.intellij.openapi.project.Project;
import com.intellij.openapi.util.text.StringUtil;
import com.intellij.psi.codeStyle.CommonCodeStyleSettings;
public class IndentConfig {
private final int indentSize;
private final int tabSize;
private final boolean useTabs;
private IndentConfig(CommonCodeStyleSettings.IndentOptions indentOptions) {
this.indentSize = indentOptions.INDENT_SIZE;
this.tabSize = indentOptions.TAB_SIZE;
this.useTabs = indentOptions.USE_TAB_CHARACTER;
}
public static IndentConfig create(Editor editor) {
return create(editor, editor.getProject());
}
public static IndentConfig create(Editor editor, DataContext context) {
return create(editor, PlatformDataKeys.PROJECT.getData(context));
}
public static IndentConfig create(Editor editor, Project project) {
CommonCodeStyleSettings.IndentOptions indentOptions;
if(project != null) {
indentOptions = CodeStyle.getIndentOptions(project, editor.getDocument());
} else {
indentOptions = CodeStyle.getDefaultSettings().getIndentOptions();
}
return new IndentConfig(indentOptions);
}
public int getIndentSize() {
return indentSize;
}
public int getTabSize() {
return tabSize;
}
public boolean isUseTabs() {
return useTabs;
}
public int getTotalIndent(int count) {
return indentSize * count;
}
public String createIndentByCount(int count) {
return createIndentBySize(getTotalIndent(count));
}
public String createIndentBySize(int size) {
final int tabCount;
final int spaceCount;
if (useTabs) {
tabCount = size / tabSize;
spaceCount = size % tabSize;
}
else {
tabCount = 0;
spaceCount = size;
}
return StringUtil.repeat("\t", tabCount) + StringUtil.repeat(" ", spaceCount);
}
}

View File

@@ -0,0 +1,51 @@
package com.maddyhome.idea.vim.common
import com.intellij.application.options.CodeStyle
import com.intellij.openapi.actionSystem.DataContext
import com.intellij.openapi.actionSystem.PlatformDataKeys
import com.intellij.openapi.editor.Editor
import com.intellij.openapi.project.Project
import com.intellij.psi.codeStyle.CommonCodeStyleSettings.IndentOptions
class IndentConfig private constructor(indentOptions: IndentOptions) {
private val indentSize = indentOptions.INDENT_SIZE
private val tabSize = indentOptions.TAB_SIZE
private val isUseTabs = indentOptions.USE_TAB_CHARACTER
fun getTotalIndent(count: Int): Int = indentSize * count
fun createIndentByCount(count: Int): String = createIndentBySize(getTotalIndent(count))
fun createIndentBySize(size: Int): String {
val tabCount: Int
val spaceCount: Int
if (isUseTabs) {
tabCount = size / tabSize
spaceCount = size % tabSize
} else {
tabCount = 0
spaceCount = size
}
return "\t".repeat(tabCount) + " ".repeat(spaceCount)
}
companion object {
@JvmStatic
fun create(editor: Editor, context: DataContext): IndentConfig {
return create(editor, PlatformDataKeys.PROJECT.getData(context))
}
@JvmStatic
@JvmOverloads
fun create(editor: Editor, project: Project? = editor.project): IndentConfig {
val indentOptions = if (project != null) {
CodeStyle.getIndentOptions(project, editor.document)
} else {
// [VERSION UPDATE] 191+
@Suppress("UNNECESSARY_NOT_NULL_ASSERTION")
CodeStyle.getDefaultSettings().indentOptions!!
}
return IndentConfig(indentOptions)
}
}
}

View File

@@ -1,121 +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.common;
import com.intellij.codeInsight.editorActions.TextBlockTransferableData;
import com.maddyhome.idea.vim.command.SelectionType;
import com.maddyhome.idea.vim.helper.StringHelper;
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.Comparator;
import java.util.List;
/**
* Represents a register.
*/
public class Register {
private char name;
@NotNull private final SelectionType type;
@NotNull private final List<KeyStroke> keys;
@NotNull private List<? extends TextBlockTransferableData> transferableData = new ArrayList<>();
public Register(char name, @NotNull SelectionType type, @NotNull List<KeyStroke> keys) {
this.name = name;
this.type = type;
this.keys = keys;
}
public Register(char name, @NotNull SelectionType type, @NotNull String text, @NotNull List<? extends TextBlockTransferableData> transferableData) {
this.name = name;
this.type = type;
this.keys = StringHelper.stringToKeys(text);
this.transferableData = transferableData;
}
public void rename(char name) {
this.name = name;
}
/**
* Get the name the register is assigned to.
*/
public char getName() {
return name;
}
@NotNull
public List<? extends TextBlockTransferableData> getTransferableData() {
return transferableData;
}
/**
* Get the register type.
*/
@NotNull
public SelectionType getType() {
return type;
}
/**
* Get the text in the register.
*/
@Nullable
public String getText() {
final StringBuilder builder = new StringBuilder();
for (KeyStroke key : keys) {
final char c = key.getKeyChar();
if (c == KeyEvent.CHAR_UNDEFINED) {
return null;
}
builder.append(c);
}
return builder.toString();
}
/**
* Get the sequence of keys in the register.
*/
@NotNull
public List<KeyStroke> getKeys() {
return keys;
}
/**
* Append the supplied text to any existing text.
*/
public void addTextAndResetTransferableData(@NotNull String text) {
addKeys(StringHelper.stringToKeys(text));
transferableData.clear();
}
public void addKeys(@NotNull List<KeyStroke> keys) {
this.keys.addAll(keys);
}
public static class KeySorter implements Comparator<Register> {
@Override
public int compare(@NotNull Register o1, @NotNull Register o2) {
return Character.compare(o1.name, o2.name);
}
}
}

View File

@@ -0,0 +1,75 @@
/*
* 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.common
import com.intellij.codeInsight.editorActions.TextBlockTransferableData
import com.maddyhome.idea.vim.command.SelectionType
import com.maddyhome.idea.vim.helper.StringHelper
import java.awt.event.KeyEvent
import java.util.*
import javax.swing.KeyStroke
class Register {
var name: Char
val type: SelectionType
val keys: MutableList<KeyStroke>
val transferableData: MutableList<out TextBlockTransferableData>
constructor(name: Char, type: SelectionType, keys: MutableList<KeyStroke>) {
this.name = name
this.type = type
this.keys = keys
this.transferableData = mutableListOf()
}
constructor(name: Char, type: SelectionType, text: String, transferableData: MutableList<out TextBlockTransferableData>) {
this.name = name
this.type = type
this.keys = StringHelper.stringToKeys(text)
this.transferableData = transferableData
}
val text: String?
get() {
val builder = StringBuilder()
for (key in keys) {
val c = key.keyChar
if (c == KeyEvent.CHAR_UNDEFINED) {
return null
}
builder.append(c)
}
return builder.toString()
}
/**
* Append the supplied text to any existing text.
*/
fun addTextAndResetTransferableData(text: String) {
addKeys(StringHelper.stringToKeys(text))
transferableData.clear()
}
fun addKeys(keys: List<KeyStroke>) {
this.keys.addAll(keys)
}
object KeySorter : Comparator<Register> {
override fun compare(o1: Register, o2: Register): Int = o1.name.compareTo(o2.name)
}
}

View File

@@ -1,133 +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.common;
import org.jetbrains.annotations.Contract;
import org.jetbrains.annotations.NotNull;
/**
* Please prefer {@link com.maddyhome.idea.vim.group.visual.VimSelection} for visual selection
*/
public class TextRange {
@Contract(pure = true)
public TextRange(int start, int end) {
this(new int[]{start}, new int[]{end});
}
@Contract(pure = true)
public TextRange(int[] starts, int[] ends) {
this.starts = starts;
this.ends = ends;
}
public boolean isMultiple() {
return starts != null && starts.length > 1;
}
public int getMaxLength() {
int max = 0;
for (int i = 0; i < size(); i++) {
max = Math.max(max, getEndOffsets()[i] - getStartOffsets()[i]);
}
return max;
}
public int getSelectionCount() {
int res = 0;
for (int i = 0; i < size(); i++) {
res += getEndOffsets()[i] - getStartOffsets()[i];
}
return res;
}
public int size() {
return starts.length;
}
public int getStartOffset() {
return starts[0];
}
public int getEndOffset() {
return ends[ends.length - 1];
}
public int[] getStartOffsets() {
return starts;
}
public int[] getEndOffsets() {
return ends;
}
@NotNull
public TextRange normalize() {
normalizeIndex(0);
return this;
}
private void normalizeIndex(final int index) {
if (index < size() && ends[index] < starts[index]) {
int t = starts[index];
starts[index] = ends[index];
ends[index] = t;
}
}
@Contract(mutates = "this")
public boolean normalize(final int fileSize) {
for (int i = 0; i < size(); i++) {
normalizeIndex(i);
starts[i] = Math.max(0, Math.min(starts[i], fileSize));
if (starts[i] == fileSize && fileSize != 0) {
return false;
}
ends[i] = Math.max(0, Math.min(ends[i], fileSize));
}
return true;
}
public boolean contains(final int offset) {
if (isMultiple()) {
return false;
}
return this.getStartOffset() <= offset && offset < this.getEndOffset();
}
@NotNull
public String toString() {
final StringBuilder sb = new StringBuilder();
sb.append("TextRange");
sb.append("{starts=").append(starts == null ? "null" : "");
for (int i = 0; starts != null && i < starts.length; ++i) {
sb.append(i == 0 ? "" : ", ").append(starts[i]);
}
sb.append(", ends=").append(ends == null ? "null" : "");
for (int i = 0; ends != null && i < ends.length; ++i) {
sb.append(i == 0 ? "" : ", ").append(ends[i]);
}
sb.append('}');
return sb.toString();
}
private final int[] starts;
private final int[] ends;
}

View File

@@ -0,0 +1,107 @@
/*
* 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.common
import org.jetbrains.annotations.Contract
import kotlin.math.max
import kotlin.math.min
/**
* Please prefer [com.maddyhome.idea.vim.group.visual.VimSelection] for visual selection
*/
class TextRange(val startOffsets: IntArray, val endOffsets: IntArray) {
constructor(start: Int, end: Int) : this(intArrayOf(start), intArrayOf(end))
val isMultiple
get() = startOffsets.size > 1
val maxLength: Int
get() {
var max = 0
for (i in 0 until size()) {
max = max(max, endOffsets[i] - startOffsets[i])
}
return max
}
val selectionCount: Int
get() {
var res = 0
for (i in 0 until size()) {
res += endOffsets[i] - startOffsets[i]
}
return res
}
fun size(): Int = startOffsets.size
val startOffset: Int
get() = startOffsets.first()
val endOffset: Int
get() = endOffsets.last()
fun normalize(): TextRange {
normalizeIndex(0)
return this
}
private fun normalizeIndex(index: Int) {
if (index < size() && endOffsets[index] < startOffsets[index]) {
val t = startOffsets[index]
startOffsets[index] = endOffsets[index]
endOffsets[index] = t
}
}
@Contract(mutates = "this")
fun normalize(fileSize: Int): Boolean {
for (i in 0 until size()) {
normalizeIndex(i)
startOffsets[i] = max(0, min(startOffsets[i], fileSize))
if (startOffsets[i] == fileSize && fileSize != 0) {
return false
}
endOffsets[i] = max(0, min(endOffsets[i], fileSize))
}
return true
}
operator fun contains(offset: Int): Boolean = if (isMultiple) false else offset in startOffset until endOffset
override fun toString(): String {
val sb = StringBuilder()
sb.append("TextRange")
sb.append("{starts=")
var i = 0
while (i < startOffsets.size) {
sb.append(if (i == 0) "" else ", ").append(startOffsets[i])
++i
}
sb.append(", ends=")
i = 0
while (i < endOffsets.size) {
sb.append(if (i == 0) "" else ", ").append(endOffsets[i])
++i
}
sb.append('}')
return sb.toString()
}
}

View File

@@ -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;

View File

@@ -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;
}

View 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
}

View File

@@ -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()

View File

@@ -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;
}
}

View 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
}
}
}

View File

@@ -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);
}
}

View File

@@ -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);
}
}

View File

@@ -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;
}

View File

@@ -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);
}
}

View File

@@ -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);
}
}

View File

@@ -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);
}
}

View File

@@ -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);
}
}

View File

@@ -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;
}
}

View File

@@ -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();
}

View File

@@ -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;
}

View File

@@ -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

View File

@@ -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.*

View File

@@ -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;
}

View File

@@ -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;
}

View File

@@ -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;
}

View File

@@ -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 '\\/', '\\?', '\\&amp;', /{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());
}

View 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 '\\/', '\\?', '\\&amp;', /{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)
}
}

View File

@@ -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
}
}
}
}

View 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()
}

View File

@@ -41,7 +41,7 @@ import java.util.regex.Pattern;
public class VimScriptParser {
public static final String VIMRC_FILE_NAME = "ideavimrc";
public static final String[] HOME_VIMRC_PATHS = {"." + VIMRC_FILE_NAME, "_" + VIMRC_FILE_NAME};
public static final String XDG_VIMRC_PATH = "ideavim" + File.pathSeparator + VIMRC_FILE_NAME;
public static final String XDG_VIMRC_PATH = "ideavim" + File.separator + VIMRC_FILE_NAME;
public static final int BUFSIZE = 4096;
private static final Pattern EOL_SPLIT_PATTERN = Pattern.compile(" *(\r\n|\n)+ *");
private static final Pattern DOUBLE_QUOTED_STRING = Pattern.compile("\"([^\"]*)\"");
@@ -55,7 +55,6 @@ public class VimScriptParser {
@Nullable
public static File findIdeaVimRc() {
final String homeDirName = System.getProperty("user.home");
// Check whether file exists in home dir
if (homeDirName != null) {
for (String fileName : HOME_VIMRC_PATHS) {

View File

@@ -40,7 +40,7 @@ public class VimExtensionRegistrar {
registeredExtensions.add(name);
ToggleOption option = new ToggleOption(name, name, false);
option.addOptionChangeListener(event -> {
option.addOptionChangeListener((oldValue, newValue) -> {
for (VimExtension extensionInListener : VimExtension.EP_NAME.getExtensionList()) {
if (name.equals(extensionInListener.getName())) {
if (OptionsManager.INSTANCE.isSet(name)) {

View File

@@ -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;
}
}
}
}

View File

@@ -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
}
}
}

View File

@@ -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;

View File

@@ -32,7 +32,6 @@ import com.intellij.openapi.project.Project;
import com.maddyhome.idea.vim.KeyHandler;
import com.maddyhome.idea.vim.VimPlugin;
import com.maddyhome.idea.vim.helper.*;
import com.maddyhome.idea.vim.option.OptionChangeEvent;
import com.maddyhome.idea.vim.option.OptionChangeListener;
import com.maddyhome.idea.vim.option.OptionsManager;
import gnu.trove.TIntFunction;
@@ -276,7 +275,7 @@ public class EditorGroup {
VimPlugin.getNotifications(project).notifyAboutIdeaJoin();
}
public static class NumberChangeListener implements OptionChangeListener {
public static class NumberChangeListener implements OptionChangeListener<Boolean> {
public static NumberChangeListener INSTANCE = new NumberChangeListener();
@Contract(pure = true)
@@ -284,7 +283,7 @@ public class EditorGroup {
}
@Override
public void valueChange(OptionChangeEvent event) {
public void valueChange(Boolean oldValue, Boolean newValue) {
for (Editor editor : EditorFactory.getInstance().getAllEditors()) {
if (UserDataManager.getVimEditorGroup(editor) && supportsVimLineNumbers(editor)) {
updateLineNumbers(editor, true);

View File

@@ -89,7 +89,7 @@ public class RegisterGroup {
public RegisterGroup() {
final ListOption clipboardOption = OptionsManager.INSTANCE.getClipboard();
clipboardOption.addOptionChangeListener(event -> {
clipboardOption.addOptionChangeListenerAndExecute((oldValue, newValue) -> {
if (clipboardOption.contains("unnamed")) {
defaultRegister = '*';
}
@@ -221,7 +221,7 @@ public class RegisterGroup {
for (char d = '8'; d >= '1'; d--) {
Register t = registers.get(d);
if (t != null) {
t.rename((char)(d + 1));
t.setName((char)(d + 1));
registers.put((char)(d + 1), t);
}
}
@@ -365,7 +365,7 @@ public class RegisterGroup {
res.add(register);
}
}
res.sort(new Register.KeySorter());
res.sort(Register.KeySorter.INSTANCE);
return res;
}

View File

@@ -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;
@@ -63,12 +63,12 @@ import java.util.*;
public class SearchGroup {
public SearchGroup() {
final OptionsManager options = OptionsManager.INSTANCE;
options.getHlsearch().addOptionChangeListener(event -> {
options.getHlsearch().addOptionChangeListener((oldValue, newValue) -> {
resetShowSearchHighlight();
forceUpdateSearchHighlights();
});
final OptionChangeListener updateHighlightsIfVisible = event -> {
final OptionChangeListener<Boolean> updateHighlightsIfVisible = (oldValue, newValue) -> {
if (showSearchHighlight) {
forceUpdateSearchHighlights();
}
@@ -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;

View File

@@ -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;
}
}

View 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
}
}

View File

@@ -2154,9 +2154,7 @@ public class SearchHelper {
private static String getPairChars() {
if (pairsChars == null) {
ListOption lo = OptionsManager.INSTANCE.getMatchpairs();
pairsChars = parseOption(lo);
lo.addOptionChangeListener(event -> pairsChars = parseOption((ListOption)event.getOption()));
lo.addOptionChangeListenerAndExecute((oldValue, newValue) -> pairsChars = parseOption(lo));
}
return pairsChars;

View File

@@ -44,19 +44,21 @@ public final class KeywordOption extends ListOption {
@Override
public boolean append(@NotNull String val) {
String oldValue = getValue();
final List<String> vals = parseVals(val);
final List<KeywordSpec> specs = valsToValidatedAndReversedSpecs(vals);
if (vals == null || specs == null) {
return false;
}
value.addAll(vals);
this.value.addAll(vals);
keywordSpecs.addAll(0, specs);
fireOptionChangeEvent();
fireOptionChangeEvent(oldValue, getValue());
return true;
}
@Override
public boolean prepend(@NotNull String val) {
String oldValue = getValue();
final List<String> vals = parseVals(val);
final List<KeywordSpec> specs = valsToValidatedAndReversedSpecs(vals);
if (vals == null || specs == null) {
@@ -64,13 +66,14 @@ public final class KeywordOption extends ListOption {
}
value.addAll(0, vals);
keywordSpecs.addAll(specs);
fireOptionChangeEvent();
fireOptionChangeEvent(oldValue, getValue());
return true;
}
@Override
public boolean remove(@NotNull String val) {
String oldValue = getValue();
final List<String> vals = parseVals(val);
final List<KeywordSpec> specs = valsToValidatedAndReversedSpecs(vals);
if (vals == null || specs == null) {
@@ -78,20 +81,22 @@ public final class KeywordOption extends ListOption {
}
value.removeAll(vals);
keywordSpecs.removeAll(specs);
fireOptionChangeEvent();
fireOptionChangeEvent(oldValue, getValue());
return true;
}
private void initialSet(String[] values) {
final List<String> vals = Arrays.asList(values);
String oldValue = getValue();
final List<String> vals = new ArrayList<>(Arrays.asList(values));
final List<KeywordSpec> specs = valsToReversedSpecs(vals);
value = vals;
keywordSpecs = specs;
fireOptionChangeEvent();
fireOptionChangeEvent(oldValue, getValue());
}
@Override
public boolean set(@NotNull String val) {
String oldValue = getValue();
final List<String> vals = parseVals(val);
final List<KeywordSpec> specs = valsToValidatedAndReversedSpecs(vals);
if (vals == null || specs == null) {
@@ -99,7 +104,7 @@ public final class KeywordOption extends ListOption {
}
value = vals;
keywordSpecs = specs;
fireOptionChangeEvent();
fireOptionChangeEvent(oldValue, getValue());
return true;
}

View File

@@ -128,8 +128,9 @@ public class ListOption extends TextOption {
return false;
}
value = vals;
fireOptionChangeEvent();
String oldValue = getValue();
this.value = vals;
fireOptionChangeEvent(oldValue, getValue());
return true;
}
@@ -139,8 +140,9 @@ public class ListOption extends TextOption {
return false;
}
String oldValue = getValue();
value.addAll(vals);
fireOptionChangeEvent();
fireOptionChangeEvent(oldValue, getValue());
return true;
}
@@ -150,8 +152,9 @@ public class ListOption extends TextOption {
return false;
}
String oldValue = getValue();
value.addAll(0, vals);
fireOptionChangeEvent();
fireOptionChangeEvent(oldValue, getValue());
return true;
}
@@ -161,8 +164,9 @@ public class ListOption extends TextOption {
return false;
}
String oldValue = getValue();
value.removeAll(vals);
fireOptionChangeEvent();
fireOptionChangeEvent(oldValue, getValue());
return true;
}
@@ -230,8 +234,9 @@ public class ListOption extends TextOption {
@Override
public void resetDefault() {
if (!dflt.equals(value)) {
String oldValue = getValue();
value = new ArrayList<>(dflt);
fireOptionChangeEvent();
fireOptionChangeEvent(oldValue, getValue());
}
}
}

View File

@@ -99,8 +99,10 @@ public class NumberOption extends TextOption {
}
if (inRange(num)) {
value = num;
fireOptionChangeEvent();
String oldValue = getValue();
this.value = num;
fireOptionChangeEvent(oldValue, getValue());
return true;
}
@@ -125,8 +127,9 @@ public class NumberOption extends TextOption {
}
if (inRange(value + num)) {
String oldValue = getValue();
value += num;
fireOptionChangeEvent();
fireOptionChangeEvent(oldValue, getValue());
return true;
}
@@ -151,8 +154,9 @@ public class NumberOption extends TextOption {
}
if (inRange(value * num)) {
String oldValue = getValue();
value *= num;
fireOptionChangeEvent();
fireOptionChangeEvent(oldValue, getValue());
return true;
}
@@ -177,8 +181,9 @@ public class NumberOption extends TextOption {
}
if (inRange(value - num)) {
String oldValue = getValue();
value -= num;
fireOptionChangeEvent();
fireOptionChangeEvent(oldValue, getValue());
return true;
}
@@ -202,8 +207,9 @@ public class NumberOption extends TextOption {
@Override
public void resetDefault() {
if (dflt != value) {
String oldValue = getValue();
value = dflt;
fireOptionChangeEvent();
fireOptionChangeEvent(oldValue, getValue());
}
}

View File

@@ -21,14 +21,13 @@ package com.maddyhome.idea.vim.option;
import org.jetbrains.annotations.NotNull;
import java.util.ArrayList;
import java.util.Comparator;
import java.util.List;
/**
* Represents an VIM options that can be set with the :set command. Listeners can be set that are interested in knowing
* when the value of the option changes.
*/
public abstract class Option {
public abstract class Option<T> {
/**
* Create the option
*
@@ -46,16 +45,27 @@ public abstract class Option {
*
* @param listener The listener
*/
public void addOptionChangeListener(OptionChangeListener listener) {
public void addOptionChangeListener(OptionChangeListener<T> listener) {
listeners.add(listener);
}
/**
* Registers an option change listener and fire an event.
*
* @param listener The listener
*/
public void addOptionChangeListenerAndExecute(OptionChangeListener<T> listener) {
addOptionChangeListener(listener);
T value = getValue();
fireOptionChangeEvent(value, value);
}
/**
* Removes the listener from the list.
*
* @param listener The listener
*/
public void removeOptionChangeListener(OptionChangeListener listener) {
public void removeOptionChangeListener(OptionChangeListener<T> listener) {
listeners.remove(listener);
}
@@ -93,24 +103,15 @@ public abstract class Option {
* Lets all listeners know that the value has changed. Subclasses are responsible for calling this when their
* value changes.
*/
protected void fireOptionChangeEvent() {
OptionChangeEvent event = new OptionChangeEvent(this);
for (OptionChangeListener listener : listeners) {
listener.valueChange(event);
protected void fireOptionChangeEvent(T oldValue, T newValue) {
for (OptionChangeListener<T> listener : listeners) {
listener.valueChange(oldValue, newValue);
}
}
/**
* Helper method used to sort lists of options by their name
*/
static class NameSorter<V> implements Comparator<V> {
@Override
public int compare(@NotNull V o1, @NotNull V o2) {
return ((Option)o1).name.compareTo(((Option)o2).name);
}
}
public abstract T getValue();
protected final String name;
protected final String abbrev;
@NotNull protected final List<OptionChangeListener> listeners = new ArrayList<>();
@NotNull private final List<OptionChangeListener<T>> listeners = new ArrayList<>();
}

View File

@@ -1,42 +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.option;
import org.jetbrains.annotations.NotNull;
import java.util.EventObject;
/**
* This event indicates that the value of the option has changed
*/
public class OptionChangeEvent extends EventObject {
public OptionChangeEvent(Option option) {
super(option);
}
/**
* Gets the changed option.
*
* @return The changed option
*/
@NotNull
public Option getOption() {
return (Option)getSource();
}
}

View File

@@ -18,16 +18,9 @@
package com.maddyhome.idea.vim.option;
import java.util.EventListener;
/**
* This interface is used for classes that wish to be notified whenever the value of an option has changed
*/
public interface OptionChangeListener extends EventListener {
/**
* The value of the option has changed.
*
* @param event The change event
*/
void valueChange(OptionChangeEvent event);
public interface OptionChangeListener<T> {
void valueChange(T oldValue, T newValue);
}

View File

@@ -34,6 +34,7 @@ import com.maddyhome.idea.vim.helper.Msg
import com.maddyhome.idea.vim.helper.hasVisualSelection
import com.maddyhome.idea.vim.helper.isBlockCaret
import com.maddyhome.idea.vim.helper.mode
import com.maddyhome.idea.vim.helper.subMode
import com.maddyhome.idea.vim.listener.SelectionVimListenerSuppressor
import org.jetbrains.annotations.Contract
import java.util.*
@@ -44,8 +45,8 @@ import kotlin.math.min
object OptionsManager {
private val logger = Logger.getInstance(OptionsManager::class.java)
private val options: MutableMap<String, Option> = mutableMapOf()
private val abbrevs: MutableMap<String, Option> = mutableMapOf()
private val options: MutableMap<String, Option<*>> = mutableMapOf()
private val abbrevs: MutableMap<String, Option<*>> = mutableMapOf()
val clipboard = addOption(ListOption(ClipboardOptionsData.name, ClipboardOptionsData.abbr, arrayOf(ClipboardOptionsData.ideaput, "autoselect,exclude:cons\\|linux"), null))
val digraph = addOption(ToggleOption("digraph", "dg", false))
@@ -57,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")))
@@ -95,7 +96,7 @@ object OptionsManager {
/**
* Gets an option by the supplied name or short name.
*/
fun getOption(name: String): Option? = options[name] ?: abbrevs[name]
fun getOption(name: String): Option<*>? = options[name] ?: abbrevs[name]
/**
* This parses a set of :set commands. The following types of commands are supported:
@@ -144,7 +145,7 @@ object OptionsManager {
var error: String? = null
var token = ""
val tokenizer = StringTokenizer(args)
val toShow = mutableListOf<Option>()
val toShow = mutableListOf<Option<*>>()
while (tokenizer.hasMoreTokens()) {
token = tokenizer.nextToken()
// See if a space has been backslashed, if no get the rest of the text
@@ -290,11 +291,11 @@ object OptionsManager {
* @param opts The list of options to display
* @param showIntro True if intro is displayed, false if not
*/
private fun showOptions(editor: Editor?, opts: Collection<Option>, showIntro: Boolean) {
private fun showOptions(editor: Editor?, opts: Collection<Option<*>>, showIntro: Boolean) {
if (editor == null) return
val cols = mutableListOf<Option>()
val extra = mutableListOf<Option>()
val cols = mutableListOf<Option<*>>()
val extra = mutableListOf<Option<*>>()
for (option in opts) {
if (option.toString().length > 19) extra.add(option) else cols.add(option)
}
@@ -347,7 +348,7 @@ object OptionsManager {
}
@Contract("_ -> param1")
fun <T : Option> addOption(option: T): T {
fun <T : Option<*>> addOption(option: T): T {
options += option.name to option
abbrevs += option.abbrev to option
return option
@@ -468,6 +469,13 @@ object IdeaRefactorMode {
editor.selectionModel.removeSelection()
}
}
if (editor.mode.hasVisualSelection && editor.selectionModel.hasSelection()) {
val autodetectedSubmode = VimPlugin.getVisualMotion().autodetectVisualSubmode(editor)
if (editor.subMode != autodetectedSubmode) {
// Update the submode
editor.subMode = autodetectedSubmode
}
}
if (editor.mode.isBlockCaret) {
TemplateManagerImpl.getTemplateState(editor)?.currentVariableRange?.let { segmentRange ->
@@ -498,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>"
)
}

View File

@@ -55,8 +55,9 @@ public class StringOption extends TextOption {
*/
@Override
public boolean set(String val) {
String oldValue = getValue();
value = val;
fireOptionChangeEvent();
fireOptionChangeEvent(oldValue, getValue());
return true;
}
@@ -69,8 +70,9 @@ public class StringOption extends TextOption {
*/
@Override
public boolean append(String val) {
String oldValue = getValue();
value += val;
fireOptionChangeEvent();
fireOptionChangeEvent(oldValue, getValue());
return true;
}
@@ -83,8 +85,9 @@ public class StringOption extends TextOption {
*/
@Override
public boolean prepend(String val) {
String oldValue = getValue();
value = val + value;
fireOptionChangeEvent();
fireOptionChangeEvent(oldValue, getValue());
return true;
}
@@ -99,8 +102,9 @@ public class StringOption extends TextOption {
public boolean remove(@NotNull String val) {
int pos = value.indexOf(val);
if (pos != -1) {
String oldValue = getValue();
value = value.substring(0, pos) + value.substring(pos + val.length());
fireOptionChangeEvent();
fireOptionChangeEvent(oldValue, getValue());
return true;
}
@@ -124,8 +128,9 @@ public class StringOption extends TextOption {
@Override
public void resetDefault() {
if (!dflt.equals(value)) {
String oldValue = getValue();
value = dflt;
fireOptionChangeEvent();
fireOptionChangeEvent(oldValue, getValue());
}
}

View File

@@ -18,13 +18,11 @@
package com.maddyhome.idea.vim.option;
public abstract class TextOption extends Option {
public abstract class TextOption extends Option<String> {
TextOption(String name, String abbrev) {
super(name, abbrev);
}
public abstract String getValue();
public abstract boolean set(String val);
public abstract boolean append(String val);

View File

@@ -23,7 +23,7 @@ import org.jetbrains.annotations.NotNull;
/**
* Represents a boolean option
*/
public class ToggleOption extends Option {
public class ToggleOption extends Option<Boolean> {
/**
* Creates the option
*
@@ -38,12 +38,8 @@ public class ToggleOption extends Option {
this.value = dflt;
}
/**
* The option's value
*
* @return The value
*/
public boolean getValue() {
@Override
public Boolean getValue() {
return value;
}
@@ -81,7 +77,7 @@ public class ToggleOption extends Option {
boolean old = value;
value = val;
if (val != old) {
fireOptionChangeEvent();
fireOptionChangeEvent(old, val);
}
}

View File

@@ -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;

View File

@@ -24,6 +24,7 @@ import com.maddyhome.idea.vim.command.CommandState
import com.maddyhome.idea.vim.group.visual.IdeaSelectionControl
import com.maddyhome.idea.vim.helper.StringHelper.parseKeys
import com.maddyhome.idea.vim.helper.VimBehaviorDiffers
import com.maddyhome.idea.vim.helper.subMode
import com.maddyhome.idea.vim.listener.VimListenerManager
import com.maddyhome.idea.vim.option.SelectModeOptionData
import org.jetbrains.plugins.ideavim.VimOptionDefaultAll
@@ -31,6 +32,7 @@ import org.jetbrains.plugins.ideavim.VimOptionTestCase
import org.jetbrains.plugins.ideavim.VimOptionTestConfiguration
import org.jetbrains.plugins.ideavim.VimTestOption
import org.jetbrains.plugins.ideavim.VimTestOptionType
import org.jetbrains.plugins.ideavim.waitAndAssert
import org.jetbrains.plugins.ideavim.waitAndAssertMode
/**
@@ -649,4 +651,61 @@ class IdeaVisualControlTest : VimOptionTestCase(SelectModeOptionData.name) {
waitAndAssertMode(myFixture, CommandState.Mode.VISUAL)
}
@VimOptionTestConfiguration(VimTestOption(SelectModeOptionData.name, VimTestOptionType.LIST, [""]))
fun `test control selection from line to char visual modes`() {
configureByText("""
A Discovery
I ${c}found it in a legendary land
all rocks and lavender and tufted grass,
""".trimIndent())
typeText(parseKeys("V"))
assertMode(CommandState.Mode.VISUAL)
assertSubMode(CommandState.SubMode.VISUAL_LINE)
myFixture.editor.selectionModel.setSelection(2, 5)
IdeaSelectionControl.controlNonVimSelectionChange(myFixture.editor)
waitAndAssert { myFixture.editor.subMode == CommandState.SubMode.VISUAL_CHARACTER }
assertMode(CommandState.Mode.VISUAL)
assertSubMode(CommandState.SubMode.VISUAL_CHARACTER)
assertCaretsColour()
}
/*
// [VERSION UPDATE] 191+ Open this test
@VimOptionTestConfiguration(VimTestOption(SelectModeOptionData.name, VimTestOptionType.LIST, [""]))
fun `test control selection from line to char visual modes in keep mode`() {
configureByText("""
A Discovery
I ${c}found it in a legendary land
all rocks and lavender and tufted grass,
""".trimIndent())
startDummyTemplate()
typeText(parseKeys("V"))
assertMode(CommandState.Mode.VISUAL)
assertSubMode(CommandState.SubMode.VISUAL_LINE)
myFixture.editor.selectionModel.setSelection(2, 5)
IdeaSelectionControl.controlNonVimSelectionChange(myFixture.editor)
waitAndAssert { myFixture.editor.subMode == CommandState.SubMode.VISUAL_CHARACTER }
assertMode(CommandState.Mode.VISUAL)
assertSubMode(CommandState.SubMode.VISUAL_CHARACTER)
assertCaretsColour()
}
private fun startDummyTemplate() {
TemplateManagerImpl.setTemplateTesting(myFixture.testRootDisposable)
val templateManager = TemplateManager.getInstance(myFixture.project)
val createdTemplate = templateManager.createTemplate("", "")
createdTemplate.addVariable(ConstantNode("1"), true)
templateManager.startTemplate(myFixture.editor, createdTemplate)
}
*/
}

View File

@@ -56,6 +56,11 @@ public class KeywordOptionTest extends VimTestCase {
assertEquals(",", option.values().get(0));
}
public void testSingleCommaIsAValueAsAppend() throws ExException {
option.append(",");
assertTrue(option.values().contains(","));
}
public void testSingleNegatedCommaIsAValue() throws ExException {
option.set("^,");
assertEquals("^,", option.values().get(0));