1
0
mirror of https://github.com/chylex/IntelliJ-IdeaVim.git synced 2025-08-17 16:31:45 +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
usual beta standards.
[To Be Released]
0.55, 2020-01-20
--------------
_Available since 0.54.1 EAP:_
**Features:**
* Surround and Commentary extensions can be repeated with a dot command ([VIM-1118](https://youtrack.jetbrains.com/issue/VIM-1118))
* Surround and Commentary extensions support repeating with a dot command ([VIM-1118](https://youtrack.jetbrains.com/issue/VIM-1118))
* Support XDG settings standard ([VIM-664](https://youtrack.jetbrains.com/issue/VIM-664))
* Add option to remove the status bar icon ([VIM-1847](https://youtrack.jetbrains.com/issue/VIM-1847))
@@ -43,10 +41,6 @@ _Available since 0.54.1 EAP:_
* [VIM-1853](https://youtrack.jetbrains.com/issue/VIM-1853) Fix marks for disposed projects
* [VIM-1858](https://youtrack.jetbrains.com/issue/VIM-1858) Fix imap for autocomplete
* [VIM-1362](https://youtrack.jetbrains.com/issue/VIM-1362) Search with confirm doesn't scroll down far enough
_To Be Released:_
**Fixes:**
* [VIM-1875](https://youtrack.jetbrains.com/issue/VIM-1875) Fix `isk` in `~/.ideaivmrc`
* [VIM-1874](https://youtrack.jetbrains.com/issue/VIM-1874) Fix `set clipboard=unnamed` execution from `~/.ideavimrc`
* [VIM-1878](https://youtrack.jetbrains.com/issue/VIM-1878) Fix `c` command after extract method action

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

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

View File

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