1
0
mirror of https://github.com/chylex/IntelliJ-IdeaVim.git synced 2025-08-21 07:54:06 +02:00

Compare commits

...

16 Commits

Author SHA1 Message Date
Alex Plate
c1ff6e1498 Prepare to the 0.55 release 2020-01-20 20:15:36 +03:00
Alex Plate
50c04ce71c Change LookupKeys implementation 2020-01-20 15:29:40 +03:00
Alex Plate
bc6ff6bc8e Convert characterHelper to kt 2020-01-19 17:55:15 +03:00
Alex Plate
93bcf2a7e8 Rename .java to .kt 2020-01-19 17:55:14 +03:00
Alex Plate
c3b503adff Set up a mechanism to define the KeyStrokes that should work with active lookup 2020-01-19 17:34:12 +03:00
Alex Plate
ecdcbdda10 Put all range files into the ranges directory 2020-01-16 15:13:56 +03:00
Alex Plate
b97c9a5ed0 Rename .java to .kt 2020-01-16 15:13:55 +03:00
Alex Plate
84a6843a7b Convert ExOutputModel to kt 2020-01-16 12:26:39 +03:00
Alex Plate
17eed7467c Rename .java to .kt 2020-01-16 12:26:39 +03:00
Alex Plate
310ffc849c Convert ExCommand to kt 2020-01-16 12:23:43 +03:00
Alex Plate
3e6756160a Rename .java to .kt 2020-01-16 12:23:43 +03:00
Alex Plate
563e809a2d Put all exceptions to single kt file 2020-01-16 12:18:20 +03:00
Alex Plate
86ec3f3bcd Rename .java to .kt 2020-01-16 12:17:54 +03:00
Alex Plate
b4e0ec282f Convert vim surround plugin to kt 2019-12-26 16:45:42 +03:00
Alex Plate
cbf7dfabcb Rename .java to .kt 2019-12-26 16:45:42 +03:00
Alex Plate
b8eb55d965 Update changes 2019-12-24 11:00:17 +03:00
40 changed files with 1049 additions and 1886 deletions

View File

@@ -16,13 +16,11 @@ It is important to distinguish EAP from traditional pre-release software.
Please note that the quality of EAP versions may at times be way below even Please note that the quality of EAP versions may at times be way below even
usual beta standards. usual beta standards.
[To Be Released] 0.55, 2020-01-20
-------------- --------------
_Available since 0.54.1 EAP:_
**Features:** **Features:**
* Surround and Commentary extensions can be repeated with a dot command ([VIM-1118](https://youtrack.jetbrains.com/issue/VIM-1118)) * Surround and Commentary extensions support repeating with a dot command ([VIM-1118](https://youtrack.jetbrains.com/issue/VIM-1118))
* Support XDG settings standard ([VIM-664](https://youtrack.jetbrains.com/issue/VIM-664)) * Support XDG settings standard ([VIM-664](https://youtrack.jetbrains.com/issue/VIM-664))
* Add option to remove the status bar icon ([VIM-1847](https://youtrack.jetbrains.com/issue/VIM-1847)) * Add option to remove the status bar icon ([VIM-1847](https://youtrack.jetbrains.com/issue/VIM-1847))
@@ -43,10 +41,6 @@ _Available since 0.54.1 EAP:_
* [VIM-1853](https://youtrack.jetbrains.com/issue/VIM-1853) Fix marks for disposed projects * [VIM-1853](https://youtrack.jetbrains.com/issue/VIM-1853) Fix marks for disposed projects
* [VIM-1858](https://youtrack.jetbrains.com/issue/VIM-1858) Fix imap for autocomplete * [VIM-1858](https://youtrack.jetbrains.com/issue/VIM-1858) Fix imap for autocomplete
* [VIM-1362](https://youtrack.jetbrains.com/issue/VIM-1362) Search with confirm doesn't scroll down far enough * [VIM-1362](https://youtrack.jetbrains.com/issue/VIM-1362) Search with confirm doesn't scroll down far enough
_To Be Released:_
**Fixes:**
* [VIM-1875](https://youtrack.jetbrains.com/issue/VIM-1875) Fix `isk` in `~/.ideaivmrc` * [VIM-1875](https://youtrack.jetbrains.com/issue/VIM-1875) Fix `isk` in `~/.ideaivmrc`
* [VIM-1874](https://youtrack.jetbrains.com/issue/VIM-1874) Fix `set clipboard=unnamed` execution from `~/.ideavimrc` * [VIM-1874](https://youtrack.jetbrains.com/issue/VIM-1874) Fix `set clipboard=unnamed` execution from `~/.ideavimrc`
* [VIM-1878](https://youtrack.jetbrains.com/issue/VIM-1878) Fix `c` command after extract method action * [VIM-1878](https://youtrack.jetbrains.com/issue/VIM-1878) Fix `c` command after extract method action

View File

@@ -128,7 +128,7 @@ have `-Duser.home=/my/alternate/home` then IdeaVim will source
`/my/alternate/home/.ideavimrc` instead of `~/.ideavimrc`. `/my/alternate/home/.ideavimrc` instead of `~/.ideavimrc`.
Alternatively, you can set up initialization commands using [XDG](https://specifications.freedesktop.org/basedir-spec/basedir-spec-latest.html) standard. Alternatively, you can set up initialization commands using [XDG](https://specifications.freedesktop.org/basedir-spec/basedir-spec-latest.html) standard.
Put your settings to `$XDG_CONFIG_HOME$/ideavim/ideavimrc` file. [To Be Released] Put your settings to `$XDG_CONFIG_HOME$/ideavim/ideavimrc` file.
Emulated Vim Plugins Emulated Vim Plugins

View File

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

View File

@@ -3,8 +3,10 @@
<id>IdeaVIM</id> <id>IdeaVIM</id>
<change-notes><![CDATA[ <change-notes><![CDATA[
<ul> <ul>
<li>Fix `imap jk <ESC>` for the active autocompletion lookup</li> <li>Support dot command for Surround and Commentary extensions</li>
<li>Surround and Commentary extensions can be repeated with a dot command</li> <li>Support XDG settings standard</li>
<li>Add option to remove the status bar icon</li>
<li>Various bug fixes</li>
</ul> </ul>
<p>See also the complete <a href="https://github.com/JetBrains/ideavim/blob/master/CHANGES.md">changelog</a>.</p> <p>See also the complete <a href="https://github.com/JetBrains/ideavim/blob/master/CHANGES.md">changelog</a>.</p>
]]></change-notes> ]]></change-notes>

View File

@@ -34,7 +34,6 @@ import com.intellij.openapi.util.Key
import com.intellij.ui.KeyStrokeAdapter import com.intellij.ui.KeyStrokeAdapter
import com.maddyhome.idea.vim.KeyHandler import com.maddyhome.idea.vim.KeyHandler
import com.maddyhome.idea.vim.VimPlugin import com.maddyhome.idea.vim.VimPlugin
import com.maddyhome.idea.vim.handler.EditorActionHandlerBase.Companion.parseKeysSet
import com.maddyhome.idea.vim.helper.EditorDataContext import com.maddyhome.idea.vim.helper.EditorDataContext
import com.maddyhome.idea.vim.helper.EditorHelper import com.maddyhome.idea.vim.helper.EditorHelper
import com.maddyhome.idea.vim.helper.StringHelper import com.maddyhome.idea.vim.helper.StringHelper
@@ -42,7 +41,7 @@ import com.maddyhome.idea.vim.helper.inInsertMode
import com.maddyhome.idea.vim.helper.inNormalMode import com.maddyhome.idea.vim.helper.inNormalMode
import com.maddyhome.idea.vim.key.ShortcutOwner import com.maddyhome.idea.vim.key.ShortcutOwner
import com.maddyhome.idea.vim.listener.IdeaSpecifics.aceJumpActive import com.maddyhome.idea.vim.listener.IdeaSpecifics.aceJumpActive
import com.maddyhome.idea.vim.option.OptionsManager.lookupKeys import com.maddyhome.idea.vim.option.OptionsManager
import java.awt.event.InputEvent import java.awt.event.InputEvent
import java.awt.event.KeyEvent import java.awt.event.KeyEvent
import javax.swing.KeyStroke import javax.swing.KeyStroke
@@ -90,7 +89,7 @@ class VimShortcutKeyAction : AnAction(), DumbAware {
if (aceJumpActive()) return false if (aceJumpActive()) return false
val keyCode = keyStroke.keyCode val keyCode = keyStroke.keyCode
if (LookupManager.getActiveLookup(editor) != null) { if (LookupManager.getActiveLookup(editor) != null) {
return isEnabledForLookup(keyStroke) return LookupKeys.isEnabledForLookup(keyStroke)
} }
if (keyCode == KeyEvent.VK_ESCAPE) { if (keyCode == KeyEvent.VK_ESCAPE) {
return isEnabledForEscape(editor) return isEnabledForEscape(editor)
@@ -136,29 +135,6 @@ class VimShortcutKeyAction : AnAction(), DumbAware {
return fileEditorManager.allEditors.any { fileEditor -> editor == EditorUtil.getEditorEx(fileEditor) } return fileEditorManager.allEditors.any { fileEditor -> editor == EditorUtil.getEditorEx(fileEditor) }
} }
private fun isEnabledForLookup(keyStroke: KeyStroke): Boolean {
val notAllowedKeys = parseKeysSet(
"<Tab>", "<Down>", "<Up>", "<Enter>", "<Left>", "<Right>",
// New line in vim, but QuickDoc on MacOs
"<C-J>"
)
for (keys in notAllowedKeys) {
if (keyStroke == keys[0]) {
return false
}
}
// We allow users to set custom keys that will work with lookup in case devs forgot something
val popupActions = lookupKeys
val values = popupActions.values()
for (value in values) {
val keys = StringHelper.parseKeys(value)
if (keys.size >= 1 && keyStroke == keys[0]) {
return false
}
}
return true
}
private fun isShortcutConflict(keyStroke: KeyStroke): Boolean { private fun isShortcutConflict(keyStroke: KeyStroke): Boolean {
return VimPlugin.getKey().getKeymapConflicts(keyStroke).isNotEmpty() return VimPlugin.getKey().getKeymapConflicts(keyStroke).isNotEmpty()
} }
@@ -189,6 +165,29 @@ class VimShortcutKeyAction : AnAction(), DumbAware {
private fun getEditor(e: AnActionEvent): Editor? = e.getData(PlatformDataKeys.EDITOR) private fun getEditor(e: AnActionEvent): Editor? = e.getData(PlatformDataKeys.EDITOR)
/**
* Every time the key pressed with an active lookup, there is a decision:
* should this key be processed by IdeaVim, or by IDE. For example, dot and enter should be processed by IDE, but
* <C-W> by IdeaVim.
*
* The list of keys that should be processed by IDE is stored in [OptionsManager.lookupKeys]. So, we should search
* if the pressed key is presented in this list. The caches are used to speedup the process.
*/
private object LookupKeys {
private var parsedLookupKeys: Set<KeyStroke> = parseLookupKeys()
init {
OptionsManager.lookupKeys.addOptionChangeListener { _, _ ->
parsedLookupKeys = parseLookupKeys()
}
}
fun isEnabledForLookup(keyStroke: KeyStroke): Boolean = keyStroke !in parsedLookupKeys
private fun parseLookupKeys() = OptionsManager.lookupKeys.values()
.map { StringHelper.parseKeys(it) }.filter { it.isNotEmpty() }.map { it.first() }.toSet()
}
companion object { companion object {
@JvmField @JvmField
val VIM_ONLY_EDITOR_KEYS: Set<KeyStroke> = ImmutableSet.builder<KeyStroke>().addAll(getKeyStrokes(KeyEvent.VK_ENTER, 0)).addAll(getKeyStrokes(KeyEvent.VK_ESCAPE, 0)) val VIM_ONLY_EDITOR_KEYS: Set<KeyStroke> = ImmutableSet.builder<KeyStroke>().addAll(getKeyStrokes(KeyEvent.VK_ENTER, 0)).addAll(getKeyStrokes(KeyEvent.VK_ESCAPE, 0))

View File

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

View File

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

View File

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

View File

@@ -1,122 +0,0 @@
/*
* IdeaVim - Vim emulator for IDEs based on the IntelliJ platform
* Copyright (C) 2003-2019 The IdeaVim authors
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package com.maddyhome.idea.vim.ex;
import com.intellij.openapi.actionSystem.DataContext;
import com.intellij.openapi.editor.Caret;
import com.intellij.openapi.editor.Editor;
import com.maddyhome.idea.vim.common.TextRange;
import org.jetbrains.annotations.NotNull;
public class ExCommand {
public ExCommand(@NotNull Ranges ranges, @NotNull String command, @NotNull String argument) {
this.ranges = ranges;
this.argument = argument;
this.command = command;
}
public int getLine(@NotNull Editor editor) {
return ranges.getLine(editor);
}
public int getLine(@NotNull Editor editor, @NotNull Caret caret, @NotNull DataContext context) {
return ranges.getLine(editor, caret, context);
}
public int getCount(@NotNull Editor editor, DataContext context, int defaultCount, boolean checkCount) {
int count = -1;
if (checkCount) {
count = getCountArgument();
}
int res = ranges.getCount(editor, context, count);
if (res == -1) {
res = defaultCount;
}
return res;
}
public int getCount(@NotNull Editor editor, @NotNull Caret caret, @NotNull DataContext context, int defaultCount,
boolean checkCount) {
final int count = ranges.getCount(editor, caret, context, checkCount ? getCountArgument() : -1);
if (count == -1) return defaultCount;
return count;
}
@NotNull
public LineRange getLineRange(@NotNull Editor editor) {
return ranges.getLineRange(editor, -1);
}
public LineRange getLineRange(@NotNull Editor editor, @NotNull Caret caret, @NotNull DataContext context) {
return ranges.getLineRange(editor, caret, context, -1);
}
@NotNull
public TextRange getTextRange(@NotNull Editor editor, DataContext context, boolean checkCount) {
int count = -1;
if (checkCount) {
count = getCountArgument();
}
return ranges.getTextRange(editor, context, count);
}
public TextRange getTextRange(@NotNull Editor editor, @NotNull Caret caret, @NotNull DataContext context,
boolean checkCount) {
return ranges.getTextRange(editor, caret, context, checkCount ? getCountArgument() : -1);
}
private int getCountArgument() {
try {
return Integer.parseInt(argument);
}
catch (NumberFormatException e) {
return -1;
}
}
@NotNull
public String getCommand() {
return command;
}
@NotNull
public String getArgument() {
return argument;
}
public void setArgument(@NotNull String argument) {
this.argument = argument;
}
@NotNull
public Ranges getRanges() {
return ranges;
}
@NotNull
private final Ranges ranges;
@NotNull
private final String command;
@NotNull
private String argument;
}

View File

@@ -0,0 +1,62 @@
/*
* IdeaVim - Vim emulator for IDEs based on the IntelliJ platform
* Copyright (C) 2003-2019 The IdeaVim authors
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package com.maddyhome.idea.vim.ex
import com.intellij.openapi.actionSystem.DataContext
import com.intellij.openapi.editor.Caret
import com.intellij.openapi.editor.Editor
import com.maddyhome.idea.vim.common.TextRange
import com.maddyhome.idea.vim.ex.ranges.LineRange
import com.maddyhome.idea.vim.ex.ranges.Ranges
class ExCommand(val ranges: Ranges, val command: String, var argument: String) {
fun getLine(editor: Editor): Int = ranges.getLine(editor)
fun getLine(editor: Editor, caret: Caret, context: DataContext): Int = ranges.getLine(editor, caret, context)
fun getCount(editor: Editor, context: DataContext?, defaultCount: Int, checkCount: Boolean): Int {
val count = if (checkCount) countArgument else -1
val res = ranges.getCount(editor, count)
return if (res == -1) defaultCount else res
}
fun getCount(editor: Editor, caret: Caret, context: DataContext, defaultCount: Int, checkCount: Boolean): Int {
val count = ranges.getCount(editor, caret, context, if (checkCount) countArgument else -1)
return if (count == -1) defaultCount else count
}
fun getLineRange(editor: Editor): LineRange = ranges.getLineRange(editor, -1)
fun getLineRange(editor: Editor, caret: Caret, context: DataContext): LineRange {
return ranges.getLineRange(editor, caret, context, -1)
}
fun getTextRange(editor: Editor, context: DataContext?, checkCount: Boolean): TextRange {
val count = if (checkCount) countArgument else -1
return ranges.getTextRange(editor, context, count)
}
fun getTextRange(editor: Editor, caret: Caret, context: DataContext, checkCount: Boolean): TextRange {
return ranges.getTextRange(editor, caret, context, if (checkCount) countArgument else -1)
}
private val countArgument: Int
get() = argument.toIntOrNull() ?: -1
}

View File

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

View File

@@ -1,67 +0,0 @@
/*
* IdeaVim - Vim emulator for IDEs based on the IntelliJ platform
* Copyright (C) 2003-2019 The IdeaVim authors
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package com.maddyhome.idea.vim.ex;
import com.intellij.openapi.application.ApplicationManager;
import com.intellij.openapi.editor.Editor;
import com.maddyhome.idea.vim.helper.UserDataManager;
import com.maddyhome.idea.vim.ui.ExOutputPanel;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
/**
* @author vlan
*/
public class ExOutputModel {
@NotNull private final Editor myEditor;
@Nullable private String myText;
private ExOutputModel(@NotNull Editor editor) {
myEditor = editor;
}
@NotNull
public static ExOutputModel getInstance(@NotNull Editor editor) {
ExOutputModel model = UserDataManager.getVimExOutput(editor);
if (model == null) {
model = new ExOutputModel(editor);
UserDataManager.setVimExOutput(editor, model);
}
return model;
}
public void output(@NotNull String text) {
myText = text;
if (!ApplicationManager.getApplication().isUnitTestMode()) {
ExOutputPanel.getInstance(myEditor).setText(text);
}
}
public void clear() {
myText = null;
if (!ApplicationManager.getApplication().isUnitTestMode()) {
ExOutputPanel.getInstance(myEditor).deactivate(false);
}
}
@Nullable
public String getText() {
return myText;
}
}

View File

@@ -0,0 +1,57 @@
/*
* IdeaVim - Vim emulator for IDEs based on the IntelliJ platform
* Copyright (C) 2003-2019 The IdeaVim authors
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package com.maddyhome.idea.vim.ex
import com.intellij.openapi.application.ApplicationManager
import com.intellij.openapi.editor.Editor
import com.maddyhome.idea.vim.helper.vimExOutput
import com.maddyhome.idea.vim.ui.ExOutputPanel
/**
* @author vlan
*/
class ExOutputModel private constructor(private val myEditor: Editor) {
var text: String? = null
private set
fun output(text: String) {
this.text = text
if (!ApplicationManager.getApplication().isUnitTestMode) {
ExOutputPanel.getInstance(myEditor).setText(text)
}
}
fun clear() {
text = null
if (!ApplicationManager.getApplication().isUnitTestMode) {
ExOutputPanel.getInstance(myEditor).deactivate(false)
}
}
companion object {
@JvmStatic
fun getInstance(editor: Editor): ExOutputModel {
var model = editor.vimExOutput
if (model == null) {
model = ExOutputModel(editor)
editor.vimExOutput = model
}
return model
}
}
}

View File

@@ -1,39 +0,0 @@
/*
* IdeaVim - Vim emulator for IDEs based on the IntelliJ platform
* Copyright (C) 2003-2019 The IdeaVim authors
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package com.maddyhome.idea.vim.ex;
/**
* Exception class
*/
public class InvalidArgumentException extends ExException {
/**
* Constructs an <code>InvalidArgumentException</code> with no specified detail message.
*/
public InvalidArgumentException() {
}
/**
* Constructs an <code>InvalidArgumentException</code> with the specified detail message.
*
* @param s the detail message.
*/
public InvalidArgumentException(String s) {
super(s);
}
}

View File

@@ -1,39 +0,0 @@
/*
* IdeaVim - Vim emulator for IDEs based on the IntelliJ platform
* Copyright (C) 2003-2019 The IdeaVim authors
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package com.maddyhome.idea.vim.ex;
/**
* Exception class
*/
public class InvalidRangeException extends ExException {
/**
* Constructs an <code>InvalidRangeException</code> with no specified detail message.
*/
public InvalidRangeException() {
}
/**
* Constructs an <code>InvalidRangeException</code> with the specified detail message.
*
* @param s the detail message.
*/
public InvalidRangeException(String s) {
super(s);
}
}

View File

@@ -1,44 +0,0 @@
/*
* IdeaVim - Vim emulator for IDEs based on the IntelliJ platform
* Copyright (C) 2003-2019 The IdeaVim authors
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package com.maddyhome.idea.vim.ex;
public class LineRange {
public LineRange(int startLine, int endLine) {
if (endLine >= startLine) {
this.startLine = startLine;
this.endLine = endLine;
}
else {
this.startLine = endLine;
this.endLine = startLine;
}
}
public int getStartLine() {
return startLine;
}
public int getEndLine() {
return endLine;
}
private final int startLine;
private final int endLine;
}

View File

@@ -1,39 +0,0 @@
/*
* IdeaVim - Vim emulator for IDEs based on the IntelliJ platform
* Copyright (C) 2003-2019 The IdeaVim authors
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package com.maddyhome.idea.vim.ex;
/**
* Exception class
*/
public class MissingArgumentException extends ExException {
/**
* Constructs an <code>InvalidArgumentException</code> with no specified detail message.
*/
public MissingArgumentException() {
}
/**
* Constructs an <code>InvalidArgumentException</code> with the specified detail message.
*
* @param s the detail message.
*/
public MissingArgumentException(String s) {
super(s);
}
}

View File

@@ -1,39 +0,0 @@
/*
* IdeaVim - Vim emulator for IDEs based on the IntelliJ platform
* Copyright (C) 2003-2019 The IdeaVim authors
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package com.maddyhome.idea.vim.ex;
/**
* Exception class
*/
public class MissingRangeException extends ExException {
/**
* Constructs an <code>InvalidArgumentException</code> with no specified detail message.
*/
public MissingRangeException() {
}
/**
* Constructs an <code>InvalidArgumentException</code> with the specified detail message.
*
* @param s the detail message.
*/
public MissingRangeException(String s) {
super(s);
}
}

View File

@@ -1,39 +0,0 @@
/*
* IdeaVim - Vim emulator for IDEs based on the IntelliJ platform
* Copyright (C) 2003-2019 The IdeaVim authors
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package com.maddyhome.idea.vim.ex;
/**
* Exception class
*/
public class NoArgumentAllowedException extends ExException {
/**
* Constructs an <code>InvalidArgumentException</code> with no specified detail message.
*/
public NoArgumentAllowedException() {
}
/**
* Constructs an <code>InvalidArgumentException</code> with the specified detail message.
*
* @param s the detail message.
*/
public NoArgumentAllowedException(String s) {
super(s);
}
}

View File

@@ -1,39 +0,0 @@
/*
* IdeaVim - Vim emulator for IDEs based on the IntelliJ platform
* Copyright (C) 2003-2019 The IdeaVim authors
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package com.maddyhome.idea.vim.ex;
/**
* Exception class
*/
public class NoRangeAllowedException extends ExException {
/**
* Constructs an <code>NoRangeAllowedException</code> with no specified detail message.
*/
public NoRangeAllowedException() {
}
/**
* Constructs an <code>NoRangeAllowedException</code> with the specified detail message.
*
* @param s the detail message.
*/
public NoRangeAllowedException(String s) {
super(s);
}
}

View File

@@ -1,49 +0,0 @@
/*
* IdeaVim - Vim emulator for IDEs based on the IntelliJ platform
* Copyright (C) 2003-2019 The IdeaVim authors
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package com.maddyhome.idea.vim.ex;
import org.jetbrains.annotations.NotNull;
public class ParseResult {
@NotNull private final Ranges ranges;
@NotNull private final String command;
@NotNull private final String argument;
public ParseResult(@NotNull Ranges ranges, @NotNull String command, @NotNull String argument) {
this.ranges = ranges;
this.argument = argument;
this.command = command;
}
@NotNull
public String getCommand() {
return command;
}
@NotNull
public String getArgument() {
return argument;
}
@NotNull
public Ranges getRanges() {
return ranges;
}
}

View File

@@ -1,46 +0,0 @@
/*
* IdeaVim - Vim emulator for IDEs based on the IntelliJ platform
* Copyright (C) 2003-2019 The IdeaVim authors
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package com.maddyhome.idea.vim.ex;
import com.intellij.openapi.editor.Caret;
import com.intellij.openapi.editor.Editor;
import org.jetbrains.annotations.NotNull;
/**
* Represents an Ex command range
*/
public interface Range {
/**
* Get the line number this range represents
*
* @param editor The editor to get the line for
* @param lastZero True if the last line set represents before the start of the false
* @return The zero based logical line in the editor that the range represents
*/
int getLine(Editor editor, boolean lastZero);
int getLine(@NotNull Editor editor, @NotNull Caret caret, boolean lastZero);
/**
* Should the cursor be moved to this range's line?
*
* @return True if cursor should be moved, false if not
*/
boolean isMove();
}

View File

@@ -1,288 +0,0 @@
/*
* IdeaVim - Vim emulator for IDEs based on the IntelliJ platform
* Copyright (C) 2003-2019 The IdeaVim authors
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package com.maddyhome.idea.vim.ex;
import com.intellij.openapi.actionSystem.DataContext;
import com.intellij.openapi.editor.Caret;
import com.intellij.openapi.editor.Editor;
import com.maddyhome.idea.vim.VimPlugin;
import com.maddyhome.idea.vim.common.TextRange;
import com.maddyhome.idea.vim.ex.range.AbstractRange;
import com.maddyhome.idea.vim.group.MotionGroup;
import com.maddyhome.idea.vim.helper.EditorHelper;
import org.jetbrains.annotations.NotNull;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
/**
* Handles the set of range values entered as part of an Ex command.
*/
public class Ranges {
/**
* Create the empty range list
*/
public Ranges() {
ranges = new ArrayList<>();
}
/**
* Adds a range to the list
*
* @param range The list of ranges to append to the current list
*/
public void addRange(@NotNull Range[] range) {
Collections.addAll(ranges, range);
}
/**
* Gets the number of ranges in the list
*
* @return The range count
*/
public int size() {
return ranges.size();
}
/**
* Sets the default line to be used by this range if no range was actually given by the user. -1 is used to
* mean the current line.
*
* @param line The line or -1 for current line
*/
public void setDefaultLine(int line) {
defaultLine = line;
}
/**
* Gets the line of the last range specified in the range list
*
* @param editor The editor to get the line for
* @return The line number represented by the range
*/
public int getLine(@NotNull Editor editor) {
processRange(editor);
return endLine;
}
public int getLine(@NotNull Editor editor, @NotNull Caret caret, @NotNull DataContext context) {
processRange(editor, caret, context);
return endLine;
}
/**
* Gets the start line number the range represents
*
* @param editor The editor to get the line number for
* @return The starting line number
*/
public int getFirstLine(@NotNull Editor editor) {
processRange(editor);
return startLine;
}
public int getFirstLine(@NotNull Editor editor, @NotNull Caret caret, @NotNull DataContext context) {
processRange(editor, caret, context);
return startLine;
}
/**
* Gets the count for an Ex command. This is either an explicit count enter at the end of the command or the
* end of the specified range.
*
* @param editor The editor to get the count for
* @param context The data context
* @param count The count given at the end of the command or -1 if no such count (use end line)
* @return count if count != -1, else return end line of range
*/
public int getCount(@NotNull Editor editor, DataContext context, int count) {
if (count == -1) {
return getLine(editor);
}
else {
return count;
}
}
public int getCount(@NotNull Editor editor, @NotNull Caret caret, @NotNull DataContext context, int count) {
if (count == -1) return getLine(editor, caret, context);
return count;
}
/**
* Gets the line range represented by this range. If a count is given, the range is the range end line through
* count-1 lines. If no count is given (-1), the range is the range given by the user.
*
* @param editor The editor to get the range for
* @param count The count given at the end of the command or -1 if no such count
* @return The line range
*/
@NotNull
public LineRange getLineRange(@NotNull Editor editor, int count) {
processRange(editor);
int end;
int start;
if (count == -1) {
end = endLine;
start = startLine;
}
else {
start = endLine;
end = start + count - 1;
}
return new LineRange(start, end);
}
public LineRange getLineRange(@NotNull Editor editor, @NotNull Caret caret, @NotNull DataContext context, int count) {
processRange(editor, caret, context);
if (count == -1) return new LineRange(startLine, endLine);
return new LineRange(endLine, endLine + count - 1);
}
/**
* Gets the text range represented by this range. If a count is given, the range is the range end line through
* count-1 lines. If no count is given (-1), the range is the range given by the user. The text range is based
* on the line range but this is character based from the start of the first line to the end of the last line.
*
* @param editor The editor to get the range for
* @param context The data context
* @param count The count given at the end of the command or -1 if no such count
* @return The text range
*/
@NotNull
public TextRange getTextRange(@NotNull Editor editor, DataContext context, int count) {
LineRange lr = getLineRange(editor, count);
int start = EditorHelper.getLineStartOffset(editor, lr.getStartLine());
int end = EditorHelper.getLineEndOffset(editor, lr.getEndLine(), true) + 1;
return new TextRange(start, Math.min(end, EditorHelper.getFileSize(editor)));
}
public TextRange getTextRange(@NotNull Editor editor, @NotNull Caret caret, @NotNull DataContext context, int count) {
final LineRange lineRange = getLineRange(editor, caret, context, count);
final int start = EditorHelper.getLineStartOffset(editor, lineRange.getStartLine());
final int end = EditorHelper.getLineEndOffset(editor, lineRange.getEndLine(), true) + 1;
return new TextRange(start, Math.min(end, EditorHelper.getFileSize(editor)));
}
/**
* Helper method to get the text range for the current cursor line
*
* @param editor The editor to get the range for
* @param context The data context
* @return The range of the current line
*/
@NotNull
public static TextRange getCurrentLineRange(@NotNull Editor editor, DataContext context) {
Ranges ranges = new Ranges();
return ranges.getTextRange(editor, context, -1);
}
/**
* Helper method to get the text range for the current file
*
* @param editor The editor to get the range for
* @param context The data context
* @return The range of the current file
*/
@NotNull
public static TextRange getFileTextRange(@NotNull Editor editor, DataContext context) {
Ranges ranges = new Ranges();
final Range[] range = AbstractRange.createRange("%", 0, false);
if (range != null) {
ranges.addRange(range);
}
return ranges.getTextRange(editor, context, -1);
}
/**
* Processes the list of ranges and calculates the start and end lines of the range
*
* @param editor The editor to get the lines for
*/
private void processRange(@NotNull Editor editor) {
// Already done
if (done) return;
// Start with the range being the current line
startLine = defaultLine == -1 ? editor.getCaretModel().getLogicalPosition().line : defaultLine;
endLine = startLine;
boolean lastZero = false;
// Now process each range, moving the cursor if appropriate
for (Range range : ranges) {
startLine = endLine;
endLine = range.getLine(editor, lastZero);
if (range.isMove()) {
MotionGroup.moveCaret(editor, editor.getCaretModel().getPrimaryCaret(),
VimPlugin.getMotion().moveCaretToLine(editor, endLine, editor.getCaretModel().getPrimaryCaret()));
}
// Did that last range represent the start of the file?
lastZero = (endLine < 0);
count++;
}
// If only one range given, make the start and end the same
if (count == 1) {
startLine = endLine;
}
done = true;
}
private void processRange(@NotNull Editor editor, @NotNull Caret caret, @NotNull DataContext context) {
startLine = defaultLine == -1 ? caret.getLogicalPosition().line : defaultLine;
endLine = startLine;
boolean lastZero = false;
for (Range range : ranges) {
startLine = endLine;
endLine = range.getLine(editor, caret, lastZero);
if (range.isMove()) MotionGroup.moveCaret(editor, caret, VimPlugin.getMotion().moveCaretToLine(editor, endLine, editor.getCaretModel().getPrimaryCaret()));
lastZero = endLine < 0;
++count;
}
if (count == 1) startLine = endLine;
count = 0;
}
@NotNull
public String toString() {
return "Ranges[ranges=" + ranges + "]";
}
private int startLine = 0;
private int endLine = 0;
private int count = 0;
private int defaultLine = -1;
private boolean done = false;
@NotNull
private final List<Range> ranges;
}

View File

@@ -25,6 +25,7 @@ import com.maddyhome.idea.vim.VimPlugin
import com.maddyhome.idea.vim.command.SelectionType import com.maddyhome.idea.vim.command.SelectionType
import com.maddyhome.idea.vim.common.TextRange import com.maddyhome.idea.vim.common.TextRange
import com.maddyhome.idea.vim.ex.* import com.maddyhome.idea.vim.ex.*
import com.maddyhome.idea.vim.ex.ranges.LineRange
import com.maddyhome.idea.vim.group.copy.PutData import com.maddyhome.idea.vim.group.copy.PutData
import com.maddyhome.idea.vim.helper.EditorHelper import com.maddyhome.idea.vim.helper.EditorHelper
import com.maddyhome.idea.vim.helper.MessageHelper import com.maddyhome.idea.vim.helper.MessageHelper

View File

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

View File

@@ -1,154 +0,0 @@
/*
* IdeaVim - Vim emulator for IDEs based on the IntelliJ platform
* Copyright (C) 2003-2019 The IdeaVim authors
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package com.maddyhome.idea.vim.ex.range;
import com.intellij.openapi.editor.Caret;
import com.intellij.openapi.editor.Editor;
import com.maddyhome.idea.vim.ex.Range;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
/**
* Base for all Ex command ranges
*/
public abstract class AbstractRange implements Range {
/**
* Factory method used to create an appropriate range based on the range text
*
* @param str The range text
* @param offset Any offset specified after the range
* @param move True if cursor should be moved to range line
* @return The ranges appropriate to the text
*/
@Nullable
public static Range[] createRange(@NotNull String str, int offset, boolean move) {
// Current line
if (str.equals(".") || str.length() == 0) {
return new Range[]{new LineNumberRange(offset, move)};
}
// All lines
else if (str.equals("%")) {
return new Range[]{
new LineNumberRange(0, 0, move),
new LineNumberRange(LineNumberRange.LAST_LINE, offset, move)
};
}
// Last line
else if (str.equals("$")) {
return new Range[]{new LineNumberRange(LineNumberRange.LAST_LINE, offset, move)};
}
// Mark like
else if (str.startsWith("'") && str.length() == 2) {
return new Range[]{new MarkRange(str.charAt(1), offset, move)};
}
// Pattern
else if (str.startsWith("/") || str.startsWith("\\/") || str.startsWith("\\&")) {
return new Range[]{new SearchRange(str, offset, move)};
}
// Pattern
else if (str.startsWith("?") || str.startsWith("\\?")) {
return new Range[]{new SearchRange(str, offset, move)};
}
// Specific line number (1 based)
else {
try {
int line = Integer.parseInt(str) - 1;
return new Range[]{new LineNumberRange(line, offset, move)};
}
catch (NumberFormatException e) {
// Ignore - we'll send back bad range later.
}
}
// User entered an invalid range.
return null;
}
/**
* Create the range
*
* @param offset The line offset
* @param move True if cursor moved
*/
public AbstractRange(int offset, boolean move) {
this.offset = offset;
this.move = move;
}
/**
* Gets the line offset
*
* @return The line offset
*/
protected int getOffset() {
return offset;
}
/**
* Should the cursor move
*
* @return True if cursor should move, false if not
*/
@Override
public boolean isMove() {
return move;
}
/**
* Gets the line number (0 based) specificied by this range. Includes the offset.
*
* @param editor The editor to get the line for
* @param lastZero True if last line was set to start of file
* @return The zero based line number, -1 if unable to get the line number
*/
@Override
public int getLine(Editor editor, boolean lastZero) {
int line = getRangeLine(editor, lastZero);
return line + offset;
}
@Override
public int getLine(@NotNull Editor editor, @NotNull Caret caret, boolean lastZero) {
if (offset == 0) return getRangeLine(editor, lastZero);
return getRangeLine(editor, caret, lastZero) + offset;
}
@NotNull
public String toString() {
return "AbstractRange" + "{offset=" + offset + ", move=" + move + '}';
}
/**
* Gets the line number specified by this range without regard to any offset.
*
* @param editor The editor to get the line for
* @param lastZero True if last line was set to start of file
* @return The zero based line number, -1 if inable to get the line number
*/
protected abstract int getRangeLine(Editor editor, boolean lastZero);
protected abstract int getRangeLine(@NotNull Editor editor, @NotNull Caret caret,
boolean lastZero);
protected final int offset;
protected final boolean move;
}

View File

@@ -1,92 +0,0 @@
/*
* IdeaVim - Vim emulator for IDEs based on the IntelliJ platform
* Copyright (C) 2003-2019 The IdeaVim authors
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package com.maddyhome.idea.vim.ex.range;
import com.intellij.openapi.editor.Caret;
import com.intellij.openapi.editor.Editor;
import com.maddyhome.idea.vim.helper.EditorHelper;
import org.jetbrains.annotations.NotNull;
/**
* Represents a specific line, the current line, or the last line of a file
*/
public class LineNumberRange extends AbstractRange {
public static final int CURRENT_LINE = -99999999;
public static final int LAST_LINE = -99999998;
/**
* Create a range for the current line
*
* @param offset The range offset
* @param move True if cursor should be moved
*/
public LineNumberRange(int offset, boolean move) {
super(offset, move);
this.line = CURRENT_LINE;
}
/**
* Create a range for the given line
*
* @param offset The range offset
* @param move True if cursor should be moved
*/
public LineNumberRange(int line, int offset, boolean move) {
super(offset, move);
this.line = line;
}
/**
* Gets the line number specified by this range without regard to any offset.
*
* @param editor The editor to get the line for
* @param lastZero True if last line was set to start of file
* @return The zero based line number, -1 for start of file
*/
@Override
protected int getRangeLine(@NotNull Editor editor, boolean lastZero) {
if (line == CURRENT_LINE) {
line = editor.getCaretModel().getLogicalPosition().line;
}
else if (line == LAST_LINE) {
line = EditorHelper.getLineCount(editor) - 1;
}
return line;
}
@Override
protected int getRangeLine(@NotNull Editor editor, @NotNull Caret caret,
boolean lastZero) {
if (line == LAST_LINE) line = EditorHelper.getLineCount(editor) - 1;
else line = caret.getLogicalPosition().line;
return line;
}
@NotNull
public String toString() {
return "LineNumberRange[" + "line=" + line + ", " + super.toString() + "]";
}
private int line;
}

View File

@@ -1,76 +0,0 @@
/*
* IdeaVim - Vim emulator for IDEs based on the IntelliJ platform
* Copyright (C) 2003-2019 The IdeaVim authors
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package com.maddyhome.idea.vim.ex.range;
import com.intellij.openapi.editor.Caret;
import com.intellij.openapi.editor.Editor;
import com.maddyhome.idea.vim.VimPlugin;
import com.maddyhome.idea.vim.common.Mark;
import org.jetbrains.annotations.NotNull;
/**
* Represents the line specified by a mark
*/
public class MarkRange extends AbstractRange {
/**
* Create the mark range
*
* @param mark The mark name
* @param offset The range offset
* @param move True if cursor should be moved
*/
public MarkRange(char mark, int offset, boolean move) {
super(offset, move);
this.mark = mark;
}
/**
* Gets the line number specified by this range without regard to any offset.
*
* @param editor The editor to get the line for
* @param lastZero True if last line was set to start of file
* @return The zero based line number, -1 if there is no such mark set in the file
*/
@Override
public int getRangeLine(@NotNull Editor editor, boolean lastZero) {
Mark mark = VimPlugin.getMark().getFileMark(editor, this.mark);
if (mark != null) {
return mark.getLogicalLine();
}
else {
return -1;
}
}
@Override
protected int getRangeLine(@NotNull Editor editor, @NotNull Caret caret,
boolean lastZero) {
return getRangeLine(editor, lastZero);
}
@NotNull
public String toString() {
return "MarkRange[" + "mark=" + mark + ", " + super.toString() + "]";
}
private final char mark;
}

View File

@@ -1,170 +0,0 @@
/*
* IdeaVim - Vim emulator for IDEs based on the IntelliJ platform
* Copyright (C) 2003-2019 The IdeaVim authors
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package com.maddyhome.idea.vim.ex.range;
import com.intellij.openapi.diagnostic.Logger;
import com.intellij.openapi.editor.Caret;
import com.intellij.openapi.editor.Editor;
import com.maddyhome.idea.vim.VimPlugin;
import com.maddyhome.idea.vim.command.CommandFlags;
import org.jetbrains.annotations.NotNull;
import java.util.ArrayList;
import java.util.EnumSet;
import java.util.List;
import java.util.StringTokenizer;
/**
* Represents a range given by a search pattern. The pattern can be '\\/', '\\?', '\\&amp;', /{pattern}/,
* or ?{pattern}?. The last two can be repeated 0 or more times after any of the others.
*/
public class SearchRange extends AbstractRange {
/**
* Create the pattern range
*
* @param pattern The text of the pattern. Each subpattern must be separated by the nul character (\\u0000)
* @param offset The range offset
* @param move True if the cursor should be moved
*/
public SearchRange(String pattern, int offset, boolean move) {
super(offset, move);
setPattern(pattern);
}
/**
* Parses the pattern into a list of subpatterns and flags
*
* @param pattern The full search pattern
*/
private void setPattern(String pattern) {
if (logger.isDebugEnabled()) {
logger.debug("pattern=" + pattern);
}
StringTokenizer tok = new StringTokenizer(pattern, "\u0000");
while (tok.hasMoreTokens()) {
String pat = tok.nextToken();
switch (pat) {
case "\\/":
patterns.add(VimPlugin.getSearch().getLastSearch());
flags.add(EnumSet.of(CommandFlags.FLAG_SEARCH_FWD));
break;
case "\\?":
patterns.add(VimPlugin.getSearch().getLastSearch());
flags.add(EnumSet.of(CommandFlags.FLAG_SEARCH_REV));
break;
case "\\&":
patterns.add(VimPlugin.getSearch().getLastPattern());
flags.add(EnumSet.of(CommandFlags.FLAG_SEARCH_FWD));
break;
default:
if (pat.charAt(0) == '/') {
flags.add(EnumSet.of(CommandFlags.FLAG_SEARCH_FWD));
}
else {
flags.add(EnumSet.of(CommandFlags.FLAG_SEARCH_REV));
}
pat = pat.substring(1);
if (pat.charAt(pat.length() - 1) == pat.charAt(0)) {
pat = pat.substring(0, pat.length() - 1);
}
patterns.add(pat);
break;
}
}
}
/**
* Gets the line number specified by this range without regard to any offset.
*
* @param editor The editor to get the line for
* @param lastZero True if last line was set to start of file
* @return The zero based line number, -1 if the text was not found
*/
@Override
protected int getRangeLine(@NotNull Editor editor, boolean lastZero) {
// Each subsequent pattern is searched for starting in the line after the previous search match
int line = editor.getCaretModel().getLogicalPosition().line;
int pos = -1;
for (int i = 0; i < patterns.size(); i++) {
String pattern = patterns.get(i);
EnumSet<CommandFlags> flag = flags.get(i);
if (flag.contains(CommandFlags.FLAG_SEARCH_FWD) && !lastZero) {
pos = VimPlugin.getMotion().moveCaretToLineEnd(editor, line, true);
}
else {
pos = VimPlugin.getMotion().moveCaretToLineStart(editor, line);
}
pos = VimPlugin.getSearch().search(editor, pattern, pos, 1, flag);
if (pos == -1) {
break;
}
else {
line = editor.offsetToLogicalPosition(pos).line;
}
}
if (pos != -1) {
return line;
}
else {
return -1;
}
}
@Override
protected int getRangeLine(@NotNull Editor editor, @NotNull Caret caret,
boolean lastZero) {
int line = caret.getLogicalPosition().line;
int offset = -1;
for (int i = 0; i < patterns.size(); i++) {
final String pattern = patterns.get(i);
final EnumSet<CommandFlags> flag = flags.get(i);
offset = VimPlugin.getSearch().search(editor, pattern, getSearchOffset(editor, line, flag, lastZero), 1, flag);
if (offset == -1) break;
line = editor.offsetToLogicalPosition(offset).line;
}
return offset != -1 ? line : -1;
}
private int getSearchOffset(@NotNull Editor editor, int line, EnumSet<CommandFlags> flag, boolean lastZero) {
if (flag.contains(CommandFlags.FLAG_SEARCH_FWD) && !lastZero) {
return VimPlugin.getMotion().moveCaretToLineEnd(editor, line, true);
}
return VimPlugin.getMotion().moveCaretToLineStart(editor, line);
}
@NotNull
public String toString() {
return "SearchRange[" + "patterns=" + patterns + ", " + super.toString() + "]";
}
@NotNull
private final List<String> patterns = new ArrayList<>();
@NotNull
private final List<EnumSet<CommandFlags>> flags = new ArrayList<>();
private static final Logger logger = Logger.getInstance(SearchRange.class.getName());
}

View File

@@ -0,0 +1,296 @@
/*
* IdeaVim - Vim emulator for IDEs based on the IntelliJ platform
* Copyright (C) 2003-2019 The IdeaVim authors
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package com.maddyhome.idea.vim.ex.ranges
import com.intellij.openapi.diagnostic.debug
import com.intellij.openapi.diagnostic.logger
import com.intellij.openapi.editor.Caret
import com.intellij.openapi.editor.Editor
import com.maddyhome.idea.vim.VimPlugin
import com.maddyhome.idea.vim.command.CommandFlags
import com.maddyhome.idea.vim.helper.EditorHelper
import com.maddyhome.idea.vim.helper.enumSetOf
import java.util.*
/**
* Base for all Ex command ranges
*/
sealed class Range(
// Line offset
protected val offset: Int,
val isMove: Boolean
) {
/**
* Gets the line number (0 based) specificied by this range. Includes the offset.
*
* @param editor The editor to get the line for
* @param lastZero True if last line was set to start of file
* @return The zero based line number, -1 if unable to get the line number
*/
fun getLine(editor: Editor, lastZero: Boolean): Int {
val line = getRangeLine(editor, lastZero)
return line + offset
}
fun getLine(editor: Editor, caret: Caret, lastZero: Boolean): Int {
return if (offset == 0) getRangeLine(editor, lastZero) else getRangeLine(editor, caret, lastZero) + offset
}
override fun toString(): String = "Range{offset=$offset, move=$isMove}"
/**
* Gets the line number specified by this range without regard to any offset.
*
* @param editor The editor to get the line for
* @param lastZero True if last line was set to start of file
* @return The zero based line number, -1 if inable to get the line number
*/
protected abstract fun getRangeLine(editor: Editor, lastZero: Boolean): Int
protected abstract fun getRangeLine(editor: Editor, caret: Caret, lastZero: Boolean): Int
companion object {
/**
* Factory method used to create an appropriate range based on the range text
*
* @param str The range text
* @param offset Any offset specified after the range
* @param move True if cursor should be moved to range line
* @return The ranges appropriate to the text
*/
@JvmStatic
fun createRange(str: String, offset: Int, move: Boolean): Array<Range>? {
// Current line
if (str.isEmpty() || str == ".") {
return arrayOf(LineNumberRange(offset, move))
} else if (str == "%") {
return arrayOf(
LineNumberRange(0, 0, move),
LineNumberRange(LineNumberRange.LAST_LINE, offset, move)
)
} else if (str == "$") {
return arrayOf(LineNumberRange(LineNumberRange.LAST_LINE, offset, move))
} else if (str.startsWith("'") && str.length == 2) {
return arrayOf(MarkRange(str[1], offset, move))
} else if (str.startsWith("/") || str.startsWith("\\/") || str.startsWith("\\&")) {
return arrayOf(SearchRange(str, offset, move))
} else if (str.startsWith("?") || str.startsWith("\\?")) {
return arrayOf(SearchRange(str, offset, move))
} else {
try {
val line = str.toInt() - 1
return arrayOf(LineNumberRange(line, offset, move))
} catch (e: NumberFormatException) { // Ignore - we'll send back bad range later.
}
}
// User entered an invalid range.
return null
}
}
}
/**
* Represents a specific line, the current line, or the last line of a file
*/
class LineNumberRange : Range {
/**
* Create a range for the current line
*
* @param offset The range offset
* @param move True if cursor should be moved
*/
constructor(offset: Int, move: Boolean) : super(offset, move) {
line = CURRENT_LINE
}
/**
* Create a range for the given line
*
* @param offset The range offset
* @param move True if cursor should be moved
*/
constructor(line: Int, offset: Int, move: Boolean) : super(offset, move) {
this.line = line
}
/**
* Gets the line number specified by this range without regard to any offset.
*
* @param editor The editor to get the line for
* @param lastZero True if last line was set to start of file
* @return The zero based line number, -1 for start of file
*/
override fun getRangeLine(editor: Editor, lastZero: Boolean): Int {
if (line == CURRENT_LINE) {
line = editor.caretModel.logicalPosition.line
} else if (line == LAST_LINE) {
line = EditorHelper.getLineCount(editor) - 1
}
return line
}
override fun getRangeLine(editor: Editor, caret: Caret,
lastZero: Boolean): Int {
line = if (line == LAST_LINE) EditorHelper.getLineCount(editor) - 1 else caret.logicalPosition.line
return line
}
override fun toString(): String = "LineNumberRange[line=$line, ${super.toString()}]"
private var line: Int
companion object {
const val CURRENT_LINE = -99999999
const val LAST_LINE = -99999998
}
}
/**
* Represents the line specified by a mark
*/
class MarkRange(private val mark: Char, offset: Int, move: Boolean) : Range(offset, move) {
/**
* Gets the line number specified by this range without regard to any offset.
*
* @param editor The editor to get the line for
* @param lastZero True if last line was set to start of file
* @return The zero based line number, -1 if there is no such mark set in the file
*/
override fun getRangeLine(editor: Editor, lastZero: Boolean): Int {
val mark = VimPlugin.getMark().getFileMark(editor, mark)
return mark?.logicalLine ?: -1
}
override fun getRangeLine(editor: Editor, caret: Caret, lastZero: Boolean): Int = getRangeLine(editor, lastZero)
override fun toString(): String = "MarkRange[mark=$mark, ${super.toString()}]"
}
/**
* Represents a range given by a search pattern. The pattern can be '\\/', '\\?', '\\&amp;', /{pattern}/,
* or ?{pattern}?. The last two can be repeated 0 or more times after any of the others.
*/
class SearchRange(pattern: String, offset: Int, move: Boolean) : Range(offset, move) {
/**
* Parses the pattern into a list of subpatterns and flags
*
* @param pattern The full search pattern
*/
private fun setPattern(pattern: String) {
logger.debug { "pattern=$pattern" }
val tok = StringTokenizer(pattern, "\u0000")
while (tok.hasMoreTokens()) {
var pat = tok.nextToken()
when (pat) {
"\\/" -> {
patterns.add(VimPlugin.getSearch().lastSearch)
flags.add(enumSetOf(CommandFlags.FLAG_SEARCH_FWD))
}
"\\?" -> {
patterns.add(VimPlugin.getSearch().lastSearch)
flags.add(enumSetOf(CommandFlags.FLAG_SEARCH_REV))
}
"\\&" -> {
patterns.add(VimPlugin.getSearch().lastPattern)
flags.add(enumSetOf(CommandFlags.FLAG_SEARCH_FWD))
}
else -> {
if (pat[0] == '/') {
flags.add(enumSetOf(CommandFlags.FLAG_SEARCH_FWD))
} else {
flags.add(enumSetOf(CommandFlags.FLAG_SEARCH_REV))
}
pat = pat.substring(1)
if (pat.last() == pat[0]) {
pat = pat.substring(0, pat.length - 1)
}
patterns.add(pat)
}
}
}
}
/**
* Gets the line number specified by this range without regard to any offset.
*
* @param editor The editor to get the line for
* @param lastZero True if last line was set to start of file
* @return The zero based line number, -1 if the text was not found
*/
override fun getRangeLine(editor: Editor, lastZero: Boolean): Int { // Each subsequent pattern is searched for starting in the line after the previous search match
var line = editor.caretModel.logicalPosition.line
var pos = -1
for (i in patterns.indices) {
val pattern = patterns[i]
val flag = flags[i]
pos = if (CommandFlags.FLAG_SEARCH_FWD in flag && !lastZero) {
VimPlugin.getMotion().moveCaretToLineEnd(editor, line, true)
} else {
VimPlugin.getMotion().moveCaretToLineStart(editor, line)
}
pos = VimPlugin.getSearch().search(editor, pattern!!, pos, 1, flag)
line = if (pos == -1) {
break
} else {
editor.offsetToLogicalPosition(pos).line
}
}
return if (pos != -1) line else -1
}
override fun getRangeLine(editor: Editor, caret: Caret,
lastZero: Boolean): Int {
var line = caret.logicalPosition.line
var offset = -1
for (i in patterns.indices) {
val pattern = patterns[i]
val flag = flags[i]
offset = VimPlugin.getSearch().search(editor, pattern!!, getSearchOffset(editor, line, flag, lastZero), 1, flag)
if (offset == -1) break
line = editor.offsetToLogicalPosition(offset).line
}
return if (offset != -1) line else -1
}
private fun getSearchOffset(editor: Editor, line: Int, flag: EnumSet<CommandFlags>, lastZero: Boolean): Int {
return if (flag.contains(CommandFlags.FLAG_SEARCH_FWD) && !lastZero) {
VimPlugin.getMotion().moveCaretToLineEnd(editor, line, true)
} else VimPlugin.getMotion().moveCaretToLineStart(editor, line)
}
override fun toString(): String = "SearchRange[patterns=$patterns, ${super.toString()}]"
private val patterns: MutableList<String?> = mutableListOf()
private val flags: MutableList<EnumSet<CommandFlags>> = mutableListOf()
companion object {
private val logger = logger<SearchRange>()
}
/**
* Create the pattern range
*
* @param pattern The text of the pattern. Each subpattern must be separated by the nul character (\\u0000)
* @param offset The range offset
* @param move True if the cursor should be moved
*/
init {
setPattern(pattern)
}
}

View File

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

View File

@@ -0,0 +1,191 @@
/*
* IdeaVim - Vim emulator for IDEs based on the IntelliJ platform
* Copyright (C) 2003-2019 The IdeaVim authors
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package com.maddyhome.idea.vim.ex.ranges
import com.intellij.openapi.actionSystem.DataContext
import com.intellij.openapi.editor.Caret
import com.intellij.openapi.editor.Editor
import com.maddyhome.idea.vim.VimPlugin
import com.maddyhome.idea.vim.common.TextRange
import com.maddyhome.idea.vim.group.MotionGroup
import com.maddyhome.idea.vim.helper.EditorHelper
import kotlin.math.min
/**
* Handles the set of range values entered as part of an Ex command.
*/
class Ranges {
/** Adds a range to the list */
fun addRange(range: Array<Range>) {
ranges.addAll(range)
}
/** Gets the number of ranges in the list */
fun size(): Int = ranges.size
/**
* Sets the default line to be used by this range if no range was actually given by the user. -1 is used to
* mean the current line.
*
* @param line The line or -1 for current line
*/
fun setDefaultLine(line: Int) {
defaultLine = line
}
/**
* Gets the line of the last range specified in the range list
*
* @param editor The editor to get the line for
* @return The line number represented by the range
*/
fun getLine(editor: Editor): Int {
processRange(editor)
return endLine
}
fun getLine(editor: Editor, caret: Caret, context: DataContext): Int {
processRange(editor, caret, context)
return endLine
}
fun getFirstLine(editor: Editor, caret: Caret, context: DataContext): Int {
processRange(editor, caret, context)
return startLine
}
/**
* Gets the count for an Ex command. This is either an explicit count enter at the end of the command or the
* end of the specified range.
*
* @param editor The editor to get the count for
* @param count The count given at the end of the command or -1 if no such count (use end line)
* @return count if count != -1, else return end line of range
*/
fun getCount(editor: Editor, count: Int): Int = if (count == -1) getLine(editor) else count
fun getCount(editor: Editor, caret: Caret, context: DataContext, count: Int): Int {
return if (count == -1) getLine(editor, caret, context) else count
}
/**
* Gets the line range represented by this range. If a count is given, the range is the range end line through
* count-1 lines. If no count is given (-1), the range is the range given by the user.
*
* @param editor The editor to get the range for
* @param count The count given at the end of the command or -1 if no such count
* @return The line range
*/
fun getLineRange(editor: Editor, count: Int): LineRange {
processRange(editor)
val end: Int
val start: Int
if (count == -1) {
end = endLine
start = startLine
} else {
start = endLine
end = start + count - 1
}
return LineRange(start, end)
}
fun getLineRange(editor: Editor, caret: Caret, context: DataContext, count: Int): LineRange {
processRange(editor, caret, context)
return if (count == -1) LineRange(startLine, endLine) else LineRange(endLine, endLine + count - 1)
}
/**
* Gets the text range represented by this range. If a count is given, the range is the range end line through
* count-1 lines. If no count is given (-1), the range is the range given by the user. The text range is based
* on the line range but this is character based from the start of the first line to the end of the last line.
*
* @param editor The editor to get the range for
* @param context The data context
* @param count The count given at the end of the command or -1 if no such count
* @return The text range
*/
fun getTextRange(editor: Editor, context: DataContext?, count: Int): TextRange {
val lr = getLineRange(editor, count)
val start = EditorHelper.getLineStartOffset(editor, lr.startLine)
val end = EditorHelper.getLineEndOffset(editor, lr.endLine, true) + 1
return TextRange(start, min(end, EditorHelper.getFileSize(editor)))
}
fun getTextRange(editor: Editor, caret: Caret, context: DataContext, count: Int): TextRange {
val lineRange = getLineRange(editor, caret, context, count)
val start = EditorHelper.getLineStartOffset(editor, lineRange.startLine)
val end = EditorHelper.getLineEndOffset(editor, lineRange.endLine, true) + 1
return TextRange(start, min(end, EditorHelper.getFileSize(editor)))
}
/**
* Processes the list of ranges and calculates the start and end lines of the range
*
* @param editor The editor to get the lines for
*/
private fun processRange(editor: Editor) {
// Already done
if (done) return
// Start with the range being the current line
startLine = if (defaultLine == -1) editor.caretModel.logicalPosition.line else defaultLine
endLine = startLine
var lastZero = false
// Now process each range, moving the cursor if appropriate
for (range in ranges) {
startLine = endLine
endLine = range.getLine(editor, lastZero)
if (range.isMove) {
MotionGroup.moveCaret(editor, editor.caretModel.primaryCaret,
VimPlugin.getMotion().moveCaretToLine(editor, endLine, editor.caretModel.primaryCaret))
}
// Did that last range represent the start of the file?
lastZero = endLine < 0
count++
}
// If only one range given, make the start and end the same
if (count == 1) {
startLine = endLine
}
done = true
}
private fun processRange(editor: Editor, caret: Caret, context: DataContext) {
startLine = if (defaultLine == -1) caret.logicalPosition.line else defaultLine
endLine = startLine
var lastZero = false
for (range in ranges) {
startLine = endLine
endLine = range.getLine(editor, caret, lastZero)
if (range.isMove) MotionGroup.moveCaret(editor, caret, VimPlugin.getMotion().moveCaretToLine(editor, endLine, editor.caretModel.primaryCaret))
lastZero = endLine < 0
++count
}
if (count == 1) startLine = endLine
count = 0
}
override fun toString(): String = "Ranges[ranges=$ranges]"
private var startLine = 0
private var endLine = 0
private var count = 0
private var defaultLine = -1
private var done = false
private val ranges: MutableList<Range> = mutableListOf()
}

View File

@@ -1,357 +0,0 @@
/*
* IdeaVim - Vim emulator for IDEs based on the IntelliJ platform
* Copyright (C) 2003-2019 The IdeaVim authors
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package com.maddyhome.idea.vim.extension.surround;
import com.google.common.collect.ImmutableMap;
import com.intellij.openapi.actionSystem.DataContext;
import com.intellij.openapi.application.WriteAction;
import com.intellij.openapi.editor.Caret;
import com.intellij.openapi.editor.Editor;
import com.intellij.openapi.util.Pair;
import com.maddyhome.idea.vim.VimPlugin;
import com.maddyhome.idea.vim.command.CommandState;
import com.maddyhome.idea.vim.command.MappingMode;
import com.maddyhome.idea.vim.command.SelectionType;
import com.maddyhome.idea.vim.common.Mark;
import com.maddyhome.idea.vim.common.TextRange;
import com.maddyhome.idea.vim.extension.VimExtensionHandler;
import com.maddyhome.idea.vim.extension.VimNonDisposableExtension;
import com.maddyhome.idea.vim.group.ChangeGroup;
import com.maddyhome.idea.vim.helper.EditorHelper;
import com.maddyhome.idea.vim.key.OperatorFunction;
import com.maddyhome.idea.vim.option.ClipboardOptionsData;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import javax.swing.*;
import java.awt.event.KeyEvent;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import static com.maddyhome.idea.vim.extension.VimExtensionFacade.*;
import static com.maddyhome.idea.vim.helper.StringHelper.parseKeys;
/**
* Port of vim-surround.
*
* See https://github.com/tpope/vim-surround
*
* @author dhleong
* @author vlan
*/
public class VimSurroundExtension extends VimNonDisposableExtension {
private static final char REGISTER = '"';
private final static Pattern tagNameAndAttributesCapturePattern = Pattern.compile("(\\w+)([^>]*)>");
private static final Map<Character, Pair<String, String>> SURROUND_PAIRS = ImmutableMap.<Character, Pair<String, String>>builder()
.put('b', Pair.create("(", ")"))
.put('(', Pair.create("( ", " )"))
.put(')', Pair.create("(", ")"))
.put('B', Pair.create("{", "}"))
.put('{', Pair.create("{ ", " }"))
.put('}', Pair.create("{", "}"))
.put('r', Pair.create("[", "]"))
.put('[', Pair.create("[ ", " ]"))
.put(']', Pair.create("[", "]"))
.put('a', Pair.create("<", ">"))
.put('>', Pair.create("<", ">"))
.put('s', Pair.create(" ", ""))
.build();
@NotNull
@Override
public String getName() {
return "surround";
}
@Override
protected void initOnce() {
putExtensionHandlerMapping(MappingMode.N, parseKeys("<Plug>YSurround"), new YSurroundHandler(), false);
putExtensionHandlerMapping(MappingMode.N, parseKeys("<Plug>CSurround"), new CSurroundHandler(), false);
putExtensionHandlerMapping(MappingMode.N, parseKeys("<Plug>DSurround"), new DSurroundHandler(), false);
putExtensionHandlerMapping(MappingMode.XO, parseKeys("<Plug>VSurround"), new VSurroundHandler(), false);
putKeyMapping(MappingMode.N, parseKeys("ys"), parseKeys("<Plug>YSurround"), true);
putKeyMapping(MappingMode.N, parseKeys("cs"), parseKeys("<Plug>CSurround"), true);
putKeyMapping(MappingMode.N, parseKeys("ds"), parseKeys("<Plug>DSurround"), true);
putKeyMapping(MappingMode.XO, parseKeys("S"), parseKeys("<Plug>VSurround"), true);
}
@Nullable
private static Pair<String, String> getSurroundPair(char c) {
if (SURROUND_PAIRS.containsKey(c)) {
return SURROUND_PAIRS.get(c);
}
else if (!Character.isLetter(c)) {
final String s = String.valueOf(c);
return Pair.create(s, s);
}
else {
return null;
}
}
@Nullable
private static Pair<String, String> inputTagPair(@NotNull Editor editor) {
final String tagInput = inputString(editor, "<", '>');
final Matcher matcher = tagNameAndAttributesCapturePattern.matcher(tagInput);
if (matcher.find()) {
final String tagName = matcher.group(1);
final String tagAttributes = matcher.group(2);
return Pair.create("<" + tagName + tagAttributes + ">", "</" + tagName + ">");
}
else {
return null;
}
}
@Nullable
private static Pair<String, String> inputFunctionName(
@NotNull Editor editor,
boolean withInternalSpaces
) {
final String functionNameInput = inputString(editor, "function: ", null);
if (functionNameInput.isEmpty()) {
return null;
}
return withInternalSpaces
? Pair.create(functionNameInput + "( ", " )")
: Pair.create(functionNameInput + "(", ")");
}
@Nullable
private static Pair<String, String> getOrInputPair(char c, @NotNull Editor editor) {
switch (c) {
case '<':
case 't':
return inputTagPair(editor);
case 'f':
return inputFunctionName(editor, false);
case 'F':
return inputFunctionName(editor, true);
default:
return getSurroundPair(c);
}
}
private static char getChar(@NotNull Editor editor) {
final KeyStroke key = inputKeyStroke(editor);
final char keyChar = key.getKeyChar();
if (keyChar == KeyEvent.CHAR_UNDEFINED || keyChar == KeyEvent.VK_ESCAPE) {
return 0;
}
return keyChar;
}
private static class YSurroundHandler implements VimExtensionHandler {
@Override
public boolean isRepeatable() {
return true;
}
@Override
public void execute(@NotNull Editor editor, @NotNull DataContext context) {
setOperatorFunction(new Operator());
executeNormal(parseKeys("g@"), editor);
}
}
private static class VSurroundHandler implements VimExtensionHandler {
@Override
public void execute(@NotNull Editor editor, @NotNull DataContext context) {
int selectionStart = editor.getCaretModel().getPrimaryCaret().getSelectionStart();
// NB: Operator ignores SelectionType anyway
if (!new Operator().apply(editor, context, SelectionType.CHARACTER_WISE)) {
return;
}
WriteAction.run(() -> {
// Leave visual mode
executeNormal(parseKeys("<Esc>"), editor);
editor.getCaretModel().moveToOffset(selectionStart);
});
}
}
private static class CSurroundHandler implements VimExtensionHandler {
@Override
public boolean isRepeatable() {
return true;
}
@Override
public void execute(@NotNull Editor editor, @NotNull DataContext context) {
final char charFrom = getChar(editor);
if (charFrom == 0) {
return;
}
final char charTo = getChar(editor);
if (charTo == 0) {
return;
}
Pair<String, String> newSurround = getOrInputPair(charTo, editor);
if (newSurround == null) {
return;
}
WriteAction.run(() -> change(editor, charFrom, newSurround));
}
static void change(@NotNull Editor editor, char charFrom, @Nullable Pair<String, String> newSurround) {
// We take over the " register, so preserve it
final List<KeyStroke> oldValue = getRegister(REGISTER);
// Extract the inner value
perform("di" + pick(charFrom), editor);
List<KeyStroke> innerValue = getRegister(REGISTER);
if (innerValue == null) {
innerValue = new ArrayList<>();
} else {
innerValue = new ArrayList<>(innerValue);
}
// Delete the surrounding
perform("da" + pick(charFrom), editor);
// Insert the surrounding characters and paste
if (newSurround != null) {
innerValue.addAll(0, parseKeys(escape(newSurround.first)));
innerValue.addAll(parseKeys(escape(newSurround.second)));
}
pasteSurround(innerValue, editor);
// Restore the old value
setRegister(REGISTER, oldValue);
// Jump back to start
executeNormal(parseKeys("`["), editor);
}
@NotNull
private static String escape(@NotNull String sequence) {
return sequence.replace("<", "\\<");
}
private static void perform(@NotNull String sequence, @NotNull Editor editor) {
try (ClipboardOptionsData.IdeaputDisabler ignored = new ClipboardOptionsData.IdeaputDisabler()) {
executeNormal(parseKeys("\"" + REGISTER + sequence), editor);
}
}
private static void pasteSurround(@NotNull List<KeyStroke> innerValue, @NotNull Editor editor) {
// This logic is direct from vim-surround
final int offset = editor.getCaretModel().getOffset();
final int lineEndOffset = EditorHelper.getLineEndForOffset(editor, offset);
final Mark motionEndMark = VimPlugin.getMark().getMark(editor, ']');
final int motionEndOffset;
if (motionEndMark != null) {
motionEndOffset = EditorHelper.getOffset(editor, motionEndMark.getLogicalLine(), motionEndMark.getCol());
}
else {
motionEndOffset = -1;
}
final String pasteCommand = motionEndOffset == lineEndOffset && offset + 1 == lineEndOffset ? "p" : "P";
setRegister(REGISTER, innerValue);
perform(pasteCommand, editor);
}
private static char pick(char charFrom) {
switch (charFrom) {
case 'a': return '>';
case 'r': return ']';
default: return charFrom;
}
}
}
private static class DSurroundHandler implements VimExtensionHandler {
@Override
public boolean isRepeatable() {
return true;
}
@Override
public void execute(@NotNull Editor editor, @NotNull DataContext context) {
// Deleting surround is just changing the surrounding to "nothing"
final char charFrom = getChar(editor);
if (charFrom == 0) {
return;
}
WriteAction.run(() -> CSurroundHandler.change(editor, charFrom, null));
}
}
private static class Operator implements OperatorFunction {
@Override
public boolean apply(@NotNull Editor editor, @NotNull DataContext context, @NotNull SelectionType selectionType) {
final char c = getChar(editor);
if (c == 0) {
return true;
}
final Pair<String, String> pair = getOrInputPair(c, editor);
if (pair == null) {
return false;
}
// XXX: Will it work with line-wise or block-wise selections?
final TextRange range = getSurroundRange(editor);
if (range == null) {
return false;
}
WriteAction.run(() -> {
final ChangeGroup change = VimPlugin.getChange();
final String leftSurround = pair.getFirst();
final Caret primaryCaret = editor.getCaretModel().getPrimaryCaret();
primaryCaret.moveToOffset(range.getStartOffset());
change.insertText(editor, primaryCaret, leftSurround);
primaryCaret.moveToOffset(range.getEndOffset() + leftSurround.length());
change.insertText(editor, primaryCaret, pair.getSecond());
// Jump back to start
executeNormal(parseKeys("`["), editor);
});
return true;
}
@Nullable
private TextRange getSurroundRange(@NotNull Editor editor) {
final CommandState.Mode mode = CommandState.getInstance(editor).getMode();
switch (mode) {
case COMMAND:
return VimPlugin.getMark().getChangeMarks(editor);
case VISUAL:
int selectionStart = editor.getCaretModel().getPrimaryCaret().getSelectionStart();
int selectionEnd = editor.getCaretModel().getPrimaryCaret().getSelectionEnd();
return new TextRange(selectionStart, selectionEnd);
default:
return null;
}
}
}
}

View File

@@ -0,0 +1,256 @@
/*
* IdeaVim - Vim emulator for IDEs based on the IntelliJ platform
* Copyright (C) 2003-2019 The IdeaVim authors
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package com.maddyhome.idea.vim.extension.surround
import com.intellij.openapi.actionSystem.DataContext
import com.intellij.openapi.application.runWriteAction
import com.intellij.openapi.editor.Editor
import com.maddyhome.idea.vim.VimPlugin
import com.maddyhome.idea.vim.command.CommandState
import com.maddyhome.idea.vim.command.MappingMode
import com.maddyhome.idea.vim.command.SelectionType
import com.maddyhome.idea.vim.common.TextRange
import com.maddyhome.idea.vim.extension.VimExtensionFacade.executeNormal
import com.maddyhome.idea.vim.extension.VimExtensionFacade.getRegister
import com.maddyhome.idea.vim.extension.VimExtensionFacade.inputKeyStroke
import com.maddyhome.idea.vim.extension.VimExtensionFacade.inputString
import com.maddyhome.idea.vim.extension.VimExtensionFacade.putExtensionHandlerMapping
import com.maddyhome.idea.vim.extension.VimExtensionFacade.putKeyMapping
import com.maddyhome.idea.vim.extension.VimExtensionFacade.setOperatorFunction
import com.maddyhome.idea.vim.extension.VimExtensionFacade.setRegister
import com.maddyhome.idea.vim.extension.VimExtensionHandler
import com.maddyhome.idea.vim.extension.VimNonDisposableExtension
import com.maddyhome.idea.vim.helper.EditorHelper
import com.maddyhome.idea.vim.helper.StringHelper
import com.maddyhome.idea.vim.helper.mode
import com.maddyhome.idea.vim.key.OperatorFunction
import com.maddyhome.idea.vim.option.ClipboardOptionsData.IdeaputDisabler
import java.awt.event.KeyEvent
import javax.swing.KeyStroke
/**
* Port of vim-surround.
*
* See https://github.com/tpope/vim-surround
*
* @author dhleong
* @author vlan
*/
class VimSurroundExtension : VimNonDisposableExtension() {
override fun getName() = "surround"
override fun initOnce() {
putExtensionHandlerMapping(MappingMode.N, StringHelper.parseKeys("<Plug>YSurround"), YSurroundHandler(), false)
putExtensionHandlerMapping(MappingMode.N, StringHelper.parseKeys("<Plug>CSurround"), CSurroundHandler(), false)
putExtensionHandlerMapping(MappingMode.N, StringHelper.parseKeys("<Plug>DSurround"), DSurroundHandler(), false)
putExtensionHandlerMapping(MappingMode.XO, StringHelper.parseKeys("<Plug>VSurround"), VSurroundHandler(), false)
putKeyMapping(MappingMode.N, StringHelper.parseKeys("ys"), StringHelper.parseKeys("<Plug>YSurround"), true)
putKeyMapping(MappingMode.N, StringHelper.parseKeys("cs"), StringHelper.parseKeys("<Plug>CSurround"), true)
putKeyMapping(MappingMode.N, StringHelper.parseKeys("ds"), StringHelper.parseKeys("<Plug>DSurround"), true)
putKeyMapping(MappingMode.XO, StringHelper.parseKeys("S"), StringHelper.parseKeys("<Plug>VSurround"), true)
}
private class YSurroundHandler : VimExtensionHandler {
override fun isRepeatable() = true
override fun execute(editor: Editor, context: DataContext) {
setOperatorFunction(Operator())
executeNormal(StringHelper.parseKeys("g@"), editor)
}
}
private class VSurroundHandler : VimExtensionHandler {
override fun execute(editor: Editor, context: DataContext) {
val selectionStart = editor.caretModel.primaryCaret.selectionStart
// NB: Operator ignores SelectionType anyway
if (!Operator().apply(editor, context, SelectionType.CHARACTER_WISE)) {
return
}
runWriteAction {
// Leave visual mode
executeNormal(StringHelper.parseKeys("<Esc>"), editor)
editor.caretModel.moveToOffset(selectionStart)
}
}
}
private class CSurroundHandler : VimExtensionHandler {
override fun isRepeatable() = true
override fun execute(editor: Editor, context: DataContext) {
val charFrom = getChar(editor)
if (charFrom.toInt() == 0) return
val charTo = getChar(editor)
if (charTo.toInt() == 0) return
val newSurround = getOrInputPair(charTo, editor) ?: return
runWriteAction { change(editor, charFrom, newSurround) }
}
companion object {
fun change(editor: Editor, charFrom: Char, newSurround: Pair<String, String>?) {
// We take over the " register, so preserve it
val oldValue: List<KeyStroke>? = getRegister(REGISTER)
// Extract the inner value
perform("di" + pick(charFrom), editor)
val innerValue: MutableList<KeyStroke> = getRegister(REGISTER)?.toMutableList() ?: mutableListOf()
// Delete the surrounding
perform("da" + pick(charFrom), editor)
// Insert the surrounding characters and paste
if (newSurround != null) {
innerValue.addAll(0, StringHelper.parseKeys(escape(newSurround.first)))
innerValue.addAll(StringHelper.parseKeys(escape(newSurround.second)))
}
pasteSurround(innerValue, editor)
// Restore the old value
setRegister(REGISTER, oldValue)
// Jump back to start
executeNormal(StringHelper.parseKeys("`["), editor)
}
private fun escape(sequence: String): String = sequence.replace("<", "\\<")
private fun perform(sequence: String, editor: Editor) {
IdeaputDisabler().use { executeNormal(StringHelper.parseKeys("\"" + REGISTER + sequence), editor) }
}
private fun pasteSurround(innerValue: List<KeyStroke?>, editor: Editor) { // This logic is direct from vim-surround
val offset = editor.caretModel.offset
val lineEndOffset = EditorHelper.getLineEndForOffset(editor, offset)
val motionEndMark = VimPlugin.getMark().getMark(editor, ']')
val motionEndOffset = if (motionEndMark != null) {
EditorHelper.getOffset(editor, motionEndMark.logicalLine, motionEndMark.col)
} else -1
val pasteCommand = if (motionEndOffset == lineEndOffset && offset + 1 == lineEndOffset) "p" else "P"
setRegister(REGISTER, innerValue)
perform(pasteCommand, editor)
}
private fun pick(charFrom: Char) = when (charFrom) {
'a' -> '>'
'r' -> ']'
else -> charFrom
}
}
}
private class DSurroundHandler : VimExtensionHandler {
override fun isRepeatable() = true
override fun execute(editor: Editor, context: DataContext) {
// Deleting surround is just changing the surrounding to "nothing"
val charFrom = getChar(editor)
if (charFrom.toInt() == 0) return
runWriteAction { CSurroundHandler.change(editor, charFrom, null) }
}
}
private class Operator : OperatorFunction {
override fun apply(editor: Editor, context: DataContext, selectionType: SelectionType): Boolean {
val c = getChar(editor)
if (c.toInt() == 0) return true
val pair = getOrInputPair(c, editor) ?: return false
// XXX: Will it work with line-wise or block-wise selections?
val range = getSurroundRange(editor) ?: return false
runWriteAction {
val change = VimPlugin.getChange()
val leftSurround = pair.first
val primaryCaret = editor.caretModel.primaryCaret
primaryCaret.moveToOffset(range.startOffset)
change.insertText(editor, primaryCaret, leftSurround)
primaryCaret.moveToOffset(range.endOffset + leftSurround.length)
change.insertText(editor, primaryCaret, pair.second)
// Jump back to start
executeNormal(StringHelper.parseKeys("`["), editor)
}
return true
}
private fun getSurroundRange(editor: Editor): TextRange? = when (editor.mode) {
CommandState.Mode.COMMAND -> VimPlugin.getMark().getChangeMarks(editor)
CommandState.Mode.VISUAL -> editor.caretModel.primaryCaret.run { TextRange(selectionStart, selectionEnd) }
else -> null
}
}
companion object {
private const val REGISTER = '"'
private val tagNameAndAttributesCapturePattern = "(\\w+)([^>]*)>".toPattern()
private val SURROUND_PAIRS = mapOf(
'b' to ("(" to ")"),
'(' to ("( " to " )"),
')' to ("(" to ")"),
'B' to ("{" to "}"),
'{' to ("{ " to " }"),
'}' to ("{" to "}"),
'r' to ("[" to "]"),
'[' to ("[ " to " ]"),
']' to ("[" to "]"),
'a' to ("<" to ">"),
'>' to ("<" to ">"),
's' to (" " to "")
)
private fun getSurroundPair(c: Char): Pair<String, String>? = if (c in SURROUND_PAIRS) {
SURROUND_PAIRS[c]
} else if (!c.isLetter()) {
val s = c.toString()
s to s
} else null
private fun inputTagPair(editor: Editor): Pair<String, String>? {
val tagInput = inputString(editor, "<", '>')
val matcher = tagNameAndAttributesCapturePattern.matcher(tagInput)
return if (matcher.find()) {
val tagName = matcher.group(1)
val tagAttributes = matcher.group(2)
"<$tagName$tagAttributes>" to "</$tagName>"
} else null
}
private fun inputFunctionName(
editor: Editor,
withInternalSpaces: Boolean
): Pair<String, String>? {
val functionNameInput = inputString(editor, "function: ", null)
if (functionNameInput.isEmpty()) return null
return if (withInternalSpaces) "$functionNameInput( " to " )" else "$functionNameInput(" to ")"
}
private fun getOrInputPair(c: Char, editor: Editor): Pair<String, String>? = when (c) {
'<', 't' -> inputTagPair(editor)
'f' -> inputFunctionName(editor, false)
'F' -> inputFunctionName(editor, true)
else -> getSurroundPair(c)
}
private fun getChar(editor: Editor): Char {
val key = inputKeyStroke(editor)
val keyChar = key.keyChar
return if (keyChar == KeyEvent.CHAR_UNDEFINED || keyChar.toInt() == KeyEvent.VK_ESCAPE) {
0.toChar()
} else keyChar
}
}
}

View File

@@ -51,7 +51,7 @@ import com.maddyhome.idea.vim.command.*;
import com.maddyhome.idea.vim.common.IndentConfig; import com.maddyhome.idea.vim.common.IndentConfig;
import com.maddyhome.idea.vim.common.Register; import com.maddyhome.idea.vim.common.Register;
import com.maddyhome.idea.vim.common.TextRange; import com.maddyhome.idea.vim.common.TextRange;
import com.maddyhome.idea.vim.ex.LineRange; import com.maddyhome.idea.vim.ex.ranges.LineRange;
import com.maddyhome.idea.vim.group.visual.VimSelection; import com.maddyhome.idea.vim.group.visual.VimSelection;
import com.maddyhome.idea.vim.group.visual.VisualGroupKt; import com.maddyhome.idea.vim.group.visual.VisualGroupKt;
import com.maddyhome.idea.vim.group.visual.VisualModeHelperKt; import com.maddyhome.idea.vim.group.visual.VisualModeHelperKt;
@@ -1787,8 +1787,8 @@ public class ChangeGroup {
public boolean sortRange(@NotNull Editor editor, public boolean sortRange(@NotNull Editor editor,
@NotNull LineRange range, @NotNull LineRange range,
@NotNull Comparator<String> lineComparator) { @NotNull Comparator<String> lineComparator) {
final int startLine = range.getStartLine(); final int startLine = range.startLine;
final int endLine = range.getEndLine(); final int endLine = range.endLine;
final int count = endLine - startLine + 1; final int count = endLine - startLine + 1;
if (count < 2) { if (count < 2) {
return false; return false;

View File

@@ -37,7 +37,7 @@ import com.maddyhome.idea.vim.command.CommandFlags;
import com.maddyhome.idea.vim.command.SelectionType; import com.maddyhome.idea.vim.command.SelectionType;
import com.maddyhome.idea.vim.common.CharacterPosition; import com.maddyhome.idea.vim.common.CharacterPosition;
import com.maddyhome.idea.vim.common.TextRange; import com.maddyhome.idea.vim.common.TextRange;
import com.maddyhome.idea.vim.ex.LineRange; import com.maddyhome.idea.vim.ex.ranges.LineRange;
import com.maddyhome.idea.vim.helper.*; import com.maddyhome.idea.vim.helper.*;
import com.maddyhome.idea.vim.option.ListOption; import com.maddyhome.idea.vim.option.ListOption;
import com.maddyhome.idea.vim.option.OptionChangeListener; import com.maddyhome.idea.vim.option.OptionChangeListener;
@@ -377,7 +377,7 @@ public class SearchGroup {
} }
public int updateIncsearchHighlights(@NotNull Editor editor, @NotNull String pattern, boolean forwards, int caretOffset, @Nullable LineRange searchRange) { public int updateIncsearchHighlights(@NotNull Editor editor, @NotNull String pattern, boolean forwards, int caretOffset, @Nullable LineRange searchRange) {
final int searchStartOffset = searchRange != null ? EditorHelper.getLineStartOffset(editor, searchRange.getStartLine()) : caretOffset; final int searchStartOffset = searchRange != null ? EditorHelper.getLineStartOffset(editor, searchRange.startLine) : caretOffset;
final boolean showHighlights = OptionsManager.INSTANCE.getHlsearch().isSet(); final boolean showHighlights = OptionsManager.INSTANCE.getHlsearch().isSet();
return updateSearchHighlights(pattern, false, showHighlights, searchStartOffset, searchRange, forwards, false); return updateSearchHighlights(pattern, false, showHighlights, searchStartOffset, searchRange, forwards, false);
} }
@@ -410,8 +410,8 @@ public class SearchGroup {
} }
if (shouldAddAllSearchHighlights(editor, pattern, showHighlights)) { if (shouldAddAllSearchHighlights(editor, pattern, showHighlights)) {
final int startLine = searchRange == null ? 0 : searchRange.getStartLine(); final int startLine = searchRange == null ? 0 : searchRange.startLine;
final int endLine = searchRange == null ? -1 : searchRange.getEndLine(); final int endLine = searchRange == null ? -1 : searchRange.endLine;
List<TextRange> results = findAll(editor, pattern, startLine, endLine, shouldIgnoreCase(pattern, shouldIgnoreSmartCase)); List<TextRange> results = findAll(editor, pattern, startLine, endLine, shouldIgnoreCase(pattern, shouldIgnoreSmartCase));
if (!results.isEmpty()) { if (!results.isEmpty()) {
currentMatchOffset = findClosestMatch(editor, results, initialOffset, forwards); currentMatchOffset = findClosestMatch(editor, results, initialOffset, forwards);
@@ -1114,8 +1114,8 @@ public class SearchGroup {
cmd.inc(); cmd.inc();
} }
int line1 = range.getStartLine(); int line1 = range.startLine;
int line2 = range.getEndLine(); int line2 = range.endLine;
if (line1 < 0 || line2 < 0) { if (line1 < 0 || line2 < 0) {
return false; return false;

View File

@@ -1,107 +0,0 @@
/*
* IdeaVim - Vim emulator for IDEs based on the IntelliJ platform
* Copyright (C) 2003-2019 The IdeaVim authors
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package com.maddyhome.idea.vim.helper;
import com.maddyhome.idea.vim.option.OptionsManager;
import org.jetbrains.annotations.NotNull;
/**
* This helper class is used when working with various character level operations
*/
public class CharacterHelper {
public enum CharacterType {
KEYWORD,
HIRAGANA,
KATAKANA,
HALF_WIDTH_KATAKANA,
PUNCTUATION,
WHITESPACE
}
public static final char CASE_TOGGLE = '~';
public static final char CASE_UPPER = 'u';
public static final char CASE_LOWER = 'l';
/**
* This returns the type of the supplied character. The logic is as follows:<br>
* If the character is whitespace, <code>WHITESPACE</code> is returned.<br>
* If the punctuation is being skipped or the character is a letter, digit, or underscore, <code>KEYWORD</code>
* is returned.<br>
* Otherwise <code>PUNCTUATION</code> is returned.
*
* @param ch The character to analyze
* @param punctuationAsLetters True if punctuation is to be ignored, false if not
* @return The type of the character
*/
@NotNull
public static CharacterType charType(char ch, boolean punctuationAsLetters) {
final Character.UnicodeBlock block = Character.UnicodeBlock.of(ch);
if (Character.isWhitespace(ch)) {
return CharacterType.WHITESPACE;
}
else if (block == Character.UnicodeBlock.HIRAGANA) {
return CharacterType.HIRAGANA;
}
else if (block == Character.UnicodeBlock.KATAKANA) {
return CharacterType.KATAKANA;
}
else if (isHalfWidthKatakanaLetter(ch)) {
return CharacterType.HALF_WIDTH_KATAKANA;
}
else if (punctuationAsLetters || OptionsManager.INSTANCE.getIskeyword().isKeyword(ch)) {
return CharacterType.KEYWORD;
}
else {
return CharacterType.PUNCTUATION;
}
}
private static boolean isHalfWidthKatakanaLetter(char ch) {
return ch >= '\uFF66' && ch <= '\uFF9F';
}
/**
* Changes the case of the supplied character based on the supplied change type
*
* @param ch The character to change
* @param type One of <code>CASE_TOGGLE</code>, <code>CASE_UPPER</code>, or <code>CASE_LOWER</code>
* @return The character with changed case or the original if not a letter
*/
public static char changeCase(char ch, char type) {
switch (type) {
case CASE_TOGGLE:
if (Character.isLowerCase(ch)) {
ch = Character.toUpperCase(ch);
}
else if (Character.isUpperCase(ch)) {
ch = Character.toLowerCase(ch);
}
break;
case CASE_LOWER:
ch = Character.toLowerCase(ch);
break;
case CASE_UPPER:
ch = Character.toUpperCase(ch);
break;
}
return ch;
}
}

View File

@@ -0,0 +1,84 @@
/*
* IdeaVim - Vim emulator for IDEs based on the IntelliJ platform
* Copyright (C) 2003-2019 The IdeaVim authors
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package com.maddyhome.idea.vim.helper
import com.maddyhome.idea.vim.option.OptionsManager.iskeyword
import java.lang.Character.UnicodeBlock
/**
* This helper class is used when working with various character level operations
*/
object CharacterHelper {
const val CASE_TOGGLE = '~'
const val CASE_UPPER = 'u'
const val CASE_LOWER = 'l'
/**
* This returns the type of the supplied character. The logic is as follows:<br></br>
* If the character is whitespace, `WHITESPACE` is returned.<br></br>
* If the punctuation is being skipped or the character is a letter, digit, or underscore, `KEYWORD`
* is returned.<br></br>
* Otherwise `PUNCTUATION` is returned.
*
* @param ch The character to analyze
* @param punctuationAsLetters True if punctuation is to be ignored, false if not
* @return The type of the character
*/
@JvmStatic
fun charType(ch: Char, punctuationAsLetters: Boolean): CharacterType {
val block = UnicodeBlock.of(ch)
return if (Character.isWhitespace(ch)) {
CharacterType.WHITESPACE
} else if (block === UnicodeBlock.HIRAGANA) {
CharacterType.HIRAGANA
} else if (block === UnicodeBlock.KATAKANA) {
CharacterType.KATAKANA
} else if (isHalfWidthKatakanaLetter(ch)) {
CharacterType.HALF_WIDTH_KATAKANA
} else if (punctuationAsLetters || iskeyword.isKeyword(ch)) {
CharacterType.KEYWORD
} else {
CharacterType.PUNCTUATION
}
}
private fun isHalfWidthKatakanaLetter(ch: Char): Boolean = ch in '\uFF66'..'\uFF9F'
/**
* Changes the case of the supplied character based on the supplied change type
*
* @param ch The character to change
* @param type One of `CASE_TOGGLE`, `CASE_UPPER`, or `CASE_LOWER`
* @return The character with changed case or the original if not a letter
*/
@JvmStatic
fun changeCase(ch: Char, type: Char): Char = when (type) {
CASE_TOGGLE -> when {
Character.isLowerCase(ch) -> Character.toUpperCase(ch)
Character.isUpperCase(ch) -> Character.toLowerCase(ch)
else -> ch
}
CASE_LOWER -> Character.toLowerCase(ch)
CASE_UPPER -> Character.toUpperCase(ch)
else -> ch
}
enum class CharacterType {
KEYWORD, HIRAGANA, KATAKANA, HALF_WIDTH_KATAKANA, PUNCTUATION, WHITESPACE
}
}

View File

@@ -58,7 +58,7 @@ object OptionsManager {
val incsearch = addOption(ToggleOption("incsearch", "is", false)) val incsearch = addOption(ToggleOption("incsearch", "is", false))
val iskeyword = addOption(KeywordOption("iskeyword", "isk", arrayOf("@", "48-57", "_"))) val iskeyword = addOption(KeywordOption("iskeyword", "isk", arrayOf("@", "48-57", "_")))
val keymodel = addOption(KeyModelOptionData.option) val keymodel = addOption(KeyModelOptionData.option)
val lookupKeys = addOption(ListOption("lookupkeys", "lookupkeys", arrayOf(), null)) val lookupKeys = addOption(ListOption(LookupKeysData.name, LookupKeysData.name, LookupKeysData.defaultValues, null))
val matchpairs = addOption(ListOption("matchpairs", "mps", arrayOf("(:)", "{:}", "[:]"), ".:.")) val matchpairs = addOption(ListOption("matchpairs", "mps", arrayOf("(:)", "{:}", "[:]"), ".:."))
val more = addOption(ToggleOption("more", "more", true)) val more = addOption(ToggleOption("more", "more", true))
val nrformats = addOption(BoundListOption("nrformats", "nf", arrayOf("octal", "hex"), arrayOf("octal", "hex", "alpha"))) val nrformats = addOption(BoundListOption("nrformats", "nf", arrayOf("octal", "hex"), arrayOf("octal", "hex", "alpha")))
@@ -506,3 +506,17 @@ object IdeaRefactorMode {
} }
} }
} }
object LookupKeysData {
val name = "lookupkeys"
val defaultValues = arrayOf(
"<Tab>", "<Down>", "<Up>", "<Enter>", "<Left>", "<Right>",
"<C-Down>", "<C-Up>",
"<PageUp>", "<PageDown>",
// New line in vim, but QuickDoc on MacOs
"<C-J>",
// QuickDoc for non-mac layouts.
// Vim: Insert next non-digit literally (same as <Ctrl-V>). Not yet supported (19.01.2020)
"<C-Q>"
)
}

View File

@@ -31,7 +31,7 @@ import com.intellij.util.IJSwingUtilities;
import com.maddyhome.idea.vim.VimPlugin; import com.maddyhome.idea.vim.VimPlugin;
import com.maddyhome.idea.vim.ex.CommandParser; import com.maddyhome.idea.vim.ex.CommandParser;
import com.maddyhome.idea.vim.ex.ExCommand; import com.maddyhome.idea.vim.ex.ExCommand;
import com.maddyhome.idea.vim.ex.LineRange; import com.maddyhome.idea.vim.ex.ranges.LineRange;
import com.maddyhome.idea.vim.group.MotionGroup; import com.maddyhome.idea.vim.group.MotionGroup;
import com.maddyhome.idea.vim.helper.UiHelper; import com.maddyhome.idea.vim.helper.UiHelper;
import com.maddyhome.idea.vim.option.OptionsManager; import com.maddyhome.idea.vim.option.OptionsManager;