mirror of
https://github.com/chylex/IntelliJ-IdeaVim.git
synced 2025-08-17 16:31:45 +02:00
Compare commits
31 Commits
0.54.1-EAP
...
0.55
Author | SHA1 | Date | |
---|---|---|---|
![]() |
c1ff6e1498 | ||
![]() |
50c04ce71c | ||
![]() |
bc6ff6bc8e | ||
![]() |
93bcf2a7e8 | ||
![]() |
c3b503adff | ||
![]() |
ecdcbdda10 | ||
![]() |
b97c9a5ed0 | ||
![]() |
84a6843a7b | ||
![]() |
17eed7467c | ||
![]() |
310ffc849c | ||
![]() |
3e6756160a | ||
![]() |
563e809a2d | ||
![]() |
86ec3f3bcd | ||
![]() |
b4e0ec282f | ||
![]() |
cbf7dfabcb | ||
![]() |
b8eb55d965 | ||
![]() |
30662a8110 | ||
![]() |
69a273982f | ||
![]() |
eef1b25e9f | ||
![]() |
622de851fe | ||
![]() |
8cecb61d28 | ||
![]() |
77d8d27dfa | ||
![]() |
340f259b17 | ||
![]() |
5cf68a7f06 | ||
![]() |
0ae5abcd0c | ||
![]() |
b6cecb2125 | ||
![]() |
3aa3a9c9a8 | ||
![]() |
2c11ed43e4 | ||
![]() |
be0bee672e | ||
![]() |
b695e3c646 | ||
![]() |
33840dc5cd |
11
CHANGES.md
11
CHANGES.md
@@ -16,13 +16,11 @@ It is important to distinguish EAP from traditional pre-release software.
|
||||
Please note that the quality of EAP versions may at times be way below even
|
||||
usual beta standards.
|
||||
|
||||
[To Be Released]
|
||||
0.55, 2020-01-20
|
||||
--------------
|
||||
|
||||
_Available since 0.54.1 EAP:_
|
||||
|
||||
**Features:**
|
||||
* Surround and Commentary extensions can be repeated with a dot command ([VIM-1118](https://youtrack.jetbrains.com/issue/VIM-1118))
|
||||
* Surround and Commentary extensions support repeating with a dot command ([VIM-1118](https://youtrack.jetbrains.com/issue/VIM-1118))
|
||||
* Support XDG settings standard ([VIM-664](https://youtrack.jetbrains.com/issue/VIM-664))
|
||||
* Add option to remove the status bar icon ([VIM-1847](https://youtrack.jetbrains.com/issue/VIM-1847))
|
||||
|
||||
@@ -43,7 +41,10 @@ _Available since 0.54.1 EAP:_
|
||||
* [VIM-1853](https://youtrack.jetbrains.com/issue/VIM-1853) Fix marks for disposed projects
|
||||
* [VIM-1858](https://youtrack.jetbrains.com/issue/VIM-1858) Fix imap for autocomplete
|
||||
* [VIM-1362](https://youtrack.jetbrains.com/issue/VIM-1362) Search with confirm doesn't scroll down far enough
|
||||
|
||||
* [VIM-1875](https://youtrack.jetbrains.com/issue/VIM-1875) Fix `isk` in `~/.ideaivmrc`
|
||||
* [VIM-1874](https://youtrack.jetbrains.com/issue/VIM-1874) Fix `set clipboard=unnamed` execution from `~/.ideavimrc`
|
||||
* [VIM-1878](https://youtrack.jetbrains.com/issue/VIM-1878) Fix `c` command after extract method action
|
||||
* [VIM-1884](https://youtrack.jetbrains.com/issue/VIM-1884) Show quickDoc during popup with `CTRL-J`
|
||||
|
||||
0.54, 2019-11-20
|
||||
--------------
|
||||
|
@@ -128,7 +128,7 @@ have `-Duser.home=/my/alternate/home` then IdeaVim will source
|
||||
`/my/alternate/home/.ideavimrc` instead of `~/.ideavimrc`.
|
||||
|
||||
Alternatively, you can set up initialization commands using [XDG](https://specifications.freedesktop.org/basedir-spec/basedir-spec-latest.html) standard.
|
||||
Put your settings to `$XDG_CONFIG_HOME$/ideavim/ideavimrc` file. [To Be Released]
|
||||
Put your settings to `$XDG_CONFIG_HOME$/ideavim/ideavimrc` file.
|
||||
|
||||
|
||||
Emulated Vim Plugins
|
||||
|
@@ -103,10 +103,20 @@ The following `:set` commands can appear in `~/.ideavimrc` or be set manually in
|
||||
If true, join command will be performed via IDE
|
||||
See wiki/`ideajoin` examples
|
||||
|
||||
`ideastatusbar` `ideastatusbar` Boolean (default true) [To Be Released]
|
||||
`ideastatusbar` `ideastatusbar` Boolean (default true)
|
||||
|
||||
If false, IdeaVim icon won't be shown in the status bar.
|
||||
Works only from `~/.ideavimrc` after the IDE restart.
|
||||
|
||||
`lookupkeys` `lookupkeys` List of strings
|
||||
|
||||
List of keys that should be processed by the IDE during the active lookup (autocompletion).
|
||||
For example, <Tab> and <Enter> are used by the IDE to finish the lookup,
|
||||
but <C-W> should be passed to IdeaVim.
|
||||
Default value:
|
||||
"<Tab>", "<Down>", "<Up>", "<Enter>", "<Left>", "<Right>",
|
||||
"<C-Down>", "<C-Up>", "<PageUp>", "<PageDown>",
|
||||
"<C-J>", "<C-Q>"
|
||||
|
||||
----------
|
||||
[1] - cursor keys, <End>, <Home>, <PageUp> and <PageDown>
|
||||
|
@@ -3,8 +3,10 @@
|
||||
<id>IdeaVIM</id>
|
||||
<change-notes><![CDATA[
|
||||
<ul>
|
||||
<li>Fix `imap jk <ESC>` for the active autocompletion lookup</li>
|
||||
<li>Surround and Commentary extensions can be repeated with a dot command</li>
|
||||
<li>Support dot command for Surround and Commentary extensions</li>
|
||||
<li>Support XDG settings standard</li>
|
||||
<li>Add option to remove the status bar icon</li>
|
||||
<li>Various bug fixes</li>
|
||||
</ul>
|
||||
<p>See also the complete <a href="https://github.com/JetBrains/ideavim/blob/master/CHANGES.md">changelog</a>.</p>
|
||||
]]></change-notes>
|
||||
@@ -21,6 +23,7 @@
|
||||
<vendor>JetBrains</vendor>
|
||||
|
||||
<!-- Please search for "[VERSION UPDATE]" in project in case you update the since-build version -->
|
||||
<!-- Check for [Version Update] tag in YouTrack as well -->
|
||||
<idea-version since-build="183.4284.148"/>
|
||||
|
||||
<!-- Mark the plugin as compatible with RubyMine and other products based on the IntelliJ platform -->
|
||||
|
@@ -34,7 +34,6 @@ import com.intellij.openapi.util.Key
|
||||
import com.intellij.ui.KeyStrokeAdapter
|
||||
import com.maddyhome.idea.vim.KeyHandler
|
||||
import com.maddyhome.idea.vim.VimPlugin
|
||||
import com.maddyhome.idea.vim.handler.EditorActionHandlerBase.Companion.parseKeysSet
|
||||
import com.maddyhome.idea.vim.helper.EditorDataContext
|
||||
import com.maddyhome.idea.vim.helper.EditorHelper
|
||||
import com.maddyhome.idea.vim.helper.StringHelper
|
||||
@@ -42,7 +41,7 @@ import com.maddyhome.idea.vim.helper.inInsertMode
|
||||
import com.maddyhome.idea.vim.helper.inNormalMode
|
||||
import com.maddyhome.idea.vim.key.ShortcutOwner
|
||||
import com.maddyhome.idea.vim.listener.IdeaSpecifics.aceJumpActive
|
||||
import com.maddyhome.idea.vim.option.OptionsManager.lookupKeys
|
||||
import com.maddyhome.idea.vim.option.OptionsManager
|
||||
import java.awt.event.InputEvent
|
||||
import java.awt.event.KeyEvent
|
||||
import javax.swing.KeyStroke
|
||||
@@ -90,7 +89,7 @@ class VimShortcutKeyAction : AnAction(), DumbAware {
|
||||
if (aceJumpActive()) return false
|
||||
val keyCode = keyStroke.keyCode
|
||||
if (LookupManager.getActiveLookup(editor) != null) {
|
||||
return isEnabledForLookup(keyStroke)
|
||||
return LookupKeys.isEnabledForLookup(keyStroke)
|
||||
}
|
||||
if (keyCode == KeyEvent.VK_ESCAPE) {
|
||||
return isEnabledForEscape(editor)
|
||||
@@ -136,27 +135,6 @@ class VimShortcutKeyAction : AnAction(), DumbAware {
|
||||
return fileEditorManager.allEditors.any { fileEditor -> editor == EditorUtil.getEditorEx(fileEditor) }
|
||||
}
|
||||
|
||||
private fun isEnabledForLookup(keyStroke: KeyStroke): Boolean {
|
||||
val notAllowedKeys = parseKeysSet(
|
||||
"<Tab>", "<Down>", "<Up>", "<Enter>", "<Left>", "<Right>"
|
||||
)
|
||||
for (keys in notAllowedKeys) {
|
||||
if (keyStroke == keys[0]) {
|
||||
return false
|
||||
}
|
||||
}
|
||||
// We allow users to set custom keys that will work with lookup in case devs forgot something
|
||||
val popupActions = lookupKeys
|
||||
val values = popupActions.values()
|
||||
for (value in values) {
|
||||
val keys = StringHelper.parseKeys(value)
|
||||
if (keys.size >= 1 && keyStroke == keys[0]) {
|
||||
return false
|
||||
}
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
private fun isShortcutConflict(keyStroke: KeyStroke): Boolean {
|
||||
return VimPlugin.getKey().getKeymapConflicts(keyStroke).isNotEmpty()
|
||||
}
|
||||
@@ -187,6 +165,29 @@ class VimShortcutKeyAction : AnAction(), DumbAware {
|
||||
|
||||
private fun getEditor(e: AnActionEvent): Editor? = e.getData(PlatformDataKeys.EDITOR)
|
||||
|
||||
/**
|
||||
* Every time the key pressed with an active lookup, there is a decision:
|
||||
* should this key be processed by IdeaVim, or by IDE. For example, dot and enter should be processed by IDE, but
|
||||
* <C-W> by IdeaVim.
|
||||
*
|
||||
* The list of keys that should be processed by IDE is stored in [OptionsManager.lookupKeys]. So, we should search
|
||||
* if the pressed key is presented in this list. The caches are used to speedup the process.
|
||||
*/
|
||||
private object LookupKeys {
|
||||
private var parsedLookupKeys: Set<KeyStroke> = parseLookupKeys()
|
||||
|
||||
init {
|
||||
OptionsManager.lookupKeys.addOptionChangeListener { _, _ ->
|
||||
parsedLookupKeys = parseLookupKeys()
|
||||
}
|
||||
}
|
||||
|
||||
fun isEnabledForLookup(keyStroke: KeyStroke): Boolean = keyStroke !in parsedLookupKeys
|
||||
|
||||
private fun parseLookupKeys() = OptionsManager.lookupKeys.values()
|
||||
.map { StringHelper.parseKeys(it) }.filter { it.isNotEmpty() }.map { it.first() }.toSet()
|
||||
}
|
||||
|
||||
companion object {
|
||||
@JvmField
|
||||
val VIM_ONLY_EDITOR_KEYS: Set<KeyStroke> = ImmutableSet.builder<KeyStroke>().addAll(getKeyStrokes(KeyEvent.VK_ENTER, 0)).addAll(getKeyStrokes(KeyEvent.VK_ESCAPE, 0))
|
||||
|
@@ -22,7 +22,7 @@ import com.intellij.openapi.editor.Editor
|
||||
import com.maddyhome.idea.vim.VimPlugin
|
||||
import com.maddyhome.idea.vim.command.Argument
|
||||
import com.maddyhome.idea.vim.command.Command
|
||||
import com.maddyhome.idea.vim.ex.LineRange
|
||||
import com.maddyhome.idea.vim.ex.ranges.LineRange
|
||||
import com.maddyhome.idea.vim.handler.ChangeEditorActionHandler
|
||||
import com.maddyhome.idea.vim.helper.EditorHelper
|
||||
|
||||
|
@@ -22,7 +22,7 @@ import com.intellij.openapi.editor.Editor
|
||||
import com.maddyhome.idea.vim.VimPlugin
|
||||
import com.maddyhome.idea.vim.command.Argument
|
||||
import com.maddyhome.idea.vim.command.Command
|
||||
import com.maddyhome.idea.vim.ex.LineRange
|
||||
import com.maddyhome.idea.vim.ex.ranges.LineRange
|
||||
import com.maddyhome.idea.vim.handler.ChangeEditorActionHandler
|
||||
|
||||
class ChangeLastSearchReplaceAction : ChangeEditorActionHandler.SingleExecution() {
|
||||
|
@@ -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;
|
||||
}
|
||||
}
|
||||
}
|
63
src/com/maddyhome/idea/vim/command/SelectionType.kt
Normal file
63
src/com/maddyhome/idea/vim/command/SelectionType.kt
Normal 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
|
||||
}
|
||||
}
|
||||
}
|
@@ -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);
|
||||
}
|
||||
}
|
51
src/com/maddyhome/idea/vim/common/IndentConfig.kt
Normal file
51
src/com/maddyhome/idea/vim/common/IndentConfig.kt
Normal 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)
|
||||
}
|
||||
}
|
||||
}
|
@@ -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);
|
||||
}
|
||||
}
|
||||
}
|
75
src/com/maddyhome/idea/vim/common/Register.kt
Normal file
75
src/com/maddyhome/idea/vim/common/Register.kt
Normal 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)
|
||||
}
|
||||
}
|
@@ -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;
|
||||
}
|
107
src/com/maddyhome/idea/vim/common/TextRange.kt
Normal file
107
src/com/maddyhome/idea/vim/common/TextRange.kt
Normal 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()
|
||||
}
|
||||
}
|
@@ -28,7 +28,8 @@ import com.maddyhome.idea.vim.command.SelectionType;
|
||||
import com.maddyhome.idea.vim.common.Register;
|
||||
import com.maddyhome.idea.vim.common.TextRange;
|
||||
import com.maddyhome.idea.vim.ex.handler.GotoLineHandler;
|
||||
import com.maddyhome.idea.vim.ex.range.AbstractRange;
|
||||
import com.maddyhome.idea.vim.ex.ranges.Range;
|
||||
import com.maddyhome.idea.vim.ex.ranges.Ranges;
|
||||
import com.maddyhome.idea.vim.group.HistoryGroup;
|
||||
import com.maddyhome.idea.vim.helper.MessageHelper;
|
||||
import com.maddyhome.idea.vim.helper.Msg;
|
||||
@@ -412,7 +413,7 @@ public class CommandParser {
|
||||
reprocess = false;
|
||||
break;
|
||||
case RANGE_DONE: // We have hit the end of a range - process it
|
||||
Range[] range = AbstractRange.createRange(location.toString(), offsetTotal, move);
|
||||
Range[] range = Range.createRange(location.toString(), offsetTotal, move);
|
||||
if (range == null) {
|
||||
error = MessageHelper.message(Msg.e_badrange, Character.toString(ch));
|
||||
state = State.ERROR;
|
||||
|
@@ -1,122 +0,0 @@
|
||||
/*
|
||||
* IdeaVim - Vim emulator for IDEs based on the IntelliJ platform
|
||||
* Copyright (C) 2003-2019 The IdeaVim authors
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 2 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package com.maddyhome.idea.vim.ex;
|
||||
|
||||
import com.intellij.openapi.actionSystem.DataContext;
|
||||
import com.intellij.openapi.editor.Caret;
|
||||
import com.intellij.openapi.editor.Editor;
|
||||
import com.maddyhome.idea.vim.common.TextRange;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
|
||||
|
||||
public class ExCommand {
|
||||
public ExCommand(@NotNull Ranges ranges, @NotNull String command, @NotNull String argument) {
|
||||
this.ranges = ranges;
|
||||
this.argument = argument;
|
||||
this.command = command;
|
||||
}
|
||||
|
||||
public int getLine(@NotNull Editor editor) {
|
||||
return ranges.getLine(editor);
|
||||
}
|
||||
|
||||
public int getLine(@NotNull Editor editor, @NotNull Caret caret, @NotNull DataContext context) {
|
||||
return ranges.getLine(editor, caret, context);
|
||||
}
|
||||
|
||||
public int getCount(@NotNull Editor editor, DataContext context, int defaultCount, boolean checkCount) {
|
||||
int count = -1;
|
||||
if (checkCount) {
|
||||
count = getCountArgument();
|
||||
}
|
||||
|
||||
int res = ranges.getCount(editor, context, count);
|
||||
if (res == -1) {
|
||||
res = defaultCount;
|
||||
}
|
||||
|
||||
return res;
|
||||
}
|
||||
|
||||
public int getCount(@NotNull Editor editor, @NotNull Caret caret, @NotNull DataContext context, int defaultCount,
|
||||
boolean checkCount) {
|
||||
final int count = ranges.getCount(editor, caret, context, checkCount ? getCountArgument() : -1);
|
||||
if (count == -1) return defaultCount;
|
||||
return count;
|
||||
}
|
||||
|
||||
@NotNull
|
||||
public LineRange getLineRange(@NotNull Editor editor) {
|
||||
return ranges.getLineRange(editor, -1);
|
||||
}
|
||||
|
||||
public LineRange getLineRange(@NotNull Editor editor, @NotNull Caret caret, @NotNull DataContext context) {
|
||||
return ranges.getLineRange(editor, caret, context, -1);
|
||||
}
|
||||
|
||||
@NotNull
|
||||
public TextRange getTextRange(@NotNull Editor editor, DataContext context, boolean checkCount) {
|
||||
int count = -1;
|
||||
if (checkCount) {
|
||||
count = getCountArgument();
|
||||
}
|
||||
|
||||
return ranges.getTextRange(editor, context, count);
|
||||
}
|
||||
|
||||
public TextRange getTextRange(@NotNull Editor editor, @NotNull Caret caret, @NotNull DataContext context,
|
||||
boolean checkCount) {
|
||||
return ranges.getTextRange(editor, caret, context, checkCount ? getCountArgument() : -1);
|
||||
}
|
||||
|
||||
private int getCountArgument() {
|
||||
try {
|
||||
return Integer.parseInt(argument);
|
||||
}
|
||||
catch (NumberFormatException e) {
|
||||
return -1;
|
||||
}
|
||||
}
|
||||
|
||||
@NotNull
|
||||
public String getCommand() {
|
||||
return command;
|
||||
}
|
||||
|
||||
@NotNull
|
||||
public String getArgument() {
|
||||
return argument;
|
||||
}
|
||||
|
||||
public void setArgument(@NotNull String argument) {
|
||||
this.argument = argument;
|
||||
}
|
||||
|
||||
@NotNull
|
||||
public Ranges getRanges() {
|
||||
return ranges;
|
||||
}
|
||||
|
||||
@NotNull
|
||||
private final Ranges ranges;
|
||||
@NotNull
|
||||
private final String command;
|
||||
@NotNull
|
||||
private String argument;
|
||||
}
|
62
src/com/maddyhome/idea/vim/ex/ExCommand.kt
Normal file
62
src/com/maddyhome/idea/vim/ex/ExCommand.kt
Normal file
@@ -0,0 +1,62 @@
|
||||
/*
|
||||
* IdeaVim - Vim emulator for IDEs based on the IntelliJ platform
|
||||
* Copyright (C) 2003-2019 The IdeaVim authors
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 2 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
package com.maddyhome.idea.vim.ex
|
||||
|
||||
import com.intellij.openapi.actionSystem.DataContext
|
||||
import com.intellij.openapi.editor.Caret
|
||||
import com.intellij.openapi.editor.Editor
|
||||
import com.maddyhome.idea.vim.common.TextRange
|
||||
import com.maddyhome.idea.vim.ex.ranges.LineRange
|
||||
import com.maddyhome.idea.vim.ex.ranges.Ranges
|
||||
|
||||
class ExCommand(val ranges: Ranges, val command: String, var argument: String) {
|
||||
|
||||
fun getLine(editor: Editor): Int = ranges.getLine(editor)
|
||||
|
||||
fun getLine(editor: Editor, caret: Caret, context: DataContext): Int = ranges.getLine(editor, caret, context)
|
||||
|
||||
fun getCount(editor: Editor, context: DataContext?, defaultCount: Int, checkCount: Boolean): Int {
|
||||
val count = if (checkCount) countArgument else -1
|
||||
|
||||
val res = ranges.getCount(editor, count)
|
||||
return if (res == -1) defaultCount else res
|
||||
}
|
||||
|
||||
fun getCount(editor: Editor, caret: Caret, context: DataContext, defaultCount: Int, checkCount: Boolean): Int {
|
||||
val count = ranges.getCount(editor, caret, context, if (checkCount) countArgument else -1)
|
||||
return if (count == -1) defaultCount else count
|
||||
}
|
||||
|
||||
fun getLineRange(editor: Editor): LineRange = ranges.getLineRange(editor, -1)
|
||||
|
||||
fun getLineRange(editor: Editor, caret: Caret, context: DataContext): LineRange {
|
||||
return ranges.getLineRange(editor, caret, context, -1)
|
||||
}
|
||||
|
||||
fun getTextRange(editor: Editor, context: DataContext?, checkCount: Boolean): TextRange {
|
||||
val count = if (checkCount) countArgument else -1
|
||||
return ranges.getTextRange(editor, context, count)
|
||||
}
|
||||
|
||||
fun getTextRange(editor: Editor, caret: Caret, context: DataContext, checkCount: Boolean): TextRange {
|
||||
return ranges.getTextRange(editor, caret, context, if (checkCount) countArgument else -1)
|
||||
}
|
||||
|
||||
private val countArgument: Int
|
||||
get() = argument.toIntOrNull() ?: -1
|
||||
}
|
@@ -15,25 +15,18 @@
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
package com.maddyhome.idea.vim.ex
|
||||
|
||||
package com.maddyhome.idea.vim.ex;
|
||||
open class ExException(s: String? = null) : Exception(s)
|
||||
|
||||
/**
|
||||
* Exception class
|
||||
*/
|
||||
public class ExException extends Exception {
|
||||
/**
|
||||
* Constructs an <code>ExException</code> with no specified detail message.
|
||||
*/
|
||||
public ExException() {
|
||||
}
|
||||
class InvalidCommandException(message: String, cmd: String?) : ExException("$message | $cmd")
|
||||
|
||||
/**
|
||||
* Constructs an <code>ExException</code> with the specified detail message.
|
||||
*
|
||||
* @param s the detail message.
|
||||
*/
|
||||
public ExException(String s) {
|
||||
super(s);
|
||||
}
|
||||
}
|
||||
class InvalidRangeException(s: String) : ExException(s)
|
||||
|
||||
class MissingArgumentException : ExException()
|
||||
|
||||
class MissingRangeException : ExException()
|
||||
|
||||
class NoArgumentAllowedException : ExException()
|
||||
|
||||
class NoRangeAllowedException : ExException()
|
@@ -1,67 +0,0 @@
|
||||
/*
|
||||
* IdeaVim - Vim emulator for IDEs based on the IntelliJ platform
|
||||
* Copyright (C) 2003-2019 The IdeaVim authors
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 2 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package com.maddyhome.idea.vim.ex;
|
||||
|
||||
import com.intellij.openapi.application.ApplicationManager;
|
||||
import com.intellij.openapi.editor.Editor;
|
||||
import com.maddyhome.idea.vim.helper.UserDataManager;
|
||||
import com.maddyhome.idea.vim.ui.ExOutputPanel;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
|
||||
/**
|
||||
* @author vlan
|
||||
*/
|
||||
public class ExOutputModel {
|
||||
@NotNull private final Editor myEditor;
|
||||
@Nullable private String myText;
|
||||
|
||||
private ExOutputModel(@NotNull Editor editor) {
|
||||
myEditor = editor;
|
||||
}
|
||||
|
||||
@NotNull
|
||||
public static ExOutputModel getInstance(@NotNull Editor editor) {
|
||||
ExOutputModel model = UserDataManager.getVimExOutput(editor);
|
||||
if (model == null) {
|
||||
model = new ExOutputModel(editor);
|
||||
UserDataManager.setVimExOutput(editor, model);
|
||||
}
|
||||
return model;
|
||||
}
|
||||
|
||||
public void output(@NotNull String text) {
|
||||
myText = text;
|
||||
if (!ApplicationManager.getApplication().isUnitTestMode()) {
|
||||
ExOutputPanel.getInstance(myEditor).setText(text);
|
||||
}
|
||||
}
|
||||
|
||||
public void clear() {
|
||||
myText = null;
|
||||
if (!ApplicationManager.getApplication().isUnitTestMode()) {
|
||||
ExOutputPanel.getInstance(myEditor).deactivate(false);
|
||||
}
|
||||
}
|
||||
|
||||
@Nullable
|
||||
public String getText() {
|
||||
return myText;
|
||||
}
|
||||
}
|
57
src/com/maddyhome/idea/vim/ex/ExOutputModel.kt
Normal file
57
src/com/maddyhome/idea/vim/ex/ExOutputModel.kt
Normal file
@@ -0,0 +1,57 @@
|
||||
/*
|
||||
* IdeaVim - Vim emulator for IDEs based on the IntelliJ platform
|
||||
* Copyright (C) 2003-2019 The IdeaVim authors
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 2 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
package com.maddyhome.idea.vim.ex
|
||||
|
||||
import com.intellij.openapi.application.ApplicationManager
|
||||
import com.intellij.openapi.editor.Editor
|
||||
import com.maddyhome.idea.vim.helper.vimExOutput
|
||||
import com.maddyhome.idea.vim.ui.ExOutputPanel
|
||||
|
||||
/**
|
||||
* @author vlan
|
||||
*/
|
||||
class ExOutputModel private constructor(private val myEditor: Editor) {
|
||||
var text: String? = null
|
||||
private set
|
||||
|
||||
fun output(text: String) {
|
||||
this.text = text
|
||||
if (!ApplicationManager.getApplication().isUnitTestMode) {
|
||||
ExOutputPanel.getInstance(myEditor).setText(text)
|
||||
}
|
||||
}
|
||||
|
||||
fun clear() {
|
||||
text = null
|
||||
if (!ApplicationManager.getApplication().isUnitTestMode) {
|
||||
ExOutputPanel.getInstance(myEditor).deactivate(false)
|
||||
}
|
||||
}
|
||||
|
||||
companion object {
|
||||
@JvmStatic
|
||||
fun getInstance(editor: Editor): ExOutputModel {
|
||||
var model = editor.vimExOutput
|
||||
if (model == null) {
|
||||
model = ExOutputModel(editor)
|
||||
editor.vimExOutput = model
|
||||
}
|
||||
return model
|
||||
}
|
||||
}
|
||||
}
|
@@ -1,39 +0,0 @@
|
||||
/*
|
||||
* IdeaVim - Vim emulator for IDEs based on the IntelliJ platform
|
||||
* Copyright (C) 2003-2019 The IdeaVim authors
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 2 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package com.maddyhome.idea.vim.ex;
|
||||
|
||||
/**
|
||||
* Exception class
|
||||
*/
|
||||
public class InvalidArgumentException extends ExException {
|
||||
/**
|
||||
* Constructs an <code>InvalidArgumentException</code> with no specified detail message.
|
||||
*/
|
||||
public InvalidArgumentException() {
|
||||
}
|
||||
|
||||
/**
|
||||
* Constructs an <code>InvalidArgumentException</code> with the specified detail message.
|
||||
*
|
||||
* @param s the detail message.
|
||||
*/
|
||||
public InvalidArgumentException(String s) {
|
||||
super(s);
|
||||
}
|
||||
}
|
@@ -1,39 +0,0 @@
|
||||
/*
|
||||
* IdeaVim - Vim emulator for IDEs based on the IntelliJ platform
|
||||
* Copyright (C) 2003-2019 The IdeaVim authors
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 2 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package com.maddyhome.idea.vim.ex;
|
||||
|
||||
/**
|
||||
* Exception class
|
||||
*/
|
||||
public class InvalidRangeException extends ExException {
|
||||
/**
|
||||
* Constructs an <code>InvalidRangeException</code> with no specified detail message.
|
||||
*/
|
||||
public InvalidRangeException() {
|
||||
}
|
||||
|
||||
/**
|
||||
* Constructs an <code>InvalidRangeException</code> with the specified detail message.
|
||||
*
|
||||
* @param s the detail message.
|
||||
*/
|
||||
public InvalidRangeException(String s) {
|
||||
super(s);
|
||||
}
|
||||
}
|
@@ -1,44 +0,0 @@
|
||||
/*
|
||||
* IdeaVim - Vim emulator for IDEs based on the IntelliJ platform
|
||||
* Copyright (C) 2003-2019 The IdeaVim authors
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 2 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package com.maddyhome.idea.vim.ex;
|
||||
|
||||
|
||||
public class LineRange {
|
||||
public LineRange(int startLine, int endLine) {
|
||||
if (endLine >= startLine) {
|
||||
this.startLine = startLine;
|
||||
this.endLine = endLine;
|
||||
}
|
||||
else {
|
||||
this.startLine = endLine;
|
||||
this.endLine = startLine;
|
||||
}
|
||||
}
|
||||
|
||||
public int getStartLine() {
|
||||
return startLine;
|
||||
}
|
||||
|
||||
public int getEndLine() {
|
||||
return endLine;
|
||||
}
|
||||
|
||||
private final int startLine;
|
||||
private final int endLine;
|
||||
}
|
@@ -1,39 +0,0 @@
|
||||
/*
|
||||
* IdeaVim - Vim emulator for IDEs based on the IntelliJ platform
|
||||
* Copyright (C) 2003-2019 The IdeaVim authors
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 2 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package com.maddyhome.idea.vim.ex;
|
||||
|
||||
/**
|
||||
* Exception class
|
||||
*/
|
||||
public class MissingArgumentException extends ExException {
|
||||
/**
|
||||
* Constructs an <code>InvalidArgumentException</code> with no specified detail message.
|
||||
*/
|
||||
public MissingArgumentException() {
|
||||
}
|
||||
|
||||
/**
|
||||
* Constructs an <code>InvalidArgumentException</code> with the specified detail message.
|
||||
*
|
||||
* @param s the detail message.
|
||||
*/
|
||||
public MissingArgumentException(String s) {
|
||||
super(s);
|
||||
}
|
||||
}
|
@@ -1,39 +0,0 @@
|
||||
/*
|
||||
* IdeaVim - Vim emulator for IDEs based on the IntelliJ platform
|
||||
* Copyright (C) 2003-2019 The IdeaVim authors
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 2 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package com.maddyhome.idea.vim.ex;
|
||||
|
||||
/**
|
||||
* Exception class
|
||||
*/
|
||||
public class MissingRangeException extends ExException {
|
||||
/**
|
||||
* Constructs an <code>InvalidArgumentException</code> with no specified detail message.
|
||||
*/
|
||||
public MissingRangeException() {
|
||||
}
|
||||
|
||||
/**
|
||||
* Constructs an <code>InvalidArgumentException</code> with the specified detail message.
|
||||
*
|
||||
* @param s the detail message.
|
||||
*/
|
||||
public MissingRangeException(String s) {
|
||||
super(s);
|
||||
}
|
||||
}
|
@@ -1,39 +0,0 @@
|
||||
/*
|
||||
* IdeaVim - Vim emulator for IDEs based on the IntelliJ platform
|
||||
* Copyright (C) 2003-2019 The IdeaVim authors
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 2 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package com.maddyhome.idea.vim.ex;
|
||||
|
||||
/**
|
||||
* Exception class
|
||||
*/
|
||||
public class NoArgumentAllowedException extends ExException {
|
||||
/**
|
||||
* Constructs an <code>InvalidArgumentException</code> with no specified detail message.
|
||||
*/
|
||||
public NoArgumentAllowedException() {
|
||||
}
|
||||
|
||||
/**
|
||||
* Constructs an <code>InvalidArgumentException</code> with the specified detail message.
|
||||
*
|
||||
* @param s the detail message.
|
||||
*/
|
||||
public NoArgumentAllowedException(String s) {
|
||||
super(s);
|
||||
}
|
||||
}
|
@@ -1,39 +0,0 @@
|
||||
/*
|
||||
* IdeaVim - Vim emulator for IDEs based on the IntelliJ platform
|
||||
* Copyright (C) 2003-2019 The IdeaVim authors
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 2 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package com.maddyhome.idea.vim.ex;
|
||||
|
||||
/**
|
||||
* Exception class
|
||||
*/
|
||||
public class NoRangeAllowedException extends ExException {
|
||||
/**
|
||||
* Constructs an <code>NoRangeAllowedException</code> with no specified detail message.
|
||||
*/
|
||||
public NoRangeAllowedException() {
|
||||
}
|
||||
|
||||
/**
|
||||
* Constructs an <code>NoRangeAllowedException</code> with the specified detail message.
|
||||
*
|
||||
* @param s the detail message.
|
||||
*/
|
||||
public NoRangeAllowedException(String s) {
|
||||
super(s);
|
||||
}
|
||||
}
|
@@ -1,49 +0,0 @@
|
||||
/*
|
||||
* IdeaVim - Vim emulator for IDEs based on the IntelliJ platform
|
||||
* Copyright (C) 2003-2019 The IdeaVim authors
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 2 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package com.maddyhome.idea.vim.ex;
|
||||
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
|
||||
|
||||
public class ParseResult {
|
||||
@NotNull private final Ranges ranges;
|
||||
@NotNull private final String command;
|
||||
@NotNull private final String argument;
|
||||
|
||||
public ParseResult(@NotNull Ranges ranges, @NotNull String command, @NotNull String argument) {
|
||||
this.ranges = ranges;
|
||||
this.argument = argument;
|
||||
this.command = command;
|
||||
}
|
||||
|
||||
@NotNull
|
||||
public String getCommand() {
|
||||
return command;
|
||||
}
|
||||
|
||||
@NotNull
|
||||
public String getArgument() {
|
||||
return argument;
|
||||
}
|
||||
|
||||
@NotNull
|
||||
public Ranges getRanges() {
|
||||
return ranges;
|
||||
}
|
||||
}
|
@@ -1,46 +0,0 @@
|
||||
/*
|
||||
* IdeaVim - Vim emulator for IDEs based on the IntelliJ platform
|
||||
* Copyright (C) 2003-2019 The IdeaVim authors
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 2 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package com.maddyhome.idea.vim.ex;
|
||||
|
||||
import com.intellij.openapi.editor.Caret;
|
||||
import com.intellij.openapi.editor.Editor;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
|
||||
/**
|
||||
* Represents an Ex command range
|
||||
*/
|
||||
public interface Range {
|
||||
/**
|
||||
* Get the line number this range represents
|
||||
*
|
||||
* @param editor The editor to get the line for
|
||||
* @param lastZero True if the last line set represents before the start of the false
|
||||
* @return The zero based logical line in the editor that the range represents
|
||||
*/
|
||||
int getLine(Editor editor, boolean lastZero);
|
||||
|
||||
int getLine(@NotNull Editor editor, @NotNull Caret caret, boolean lastZero);
|
||||
|
||||
/**
|
||||
* Should the cursor be moved to this range's line?
|
||||
*
|
||||
* @return True if cursor should be moved, false if not
|
||||
*/
|
||||
boolean isMove();
|
||||
}
|
@@ -1,288 +0,0 @@
|
||||
/*
|
||||
* IdeaVim - Vim emulator for IDEs based on the IntelliJ platform
|
||||
* Copyright (C) 2003-2019 The IdeaVim authors
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 2 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package com.maddyhome.idea.vim.ex;
|
||||
|
||||
import com.intellij.openapi.actionSystem.DataContext;
|
||||
import com.intellij.openapi.editor.Caret;
|
||||
import com.intellij.openapi.editor.Editor;
|
||||
import com.maddyhome.idea.vim.VimPlugin;
|
||||
import com.maddyhome.idea.vim.common.TextRange;
|
||||
import com.maddyhome.idea.vim.ex.range.AbstractRange;
|
||||
import com.maddyhome.idea.vim.group.MotionGroup;
|
||||
import com.maddyhome.idea.vim.helper.EditorHelper;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* Handles the set of range values entered as part of an Ex command.
|
||||
*/
|
||||
public class Ranges {
|
||||
/**
|
||||
* Create the empty range list
|
||||
*/
|
||||
public Ranges() {
|
||||
ranges = new ArrayList<>();
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds a range to the list
|
||||
*
|
||||
* @param range The list of ranges to append to the current list
|
||||
*/
|
||||
public void addRange(@NotNull Range[] range) {
|
||||
Collections.addAll(ranges, range);
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the number of ranges in the list
|
||||
*
|
||||
* @return The range count
|
||||
*/
|
||||
public int size() {
|
||||
return ranges.size();
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the default line to be used by this range if no range was actually given by the user. -1 is used to
|
||||
* mean the current line.
|
||||
*
|
||||
* @param line The line or -1 for current line
|
||||
*/
|
||||
public void setDefaultLine(int line) {
|
||||
defaultLine = line;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the line of the last range specified in the range list
|
||||
*
|
||||
* @param editor The editor to get the line for
|
||||
* @return The line number represented by the range
|
||||
*/
|
||||
public int getLine(@NotNull Editor editor) {
|
||||
processRange(editor);
|
||||
|
||||
return endLine;
|
||||
}
|
||||
|
||||
public int getLine(@NotNull Editor editor, @NotNull Caret caret, @NotNull DataContext context) {
|
||||
processRange(editor, caret, context);
|
||||
return endLine;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the start line number the range represents
|
||||
*
|
||||
* @param editor The editor to get the line number for
|
||||
* @return The starting line number
|
||||
*/
|
||||
public int getFirstLine(@NotNull Editor editor) {
|
||||
processRange(editor);
|
||||
|
||||
return startLine;
|
||||
}
|
||||
|
||||
public int getFirstLine(@NotNull Editor editor, @NotNull Caret caret, @NotNull DataContext context) {
|
||||
processRange(editor, caret, context);
|
||||
return startLine;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the count for an Ex command. This is either an explicit count enter at the end of the command or the
|
||||
* end of the specified range.
|
||||
*
|
||||
* @param editor The editor to get the count for
|
||||
* @param context The data context
|
||||
* @param count The count given at the end of the command or -1 if no such count (use end line)
|
||||
* @return count if count != -1, else return end line of range
|
||||
*/
|
||||
public int getCount(@NotNull Editor editor, DataContext context, int count) {
|
||||
if (count == -1) {
|
||||
return getLine(editor);
|
||||
}
|
||||
else {
|
||||
return count;
|
||||
}
|
||||
}
|
||||
|
||||
public int getCount(@NotNull Editor editor, @NotNull Caret caret, @NotNull DataContext context, int count) {
|
||||
if (count == -1) return getLine(editor, caret, context);
|
||||
return count;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the line range represented by this range. If a count is given, the range is the range end line through
|
||||
* count-1 lines. If no count is given (-1), the range is the range given by the user.
|
||||
*
|
||||
* @param editor The editor to get the range for
|
||||
* @param count The count given at the end of the command or -1 if no such count
|
||||
* @return The line range
|
||||
*/
|
||||
@NotNull
|
||||
public LineRange getLineRange(@NotNull Editor editor, int count) {
|
||||
processRange(editor);
|
||||
int end;
|
||||
int start;
|
||||
if (count == -1) {
|
||||
end = endLine;
|
||||
start = startLine;
|
||||
}
|
||||
else {
|
||||
start = endLine;
|
||||
end = start + count - 1;
|
||||
}
|
||||
|
||||
return new LineRange(start, end);
|
||||
}
|
||||
|
||||
public LineRange getLineRange(@NotNull Editor editor, @NotNull Caret caret, @NotNull DataContext context, int count) {
|
||||
processRange(editor, caret, context);
|
||||
if (count == -1) return new LineRange(startLine, endLine);
|
||||
return new LineRange(endLine, endLine + count - 1);
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the text range represented by this range. If a count is given, the range is the range end line through
|
||||
* count-1 lines. If no count is given (-1), the range is the range given by the user. The text range is based
|
||||
* on the line range but this is character based from the start of the first line to the end of the last line.
|
||||
*
|
||||
* @param editor The editor to get the range for
|
||||
* @param context The data context
|
||||
* @param count The count given at the end of the command or -1 if no such count
|
||||
* @return The text range
|
||||
*/
|
||||
@NotNull
|
||||
public TextRange getTextRange(@NotNull Editor editor, DataContext context, int count) {
|
||||
LineRange lr = getLineRange(editor, count);
|
||||
int start = EditorHelper.getLineStartOffset(editor, lr.getStartLine());
|
||||
int end = EditorHelper.getLineEndOffset(editor, lr.getEndLine(), true) + 1;
|
||||
|
||||
return new TextRange(start, Math.min(end, EditorHelper.getFileSize(editor)));
|
||||
}
|
||||
|
||||
public TextRange getTextRange(@NotNull Editor editor, @NotNull Caret caret, @NotNull DataContext context, int count) {
|
||||
final LineRange lineRange = getLineRange(editor, caret, context, count);
|
||||
|
||||
final int start = EditorHelper.getLineStartOffset(editor, lineRange.getStartLine());
|
||||
final int end = EditorHelper.getLineEndOffset(editor, lineRange.getEndLine(), true) + 1;
|
||||
|
||||
return new TextRange(start, Math.min(end, EditorHelper.getFileSize(editor)));
|
||||
}
|
||||
|
||||
/**
|
||||
* Helper method to get the text range for the current cursor line
|
||||
*
|
||||
* @param editor The editor to get the range for
|
||||
* @param context The data context
|
||||
* @return The range of the current line
|
||||
*/
|
||||
@NotNull
|
||||
public static TextRange getCurrentLineRange(@NotNull Editor editor, DataContext context) {
|
||||
Ranges ranges = new Ranges();
|
||||
|
||||
return ranges.getTextRange(editor, context, -1);
|
||||
}
|
||||
|
||||
/**
|
||||
* Helper method to get the text range for the current file
|
||||
*
|
||||
* @param editor The editor to get the range for
|
||||
* @param context The data context
|
||||
* @return The range of the current file
|
||||
*/
|
||||
@NotNull
|
||||
public static TextRange getFileTextRange(@NotNull Editor editor, DataContext context) {
|
||||
Ranges ranges = new Ranges();
|
||||
final Range[] range = AbstractRange.createRange("%", 0, false);
|
||||
if (range != null) {
|
||||
ranges.addRange(range);
|
||||
}
|
||||
|
||||
return ranges.getTextRange(editor, context, -1);
|
||||
}
|
||||
|
||||
/**
|
||||
* Processes the list of ranges and calculates the start and end lines of the range
|
||||
*
|
||||
* @param editor The editor to get the lines for
|
||||
*/
|
||||
private void processRange(@NotNull Editor editor) {
|
||||
// Already done
|
||||
if (done) return;
|
||||
|
||||
// Start with the range being the current line
|
||||
startLine = defaultLine == -1 ? editor.getCaretModel().getLogicalPosition().line : defaultLine;
|
||||
endLine = startLine;
|
||||
boolean lastZero = false;
|
||||
// Now process each range, moving the cursor if appropriate
|
||||
for (Range range : ranges) {
|
||||
startLine = endLine;
|
||||
endLine = range.getLine(editor, lastZero);
|
||||
if (range.isMove()) {
|
||||
MotionGroup.moveCaret(editor, editor.getCaretModel().getPrimaryCaret(),
|
||||
VimPlugin.getMotion().moveCaretToLine(editor, endLine, editor.getCaretModel().getPrimaryCaret()));
|
||||
}
|
||||
// Did that last range represent the start of the file?
|
||||
lastZero = (endLine < 0);
|
||||
count++;
|
||||
}
|
||||
|
||||
// If only one range given, make the start and end the same
|
||||
if (count == 1) {
|
||||
startLine = endLine;
|
||||
}
|
||||
|
||||
done = true;
|
||||
}
|
||||
|
||||
private void processRange(@NotNull Editor editor, @NotNull Caret caret, @NotNull DataContext context) {
|
||||
startLine = defaultLine == -1 ? caret.getLogicalPosition().line : defaultLine;
|
||||
endLine = startLine;
|
||||
boolean lastZero = false;
|
||||
for (Range range : ranges) {
|
||||
startLine = endLine;
|
||||
endLine = range.getLine(editor, caret, lastZero);
|
||||
|
||||
if (range.isMove()) MotionGroup.moveCaret(editor, caret, VimPlugin.getMotion().moveCaretToLine(editor, endLine, editor.getCaretModel().getPrimaryCaret()));
|
||||
|
||||
lastZero = endLine < 0;
|
||||
++count;
|
||||
}
|
||||
|
||||
if (count == 1) startLine = endLine;
|
||||
|
||||
count = 0;
|
||||
}
|
||||
|
||||
@NotNull
|
||||
public String toString() {
|
||||
|
||||
return "Ranges[ranges=" + ranges + "]";
|
||||
}
|
||||
|
||||
private int startLine = 0;
|
||||
private int endLine = 0;
|
||||
private int count = 0;
|
||||
private int defaultLine = -1;
|
||||
private boolean done = false;
|
||||
@NotNull
|
||||
private final List<Range> ranges;
|
||||
}
|
@@ -25,6 +25,7 @@ import com.maddyhome.idea.vim.VimPlugin
|
||||
import com.maddyhome.idea.vim.command.SelectionType
|
||||
import com.maddyhome.idea.vim.common.TextRange
|
||||
import com.maddyhome.idea.vim.ex.*
|
||||
import com.maddyhome.idea.vim.ex.ranges.LineRange
|
||||
import com.maddyhome.idea.vim.group.copy.PutData
|
||||
import com.maddyhome.idea.vim.helper.EditorHelper
|
||||
import com.maddyhome.idea.vim.helper.MessageHelper
|
||||
|
@@ -24,6 +24,7 @@ import com.intellij.openapi.editor.Editor
|
||||
import com.intellij.openapi.util.text.StringUtil
|
||||
import com.maddyhome.idea.vim.VimPlugin
|
||||
import com.maddyhome.idea.vim.ex.*
|
||||
import com.maddyhome.idea.vim.ex.ranges.LineRange
|
||||
import com.maddyhome.idea.vim.helper.inBlockSubMode
|
||||
import java.util.*
|
||||
|
||||
|
@@ -1,154 +0,0 @@
|
||||
/*
|
||||
* IdeaVim - Vim emulator for IDEs based on the IntelliJ platform
|
||||
* Copyright (C) 2003-2019 The IdeaVim authors
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 2 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package com.maddyhome.idea.vim.ex.range;
|
||||
|
||||
import com.intellij.openapi.editor.Caret;
|
||||
import com.intellij.openapi.editor.Editor;
|
||||
import com.maddyhome.idea.vim.ex.Range;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
|
||||
/**
|
||||
* Base for all Ex command ranges
|
||||
*/
|
||||
public abstract class AbstractRange implements Range {
|
||||
/**
|
||||
* Factory method used to create an appropriate range based on the range text
|
||||
*
|
||||
* @param str The range text
|
||||
* @param offset Any offset specified after the range
|
||||
* @param move True if cursor should be moved to range line
|
||||
* @return The ranges appropriate to the text
|
||||
*/
|
||||
@Nullable
|
||||
public static Range[] createRange(@NotNull String str, int offset, boolean move) {
|
||||
// Current line
|
||||
if (str.equals(".") || str.length() == 0) {
|
||||
return new Range[]{new LineNumberRange(offset, move)};
|
||||
}
|
||||
// All lines
|
||||
else if (str.equals("%")) {
|
||||
return new Range[]{
|
||||
new LineNumberRange(0, 0, move),
|
||||
new LineNumberRange(LineNumberRange.LAST_LINE, offset, move)
|
||||
};
|
||||
}
|
||||
// Last line
|
||||
else if (str.equals("$")) {
|
||||
return new Range[]{new LineNumberRange(LineNumberRange.LAST_LINE, offset, move)};
|
||||
}
|
||||
// Mark like
|
||||
else if (str.startsWith("'") && str.length() == 2) {
|
||||
return new Range[]{new MarkRange(str.charAt(1), offset, move)};
|
||||
}
|
||||
// Pattern
|
||||
else if (str.startsWith("/") || str.startsWith("\\/") || str.startsWith("\\&")) {
|
||||
return new Range[]{new SearchRange(str, offset, move)};
|
||||
}
|
||||
// Pattern
|
||||
else if (str.startsWith("?") || str.startsWith("\\?")) {
|
||||
return new Range[]{new SearchRange(str, offset, move)};
|
||||
}
|
||||
// Specific line number (1 based)
|
||||
else {
|
||||
try {
|
||||
int line = Integer.parseInt(str) - 1;
|
||||
|
||||
return new Range[]{new LineNumberRange(line, offset, move)};
|
||||
}
|
||||
catch (NumberFormatException e) {
|
||||
// Ignore - we'll send back bad range later.
|
||||
}
|
||||
}
|
||||
|
||||
// User entered an invalid range.
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Create the range
|
||||
*
|
||||
* @param offset The line offset
|
||||
* @param move True if cursor moved
|
||||
*/
|
||||
public AbstractRange(int offset, boolean move) {
|
||||
this.offset = offset;
|
||||
this.move = move;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the line offset
|
||||
*
|
||||
* @return The line offset
|
||||
*/
|
||||
protected int getOffset() {
|
||||
return offset;
|
||||
}
|
||||
|
||||
/**
|
||||
* Should the cursor move
|
||||
*
|
||||
* @return True if cursor should move, false if not
|
||||
*/
|
||||
@Override
|
||||
public boolean isMove() {
|
||||
return move;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the line number (0 based) specificied by this range. Includes the offset.
|
||||
*
|
||||
* @param editor The editor to get the line for
|
||||
* @param lastZero True if last line was set to start of file
|
||||
* @return The zero based line number, -1 if unable to get the line number
|
||||
*/
|
||||
@Override
|
||||
public int getLine(Editor editor, boolean lastZero) {
|
||||
int line = getRangeLine(editor, lastZero);
|
||||
|
||||
return line + offset;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getLine(@NotNull Editor editor, @NotNull Caret caret, boolean lastZero) {
|
||||
if (offset == 0) return getRangeLine(editor, lastZero);
|
||||
|
||||
return getRangeLine(editor, caret, lastZero) + offset;
|
||||
}
|
||||
|
||||
@NotNull
|
||||
public String toString() {
|
||||
return "AbstractRange" + "{offset=" + offset + ", move=" + move + '}';
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the line number specified by this range without regard to any offset.
|
||||
*
|
||||
* @param editor The editor to get the line for
|
||||
* @param lastZero True if last line was set to start of file
|
||||
* @return The zero based line number, -1 if inable to get the line number
|
||||
*/
|
||||
protected abstract int getRangeLine(Editor editor, boolean lastZero);
|
||||
|
||||
protected abstract int getRangeLine(@NotNull Editor editor, @NotNull Caret caret,
|
||||
boolean lastZero);
|
||||
|
||||
protected final int offset;
|
||||
protected final boolean move;
|
||||
}
|
@@ -1,92 +0,0 @@
|
||||
/*
|
||||
* IdeaVim - Vim emulator for IDEs based on the IntelliJ platform
|
||||
* Copyright (C) 2003-2019 The IdeaVim authors
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 2 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package com.maddyhome.idea.vim.ex.range;
|
||||
|
||||
import com.intellij.openapi.editor.Caret;
|
||||
import com.intellij.openapi.editor.Editor;
|
||||
import com.maddyhome.idea.vim.helper.EditorHelper;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
|
||||
/**
|
||||
* Represents a specific line, the current line, or the last line of a file
|
||||
*/
|
||||
public class LineNumberRange extends AbstractRange {
|
||||
public static final int CURRENT_LINE = -99999999;
|
||||
public static final int LAST_LINE = -99999998;
|
||||
|
||||
/**
|
||||
* Create a range for the current line
|
||||
*
|
||||
* @param offset The range offset
|
||||
* @param move True if cursor should be moved
|
||||
*/
|
||||
public LineNumberRange(int offset, boolean move) {
|
||||
super(offset, move);
|
||||
|
||||
this.line = CURRENT_LINE;
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a range for the given line
|
||||
*
|
||||
* @param offset The range offset
|
||||
* @param move True if cursor should be moved
|
||||
*/
|
||||
public LineNumberRange(int line, int offset, boolean move) {
|
||||
super(offset, move);
|
||||
|
||||
this.line = line;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the line number specified by this range without regard to any offset.
|
||||
*
|
||||
* @param editor The editor to get the line for
|
||||
* @param lastZero True if last line was set to start of file
|
||||
* @return The zero based line number, -1 for start of file
|
||||
*/
|
||||
@Override
|
||||
protected int getRangeLine(@NotNull Editor editor, boolean lastZero) {
|
||||
if (line == CURRENT_LINE) {
|
||||
line = editor.getCaretModel().getLogicalPosition().line;
|
||||
}
|
||||
else if (line == LAST_LINE) {
|
||||
line = EditorHelper.getLineCount(editor) - 1;
|
||||
}
|
||||
|
||||
return line;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected int getRangeLine(@NotNull Editor editor, @NotNull Caret caret,
|
||||
boolean lastZero) {
|
||||
if (line == LAST_LINE) line = EditorHelper.getLineCount(editor) - 1;
|
||||
else line = caret.getLogicalPosition().line;
|
||||
|
||||
return line;
|
||||
}
|
||||
|
||||
@NotNull
|
||||
public String toString() {
|
||||
|
||||
return "LineNumberRange[" + "line=" + line + ", " + super.toString() + "]";
|
||||
}
|
||||
|
||||
private int line;
|
||||
}
|
@@ -1,76 +0,0 @@
|
||||
/*
|
||||
* IdeaVim - Vim emulator for IDEs based on the IntelliJ platform
|
||||
* Copyright (C) 2003-2019 The IdeaVim authors
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 2 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package com.maddyhome.idea.vim.ex.range;
|
||||
|
||||
import com.intellij.openapi.editor.Caret;
|
||||
import com.intellij.openapi.editor.Editor;
|
||||
import com.maddyhome.idea.vim.VimPlugin;
|
||||
import com.maddyhome.idea.vim.common.Mark;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
|
||||
/**
|
||||
* Represents the line specified by a mark
|
||||
*/
|
||||
public class MarkRange extends AbstractRange {
|
||||
/**
|
||||
* Create the mark range
|
||||
*
|
||||
* @param mark The mark name
|
||||
* @param offset The range offset
|
||||
* @param move True if cursor should be moved
|
||||
*/
|
||||
public MarkRange(char mark, int offset, boolean move) {
|
||||
super(offset, move);
|
||||
|
||||
this.mark = mark;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the line number specified by this range without regard to any offset.
|
||||
*
|
||||
* @param editor The editor to get the line for
|
||||
* @param lastZero True if last line was set to start of file
|
||||
* @return The zero based line number, -1 if there is no such mark set in the file
|
||||
*/
|
||||
@Override
|
||||
public int getRangeLine(@NotNull Editor editor, boolean lastZero) {
|
||||
Mark mark = VimPlugin.getMark().getFileMark(editor, this.mark);
|
||||
|
||||
if (mark != null) {
|
||||
return mark.getLogicalLine();
|
||||
}
|
||||
else {
|
||||
return -1;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
protected int getRangeLine(@NotNull Editor editor, @NotNull Caret caret,
|
||||
boolean lastZero) {
|
||||
return getRangeLine(editor, lastZero);
|
||||
}
|
||||
|
||||
@NotNull
|
||||
public String toString() {
|
||||
|
||||
return "MarkRange[" + "mark=" + mark + ", " + super.toString() + "]";
|
||||
}
|
||||
|
||||
private final char mark;
|
||||
}
|
@@ -1,170 +0,0 @@
|
||||
/*
|
||||
* IdeaVim - Vim emulator for IDEs based on the IntelliJ platform
|
||||
* Copyright (C) 2003-2019 The IdeaVim authors
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 2 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package com.maddyhome.idea.vim.ex.range;
|
||||
|
||||
import com.intellij.openapi.diagnostic.Logger;
|
||||
import com.intellij.openapi.editor.Caret;
|
||||
import com.intellij.openapi.editor.Editor;
|
||||
import com.maddyhome.idea.vim.VimPlugin;
|
||||
import com.maddyhome.idea.vim.command.CommandFlags;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.EnumSet;
|
||||
import java.util.List;
|
||||
import java.util.StringTokenizer;
|
||||
|
||||
/**
|
||||
* Represents a range given by a search pattern. The pattern can be '\\/', '\\?', '\\&', /{pattern}/,
|
||||
* or ?{pattern}?. The last two can be repeated 0 or more times after any of the others.
|
||||
*/
|
||||
public class SearchRange extends AbstractRange {
|
||||
/**
|
||||
* Create the pattern range
|
||||
*
|
||||
* @param pattern The text of the pattern. Each subpattern must be separated by the nul character (\\u0000)
|
||||
* @param offset The range offset
|
||||
* @param move True if the cursor should be moved
|
||||
*/
|
||||
public SearchRange(String pattern, int offset, boolean move) {
|
||||
super(offset, move);
|
||||
setPattern(pattern);
|
||||
}
|
||||
|
||||
/**
|
||||
* Parses the pattern into a list of subpatterns and flags
|
||||
*
|
||||
* @param pattern The full search pattern
|
||||
*/
|
||||
private void setPattern(String pattern) {
|
||||
if (logger.isDebugEnabled()) {
|
||||
logger.debug("pattern=" + pattern);
|
||||
}
|
||||
StringTokenizer tok = new StringTokenizer(pattern, "\u0000");
|
||||
while (tok.hasMoreTokens()) {
|
||||
String pat = tok.nextToken();
|
||||
switch (pat) {
|
||||
case "\\/":
|
||||
patterns.add(VimPlugin.getSearch().getLastSearch());
|
||||
flags.add(EnumSet.of(CommandFlags.FLAG_SEARCH_FWD));
|
||||
break;
|
||||
case "\\?":
|
||||
patterns.add(VimPlugin.getSearch().getLastSearch());
|
||||
flags.add(EnumSet.of(CommandFlags.FLAG_SEARCH_REV));
|
||||
break;
|
||||
case "\\&":
|
||||
patterns.add(VimPlugin.getSearch().getLastPattern());
|
||||
flags.add(EnumSet.of(CommandFlags.FLAG_SEARCH_FWD));
|
||||
break;
|
||||
default:
|
||||
if (pat.charAt(0) == '/') {
|
||||
flags.add(EnumSet.of(CommandFlags.FLAG_SEARCH_FWD));
|
||||
}
|
||||
else {
|
||||
flags.add(EnumSet.of(CommandFlags.FLAG_SEARCH_REV));
|
||||
}
|
||||
|
||||
pat = pat.substring(1);
|
||||
if (pat.charAt(pat.length() - 1) == pat.charAt(0)) {
|
||||
pat = pat.substring(0, pat.length() - 1);
|
||||
}
|
||||
patterns.add(pat);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the line number specified by this range without regard to any offset.
|
||||
*
|
||||
* @param editor The editor to get the line for
|
||||
* @param lastZero True if last line was set to start of file
|
||||
* @return The zero based line number, -1 if the text was not found
|
||||
*/
|
||||
@Override
|
||||
protected int getRangeLine(@NotNull Editor editor, boolean lastZero) {
|
||||
// Each subsequent pattern is searched for starting in the line after the previous search match
|
||||
int line = editor.getCaretModel().getLogicalPosition().line;
|
||||
int pos = -1;
|
||||
for (int i = 0; i < patterns.size(); i++) {
|
||||
String pattern = patterns.get(i);
|
||||
EnumSet<CommandFlags> flag = flags.get(i);
|
||||
if (flag.contains(CommandFlags.FLAG_SEARCH_FWD) && !lastZero) {
|
||||
pos = VimPlugin.getMotion().moveCaretToLineEnd(editor, line, true);
|
||||
}
|
||||
else {
|
||||
pos = VimPlugin.getMotion().moveCaretToLineStart(editor, line);
|
||||
}
|
||||
|
||||
pos = VimPlugin.getSearch().search(editor, pattern, pos, 1, flag);
|
||||
if (pos == -1) {
|
||||
break;
|
||||
}
|
||||
else {
|
||||
line = editor.offsetToLogicalPosition(pos).line;
|
||||
}
|
||||
}
|
||||
|
||||
if (pos != -1) {
|
||||
return line;
|
||||
}
|
||||
else {
|
||||
return -1;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
protected int getRangeLine(@NotNull Editor editor, @NotNull Caret caret,
|
||||
boolean lastZero) {
|
||||
int line = caret.getLogicalPosition().line;
|
||||
int offset = -1;
|
||||
for (int i = 0; i < patterns.size(); i++) {
|
||||
final String pattern = patterns.get(i);
|
||||
final EnumSet<CommandFlags> flag = flags.get(i);
|
||||
|
||||
offset = VimPlugin.getSearch().search(editor, pattern, getSearchOffset(editor, line, flag, lastZero), 1, flag);
|
||||
if (offset == -1) break;
|
||||
|
||||
line = editor.offsetToLogicalPosition(offset).line;
|
||||
}
|
||||
|
||||
return offset != -1 ? line : -1;
|
||||
}
|
||||
|
||||
private int getSearchOffset(@NotNull Editor editor, int line, EnumSet<CommandFlags> flag, boolean lastZero) {
|
||||
if (flag.contains(CommandFlags.FLAG_SEARCH_FWD) && !lastZero) {
|
||||
return VimPlugin.getMotion().moveCaretToLineEnd(editor, line, true);
|
||||
}
|
||||
|
||||
return VimPlugin.getMotion().moveCaretToLineStart(editor, line);
|
||||
}
|
||||
|
||||
@NotNull
|
||||
public String toString() {
|
||||
|
||||
return "SearchRange[" + "patterns=" + patterns + ", " + super.toString() + "]";
|
||||
}
|
||||
|
||||
@NotNull
|
||||
private final List<String> patterns = new ArrayList<>();
|
||||
@NotNull
|
||||
private final List<EnumSet<CommandFlags>> flags = new ArrayList<>();
|
||||
|
||||
private static final Logger logger = Logger.getInstance(SearchRange.class.getName());
|
||||
}
|
296
src/com/maddyhome/idea/vim/ex/ranges/ExRanges.kt
Normal file
296
src/com/maddyhome/idea/vim/ex/ranges/ExRanges.kt
Normal file
@@ -0,0 +1,296 @@
|
||||
/*
|
||||
* IdeaVim - Vim emulator for IDEs based on the IntelliJ platform
|
||||
* Copyright (C) 2003-2019 The IdeaVim authors
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 2 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
package com.maddyhome.idea.vim.ex.ranges
|
||||
|
||||
import com.intellij.openapi.diagnostic.debug
|
||||
import com.intellij.openapi.diagnostic.logger
|
||||
import com.intellij.openapi.editor.Caret
|
||||
import com.intellij.openapi.editor.Editor
|
||||
import com.maddyhome.idea.vim.VimPlugin
|
||||
import com.maddyhome.idea.vim.command.CommandFlags
|
||||
import com.maddyhome.idea.vim.helper.EditorHelper
|
||||
import com.maddyhome.idea.vim.helper.enumSetOf
|
||||
import java.util.*
|
||||
|
||||
/**
|
||||
* Base for all Ex command ranges
|
||||
*/
|
||||
sealed class Range(
|
||||
// Line offset
|
||||
protected val offset: Int,
|
||||
val isMove: Boolean
|
||||
) {
|
||||
/**
|
||||
* Gets the line number (0 based) specificied by this range. Includes the offset.
|
||||
*
|
||||
* @param editor The editor to get the line for
|
||||
* @param lastZero True if last line was set to start of file
|
||||
* @return The zero based line number, -1 if unable to get the line number
|
||||
*/
|
||||
fun getLine(editor: Editor, lastZero: Boolean): Int {
|
||||
val line = getRangeLine(editor, lastZero)
|
||||
return line + offset
|
||||
}
|
||||
|
||||
fun getLine(editor: Editor, caret: Caret, lastZero: Boolean): Int {
|
||||
return if (offset == 0) getRangeLine(editor, lastZero) else getRangeLine(editor, caret, lastZero) + offset
|
||||
}
|
||||
|
||||
override fun toString(): String = "Range{offset=$offset, move=$isMove}"
|
||||
|
||||
/**
|
||||
* Gets the line number specified by this range without regard to any offset.
|
||||
*
|
||||
* @param editor The editor to get the line for
|
||||
* @param lastZero True if last line was set to start of file
|
||||
* @return The zero based line number, -1 if inable to get the line number
|
||||
*/
|
||||
protected abstract fun getRangeLine(editor: Editor, lastZero: Boolean): Int
|
||||
|
||||
protected abstract fun getRangeLine(editor: Editor, caret: Caret, lastZero: Boolean): Int
|
||||
|
||||
companion object {
|
||||
/**
|
||||
* Factory method used to create an appropriate range based on the range text
|
||||
*
|
||||
* @param str The range text
|
||||
* @param offset Any offset specified after the range
|
||||
* @param move True if cursor should be moved to range line
|
||||
* @return The ranges appropriate to the text
|
||||
*/
|
||||
@JvmStatic
|
||||
fun createRange(str: String, offset: Int, move: Boolean): Array<Range>? {
|
||||
// Current line
|
||||
if (str.isEmpty() || str == ".") {
|
||||
return arrayOf(LineNumberRange(offset, move))
|
||||
} else if (str == "%") {
|
||||
return arrayOf(
|
||||
LineNumberRange(0, 0, move),
|
||||
LineNumberRange(LineNumberRange.LAST_LINE, offset, move)
|
||||
)
|
||||
} else if (str == "$") {
|
||||
return arrayOf(LineNumberRange(LineNumberRange.LAST_LINE, offset, move))
|
||||
} else if (str.startsWith("'") && str.length == 2) {
|
||||
return arrayOf(MarkRange(str[1], offset, move))
|
||||
} else if (str.startsWith("/") || str.startsWith("\\/") || str.startsWith("\\&")) {
|
||||
return arrayOf(SearchRange(str, offset, move))
|
||||
} else if (str.startsWith("?") || str.startsWith("\\?")) {
|
||||
return arrayOf(SearchRange(str, offset, move))
|
||||
} else {
|
||||
try {
|
||||
val line = str.toInt() - 1
|
||||
return arrayOf(LineNumberRange(line, offset, move))
|
||||
} catch (e: NumberFormatException) { // Ignore - we'll send back bad range later.
|
||||
}
|
||||
}
|
||||
// User entered an invalid range.
|
||||
return null
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Represents a specific line, the current line, or the last line of a file
|
||||
*/
|
||||
class LineNumberRange : Range {
|
||||
/**
|
||||
* Create a range for the current line
|
||||
*
|
||||
* @param offset The range offset
|
||||
* @param move True if cursor should be moved
|
||||
*/
|
||||
constructor(offset: Int, move: Boolean) : super(offset, move) {
|
||||
line = CURRENT_LINE
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a range for the given line
|
||||
*
|
||||
* @param offset The range offset
|
||||
* @param move True if cursor should be moved
|
||||
*/
|
||||
constructor(line: Int, offset: Int, move: Boolean) : super(offset, move) {
|
||||
this.line = line
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the line number specified by this range without regard to any offset.
|
||||
*
|
||||
* @param editor The editor to get the line for
|
||||
* @param lastZero True if last line was set to start of file
|
||||
* @return The zero based line number, -1 for start of file
|
||||
*/
|
||||
override fun getRangeLine(editor: Editor, lastZero: Boolean): Int {
|
||||
if (line == CURRENT_LINE) {
|
||||
line = editor.caretModel.logicalPosition.line
|
||||
} else if (line == LAST_LINE) {
|
||||
line = EditorHelper.getLineCount(editor) - 1
|
||||
}
|
||||
return line
|
||||
}
|
||||
|
||||
override fun getRangeLine(editor: Editor, caret: Caret,
|
||||
lastZero: Boolean): Int {
|
||||
line = if (line == LAST_LINE) EditorHelper.getLineCount(editor) - 1 else caret.logicalPosition.line
|
||||
return line
|
||||
}
|
||||
|
||||
override fun toString(): String = "LineNumberRange[line=$line, ${super.toString()}]"
|
||||
|
||||
private var line: Int
|
||||
|
||||
companion object {
|
||||
const val CURRENT_LINE = -99999999
|
||||
const val LAST_LINE = -99999998
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Represents the line specified by a mark
|
||||
*/
|
||||
class MarkRange(private val mark: Char, offset: Int, move: Boolean) : Range(offset, move) {
|
||||
/**
|
||||
* Gets the line number specified by this range without regard to any offset.
|
||||
*
|
||||
* @param editor The editor to get the line for
|
||||
* @param lastZero True if last line was set to start of file
|
||||
* @return The zero based line number, -1 if there is no such mark set in the file
|
||||
*/
|
||||
override fun getRangeLine(editor: Editor, lastZero: Boolean): Int {
|
||||
val mark = VimPlugin.getMark().getFileMark(editor, mark)
|
||||
return mark?.logicalLine ?: -1
|
||||
}
|
||||
|
||||
override fun getRangeLine(editor: Editor, caret: Caret, lastZero: Boolean): Int = getRangeLine(editor, lastZero)
|
||||
|
||||
override fun toString(): String = "MarkRange[mark=$mark, ${super.toString()}]"
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Represents a range given by a search pattern. The pattern can be '\\/', '\\?', '\\&', /{pattern}/,
|
||||
* or ?{pattern}?. The last two can be repeated 0 or more times after any of the others.
|
||||
*/
|
||||
class SearchRange(pattern: String, offset: Int, move: Boolean) : Range(offset, move) {
|
||||
/**
|
||||
* Parses the pattern into a list of subpatterns and flags
|
||||
*
|
||||
* @param pattern The full search pattern
|
||||
*/
|
||||
private fun setPattern(pattern: String) {
|
||||
logger.debug { "pattern=$pattern" }
|
||||
val tok = StringTokenizer(pattern, "\u0000")
|
||||
while (tok.hasMoreTokens()) {
|
||||
var pat = tok.nextToken()
|
||||
when (pat) {
|
||||
"\\/" -> {
|
||||
patterns.add(VimPlugin.getSearch().lastSearch)
|
||||
flags.add(enumSetOf(CommandFlags.FLAG_SEARCH_FWD))
|
||||
}
|
||||
"\\?" -> {
|
||||
patterns.add(VimPlugin.getSearch().lastSearch)
|
||||
flags.add(enumSetOf(CommandFlags.FLAG_SEARCH_REV))
|
||||
}
|
||||
"\\&" -> {
|
||||
patterns.add(VimPlugin.getSearch().lastPattern)
|
||||
flags.add(enumSetOf(CommandFlags.FLAG_SEARCH_FWD))
|
||||
}
|
||||
else -> {
|
||||
if (pat[0] == '/') {
|
||||
flags.add(enumSetOf(CommandFlags.FLAG_SEARCH_FWD))
|
||||
} else {
|
||||
flags.add(enumSetOf(CommandFlags.FLAG_SEARCH_REV))
|
||||
}
|
||||
pat = pat.substring(1)
|
||||
if (pat.last() == pat[0]) {
|
||||
pat = pat.substring(0, pat.length - 1)
|
||||
}
|
||||
patterns.add(pat)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the line number specified by this range without regard to any offset.
|
||||
*
|
||||
* @param editor The editor to get the line for
|
||||
* @param lastZero True if last line was set to start of file
|
||||
* @return The zero based line number, -1 if the text was not found
|
||||
*/
|
||||
override fun getRangeLine(editor: Editor, lastZero: Boolean): Int { // Each subsequent pattern is searched for starting in the line after the previous search match
|
||||
var line = editor.caretModel.logicalPosition.line
|
||||
var pos = -1
|
||||
for (i in patterns.indices) {
|
||||
val pattern = patterns[i]
|
||||
val flag = flags[i]
|
||||
pos = if (CommandFlags.FLAG_SEARCH_FWD in flag && !lastZero) {
|
||||
VimPlugin.getMotion().moveCaretToLineEnd(editor, line, true)
|
||||
} else {
|
||||
VimPlugin.getMotion().moveCaretToLineStart(editor, line)
|
||||
}
|
||||
pos = VimPlugin.getSearch().search(editor, pattern!!, pos, 1, flag)
|
||||
line = if (pos == -1) {
|
||||
break
|
||||
} else {
|
||||
editor.offsetToLogicalPosition(pos).line
|
||||
}
|
||||
}
|
||||
return if (pos != -1) line else -1
|
||||
}
|
||||
|
||||
override fun getRangeLine(editor: Editor, caret: Caret,
|
||||
lastZero: Boolean): Int {
|
||||
var line = caret.logicalPosition.line
|
||||
var offset = -1
|
||||
for (i in patterns.indices) {
|
||||
val pattern = patterns[i]
|
||||
val flag = flags[i]
|
||||
offset = VimPlugin.getSearch().search(editor, pattern!!, getSearchOffset(editor, line, flag, lastZero), 1, flag)
|
||||
if (offset == -1) break
|
||||
line = editor.offsetToLogicalPosition(offset).line
|
||||
}
|
||||
return if (offset != -1) line else -1
|
||||
}
|
||||
|
||||
private fun getSearchOffset(editor: Editor, line: Int, flag: EnumSet<CommandFlags>, lastZero: Boolean): Int {
|
||||
return if (flag.contains(CommandFlags.FLAG_SEARCH_FWD) && !lastZero) {
|
||||
VimPlugin.getMotion().moveCaretToLineEnd(editor, line, true)
|
||||
} else VimPlugin.getMotion().moveCaretToLineStart(editor, line)
|
||||
}
|
||||
|
||||
override fun toString(): String = "SearchRange[patterns=$patterns, ${super.toString()}]"
|
||||
|
||||
private val patterns: MutableList<String?> = mutableListOf()
|
||||
private val flags: MutableList<EnumSet<CommandFlags>> = mutableListOf()
|
||||
|
||||
companion object {
|
||||
private val logger = logger<SearchRange>()
|
||||
}
|
||||
|
||||
/**
|
||||
* Create the pattern range
|
||||
*
|
||||
* @param pattern The text of the pattern. Each subpattern must be separated by the nul character (\\u0000)
|
||||
* @param offset The range offset
|
||||
* @param move True if the cursor should be moved
|
||||
*/
|
||||
init {
|
||||
setPattern(pattern)
|
||||
}
|
||||
}
|
@@ -15,14 +15,22 @@
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
package com.maddyhome.idea.vim.ex.ranges
|
||||
|
||||
package com.maddyhome.idea.vim.ex;
|
||||
class LineRange(startLine: Int, endLine: Int) {
|
||||
|
||||
/**
|
||||
* Exception class
|
||||
*/
|
||||
public class InvalidCommandException extends ExException {
|
||||
public InvalidCommandException(String message, String cmd) {
|
||||
super(message);
|
||||
@JvmField
|
||||
val startLine: Int
|
||||
@JvmField
|
||||
val endLine: Int
|
||||
|
||||
init {
|
||||
if (endLine >= startLine) {
|
||||
this.startLine = startLine
|
||||
this.endLine = endLine
|
||||
} else {
|
||||
this.startLine = endLine
|
||||
this.endLine = startLine
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
191
src/com/maddyhome/idea/vim/ex/ranges/Ranges.kt
Normal file
191
src/com/maddyhome/idea/vim/ex/ranges/Ranges.kt
Normal file
@@ -0,0 +1,191 @@
|
||||
/*
|
||||
* IdeaVim - Vim emulator for IDEs based on the IntelliJ platform
|
||||
* Copyright (C) 2003-2019 The IdeaVim authors
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 2 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
package com.maddyhome.idea.vim.ex.ranges
|
||||
|
||||
import com.intellij.openapi.actionSystem.DataContext
|
||||
import com.intellij.openapi.editor.Caret
|
||||
import com.intellij.openapi.editor.Editor
|
||||
import com.maddyhome.idea.vim.VimPlugin
|
||||
import com.maddyhome.idea.vim.common.TextRange
|
||||
import com.maddyhome.idea.vim.group.MotionGroup
|
||||
import com.maddyhome.idea.vim.helper.EditorHelper
|
||||
import kotlin.math.min
|
||||
|
||||
/**
|
||||
* Handles the set of range values entered as part of an Ex command.
|
||||
*/
|
||||
class Ranges {
|
||||
/** Adds a range to the list */
|
||||
fun addRange(range: Array<Range>) {
|
||||
ranges.addAll(range)
|
||||
}
|
||||
|
||||
/** Gets the number of ranges in the list */
|
||||
fun size(): Int = ranges.size
|
||||
|
||||
/**
|
||||
* Sets the default line to be used by this range if no range was actually given by the user. -1 is used to
|
||||
* mean the current line.
|
||||
*
|
||||
* @param line The line or -1 for current line
|
||||
*/
|
||||
fun setDefaultLine(line: Int) {
|
||||
defaultLine = line
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the line of the last range specified in the range list
|
||||
*
|
||||
* @param editor The editor to get the line for
|
||||
* @return The line number represented by the range
|
||||
*/
|
||||
fun getLine(editor: Editor): Int {
|
||||
processRange(editor)
|
||||
return endLine
|
||||
}
|
||||
|
||||
fun getLine(editor: Editor, caret: Caret, context: DataContext): Int {
|
||||
processRange(editor, caret, context)
|
||||
return endLine
|
||||
}
|
||||
|
||||
fun getFirstLine(editor: Editor, caret: Caret, context: DataContext): Int {
|
||||
processRange(editor, caret, context)
|
||||
return startLine
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the count for an Ex command. This is either an explicit count enter at the end of the command or the
|
||||
* end of the specified range.
|
||||
*
|
||||
* @param editor The editor to get the count for
|
||||
* @param count The count given at the end of the command or -1 if no such count (use end line)
|
||||
* @return count if count != -1, else return end line of range
|
||||
*/
|
||||
fun getCount(editor: Editor, count: Int): Int = if (count == -1) getLine(editor) else count
|
||||
|
||||
fun getCount(editor: Editor, caret: Caret, context: DataContext, count: Int): Int {
|
||||
return if (count == -1) getLine(editor, caret, context) else count
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the line range represented by this range. If a count is given, the range is the range end line through
|
||||
* count-1 lines. If no count is given (-1), the range is the range given by the user.
|
||||
*
|
||||
* @param editor The editor to get the range for
|
||||
* @param count The count given at the end of the command or -1 if no such count
|
||||
* @return The line range
|
||||
*/
|
||||
fun getLineRange(editor: Editor, count: Int): LineRange {
|
||||
processRange(editor)
|
||||
val end: Int
|
||||
val start: Int
|
||||
if (count == -1) {
|
||||
end = endLine
|
||||
start = startLine
|
||||
} else {
|
||||
start = endLine
|
||||
end = start + count - 1
|
||||
}
|
||||
return LineRange(start, end)
|
||||
}
|
||||
|
||||
fun getLineRange(editor: Editor, caret: Caret, context: DataContext, count: Int): LineRange {
|
||||
processRange(editor, caret, context)
|
||||
return if (count == -1) LineRange(startLine, endLine) else LineRange(endLine, endLine + count - 1)
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the text range represented by this range. If a count is given, the range is the range end line through
|
||||
* count-1 lines. If no count is given (-1), the range is the range given by the user. The text range is based
|
||||
* on the line range but this is character based from the start of the first line to the end of the last line.
|
||||
*
|
||||
* @param editor The editor to get the range for
|
||||
* @param context The data context
|
||||
* @param count The count given at the end of the command or -1 if no such count
|
||||
* @return The text range
|
||||
*/
|
||||
fun getTextRange(editor: Editor, context: DataContext?, count: Int): TextRange {
|
||||
val lr = getLineRange(editor, count)
|
||||
val start = EditorHelper.getLineStartOffset(editor, lr.startLine)
|
||||
val end = EditorHelper.getLineEndOffset(editor, lr.endLine, true) + 1
|
||||
return TextRange(start, min(end, EditorHelper.getFileSize(editor)))
|
||||
}
|
||||
|
||||
fun getTextRange(editor: Editor, caret: Caret, context: DataContext, count: Int): TextRange {
|
||||
val lineRange = getLineRange(editor, caret, context, count)
|
||||
val start = EditorHelper.getLineStartOffset(editor, lineRange.startLine)
|
||||
val end = EditorHelper.getLineEndOffset(editor, lineRange.endLine, true) + 1
|
||||
return TextRange(start, min(end, EditorHelper.getFileSize(editor)))
|
||||
}
|
||||
|
||||
/**
|
||||
* Processes the list of ranges and calculates the start and end lines of the range
|
||||
*
|
||||
* @param editor The editor to get the lines for
|
||||
*/
|
||||
private fun processRange(editor: Editor) {
|
||||
// Already done
|
||||
if (done) return
|
||||
// Start with the range being the current line
|
||||
startLine = if (defaultLine == -1) editor.caretModel.logicalPosition.line else defaultLine
|
||||
endLine = startLine
|
||||
var lastZero = false
|
||||
// Now process each range, moving the cursor if appropriate
|
||||
for (range in ranges) {
|
||||
startLine = endLine
|
||||
endLine = range.getLine(editor, lastZero)
|
||||
if (range.isMove) {
|
||||
MotionGroup.moveCaret(editor, editor.caretModel.primaryCaret,
|
||||
VimPlugin.getMotion().moveCaretToLine(editor, endLine, editor.caretModel.primaryCaret))
|
||||
}
|
||||
// Did that last range represent the start of the file?
|
||||
lastZero = endLine < 0
|
||||
count++
|
||||
}
|
||||
// If only one range given, make the start and end the same
|
||||
if (count == 1) {
|
||||
startLine = endLine
|
||||
}
|
||||
done = true
|
||||
}
|
||||
|
||||
private fun processRange(editor: Editor, caret: Caret, context: DataContext) {
|
||||
startLine = if (defaultLine == -1) caret.logicalPosition.line else defaultLine
|
||||
endLine = startLine
|
||||
var lastZero = false
|
||||
for (range in ranges) {
|
||||
startLine = endLine
|
||||
endLine = range.getLine(editor, caret, lastZero)
|
||||
if (range.isMove) MotionGroup.moveCaret(editor, caret, VimPlugin.getMotion().moveCaretToLine(editor, endLine, editor.caretModel.primaryCaret))
|
||||
lastZero = endLine < 0
|
||||
++count
|
||||
}
|
||||
if (count == 1) startLine = endLine
|
||||
count = 0
|
||||
}
|
||||
|
||||
override fun toString(): String = "Ranges[ranges=$ranges]"
|
||||
|
||||
private var startLine = 0
|
||||
private var endLine = 0
|
||||
private var count = 0
|
||||
private var defaultLine = -1
|
||||
private var done = false
|
||||
private val ranges: MutableList<Range> = mutableListOf()
|
||||
}
|
@@ -41,7 +41,7 @@ import java.util.regex.Pattern;
|
||||
public class VimScriptParser {
|
||||
public static final String VIMRC_FILE_NAME = "ideavimrc";
|
||||
public static final String[] HOME_VIMRC_PATHS = {"." + VIMRC_FILE_NAME, "_" + VIMRC_FILE_NAME};
|
||||
public static final String XDG_VIMRC_PATH = "ideavim" + File.pathSeparator + VIMRC_FILE_NAME;
|
||||
public static final String XDG_VIMRC_PATH = "ideavim" + File.separator + VIMRC_FILE_NAME;
|
||||
public static final int BUFSIZE = 4096;
|
||||
private static final Pattern EOL_SPLIT_PATTERN = Pattern.compile(" *(\r\n|\n)+ *");
|
||||
private static final Pattern DOUBLE_QUOTED_STRING = Pattern.compile("\"([^\"]*)\"");
|
||||
@@ -55,7 +55,6 @@ public class VimScriptParser {
|
||||
@Nullable
|
||||
public static File findIdeaVimRc() {
|
||||
final String homeDirName = System.getProperty("user.home");
|
||||
|
||||
// Check whether file exists in home dir
|
||||
if (homeDirName != null) {
|
||||
for (String fileName : HOME_VIMRC_PATHS) {
|
||||
|
@@ -40,7 +40,7 @@ public class VimExtensionRegistrar {
|
||||
|
||||
registeredExtensions.add(name);
|
||||
ToggleOption option = new ToggleOption(name, name, false);
|
||||
option.addOptionChangeListener(event -> {
|
||||
option.addOptionChangeListener((oldValue, newValue) -> {
|
||||
for (VimExtension extensionInListener : VimExtension.EP_NAME.getExtensionList()) {
|
||||
if (name.equals(extensionInListener.getName())) {
|
||||
if (OptionsManager.INSTANCE.isSet(name)) {
|
||||
|
@@ -1,357 +0,0 @@
|
||||
/*
|
||||
* IdeaVim - Vim emulator for IDEs based on the IntelliJ platform
|
||||
* Copyright (C) 2003-2019 The IdeaVim authors
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 2 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package com.maddyhome.idea.vim.extension.surround;
|
||||
|
||||
import com.google.common.collect.ImmutableMap;
|
||||
import com.intellij.openapi.actionSystem.DataContext;
|
||||
import com.intellij.openapi.application.WriteAction;
|
||||
import com.intellij.openapi.editor.Caret;
|
||||
import com.intellij.openapi.editor.Editor;
|
||||
import com.intellij.openapi.util.Pair;
|
||||
import com.maddyhome.idea.vim.VimPlugin;
|
||||
import com.maddyhome.idea.vim.command.CommandState;
|
||||
import com.maddyhome.idea.vim.command.MappingMode;
|
||||
import com.maddyhome.idea.vim.command.SelectionType;
|
||||
import com.maddyhome.idea.vim.common.Mark;
|
||||
import com.maddyhome.idea.vim.common.TextRange;
|
||||
import com.maddyhome.idea.vim.extension.VimExtensionHandler;
|
||||
import com.maddyhome.idea.vim.extension.VimNonDisposableExtension;
|
||||
import com.maddyhome.idea.vim.group.ChangeGroup;
|
||||
import com.maddyhome.idea.vim.helper.EditorHelper;
|
||||
import com.maddyhome.idea.vim.key.OperatorFunction;
|
||||
import com.maddyhome.idea.vim.option.ClipboardOptionsData;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
|
||||
import javax.swing.*;
|
||||
import java.awt.event.KeyEvent;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.regex.Matcher;
|
||||
import java.util.regex.Pattern;
|
||||
|
||||
import static com.maddyhome.idea.vim.extension.VimExtensionFacade.*;
|
||||
import static com.maddyhome.idea.vim.helper.StringHelper.parseKeys;
|
||||
|
||||
/**
|
||||
* Port of vim-surround.
|
||||
*
|
||||
* See https://github.com/tpope/vim-surround
|
||||
*
|
||||
* @author dhleong
|
||||
* @author vlan
|
||||
*/
|
||||
public class VimSurroundExtension extends VimNonDisposableExtension {
|
||||
|
||||
private static final char REGISTER = '"';
|
||||
private final static Pattern tagNameAndAttributesCapturePattern = Pattern.compile("(\\w+)([^>]*)>");
|
||||
|
||||
private static final Map<Character, Pair<String, String>> SURROUND_PAIRS = ImmutableMap.<Character, Pair<String, String>>builder()
|
||||
.put('b', Pair.create("(", ")"))
|
||||
.put('(', Pair.create("( ", " )"))
|
||||
.put(')', Pair.create("(", ")"))
|
||||
.put('B', Pair.create("{", "}"))
|
||||
.put('{', Pair.create("{ ", " }"))
|
||||
.put('}', Pair.create("{", "}"))
|
||||
.put('r', Pair.create("[", "]"))
|
||||
.put('[', Pair.create("[ ", " ]"))
|
||||
.put(']', Pair.create("[", "]"))
|
||||
.put('a', Pair.create("<", ">"))
|
||||
.put('>', Pair.create("<", ">"))
|
||||
.put('s', Pair.create(" ", ""))
|
||||
.build();
|
||||
|
||||
@NotNull
|
||||
@Override
|
||||
public String getName() {
|
||||
return "surround";
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void initOnce() {
|
||||
putExtensionHandlerMapping(MappingMode.N, parseKeys("<Plug>YSurround"), new YSurroundHandler(), false);
|
||||
putExtensionHandlerMapping(MappingMode.N, parseKeys("<Plug>CSurround"), new CSurroundHandler(), false);
|
||||
putExtensionHandlerMapping(MappingMode.N, parseKeys("<Plug>DSurround"), new DSurroundHandler(), false);
|
||||
putExtensionHandlerMapping(MappingMode.XO, parseKeys("<Plug>VSurround"), new VSurroundHandler(), false);
|
||||
|
||||
putKeyMapping(MappingMode.N, parseKeys("ys"), parseKeys("<Plug>YSurround"), true);
|
||||
putKeyMapping(MappingMode.N, parseKeys("cs"), parseKeys("<Plug>CSurround"), true);
|
||||
putKeyMapping(MappingMode.N, parseKeys("ds"), parseKeys("<Plug>DSurround"), true);
|
||||
putKeyMapping(MappingMode.XO, parseKeys("S"), parseKeys("<Plug>VSurround"), true);
|
||||
}
|
||||
|
||||
@Nullable
|
||||
private static Pair<String, String> getSurroundPair(char c) {
|
||||
if (SURROUND_PAIRS.containsKey(c)) {
|
||||
return SURROUND_PAIRS.get(c);
|
||||
}
|
||||
else if (!Character.isLetter(c)) {
|
||||
final String s = String.valueOf(c);
|
||||
return Pair.create(s, s);
|
||||
}
|
||||
else {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
@Nullable
|
||||
private static Pair<String, String> inputTagPair(@NotNull Editor editor) {
|
||||
final String tagInput = inputString(editor, "<", '>');
|
||||
final Matcher matcher = tagNameAndAttributesCapturePattern.matcher(tagInput);
|
||||
|
||||
if (matcher.find()) {
|
||||
final String tagName = matcher.group(1);
|
||||
final String tagAttributes = matcher.group(2);
|
||||
return Pair.create("<" + tagName + tagAttributes + ">", "</" + tagName + ">");
|
||||
}
|
||||
else {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
@Nullable
|
||||
private static Pair<String, String> inputFunctionName(
|
||||
@NotNull Editor editor,
|
||||
boolean withInternalSpaces
|
||||
) {
|
||||
final String functionNameInput = inputString(editor, "function: ", null);
|
||||
|
||||
if (functionNameInput.isEmpty()) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return withInternalSpaces
|
||||
? Pair.create(functionNameInput + "( ", " )")
|
||||
: Pair.create(functionNameInput + "(", ")");
|
||||
}
|
||||
|
||||
@Nullable
|
||||
private static Pair<String, String> getOrInputPair(char c, @NotNull Editor editor) {
|
||||
switch (c) {
|
||||
case '<':
|
||||
case 't':
|
||||
return inputTagPair(editor);
|
||||
case 'f':
|
||||
return inputFunctionName(editor, false);
|
||||
case 'F':
|
||||
return inputFunctionName(editor, true);
|
||||
default:
|
||||
return getSurroundPair(c);
|
||||
}
|
||||
}
|
||||
|
||||
private static char getChar(@NotNull Editor editor) {
|
||||
final KeyStroke key = inputKeyStroke(editor);
|
||||
final char keyChar = key.getKeyChar();
|
||||
if (keyChar == KeyEvent.CHAR_UNDEFINED || keyChar == KeyEvent.VK_ESCAPE) {
|
||||
return 0;
|
||||
}
|
||||
return keyChar;
|
||||
}
|
||||
|
||||
private static class YSurroundHandler implements VimExtensionHandler {
|
||||
@Override
|
||||
public boolean isRepeatable() {
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void execute(@NotNull Editor editor, @NotNull DataContext context) {
|
||||
setOperatorFunction(new Operator());
|
||||
executeNormal(parseKeys("g@"), editor);
|
||||
}
|
||||
}
|
||||
|
||||
private static class VSurroundHandler implements VimExtensionHandler {
|
||||
@Override
|
||||
public void execute(@NotNull Editor editor, @NotNull DataContext context) {
|
||||
int selectionStart = editor.getCaretModel().getPrimaryCaret().getSelectionStart();
|
||||
// NB: Operator ignores SelectionType anyway
|
||||
if (!new Operator().apply(editor, context, SelectionType.CHARACTER_WISE)) {
|
||||
return;
|
||||
}
|
||||
|
||||
WriteAction.run(() -> {
|
||||
// Leave visual mode
|
||||
executeNormal(parseKeys("<Esc>"), editor);
|
||||
editor.getCaretModel().moveToOffset(selectionStart);
|
||||
});
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
private static class CSurroundHandler implements VimExtensionHandler {
|
||||
@Override
|
||||
public boolean isRepeatable() {
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void execute(@NotNull Editor editor, @NotNull DataContext context) {
|
||||
final char charFrom = getChar(editor);
|
||||
if (charFrom == 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
final char charTo = getChar(editor);
|
||||
if (charTo == 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
Pair<String, String> newSurround = getOrInputPair(charTo, editor);
|
||||
if (newSurround == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
WriteAction.run(() -> change(editor, charFrom, newSurround));
|
||||
}
|
||||
|
||||
static void change(@NotNull Editor editor, char charFrom, @Nullable Pair<String, String> newSurround) {
|
||||
// We take over the " register, so preserve it
|
||||
final List<KeyStroke> oldValue = getRegister(REGISTER);
|
||||
|
||||
// Extract the inner value
|
||||
perform("di" + pick(charFrom), editor);
|
||||
List<KeyStroke> innerValue = getRegister(REGISTER);
|
||||
if (innerValue == null) {
|
||||
innerValue = new ArrayList<>();
|
||||
} else {
|
||||
innerValue = new ArrayList<>(innerValue);
|
||||
}
|
||||
|
||||
// Delete the surrounding
|
||||
perform("da" + pick(charFrom), editor);
|
||||
|
||||
// Insert the surrounding characters and paste
|
||||
if (newSurround != null) {
|
||||
innerValue.addAll(0, parseKeys(escape(newSurround.first)));
|
||||
innerValue.addAll(parseKeys(escape(newSurround.second)));
|
||||
}
|
||||
pasteSurround(innerValue, editor);
|
||||
|
||||
// Restore the old value
|
||||
setRegister(REGISTER, oldValue);
|
||||
|
||||
// Jump back to start
|
||||
executeNormal(parseKeys("`["), editor);
|
||||
}
|
||||
|
||||
@NotNull
|
||||
private static String escape(@NotNull String sequence) {
|
||||
return sequence.replace("<", "\\<");
|
||||
}
|
||||
|
||||
private static void perform(@NotNull String sequence, @NotNull Editor editor) {
|
||||
try (ClipboardOptionsData.IdeaputDisabler ignored = new ClipboardOptionsData.IdeaputDisabler()) {
|
||||
executeNormal(parseKeys("\"" + REGISTER + sequence), editor);
|
||||
}
|
||||
}
|
||||
|
||||
private static void pasteSurround(@NotNull List<KeyStroke> innerValue, @NotNull Editor editor) {
|
||||
// This logic is direct from vim-surround
|
||||
final int offset = editor.getCaretModel().getOffset();
|
||||
final int lineEndOffset = EditorHelper.getLineEndForOffset(editor, offset);
|
||||
|
||||
final Mark motionEndMark = VimPlugin.getMark().getMark(editor, ']');
|
||||
final int motionEndOffset;
|
||||
if (motionEndMark != null) {
|
||||
motionEndOffset = EditorHelper.getOffset(editor, motionEndMark.getLogicalLine(), motionEndMark.getCol());
|
||||
}
|
||||
else {
|
||||
motionEndOffset = -1;
|
||||
}
|
||||
final String pasteCommand = motionEndOffset == lineEndOffset && offset + 1 == lineEndOffset ? "p" : "P";
|
||||
setRegister(REGISTER, innerValue);
|
||||
perform(pasteCommand, editor);
|
||||
}
|
||||
|
||||
private static char pick(char charFrom) {
|
||||
switch (charFrom) {
|
||||
case 'a': return '>';
|
||||
case 'r': return ']';
|
||||
default: return charFrom;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private static class DSurroundHandler implements VimExtensionHandler {
|
||||
@Override
|
||||
public boolean isRepeatable() {
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void execute(@NotNull Editor editor, @NotNull DataContext context) {
|
||||
// Deleting surround is just changing the surrounding to "nothing"
|
||||
final char charFrom = getChar(editor);
|
||||
if (charFrom == 0) {
|
||||
return;
|
||||
}
|
||||
WriteAction.run(() -> CSurroundHandler.change(editor, charFrom, null));
|
||||
}
|
||||
}
|
||||
|
||||
private static class Operator implements OperatorFunction {
|
||||
@Override
|
||||
public boolean apply(@NotNull Editor editor, @NotNull DataContext context, @NotNull SelectionType selectionType) {
|
||||
final char c = getChar(editor);
|
||||
if (c == 0) {
|
||||
return true;
|
||||
}
|
||||
final Pair<String, String> pair = getOrInputPair(c, editor);
|
||||
if (pair == null) {
|
||||
return false;
|
||||
}
|
||||
// XXX: Will it work with line-wise or block-wise selections?
|
||||
final TextRange range = getSurroundRange(editor);
|
||||
if (range == null) {
|
||||
return false;
|
||||
}
|
||||
WriteAction.run(() -> {
|
||||
final ChangeGroup change = VimPlugin.getChange();
|
||||
final String leftSurround = pair.getFirst();
|
||||
final Caret primaryCaret = editor.getCaretModel().getPrimaryCaret();
|
||||
primaryCaret.moveToOffset(range.getStartOffset());
|
||||
change.insertText(editor, primaryCaret, leftSurround);
|
||||
primaryCaret.moveToOffset(range.getEndOffset() + leftSurround.length());
|
||||
change.insertText(editor, primaryCaret, pair.getSecond());
|
||||
|
||||
// Jump back to start
|
||||
executeNormal(parseKeys("`["), editor);
|
||||
});
|
||||
return true;
|
||||
}
|
||||
|
||||
@Nullable
|
||||
private TextRange getSurroundRange(@NotNull Editor editor) {
|
||||
final CommandState.Mode mode = CommandState.getInstance(editor).getMode();
|
||||
switch (mode) {
|
||||
case COMMAND:
|
||||
return VimPlugin.getMark().getChangeMarks(editor);
|
||||
case VISUAL:
|
||||
int selectionStart = editor.getCaretModel().getPrimaryCaret().getSelectionStart();
|
||||
int selectionEnd = editor.getCaretModel().getPrimaryCaret().getSelectionEnd();
|
||||
return new TextRange(selectionStart, selectionEnd);
|
||||
default:
|
||||
return null;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@@ -0,0 +1,256 @@
|
||||
/*
|
||||
* IdeaVim - Vim emulator for IDEs based on the IntelliJ platform
|
||||
* Copyright (C) 2003-2019 The IdeaVim authors
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 2 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
package com.maddyhome.idea.vim.extension.surround
|
||||
|
||||
import com.intellij.openapi.actionSystem.DataContext
|
||||
import com.intellij.openapi.application.runWriteAction
|
||||
import com.intellij.openapi.editor.Editor
|
||||
import com.maddyhome.idea.vim.VimPlugin
|
||||
import com.maddyhome.idea.vim.command.CommandState
|
||||
import com.maddyhome.idea.vim.command.MappingMode
|
||||
import com.maddyhome.idea.vim.command.SelectionType
|
||||
import com.maddyhome.idea.vim.common.TextRange
|
||||
import com.maddyhome.idea.vim.extension.VimExtensionFacade.executeNormal
|
||||
import com.maddyhome.idea.vim.extension.VimExtensionFacade.getRegister
|
||||
import com.maddyhome.idea.vim.extension.VimExtensionFacade.inputKeyStroke
|
||||
import com.maddyhome.idea.vim.extension.VimExtensionFacade.inputString
|
||||
import com.maddyhome.idea.vim.extension.VimExtensionFacade.putExtensionHandlerMapping
|
||||
import com.maddyhome.idea.vim.extension.VimExtensionFacade.putKeyMapping
|
||||
import com.maddyhome.idea.vim.extension.VimExtensionFacade.setOperatorFunction
|
||||
import com.maddyhome.idea.vim.extension.VimExtensionFacade.setRegister
|
||||
import com.maddyhome.idea.vim.extension.VimExtensionHandler
|
||||
import com.maddyhome.idea.vim.extension.VimNonDisposableExtension
|
||||
import com.maddyhome.idea.vim.helper.EditorHelper
|
||||
import com.maddyhome.idea.vim.helper.StringHelper
|
||||
import com.maddyhome.idea.vim.helper.mode
|
||||
import com.maddyhome.idea.vim.key.OperatorFunction
|
||||
import com.maddyhome.idea.vim.option.ClipboardOptionsData.IdeaputDisabler
|
||||
import java.awt.event.KeyEvent
|
||||
import javax.swing.KeyStroke
|
||||
|
||||
/**
|
||||
* Port of vim-surround.
|
||||
*
|
||||
* See https://github.com/tpope/vim-surround
|
||||
*
|
||||
* @author dhleong
|
||||
* @author vlan
|
||||
*/
|
||||
class VimSurroundExtension : VimNonDisposableExtension() {
|
||||
override fun getName() = "surround"
|
||||
|
||||
override fun initOnce() {
|
||||
putExtensionHandlerMapping(MappingMode.N, StringHelper.parseKeys("<Plug>YSurround"), YSurroundHandler(), false)
|
||||
putExtensionHandlerMapping(MappingMode.N, StringHelper.parseKeys("<Plug>CSurround"), CSurroundHandler(), false)
|
||||
putExtensionHandlerMapping(MappingMode.N, StringHelper.parseKeys("<Plug>DSurround"), DSurroundHandler(), false)
|
||||
putExtensionHandlerMapping(MappingMode.XO, StringHelper.parseKeys("<Plug>VSurround"), VSurroundHandler(), false)
|
||||
|
||||
putKeyMapping(MappingMode.N, StringHelper.parseKeys("ys"), StringHelper.parseKeys("<Plug>YSurround"), true)
|
||||
putKeyMapping(MappingMode.N, StringHelper.parseKeys("cs"), StringHelper.parseKeys("<Plug>CSurround"), true)
|
||||
putKeyMapping(MappingMode.N, StringHelper.parseKeys("ds"), StringHelper.parseKeys("<Plug>DSurround"), true)
|
||||
putKeyMapping(MappingMode.XO, StringHelper.parseKeys("S"), StringHelper.parseKeys("<Plug>VSurround"), true)
|
||||
}
|
||||
|
||||
private class YSurroundHandler : VimExtensionHandler {
|
||||
override fun isRepeatable() = true
|
||||
|
||||
override fun execute(editor: Editor, context: DataContext) {
|
||||
setOperatorFunction(Operator())
|
||||
executeNormal(StringHelper.parseKeys("g@"), editor)
|
||||
}
|
||||
}
|
||||
|
||||
private class VSurroundHandler : VimExtensionHandler {
|
||||
override fun execute(editor: Editor, context: DataContext) {
|
||||
val selectionStart = editor.caretModel.primaryCaret.selectionStart
|
||||
// NB: Operator ignores SelectionType anyway
|
||||
if (!Operator().apply(editor, context, SelectionType.CHARACTER_WISE)) {
|
||||
return
|
||||
}
|
||||
runWriteAction {
|
||||
// Leave visual mode
|
||||
executeNormal(StringHelper.parseKeys("<Esc>"), editor)
|
||||
editor.caretModel.moveToOffset(selectionStart)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private class CSurroundHandler : VimExtensionHandler {
|
||||
override fun isRepeatable() = true
|
||||
|
||||
override fun execute(editor: Editor, context: DataContext) {
|
||||
val charFrom = getChar(editor)
|
||||
if (charFrom.toInt() == 0) return
|
||||
|
||||
val charTo = getChar(editor)
|
||||
if (charTo.toInt() == 0) return
|
||||
|
||||
val newSurround = getOrInputPair(charTo, editor) ?: return
|
||||
runWriteAction { change(editor, charFrom, newSurround) }
|
||||
}
|
||||
|
||||
companion object {
|
||||
fun change(editor: Editor, charFrom: Char, newSurround: Pair<String, String>?) {
|
||||
// We take over the " register, so preserve it
|
||||
val oldValue: List<KeyStroke>? = getRegister(REGISTER)
|
||||
// Extract the inner value
|
||||
perform("di" + pick(charFrom), editor)
|
||||
val innerValue: MutableList<KeyStroke> = getRegister(REGISTER)?.toMutableList() ?: mutableListOf()
|
||||
// Delete the surrounding
|
||||
perform("da" + pick(charFrom), editor)
|
||||
// Insert the surrounding characters and paste
|
||||
if (newSurround != null) {
|
||||
innerValue.addAll(0, StringHelper.parseKeys(escape(newSurround.first)))
|
||||
innerValue.addAll(StringHelper.parseKeys(escape(newSurround.second)))
|
||||
}
|
||||
pasteSurround(innerValue, editor)
|
||||
// Restore the old value
|
||||
setRegister(REGISTER, oldValue)
|
||||
// Jump back to start
|
||||
executeNormal(StringHelper.parseKeys("`["), editor)
|
||||
}
|
||||
|
||||
private fun escape(sequence: String): String = sequence.replace("<", "\\<")
|
||||
|
||||
private fun perform(sequence: String, editor: Editor) {
|
||||
IdeaputDisabler().use { executeNormal(StringHelper.parseKeys("\"" + REGISTER + sequence), editor) }
|
||||
}
|
||||
|
||||
private fun pasteSurround(innerValue: List<KeyStroke?>, editor: Editor) { // This logic is direct from vim-surround
|
||||
val offset = editor.caretModel.offset
|
||||
val lineEndOffset = EditorHelper.getLineEndForOffset(editor, offset)
|
||||
val motionEndMark = VimPlugin.getMark().getMark(editor, ']')
|
||||
val motionEndOffset = if (motionEndMark != null) {
|
||||
EditorHelper.getOffset(editor, motionEndMark.logicalLine, motionEndMark.col)
|
||||
} else -1
|
||||
val pasteCommand = if (motionEndOffset == lineEndOffset && offset + 1 == lineEndOffset) "p" else "P"
|
||||
setRegister(REGISTER, innerValue)
|
||||
perform(pasteCommand, editor)
|
||||
}
|
||||
|
||||
private fun pick(charFrom: Char) = when (charFrom) {
|
||||
'a' -> '>'
|
||||
'r' -> ']'
|
||||
else -> charFrom
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private class DSurroundHandler : VimExtensionHandler {
|
||||
override fun isRepeatable() = true
|
||||
|
||||
override fun execute(editor: Editor, context: DataContext) {
|
||||
// Deleting surround is just changing the surrounding to "nothing"
|
||||
val charFrom = getChar(editor)
|
||||
if (charFrom.toInt() == 0) return
|
||||
|
||||
runWriteAction { CSurroundHandler.change(editor, charFrom, null) }
|
||||
}
|
||||
}
|
||||
|
||||
private class Operator : OperatorFunction {
|
||||
override fun apply(editor: Editor, context: DataContext, selectionType: SelectionType): Boolean {
|
||||
val c = getChar(editor)
|
||||
if (c.toInt() == 0) return true
|
||||
|
||||
val pair = getOrInputPair(c, editor) ?: return false
|
||||
// XXX: Will it work with line-wise or block-wise selections?
|
||||
val range = getSurroundRange(editor) ?: return false
|
||||
runWriteAction {
|
||||
val change = VimPlugin.getChange()
|
||||
val leftSurround = pair.first
|
||||
val primaryCaret = editor.caretModel.primaryCaret
|
||||
primaryCaret.moveToOffset(range.startOffset)
|
||||
change.insertText(editor, primaryCaret, leftSurround)
|
||||
primaryCaret.moveToOffset(range.endOffset + leftSurround.length)
|
||||
change.insertText(editor, primaryCaret, pair.second)
|
||||
// Jump back to start
|
||||
executeNormal(StringHelper.parseKeys("`["), editor)
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
private fun getSurroundRange(editor: Editor): TextRange? = when (editor.mode) {
|
||||
CommandState.Mode.COMMAND -> VimPlugin.getMark().getChangeMarks(editor)
|
||||
CommandState.Mode.VISUAL -> editor.caretModel.primaryCaret.run { TextRange(selectionStart, selectionEnd) }
|
||||
else -> null
|
||||
}
|
||||
}
|
||||
|
||||
companion object {
|
||||
private const val REGISTER = '"'
|
||||
|
||||
private val tagNameAndAttributesCapturePattern = "(\\w+)([^>]*)>".toPattern()
|
||||
|
||||
private val SURROUND_PAIRS = mapOf(
|
||||
'b' to ("(" to ")"),
|
||||
'(' to ("( " to " )"),
|
||||
')' to ("(" to ")"),
|
||||
'B' to ("{" to "}"),
|
||||
'{' to ("{ " to " }"),
|
||||
'}' to ("{" to "}"),
|
||||
'r' to ("[" to "]"),
|
||||
'[' to ("[ " to " ]"),
|
||||
']' to ("[" to "]"),
|
||||
'a' to ("<" to ">"),
|
||||
'>' to ("<" to ">"),
|
||||
's' to (" " to "")
|
||||
)
|
||||
|
||||
private fun getSurroundPair(c: Char): Pair<String, String>? = if (c in SURROUND_PAIRS) {
|
||||
SURROUND_PAIRS[c]
|
||||
} else if (!c.isLetter()) {
|
||||
val s = c.toString()
|
||||
s to s
|
||||
} else null
|
||||
|
||||
private fun inputTagPair(editor: Editor): Pair<String, String>? {
|
||||
val tagInput = inputString(editor, "<", '>')
|
||||
val matcher = tagNameAndAttributesCapturePattern.matcher(tagInput)
|
||||
return if (matcher.find()) {
|
||||
val tagName = matcher.group(1)
|
||||
val tagAttributes = matcher.group(2)
|
||||
"<$tagName$tagAttributes>" to "</$tagName>"
|
||||
} else null
|
||||
}
|
||||
|
||||
private fun inputFunctionName(
|
||||
editor: Editor,
|
||||
withInternalSpaces: Boolean
|
||||
): Pair<String, String>? {
|
||||
val functionNameInput = inputString(editor, "function: ", null)
|
||||
if (functionNameInput.isEmpty()) return null
|
||||
return if (withInternalSpaces) "$functionNameInput( " to " )" else "$functionNameInput(" to ")"
|
||||
}
|
||||
|
||||
private fun getOrInputPair(c: Char, editor: Editor): Pair<String, String>? = when (c) {
|
||||
'<', 't' -> inputTagPair(editor)
|
||||
'f' -> inputFunctionName(editor, false)
|
||||
'F' -> inputFunctionName(editor, true)
|
||||
else -> getSurroundPair(c)
|
||||
}
|
||||
|
||||
private fun getChar(editor: Editor): Char {
|
||||
val key = inputKeyStroke(editor)
|
||||
val keyChar = key.keyChar
|
||||
return if (keyChar == KeyEvent.CHAR_UNDEFINED || keyChar.toInt() == KeyEvent.VK_ESCAPE) {
|
||||
0.toChar()
|
||||
} else keyChar
|
||||
}
|
||||
}
|
||||
}
|
@@ -51,7 +51,7 @@ import com.maddyhome.idea.vim.command.*;
|
||||
import com.maddyhome.idea.vim.common.IndentConfig;
|
||||
import com.maddyhome.idea.vim.common.Register;
|
||||
import com.maddyhome.idea.vim.common.TextRange;
|
||||
import com.maddyhome.idea.vim.ex.LineRange;
|
||||
import com.maddyhome.idea.vim.ex.ranges.LineRange;
|
||||
import com.maddyhome.idea.vim.group.visual.VimSelection;
|
||||
import com.maddyhome.idea.vim.group.visual.VisualGroupKt;
|
||||
import com.maddyhome.idea.vim.group.visual.VisualModeHelperKt;
|
||||
@@ -1787,8 +1787,8 @@ public class ChangeGroup {
|
||||
public boolean sortRange(@NotNull Editor editor,
|
||||
@NotNull LineRange range,
|
||||
@NotNull Comparator<String> lineComparator) {
|
||||
final int startLine = range.getStartLine();
|
||||
final int endLine = range.getEndLine();
|
||||
final int startLine = range.startLine;
|
||||
final int endLine = range.endLine;
|
||||
final int count = endLine - startLine + 1;
|
||||
if (count < 2) {
|
||||
return false;
|
||||
|
@@ -32,7 +32,6 @@ import com.intellij.openapi.project.Project;
|
||||
import com.maddyhome.idea.vim.KeyHandler;
|
||||
import com.maddyhome.idea.vim.VimPlugin;
|
||||
import com.maddyhome.idea.vim.helper.*;
|
||||
import com.maddyhome.idea.vim.option.OptionChangeEvent;
|
||||
import com.maddyhome.idea.vim.option.OptionChangeListener;
|
||||
import com.maddyhome.idea.vim.option.OptionsManager;
|
||||
import gnu.trove.TIntFunction;
|
||||
@@ -276,7 +275,7 @@ public class EditorGroup {
|
||||
VimPlugin.getNotifications(project).notifyAboutIdeaJoin();
|
||||
}
|
||||
|
||||
public static class NumberChangeListener implements OptionChangeListener {
|
||||
public static class NumberChangeListener implements OptionChangeListener<Boolean> {
|
||||
public static NumberChangeListener INSTANCE = new NumberChangeListener();
|
||||
|
||||
@Contract(pure = true)
|
||||
@@ -284,7 +283,7 @@ public class EditorGroup {
|
||||
}
|
||||
|
||||
@Override
|
||||
public void valueChange(OptionChangeEvent event) {
|
||||
public void valueChange(Boolean oldValue, Boolean newValue) {
|
||||
for (Editor editor : EditorFactory.getInstance().getAllEditors()) {
|
||||
if (UserDataManager.getVimEditorGroup(editor) && supportsVimLineNumbers(editor)) {
|
||||
updateLineNumbers(editor, true);
|
||||
|
@@ -89,7 +89,7 @@ public class RegisterGroup {
|
||||
|
||||
public RegisterGroup() {
|
||||
final ListOption clipboardOption = OptionsManager.INSTANCE.getClipboard();
|
||||
clipboardOption.addOptionChangeListener(event -> {
|
||||
clipboardOption.addOptionChangeListenerAndExecute((oldValue, newValue) -> {
|
||||
if (clipboardOption.contains("unnamed")) {
|
||||
defaultRegister = '*';
|
||||
}
|
||||
@@ -221,7 +221,7 @@ public class RegisterGroup {
|
||||
for (char d = '8'; d >= '1'; d--) {
|
||||
Register t = registers.get(d);
|
||||
if (t != null) {
|
||||
t.rename((char)(d + 1));
|
||||
t.setName((char)(d + 1));
|
||||
registers.put((char)(d + 1), t);
|
||||
}
|
||||
}
|
||||
@@ -365,7 +365,7 @@ public class RegisterGroup {
|
||||
res.add(register);
|
||||
}
|
||||
}
|
||||
res.sort(new Register.KeySorter());
|
||||
res.sort(Register.KeySorter.INSTANCE);
|
||||
return res;
|
||||
}
|
||||
|
||||
|
@@ -37,7 +37,7 @@ import com.maddyhome.idea.vim.command.CommandFlags;
|
||||
import com.maddyhome.idea.vim.command.SelectionType;
|
||||
import com.maddyhome.idea.vim.common.CharacterPosition;
|
||||
import com.maddyhome.idea.vim.common.TextRange;
|
||||
import com.maddyhome.idea.vim.ex.LineRange;
|
||||
import com.maddyhome.idea.vim.ex.ranges.LineRange;
|
||||
import com.maddyhome.idea.vim.helper.*;
|
||||
import com.maddyhome.idea.vim.option.ListOption;
|
||||
import com.maddyhome.idea.vim.option.OptionChangeListener;
|
||||
@@ -63,12 +63,12 @@ import java.util.*;
|
||||
public class SearchGroup {
|
||||
public SearchGroup() {
|
||||
final OptionsManager options = OptionsManager.INSTANCE;
|
||||
options.getHlsearch().addOptionChangeListener(event -> {
|
||||
options.getHlsearch().addOptionChangeListener((oldValue, newValue) -> {
|
||||
resetShowSearchHighlight();
|
||||
forceUpdateSearchHighlights();
|
||||
});
|
||||
|
||||
final OptionChangeListener updateHighlightsIfVisible = event -> {
|
||||
final OptionChangeListener<Boolean> updateHighlightsIfVisible = (oldValue, newValue) -> {
|
||||
if (showSearchHighlight) {
|
||||
forceUpdateSearchHighlights();
|
||||
}
|
||||
@@ -377,7 +377,7 @@ public class SearchGroup {
|
||||
}
|
||||
|
||||
public int updateIncsearchHighlights(@NotNull Editor editor, @NotNull String pattern, boolean forwards, int caretOffset, @Nullable LineRange searchRange) {
|
||||
final int searchStartOffset = searchRange != null ? EditorHelper.getLineStartOffset(editor, searchRange.getStartLine()) : caretOffset;
|
||||
final int searchStartOffset = searchRange != null ? EditorHelper.getLineStartOffset(editor, searchRange.startLine) : caretOffset;
|
||||
final boolean showHighlights = OptionsManager.INSTANCE.getHlsearch().isSet();
|
||||
return updateSearchHighlights(pattern, false, showHighlights, searchStartOffset, searchRange, forwards, false);
|
||||
}
|
||||
@@ -410,8 +410,8 @@ public class SearchGroup {
|
||||
}
|
||||
|
||||
if (shouldAddAllSearchHighlights(editor, pattern, showHighlights)) {
|
||||
final int startLine = searchRange == null ? 0 : searchRange.getStartLine();
|
||||
final int endLine = searchRange == null ? -1 : searchRange.getEndLine();
|
||||
final int startLine = searchRange == null ? 0 : searchRange.startLine;
|
||||
final int endLine = searchRange == null ? -1 : searchRange.endLine;
|
||||
List<TextRange> results = findAll(editor, pattern, startLine, endLine, shouldIgnoreCase(pattern, shouldIgnoreSmartCase));
|
||||
if (!results.isEmpty()) {
|
||||
currentMatchOffset = findClosestMatch(editor, results, initialOffset, forwards);
|
||||
@@ -1114,8 +1114,8 @@ public class SearchGroup {
|
||||
cmd.inc();
|
||||
}
|
||||
|
||||
int line1 = range.getStartLine();
|
||||
int line2 = range.getEndLine();
|
||||
int line1 = range.startLine;
|
||||
int line2 = range.endLine;
|
||||
|
||||
if (line1 < 0 || line2 < 0) {
|
||||
return false;
|
||||
|
@@ -1,107 +0,0 @@
|
||||
/*
|
||||
* IdeaVim - Vim emulator for IDEs based on the IntelliJ platform
|
||||
* Copyright (C) 2003-2019 The IdeaVim authors
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 2 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package com.maddyhome.idea.vim.helper;
|
||||
|
||||
import com.maddyhome.idea.vim.option.OptionsManager;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
|
||||
/**
|
||||
* This helper class is used when working with various character level operations
|
||||
*/
|
||||
public class CharacterHelper {
|
||||
|
||||
public enum CharacterType {
|
||||
KEYWORD,
|
||||
HIRAGANA,
|
||||
KATAKANA,
|
||||
HALF_WIDTH_KATAKANA,
|
||||
PUNCTUATION,
|
||||
WHITESPACE
|
||||
}
|
||||
|
||||
public static final char CASE_TOGGLE = '~';
|
||||
public static final char CASE_UPPER = 'u';
|
||||
public static final char CASE_LOWER = 'l';
|
||||
|
||||
/**
|
||||
* This returns the type of the supplied character. The logic is as follows:<br>
|
||||
* If the character is whitespace, <code>WHITESPACE</code> is returned.<br>
|
||||
* If the punctuation is being skipped or the character is a letter, digit, or underscore, <code>KEYWORD</code>
|
||||
* is returned.<br>
|
||||
* Otherwise <code>PUNCTUATION</code> is returned.
|
||||
*
|
||||
* @param ch The character to analyze
|
||||
* @param punctuationAsLetters True if punctuation is to be ignored, false if not
|
||||
* @return The type of the character
|
||||
*/
|
||||
@NotNull
|
||||
public static CharacterType charType(char ch, boolean punctuationAsLetters) {
|
||||
final Character.UnicodeBlock block = Character.UnicodeBlock.of(ch);
|
||||
if (Character.isWhitespace(ch)) {
|
||||
return CharacterType.WHITESPACE;
|
||||
}
|
||||
else if (block == Character.UnicodeBlock.HIRAGANA) {
|
||||
return CharacterType.HIRAGANA;
|
||||
}
|
||||
else if (block == Character.UnicodeBlock.KATAKANA) {
|
||||
return CharacterType.KATAKANA;
|
||||
}
|
||||
else if (isHalfWidthKatakanaLetter(ch)) {
|
||||
return CharacterType.HALF_WIDTH_KATAKANA;
|
||||
}
|
||||
else if (punctuationAsLetters || OptionsManager.INSTANCE.getIskeyword().isKeyword(ch)) {
|
||||
return CharacterType.KEYWORD;
|
||||
}
|
||||
else {
|
||||
return CharacterType.PUNCTUATION;
|
||||
}
|
||||
}
|
||||
|
||||
private static boolean isHalfWidthKatakanaLetter(char ch) {
|
||||
return ch >= '\uFF66' && ch <= '\uFF9F';
|
||||
}
|
||||
|
||||
/**
|
||||
* Changes the case of the supplied character based on the supplied change type
|
||||
*
|
||||
* @param ch The character to change
|
||||
* @param type One of <code>CASE_TOGGLE</code>, <code>CASE_UPPER</code>, or <code>CASE_LOWER</code>
|
||||
* @return The character with changed case or the original if not a letter
|
||||
*/
|
||||
public static char changeCase(char ch, char type) {
|
||||
switch (type) {
|
||||
case CASE_TOGGLE:
|
||||
if (Character.isLowerCase(ch)) {
|
||||
ch = Character.toUpperCase(ch);
|
||||
}
|
||||
else if (Character.isUpperCase(ch)) {
|
||||
ch = Character.toLowerCase(ch);
|
||||
}
|
||||
break;
|
||||
case CASE_LOWER:
|
||||
ch = Character.toLowerCase(ch);
|
||||
break;
|
||||
case CASE_UPPER:
|
||||
ch = Character.toUpperCase(ch);
|
||||
break;
|
||||
}
|
||||
|
||||
return ch;
|
||||
}
|
||||
}
|
84
src/com/maddyhome/idea/vim/helper/CharacterHelper.kt
Normal file
84
src/com/maddyhome/idea/vim/helper/CharacterHelper.kt
Normal file
@@ -0,0 +1,84 @@
|
||||
/*
|
||||
* IdeaVim - Vim emulator for IDEs based on the IntelliJ platform
|
||||
* Copyright (C) 2003-2019 The IdeaVim authors
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 2 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
package com.maddyhome.idea.vim.helper
|
||||
|
||||
import com.maddyhome.idea.vim.option.OptionsManager.iskeyword
|
||||
import java.lang.Character.UnicodeBlock
|
||||
|
||||
/**
|
||||
* This helper class is used when working with various character level operations
|
||||
*/
|
||||
object CharacterHelper {
|
||||
const val CASE_TOGGLE = '~'
|
||||
const val CASE_UPPER = 'u'
|
||||
const val CASE_LOWER = 'l'
|
||||
|
||||
/**
|
||||
* This returns the type of the supplied character. The logic is as follows:<br></br>
|
||||
* If the character is whitespace, `WHITESPACE` is returned.<br></br>
|
||||
* If the punctuation is being skipped or the character is a letter, digit, or underscore, `KEYWORD`
|
||||
* is returned.<br></br>
|
||||
* Otherwise `PUNCTUATION` is returned.
|
||||
*
|
||||
* @param ch The character to analyze
|
||||
* @param punctuationAsLetters True if punctuation is to be ignored, false if not
|
||||
* @return The type of the character
|
||||
*/
|
||||
@JvmStatic
|
||||
fun charType(ch: Char, punctuationAsLetters: Boolean): CharacterType {
|
||||
val block = UnicodeBlock.of(ch)
|
||||
return if (Character.isWhitespace(ch)) {
|
||||
CharacterType.WHITESPACE
|
||||
} else if (block === UnicodeBlock.HIRAGANA) {
|
||||
CharacterType.HIRAGANA
|
||||
} else if (block === UnicodeBlock.KATAKANA) {
|
||||
CharacterType.KATAKANA
|
||||
} else if (isHalfWidthKatakanaLetter(ch)) {
|
||||
CharacterType.HALF_WIDTH_KATAKANA
|
||||
} else if (punctuationAsLetters || iskeyword.isKeyword(ch)) {
|
||||
CharacterType.KEYWORD
|
||||
} else {
|
||||
CharacterType.PUNCTUATION
|
||||
}
|
||||
}
|
||||
|
||||
private fun isHalfWidthKatakanaLetter(ch: Char): Boolean = ch in '\uFF66'..'\uFF9F'
|
||||
|
||||
/**
|
||||
* Changes the case of the supplied character based on the supplied change type
|
||||
*
|
||||
* @param ch The character to change
|
||||
* @param type One of `CASE_TOGGLE`, `CASE_UPPER`, or `CASE_LOWER`
|
||||
* @return The character with changed case or the original if not a letter
|
||||
*/
|
||||
@JvmStatic
|
||||
fun changeCase(ch: Char, type: Char): Char = when (type) {
|
||||
CASE_TOGGLE -> when {
|
||||
Character.isLowerCase(ch) -> Character.toUpperCase(ch)
|
||||
Character.isUpperCase(ch) -> Character.toLowerCase(ch)
|
||||
else -> ch
|
||||
}
|
||||
CASE_LOWER -> Character.toLowerCase(ch)
|
||||
CASE_UPPER -> Character.toUpperCase(ch)
|
||||
else -> ch
|
||||
}
|
||||
|
||||
enum class CharacterType {
|
||||
KEYWORD, HIRAGANA, KATAKANA, HALF_WIDTH_KATAKANA, PUNCTUATION, WHITESPACE
|
||||
}
|
||||
}
|
@@ -2154,9 +2154,7 @@ public class SearchHelper {
|
||||
private static String getPairChars() {
|
||||
if (pairsChars == null) {
|
||||
ListOption lo = OptionsManager.INSTANCE.getMatchpairs();
|
||||
pairsChars = parseOption(lo);
|
||||
|
||||
lo.addOptionChangeListener(event -> pairsChars = parseOption((ListOption)event.getOption()));
|
||||
lo.addOptionChangeListenerAndExecute((oldValue, newValue) -> pairsChars = parseOption(lo));
|
||||
}
|
||||
|
||||
return pairsChars;
|
||||
|
@@ -44,19 +44,21 @@ public final class KeywordOption extends ListOption {
|
||||
|
||||
@Override
|
||||
public boolean append(@NotNull String val) {
|
||||
String oldValue = getValue();
|
||||
final List<String> vals = parseVals(val);
|
||||
final List<KeywordSpec> specs = valsToValidatedAndReversedSpecs(vals);
|
||||
if (vals == null || specs == null) {
|
||||
return false;
|
||||
}
|
||||
value.addAll(vals);
|
||||
this.value.addAll(vals);
|
||||
keywordSpecs.addAll(0, specs);
|
||||
fireOptionChangeEvent();
|
||||
fireOptionChangeEvent(oldValue, getValue());
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean prepend(@NotNull String val) {
|
||||
String oldValue = getValue();
|
||||
final List<String> vals = parseVals(val);
|
||||
final List<KeywordSpec> specs = valsToValidatedAndReversedSpecs(vals);
|
||||
if (vals == null || specs == null) {
|
||||
@@ -64,13 +66,14 @@ public final class KeywordOption extends ListOption {
|
||||
}
|
||||
value.addAll(0, vals);
|
||||
keywordSpecs.addAll(specs);
|
||||
fireOptionChangeEvent();
|
||||
fireOptionChangeEvent(oldValue, getValue());
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public boolean remove(@NotNull String val) {
|
||||
String oldValue = getValue();
|
||||
final List<String> vals = parseVals(val);
|
||||
final List<KeywordSpec> specs = valsToValidatedAndReversedSpecs(vals);
|
||||
if (vals == null || specs == null) {
|
||||
@@ -78,20 +81,22 @@ public final class KeywordOption extends ListOption {
|
||||
}
|
||||
value.removeAll(vals);
|
||||
keywordSpecs.removeAll(specs);
|
||||
fireOptionChangeEvent();
|
||||
fireOptionChangeEvent(oldValue, getValue());
|
||||
return true;
|
||||
}
|
||||
|
||||
private void initialSet(String[] values) {
|
||||
final List<String> vals = Arrays.asList(values);
|
||||
String oldValue = getValue();
|
||||
final List<String> vals = new ArrayList<>(Arrays.asList(values));
|
||||
final List<KeywordSpec> specs = valsToReversedSpecs(vals);
|
||||
value = vals;
|
||||
keywordSpecs = specs;
|
||||
fireOptionChangeEvent();
|
||||
fireOptionChangeEvent(oldValue, getValue());
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean set(@NotNull String val) {
|
||||
String oldValue = getValue();
|
||||
final List<String> vals = parseVals(val);
|
||||
final List<KeywordSpec> specs = valsToValidatedAndReversedSpecs(vals);
|
||||
if (vals == null || specs == null) {
|
||||
@@ -99,7 +104,7 @@ public final class KeywordOption extends ListOption {
|
||||
}
|
||||
value = vals;
|
||||
keywordSpecs = specs;
|
||||
fireOptionChangeEvent();
|
||||
fireOptionChangeEvent(oldValue, getValue());
|
||||
return true;
|
||||
}
|
||||
|
||||
|
@@ -128,8 +128,9 @@ public class ListOption extends TextOption {
|
||||
return false;
|
||||
}
|
||||
|
||||
value = vals;
|
||||
fireOptionChangeEvent();
|
||||
String oldValue = getValue();
|
||||
this.value = vals;
|
||||
fireOptionChangeEvent(oldValue, getValue());
|
||||
|
||||
return true;
|
||||
}
|
||||
@@ -139,8 +140,9 @@ public class ListOption extends TextOption {
|
||||
return false;
|
||||
}
|
||||
|
||||
String oldValue = getValue();
|
||||
value.addAll(vals);
|
||||
fireOptionChangeEvent();
|
||||
fireOptionChangeEvent(oldValue, getValue());
|
||||
|
||||
return true;
|
||||
}
|
||||
@@ -150,8 +152,9 @@ public class ListOption extends TextOption {
|
||||
return false;
|
||||
}
|
||||
|
||||
String oldValue = getValue();
|
||||
value.addAll(0, vals);
|
||||
fireOptionChangeEvent();
|
||||
fireOptionChangeEvent(oldValue, getValue());
|
||||
|
||||
return true;
|
||||
}
|
||||
@@ -161,8 +164,9 @@ public class ListOption extends TextOption {
|
||||
return false;
|
||||
}
|
||||
|
||||
String oldValue = getValue();
|
||||
value.removeAll(vals);
|
||||
fireOptionChangeEvent();
|
||||
fireOptionChangeEvent(oldValue, getValue());
|
||||
|
||||
return true;
|
||||
}
|
||||
@@ -230,8 +234,9 @@ public class ListOption extends TextOption {
|
||||
@Override
|
||||
public void resetDefault() {
|
||||
if (!dflt.equals(value)) {
|
||||
String oldValue = getValue();
|
||||
value = new ArrayList<>(dflt);
|
||||
fireOptionChangeEvent();
|
||||
fireOptionChangeEvent(oldValue, getValue());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@@ -99,8 +99,10 @@ public class NumberOption extends TextOption {
|
||||
}
|
||||
|
||||
if (inRange(num)) {
|
||||
value = num;
|
||||
fireOptionChangeEvent();
|
||||
|
||||
String oldValue = getValue();
|
||||
this.value = num;
|
||||
fireOptionChangeEvent(oldValue, getValue());
|
||||
|
||||
return true;
|
||||
}
|
||||
@@ -125,8 +127,9 @@ public class NumberOption extends TextOption {
|
||||
}
|
||||
|
||||
if (inRange(value + num)) {
|
||||
String oldValue = getValue();
|
||||
value += num;
|
||||
fireOptionChangeEvent();
|
||||
fireOptionChangeEvent(oldValue, getValue());
|
||||
|
||||
return true;
|
||||
}
|
||||
@@ -151,8 +154,9 @@ public class NumberOption extends TextOption {
|
||||
}
|
||||
|
||||
if (inRange(value * num)) {
|
||||
String oldValue = getValue();
|
||||
value *= num;
|
||||
fireOptionChangeEvent();
|
||||
fireOptionChangeEvent(oldValue, getValue());
|
||||
|
||||
return true;
|
||||
}
|
||||
@@ -177,8 +181,9 @@ public class NumberOption extends TextOption {
|
||||
}
|
||||
|
||||
if (inRange(value - num)) {
|
||||
String oldValue = getValue();
|
||||
value -= num;
|
||||
fireOptionChangeEvent();
|
||||
fireOptionChangeEvent(oldValue, getValue());
|
||||
|
||||
return true;
|
||||
}
|
||||
@@ -202,8 +207,9 @@ public class NumberOption extends TextOption {
|
||||
@Override
|
||||
public void resetDefault() {
|
||||
if (dflt != value) {
|
||||
String oldValue = getValue();
|
||||
value = dflt;
|
||||
fireOptionChangeEvent();
|
||||
fireOptionChangeEvent(oldValue, getValue());
|
||||
}
|
||||
}
|
||||
|
||||
|
@@ -21,14 +21,13 @@ package com.maddyhome.idea.vim.option;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Comparator;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* Represents an VIM options that can be set with the :set command. Listeners can be set that are interested in knowing
|
||||
* when the value of the option changes.
|
||||
*/
|
||||
public abstract class Option {
|
||||
public abstract class Option<T> {
|
||||
/**
|
||||
* Create the option
|
||||
*
|
||||
@@ -46,16 +45,27 @@ public abstract class Option {
|
||||
*
|
||||
* @param listener The listener
|
||||
*/
|
||||
public void addOptionChangeListener(OptionChangeListener listener) {
|
||||
public void addOptionChangeListener(OptionChangeListener<T> listener) {
|
||||
listeners.add(listener);
|
||||
}
|
||||
|
||||
/**
|
||||
* Registers an option change listener and fire an event.
|
||||
*
|
||||
* @param listener The listener
|
||||
*/
|
||||
public void addOptionChangeListenerAndExecute(OptionChangeListener<T> listener) {
|
||||
addOptionChangeListener(listener);
|
||||
T value = getValue();
|
||||
fireOptionChangeEvent(value, value);
|
||||
}
|
||||
|
||||
/**
|
||||
* Removes the listener from the list.
|
||||
*
|
||||
* @param listener The listener
|
||||
*/
|
||||
public void removeOptionChangeListener(OptionChangeListener listener) {
|
||||
public void removeOptionChangeListener(OptionChangeListener<T> listener) {
|
||||
listeners.remove(listener);
|
||||
}
|
||||
|
||||
@@ -93,24 +103,15 @@ public abstract class Option {
|
||||
* Lets all listeners know that the value has changed. Subclasses are responsible for calling this when their
|
||||
* value changes.
|
||||
*/
|
||||
protected void fireOptionChangeEvent() {
|
||||
OptionChangeEvent event = new OptionChangeEvent(this);
|
||||
for (OptionChangeListener listener : listeners) {
|
||||
listener.valueChange(event);
|
||||
protected void fireOptionChangeEvent(T oldValue, T newValue) {
|
||||
for (OptionChangeListener<T> listener : listeners) {
|
||||
listener.valueChange(oldValue, newValue);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Helper method used to sort lists of options by their name
|
||||
*/
|
||||
static class NameSorter<V> implements Comparator<V> {
|
||||
@Override
|
||||
public int compare(@NotNull V o1, @NotNull V o2) {
|
||||
return ((Option)o1).name.compareTo(((Option)o2).name);
|
||||
}
|
||||
}
|
||||
public abstract T getValue();
|
||||
|
||||
protected final String name;
|
||||
protected final String abbrev;
|
||||
@NotNull protected final List<OptionChangeListener> listeners = new ArrayList<>();
|
||||
@NotNull private final List<OptionChangeListener<T>> listeners = new ArrayList<>();
|
||||
}
|
||||
|
@@ -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();
|
||||
}
|
||||
}
|
@@ -18,16 +18,9 @@
|
||||
|
||||
package com.maddyhome.idea.vim.option;
|
||||
|
||||
import java.util.EventListener;
|
||||
|
||||
/**
|
||||
* This interface is used for classes that wish to be notified whenever the value of an option has changed
|
||||
*/
|
||||
public interface OptionChangeListener extends EventListener {
|
||||
/**
|
||||
* The value of the option has changed.
|
||||
*
|
||||
* @param event The change event
|
||||
*/
|
||||
void valueChange(OptionChangeEvent event);
|
||||
public interface OptionChangeListener<T> {
|
||||
void valueChange(T oldValue, T newValue);
|
||||
}
|
||||
|
@@ -34,6 +34,7 @@ import com.maddyhome.idea.vim.helper.Msg
|
||||
import com.maddyhome.idea.vim.helper.hasVisualSelection
|
||||
import com.maddyhome.idea.vim.helper.isBlockCaret
|
||||
import com.maddyhome.idea.vim.helper.mode
|
||||
import com.maddyhome.idea.vim.helper.subMode
|
||||
import com.maddyhome.idea.vim.listener.SelectionVimListenerSuppressor
|
||||
import org.jetbrains.annotations.Contract
|
||||
import java.util.*
|
||||
@@ -44,8 +45,8 @@ import kotlin.math.min
|
||||
object OptionsManager {
|
||||
private val logger = Logger.getInstance(OptionsManager::class.java)
|
||||
|
||||
private val options: MutableMap<String, Option> = mutableMapOf()
|
||||
private val abbrevs: MutableMap<String, Option> = mutableMapOf()
|
||||
private val options: MutableMap<String, Option<*>> = mutableMapOf()
|
||||
private val abbrevs: MutableMap<String, Option<*>> = mutableMapOf()
|
||||
|
||||
val clipboard = addOption(ListOption(ClipboardOptionsData.name, ClipboardOptionsData.abbr, arrayOf(ClipboardOptionsData.ideaput, "autoselect,exclude:cons\\|linux"), null))
|
||||
val digraph = addOption(ToggleOption("digraph", "dg", false))
|
||||
@@ -57,7 +58,7 @@ object OptionsManager {
|
||||
val incsearch = addOption(ToggleOption("incsearch", "is", false))
|
||||
val iskeyword = addOption(KeywordOption("iskeyword", "isk", arrayOf("@", "48-57", "_")))
|
||||
val keymodel = addOption(KeyModelOptionData.option)
|
||||
val lookupKeys = addOption(ListOption("lookupkeys", "lookupkeys", arrayOf(), null))
|
||||
val lookupKeys = addOption(ListOption(LookupKeysData.name, LookupKeysData.name, LookupKeysData.defaultValues, null))
|
||||
val matchpairs = addOption(ListOption("matchpairs", "mps", arrayOf("(:)", "{:}", "[:]"), ".:."))
|
||||
val more = addOption(ToggleOption("more", "more", true))
|
||||
val nrformats = addOption(BoundListOption("nrformats", "nf", arrayOf("octal", "hex"), arrayOf("octal", "hex", "alpha")))
|
||||
@@ -95,7 +96,7 @@ object OptionsManager {
|
||||
/**
|
||||
* Gets an option by the supplied name or short name.
|
||||
*/
|
||||
fun getOption(name: String): Option? = options[name] ?: abbrevs[name]
|
||||
fun getOption(name: String): Option<*>? = options[name] ?: abbrevs[name]
|
||||
|
||||
/**
|
||||
* This parses a set of :set commands. The following types of commands are supported:
|
||||
@@ -144,7 +145,7 @@ object OptionsManager {
|
||||
var error: String? = null
|
||||
var token = ""
|
||||
val tokenizer = StringTokenizer(args)
|
||||
val toShow = mutableListOf<Option>()
|
||||
val toShow = mutableListOf<Option<*>>()
|
||||
while (tokenizer.hasMoreTokens()) {
|
||||
token = tokenizer.nextToken()
|
||||
// See if a space has been backslashed, if no get the rest of the text
|
||||
@@ -290,11 +291,11 @@ object OptionsManager {
|
||||
* @param opts The list of options to display
|
||||
* @param showIntro True if intro is displayed, false if not
|
||||
*/
|
||||
private fun showOptions(editor: Editor?, opts: Collection<Option>, showIntro: Boolean) {
|
||||
private fun showOptions(editor: Editor?, opts: Collection<Option<*>>, showIntro: Boolean) {
|
||||
if (editor == null) return
|
||||
|
||||
val cols = mutableListOf<Option>()
|
||||
val extra = mutableListOf<Option>()
|
||||
val cols = mutableListOf<Option<*>>()
|
||||
val extra = mutableListOf<Option<*>>()
|
||||
for (option in opts) {
|
||||
if (option.toString().length > 19) extra.add(option) else cols.add(option)
|
||||
}
|
||||
@@ -347,7 +348,7 @@ object OptionsManager {
|
||||
}
|
||||
|
||||
@Contract("_ -> param1")
|
||||
fun <T : Option> addOption(option: T): T {
|
||||
fun <T : Option<*>> addOption(option: T): T {
|
||||
options += option.name to option
|
||||
abbrevs += option.abbrev to option
|
||||
return option
|
||||
@@ -468,6 +469,13 @@ object IdeaRefactorMode {
|
||||
editor.selectionModel.removeSelection()
|
||||
}
|
||||
}
|
||||
if (editor.mode.hasVisualSelection && editor.selectionModel.hasSelection()) {
|
||||
val autodetectedSubmode = VimPlugin.getVisualMotion().autodetectVisualSubmode(editor)
|
||||
if (editor.subMode != autodetectedSubmode) {
|
||||
// Update the submode
|
||||
editor.subMode = autodetectedSubmode
|
||||
}
|
||||
}
|
||||
|
||||
if (editor.mode.isBlockCaret) {
|
||||
TemplateManagerImpl.getTemplateState(editor)?.currentVariableRange?.let { segmentRange ->
|
||||
@@ -498,3 +506,17 @@ object IdeaRefactorMode {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
object LookupKeysData {
|
||||
val name = "lookupkeys"
|
||||
val defaultValues = arrayOf(
|
||||
"<Tab>", "<Down>", "<Up>", "<Enter>", "<Left>", "<Right>",
|
||||
"<C-Down>", "<C-Up>",
|
||||
"<PageUp>", "<PageDown>",
|
||||
// New line in vim, but QuickDoc on MacOs
|
||||
"<C-J>",
|
||||
// QuickDoc for non-mac layouts.
|
||||
// Vim: Insert next non-digit literally (same as <Ctrl-V>). Not yet supported (19.01.2020)
|
||||
"<C-Q>"
|
||||
)
|
||||
}
|
||||
|
@@ -55,8 +55,9 @@ public class StringOption extends TextOption {
|
||||
*/
|
||||
@Override
|
||||
public boolean set(String val) {
|
||||
String oldValue = getValue();
|
||||
value = val;
|
||||
fireOptionChangeEvent();
|
||||
fireOptionChangeEvent(oldValue, getValue());
|
||||
|
||||
return true;
|
||||
}
|
||||
@@ -69,8 +70,9 @@ public class StringOption extends TextOption {
|
||||
*/
|
||||
@Override
|
||||
public boolean append(String val) {
|
||||
String oldValue = getValue();
|
||||
value += val;
|
||||
fireOptionChangeEvent();
|
||||
fireOptionChangeEvent(oldValue, getValue());
|
||||
|
||||
return true;
|
||||
}
|
||||
@@ -83,8 +85,9 @@ public class StringOption extends TextOption {
|
||||
*/
|
||||
@Override
|
||||
public boolean prepend(String val) {
|
||||
String oldValue = getValue();
|
||||
value = val + value;
|
||||
fireOptionChangeEvent();
|
||||
fireOptionChangeEvent(oldValue, getValue());
|
||||
|
||||
return true;
|
||||
}
|
||||
@@ -99,8 +102,9 @@ public class StringOption extends TextOption {
|
||||
public boolean remove(@NotNull String val) {
|
||||
int pos = value.indexOf(val);
|
||||
if (pos != -1) {
|
||||
String oldValue = getValue();
|
||||
value = value.substring(0, pos) + value.substring(pos + val.length());
|
||||
fireOptionChangeEvent();
|
||||
fireOptionChangeEvent(oldValue, getValue());
|
||||
|
||||
return true;
|
||||
}
|
||||
@@ -124,8 +128,9 @@ public class StringOption extends TextOption {
|
||||
@Override
|
||||
public void resetDefault() {
|
||||
if (!dflt.equals(value)) {
|
||||
String oldValue = getValue();
|
||||
value = dflt;
|
||||
fireOptionChangeEvent();
|
||||
fireOptionChangeEvent(oldValue, getValue());
|
||||
}
|
||||
}
|
||||
|
||||
|
@@ -18,13 +18,11 @@
|
||||
|
||||
package com.maddyhome.idea.vim.option;
|
||||
|
||||
public abstract class TextOption extends Option {
|
||||
public abstract class TextOption extends Option<String> {
|
||||
TextOption(String name, String abbrev) {
|
||||
super(name, abbrev);
|
||||
}
|
||||
|
||||
public abstract String getValue();
|
||||
|
||||
public abstract boolean set(String val);
|
||||
|
||||
public abstract boolean append(String val);
|
||||
|
@@ -23,7 +23,7 @@ import org.jetbrains.annotations.NotNull;
|
||||
/**
|
||||
* Represents a boolean option
|
||||
*/
|
||||
public class ToggleOption extends Option {
|
||||
public class ToggleOption extends Option<Boolean> {
|
||||
/**
|
||||
* Creates the option
|
||||
*
|
||||
@@ -38,12 +38,8 @@ public class ToggleOption extends Option {
|
||||
this.value = dflt;
|
||||
}
|
||||
|
||||
/**
|
||||
* The option's value
|
||||
*
|
||||
* @return The value
|
||||
*/
|
||||
public boolean getValue() {
|
||||
@Override
|
||||
public Boolean getValue() {
|
||||
return value;
|
||||
}
|
||||
|
||||
@@ -81,7 +77,7 @@ public class ToggleOption extends Option {
|
||||
boolean old = value;
|
||||
value = val;
|
||||
if (val != old) {
|
||||
fireOptionChangeEvent();
|
||||
fireOptionChangeEvent(old, val);
|
||||
}
|
||||
}
|
||||
|
||||
|
@@ -31,7 +31,7 @@ import com.intellij.util.IJSwingUtilities;
|
||||
import com.maddyhome.idea.vim.VimPlugin;
|
||||
import com.maddyhome.idea.vim.ex.CommandParser;
|
||||
import com.maddyhome.idea.vim.ex.ExCommand;
|
||||
import com.maddyhome.idea.vim.ex.LineRange;
|
||||
import com.maddyhome.idea.vim.ex.ranges.LineRange;
|
||||
import com.maddyhome.idea.vim.group.MotionGroup;
|
||||
import com.maddyhome.idea.vim.helper.UiHelper;
|
||||
import com.maddyhome.idea.vim.option.OptionsManager;
|
||||
|
@@ -24,6 +24,7 @@ import com.maddyhome.idea.vim.command.CommandState
|
||||
import com.maddyhome.idea.vim.group.visual.IdeaSelectionControl
|
||||
import com.maddyhome.idea.vim.helper.StringHelper.parseKeys
|
||||
import com.maddyhome.idea.vim.helper.VimBehaviorDiffers
|
||||
import com.maddyhome.idea.vim.helper.subMode
|
||||
import com.maddyhome.idea.vim.listener.VimListenerManager
|
||||
import com.maddyhome.idea.vim.option.SelectModeOptionData
|
||||
import org.jetbrains.plugins.ideavim.VimOptionDefaultAll
|
||||
@@ -31,6 +32,7 @@ import org.jetbrains.plugins.ideavim.VimOptionTestCase
|
||||
import org.jetbrains.plugins.ideavim.VimOptionTestConfiguration
|
||||
import org.jetbrains.plugins.ideavim.VimTestOption
|
||||
import org.jetbrains.plugins.ideavim.VimTestOptionType
|
||||
import org.jetbrains.plugins.ideavim.waitAndAssert
|
||||
import org.jetbrains.plugins.ideavim.waitAndAssertMode
|
||||
|
||||
/**
|
||||
@@ -649,4 +651,61 @@ class IdeaVisualControlTest : VimOptionTestCase(SelectModeOptionData.name) {
|
||||
|
||||
waitAndAssertMode(myFixture, CommandState.Mode.VISUAL)
|
||||
}
|
||||
|
||||
@VimOptionTestConfiguration(VimTestOption(SelectModeOptionData.name, VimTestOptionType.LIST, [""]))
|
||||
fun `test control selection from line to char visual modes`() {
|
||||
configureByText("""
|
||||
A Discovery
|
||||
|
||||
I ${c}found it in a legendary land
|
||||
all rocks and lavender and tufted grass,
|
||||
""".trimIndent())
|
||||
typeText(parseKeys("V"))
|
||||
assertMode(CommandState.Mode.VISUAL)
|
||||
assertSubMode(CommandState.SubMode.VISUAL_LINE)
|
||||
|
||||
myFixture.editor.selectionModel.setSelection(2, 5)
|
||||
IdeaSelectionControl.controlNonVimSelectionChange(myFixture.editor)
|
||||
|
||||
waitAndAssert { myFixture.editor.subMode == CommandState.SubMode.VISUAL_CHARACTER }
|
||||
assertMode(CommandState.Mode.VISUAL)
|
||||
assertSubMode(CommandState.SubMode.VISUAL_CHARACTER)
|
||||
assertCaretsColour()
|
||||
}
|
||||
|
||||
|
||||
/*
|
||||
// [VERSION UPDATE] 191+ Open this test
|
||||
@VimOptionTestConfiguration(VimTestOption(SelectModeOptionData.name, VimTestOptionType.LIST, [""]))
|
||||
fun `test control selection from line to char visual modes in keep mode`() {
|
||||
configureByText("""
|
||||
A Discovery
|
||||
|
||||
I ${c}found it in a legendary land
|
||||
all rocks and lavender and tufted grass,
|
||||
""".trimIndent())
|
||||
|
||||
startDummyTemplate()
|
||||
|
||||
typeText(parseKeys("V"))
|
||||
assertMode(CommandState.Mode.VISUAL)
|
||||
assertSubMode(CommandState.SubMode.VISUAL_LINE)
|
||||
|
||||
myFixture.editor.selectionModel.setSelection(2, 5)
|
||||
IdeaSelectionControl.controlNonVimSelectionChange(myFixture.editor)
|
||||
|
||||
waitAndAssert { myFixture.editor.subMode == CommandState.SubMode.VISUAL_CHARACTER }
|
||||
assertMode(CommandState.Mode.VISUAL)
|
||||
assertSubMode(CommandState.SubMode.VISUAL_CHARACTER)
|
||||
assertCaretsColour()
|
||||
}
|
||||
|
||||
private fun startDummyTemplate() {
|
||||
TemplateManagerImpl.setTemplateTesting(myFixture.testRootDisposable)
|
||||
val templateManager = TemplateManager.getInstance(myFixture.project)
|
||||
val createdTemplate = templateManager.createTemplate("", "")
|
||||
createdTemplate.addVariable(ConstantNode("1"), true)
|
||||
templateManager.startTemplate(myFixture.editor, createdTemplate)
|
||||
}
|
||||
*/
|
||||
}
|
||||
|
@@ -56,6 +56,11 @@ public class KeywordOptionTest extends VimTestCase {
|
||||
assertEquals(",", option.values().get(0));
|
||||
}
|
||||
|
||||
public void testSingleCommaIsAValueAsAppend() throws ExException {
|
||||
option.append(",");
|
||||
assertTrue(option.values().contains(","));
|
||||
}
|
||||
|
||||
public void testSingleNegatedCommaIsAValue() throws ExException {
|
||||
option.set("^,");
|
||||
assertEquals("^,", option.values().get(0));
|
||||
|
Reference in New Issue
Block a user