1
0
mirror of https://github.com/chylex/IntelliJ-IdeaVim.git synced 2025-08-22 10:25:28 +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 Please note that the quality of EAP versions may at times be way below even
usual beta standards. usual beta standards.
[To Be Released] 0.55, 2020-01-20
-------------- --------------
_Available since 0.54.1 EAP:_
**Features:** **Features:**
* Surround and Commentary extensions can be repeated with a dot command ([VIM-1118](https://youtrack.jetbrains.com/issue/VIM-1118)) * Surround and Commentary extensions support repeating with a dot command ([VIM-1118](https://youtrack.jetbrains.com/issue/VIM-1118))
* Support XDG settings standard ([VIM-664](https://youtrack.jetbrains.com/issue/VIM-664)) * Support XDG settings standard ([VIM-664](https://youtrack.jetbrains.com/issue/VIM-664))
* Add option to remove the status bar icon ([VIM-1847](https://youtrack.jetbrains.com/issue/VIM-1847)) * Add option to remove the status bar icon ([VIM-1847](https://youtrack.jetbrains.com/issue/VIM-1847))
@@ -43,7 +41,10 @@ _Available since 0.54.1 EAP:_
* [VIM-1853](https://youtrack.jetbrains.com/issue/VIM-1853) Fix marks for disposed projects * [VIM-1853](https://youtrack.jetbrains.com/issue/VIM-1853) Fix marks for disposed projects
* [VIM-1858](https://youtrack.jetbrains.com/issue/VIM-1858) Fix imap for autocomplete * [VIM-1858](https://youtrack.jetbrains.com/issue/VIM-1858) Fix imap for autocomplete
* [VIM-1362](https://youtrack.jetbrains.com/issue/VIM-1362) Search with confirm doesn't scroll down far enough * [VIM-1362](https://youtrack.jetbrains.com/issue/VIM-1362) Search with confirm doesn't scroll down far enough
* [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 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`. `/my/alternate/home/.ideavimrc` instead of `~/.ideavimrc`.
Alternatively, you can set up initialization commands using [XDG](https://specifications.freedesktop.org/basedir-spec/basedir-spec-latest.html) standard. Alternatively, you can set up initialization commands using [XDG](https://specifications.freedesktop.org/basedir-spec/basedir-spec-latest.html) standard.
Put your settings to `$XDG_CONFIG_HOME$/ideavim/ideavimrc` file. [To Be Released] Put your settings to `$XDG_CONFIG_HOME$/ideavim/ideavimrc` file.
Emulated Vim Plugins Emulated Vim Plugins

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 If true, join command will be performed via IDE
See wiki/`ideajoin` examples See wiki/`ideajoin` examples
`ideastatusbar` `ideastatusbar` Boolean (default true) [To Be Released] `ideastatusbar` `ideastatusbar` Boolean (default true)
If false, IdeaVim icon won't be shown in the status bar. If false, IdeaVim icon won't be shown in the status bar.
Works only from `~/.ideavimrc` after the IDE restart. Works only from `~/.ideavimrc` after the IDE restart.
`lookupkeys` `lookupkeys` List of strings
List of keys that should be processed by the IDE during the active lookup (autocompletion).
For example, <Tab> and <Enter> are used by the IDE to finish the lookup,
but <C-W> should be passed to IdeaVim.
Default value:
"<Tab>", "<Down>", "<Up>", "<Enter>", "<Left>", "<Right>",
"<C-Down>", "<C-Up>", "<PageUp>", "<PageDown>",
"<C-J>", "<C-Q>"
---------- ----------
[1] - cursor keys, <End>, <Home>, <PageUp> and <PageDown> [1] - cursor keys, <End>, <Home>, <PageUp> and <PageDown>

View File

@@ -3,8 +3,10 @@
<id>IdeaVIM</id> <id>IdeaVIM</id>
<change-notes><![CDATA[ <change-notes><![CDATA[
<ul> <ul>
<li>Fix `imap jk <ESC>` for the active autocompletion lookup</li> <li>Support dot command for Surround and Commentary extensions</li>
<li>Surround and Commentary extensions can be repeated with a dot command</li> <li>Support XDG settings standard</li>
<li>Add option to remove the status bar icon</li>
<li>Various bug fixes</li>
</ul> </ul>
<p>See also the complete <a href="https://github.com/JetBrains/ideavim/blob/master/CHANGES.md">changelog</a>.</p> <p>See also the complete <a href="https://github.com/JetBrains/ideavim/blob/master/CHANGES.md">changelog</a>.</p>
]]></change-notes> ]]></change-notes>
@@ -21,6 +23,7 @@
<vendor>JetBrains</vendor> <vendor>JetBrains</vendor>
<!-- Please search for "[VERSION UPDATE]" in project in case you update the since-build version --> <!-- 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"/> <idea-version since-build="183.4284.148"/>
<!-- Mark the plugin as compatible with RubyMine and other products based on the IntelliJ platform --> <!-- 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.intellij.ui.KeyStrokeAdapter
import com.maddyhome.idea.vim.KeyHandler import com.maddyhome.idea.vim.KeyHandler
import com.maddyhome.idea.vim.VimPlugin import com.maddyhome.idea.vim.VimPlugin
import com.maddyhome.idea.vim.handler.EditorActionHandlerBase.Companion.parseKeysSet
import com.maddyhome.idea.vim.helper.EditorDataContext import com.maddyhome.idea.vim.helper.EditorDataContext
import com.maddyhome.idea.vim.helper.EditorHelper import com.maddyhome.idea.vim.helper.EditorHelper
import com.maddyhome.idea.vim.helper.StringHelper import com.maddyhome.idea.vim.helper.StringHelper
@@ -42,7 +41,7 @@ import com.maddyhome.idea.vim.helper.inInsertMode
import com.maddyhome.idea.vim.helper.inNormalMode import com.maddyhome.idea.vim.helper.inNormalMode
import com.maddyhome.idea.vim.key.ShortcutOwner import com.maddyhome.idea.vim.key.ShortcutOwner
import com.maddyhome.idea.vim.listener.IdeaSpecifics.aceJumpActive import com.maddyhome.idea.vim.listener.IdeaSpecifics.aceJumpActive
import com.maddyhome.idea.vim.option.OptionsManager.lookupKeys import com.maddyhome.idea.vim.option.OptionsManager
import java.awt.event.InputEvent import java.awt.event.InputEvent
import java.awt.event.KeyEvent import java.awt.event.KeyEvent
import javax.swing.KeyStroke import javax.swing.KeyStroke
@@ -90,7 +89,7 @@ class VimShortcutKeyAction : AnAction(), DumbAware {
if (aceJumpActive()) return false if (aceJumpActive()) return false
val keyCode = keyStroke.keyCode val keyCode = keyStroke.keyCode
if (LookupManager.getActiveLookup(editor) != null) { if (LookupManager.getActiveLookup(editor) != null) {
return isEnabledForLookup(keyStroke) return LookupKeys.isEnabledForLookup(keyStroke)
} }
if (keyCode == KeyEvent.VK_ESCAPE) { if (keyCode == KeyEvent.VK_ESCAPE) {
return isEnabledForEscape(editor) return isEnabledForEscape(editor)
@@ -136,27 +135,6 @@ class VimShortcutKeyAction : AnAction(), DumbAware {
return fileEditorManager.allEditors.any { fileEditor -> editor == EditorUtil.getEditorEx(fileEditor) } return fileEditorManager.allEditors.any { fileEditor -> editor == EditorUtil.getEditorEx(fileEditor) }
} }
private fun isEnabledForLookup(keyStroke: KeyStroke): Boolean {
val notAllowedKeys = parseKeysSet(
"<Tab>", "<Down>", "<Up>", "<Enter>", "<Left>", "<Right>"
)
for (keys in notAllowedKeys) {
if (keyStroke == keys[0]) {
return false
}
}
// We allow users to set custom keys that will work with lookup in case devs forgot something
val popupActions = lookupKeys
val values = popupActions.values()
for (value in values) {
val keys = StringHelper.parseKeys(value)
if (keys.size >= 1 && keyStroke == keys[0]) {
return false
}
}
return true
}
private fun isShortcutConflict(keyStroke: KeyStroke): Boolean { private fun isShortcutConflict(keyStroke: KeyStroke): Boolean {
return VimPlugin.getKey().getKeymapConflicts(keyStroke).isNotEmpty() return VimPlugin.getKey().getKeymapConflicts(keyStroke).isNotEmpty()
} }
@@ -187,6 +165,29 @@ class VimShortcutKeyAction : AnAction(), DumbAware {
private fun getEditor(e: AnActionEvent): Editor? = e.getData(PlatformDataKeys.EDITOR) private fun getEditor(e: AnActionEvent): Editor? = e.getData(PlatformDataKeys.EDITOR)
/**
* Every time the key pressed with an active lookup, there is a decision:
* should this key be processed by IdeaVim, or by IDE. For example, dot and enter should be processed by IDE, but
* <C-W> by IdeaVim.
*
* The list of keys that should be processed by IDE is stored in [OptionsManager.lookupKeys]. So, we should search
* if the pressed key is presented in this list. The caches are used to speedup the process.
*/
private object LookupKeys {
private var parsedLookupKeys: Set<KeyStroke> = parseLookupKeys()
init {
OptionsManager.lookupKeys.addOptionChangeListener { _, _ ->
parsedLookupKeys = parseLookupKeys()
}
}
fun isEnabledForLookup(keyStroke: KeyStroke): Boolean = keyStroke !in parsedLookupKeys
private fun parseLookupKeys() = OptionsManager.lookupKeys.values()
.map { StringHelper.parseKeys(it) }.filter { it.isNotEmpty() }.map { it.first() }.toSet()
}
companion object { companion object {
@JvmField @JvmField
val VIM_ONLY_EDITOR_KEYS: Set<KeyStroke> = ImmutableSet.builder<KeyStroke>().addAll(getKeyStrokes(KeyEvent.VK_ENTER, 0)).addAll(getKeyStrokes(KeyEvent.VK_ESCAPE, 0)) val VIM_ONLY_EDITOR_KEYS: Set<KeyStroke> = ImmutableSet.builder<KeyStroke>().addAll(getKeyStrokes(KeyEvent.VK_ENTER, 0)).addAll(getKeyStrokes(KeyEvent.VK_ESCAPE, 0))

View File

@@ -22,7 +22,7 @@ import com.intellij.openapi.editor.Editor
import com.maddyhome.idea.vim.VimPlugin import com.maddyhome.idea.vim.VimPlugin
import com.maddyhome.idea.vim.command.Argument import com.maddyhome.idea.vim.command.Argument
import com.maddyhome.idea.vim.command.Command import com.maddyhome.idea.vim.command.Command
import com.maddyhome.idea.vim.ex.LineRange import com.maddyhome.idea.vim.ex.ranges.LineRange
import com.maddyhome.idea.vim.handler.ChangeEditorActionHandler import com.maddyhome.idea.vim.handler.ChangeEditorActionHandler
import com.maddyhome.idea.vim.helper.EditorHelper import com.maddyhome.idea.vim.helper.EditorHelper

View File

@@ -22,7 +22,7 @@ import com.intellij.openapi.editor.Editor
import com.maddyhome.idea.vim.VimPlugin import com.maddyhome.idea.vim.VimPlugin
import com.maddyhome.idea.vim.command.Argument import com.maddyhome.idea.vim.command.Argument
import com.maddyhome.idea.vim.command.Command import com.maddyhome.idea.vim.command.Command
import com.maddyhome.idea.vim.ex.LineRange import com.maddyhome.idea.vim.ex.ranges.LineRange
import com.maddyhome.idea.vim.handler.ChangeEditorActionHandler import com.maddyhome.idea.vim.handler.ChangeEditorActionHandler
class ChangeLastSearchReplaceAction : ChangeEditorActionHandler.SingleExecution() { class ChangeLastSearchReplaceAction : ChangeEditorActionHandler.SingleExecution() {

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.Register;
import com.maddyhome.idea.vim.common.TextRange; import com.maddyhome.idea.vim.common.TextRange;
import com.maddyhome.idea.vim.ex.handler.GotoLineHandler; import com.maddyhome.idea.vim.ex.handler.GotoLineHandler;
import com.maddyhome.idea.vim.ex.range.AbstractRange; import com.maddyhome.idea.vim.ex.ranges.Range;
import com.maddyhome.idea.vim.ex.ranges.Ranges;
import com.maddyhome.idea.vim.group.HistoryGroup; import com.maddyhome.idea.vim.group.HistoryGroup;
import com.maddyhome.idea.vim.helper.MessageHelper; import com.maddyhome.idea.vim.helper.MessageHelper;
import com.maddyhome.idea.vim.helper.Msg; import com.maddyhome.idea.vim.helper.Msg;
@@ -412,7 +413,7 @@ public class CommandParser {
reprocess = false; reprocess = false;
break; break;
case RANGE_DONE: // We have hit the end of a range - process it case RANGE_DONE: // We have hit the end of a range - process it
Range[] range = AbstractRange.createRange(location.toString(), offsetTotal, move); Range[] range = Range.createRange(location.toString(), offsetTotal, move);
if (range == null) { if (range == null) {
error = MessageHelper.message(Msg.e_badrange, Character.toString(ch)); error = MessageHelper.message(Msg.e_badrange, Character.toString(ch));
state = State.ERROR; state = State.ERROR;

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 * You should have received a copy of the GNU General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>. * along with this program. If not, see <https://www.gnu.org/licenses/>.
*/ */
package com.maddyhome.idea.vim.ex
package com.maddyhome.idea.vim.ex; open class ExException(s: String? = null) : Exception(s)
/** class InvalidCommandException(message: String, cmd: String?) : ExException("$message | $cmd")
* Exception class
*/
public class ExException extends Exception {
/**
* Constructs an <code>ExException</code> with no specified detail message.
*/
public ExException() {
}
/** class InvalidRangeException(s: String) : ExException(s)
* Constructs an <code>ExException</code> with the specified detail message.
* class MissingArgumentException : ExException()
* @param s the detail message.
*/ class MissingRangeException : ExException()
public ExException(String s) {
super(s); class NoArgumentAllowedException : ExException()
}
} class NoRangeAllowedException : ExException()

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.command.SelectionType
import com.maddyhome.idea.vim.common.TextRange import com.maddyhome.idea.vim.common.TextRange
import com.maddyhome.idea.vim.ex.* import com.maddyhome.idea.vim.ex.*
import com.maddyhome.idea.vim.ex.ranges.LineRange
import com.maddyhome.idea.vim.group.copy.PutData import com.maddyhome.idea.vim.group.copy.PutData
import com.maddyhome.idea.vim.helper.EditorHelper import com.maddyhome.idea.vim.helper.EditorHelper
import com.maddyhome.idea.vim.helper.MessageHelper import com.maddyhome.idea.vim.helper.MessageHelper

View File

@@ -24,6 +24,7 @@ import com.intellij.openapi.editor.Editor
import com.intellij.openapi.util.text.StringUtil import com.intellij.openapi.util.text.StringUtil
import com.maddyhome.idea.vim.VimPlugin import com.maddyhome.idea.vim.VimPlugin
import com.maddyhome.idea.vim.ex.* import com.maddyhome.idea.vim.ex.*
import com.maddyhome.idea.vim.ex.ranges.LineRange
import com.maddyhome.idea.vim.helper.inBlockSubMode import com.maddyhome.idea.vim.helper.inBlockSubMode
import java.util.* import java.util.*

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 * You should have received a copy of the GNU General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>. * along with this program. If not, see <https://www.gnu.org/licenses/>.
*/ */
package com.maddyhome.idea.vim.ex.ranges
package com.maddyhome.idea.vim.ex; class LineRange(startLine: Int, endLine: Int) {
/** @JvmField
* Exception class val startLine: Int
*/ @JvmField
public class InvalidCommandException extends ExException { val endLine: Int
public InvalidCommandException(String message, String cmd) {
super(message); init {
if (endLine >= startLine) {
this.startLine = startLine
this.endLine = endLine
} else {
this.startLine = endLine
this.endLine = startLine
}
} }
} }

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 class VimScriptParser {
public static final String VIMRC_FILE_NAME = "ideavimrc"; 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[] 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; public static final int BUFSIZE = 4096;
private static final Pattern EOL_SPLIT_PATTERN = Pattern.compile(" *(\r\n|\n)+ *"); private static final Pattern EOL_SPLIT_PATTERN = Pattern.compile(" *(\r\n|\n)+ *");
private static final Pattern DOUBLE_QUOTED_STRING = Pattern.compile("\"([^\"]*)\""); private static final Pattern DOUBLE_QUOTED_STRING = Pattern.compile("\"([^\"]*)\"");
@@ -55,7 +55,6 @@ public class VimScriptParser {
@Nullable @Nullable
public static File findIdeaVimRc() { public static File findIdeaVimRc() {
final String homeDirName = System.getProperty("user.home"); final String homeDirName = System.getProperty("user.home");
// Check whether file exists in home dir // Check whether file exists in home dir
if (homeDirName != null) { if (homeDirName != null) {
for (String fileName : HOME_VIMRC_PATHS) { for (String fileName : HOME_VIMRC_PATHS) {

View File

@@ -40,7 +40,7 @@ public class VimExtensionRegistrar {
registeredExtensions.add(name); registeredExtensions.add(name);
ToggleOption option = new ToggleOption(name, name, false); ToggleOption option = new ToggleOption(name, name, false);
option.addOptionChangeListener(event -> { option.addOptionChangeListener((oldValue, newValue) -> {
for (VimExtension extensionInListener : VimExtension.EP_NAME.getExtensionList()) { for (VimExtension extensionInListener : VimExtension.EP_NAME.getExtensionList()) {
if (name.equals(extensionInListener.getName())) { if (name.equals(extensionInListener.getName())) {
if (OptionsManager.INSTANCE.isSet(name)) { 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.IndentConfig;
import com.maddyhome.idea.vim.common.Register; import com.maddyhome.idea.vim.common.Register;
import com.maddyhome.idea.vim.common.TextRange; import com.maddyhome.idea.vim.common.TextRange;
import com.maddyhome.idea.vim.ex.LineRange; import com.maddyhome.idea.vim.ex.ranges.LineRange;
import com.maddyhome.idea.vim.group.visual.VimSelection; import com.maddyhome.idea.vim.group.visual.VimSelection;
import com.maddyhome.idea.vim.group.visual.VisualGroupKt; import com.maddyhome.idea.vim.group.visual.VisualGroupKt;
import com.maddyhome.idea.vim.group.visual.VisualModeHelperKt; import com.maddyhome.idea.vim.group.visual.VisualModeHelperKt;
@@ -1787,8 +1787,8 @@ public class ChangeGroup {
public boolean sortRange(@NotNull Editor editor, public boolean sortRange(@NotNull Editor editor,
@NotNull LineRange range, @NotNull LineRange range,
@NotNull Comparator<String> lineComparator) { @NotNull Comparator<String> lineComparator) {
final int startLine = range.getStartLine(); final int startLine = range.startLine;
final int endLine = range.getEndLine(); final int endLine = range.endLine;
final int count = endLine - startLine + 1; final int count = endLine - startLine + 1;
if (count < 2) { if (count < 2) {
return false; return false;

View File

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

View File

@@ -89,7 +89,7 @@ public class RegisterGroup {
public RegisterGroup() { public RegisterGroup() {
final ListOption clipboardOption = OptionsManager.INSTANCE.getClipboard(); final ListOption clipboardOption = OptionsManager.INSTANCE.getClipboard();
clipboardOption.addOptionChangeListener(event -> { clipboardOption.addOptionChangeListenerAndExecute((oldValue, newValue) -> {
if (clipboardOption.contains("unnamed")) { if (clipboardOption.contains("unnamed")) {
defaultRegister = '*'; defaultRegister = '*';
} }
@@ -221,7 +221,7 @@ public class RegisterGroup {
for (char d = '8'; d >= '1'; d--) { for (char d = '8'; d >= '1'; d--) {
Register t = registers.get(d); Register t = registers.get(d);
if (t != null) { if (t != null) {
t.rename((char)(d + 1)); t.setName((char)(d + 1));
registers.put((char)(d + 1), t); registers.put((char)(d + 1), t);
} }
} }
@@ -365,7 +365,7 @@ public class RegisterGroup {
res.add(register); res.add(register);
} }
} }
res.sort(new Register.KeySorter()); res.sort(Register.KeySorter.INSTANCE);
return res; 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.command.SelectionType;
import com.maddyhome.idea.vim.common.CharacterPosition; import com.maddyhome.idea.vim.common.CharacterPosition;
import com.maddyhome.idea.vim.common.TextRange; import com.maddyhome.idea.vim.common.TextRange;
import com.maddyhome.idea.vim.ex.LineRange; import com.maddyhome.idea.vim.ex.ranges.LineRange;
import com.maddyhome.idea.vim.helper.*; import com.maddyhome.idea.vim.helper.*;
import com.maddyhome.idea.vim.option.ListOption; import com.maddyhome.idea.vim.option.ListOption;
import com.maddyhome.idea.vim.option.OptionChangeListener; import com.maddyhome.idea.vim.option.OptionChangeListener;
@@ -63,12 +63,12 @@ import java.util.*;
public class SearchGroup { public class SearchGroup {
public SearchGroup() { public SearchGroup() {
final OptionsManager options = OptionsManager.INSTANCE; final OptionsManager options = OptionsManager.INSTANCE;
options.getHlsearch().addOptionChangeListener(event -> { options.getHlsearch().addOptionChangeListener((oldValue, newValue) -> {
resetShowSearchHighlight(); resetShowSearchHighlight();
forceUpdateSearchHighlights(); forceUpdateSearchHighlights();
}); });
final OptionChangeListener updateHighlightsIfVisible = event -> { final OptionChangeListener<Boolean> updateHighlightsIfVisible = (oldValue, newValue) -> {
if (showSearchHighlight) { if (showSearchHighlight) {
forceUpdateSearchHighlights(); forceUpdateSearchHighlights();
} }
@@ -377,7 +377,7 @@ public class SearchGroup {
} }
public int updateIncsearchHighlights(@NotNull Editor editor, @NotNull String pattern, boolean forwards, int caretOffset, @Nullable LineRange searchRange) { public int updateIncsearchHighlights(@NotNull Editor editor, @NotNull String pattern, boolean forwards, int caretOffset, @Nullable LineRange searchRange) {
final int searchStartOffset = searchRange != null ? EditorHelper.getLineStartOffset(editor, searchRange.getStartLine()) : caretOffset; final int searchStartOffset = searchRange != null ? EditorHelper.getLineStartOffset(editor, searchRange.startLine) : caretOffset;
final boolean showHighlights = OptionsManager.INSTANCE.getHlsearch().isSet(); final boolean showHighlights = OptionsManager.INSTANCE.getHlsearch().isSet();
return updateSearchHighlights(pattern, false, showHighlights, searchStartOffset, searchRange, forwards, false); return updateSearchHighlights(pattern, false, showHighlights, searchStartOffset, searchRange, forwards, false);
} }
@@ -410,8 +410,8 @@ public class SearchGroup {
} }
if (shouldAddAllSearchHighlights(editor, pattern, showHighlights)) { if (shouldAddAllSearchHighlights(editor, pattern, showHighlights)) {
final int startLine = searchRange == null ? 0 : searchRange.getStartLine(); final int startLine = searchRange == null ? 0 : searchRange.startLine;
final int endLine = searchRange == null ? -1 : searchRange.getEndLine(); final int endLine = searchRange == null ? -1 : searchRange.endLine;
List<TextRange> results = findAll(editor, pattern, startLine, endLine, shouldIgnoreCase(pattern, shouldIgnoreSmartCase)); List<TextRange> results = findAll(editor, pattern, startLine, endLine, shouldIgnoreCase(pattern, shouldIgnoreSmartCase));
if (!results.isEmpty()) { if (!results.isEmpty()) {
currentMatchOffset = findClosestMatch(editor, results, initialOffset, forwards); currentMatchOffset = findClosestMatch(editor, results, initialOffset, forwards);
@@ -1114,8 +1114,8 @@ public class SearchGroup {
cmd.inc(); cmd.inc();
} }
int line1 = range.getStartLine(); int line1 = range.startLine;
int line2 = range.getEndLine(); int line2 = range.endLine;
if (line1 < 0 || line2 < 0) { if (line1 < 0 || line2 < 0) {
return false; return false;

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() { private static String getPairChars() {
if (pairsChars == null) { if (pairsChars == null) {
ListOption lo = OptionsManager.INSTANCE.getMatchpairs(); ListOption lo = OptionsManager.INSTANCE.getMatchpairs();
pairsChars = parseOption(lo); lo.addOptionChangeListenerAndExecute((oldValue, newValue) -> pairsChars = parseOption(lo));
lo.addOptionChangeListener(event -> pairsChars = parseOption((ListOption)event.getOption()));
} }
return pairsChars; return pairsChars;

View File

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

View File

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

View File

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

View File

@@ -21,14 +21,13 @@ package com.maddyhome.idea.vim.option;
import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.NotNull;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.Comparator;
import java.util.List; 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 * 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. * when the value of the option changes.
*/ */
public abstract class Option { public abstract class Option<T> {
/** /**
* Create the option * Create the option
* *
@@ -46,16 +45,27 @@ public abstract class Option {
* *
* @param listener The listener * @param listener The listener
*/ */
public void addOptionChangeListener(OptionChangeListener listener) { public void addOptionChangeListener(OptionChangeListener<T> listener) {
listeners.add(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. * Removes the listener from the list.
* *
* @param listener The listener * @param listener The listener
*/ */
public void removeOptionChangeListener(OptionChangeListener listener) { public void removeOptionChangeListener(OptionChangeListener<T> listener) {
listeners.remove(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 * Lets all listeners know that the value has changed. Subclasses are responsible for calling this when their
* value changes. * value changes.
*/ */
protected void fireOptionChangeEvent() { protected void fireOptionChangeEvent(T oldValue, T newValue) {
OptionChangeEvent event = new OptionChangeEvent(this); for (OptionChangeListener<T> listener : listeners) {
for (OptionChangeListener listener : listeners) { listener.valueChange(oldValue, newValue);
listener.valueChange(event);
} }
} }
/** public abstract T getValue();
* 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);
}
}
protected final String name; protected final String name;
protected final String abbrev; 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; 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 * This interface is used for classes that wish to be notified whenever the value of an option has changed
*/ */
public interface OptionChangeListener extends EventListener { public interface OptionChangeListener<T> {
/** void valueChange(T oldValue, T newValue);
* The value of the option has changed.
*
* @param event The change event
*/
void valueChange(OptionChangeEvent event);
} }

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.hasVisualSelection
import com.maddyhome.idea.vim.helper.isBlockCaret import com.maddyhome.idea.vim.helper.isBlockCaret
import com.maddyhome.idea.vim.helper.mode import com.maddyhome.idea.vim.helper.mode
import com.maddyhome.idea.vim.helper.subMode
import com.maddyhome.idea.vim.listener.SelectionVimListenerSuppressor import com.maddyhome.idea.vim.listener.SelectionVimListenerSuppressor
import org.jetbrains.annotations.Contract import org.jetbrains.annotations.Contract
import java.util.* import java.util.*
@@ -44,8 +45,8 @@ import kotlin.math.min
object OptionsManager { object OptionsManager {
private val logger = Logger.getInstance(OptionsManager::class.java) private val logger = Logger.getInstance(OptionsManager::class.java)
private val options: MutableMap<String, Option> = mutableMapOf() private val options: MutableMap<String, Option<*>> = mutableMapOf()
private val abbrevs: 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 clipboard = addOption(ListOption(ClipboardOptionsData.name, ClipboardOptionsData.abbr, arrayOf(ClipboardOptionsData.ideaput, "autoselect,exclude:cons\\|linux"), null))
val digraph = addOption(ToggleOption("digraph", "dg", false)) val digraph = addOption(ToggleOption("digraph", "dg", false))
@@ -57,7 +58,7 @@ object OptionsManager {
val incsearch = addOption(ToggleOption("incsearch", "is", false)) val incsearch = addOption(ToggleOption("incsearch", "is", false))
val iskeyword = addOption(KeywordOption("iskeyword", "isk", arrayOf("@", "48-57", "_"))) val iskeyword = addOption(KeywordOption("iskeyword", "isk", arrayOf("@", "48-57", "_")))
val keymodel = addOption(KeyModelOptionData.option) val keymodel = addOption(KeyModelOptionData.option)
val lookupKeys = addOption(ListOption("lookupkeys", "lookupkeys", arrayOf(), null)) val lookupKeys = addOption(ListOption(LookupKeysData.name, LookupKeysData.name, LookupKeysData.defaultValues, null))
val matchpairs = addOption(ListOption("matchpairs", "mps", arrayOf("(:)", "{:}", "[:]"), ".:.")) val matchpairs = addOption(ListOption("matchpairs", "mps", arrayOf("(:)", "{:}", "[:]"), ".:."))
val more = addOption(ToggleOption("more", "more", true)) val more = addOption(ToggleOption("more", "more", true))
val nrformats = addOption(BoundListOption("nrformats", "nf", arrayOf("octal", "hex"), arrayOf("octal", "hex", "alpha"))) val nrformats = addOption(BoundListOption("nrformats", "nf", arrayOf("octal", "hex"), arrayOf("octal", "hex", "alpha")))
@@ -95,7 +96,7 @@ object OptionsManager {
/** /**
* Gets an option by the supplied name or short name. * 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: * 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 error: String? = null
var token = "" var token = ""
val tokenizer = StringTokenizer(args) val tokenizer = StringTokenizer(args)
val toShow = mutableListOf<Option>() val toShow = mutableListOf<Option<*>>()
while (tokenizer.hasMoreTokens()) { while (tokenizer.hasMoreTokens()) {
token = tokenizer.nextToken() token = tokenizer.nextToken()
// See if a space has been backslashed, if no get the rest of the text // 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 opts The list of options to display
* @param showIntro True if intro is displayed, false if not * @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 if (editor == null) return
val cols = mutableListOf<Option>() val cols = mutableListOf<Option<*>>()
val extra = mutableListOf<Option>() val extra = mutableListOf<Option<*>>()
for (option in opts) { for (option in opts) {
if (option.toString().length > 19) extra.add(option) else cols.add(option) if (option.toString().length > 19) extra.add(option) else cols.add(option)
} }
@@ -347,7 +348,7 @@ object OptionsManager {
} }
@Contract("_ -> param1") @Contract("_ -> param1")
fun <T : Option> addOption(option: T): T { fun <T : Option<*>> addOption(option: T): T {
options += option.name to option options += option.name to option
abbrevs += option.abbrev to option abbrevs += option.abbrev to option
return option return option
@@ -468,6 +469,13 @@ object IdeaRefactorMode {
editor.selectionModel.removeSelection() 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) { if (editor.mode.isBlockCaret) {
TemplateManagerImpl.getTemplateState(editor)?.currentVariableRange?.let { segmentRange -> 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 @Override
public boolean set(String val) { public boolean set(String val) {
String oldValue = getValue();
value = val; value = val;
fireOptionChangeEvent(); fireOptionChangeEvent(oldValue, getValue());
return true; return true;
} }
@@ -69,8 +70,9 @@ public class StringOption extends TextOption {
*/ */
@Override @Override
public boolean append(String val) { public boolean append(String val) {
String oldValue = getValue();
value += val; value += val;
fireOptionChangeEvent(); fireOptionChangeEvent(oldValue, getValue());
return true; return true;
} }
@@ -83,8 +85,9 @@ public class StringOption extends TextOption {
*/ */
@Override @Override
public boolean prepend(String val) { public boolean prepend(String val) {
String oldValue = getValue();
value = val + value; value = val + value;
fireOptionChangeEvent(); fireOptionChangeEvent(oldValue, getValue());
return true; return true;
} }
@@ -99,8 +102,9 @@ public class StringOption extends TextOption {
public boolean remove(@NotNull String val) { public boolean remove(@NotNull String val) {
int pos = value.indexOf(val); int pos = value.indexOf(val);
if (pos != -1) { if (pos != -1) {
String oldValue = getValue();
value = value.substring(0, pos) + value.substring(pos + val.length()); value = value.substring(0, pos) + value.substring(pos + val.length());
fireOptionChangeEvent(); fireOptionChangeEvent(oldValue, getValue());
return true; return true;
} }
@@ -124,8 +128,9 @@ public class StringOption extends TextOption {
@Override @Override
public void resetDefault() { public void resetDefault() {
if (!dflt.equals(value)) { if (!dflt.equals(value)) {
String oldValue = getValue();
value = dflt; value = dflt;
fireOptionChangeEvent(); fireOptionChangeEvent(oldValue, getValue());
} }
} }

View File

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

View File

@@ -23,7 +23,7 @@ import org.jetbrains.annotations.NotNull;
/** /**
* Represents a boolean option * Represents a boolean option
*/ */
public class ToggleOption extends Option { public class ToggleOption extends Option<Boolean> {
/** /**
* Creates the option * Creates the option
* *
@@ -38,12 +38,8 @@ public class ToggleOption extends Option {
this.value = dflt; this.value = dflt;
} }
/** @Override
* The option's value public Boolean getValue() {
*
* @return The value
*/
public boolean getValue() {
return value; return value;
} }
@@ -81,7 +77,7 @@ public class ToggleOption extends Option {
boolean old = value; boolean old = value;
value = val; value = val;
if (val != old) { 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.VimPlugin;
import com.maddyhome.idea.vim.ex.CommandParser; import com.maddyhome.idea.vim.ex.CommandParser;
import com.maddyhome.idea.vim.ex.ExCommand; import com.maddyhome.idea.vim.ex.ExCommand;
import com.maddyhome.idea.vim.ex.LineRange; import com.maddyhome.idea.vim.ex.ranges.LineRange;
import com.maddyhome.idea.vim.group.MotionGroup; import com.maddyhome.idea.vim.group.MotionGroup;
import com.maddyhome.idea.vim.helper.UiHelper; import com.maddyhome.idea.vim.helper.UiHelper;
import com.maddyhome.idea.vim.option.OptionsManager; import com.maddyhome.idea.vim.option.OptionsManager;

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.group.visual.IdeaSelectionControl
import com.maddyhome.idea.vim.helper.StringHelper.parseKeys import com.maddyhome.idea.vim.helper.StringHelper.parseKeys
import com.maddyhome.idea.vim.helper.VimBehaviorDiffers 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.listener.VimListenerManager
import com.maddyhome.idea.vim.option.SelectModeOptionData import com.maddyhome.idea.vim.option.SelectModeOptionData
import org.jetbrains.plugins.ideavim.VimOptionDefaultAll 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.VimOptionTestConfiguration
import org.jetbrains.plugins.ideavim.VimTestOption import org.jetbrains.plugins.ideavim.VimTestOption
import org.jetbrains.plugins.ideavim.VimTestOptionType import org.jetbrains.plugins.ideavim.VimTestOptionType
import org.jetbrains.plugins.ideavim.waitAndAssert
import org.jetbrains.plugins.ideavim.waitAndAssertMode import org.jetbrains.plugins.ideavim.waitAndAssertMode
/** /**
@@ -649,4 +651,61 @@ class IdeaVisualControlTest : VimOptionTestCase(SelectModeOptionData.name) {
waitAndAssertMode(myFixture, CommandState.Mode.VISUAL) 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)); assertEquals(",", option.values().get(0));
} }
public void testSingleCommaIsAValueAsAppend() throws ExException {
option.append(",");
assertTrue(option.values().contains(","));
}
public void testSingleNegatedCommaIsAValue() throws ExException { public void testSingleNegatedCommaIsAValue() throws ExException {
option.set("^,"); option.set("^,");
assertEquals("^,", option.values().get(0)); assertEquals("^,", option.values().get(0));