mirror of
https://github.com/chylex/IntelliJ-IdeaVim.git
synced 2025-05-30 13:34:08 +02:00
Merge branch 'master' into VIM-510
This commit is contained in:
commit
bdc9b78ec8
.editorconfigAUTHORS.mdCHANGES.mdCONTRIBUTING.mdindex.txt
resources/META-INF
src/com/maddyhome/idea/vim
KeyHandler.javaRegisterActions.javaVimLocalConfig.ktVimPlugin.javaVimTypedActionHandler.java
action
common
ex
group
helper
listener
package-info.javaui
test/org/jetbrains/plugins/ideavim
@ -3,3 +3,7 @@ root = true
|
|||||||
[*.java]
|
[*.java]
|
||||||
indent_size = 2
|
indent_size = 2
|
||||||
indent_style = space
|
indent_style = space
|
||||||
|
|
||||||
|
[*.kt]
|
||||||
|
indent_size = 2
|
||||||
|
indent_style = space
|
||||||
|
@ -66,6 +66,10 @@ Contributors:
|
|||||||
* [gecko655](mailto:aqwsedrft1234@yahoo.co.jp)
|
* [gecko655](mailto:aqwsedrft1234@yahoo.co.jp)
|
||||||
* [Daniele Megna](mailto:megna.dany@gmail.com)
|
* [Daniele Megna](mailto:megna.dany@gmail.com)
|
||||||
* [Andrew Potter](mailto:apottere@gmail.com)
|
* [Andrew Potter](mailto:apottere@gmail.com)
|
||||||
|
* [Romain Gautier](mailto:romain.gautier@nimamoh.net)
|
||||||
|
* [Elliot Courant](mailto:elliot.courant@wheniwork.com)
|
||||||
|
* [Simon Rainer](mailto:simon.rainer@fau.de)
|
||||||
|
* [Michael Ziwisky](mailto:mziwisky@instructure.com)
|
||||||
|
|
||||||
If you are a contributor and your name is not listed here, feel free to
|
If you are a contributor and your name is not listed here, feel free to
|
||||||
contact the maintainer.
|
contact the maintainer.
|
||||||
|
12
CHANGES.md
12
CHANGES.md
@ -38,6 +38,18 @@ To Be Released
|
|||||||
* [VIM-607](https://youtrack.jetbrains.com/issue/VIM-607) Fix memory leaks
|
* [VIM-607](https://youtrack.jetbrains.com/issue/VIM-607) Fix memory leaks
|
||||||
* [VIM-1546](https://youtrack.jetbrains.com/issue/VIM-1546) Storing TAB key as input
|
* [VIM-1546](https://youtrack.jetbrains.com/issue/VIM-1546) Storing TAB key as input
|
||||||
* [VIM-1231](https://youtrack.jetbrains.com/issue/VIM-1231) Get indent from PsiFile
|
* [VIM-1231](https://youtrack.jetbrains.com/issue/VIM-1231) Get indent from PsiFile
|
||||||
|
* [VIM-1633](https://youtrack.jetbrains.com/issue/VIM-1633) Fixed sequential text object commands in visual mode
|
||||||
|
* [VIM-1105](https://youtrack.jetbrains.com/issue/VIM-1105) Added the `:command` command
|
||||||
|
* [VIM-1090](https://youtrack.jetbrains.com/issue/VIM-1090) Fixed tag motion with duplicate tags
|
||||||
|
* [VIM-1644](https://youtrack.jetbrains.com/issue/VIM-1644) Fixed repeat with visual mode
|
||||||
|
* Fixed invoking IDE actions instead of command line actions with same shortcuts
|
||||||
|
* [VIM-1550](https://youtrack.jetbrains.com/issue/VIM-1550) Fixed leaving command line mode on backspace
|
||||||
|
* Fix insert position of `<C-R>` in ex commands
|
||||||
|
* Command line editing caret shape and insert digraph/register feedback
|
||||||
|
* [VIM-1419](https://youtrack.jetbrains.com/issue/VIM-1419), [VIM-1493](https://youtrack.jetbrains.com/issue/VIM-1493) Correctly set focus when handling cmode mapping
|
||||||
|
* Fix incorrect handling of subsequent key strokes after ex command line loses focus
|
||||||
|
* [VIM-1240](https://youtrack.jetbrains.com/issue/VIM-1240) Improve UI of ex command line and output panel
|
||||||
|
* [VIM-1485](https://youtrack.jetbrains.com/issue/VIM-1485) Remove incorrect gap between ex command line label and text
|
||||||
|
|
||||||
0.51, 2019-02-12
|
0.51, 2019-02-12
|
||||||
----------------
|
----------------
|
||||||
|
@ -66,7 +66,7 @@ in the issue tracker.
|
|||||||
|
|
||||||
### Copyright
|
### Copyright
|
||||||
|
|
||||||
1. Go to `Preferences | Appearance & Behavior | Scopes`, press "+" button, `local`.
|
1. Go to `Preferences | Appearance & Behavior | Scopes`, press "+" button, `Shared`.
|
||||||
Name: Copyright scope
|
Name: Copyright scope
|
||||||
Pattern: `file[IdeaVIM.main]:com//*||file[IdeaVIM.test]:*/`
|
Pattern: `file[IdeaVIM.main]:com//*||file[IdeaVIM.test]:*/`
|
||||||
|
|
||||||
|
191
index.txt
191
index.txt
@ -1,191 +0,0 @@
|
|||||||
*index.txt*
|
|
||||||
|
|
||||||
|
|
||||||
IDEAVIM REFERENCE MANUAL based on Vim Reference Manual
|
|
||||||
|
|
||||||
*index*
|
|
||||||
This file contains a list of commands that are covered with tests, for each
|
|
||||||
mode, with a tag and a short description. The lists are sorted on ASCII value.
|
|
||||||
|
|
||||||
Tip: When looking for certain functionality, use a search command. E.g.,
|
|
||||||
to look for deleting something, use: "/delete".
|
|
||||||
|
|
||||||
1. Insert mode |insert-index|
|
|
||||||
2. Normal mode |normal-index|
|
|
||||||
2.1. Text objects |objects|
|
|
||||||
2.3. Square bracket commands |[|
|
|
||||||
3. Visual mode |visual-index|
|
|
||||||
5. EX commands |ex-cmd-index|
|
|
||||||
|
|
||||||
==============================================================================
|
|
||||||
1. Insert mode *insert-index*
|
|
||||||
|
|
||||||
tag char action in Insert mode ~
|
|
||||||
-----------------------------------------------------------------------
|
|
||||||
|i_CTRL-K| CTRL-K {char1} {char2}
|
|
||||||
enter digraph
|
|
||||||
|i_CTRL-O| CTRL-O execute a single command and return to insert
|
|
||||||
mode
|
|
||||||
|i_CTRL-R| CTRL-R {0-9a-z"%#*:=}
|
|
||||||
insert the contents of a register
|
|
||||||
|i_CTRL-W| CTRL-W delete word before the cursor
|
|
||||||
|
|
||||||
==============================================================================
|
|
||||||
2. Normal mode *normal-index*
|
|
||||||
|
|
||||||
CHAR any non-blank character
|
|
||||||
WORD a sequence of non-blank characters
|
|
||||||
N a number entered before the command
|
|
||||||
{motion} a cursor movement command
|
|
||||||
Nmove the text that is moved over with a {motion}
|
|
||||||
SECTION a section that possibly starts with '}' instead of '{'
|
|
||||||
|
|
||||||
note: 1 = cursor movement command; 2 = can be undone/redone
|
|
||||||
|
|
||||||
tag char note action in Normal mode ~
|
|
||||||
------------------------------------------------------------------------------
|
|
||||||
|quote| "{a-zA-Z0-9.%#:-"} use register {a-zA-Z0-9.%#:-"} for next
|
|
||||||
delete, yank or put (uppercase to append)
|
|
||||||
({.%#:} only work with put)
|
|
||||||
|%| % 1 find the next (curly/square) bracket on
|
|
||||||
this line and go to its match, or go to
|
|
||||||
matching comment bracket, or go to matching
|
|
||||||
preprocessor directive.
|
|
||||||
|/| /{pattern}<CR> 1 search forward for the Nth occurrence of
|
|
||||||
{pattern}
|
|
||||||
|count| 0 1 cursor to the first char of the line
|
|
||||||
|count| 1 prepend to command to give a count
|
|
||||||
|count| 2 "
|
|
||||||
|count| 3 "
|
|
||||||
|count| 4 "
|
|
||||||
|count| 5 "
|
|
||||||
|count| 6 "
|
|
||||||
|count| 7 "
|
|
||||||
|count| 8 "
|
|
||||||
|count| 9 "
|
|
||||||
|F| F{char} 1 cursor to the Nth occurrence of {char} to
|
|
||||||
the left
|
|
||||||
|O| O 2 begin a new line above the cursor and
|
|
||||||
insert text, repeat N times
|
|
||||||
|P| ["x]P 2 put the text [from buffer x] before the
|
|
||||||
cursor N times
|
|
||||||
|T| T{char} 1 cursor till after Nth occurrence of {char}
|
|
||||||
to the left
|
|
||||||
|Y| ["x]Y yank N lines [into buffer x]; synonym for
|
|
||||||
"yy"
|
|
||||||
|c| ["x]c{motion} 2 delete Nmove text [into buffer x] and start
|
|
||||||
insert
|
|
||||||
|cc| ["x]cc 2 delete N lines [into buffer x] and start
|
|
||||||
|d| ["x]d{motion} 2 delete Nmove text [into buffer x]
|
|
||||||
|f| f{char} 1 cursor to Nth occurrence of {char} to the
|
|
||||||
right
|
|
||||||
|i| i 2 insert text before the cursor N times
|
|
||||||
|p| ["x]p 2 put the text [from register x] after the
|
|
||||||
cursor N times
|
|
||||||
|q| q{0-9a-zA-Z"} record typed characters into named register
|
|
||||||
{0-9a-zA-Z"} (uppercase to append)
|
|
||||||
|q| q (while recording) stops recording
|
|
||||||
|t| t{char} 1 cursor till before Nth occurrence of {char}
|
|
||||||
to the right
|
|
||||||
|y| ["x]y{motion} yank Nmove text [into buffer x]
|
|
||||||
|yy| ["x]yy yank N lines [into buffer x]
|
|
||||||
|~| ~ 2 'tildeop' off: switch case of N characters
|
|
||||||
under cursor and move the cursor N
|
|
||||||
characters to the right
|
|
||||||
|
|
||||||
==============================================================================
|
|
||||||
2.1 Text objects *objects*
|
|
||||||
|
|
||||||
These can be used after an operator or in Visual mode to select an object.
|
|
||||||
|
|
||||||
tag command action in op-pending and Visual mode ~
|
|
||||||
------------------------------------------------------------------------------
|
|
||||||
|v_aquote| a" double quoted string
|
|
||||||
|v_a'| a' single quoted string
|
|
||||||
|v_a(| a( same as ab
|
|
||||||
|v_a)| a) same as ab
|
|
||||||
|v_a<| a< "a <>" from '<' to the matching '>'
|
|
||||||
|v_a>| a> same as a<
|
|
||||||
|v_aB| aB "a Block" from "[{" to "]}" (with brackets)
|
|
||||||
|v_aW| aW "a WORD" (with white space)
|
|
||||||
|v_a[| a[ "a []" from '[' to the matching ']'
|
|
||||||
|v_a]| a] same as a[
|
|
||||||
|v_a`| a` string in backticks
|
|
||||||
|v_ab| ab "a block" from "[(" to "])" (with braces)
|
|
||||||
|v_ap| ap "a paragraph" (with white space)
|
|
||||||
|v_as| as "a sentence" (with white space)
|
|
||||||
|v_aw| aw "a word" (with white space)
|
|
||||||
|v_a{| a{ same as aB
|
|
||||||
|v_a}| a} same as aB
|
|
||||||
|v_iquote| i" double quoted string without the quotes
|
|
||||||
|v_i'| i' single quoted string without the quotes
|
|
||||||
|v_i(| i( same as ib
|
|
||||||
|v_i)| i) same as ib
|
|
||||||
|v_i<| i< "inner <>" from '<' to the matching '>'
|
|
||||||
|v_i>| i> same as i<
|
|
||||||
|v_iB| iB "inner Block" from "[{" and "]}"
|
|
||||||
|v_iW| iW "inner WORD"
|
|
||||||
|v_i[| i[ "inner []" from '[' to the matching ']'
|
|
||||||
|v_i]| i] same as i[
|
|
||||||
|v_i`| i` string in backticks without the backticks
|
|
||||||
|v_ib| ib "inner block" from "[(" to "])"
|
|
||||||
|v_ip| ip "inner paragraph"
|
|
||||||
|v_is| is "inner sentence"
|
|
||||||
|v_iw| iw "inner word"
|
|
||||||
|v_i{| i{ same as iB
|
|
||||||
|v_i}| i} same as iB
|
|
||||||
|
|
||||||
==============================================================================
|
|
||||||
2.3 Square bracket commands *[* *]*
|
|
||||||
|
|
||||||
tag char note action in Normal mode ~
|
|
||||||
------------------------------------------------------------------------------
|
|
||||||
|[(| [( 1 cursor N times back to unmatched '('
|
|
||||||
|[{| [{ 1 cursor N times back to unmatched '{'
|
|
||||||
|])| ]) 1 cursor N times forward to unmatched ')'
|
|
||||||
|]}| ]} 1 cursor N times forward to unmatched '}'
|
|
||||||
|
|
||||||
==============================================================================
|
|
||||||
2.4 Commands starting with 'g' *g*
|
|
||||||
|
|
||||||
tag char note action in Normal mode ~
|
|
||||||
------------------------------------------------------------------------------
|
|
||||||
|gg| gg 1 cursor to line N, default first line
|
|
||||||
|gi| gi 2 like "i", but first move to the |'^| mark
|
|
||||||
|
|
||||||
==============================================================================
|
|
||||||
3. Visual mode *visual-index*
|
|
||||||
|
|
||||||
Most commands in Visual mode are the same as in Normal mode. The ones listed
|
|
||||||
here are those that are different.
|
|
||||||
|
|
||||||
tag command note action in Visual mode ~
|
|
||||||
------------------------------------------------------------------------------
|
|
||||||
|v_y| y yank the highlighted area
|
|
||||||
|
|
||||||
==============================================================================
|
|
||||||
4. Command-line editing *ex-edit-index*
|
|
||||||
|
|
||||||
Get to the command-line with the ':', '!', '/' or '?' commands.
|
|
||||||
Normal characters are inserted at the current cursor position.
|
|
||||||
"Completion" below refers to context-sensitive completion. It will complete
|
|
||||||
file names, tags, commands etc. as appropriate.
|
|
||||||
|
|
||||||
tag command action in Command-line editing mode ~
|
|
||||||
------------------------------------------------------------------------------
|
|
||||||
|c_CTRL-R| CTRL-R {0-9a-z"%#*:= CTRL-F CTRL-P CTRL-W CTRL-A}
|
|
||||||
insert the contents of a register or object
|
|
||||||
under the cursor as if typed
|
|
||||||
|
|
||||||
==============================================================================
|
|
||||||
5. EX commands *ex-cmd-index*
|
|
||||||
|
|
||||||
This is a brief but complete listing of all the ":" commands, without
|
|
||||||
mentioning any arguments. The optional part of the command name is inside [].
|
|
||||||
The commands are sorted on the non-optional part of their name.
|
|
||||||
|
|
||||||
tag command action ~
|
|
||||||
------------------------------------------------------------------------------
|
|
||||||
|:display| :di[splay] display registers
|
|
||||||
|:registers| :reg[isters] display the contents of registers
|
|
||||||
|:substitute| :s[ubstitute] find and replace text
|
|
@ -4,6 +4,7 @@
|
|||||||
<change-notes><![CDATA[
|
<change-notes><![CDATA[
|
||||||
<p>To be released:</p>
|
<p>To be released:</p>
|
||||||
<ul>
|
<ul>
|
||||||
|
<li>Support :command command</li>
|
||||||
<li>Support :shell command</li>
|
<li>Support :shell command</li>
|
||||||
<li>Support :tabnext and :tabprevious commands</li>
|
<li>Support :tabnext and :tabprevious commands</li>
|
||||||
<li>Commentary extension</li>
|
<li>Commentary extension</li>
|
||||||
@ -67,6 +68,9 @@
|
|||||||
<component>
|
<component>
|
||||||
<implementation-class>com.maddyhome.idea.vim.VimPlugin</implementation-class>
|
<implementation-class>com.maddyhome.idea.vim.VimPlugin</implementation-class>
|
||||||
</component>
|
</component>
|
||||||
|
<component>
|
||||||
|
<implementation-class>com.maddyhome.idea.vim.VimLocalConfig</implementation-class>
|
||||||
|
</component>
|
||||||
</application-components>
|
</application-components>
|
||||||
|
|
||||||
<extensionPoints>
|
<extensionPoints>
|
||||||
@ -387,10 +391,7 @@
|
|||||||
<action id="VimPlaybackLastRegister" class="com.maddyhome.idea.vim.action.macro.PlaybackLastRegisterAction" text="Playback Last Register"/>
|
<action id="VimPlaybackLastRegister" class="com.maddyhome.idea.vim.action.macro.PlaybackLastRegisterAction" text="Playback Last Register"/>
|
||||||
|
|
||||||
<!-- Command Line -->
|
<!-- Command Line -->
|
||||||
<action id="VimExBackspace" class="com.maddyhome.idea.vim.action.ex.BackspaceAction" text="Backspace"/>
|
|
||||||
<action id="VimProcessExEntry" class="com.maddyhome.idea.vim.action.ex.ProcessExEntryAction" text="Process Ex Entry"/>
|
<action id="VimProcessExEntry" class="com.maddyhome.idea.vim.action.ex.ProcessExEntryAction" text="Process Ex Entry"/>
|
||||||
<action id="VimProcessExKey" class="com.maddyhome.idea.vim.action.ex.ProcessExKeyAction" text="Process Ex Key"/>
|
|
||||||
<action id="VimCancelExEntry" class="com.maddyhome.idea.vim.action.ex.CancelExEntryAction" text="Cancel Ex Entry"/>
|
|
||||||
|
|
||||||
<!-- Other -->
|
<!-- Other -->
|
||||||
<action id="VimLastSearchReplace" class="com.maddyhome.idea.vim.action.change.change.ChangeLastSearchReplaceAction" text="Repeat Last :s"/>
|
<action id="VimLastSearchReplace" class="com.maddyhome.idea.vim.action.change.change.ChangeLastSearchReplaceAction" text="Repeat Last :s"/>
|
||||||
@ -403,7 +404,7 @@
|
|||||||
<action id="VimUndo" class="com.maddyhome.idea.vim.action.change.UndoAction" text="Undo"/>
|
<action id="VimUndo" class="com.maddyhome.idea.vim.action.change.UndoAction" text="Undo"/>
|
||||||
|
|
||||||
<!-- Internal -->
|
<!-- Internal -->
|
||||||
<action id="VimInternalAddInlays" class="com.maddyhome.idea.vim.action.internal.AddInlaysAction" text="Vim (internal) add test inlays" internal="true"/>
|
<action id="VimInternalAddInlays" class="com.maddyhome.idea.vim.action.internal.AddInlaysAction" text="Add test inlays | IdeaVim internal" internal="true"/>
|
||||||
|
|
||||||
<!-- Keys -->
|
<!-- Keys -->
|
||||||
<action id="VimShortcutKeyAction" class="com.maddyhome.idea.vim.action.VimShortcutKeyAction" text="Shortcuts"/>
|
<action id="VimShortcutKeyAction" class="com.maddyhome.idea.vim.action.VimShortcutKeyAction" text="Shortcuts"/>
|
||||||
|
@ -247,15 +247,15 @@ public class KeyHandler {
|
|||||||
final CommandState commandState = CommandState.getInstance(editor);
|
final CommandState commandState = CommandState.getInstance(editor);
|
||||||
commandState.stopMappingTimer();
|
commandState.stopMappingTimer();
|
||||||
|
|
||||||
final List<KeyStroke> mappingKeys = commandState.getMappingKeys();
|
|
||||||
final List<KeyStroke> fromKeys = new ArrayList<KeyStroke>(mappingKeys);
|
|
||||||
fromKeys.add(key);
|
|
||||||
|
|
||||||
final MappingMode mappingMode = commandState.getMappingMode();
|
final MappingMode mappingMode = commandState.getMappingMode();
|
||||||
if (MappingMode.NVO.contains(mappingMode) && (state != State.NEW_COMMAND || currentArg != Argument.Type.NONE)) {
|
if (MappingMode.NVO.contains(mappingMode) && (state != State.NEW_COMMAND || currentArg != Argument.Type.NONE)) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
final List<KeyStroke> mappingKeys = commandState.getMappingKeys();
|
||||||
|
final List<KeyStroke> fromKeys = new ArrayList<KeyStroke>(mappingKeys);
|
||||||
|
fromKeys.add(key);
|
||||||
|
|
||||||
final KeyMapping mapping = VimPlugin.getKey().getKeyMapping(mappingMode);
|
final KeyMapping mapping = VimPlugin.getKey().getKeyMapping(mappingMode);
|
||||||
final MappingInfo currentMappingInfo = mapping.get(fromKeys);
|
final MappingInfo currentMappingInfo = mapping.get(fromKeys);
|
||||||
final MappingInfo prevMappingInfo = mapping.get(mappingKeys);
|
final MappingInfo prevMappingInfo = mapping.get(mappingKeys);
|
||||||
|
@ -31,11 +31,11 @@ import javax.swing.*;
|
|||||||
import java.awt.event.KeyEvent;
|
import java.awt.event.KeyEvent;
|
||||||
import java.util.EnumSet;
|
import java.util.EnumSet;
|
||||||
|
|
||||||
public class RegisterActions {
|
class RegisterActions {
|
||||||
/**
|
/**
|
||||||
* Register all the key/action mappings for the plugin.
|
* Register all the key/action mappings for the plugin.
|
||||||
*/
|
*/
|
||||||
public static void registerActions() {
|
static void registerActions() {
|
||||||
registerVimCommandActions();
|
registerVimCommandActions();
|
||||||
|
|
||||||
registerInsertModeActions();
|
registerInsertModeActions();
|
||||||
|
38
src/com/maddyhome/idea/vim/VimLocalConfig.kt
Normal file
38
src/com/maddyhome/idea/vim/VimLocalConfig.kt
Normal file
@ -0,0 +1,38 @@
|
|||||||
|
package com.maddyhome.idea.vim
|
||||||
|
|
||||||
|
import com.intellij.configurationStore.APP_CONFIG
|
||||||
|
import com.intellij.openapi.components.PersistentStateComponent
|
||||||
|
import com.intellij.openapi.components.RoamingType
|
||||||
|
import com.intellij.openapi.components.State
|
||||||
|
import com.intellij.openapi.components.Storage
|
||||||
|
import com.maddyhome.idea.vim.VimPlugin.STATE_VERSION
|
||||||
|
import org.jdom.Element
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @author Alex Plate
|
||||||
|
*/
|
||||||
|
|
||||||
|
@State(name = "VimLocalSettings",
|
||||||
|
storages = [Storage("$APP_CONFIG$/vim_local_settings.xml", roamingType = RoamingType.DISABLED)])
|
||||||
|
class VimLocalConfig : PersistentStateComponent<Element> {
|
||||||
|
override fun getState(): Element {
|
||||||
|
val element = Element("ideavim-local")
|
||||||
|
|
||||||
|
val state = Element("state")
|
||||||
|
state.setAttribute("version", Integer.toString(STATE_VERSION))
|
||||||
|
element.addContent(state)
|
||||||
|
|
||||||
|
VimPlugin.getMark().saveData(element)
|
||||||
|
VimPlugin.getRegister().saveData(element)
|
||||||
|
VimPlugin.getSearch().saveData(element)
|
||||||
|
VimPlugin.getHistory().saveData(element)
|
||||||
|
return element
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun loadState(state: Element) {
|
||||||
|
VimPlugin.getMark().readData(state)
|
||||||
|
VimPlugin.getRegister().readData(state)
|
||||||
|
VimPlugin.getSearch().readData(state)
|
||||||
|
VimPlugin.getHistory().readData(state)
|
||||||
|
}
|
||||||
|
}
|
@ -84,7 +84,7 @@ import java.util.concurrent.TimeUnit;
|
|||||||
*/
|
*/
|
||||||
@State(
|
@State(
|
||||||
name = "VimSettings",
|
name = "VimSettings",
|
||||||
storages = {@Storage(file = "$APP_CONFIG$/vim_settings.xml")})
|
storages = {@Storage("$APP_CONFIG$/vim_settings.xml")})
|
||||||
public class VimPlugin implements ApplicationComponent, PersistentStateComponent<Element> {
|
public class VimPlugin implements ApplicationComponent, PersistentStateComponent<Element> {
|
||||||
private static final String IDEAVIM_COMPONENT_NAME = "VimPlugin";
|
private static final String IDEAVIM_COMPONENT_NAME = "VimPlugin";
|
||||||
private static final String IDEAVIM_PLUGIN_ID = "IdeaVIM";
|
private static final String IDEAVIM_PLUGIN_ID = "IdeaVIM";
|
||||||
@ -92,7 +92,9 @@ public class VimPlugin implements ApplicationComponent, PersistentStateComponent
|
|||||||
public static final String IDEAVIM_NOTIFICATION_ID = "ideavim";
|
public static final String IDEAVIM_NOTIFICATION_ID = "ideavim";
|
||||||
public static final String IDEAVIM_STICKY_NOTIFICATION_ID = "ideavim-sticky";
|
public static final String IDEAVIM_STICKY_NOTIFICATION_ID = "ideavim-sticky";
|
||||||
public static final String IDEAVIM_NOTIFICATION_TITLE = "IdeaVim";
|
public static final String IDEAVIM_NOTIFICATION_TITLE = "IdeaVim";
|
||||||
public static final int STATE_VERSION = 4;
|
public static final int STATE_VERSION = 5;
|
||||||
|
|
||||||
|
private static long lastBeepTimeMillis;
|
||||||
|
|
||||||
private boolean error = false;
|
private boolean error = false;
|
||||||
|
|
||||||
@ -106,6 +108,7 @@ public class VimPlugin implements ApplicationComponent, PersistentStateComponent
|
|||||||
|
|
||||||
@NotNull private final MotionGroup motion;
|
@NotNull private final MotionGroup motion;
|
||||||
@NotNull private final ChangeGroup change;
|
@NotNull private final ChangeGroup change;
|
||||||
|
@NotNull private final CommandGroup command;
|
||||||
@NotNull private final MarkGroup mark;
|
@NotNull private final MarkGroup mark;
|
||||||
@NotNull private final RegisterGroup register;
|
@NotNull private final RegisterGroup register;
|
||||||
@NotNull private final FileGroup file;
|
@NotNull private final FileGroup file;
|
||||||
@ -124,6 +127,7 @@ public class VimPlugin implements ApplicationComponent, PersistentStateComponent
|
|||||||
public VimPlugin() {
|
public VimPlugin() {
|
||||||
motion = new MotionGroup();
|
motion = new MotionGroup();
|
||||||
change = new ChangeGroup();
|
change = new ChangeGroup();
|
||||||
|
command = new CommandGroup();
|
||||||
mark = new MarkGroup();
|
mark = new MarkGroup();
|
||||||
register = new RegisterGroup();
|
register = new RegisterGroup();
|
||||||
file = new FileGroup();
|
file = new FileGroup();
|
||||||
@ -201,10 +205,6 @@ public class VimPlugin implements ApplicationComponent, PersistentStateComponent
|
|||||||
state.setAttribute("enabled", Boolean.toString(enabled));
|
state.setAttribute("enabled", Boolean.toString(enabled));
|
||||||
element.addContent(state);
|
element.addContent(state);
|
||||||
|
|
||||||
mark.saveData(element);
|
|
||||||
register.saveData(element);
|
|
||||||
search.saveData(element);
|
|
||||||
history.saveData(element);
|
|
||||||
key.saveData(element);
|
key.saveData(element);
|
||||||
editor.saveData(element);
|
editor.saveData(element);
|
||||||
|
|
||||||
@ -227,10 +227,13 @@ public class VimPlugin implements ApplicationComponent, PersistentStateComponent
|
|||||||
previousKeyMap = state.getAttributeValue("keymap");
|
previousKeyMap = state.getAttributeValue("keymap");
|
||||||
}
|
}
|
||||||
|
|
||||||
mark.readData(element);
|
if (previousStateVersion > 0 && previousStateVersion < 5) {
|
||||||
register.readData(element);
|
// Migrate settings from 4 to 5 version
|
||||||
search.readData(element);
|
mark.readData(element);
|
||||||
history.readData(element);
|
register.readData(element);
|
||||||
|
search.readData(element);
|
||||||
|
history.readData(element);
|
||||||
|
}
|
||||||
key.readData(element);
|
key.readData(element);
|
||||||
editor.readData(element);
|
editor.readData(element);
|
||||||
}
|
}
|
||||||
@ -245,6 +248,9 @@ public class VimPlugin implements ApplicationComponent, PersistentStateComponent
|
|||||||
return getInstance().change;
|
return getInstance().change;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@NotNull
|
||||||
|
public static CommandGroup getCommand() { return getInstance().command; }
|
||||||
|
|
||||||
@NotNull
|
@NotNull
|
||||||
public static MarkGroup getMark() {
|
public static MarkGroup getMark() {
|
||||||
return getInstance().mark;
|
return getInstance().mark;
|
||||||
@ -359,7 +365,12 @@ public class VimPlugin implements ApplicationComponent, PersistentStateComponent
|
|||||||
getInstance().error = true;
|
getInstance().error = true;
|
||||||
}
|
}
|
||||||
else if (!Options.getInstance().isSet("visualbell")) {
|
else if (!Options.getInstance().isSet("visualbell")) {
|
||||||
Toolkit.getDefaultToolkit().beep();
|
// Vim only allows a beep once every half second - :help 'visualbell'
|
||||||
|
final long currentTimeMillis = System.currentTimeMillis();
|
||||||
|
if (currentTimeMillis - lastBeepTimeMillis > 500) {
|
||||||
|
Toolkit.getDefaultToolkit().beep();
|
||||||
|
lastBeepTimeMillis = currentTimeMillis;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -42,11 +42,9 @@ import java.awt.event.KeyEvent;
|
|||||||
public class VimTypedActionHandler implements TypedActionHandlerEx {
|
public class VimTypedActionHandler implements TypedActionHandlerEx {
|
||||||
private static final Logger logger = Logger.getInstance(VimTypedActionHandler.class.getName());
|
private static final Logger logger = Logger.getInstance(VimTypedActionHandler.class.getName());
|
||||||
|
|
||||||
private final TypedActionHandler origHandler;
|
|
||||||
@NotNull private final KeyHandler handler;
|
@NotNull private final KeyHandler handler;
|
||||||
|
|
||||||
public VimTypedActionHandler(TypedActionHandler origHandler) {
|
VimTypedActionHandler(TypedActionHandler origHandler) {
|
||||||
this.origHandler = origHandler;
|
|
||||||
handler = KeyHandler.getInstance();
|
handler = KeyHandler.getInstance();
|
||||||
handler.setOriginalHandler(origHandler);
|
handler.setOriginalHandler(origHandler);
|
||||||
}
|
}
|
||||||
@ -57,7 +55,7 @@ public class VimTypedActionHandler implements TypedActionHandlerEx {
|
|||||||
handler.beforeHandleKey(editor, KeyStroke.getKeyStroke(charTyped), context, plan);
|
handler.beforeHandleKey(editor, KeyStroke.getKeyStroke(charTyped), context, plan);
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
TypedActionHandler originalHandler = KeyHandler.getInstance().getOriginalHandler();
|
TypedActionHandler originalHandler = handler.getOriginalHandler();
|
||||||
if (originalHandler instanceof TypedActionHandlerEx) {
|
if (originalHandler instanceof TypedActionHandlerEx) {
|
||||||
((TypedActionHandlerEx)originalHandler).beforeExecute(editor, charTyped, context, plan);
|
((TypedActionHandlerEx)originalHandler).beforeExecute(editor, charTyped, context, plan);
|
||||||
}
|
}
|
||||||
@ -76,6 +74,7 @@ public class VimTypedActionHandler implements TypedActionHandlerEx {
|
|||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
try (final VimListenerSuppressor ignored = SelectionVimListenerSuppressor.INSTANCE.lock()) {
|
try (final VimListenerSuppressor ignored = SelectionVimListenerSuppressor.INSTANCE.lock()) {
|
||||||
|
TypedActionHandler origHandler = handler.getOriginalHandler();
|
||||||
origHandler.execute(editor, charTyped, context);
|
origHandler.execute(editor, charTyped, context);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -22,6 +22,7 @@ import com.intellij.openapi.actionSystem.AnActionEvent;
|
|||||||
import com.intellij.openapi.actionSystem.ToggleAction;
|
import com.intellij.openapi.actionSystem.ToggleAction;
|
||||||
import com.intellij.openapi.project.DumbAware;
|
import com.intellij.openapi.project.DumbAware;
|
||||||
import com.maddyhome.idea.vim.VimPlugin;
|
import com.maddyhome.idea.vim.VimPlugin;
|
||||||
|
import org.jetbrains.annotations.NotNull;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* This class is used to handle the Vim Plugin enabled/disabled toggle. This is most likely used as a menu option
|
* This class is used to handle the Vim Plugin enabled/disabled toggle. This is most likely used as a menu option
|
||||||
@ -34,7 +35,7 @@ public class VimPluginToggleAction extends ToggleAction implements DumbAware {
|
|||||||
* @param event The event that triggered the action
|
* @param event The event that triggered the action
|
||||||
* @return true if the toggle is on, false if off
|
* @return true if the toggle is on, false if off
|
||||||
*/
|
*/
|
||||||
public boolean isSelected(AnActionEvent event) {
|
public boolean isSelected(@NotNull AnActionEvent event) {
|
||||||
return VimPlugin.isEnabled();
|
return VimPlugin.isEnabled();
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -44,7 +45,7 @@ public class VimPluginToggleAction extends ToggleAction implements DumbAware {
|
|||||||
* @param event The event that triggered the action
|
* @param event The event that triggered the action
|
||||||
* @param b The new state - true is on, false is off
|
* @param b The new state - true is on, false is off
|
||||||
*/
|
*/
|
||||||
public void setSelected(AnActionEvent event, boolean b) {
|
public void setSelected(@NotNull AnActionEvent event, boolean b) {
|
||||||
VimPlugin.setEnabled(b);
|
VimPlugin.setEnabled(b);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -27,6 +27,9 @@ import com.maddyhome.idea.vim.handler.EditorActionHandlerBase;
|
|||||||
import org.jetbrains.annotations.NotNull;
|
import org.jetbrains.annotations.NotNull;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
* Called by KeyHandler to process the contents of the ex entry panel
|
||||||
|
* <p>
|
||||||
|
* The mapping for this action means that the ex command is executed as a write action
|
||||||
*/
|
*/
|
||||||
public class ProcessExEntryAction extends EditorAction {
|
public class ProcessExEntryAction extends EditorAction {
|
||||||
public ProcessExEntryAction() {
|
public ProcessExEntryAction() {
|
||||||
|
@ -1,41 +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 <http://www.gnu.org/licenses/>.
|
|
||||||
*/
|
|
||||||
|
|
||||||
package com.maddyhome.idea.vim.action.ex;
|
|
||||||
|
|
||||||
import com.intellij.openapi.actionSystem.DataContext;
|
|
||||||
import com.intellij.openapi.editor.Editor;
|
|
||||||
import com.intellij.openapi.editor.actionSystem.EditorAction;
|
|
||||||
import com.maddyhome.idea.vim.VimPlugin;
|
|
||||||
import com.maddyhome.idea.vim.command.Command;
|
|
||||||
import com.maddyhome.idea.vim.handler.EditorActionHandlerBase;
|
|
||||||
import org.jetbrains.annotations.NotNull;
|
|
||||||
|
|
||||||
/**
|
|
||||||
*/
|
|
||||||
public class ProcessExKeyAction extends EditorAction {
|
|
||||||
public ProcessExKeyAction() {
|
|
||||||
super(new Handler());
|
|
||||||
}
|
|
||||||
|
|
||||||
private static class Handler extends EditorActionHandlerBase {
|
|
||||||
protected boolean execute(@NotNull Editor editor, @NotNull DataContext context, @NotNull Command cmd) {
|
|
||||||
return VimPlugin.getProcess().processExKey(editor, cmd.getKeys().get(0));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
@ -54,7 +54,7 @@ public class WindowDownAction extends VimCommandAction {
|
|||||||
@NotNull
|
@NotNull
|
||||||
@Override
|
@Override
|
||||||
public Set<List<KeyStroke>> getKeyStrokesSet() {
|
public Set<List<KeyStroke>> getKeyStrokesSet() {
|
||||||
return parseKeysSet("<C-W>j", "<C-W><Down>");
|
return parseKeysSet("<C-W>j", "<C-W><C-J>", "<C-W><Down>");
|
||||||
}
|
}
|
||||||
|
|
||||||
@NotNull
|
@NotNull
|
||||||
|
@ -54,7 +54,7 @@ public class WindowLeftAction extends VimCommandAction {
|
|||||||
@NotNull
|
@NotNull
|
||||||
@Override
|
@Override
|
||||||
public Set<List<KeyStroke>> getKeyStrokesSet() {
|
public Set<List<KeyStroke>> getKeyStrokesSet() {
|
||||||
return parseKeysSet("<C-W>h", "<C-W><Left>");
|
return parseKeysSet("<C-W>h", "<C-W><C-H>", "<C-W><Left>");
|
||||||
}
|
}
|
||||||
|
|
||||||
@NotNull
|
@NotNull
|
||||||
|
@ -54,7 +54,7 @@ public class WindowRightAction extends VimCommandAction {
|
|||||||
@NotNull
|
@NotNull
|
||||||
@Override
|
@Override
|
||||||
public Set<List<KeyStroke>> getKeyStrokesSet() {
|
public Set<List<KeyStroke>> getKeyStrokesSet() {
|
||||||
return parseKeysSet("<C-W>l", "<C-W><Right>");
|
return parseKeysSet("<C-W>l", "<C-W><C-L>", "<C-W><Right>");
|
||||||
}
|
}
|
||||||
|
|
||||||
@NotNull
|
@NotNull
|
||||||
|
@ -54,7 +54,7 @@ public class WindowUpAction extends VimCommandAction {
|
|||||||
@NotNull
|
@NotNull
|
||||||
@Override
|
@Override
|
||||||
public Set<List<KeyStroke>> getKeyStrokesSet() {
|
public Set<List<KeyStroke>> getKeyStrokesSet() {
|
||||||
return parseKeysSet("<C-W>k", "<C-W><Up>");
|
return parseKeysSet("<C-W>k", "<C-W><C-K>", "<C-W><Up>");
|
||||||
}
|
}
|
||||||
|
|
||||||
@NotNull
|
@NotNull
|
||||||
|
75
src/com/maddyhome/idea/vim/common/Alias.kt
Normal file
75
src/com/maddyhome/idea/vim/common/Alias.kt
Normal file
@ -0,0 +1,75 @@
|
|||||||
|
/*
|
||||||
|
* IdeaVim - Vim emulator for IDEs based on the IntelliJ platform
|
||||||
|
* Copyright (C) 2003-2019 The IdeaVim authors
|
||||||
|
*
|
||||||
|
* This program is free software: you can redistribute it and/or modify
|
||||||
|
* it under the terms of the GNU General Public License as published by
|
||||||
|
* the Free Software Foundation, either version 2 of the License, or
|
||||||
|
* (at your option) any later version.
|
||||||
|
*
|
||||||
|
* This program is distributed in the hope that it will be useful,
|
||||||
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
* GNU General Public License for more details.
|
||||||
|
*
|
||||||
|
* You should have received a copy of the GNU General Public License
|
||||||
|
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package com.maddyhome.idea.vim.common
|
||||||
|
|
||||||
|
import com.maddyhome.idea.vim.VimPlugin
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @author Elliot Courant
|
||||||
|
*/
|
||||||
|
class Alias(
|
||||||
|
private val minimumNumberOfArguments: Int,
|
||||||
|
private val maximumNumberOfArguments: Int,
|
||||||
|
val name: String,
|
||||||
|
val command: String
|
||||||
|
) {
|
||||||
|
val numberOfArguments =
|
||||||
|
when {
|
||||||
|
this.minimumNumberOfArguments == 0 && this.maximumNumberOfArguments == 0 -> "0" // No arguments
|
||||||
|
this.minimumNumberOfArguments == 0 && this.maximumNumberOfArguments == -1 -> "*" // Any number of arguments
|
||||||
|
this.minimumNumberOfArguments == 0 && this.maximumNumberOfArguments == 1 -> "?" // Zero or one argument
|
||||||
|
this.minimumNumberOfArguments == 1 && this.maximumNumberOfArguments == -1 -> "+" // One or more arguments
|
||||||
|
else -> this.minimumNumberOfArguments.toString() // Specified number of arguments
|
||||||
|
}
|
||||||
|
|
||||||
|
private companion object {
|
||||||
|
const val LessThan = "<lt>"
|
||||||
|
const val Count = "<count>"
|
||||||
|
const val Arguments = "<args>"
|
||||||
|
const val QuotedArguments = "<q-args>"
|
||||||
|
}
|
||||||
|
|
||||||
|
fun getCommand(input: String, count: Int): String {
|
||||||
|
if (this.maximumNumberOfArguments == 0 && this.maximumNumberOfArguments == 0) {
|
||||||
|
return this.command
|
||||||
|
}
|
||||||
|
var compiledCommand = this.command
|
||||||
|
val cleanedInput = input.trim().removePrefix(name).trim()
|
||||||
|
if (minimumNumberOfArguments > 0 && cleanedInput.isEmpty()) {
|
||||||
|
VimPlugin.showMessage("E471: Argument required")
|
||||||
|
VimPlugin.indicateError()
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
for (symbol in arrayOf(Count, Arguments, QuotedArguments)) {
|
||||||
|
compiledCommand = compiledCommand.replace(symbol, when (symbol) {
|
||||||
|
Count -> arrayOf(count.toString())
|
||||||
|
Arguments -> arrayOf(cleanedInput)
|
||||||
|
QuotedArguments -> arrayOf("'$cleanedInput'")
|
||||||
|
else -> emptyArray()
|
||||||
|
}.joinToString(", "))
|
||||||
|
}
|
||||||
|
|
||||||
|
// We want to escape <lt> after we've dropped in all of our args, if they are
|
||||||
|
// using <lt> its because they are escaping something that we don't want to handle
|
||||||
|
// yet.
|
||||||
|
compiledCommand = compiledCommand.replace(LessThan, "<")
|
||||||
|
|
||||||
|
return compiledCommand
|
||||||
|
}
|
||||||
|
}
|
@ -40,6 +40,7 @@ import java.util.regex.Pattern;
|
|||||||
* executes Ex commands entered by the user.
|
* executes Ex commands entered by the user.
|
||||||
*/
|
*/
|
||||||
public class CommandParser {
|
public class CommandParser {
|
||||||
|
private static final int MAX_RECURSION = 100;
|
||||||
public static final int RES_EMPTY = 1;
|
public static final int RES_EMPTY = 1;
|
||||||
public static final int RES_ERROR = 1;
|
public static final int RES_ERROR = 1;
|
||||||
public static final int RES_READONLY = 1;
|
public static final int RES_READONLY = 1;
|
||||||
@ -73,7 +74,10 @@ public class CommandParser {
|
|||||||
new ActionListHandler();
|
new ActionListHandler();
|
||||||
new AsciiHandler();
|
new AsciiHandler();
|
||||||
new CmdFilterHandler();
|
new CmdFilterHandler();
|
||||||
|
new CmdHandler();
|
||||||
|
new CmdClearHandler();
|
||||||
new CopyTextHandler();
|
new CopyTextHandler();
|
||||||
|
new DelCmdHandler();
|
||||||
new DeleteLinesHandler();
|
new DeleteLinesHandler();
|
||||||
new DigraphHandler();
|
new DigraphHandler();
|
||||||
new DumpLineHandler();
|
new DumpLineHandler();
|
||||||
@ -165,14 +169,51 @@ public class CommandParser {
|
|||||||
*/
|
*/
|
||||||
public int processCommand(@NotNull Editor editor, @NotNull DataContext context, @NotNull String cmd,
|
public int processCommand(@NotNull Editor editor, @NotNull DataContext context, @NotNull String cmd,
|
||||||
int count) throws ExException {
|
int count) throws ExException {
|
||||||
|
return processCommand(editor, context, cmd, count, MAX_RECURSION);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Parse and execute an Ex command entered by the user
|
||||||
|
*
|
||||||
|
* @param editor The editor to run the command in
|
||||||
|
* @param context The data context
|
||||||
|
* @param cmd The text entered by the user
|
||||||
|
* @param count The count entered before the colon
|
||||||
|
* @param aliasCountdown A countdown for the depth of alias recursion that is allowed
|
||||||
|
* @return A bitwise collection of flags, if any, from the result of running the command.
|
||||||
|
* @throws ExException if any part of the command is invalid or unknown
|
||||||
|
*/
|
||||||
|
private int processCommand(@NotNull Editor editor, @NotNull DataContext context, @NotNull String cmd,
|
||||||
|
int count, int aliasCountdown) throws ExException {
|
||||||
// Nothing entered
|
// Nothing entered
|
||||||
int result = 0;
|
int result = 0;
|
||||||
if (cmd.length() == 0) {
|
if (cmd.length() == 0) {
|
||||||
return result | RES_EMPTY;
|
return result | RES_EMPTY;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Save the command history
|
// Only save the command to the history if it is at the top of the stack.
|
||||||
VimPlugin.getHistory().addEntry(HistoryGroup.COMMAND, cmd);
|
// We don't want to save the aliases that will be executed, only the actual
|
||||||
|
// user input.
|
||||||
|
if (aliasCountdown == MAX_RECURSION) {
|
||||||
|
// Save the command history
|
||||||
|
VimPlugin.getHistory().addEntry(HistoryGroup.COMMAND, cmd);
|
||||||
|
}
|
||||||
|
|
||||||
|
// If there is a command alias for the entered text, then process the alias and return that
|
||||||
|
// instead of the original command.
|
||||||
|
if (VimPlugin.getCommand().isAlias(cmd)) {
|
||||||
|
if (aliasCountdown > 0) {
|
||||||
|
String commandAlias = VimPlugin.getCommand().getAliasCommand(cmd, count);
|
||||||
|
if (commandAlias.isEmpty()) {
|
||||||
|
return result |= RES_ERROR;
|
||||||
|
}
|
||||||
|
return processCommand(editor, context, commandAlias, count, aliasCountdown - 1);
|
||||||
|
} else {
|
||||||
|
VimPlugin.showMessage("Recursion detected, maximum alias depth reached.");
|
||||||
|
VimPlugin.indicateError();
|
||||||
|
return result |= RES_ERROR;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// Parse the command
|
// Parse the command
|
||||||
final ExCommand command = parse(cmd);
|
final ExCommand command = parse(cmd);
|
||||||
@ -192,7 +233,7 @@ public class CommandParser {
|
|||||||
boolean ok = handler.process(editor, context, command, count);
|
boolean ok = handler.process(editor, context, command, count);
|
||||||
if (ok && !handler.getArgFlags().getFlags().contains(CommandHandler.Flag.DONT_SAVE_LAST)) {
|
if (ok && !handler.getArgFlags().getFlags().contains(CommandHandler.Flag.DONT_SAVE_LAST)) {
|
||||||
VimPlugin.getRegister().storeTextInternal(editor, new TextRange(-1, -1), cmd,
|
VimPlugin.getRegister().storeTextInternal(editor, new TextRange(-1, -1), cmd,
|
||||||
SelectionType.CHARACTER_WISE, ':', false);
|
SelectionType.CHARACTER_WISE, ':', false);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (handler.getArgFlags().getFlags().contains(CommandHandler.Flag.DONT_REOPEN)) {
|
if (handler.getArgFlags().getFlags().contains(CommandHandler.Flag.DONT_REOPEN)) {
|
||||||
|
@ -34,18 +34,20 @@ class ActionListHandler : CommandHandler(
|
|||||||
flags(RangeFlag.RANGE_FORBIDDEN, ArgumentFlag.ARGUMENT_OPTIONAL, DONT_REOPEN)
|
flags(RangeFlag.RANGE_FORBIDDEN, ArgumentFlag.ARGUMENT_OPTIONAL, DONT_REOPEN)
|
||||||
) {
|
) {
|
||||||
override fun execute(editor: Editor, context: DataContext, cmd: ExCommand): Boolean {
|
override fun execute(editor: Editor, context: DataContext, cmd: ExCommand): Boolean {
|
||||||
val lineSeparator = System.lineSeparator()
|
val lineSeparator = "\n"
|
||||||
val searchPattern = cmd.argument.trim().toLowerCase().split("*")
|
val searchPattern = cmd.argument.trim().toLowerCase().split("*")
|
||||||
val actionManager = ActionManager.getInstance()
|
val actionManager = ActionManager.getInstance()
|
||||||
|
|
||||||
val actions = actionManager.getActionIds("")
|
val actions = actionManager.getActionIds("")
|
||||||
.filter { actionName -> searchPattern.all { it in actionName.toLowerCase() } }
|
.sortedWith(String.CASE_INSENSITIVE_ORDER)
|
||||||
.sortedWith(String.CASE_INSENSITIVE_ORDER).joinToString(lineSeparator) { actionName ->
|
.map { actionName ->
|
||||||
val shortcuts = actionManager.getAction(actionName).shortcutSet.shortcuts.joinToString(" ") {
|
val shortcuts = actionManager.getAction(actionName).shortcutSet.shortcuts.joinToString(" ") {
|
||||||
if (it is KeyboardShortcut) StringHelper.toKeyNotation(it.firstKeyStroke) else it.toString()
|
if (it is KeyboardShortcut) StringHelper.toKeyNotation(it.firstKeyStroke) else it.toString()
|
||||||
}
|
}
|
||||||
if (shortcuts.isBlank()) actionName else "${actionName.padEnd(50)} $shortcuts"
|
if (shortcuts.isBlank()) actionName else "${actionName.padEnd(50)} $shortcuts"
|
||||||
}
|
}
|
||||||
|
.filter { line -> searchPattern.all { it in line.toLowerCase() } }
|
||||||
|
.joinToString(lineSeparator)
|
||||||
|
|
||||||
|
|
||||||
ExOutputModel.getInstance(editor).output("--- Actions ---$lineSeparator$actions")
|
ExOutputModel.getInstance(editor).output("--- Actions ---$lineSeparator$actions")
|
||||||
|
@ -16,26 +16,22 @@
|
|||||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
package com.maddyhome.idea.vim.action.ex;
|
package com.maddyhome.idea.vim.ex.handler
|
||||||
|
|
||||||
import com.intellij.openapi.actionSystem.DataContext;
|
import com.intellij.openapi.actionSystem.DataContext
|
||||||
import com.intellij.openapi.editor.Editor;
|
import com.intellij.openapi.editor.Editor
|
||||||
import com.intellij.openapi.editor.actionSystem.EditorAction;
|
import com.maddyhome.idea.vim.VimPlugin
|
||||||
import com.maddyhome.idea.vim.VimPlugin;
|
import com.maddyhome.idea.vim.ex.CommandHandler
|
||||||
import com.maddyhome.idea.vim.command.Command;
|
import com.maddyhome.idea.vim.ex.ExCommand
|
||||||
import com.maddyhome.idea.vim.handler.EditorActionHandlerBase;
|
import com.maddyhome.idea.vim.ex.commands
|
||||||
import org.jetbrains.annotations.NotNull;
|
import com.maddyhome.idea.vim.ex.flags
|
||||||
|
|
||||||
/**
|
class CmdClearHandler : CommandHandler(
|
||||||
*/
|
commands("comc[lear]"),
|
||||||
public class BackspaceAction extends EditorAction {
|
flags(RangeFlag.RANGE_FORBIDDEN, ArgumentFlag.ARGUMENT_FORBIDDEN)
|
||||||
public BackspaceAction() {
|
) {
|
||||||
super(new Handler());
|
override fun execute(editor: Editor, context: DataContext, cmd: ExCommand): Boolean {
|
||||||
}
|
VimPlugin.getCommand().resetAliases()
|
||||||
|
return true
|
||||||
private static class Handler extends EditorActionHandlerBase {
|
|
||||||
protected boolean execute(@NotNull Editor editor, @NotNull DataContext context, @NotNull Command cmd) {
|
|
||||||
return VimPlugin.getProcess().processExKey(editor, cmd.getKeys().get(0));
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
|
177
src/com/maddyhome/idea/vim/ex/handler/CmdHandler.kt
Normal file
177
src/com/maddyhome/idea/vim/ex/handler/CmdHandler.kt
Normal file
@ -0,0 +1,177 @@
|
|||||||
|
/*
|
||||||
|
* 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 <http://www.gnu.org/licenses/>.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package com.maddyhome.idea.vim.ex.handler
|
||||||
|
|
||||||
|
import com.intellij.openapi.actionSystem.DataContext
|
||||||
|
import com.intellij.openapi.editor.Editor
|
||||||
|
import com.maddyhome.idea.vim.VimPlugin
|
||||||
|
import com.maddyhome.idea.vim.common.Alias
|
||||||
|
import com.maddyhome.idea.vim.ex.*
|
||||||
|
import com.maddyhome.idea.vim.ex.vimscript.VimScriptCommandHandler
|
||||||
|
import com.maddyhome.idea.vim.group.CommandGroup.Companion.BLACKLISTED_ALIASES
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @author Elliot Courant
|
||||||
|
*/
|
||||||
|
class CmdHandler : CommandHandler(
|
||||||
|
commands("com[mand]"),
|
||||||
|
flags(RangeFlag.RANGE_FORBIDDEN, ArgumentFlag.ARGUMENT_OPTIONAL, Flag.DONT_REOPEN)
|
||||||
|
), VimScriptCommandHandler {
|
||||||
|
// Static definitions needed for aliases.
|
||||||
|
private companion object {
|
||||||
|
const val overridePrefix = "!"
|
||||||
|
const val argsPrefix = "-nargs"
|
||||||
|
|
||||||
|
const val anyNumberOfArguments = "*"
|
||||||
|
const val zeroOrOneArguments = "?"
|
||||||
|
const val moreThanZeroArguments = "+"
|
||||||
|
|
||||||
|
const val errorInvalidNumberOfArguments = "E176: Invalid number of arguments"
|
||||||
|
const val errorCannotStartWithLowercase = "E183: User defined commands must start with an uppercase letter"
|
||||||
|
const val errorReservedName = "E841: Reserved name, cannot be used for user defined command"
|
||||||
|
const val errorCommandAlreadyExists = "E174: Command already exists: add ! to replace it"
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun execute(cmd: ExCommand) {
|
||||||
|
this.addAlias(cmd, null)
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun execute(editor: Editor, context: DataContext, cmd: ExCommand): Boolean {
|
||||||
|
if (cmd.argument.trim().isEmpty()) {
|
||||||
|
return this.listAlias(editor, "")
|
||||||
|
}
|
||||||
|
return this.addAlias(cmd, editor)
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun listAlias(editor: Editor, filter: String): Boolean {
|
||||||
|
val lineSeparator = "\n"
|
||||||
|
val allAliases = VimPlugin.getCommand().listAliases()
|
||||||
|
val aliases = allAliases.filter {
|
||||||
|
(filter.isEmpty() || it.key.startsWith(filter))
|
||||||
|
}.map {
|
||||||
|
"${it.key.padEnd(12)}${it.value.numberOfArguments.padEnd(11)}${it.value.command}"
|
||||||
|
}.sortedWith(String.CASE_INSENSITIVE_ORDER).joinToString(lineSeparator)
|
||||||
|
ExOutputModel.getInstance(editor).output("Name Args Definition$lineSeparator$aliases")
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun addAlias(cmd: ExCommand, editor: Editor?): Boolean {
|
||||||
|
var argument = cmd.argument.trim()
|
||||||
|
|
||||||
|
// Handle overwriting of aliases
|
||||||
|
val overrideAlias = argument.startsWith(overridePrefix)
|
||||||
|
if (overrideAlias) {
|
||||||
|
argument = argument.removePrefix(overridePrefix).trim()
|
||||||
|
}
|
||||||
|
|
||||||
|
// Handle alias arguments
|
||||||
|
val hasArguments = argument.startsWith(argsPrefix)
|
||||||
|
var minNumberOfArgs = 0
|
||||||
|
var maxNumberOfArgs = 0
|
||||||
|
if (hasArguments) {
|
||||||
|
// Extract the -nargs that's part of this execution, it's possible that -nargs is
|
||||||
|
// in the actual alias being created, and we don't want to parse that one.
|
||||||
|
val trimmedInput = argument.takeWhile { it != ' ' }
|
||||||
|
val pattern = Regex("(?>-nargs=((|[-])\\d+|[?]|[+]|[*]))").find(trimmedInput) ?: run {
|
||||||
|
VimPlugin.showMessage(errorInvalidNumberOfArguments)
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
val nargForTrim = pattern.groupValues[0]
|
||||||
|
val argumentValue = pattern.groups[1]!!.value
|
||||||
|
val argNum = argumentValue.toIntOrNull()
|
||||||
|
if (argNum == null) { // If the argument number is null then it is not a number.
|
||||||
|
// Make sure the argument value is a valid symbol that we can handle.
|
||||||
|
when (argumentValue) {
|
||||||
|
anyNumberOfArguments -> {
|
||||||
|
minNumberOfArgs = 0
|
||||||
|
maxNumberOfArgs = -1
|
||||||
|
}
|
||||||
|
zeroOrOneArguments -> maxNumberOfArgs = 1
|
||||||
|
moreThanZeroArguments -> {
|
||||||
|
minNumberOfArgs = 1
|
||||||
|
maxNumberOfArgs = -1
|
||||||
|
}
|
||||||
|
else -> {
|
||||||
|
// Technically this should never be reached, but is here just in case
|
||||||
|
// I missed something, since the regex limits the value to be ? + * or
|
||||||
|
// a valid number, its not possible (as far as I know) to have another value
|
||||||
|
// that regex would accept that is not valid.
|
||||||
|
VimPlugin.showMessage(errorInvalidNumberOfArguments)
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// Not sure why this isn't documented, but if you try to create a command in vim
|
||||||
|
// with an explicit number of arguments greater than 1 it returns this error.
|
||||||
|
if (argNum > 1 || argNum < 0) {
|
||||||
|
VimPlugin.showMessage(errorInvalidNumberOfArguments)
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
minNumberOfArgs = argNum
|
||||||
|
maxNumberOfArgs = argNum
|
||||||
|
}
|
||||||
|
argument = argument.removePrefix(nargForTrim).trim()
|
||||||
|
}
|
||||||
|
|
||||||
|
// We want to trim off any "!" at the beginning of the arguments.
|
||||||
|
// This will also remove any extra spaces.
|
||||||
|
argument = argument.trim()
|
||||||
|
|
||||||
|
// We want to get the first character sequence in the arguments.
|
||||||
|
// eg. command! Wq wq
|
||||||
|
// We want to extract the Wq only, and then just use the rest of
|
||||||
|
// the argument as the alias result.
|
||||||
|
val alias = argument.split(" ")[0]
|
||||||
|
argument = argument.removePrefix(alias).trim()
|
||||||
|
|
||||||
|
// User-aliases need to begin with an uppercase character.
|
||||||
|
if (!alias[0].isUpperCase()) {
|
||||||
|
VimPlugin.showMessage(errorCannotStartWithLowercase)
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
if (alias in BLACKLISTED_ALIASES) {
|
||||||
|
VimPlugin.showMessage(errorReservedName)
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
if (argument.isEmpty()) {
|
||||||
|
if (editor == null) {
|
||||||
|
// If there is no editor then we can't list aliases, just return false.
|
||||||
|
// No message should be shown either, since there is no editor.
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
return this.listAlias(editor, alias)
|
||||||
|
}
|
||||||
|
|
||||||
|
// If we are not over-writing existing aliases, and an alias with the same command
|
||||||
|
// already exists then we want to do nothing.
|
||||||
|
if (!overrideAlias && VimPlugin.getCommand().hasAlias(alias)) {
|
||||||
|
VimPlugin.showMessage(errorCommandAlreadyExists)
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
// Store the alias and the command. We don't need to parse the argument
|
||||||
|
// at this time, if the syntax is wrong an error will be returned when
|
||||||
|
// the alias is executed.
|
||||||
|
VimPlugin.getCommand().setAlias(alias, Alias(minNumberOfArgs, maxNumberOfArgs, alias, argument))
|
||||||
|
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
}
|
42
src/com/maddyhome/idea/vim/ex/handler/DelCmdHandler.kt
Normal file
42
src/com/maddyhome/idea/vim/ex/handler/DelCmdHandler.kt
Normal file
@ -0,0 +1,42 @@
|
|||||||
|
/*
|
||||||
|
* 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 <http://www.gnu.org/licenses/>.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package com.maddyhome.idea.vim.ex.handler
|
||||||
|
|
||||||
|
import com.intellij.openapi.actionSystem.DataContext
|
||||||
|
import com.intellij.openapi.editor.Editor
|
||||||
|
import com.maddyhome.idea.vim.VimPlugin
|
||||||
|
import com.maddyhome.idea.vim.ex.CommandHandler
|
||||||
|
import com.maddyhome.idea.vim.ex.ExCommand
|
||||||
|
import com.maddyhome.idea.vim.ex.commands
|
||||||
|
import com.maddyhome.idea.vim.ex.flags
|
||||||
|
|
||||||
|
class DelCmdHandler : CommandHandler(
|
||||||
|
commands("delc[ommand]"),
|
||||||
|
flags(RangeFlag.RANGE_FORBIDDEN, ArgumentFlag.ARGUMENT_REQUIRED)
|
||||||
|
) {
|
||||||
|
override fun execute(editor: Editor, context: DataContext, cmd: ExCommand): Boolean {
|
||||||
|
if (!VimPlugin.getCommand().hasAlias(cmd.argument)) {
|
||||||
|
VimPlugin.showMessage("E184: No such user-defined command: ${cmd.argument}")
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
VimPlugin.getCommand().removeAlias(cmd.argument)
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
}
|
@ -1069,20 +1069,20 @@ public class ChangeGroup {
|
|||||||
if (motion == null) {
|
if (motion == null) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
EnumSet<CommandFlags> flags = motion.getFlags().clone();
|
||||||
if (!isChange && !motion.getFlags().contains(CommandFlags.FLAG_MOT_LINEWISE)) {
|
if (!isChange && !motion.getFlags().contains(CommandFlags.FLAG_MOT_LINEWISE)) {
|
||||||
LogicalPosition start = editor.offsetToLogicalPosition(range.getStartOffset());
|
LogicalPosition start = editor.offsetToLogicalPosition(range.getStartOffset());
|
||||||
LogicalPosition end = editor.offsetToLogicalPosition(range.getEndOffset());
|
LogicalPosition end = editor.offsetToLogicalPosition(range.getEndOffset());
|
||||||
if (start.line != end.line) {
|
if (start.line != end.line) {
|
||||||
if (!SearchHelper.anyNonWhitespace(editor, range.getStartOffset(), -1) &&
|
if (!SearchHelper.anyNonWhitespace(editor, range.getStartOffset(), -1) &&
|
||||||
!SearchHelper.anyNonWhitespace(editor, range.getEndOffset(), 1)) {
|
!SearchHelper.anyNonWhitespace(editor, range.getEndOffset(), 1)) {
|
||||||
EnumSet<CommandFlags> flags = motion.getFlags();
|
|
||||||
flags.remove(CommandFlags.FLAG_MOT_EXCLUSIVE);
|
flags.remove(CommandFlags.FLAG_MOT_EXCLUSIVE);
|
||||||
flags.remove(CommandFlags.FLAG_MOT_INCLUSIVE);
|
flags.remove(CommandFlags.FLAG_MOT_INCLUSIVE);
|
||||||
flags.add(CommandFlags.FLAG_MOT_LINEWISE);
|
flags.add(CommandFlags.FLAG_MOT_LINEWISE);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return deleteRange(editor, caret, range, SelectionType.fromCommandFlags(motion.getFlags()), isChange);
|
return deleteRange(editor, caret, range, SelectionType.fromCommandFlags(flags), isChange);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -1678,7 +1678,7 @@ public class ChangeGroup {
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (type == null || VimPlugin.getRegister().storeText(editor, range, type, true)) {
|
if (type == null || CommandState.inInsertMode(editor) || VimPlugin.getRegister().storeText(editor, range, type, true)) {
|
||||||
final Document document = editor.getDocument();
|
final Document document = editor.getDocument();
|
||||||
final int[] startOffsets = range.getStartOffsets();
|
final int[] startOffsets = range.getStartOffsets();
|
||||||
final int[] endOffsets = range.getEndOffsets();
|
final int[] endOffsets = range.getEndOffsets();
|
||||||
|
84
src/com/maddyhome/idea/vim/group/CommandGroup.kt
Normal file
84
src/com/maddyhome/idea/vim/group/CommandGroup.kt
Normal file
@ -0,0 +1,84 @@
|
|||||||
|
/*
|
||||||
|
* IdeaVim - Vim emulator for IDEs based on the IntelliJ platform
|
||||||
|
* Copyright (C) 2003-2019 The IdeaVim authors
|
||||||
|
*
|
||||||
|
* This program is free software: you can redistribute it and/or modify
|
||||||
|
* it under the terms of the GNU General Public License as published by
|
||||||
|
* the Free Software Foundation, either version 2 of the License, or
|
||||||
|
* (at your option) any later version.
|
||||||
|
*
|
||||||
|
* This program is distributed in the hope that it will be useful,
|
||||||
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
* GNU General Public License for more details.
|
||||||
|
*
|
||||||
|
* You should have received a copy of the GNU General Public License
|
||||||
|
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package com.maddyhome.idea.vim.group
|
||||||
|
|
||||||
|
import com.maddyhome.idea.vim.common.Alias
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @author Elliot Courant
|
||||||
|
*/
|
||||||
|
class CommandGroup {
|
||||||
|
companion object {
|
||||||
|
val BLACKLISTED_ALIASES = arrayOf("X", "Next", "Print")
|
||||||
|
private const val overridePrefix = "!"
|
||||||
|
}
|
||||||
|
private var aliases = HashMap<String, Alias>()
|
||||||
|
|
||||||
|
fun isAlias(command: String): Boolean {
|
||||||
|
val name = this.getAliasName(command)
|
||||||
|
// If the first letter is not uppercase then it cannot be an alias
|
||||||
|
// and reject immediately.
|
||||||
|
if (!name[0].isUpperCase()) {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
// If the input is blacklisted, then it is not an alias.
|
||||||
|
if (name in BLACKLISTED_ALIASES) {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
return this.hasAlias(name)
|
||||||
|
}
|
||||||
|
|
||||||
|
fun hasAlias(name: String): Boolean {
|
||||||
|
return name in this.aliases
|
||||||
|
}
|
||||||
|
|
||||||
|
fun getAlias(name: String): Alias {
|
||||||
|
return this.aliases[name]!!
|
||||||
|
}
|
||||||
|
|
||||||
|
fun getAliasCommand(command: String, count: Int): String {
|
||||||
|
return this.getAlias(this.getAliasName(command)).getCommand(command, count)
|
||||||
|
}
|
||||||
|
|
||||||
|
fun setAlias(name: String, alias: Alias) {
|
||||||
|
this.aliases[name] = alias
|
||||||
|
}
|
||||||
|
|
||||||
|
fun removeAlias(name: String) {
|
||||||
|
this.aliases.remove(name)
|
||||||
|
}
|
||||||
|
|
||||||
|
fun listAliases(): Set<Map.Entry<String, Alias>> {
|
||||||
|
return this.aliases.entries
|
||||||
|
}
|
||||||
|
|
||||||
|
fun resetAliases() {
|
||||||
|
this.aliases.clear()
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun getAliasName(command: String): String {
|
||||||
|
val items = command.split(" ")
|
||||||
|
if (items.count() > 1) {
|
||||||
|
return items[0].removeSuffix(overridePrefix)
|
||||||
|
}
|
||||||
|
return command.removeSuffix(overridePrefix)
|
||||||
|
}
|
||||||
|
}
|
@ -28,7 +28,6 @@ import com.intellij.openapi.editor.event.*;
|
|||||||
import com.intellij.openapi.editor.ex.EditorEx;
|
import com.intellij.openapi.editor.ex.EditorEx;
|
||||||
import com.intellij.openapi.project.Project;
|
import com.intellij.openapi.project.Project;
|
||||||
import com.maddyhome.idea.vim.EventFacade;
|
import com.maddyhome.idea.vim.EventFacade;
|
||||||
import com.maddyhome.idea.vim.KeyHandler;
|
|
||||||
import com.maddyhome.idea.vim.VimPlugin;
|
import com.maddyhome.idea.vim.VimPlugin;
|
||||||
import com.maddyhome.idea.vim.command.CommandState;
|
import com.maddyhome.idea.vim.command.CommandState;
|
||||||
import com.maddyhome.idea.vim.helper.*;
|
import com.maddyhome.idea.vim.helper.*;
|
||||||
@ -39,7 +38,6 @@ import org.jdom.Element;
|
|||||||
import org.jetbrains.annotations.NotNull;
|
import org.jetbrains.annotations.NotNull;
|
||||||
import org.jetbrains.annotations.Nullable;
|
import org.jetbrains.annotations.Nullable;
|
||||||
|
|
||||||
import javax.swing.*;
|
|
||||||
import java.awt.*;
|
import java.awt.*;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
|
||||||
@ -94,7 +92,7 @@ public class EditorGroup {
|
|||||||
// Turn on insert mode if editor doesn't have any file
|
// Turn on insert mode if editor doesn't have any file
|
||||||
if (!EditorData.isFileEditor(editor) && editor.getDocument().isWritable() &&
|
if (!EditorData.isFileEditor(editor) && editor.getDocument().isWritable() &&
|
||||||
!CommandState.inInsertMode(editor)) {
|
!CommandState.inInsertMode(editor)) {
|
||||||
KeyHandler.getInstance().handleKey(editor, KeyStroke.getKeyStroke('i'), new EditorDataContext(editor));
|
VimPlugin.getChange().insertBeforeCursor(editor, new EditorDataContext(editor));
|
||||||
}
|
}
|
||||||
editor.getSettings().setBlockCursor(!CommandState.inInsertMode(editor));
|
editor.getSettings().setBlockCursor(!CommandState.inInsertMode(editor));
|
||||||
editor.getSettings().setAnimatedScrolling(ANIMATED_SCROLLING_VIM_VALUE);
|
editor.getSettings().setAnimatedScrolling(ANIMATED_SCROLLING_VIM_VALUE);
|
||||||
|
@ -68,13 +68,12 @@ public class KeyGroup {
|
|||||||
@NotNull private final Map<MappingMode, KeyMapping> keyMappings = new HashMap<>();
|
@NotNull private final Map<MappingMode, KeyMapping> keyMappings = new HashMap<>();
|
||||||
@Nullable private OperatorFunction operatorFunction = null;
|
@Nullable private OperatorFunction operatorFunction = null;
|
||||||
|
|
||||||
public void registerRequiredShortcutKeys(@NotNull Editor editor) {
|
void registerRequiredShortcutKeys(@NotNull Editor editor) {
|
||||||
final Set<KeyStroke> requiredKeys = VimPlugin.getKey().requiredShortcutKeys;
|
|
||||||
EventFacade.getInstance().registerCustomShortcutSet(VimShortcutKeyAction.getInstance(),
|
EventFacade.getInstance().registerCustomShortcutSet(VimShortcutKeyAction.getInstance(),
|
||||||
toShortcutSet(requiredKeys), editor.getComponent());
|
toShortcutSet(requiredShortcutKeys), editor.getComponent());
|
||||||
}
|
}
|
||||||
|
|
||||||
public void unregisterShortcutKeys(@NotNull Editor editor) {
|
void unregisterShortcutKeys(@NotNull Editor editor) {
|
||||||
EventFacade.getInstance().unregisterCustomShortcutSet(VimShortcutKeyAction.getInstance(), editor.getComponent());
|
EventFacade.getInstance().unregisterCustomShortcutSet(VimShortcutKeyAction.getInstance(), editor.getComponent());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -54,6 +54,7 @@ import com.maddyhome.idea.vim.listener.VimListenerManager;
|
|||||||
import com.maddyhome.idea.vim.option.NumberOption;
|
import com.maddyhome.idea.vim.option.NumberOption;
|
||||||
import com.maddyhome.idea.vim.option.Options;
|
import com.maddyhome.idea.vim.option.Options;
|
||||||
import com.maddyhome.idea.vim.ui.ExEntryPanel;
|
import com.maddyhome.idea.vim.ui.ExEntryPanel;
|
||||||
|
import kotlin.ranges.IntProgression;
|
||||||
import org.jetbrains.annotations.NotNull;
|
import org.jetbrains.annotations.NotNull;
|
||||||
import org.jetbrains.annotations.Nullable;
|
import org.jetbrains.annotations.Nullable;
|
||||||
|
|
||||||
@ -1055,7 +1056,21 @@ public class MotionGroup {
|
|||||||
|
|
||||||
public int moveCaretHorizontal(@NotNull Editor editor, @NotNull Caret caret, int count, boolean allowPastEnd) {
|
public int moveCaretHorizontal(@NotNull Editor editor, @NotNull Caret caret, int count, boolean allowPastEnd) {
|
||||||
int oldOffset = caret.getOffset();
|
int oldOffset = caret.getOffset();
|
||||||
int offset = EditorHelper.normalizeOffset(editor, caret.getLogicalPosition().line, oldOffset + count, allowPastEnd);
|
int diff = 0;
|
||||||
|
String text = editor.getDocument().getText();
|
||||||
|
int sign = (int)Math.signum(count);
|
||||||
|
for (Integer pointer : new IntProgression(0, count - sign, sign)) {
|
||||||
|
int textPointer = oldOffset + pointer;
|
||||||
|
if (textPointer < text.length() && textPointer >= 0) {
|
||||||
|
// Actual char size can differ from 1 if unicode characters are used (like 🐔)
|
||||||
|
diff += Character.charCount(text.codePointAt(textPointer));
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
diff += 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
int offset =
|
||||||
|
EditorHelper.normalizeOffset(editor, caret.getLogicalPosition().line, oldOffset + (sign * diff), allowPastEnd);
|
||||||
|
|
||||||
if (offset == oldOffset) {
|
if (offset == oldOffset) {
|
||||||
return -1;
|
return -1;
|
||||||
|
@ -90,7 +90,7 @@ public class ProcessGroup {
|
|||||||
|
|
||||||
ExEntryPanel panel = ExEntryPanel.getInstance();
|
ExEntryPanel panel = ExEntryPanel.getInstance();
|
||||||
if (panel.isActive()) {
|
if (panel.isActive()) {
|
||||||
UiHelper.requestFocus(panel);
|
UiHelper.requestFocus(panel.getEntry());
|
||||||
panel.handleKey(stroke);
|
panel.handleKey(stroke);
|
||||||
|
|
||||||
return true;
|
return true;
|
||||||
@ -144,13 +144,11 @@ public class ProcessGroup {
|
|||||||
return res;
|
return res;
|
||||||
}
|
}
|
||||||
|
|
||||||
public boolean cancelExEntry(@NotNull final Editor editor, @NotNull final DataContext context) {
|
public void cancelExEntry(@NotNull final Editor editor, @NotNull final DataContext context) {
|
||||||
CommandState.getInstance(editor).popState();
|
CommandState.getInstance(editor).popState();
|
||||||
KeyHandler.getInstance().reset(editor);
|
KeyHandler.getInstance().reset(editor);
|
||||||
ExEntryPanel panel = ExEntryPanel.getInstance();
|
ExEntryPanel panel = ExEntryPanel.getInstance();
|
||||||
panel.deactivate(true);
|
panel.deactivate(true);
|
||||||
|
|
||||||
return true;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private void record(Editor editor, @NotNull String text) {
|
private void record(Editor editor, @NotNull String text) {
|
||||||
|
@ -49,7 +49,7 @@ public class DigraphSequence {
|
|||||||
if (key.getKeyCode() == KeyEvent.VK_K && (key.getModifiers() & KeyEvent.CTRL_MASK) != 0) {
|
if (key.getKeyCode() == KeyEvent.VK_K && (key.getModifiers() & KeyEvent.CTRL_MASK) != 0) {
|
||||||
logger.debug("found Ctrl-K");
|
logger.debug("found Ctrl-K");
|
||||||
digraphState = DIG_STATE_DIG_ONE;
|
digraphState = DIG_STATE_DIG_ONE;
|
||||||
return DigraphResult.OK;
|
return DigraphResult.OK_DIGRAPH;
|
||||||
}
|
}
|
||||||
else if ((key.getKeyCode() == KeyEvent.VK_V || key.getKeyCode() == KeyEvent.VK_Q) &&
|
else if ((key.getKeyCode() == KeyEvent.VK_V || key.getKeyCode() == KeyEvent.VK_Q) &&
|
||||||
(key.getModifiers() & KeyEvent.CTRL_MASK) != 0) {
|
(key.getModifiers() & KeyEvent.CTRL_MASK) != 0) {
|
||||||
@ -57,7 +57,7 @@ public class DigraphSequence {
|
|||||||
digraphState = DIG_STATE_CODE_START;
|
digraphState = DIG_STATE_CODE_START;
|
||||||
codeChars = new char[8];
|
codeChars = new char[8];
|
||||||
codeCnt = 0;
|
codeCnt = 0;
|
||||||
return DigraphResult.OK;
|
return DigraphResult.OK_LITERAL;
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
return new DigraphResult(key);
|
return new DigraphResult(key);
|
||||||
@ -68,7 +68,7 @@ public class DigraphSequence {
|
|||||||
digraphChar = key.getKeyChar();
|
digraphChar = key.getKeyChar();
|
||||||
digraphState = DIG_STATE_DIG_TWO;
|
digraphState = DIG_STATE_DIG_TWO;
|
||||||
|
|
||||||
return DigraphResult.OK;
|
return new DigraphResult(DigraphResult.RES_OK, digraphChar);
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
digraphState = DIG_STATE_START;
|
digraphState = DIG_STATE_START;
|
||||||
@ -93,26 +93,26 @@ public class DigraphSequence {
|
|||||||
digraphState = DIG_STATE_CODE_CHAR;
|
digraphState = DIG_STATE_CODE_CHAR;
|
||||||
codeType = 8;
|
codeType = 8;
|
||||||
logger.debug("Octal");
|
logger.debug("Octal");
|
||||||
return DigraphResult.OK;
|
return DigraphResult.OK_LITERAL;
|
||||||
case 'x':
|
case 'x':
|
||||||
case 'X':
|
case 'X':
|
||||||
codeMax = 2;
|
codeMax = 2;
|
||||||
digraphState = DIG_STATE_CODE_CHAR;
|
digraphState = DIG_STATE_CODE_CHAR;
|
||||||
codeType = 16;
|
codeType = 16;
|
||||||
logger.debug("hex2");
|
logger.debug("hex2");
|
||||||
return DigraphResult.OK;
|
return DigraphResult.OK_LITERAL;
|
||||||
case 'u':
|
case 'u':
|
||||||
codeMax = 4;
|
codeMax = 4;
|
||||||
digraphState = DIG_STATE_CODE_CHAR;
|
digraphState = DIG_STATE_CODE_CHAR;
|
||||||
codeType = 16;
|
codeType = 16;
|
||||||
logger.debug("hex4");
|
logger.debug("hex4");
|
||||||
return DigraphResult.OK;
|
return DigraphResult.OK_LITERAL;
|
||||||
case 'U':
|
case 'U':
|
||||||
codeMax = 8;
|
codeMax = 8;
|
||||||
digraphState = DIG_STATE_CODE_CHAR;
|
digraphState = DIG_STATE_CODE_CHAR;
|
||||||
codeType = 16;
|
codeType = 16;
|
||||||
logger.debug("hex8");
|
logger.debug("hex8");
|
||||||
return DigraphResult.OK;
|
return DigraphResult.OK_LITERAL;
|
||||||
case '0':
|
case '0':
|
||||||
case '1':
|
case '1':
|
||||||
case '2':
|
case '2':
|
||||||
@ -128,7 +128,7 @@ public class DigraphSequence {
|
|||||||
codeType = 10;
|
codeType = 10;
|
||||||
codeChars[codeCnt++] = key.getKeyChar();
|
codeChars[codeCnt++] = key.getKeyChar();
|
||||||
logger.debug("decimal");
|
logger.debug("decimal");
|
||||||
return DigraphResult.OK;
|
return DigraphResult.OK_LITERAL;
|
||||||
default:
|
default:
|
||||||
switch (key.getKeyCode()) {
|
switch (key.getKeyCode()) {
|
||||||
case KeyEvent.VK_TAB:
|
case KeyEvent.VK_TAB:
|
||||||
@ -177,7 +177,7 @@ public class DigraphSequence {
|
|||||||
return new DigraphResult(code);
|
return new DigraphResult(code);
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
return DigraphResult.OK;
|
return DigraphResult.OK_LITERAL;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
else if (codeCnt > 0) {
|
else if (codeCnt > 0) {
|
||||||
@ -204,14 +204,21 @@ public class DigraphSequence {
|
|||||||
public static final int RES_BAD = 1;
|
public static final int RES_BAD = 1;
|
||||||
public static final int RES_DONE = 2;
|
public static final int RES_DONE = 2;
|
||||||
|
|
||||||
public static final DigraphResult OK = new DigraphResult(RES_OK);
|
static final DigraphResult OK_DIGRAPH = new DigraphResult(RES_OK, '?');
|
||||||
public static final DigraphResult BAD = new DigraphResult(RES_BAD);
|
static final DigraphResult OK_LITERAL = new DigraphResult(RES_OK, '^');
|
||||||
|
static final DigraphResult BAD = new DigraphResult(RES_BAD);
|
||||||
|
|
||||||
DigraphResult(int result) {
|
DigraphResult(int result) {
|
||||||
this.result = result;
|
this.result = result;
|
||||||
stroke = null;
|
stroke = null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
DigraphResult(int result, char promptCharacter) {
|
||||||
|
this.result = result;
|
||||||
|
this.promptCharacter = promptCharacter;
|
||||||
|
stroke = null;
|
||||||
|
}
|
||||||
|
|
||||||
DigraphResult(@Nullable KeyStroke stroke) {
|
DigraphResult(@Nullable KeyStroke stroke) {
|
||||||
result = RES_DONE;
|
result = RES_DONE;
|
||||||
this.stroke = stroke;
|
this.stroke = stroke;
|
||||||
@ -226,8 +233,13 @@ public class DigraphSequence {
|
|||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public char getPromptCharacter() {
|
||||||
|
return promptCharacter;
|
||||||
|
}
|
||||||
|
|
||||||
private final int result;
|
private final int result;
|
||||||
@Nullable private final KeyStroke stroke;
|
@Nullable private final KeyStroke stroke;
|
||||||
|
private char promptCharacter;
|
||||||
}
|
}
|
||||||
|
|
||||||
private int digraphState = DIG_STATE_START;
|
private int digraphState = DIG_STATE_START;
|
||||||
|
@ -18,7 +18,6 @@
|
|||||||
|
|
||||||
package com.maddyhome.idea.vim.helper;
|
package com.maddyhome.idea.vim.helper;
|
||||||
|
|
||||||
import com.google.common.collect.Lists;
|
|
||||||
import com.intellij.lang.CodeDocumentationAwareCommenter;
|
import com.intellij.lang.CodeDocumentationAwareCommenter;
|
||||||
import com.intellij.lang.Commenter;
|
import com.intellij.lang.Commenter;
|
||||||
import com.intellij.lang.Language;
|
import com.intellij.lang.Language;
|
||||||
@ -27,7 +26,6 @@ import com.intellij.openapi.diagnostic.Logger;
|
|||||||
import com.intellij.openapi.editor.Caret;
|
import com.intellij.openapi.editor.Caret;
|
||||||
import com.intellij.openapi.editor.Editor;
|
import com.intellij.openapi.editor.Editor;
|
||||||
import com.intellij.openapi.util.Pair;
|
import com.intellij.openapi.util.Pair;
|
||||||
import com.intellij.openapi.util.text.StringUtil;
|
|
||||||
import com.intellij.psi.PsiComment;
|
import com.intellij.psi.PsiComment;
|
||||||
import com.intellij.psi.PsiElement;
|
import com.intellij.psi.PsiElement;
|
||||||
import com.intellij.psi.PsiFile;
|
import com.intellij.psi.PsiFile;
|
||||||
@ -42,6 +40,7 @@ import org.jetbrains.annotations.Nullable;
|
|||||||
|
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
import java.util.Stack;
|
||||||
import java.util.regex.Matcher;
|
import java.util.regex.Matcher;
|
||||||
import java.util.regex.Pattern;
|
import java.util.regex.Pattern;
|
||||||
|
|
||||||
@ -115,13 +114,32 @@ public class SearchHelper {
|
|||||||
int pos = caret.getOffset();
|
int pos = caret.getOffset();
|
||||||
int start = caret.getSelectionStart();
|
int start = caret.getSelectionStart();
|
||||||
int end = caret.getSelectionEnd();
|
int end = caret.getSelectionEnd();
|
||||||
if (start != end) {
|
|
||||||
pos = Math.min(start, end);
|
|
||||||
}
|
|
||||||
|
|
||||||
int loc = blockChars.indexOf(type);
|
int loc = blockChars.indexOf(type);
|
||||||
char close = blockChars.charAt(loc + 1);
|
char close = blockChars.charAt(loc + 1);
|
||||||
|
|
||||||
|
boolean rangeSelection = end - start > 1;
|
||||||
|
if (rangeSelection && start == 0) // early return not only for optimization
|
||||||
|
return null; // but also not to break the interval semantic on this edge case (see below)
|
||||||
|
|
||||||
|
/* In case of successive inner selection. We want to break out of
|
||||||
|
* the block delimiter of the current inner selection.
|
||||||
|
* In other terms, for the rest of the algorithm, a previous inner selection of a block
|
||||||
|
* if equivalent to an outer one. */
|
||||||
|
if (!isOuter
|
||||||
|
&& (start - 1) >= 0 && type == chars.charAt(start - 1)
|
||||||
|
&& end < chars.length() && close == chars.charAt(end)) {
|
||||||
|
start = start - 1;
|
||||||
|
pos = start;
|
||||||
|
rangeSelection = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* when one char is selected, we want to find the enclosing block of (start,end]
|
||||||
|
* although when a range of characters is selected, we want the enclosing block of [start, end]
|
||||||
|
* shifting the position allow to express which kind of interval we work on */
|
||||||
|
if (rangeSelection)
|
||||||
|
pos = Math.max(0, start - 1);
|
||||||
|
|
||||||
boolean initialPosIsInString = checkInString(chars, pos, true);
|
boolean initialPosIsInString = checkInString(chars, pos, true);
|
||||||
|
|
||||||
int bstart = -1;
|
int bstart = -1;
|
||||||
@ -413,80 +431,192 @@ public class SearchHelper {
|
|||||||
return -1;
|
return -1;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/** returns new position which ignore whitespaces at beginning of the line*/
|
||||||
|
private static int ignoreWhitespaceAtLineStart(CharSequence seq, int lineStart, int pos) {
|
||||||
|
if (seq.subSequence(lineStart, pos).chars().allMatch(Character::isWhitespace)) {
|
||||||
|
while (pos < seq.length() && seq.charAt(pos) != '\n' && Character.isWhitespace(seq.charAt(pos))) {
|
||||||
|
pos++;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return pos;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
@Nullable
|
@Nullable
|
||||||
public static TextRange findBlockTagRange(@NotNull Editor editor, @NotNull Caret caret, int count, boolean isOuter) {
|
public static TextRange findBlockTagRange(@NotNull Editor editor, @NotNull Caret caret, int count, boolean isOuter) {
|
||||||
final int cursorOffset = caret.getOffset();
|
final int position = caret.getOffset();
|
||||||
int pos = cursorOffset;
|
|
||||||
int currentCount = count;
|
|
||||||
final CharSequence sequence = editor.getDocument().getCharsSequence();
|
final CharSequence sequence = editor.getDocument().getCharsSequence();
|
||||||
|
|
||||||
|
final int selectionStart = caret.getSelectionStart();
|
||||||
|
final int selectionEnd = caret.getSelectionEnd();
|
||||||
|
|
||||||
|
final boolean isRangeSelection = selectionEnd - selectionStart > 1;
|
||||||
|
|
||||||
|
int searchStartPosition;
|
||||||
|
if (!isRangeSelection) {
|
||||||
|
final int line = caret.getLogicalPosition().line;
|
||||||
|
final int lineBegin = editor.getDocument().getLineStartOffset(line);
|
||||||
|
searchStartPosition = ignoreWhitespaceAtLineStart(sequence, lineBegin, position);
|
||||||
|
} else {
|
||||||
|
searchStartPosition = selectionEnd;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (isInHTMLTag(sequence, searchStartPosition, false)) {
|
||||||
|
// caret is inside opening tag. Move to closing '>'.
|
||||||
|
while (searchStartPosition < sequence.length() && sequence.charAt(searchStartPosition) != '>') {
|
||||||
|
searchStartPosition ++;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else if (isInHTMLTag(sequence, searchStartPosition, true)) {
|
||||||
|
// caret is inside closing tag. Move to starting '<'.
|
||||||
|
while (searchStartPosition > 0 && sequence.charAt(searchStartPosition) != '<') {
|
||||||
|
searchStartPosition --;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
while (true) {
|
while (true) {
|
||||||
final Pair<TextRange, String> closingTagResult = findClosingTag(sequence, pos);
|
final Pair<TextRange, String> closingTag = findUnmatchedClosingTag(sequence, searchStartPosition, count);
|
||||||
if (closingTagResult == null) {
|
if (closingTag == null) {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
final TextRange closingTagTextRange = closingTagResult.getFirst();
|
final TextRange closingTagTextRange = closingTag.getFirst();
|
||||||
final String tagName = closingTagResult.getSecond();
|
final String tagName = closingTag.getSecond();
|
||||||
final TextRange openingTagTextRange = findOpeningTag(sequence, closingTagTextRange.getStartOffset(), tagName);
|
|
||||||
if (openingTagTextRange != null && openingTagTextRange.getStartOffset() <= cursorOffset && --currentCount == 0) {
|
TextRange openingTag = findUnmatchedOpeningTag(sequence, closingTagTextRange.getStartOffset(), tagName);
|
||||||
if (isOuter) {
|
if (openingTag == null) {
|
||||||
return new TextRange(openingTagTextRange.getStartOffset(), closingTagTextRange.getEndOffset());
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (isRangeSelection && openingTag.getEndOffset() - 1 >= selectionStart) {
|
||||||
|
// If there was already some text selected and the new selection would not extend further, we try again
|
||||||
|
searchStartPosition = closingTagTextRange.getEndOffset();
|
||||||
|
count = 1;
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
int selectionEndWithoutNewline = selectionEnd;
|
||||||
|
while (selectionEndWithoutNewline < sequence.length() && sequence.charAt(selectionEndWithoutNewline) == '\n') {
|
||||||
|
selectionEndWithoutNewline ++;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (closingTagTextRange.getStartOffset() == selectionEndWithoutNewline && openingTag.getEndOffset() == selectionStart) {
|
||||||
|
// Special case: if the inner tag is already selected we should like isOuter is active
|
||||||
|
// Note that we need to ignore newlines, because their selection is lost between multiple "it" invocations
|
||||||
|
isOuter = true;
|
||||||
|
} else
|
||||||
|
if (openingTag.getEndOffset() == closingTagTextRange.getStartOffset() && selectionStart == openingTag.getEndOffset()) {
|
||||||
|
// Special case: for an empty tag pair (e.g. <a></a>) the whole tag is selected if the caret is in the middle.
|
||||||
|
isOuter = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (isOuter) {
|
||||||
|
return new TextRange(openingTag.getStartOffset(), closingTagTextRange.getEndOffset() - 1);
|
||||||
|
} else {
|
||||||
|
return new TextRange(openingTag.getEndOffset(), Math.max(closingTagTextRange.getStartOffset() - 1, openingTag.getEndOffset()));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns true if there is a html at the given position. Ignores tags with a trailing slash like <aaa/>.
|
||||||
|
*/
|
||||||
|
private static boolean isInHTMLTag(@NotNull final CharSequence sequence, final int position, final boolean isEndtag) {
|
||||||
|
int openingBracket = -1;
|
||||||
|
for (int i = position; i >= 0 && i < sequence.length(); i--) {
|
||||||
|
if (sequence.charAt(i) == '<') {
|
||||||
|
openingBracket = i;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
if (sequence.charAt(i) == '>' && i != position) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (openingBracket == -1) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
boolean hasSlashAfterOpening = openingBracket + 1 < sequence.length() && sequence.charAt(openingBracket + 1) == '/';
|
||||||
|
if ((isEndtag && !hasSlashAfterOpening) || (!isEndtag && hasSlashAfterOpening)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
int closingBracket = -1;
|
||||||
|
for (int i = openingBracket; i < sequence.length(); i++) {
|
||||||
|
if (sequence.charAt(i) == '>') {
|
||||||
|
closingBracket = i;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return closingBracket != -1 && sequence.charAt(closingBracket - 1) != '/';
|
||||||
|
}
|
||||||
|
|
||||||
|
@Nullable
|
||||||
|
private static Pair<TextRange,String> findUnmatchedClosingTag(@NotNull final CharSequence sequence, final int position, int count) {
|
||||||
|
// The tag name may contain any characters except slashes, whitespace and '>'
|
||||||
|
final String tagNamePattern = "([^/\\s>]+)";
|
||||||
|
// An opening tag consists of '<' followed by a tag name, optionally some additional text after whitespace and a '>'
|
||||||
|
final String openingTagPattern = String.format("<%s(?:\\s[^>]*)?>", tagNamePattern);
|
||||||
|
final String closingTagPattern = String.format("</%s>", tagNamePattern);
|
||||||
|
final Pattern tagPattern = Pattern.compile(String.format("(?:%s)|(?:%s)", openingTagPattern, closingTagPattern));
|
||||||
|
final Matcher matcher = tagPattern.matcher(sequence.subSequence(position, sequence.length()));
|
||||||
|
|
||||||
|
final Stack<String> openTags = new Stack<>();
|
||||||
|
|
||||||
|
while (matcher.find()) {
|
||||||
|
boolean isClosingTag = matcher.group(1) == null;
|
||||||
|
if (isClosingTag) {
|
||||||
|
final String tagName = matcher.group(2);
|
||||||
|
// Ignore unmatched open tags. Either the file is malformed or it might be a tag like <br> that does not need to be closed.
|
||||||
|
while (!openTags.isEmpty() && !openTags.peek().equalsIgnoreCase(tagName)) {
|
||||||
|
openTags.pop();
|
||||||
}
|
}
|
||||||
else {
|
if (openTags.isEmpty()) {
|
||||||
return new TextRange(openingTagTextRange.getEndOffset() + 1, closingTagTextRange.getStartOffset() - 1);
|
if (count <= 1) {
|
||||||
|
return Pair.create(new TextRange(position + matcher.start(), position + matcher.end()), tagName);
|
||||||
|
} else {
|
||||||
|
count--;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
openTags.pop();
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
final String tagName = matcher.group(1);
|
||||||
|
openTags.push(tagName);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Nullable
|
||||||
|
private static TextRange findUnmatchedOpeningTag(@NotNull CharSequence sequence, int position, @NotNull String tagName) {
|
||||||
|
final String quotedTagName = Pattern.quote(tagName);
|
||||||
|
final String patternString = "(</%s>)" // match closing tags
|
||||||
|
+ "|(<%s" // or opening tags starting with tagName
|
||||||
|
+ "(\\s([^>]*" // After at least one whitespace there might be additional text in the tag. E.g. <html lang="en">
|
||||||
|
+ "[^/])?)?>)"; // Slash is not allowed as last character (this would be a self closing tag).
|
||||||
|
final Pattern tagPattern = Pattern.compile(String.format(patternString, quotedTagName, quotedTagName), Pattern.CASE_INSENSITIVE);
|
||||||
|
final Matcher matcher = tagPattern.matcher(sequence.subSequence(0, position+1));
|
||||||
|
final Stack<TextRange> openTags = new Stack<>();
|
||||||
|
|
||||||
|
while (matcher.find()) {
|
||||||
|
final TextRange match = new TextRange(matcher.start(), matcher.end());
|
||||||
|
if (sequence.charAt(matcher.start() + 1) == '/') {
|
||||||
|
if (!openTags.isEmpty()) {
|
||||||
|
openTags.pop();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
pos = closingTagTextRange.getEndOffset() + 1;
|
openTags.push(match);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
@Nullable
|
if (openTags.isEmpty()) {
|
||||||
private static TextRange findOpeningTag(@NotNull CharSequence sequence, int position, @NotNull String tagName) {
|
return null;
|
||||||
final String tagBeginning = "<" + tagName;
|
} else {
|
||||||
final Pattern pattern = Pattern.compile(Pattern.quote(tagBeginning), Pattern.CASE_INSENSITIVE);
|
return openTags.pop();
|
||||||
final Matcher matcher = pattern.matcher(sequence.subSequence(0, position));
|
|
||||||
final List<Integer> possibleBeginnings = Lists.newArrayList();
|
|
||||||
while (matcher.find()) {
|
|
||||||
possibleBeginnings.add(matcher.start());
|
|
||||||
}
|
}
|
||||||
final List<Integer> reversedBeginnings = Lists.reverse(possibleBeginnings);
|
|
||||||
for (int openingTagPos : reversedBeginnings) {
|
|
||||||
final int openingTagEndPos = openingTagPos + tagBeginning.length();
|
|
||||||
final int closeBracketPos = StringUtil.indexOf(sequence, '>', openingTagEndPos);
|
|
||||||
if (closeBracketPos > 0 && (closeBracketPos == openingTagEndPos || sequence.charAt(openingTagEndPos) == ' ')) {
|
|
||||||
return new TextRange(openingTagPos, closeBracketPos);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Nullable
|
|
||||||
private static Pair<TextRange, String> findClosingTag(@NotNull CharSequence sequence, int pos) {
|
|
||||||
int closeBracketPos = pos;
|
|
||||||
int openBracketPos;
|
|
||||||
while (closeBracketPos < sequence.length()) {
|
|
||||||
closeBracketPos = StringUtil.indexOf(sequence, '>', closeBracketPos);
|
|
||||||
if (closeBracketPos < 0) {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
openBracketPos = closeBracketPos - 1;
|
|
||||||
while (openBracketPos >= 0) {
|
|
||||||
openBracketPos = StringUtil.lastIndexOf(sequence, '<', 0, openBracketPos);
|
|
||||||
if (openBracketPos >= 0 &&
|
|
||||||
openBracketPos + 1 < sequence.length() &&
|
|
||||||
sequence.charAt(openBracketPos + 1) == '/') {
|
|
||||||
final String tagName = String.valueOf(sequence.subSequence(openBracketPos + "</".length(), closeBracketPos));
|
|
||||||
if (tagName.length() > 0 && tagName.charAt(0) != ' ') {
|
|
||||||
TextRange textRange = new TextRange(openBracketPos, closeBracketPos);
|
|
||||||
return Pair.create(textRange, tagName);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
openBracketPos--;
|
|
||||||
}
|
|
||||||
closeBracketPos++;
|
|
||||||
}
|
|
||||||
return null;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@ -190,7 +190,7 @@ object VimListenerManager {
|
|||||||
VimPlugin.getMotion()
|
VimPlugin.getMotion()
|
||||||
val editor = event.editor
|
val editor = event.editor
|
||||||
if (ExEntryPanel.getInstance().isActive) {
|
if (ExEntryPanel.getInstance().isActive) {
|
||||||
ExEntryPanel.getInstance().deactivate(false)
|
VimPlugin.getProcess().cancelExEntry(editor, ExEntryPanel.getInstance().entry.context)
|
||||||
}
|
}
|
||||||
|
|
||||||
ExOutputModel.getInstance(editor).clear()
|
ExOutputModel.getInstance(editor).clear()
|
||||||
@ -226,7 +226,7 @@ object VimListenerManager {
|
|||||||
event.mouseEvent.button != MouseEvent.BUTTON3) {
|
event.mouseEvent.button != MouseEvent.BUTTON3) {
|
||||||
VimPlugin.getMotion()
|
VimPlugin.getMotion()
|
||||||
if (ExEntryPanel.getInstance().isActive) {
|
if (ExEntryPanel.getInstance().isActive) {
|
||||||
ExEntryPanel.getInstance().deactivate(false)
|
VimPlugin.getProcess().cancelExEntry(event.editor, ExEntryPanel.getInstance().entry.context)
|
||||||
}
|
}
|
||||||
|
|
||||||
ExOutputModel.getInstance(event.editor).clear()
|
ExOutputModel.getInstance(event.editor).clear()
|
||||||
|
@ -307,6 +307,10 @@
|
|||||||
* |CTRL-W_<Up>| {@link com.maddyhome.idea.vim.action.window.WindowUpAction}
|
* |CTRL-W_<Up>| {@link com.maddyhome.idea.vim.action.window.WindowUpAction}
|
||||||
* |CTRL-W_<Left>| {@link com.maddyhome.idea.vim.action.window.WindowLeftAction}
|
* |CTRL-W_<Left>| {@link com.maddyhome.idea.vim.action.window.WindowLeftAction}
|
||||||
* |CTRL-W_<Right>| {@link com.maddyhome.idea.vim.action.window.WindowRightAction}
|
* |CTRL-W_<Right>| {@link com.maddyhome.idea.vim.action.window.WindowRightAction}
|
||||||
|
* |CTRL-W_CTRL-H| {@link com.maddyhome.idea.vim.action.window.WindowLeftAction}
|
||||||
|
* |CTRL-W_CTRL-J| {@link com.maddyhome.idea.vim.action.window.WindowDownAction}
|
||||||
|
* |CTRL-W_CTRL-K| {@link com.maddyhome.idea.vim.action.window.WindowUpAction}
|
||||||
|
* |CTRL-W_CTRL-L| {@link com.maddyhome.idea.vim.action.window.WindowRightAction}
|
||||||
*
|
*
|
||||||
*
|
*
|
||||||
* 2.3. Square bracket commands
|
* 2.3. Square bracket commands
|
||||||
@ -594,7 +598,72 @@
|
|||||||
*
|
*
|
||||||
* 5. Command line editing
|
* 5. Command line editing
|
||||||
*
|
*
|
||||||
* There is no up-to-date list of supported command line editing commands.
|
* tag action
|
||||||
|
* -------------------------------------------------------------------------------------------------------------------
|
||||||
|
*
|
||||||
|
* |c_CTRL-A| TODO
|
||||||
|
* |c_CTRL-B| {@link javax.swing.text.DefaultEditorKit#beginLineAction}
|
||||||
|
* |c_CTRL-C| {@link com.maddyhome.idea.vim.ui.ExEditorKit.CancelEntryAction}
|
||||||
|
* |c_CTRL-D| TODO
|
||||||
|
* |c_CTRL-E| {@link javax.swing.text.DefaultEditorKit#endLineAction}
|
||||||
|
* |c_CTRL-G| TODO
|
||||||
|
* |c_CTRL-H| {@link com.maddyhome.idea.vim.ui.ExEditorKit.DeletePreviousCharAction}
|
||||||
|
* |c_CTRL-I| TODO
|
||||||
|
* |c_CTRL-J| {@link com.maddyhome.idea.vim.ui.ExEditorKit.CompleteEntryAction}
|
||||||
|
* |c_CTRL-K| {@link com.maddyhome.idea.vim.ui.ExEditorKit.StartDigraphAction}
|
||||||
|
* |c_CTRL-L| TODO
|
||||||
|
* |c_CTRL-M| {@link com.maddyhome.idea.vim.ui.ExEditorKit.CompleteEntryAction}
|
||||||
|
* |c_CTRL-N| TODO
|
||||||
|
* |c_CTRL-P| TODO
|
||||||
|
* |c_CTRL-Q| {@link com.maddyhome.idea.vim.ui.ExEditorKit.StartDigraphAction}
|
||||||
|
* |c_CTRL-R| {@link com.maddyhome.idea.vim.ui.ExEditorKit.InsertRegisterAction}
|
||||||
|
* |c_CTRL-R_CTRL-A| TODO
|
||||||
|
* |c_CTRL-R_CTRL-F| TODO
|
||||||
|
* |c_CTRL-R_CTRL-L| TODO
|
||||||
|
* |c_CTRL-R_CTRL-O| TODO
|
||||||
|
* |c_CTRL-R_CTRL-P| TODO
|
||||||
|
* |c_CTRL-R_CTRL-R| TODO
|
||||||
|
* |c_CTRL-R_CTRL-W| TODO
|
||||||
|
* |c_CTRL-T| TODO
|
||||||
|
* |c_CTRL-U| {@link com.maddyhome.idea.vim.ui.ExEditorKit.DeleteToCursorAction}
|
||||||
|
* |c_CTRL-V| {@link com.maddyhome.idea.vim.ui.ExEditorKit.StartDigraphAction}
|
||||||
|
* |c_CTRL-W| {@link com.maddyhome.idea.vim.ui.ExEditorKit.DeletePreviousWordAction}
|
||||||
|
* |c_CTRL-Y| TODO
|
||||||
|
* |c_CTRL-\_e| TODO
|
||||||
|
* |c_CTRL-\_CTRL-G| TODO
|
||||||
|
* |c_CTRL-\_CTRL-N| TODO
|
||||||
|
* |c_CTRL-_| not applicable
|
||||||
|
* |c_CTRL-^| not applicable
|
||||||
|
* |c_CTRL-]| TODO
|
||||||
|
* |c_CTRL-[| {@link com.maddyhome.idea.vim.ui.ExEditorKit.EscapeCharAction}
|
||||||
|
* |c_<BS>| {@link com.maddyhome.idea.vim.ui.ExEditorKit.DeletePreviousCharAction}
|
||||||
|
* |c_<CR>| {@link com.maddyhome.idea.vim.ui.ExEditorKit.CompleteEntryAction}
|
||||||
|
* |c_<C-Left>| {@link javax.swing.text.DefaultEditorKit#previousWordAction}
|
||||||
|
* |c_<C-Right>| {@link javax.swing.text.DefaultEditorKit#nextWordAction}
|
||||||
|
* |c_<Del>| {@link javax.swing.text.DefaultEditorKit#deleteNextCharAction}
|
||||||
|
* |c_<Down>| {@link com.maddyhome.idea.vim.ui.ExEditorKit.HistoryDownFilterAction}
|
||||||
|
* |c_<End>| {@link javax.swing.text.DefaultEditorKit#endLineAction}
|
||||||
|
* |c_<Esc>| {@link com.maddyhome.idea.vim.ui.ExEditorKit.EscapeCharAction}
|
||||||
|
* |c_<Home>| {@link javax.swing.text.DefaultEditorKit#beginLineAction}
|
||||||
|
* |c_<Insert>| {@link com.maddyhome.idea.vim.ui.ExEditorKit.ToggleInsertReplaceAction}
|
||||||
|
* |c_<Left>| {@link javax.swing.text.DefaultEditorKit#backwardAction}
|
||||||
|
* |c_<LeftMouse>| not applicable
|
||||||
|
* |c_<MiddleMouse>| TODO
|
||||||
|
* |c_<NL>| {@link com.maddyhome.idea.vim.ui.ExEditorKit.CompleteEntryAction}
|
||||||
|
* |c_<PageUp>| {@link com.maddyhome.idea.vim.ui.ExEditorKit.HistoryUpAction}
|
||||||
|
* |c_<PageDown>| {@link com.maddyhome.idea.vim.ui.ExEditorKit.HistoryDownAction}
|
||||||
|
* |c_<Right>| {@link javax.swing.text.DefaultEditorKit#forwardAction}
|
||||||
|
* |c_<S-Down>| {@link com.maddyhome.idea.vim.ui.ExEditorKit.HistoryDownAction}
|
||||||
|
* |c_<S-Left>| {@link javax.swing.text.DefaultEditorKit#previousWordAction}
|
||||||
|
* |c_<S-Right>| {@link javax.swing.text.DefaultEditorKit#nextWordAction}
|
||||||
|
* |c_<S-Tab>| TODO
|
||||||
|
* |c_<S-Up>| {@link com.maddyhome.idea.vim.ui.ExEditorKit.HistoryUpAction}
|
||||||
|
* |c_<Tab>| TODO
|
||||||
|
* |c_<Up>| {@link com.maddyhome.idea.vim.ui.ExEditorKit.HistoryUpFilterAction}
|
||||||
|
* |c_digraph| {char1} <BS> {char2} {@link com.maddyhome.idea.vim.ui.ExEditorKit.StartDigraphAction}
|
||||||
|
* |c_wildchar| TODO
|
||||||
|
* |'cedit'| TODO
|
||||||
|
*
|
||||||
*
|
*
|
||||||
* 6. Ex commands
|
* 6. Ex commands
|
||||||
*
|
*
|
||||||
@ -621,6 +690,9 @@
|
|||||||
* |:quitall| {@link com.maddyhome.idea.vim.ex.handler.ExitHandler}
|
* |:quitall| {@link com.maddyhome.idea.vim.ex.handler.ExitHandler}
|
||||||
* |:wqall| {@link com.maddyhome.idea.vim.ex.handler.ExitHandler}
|
* |:wqall| {@link com.maddyhome.idea.vim.ex.handler.ExitHandler}
|
||||||
* |:xall| {@link com.maddyhome.idea.vim.ex.handler.ExitHandler}
|
* |:xall| {@link com.maddyhome.idea.vim.ex.handler.ExitHandler}
|
||||||
|
* |:command| {@link com.maddyhome.idea.vim.ex.handler.CmdHandler} [To Be Released]
|
||||||
|
* |:delcommand| {@link com.maddyhome.idea.vim.ex.handler.DelCmdHandler} [To Be Released]
|
||||||
|
* |:comclear| {@link com.maddyhome.idea.vim.ex.handler.CmdClearHandler} [To Be Released]
|
||||||
* ...
|
* ...
|
||||||
*
|
*
|
||||||
* The list of supported Ex commands is incomplete.
|
* The list of supported Ex commands is incomplete.
|
||||||
|
@ -20,10 +20,7 @@ package com.maddyhome.idea.vim.ui;
|
|||||||
|
|
||||||
import org.jetbrains.annotations.NotNull;
|
import org.jetbrains.annotations.NotNull;
|
||||||
|
|
||||||
import javax.swing.text.AttributeSet;
|
import javax.swing.text.*;
|
||||||
import javax.swing.text.BadLocationException;
|
|
||||||
import javax.swing.text.Document;
|
|
||||||
import javax.swing.text.PlainDocument;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* This document provides insert/overwrite mode
|
* This document provides insert/overwrite mode
|
||||||
@ -32,7 +29,7 @@ public class ExDocument extends PlainDocument {
|
|||||||
/**
|
/**
|
||||||
* Toggles the insert/overwrite state
|
* Toggles the insert/overwrite state
|
||||||
*/
|
*/
|
||||||
public void toggleInsertReplace() {
|
void toggleInsertReplace() {
|
||||||
overwrite = !overwrite;
|
overwrite = !overwrite;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -72,5 +69,19 @@ public class ExDocument extends PlainDocument {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
protected boolean overwrite = false;
|
public char getCharacter(int offset) {
|
||||||
|
// If we're a proportional font, 'o' is a good char to use. If we're fixed width, it's still a good char to use
|
||||||
|
if (offset >= getLength())
|
||||||
|
return 'o';
|
||||||
|
|
||||||
|
try {
|
||||||
|
final Segment segment = new Segment();
|
||||||
|
getContent().getChars(offset,1, segment);
|
||||||
|
return segment.charAt(0);
|
||||||
|
} catch (BadLocationException e) {
|
||||||
|
return 'o';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private boolean overwrite = false;
|
||||||
}
|
}
|
||||||
|
@ -81,13 +81,13 @@ public class ExEditorKit extends DefaultEditorKit {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Nullable
|
@Nullable
|
||||||
public static KeyStroke convert(@NotNull ActionEvent event) {
|
private static KeyStroke convert(@NotNull ActionEvent event) {
|
||||||
String cmd = event.getActionCommand();
|
String cmd = event.getActionCommand();
|
||||||
int mods = event.getModifiers();
|
int mods = event.getModifiers();
|
||||||
if (cmd != null && cmd.length() > 0) {
|
if (cmd != null && cmd.length() > 0) {
|
||||||
char ch = cmd.charAt(0);
|
char ch = cmd.charAt(0);
|
||||||
if (ch < ' ') {
|
if (ch < ' ') {
|
||||||
if (mods == KeyEvent.CTRL_MASK) {
|
if ((mods & KeyEvent.CTRL_MASK) != 0) {
|
||||||
return KeyStroke.getKeyStroke(KeyEvent.VK_A + ch - 1, mods);
|
return KeyStroke.getKeyStroke(KeyEvent.VK_A + ch - 1, mods);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -99,25 +99,22 @@ public class ExEditorKit extends DefaultEditorKit {
|
|||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
public static final String DefaultExKey = "default-ex-key";
|
static final String CancelEntry = "cancel-entry";
|
||||||
public static final String CancelEntry = "cancel-entry";
|
static final String CompleteEntry = "complete-entry";
|
||||||
public static final String CompleteEntry = "complete-entry";
|
static final String EscapeChar = "escape";
|
||||||
public static final String EscapeChar = "escape";
|
static final String DeletePreviousChar = "delete-prev-char";
|
||||||
public static final String DeletePreviousChar = "delete-prev-char";
|
static final String DeletePreviousWord = "delete-prev-word";
|
||||||
public static final String DeletePreviousWord = "delete-prev-word";
|
static final String DeleteToCursor = "delete-to-cursor";
|
||||||
public static final String DeleteToCursor = "delete-to-cursor";
|
static final String DeleteFromCursor = "delete-from-cursor";
|
||||||
public static final String DeleteFromCursor = "delete-from-cursor";
|
static final String ToggleInsertReplace = "toggle-insert";
|
||||||
public static final String ToggleInsertReplace = "toggle-insert";
|
static final String InsertRegister = "insert-register";
|
||||||
public static final String InsertRegister = "insert-register";
|
static final String HistoryUp = "history-up";
|
||||||
public static final String InsertWord = "insert-word";
|
static final String HistoryDown = "history-down";
|
||||||
public static final String InsertWORD = "insert-WORD";
|
static final String HistoryUpFilter = "history-up-filter";
|
||||||
public static final String HistoryUp = "history-up";
|
static final String HistoryDownFilter = "history-down-filter";
|
||||||
public static final String HistoryDown = "history-down";
|
static final String StartDigraph = "start-digraph";
|
||||||
public static final String HistoryUpFilter = "history-up-filter";
|
|
||||||
public static final String HistoryDownFilter = "history-down-filter";
|
|
||||||
public static final String StartDigraph = "start-digraph";
|
|
||||||
|
|
||||||
@NotNull protected final Action[] exActions = new Action[]{
|
@NotNull private final Action[] exActions = new Action[]{
|
||||||
new ExEditorKit.CancelEntryAction(),
|
new ExEditorKit.CancelEntryAction(),
|
||||||
new ExEditorKit.CompleteEntryAction(),
|
new ExEditorKit.CompleteEntryAction(),
|
||||||
new ExEditorKit.EscapeCharAction(),
|
new ExEditorKit.EscapeCharAction(),
|
||||||
@ -160,8 +157,12 @@ public class ExEditorKit extends DefaultEditorKit {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public interface MultiStepAction extends Action {
|
||||||
|
void reset();
|
||||||
|
}
|
||||||
|
|
||||||
public static class HistoryUpAction extends TextAction {
|
public static class HistoryUpAction extends TextAction {
|
||||||
public HistoryUpAction() {
|
HistoryUpAction() {
|
||||||
super(HistoryUp);
|
super(HistoryUp);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -172,7 +173,7 @@ public class ExEditorKit extends DefaultEditorKit {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public static class HistoryDownAction extends TextAction {
|
public static class HistoryDownAction extends TextAction {
|
||||||
public HistoryDownAction() {
|
HistoryDownAction() {
|
||||||
super(HistoryDown);
|
super(HistoryDown);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -183,7 +184,7 @@ public class ExEditorKit extends DefaultEditorKit {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public static class HistoryUpFilterAction extends TextAction {
|
public static class HistoryUpFilterAction extends TextAction {
|
||||||
public HistoryUpFilterAction() {
|
HistoryUpFilterAction() {
|
||||||
super(HistoryUpFilter);
|
super(HistoryUpFilter);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -194,7 +195,7 @@ public class ExEditorKit extends DefaultEditorKit {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public static class HistoryDownFilterAction extends TextAction {
|
public static class HistoryDownFilterAction extends TextAction {
|
||||||
public HistoryDownFilterAction() {
|
HistoryDownFilterAction() {
|
||||||
super(HistoryDownFilter);
|
super(HistoryDownFilter);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -204,15 +205,15 @@ public class ExEditorKit extends DefaultEditorKit {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public static class InsertRegisterAction extends TextAction {
|
public static class InsertRegisterAction extends TextAction implements MultiStepAction {
|
||||||
private static enum State {
|
private enum State {
|
||||||
SKIP_CTRL_R,
|
SKIP_CTRL_R,
|
||||||
WAIT_REGISTER,
|
WAIT_REGISTER,
|
||||||
}
|
}
|
||||||
|
|
||||||
@NotNull private State state = State.SKIP_CTRL_R;
|
@NotNull private State state = State.SKIP_CTRL_R;
|
||||||
|
|
||||||
public InsertRegisterAction() {
|
InsertRegisterAction() {
|
||||||
super(InsertRegister);
|
super(InsertRegister);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -223,11 +224,12 @@ public class ExEditorKit extends DefaultEditorKit {
|
|||||||
switch (state) {
|
switch (state) {
|
||||||
case SKIP_CTRL_R:
|
case SKIP_CTRL_R:
|
||||||
state = State.WAIT_REGISTER;
|
state = State.WAIT_REGISTER;
|
||||||
target.setCurrentAction(this);
|
target.setCurrentAction(this, '\"');
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case WAIT_REGISTER:
|
case WAIT_REGISTER:
|
||||||
state = State.SKIP_CTRL_R;
|
state = State.SKIP_CTRL_R;
|
||||||
target.setCurrentAction(null);
|
target.clearCurrentAction();
|
||||||
final char c = key.getKeyChar();
|
final char c = key.getKeyChar();
|
||||||
if (c != KeyEvent.CHAR_UNDEFINED) {
|
if (c != KeyEvent.CHAR_UNDEFINED) {
|
||||||
final Register register = VimPlugin.getRegister().getRegister(c);
|
final Register register = VimPlugin.getRegister().getRegister(c);
|
||||||
@ -235,20 +237,27 @@ public class ExEditorKit extends DefaultEditorKit {
|
|||||||
final String oldText = target.getText();
|
final String oldText = target.getText();
|
||||||
final String text = register.getText();
|
final String text = register.getText();
|
||||||
if (oldText != null && text != null) {
|
if (oldText != null && text != null) {
|
||||||
target.setText(oldText + text);
|
final int offset = target.getCaretPosition();
|
||||||
|
target.setText(oldText.substring(0, offset) + text + oldText.substring(offset));
|
||||||
|
target.setCaretPosition(offset + text.length());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
} else if ((key.getModifiers() & KeyEvent.CTRL_MASK) != 0 && key.getKeyCode() == KeyEvent.VK_C) {
|
||||||
else {
|
// Eat any unused keys, unless it's <C-C>, in which case forward on and cancel entry
|
||||||
target.handleKey(key);
|
target.handleKey(key);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void reset() {
|
||||||
|
state = State.SKIP_CTRL_R;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public static class CompleteEntryAction extends TextAction {
|
public static class CompleteEntryAction extends TextAction {
|
||||||
public CompleteEntryAction() {
|
CompleteEntryAction() {
|
||||||
super(CompleteEntry);
|
super(CompleteEntry);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -256,27 +265,29 @@ public class ExEditorKit extends DefaultEditorKit {
|
|||||||
logger.debug("complete entry");
|
logger.debug("complete entry");
|
||||||
KeyStroke stroke = KeyStroke.getKeyStroke(KeyEvent.VK_ENTER, 0);
|
KeyStroke stroke = KeyStroke.getKeyStroke(KeyEvent.VK_ENTER, 0);
|
||||||
|
|
||||||
KeyHandler.getInstance().handleKey(
|
// We send the <Enter> keystroke through the key handler rather than calling ProcessGroup#processExEntry directly.
|
||||||
ExEntryPanel.getInstance().getEntry().getEditor(),
|
// We do this for a couple of reasons:
|
||||||
stroke,
|
// * The C mode mapping for ProcessExEntryAction handles the actual entry, and most importantly, it does so as a
|
||||||
ExEntryPanel.getInstance().getEntry().getContext());
|
// write action
|
||||||
|
// * The key handler routines get the chance to clean up and reset state
|
||||||
|
final ExTextField entry = ExEntryPanel.getInstance().getEntry();
|
||||||
|
KeyHandler.getInstance().handleKey(entry.getEditor(), stroke, entry.getContext());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public static class CancelEntryAction extends TextAction {
|
public static class CancelEntryAction extends TextAction {
|
||||||
public CancelEntryAction() {
|
CancelEntryAction() {
|
||||||
super(CancelEntry);
|
super(CancelEntry);
|
||||||
}
|
}
|
||||||
|
|
||||||
public void actionPerformed(ActionEvent e) {
|
public void actionPerformed(ActionEvent e) {
|
||||||
VimPlugin.getProcess().cancelExEntry(
|
ExTextField target = (ExTextField)getTextComponent(e);
|
||||||
ExEntryPanel.getInstance().getEntry().getEditor(),
|
target.cancel();
|
||||||
ExEntryPanel.getInstance().getEntry().getContext());
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public static class EscapeCharAction extends TextAction {
|
public static class EscapeCharAction extends TextAction {
|
||||||
public EscapeCharAction() {
|
EscapeCharAction() {
|
||||||
super(EscapeChar);
|
super(EscapeChar);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -287,7 +298,7 @@ public class ExEditorKit extends DefaultEditorKit {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public static class DeletePreviousCharAction extends TextAction {
|
public static class DeletePreviousCharAction extends TextAction {
|
||||||
public DeletePreviousCharAction() {
|
DeletePreviousCharAction() {
|
||||||
super(DeletePreviousChar);
|
super(DeletePreviousChar);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -323,9 +334,7 @@ public class ExEditorKit extends DefaultEditorKit {
|
|||||||
doc.remove(dot - delChars, delChars);
|
doc.remove(dot - delChars, delChars);
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
VimPlugin.getProcess().cancelExEntry(
|
target.cancel();
|
||||||
ExEntryPanel.getInstance().getEntry().getEditor(),
|
|
||||||
ExEntryPanel.getInstance().getEntry().getContext());
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
catch (BadLocationException bl) {
|
catch (BadLocationException bl) {
|
||||||
@ -335,7 +344,7 @@ public class ExEditorKit extends DefaultEditorKit {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public static class DeletePreviousWordAction extends TextAction {
|
public static class DeletePreviousWordAction extends TextAction {
|
||||||
public DeletePreviousWordAction() {
|
DeletePreviousWordAction() {
|
||||||
super(DeletePreviousWord);
|
super(DeletePreviousWord);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -362,7 +371,7 @@ public class ExEditorKit extends DefaultEditorKit {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public static class DeleteToCursorAction extends TextAction {
|
public static class DeleteToCursorAction extends TextAction {
|
||||||
public DeleteToCursorAction() {
|
DeleteToCursorAction() {
|
||||||
super(DeleteToCursor);
|
super(DeleteToCursor);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -385,7 +394,7 @@ public class ExEditorKit extends DefaultEditorKit {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public static class DeleteFromCursorAction extends TextAction {
|
public static class DeleteFromCursorAction extends TextAction {
|
||||||
public DeleteFromCursorAction() {
|
DeleteFromCursorAction() {
|
||||||
super(DeleteFromCursor);
|
super(DeleteFromCursor);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -408,7 +417,7 @@ public class ExEditorKit extends DefaultEditorKit {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public static class ToggleInsertReplaceAction extends TextAction {
|
public static class ToggleInsertReplaceAction extends TextAction {
|
||||||
public ToggleInsertReplaceAction() {
|
ToggleInsertReplaceAction() {
|
||||||
super(ToggleInsertReplace);
|
super(ToggleInsertReplace);
|
||||||
|
|
||||||
logger.debug("ToggleInsertReplaceAction()");
|
logger.debug("ToggleInsertReplaceAction()");
|
||||||
@ -424,10 +433,10 @@ public class ExEditorKit extends DefaultEditorKit {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public static class StartDigraphAction extends TextAction {
|
public static class StartDigraphAction extends TextAction implements MultiStepAction {
|
||||||
@Nullable private DigraphSequence digraphSequence;
|
@Nullable private DigraphSequence digraphSequence;
|
||||||
|
|
||||||
public StartDigraphAction() {
|
StartDigraphAction() {
|
||||||
super(StartDigraph);
|
super(StartDigraph);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -437,14 +446,23 @@ public class ExEditorKit extends DefaultEditorKit {
|
|||||||
if (key != null && digraphSequence != null) {
|
if (key != null && digraphSequence != null) {
|
||||||
DigraphSequence.DigraphResult res = digraphSequence.processKey(key, target.getEditor());
|
DigraphSequence.DigraphResult res = digraphSequence.processKey(key, target.getEditor());
|
||||||
switch (res.getResult()) {
|
switch (res.getResult()) {
|
||||||
case DigraphSequence.DigraphResult.RES_BAD:
|
case DigraphSequence.DigraphResult.RES_OK:
|
||||||
target.setCurrentAction(null);
|
target.setCurrentActionPromptCharacter(res.getPromptCharacter());
|
||||||
target.handleKey(key);
|
|
||||||
break;
|
break;
|
||||||
|
|
||||||
|
case DigraphSequence.DigraphResult.RES_BAD:
|
||||||
|
target.clearCurrentAction();
|
||||||
|
// Eat the character, unless it's <C-C>, in which case, forward on and cancel entry. Note that at some point
|
||||||
|
// we should support input of control characters
|
||||||
|
if ((key.getModifiers() & KeyEvent.CTRL_MASK) != 0 && key.getKeyCode() == KeyEvent.VK_C) {
|
||||||
|
target.handleKey(key);
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
|
||||||
case DigraphSequence.DigraphResult.RES_DONE:
|
case DigraphSequence.DigraphResult.RES_DONE:
|
||||||
final KeyStroke digraph = res.getStroke();
|
final KeyStroke digraph = res.getStroke();
|
||||||
digraphSequence = null;
|
digraphSequence = null;
|
||||||
target.setCurrentAction(null);
|
target.clearCurrentAction();
|
||||||
if (digraph != null) {
|
if (digraph != null) {
|
||||||
target.handleKey(digraph);
|
target.handleKey(digraph);
|
||||||
}
|
}
|
||||||
@ -452,11 +470,16 @@ public class ExEditorKit extends DefaultEditorKit {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
else if (key != null && DigraphSequence.isDigraphStart(key)) {
|
else if (key != null && DigraphSequence.isDigraphStart(key)) {
|
||||||
target.setCurrentAction(this);
|
|
||||||
digraphSequence = new DigraphSequence();
|
digraphSequence = new DigraphSequence();
|
||||||
digraphSequence.processKey(key, target.getEditor());
|
DigraphSequence.DigraphResult res = digraphSequence.processKey(key, target.getEditor());
|
||||||
|
target.setCurrentAction(this, res.getPromptCharacter());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void reset() {
|
||||||
|
digraphSequence = null;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private static ExEditorKit instance;
|
private static ExEditorKit instance;
|
||||||
|
@ -79,6 +79,8 @@ public class ExEntryPanel extends JPanel implements LafManagerListener {
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
new ExShortcutKeyAction(this).registerCustomShortcutSet();
|
||||||
|
|
||||||
LafManager.getInstance().addLafManagerListener(this);
|
LafManager.getInstance().addLafManagerListener(this);
|
||||||
|
|
||||||
updateUI();
|
updateUI();
|
||||||
@ -93,6 +95,7 @@ public class ExEntryPanel extends JPanel implements LafManagerListener {
|
|||||||
private void setFontForElements() {
|
private void setFontForElements() {
|
||||||
final Font font = UiHelper.getEditorFont();
|
final Font font = UiHelper.getEditorFont();
|
||||||
label.setFont(font);
|
label.setFont(font);
|
||||||
|
entry.setFont(font);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -105,11 +108,11 @@ public class ExEntryPanel extends JPanel implements LafManagerListener {
|
|||||||
* @param count A holder for the ex entry count
|
* @param count A holder for the ex entry count
|
||||||
*/
|
*/
|
||||||
public void activate(@NotNull Editor editor, DataContext context, @NotNull String label, String initText, int count) {
|
public void activate(@NotNull Editor editor, DataContext context, @NotNull String label, String initText, int count) {
|
||||||
entry.setEditor(editor, context);
|
|
||||||
this.label.setText(label);
|
this.label.setText(label);
|
||||||
this.count = count;
|
this.count = count;
|
||||||
setFontForElements();
|
setFontForElements();
|
||||||
entry.setDocument(entry.createDefaultModel());
|
entry.reset();
|
||||||
|
entry.setEditor(editor, context);
|
||||||
entry.setText(initText);
|
entry.setText(initText);
|
||||||
entry.setType(label);
|
entry.setType(label);
|
||||||
parent = editor.getContentComponent();
|
parent = editor.getContentComponent();
|
||||||
@ -139,7 +142,7 @@ public class ExEntryPanel extends JPanel implements LafManagerListener {
|
|||||||
public void updateUI() {
|
public void updateUI() {
|
||||||
super.updateUI();
|
super.updateUI();
|
||||||
|
|
||||||
setBorder(BorderFactory.createEtchedBorder());
|
setBorder(new ExPanelBorder());
|
||||||
|
|
||||||
// Can be null when called from base constructor
|
// Can be null when called from base constructor
|
||||||
//noinspection ConstantConditions
|
//noinspection ConstantConditions
|
||||||
@ -229,6 +232,8 @@ public class ExEntryPanel extends JPanel implements LafManagerListener {
|
|||||||
logger.info("deactivate");
|
logger.info("deactivate");
|
||||||
if (!active) return;
|
if (!active) return;
|
||||||
active = false;
|
active = false;
|
||||||
|
entry.deactivate();
|
||||||
|
|
||||||
if (!ApplicationManager.getApplication().isUnitTestMode()) {
|
if (!ApplicationManager.getApplication().isUnitTestMode()) {
|
||||||
if (refocusOwningEditor && parent != null) {
|
if (refocusOwningEditor && parent != null) {
|
||||||
UiHelper.requestFocus(parent);
|
UiHelper.requestFocus(parent);
|
||||||
@ -279,7 +284,7 @@ public class ExEntryPanel extends JPanel implements LafManagerListener {
|
|||||||
|
|
||||||
@NotNull private final DocumentListener documentListener = new DocumentAdapter() {
|
@NotNull private final DocumentListener documentListener = new DocumentAdapter() {
|
||||||
@Override
|
@Override
|
||||||
protected void textChanged(DocumentEvent e) {
|
protected void textChanged(@NotNull DocumentEvent e) {
|
||||||
final Editor editor = entry.getEditor();
|
final Editor editor = entry.getEditor();
|
||||||
final boolean forwards = !label.getText().equals("?");
|
final boolean forwards = !label.getText().equals("?");
|
||||||
if (incHighlighter != null) {
|
if (incHighlighter != null) {
|
||||||
@ -300,6 +305,5 @@ public class ExEntryPanel extends JPanel implements LafManagerListener {
|
|||||||
private boolean active;
|
private boolean active;
|
||||||
|
|
||||||
private static ExEntryPanel instance;
|
private static ExEntryPanel instance;
|
||||||
|
|
||||||
private static final Logger logger = Logger.getInstance(ExEntryPanel.class.getName());
|
private static final Logger logger = Logger.getInstance(ExEntryPanel.class.getName());
|
||||||
}
|
}
|
||||||
|
@ -24,20 +24,20 @@ import javax.swing.*;
|
|||||||
import javax.swing.text.JTextComponent.KeyBinding;
|
import javax.swing.text.JTextComponent.KeyBinding;
|
||||||
import java.awt.event.KeyEvent;
|
import java.awt.event.KeyEvent;
|
||||||
|
|
||||||
/**
|
|
||||||
*
|
|
||||||
*/
|
|
||||||
public class ExKeyBindings {
|
public class ExKeyBindings {
|
||||||
@NotNull
|
@NotNull
|
||||||
public static KeyBinding[] getBindings() {
|
static KeyBinding[] getBindings() {
|
||||||
return bindings;
|
return bindings;
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO - add the following keys:
|
// TODO - add the following keys:
|
||||||
// Ctrl-\ Ctrl-N - abort
|
// Ctrl-\ Ctrl-N - abort
|
||||||
static final KeyBinding[] bindings = new KeyBinding[]{
|
static final KeyBinding[] bindings = new KeyBinding[]{
|
||||||
|
// Note that escape will cancel a pending insert digraph/register before cancelling
|
||||||
new KeyBinding(KeyStroke.getKeyStroke(KeyEvent.VK_ESCAPE, 0), ExEditorKit.EscapeChar),
|
new KeyBinding(KeyStroke.getKeyStroke(KeyEvent.VK_ESCAPE, 0), ExEditorKit.EscapeChar),
|
||||||
new KeyBinding(KeyStroke.getKeyStroke(KeyEvent.VK_OPEN_BRACKET, KeyEvent.CTRL_MASK), ExEditorKit.EscapeChar),
|
new KeyBinding(KeyStroke.getKeyStroke(KeyEvent.VK_OPEN_BRACKET, KeyEvent.CTRL_MASK), ExEditorKit.EscapeChar),
|
||||||
|
|
||||||
|
// Cancel immediately cancels entry
|
||||||
new KeyBinding(KeyStroke.getKeyStroke(KeyEvent.VK_C, KeyEvent.CTRL_MASK), ExEditorKit.CancelEntry),
|
new KeyBinding(KeyStroke.getKeyStroke(KeyEvent.VK_C, KeyEvent.CTRL_MASK), ExEditorKit.CancelEntry),
|
||||||
|
|
||||||
new KeyBinding(KeyStroke.getKeyStroke(KeyEvent.VK_ENTER, 0), ExEditorKit.CompleteEntry),
|
new KeyBinding(KeyStroke.getKeyStroke(KeyEvent.VK_ENTER, 0), ExEditorKit.CompleteEntry),
|
||||||
@ -82,9 +82,10 @@ public class ExKeyBindings {
|
|||||||
new KeyBinding(KeyStroke.getKeyStroke(KeyEvent.VK_V, KeyEvent.CTRL_MASK), ExEditorKit.StartDigraph),
|
new KeyBinding(KeyStroke.getKeyStroke(KeyEvent.VK_V, KeyEvent.CTRL_MASK), ExEditorKit.StartDigraph),
|
||||||
new KeyBinding(KeyStroke.getKeyStroke(KeyEvent.VK_Q, KeyEvent.CTRL_MASK), ExEditorKit.StartDigraph),
|
new KeyBinding(KeyStroke.getKeyStroke(KeyEvent.VK_Q, KeyEvent.CTRL_MASK), ExEditorKit.StartDigraph),
|
||||||
|
|
||||||
|
new KeyBinding(KeyStroke.getKeyStroke(KeyEvent.VK_R, KeyEvent.CTRL_MASK), ExEditorKit.InsertRegister),
|
||||||
|
|
||||||
|
// These appear to be non-Vim shortcuts
|
||||||
new KeyBinding(KeyStroke.getKeyStroke(KeyEvent.VK_V, KeyEvent.META_MASK), ExEditorKit.pasteAction),
|
new KeyBinding(KeyStroke.getKeyStroke(KeyEvent.VK_V, KeyEvent.META_MASK), ExEditorKit.pasteAction),
|
||||||
new KeyBinding(KeyStroke.getKeyStroke(KeyEvent.VK_INSERT, KeyEvent.SHIFT_MASK), ExEditorKit.pasteAction),
|
new KeyBinding(KeyStroke.getKeyStroke(KeyEvent.VK_INSERT, KeyEvent.SHIFT_MASK), ExEditorKit.pasteAction),
|
||||||
|
|
||||||
new KeyBinding(KeyStroke.getKeyStroke(KeyEvent.VK_R, KeyEvent.CTRL_MASK), ExEditorKit.InsertRegister),
|
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
@ -113,7 +113,7 @@ public class ExOutputPanel extends JPanel implements LafManagerListener {
|
|||||||
public void updateUI() {
|
public void updateUI() {
|
||||||
super.updateUI();
|
super.updateUI();
|
||||||
|
|
||||||
setBorder(BorderFactory.createEtchedBorder());
|
setBorder(new ExPanelBorder());
|
||||||
|
|
||||||
// Can be null when called from base constructor
|
// Can be null when called from base constructor
|
||||||
//noinspection ConstantConditions
|
//noinspection ConstantConditions
|
||||||
|
22
src/com/maddyhome/idea/vim/ui/ExPanelBorder.kt
Normal file
22
src/com/maddyhome/idea/vim/ui/ExPanelBorder.kt
Normal file
@ -0,0 +1,22 @@
|
|||||||
|
package com.maddyhome.idea.vim.ui
|
||||||
|
|
||||||
|
import com.intellij.ui.JBColor
|
||||||
|
import com.intellij.ui.SideBorder
|
||||||
|
import com.intellij.util.ui.JBInsets
|
||||||
|
|
||||||
|
import java.awt.*
|
||||||
|
|
||||||
|
class ExPanelBorder internal constructor() : SideBorder(JBColor.border(), SideBorder.TOP) {
|
||||||
|
|
||||||
|
override fun getBorderInsets(component: Component?): Insets {
|
||||||
|
return JBInsets(getThickness() + 2, 0, 2, 2)
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun getBorderInsets(component: Component?, insets: Insets): Insets {
|
||||||
|
insets.top = getThickness() + 2
|
||||||
|
insets.left = 0
|
||||||
|
insets.bottom = 2
|
||||||
|
insets.right = 2
|
||||||
|
return insets
|
||||||
|
}
|
||||||
|
}
|
50
src/com/maddyhome/idea/vim/ui/ExShortcutKeyAction.kt
Normal file
50
src/com/maddyhome/idea/vim/ui/ExShortcutKeyAction.kt
Normal file
@ -0,0 +1,50 @@
|
|||||||
|
package com.maddyhome.idea.vim.ui
|
||||||
|
|
||||||
|
import com.intellij.openapi.actionSystem.*
|
||||||
|
import com.maddyhome.idea.vim.VimPlugin
|
||||||
|
import java.awt.event.KeyEvent
|
||||||
|
import javax.swing.KeyStroke
|
||||||
|
|
||||||
|
/**
|
||||||
|
* An IntelliJ action to forward shortcuts to the ex entry component
|
||||||
|
* <p>
|
||||||
|
* Key events are processed by the IDE action system before they are dispatched to the actual component, which means
|
||||||
|
* they take precedence over the keyboard shortcuts registered with the ex component as Swing actions. This can cause
|
||||||
|
* clashes such as <C-R> invoking the Run action instead of the Paste Register ex action, or <BS> being handled by the
|
||||||
|
* editor rather than allowing us to cancel the ex entry.
|
||||||
|
* <p>
|
||||||
|
* This class is an IDE action that is registered with the ex entry component, so is available only when the ex entry
|
||||||
|
* component has focus. It registers all shortcuts used by the Swing actions and forwards them directly to the key
|
||||||
|
* handler.
|
||||||
|
*/
|
||||||
|
class ExShortcutKeyAction(private val exEntryPanel: ExEntryPanel) : AnAction() {
|
||||||
|
|
||||||
|
override fun actionPerformed(e: AnActionEvent) {
|
||||||
|
val keyStroke = getKeyStroke(e)
|
||||||
|
if (keyStroke != null) {
|
||||||
|
VimPlugin.getProcess().processExKey(exEntryPanel.entry.editor, keyStroke)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun update(e: AnActionEvent) {
|
||||||
|
e.presentation.isEnabled = exEntryPanel.isActive
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun getKeyStroke(e: AnActionEvent): KeyStroke? {
|
||||||
|
val inputEvent = e.inputEvent
|
||||||
|
if (inputEvent is KeyEvent) {
|
||||||
|
return KeyStroke.getKeyStrokeForEvent(inputEvent)
|
||||||
|
}
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
|
||||||
|
fun registerCustomShortcutSet() {
|
||||||
|
|
||||||
|
val shortcuts = ExKeyBindings.bindings.map {
|
||||||
|
KeyboardShortcut(it.key, null)
|
||||||
|
}.toTypedArray()
|
||||||
|
|
||||||
|
registerCustomShortcutSet({ shortcuts }, exEntryPanel)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -21,48 +21,62 @@ package com.maddyhome.idea.vim.ui;
|
|||||||
import com.intellij.openapi.actionSystem.DataContext;
|
import com.intellij.openapi.actionSystem.DataContext;
|
||||||
import com.intellij.openapi.diagnostic.Logger;
|
import com.intellij.openapi.diagnostic.Logger;
|
||||||
import com.intellij.openapi.editor.Editor;
|
import com.intellij.openapi.editor.Editor;
|
||||||
import com.intellij.openapi.editor.colors.EditorColorsManager;
|
|
||||||
import com.intellij.openapi.editor.colors.EditorFontType;
|
|
||||||
import com.intellij.openapi.project.Project;
|
import com.intellij.openapi.project.Project;
|
||||||
import com.intellij.openapi.util.Disposer;
|
import com.intellij.openapi.util.Disposer;
|
||||||
import com.intellij.util.ui.JBUI;
|
import com.intellij.util.ui.JBUI;
|
||||||
import com.maddyhome.idea.vim.VimPlugin;
|
import com.maddyhome.idea.vim.VimPlugin;
|
||||||
import com.maddyhome.idea.vim.group.HistoryGroup;
|
import com.maddyhome.idea.vim.group.HistoryGroup;
|
||||||
|
import kotlin.text.StringsKt;
|
||||||
import org.jetbrains.annotations.NotNull;
|
import org.jetbrains.annotations.NotNull;
|
||||||
import org.jetbrains.annotations.Nullable;
|
import org.jetbrains.annotations.Nullable;
|
||||||
|
import org.jetbrains.annotations.TestOnly;
|
||||||
|
|
||||||
import javax.swing.*;
|
import javax.swing.*;
|
||||||
import javax.swing.text.Document;
|
import javax.swing.plaf.basic.BasicTextFieldUI;
|
||||||
import javax.swing.text.Keymap;
|
import javax.swing.text.*;
|
||||||
import java.awt.*;
|
import java.awt.*;
|
||||||
import java.awt.event.FocusEvent;
|
import java.awt.event.*;
|
||||||
import java.awt.event.FocusListener;
|
|
||||||
import java.awt.event.KeyEvent;
|
|
||||||
import java.util.Date;
|
import java.util.Date;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
|
||||||
|
import static java.lang.Math.max;
|
||||||
|
import static java.lang.Math.min;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Provides a custom keymap for the text field. The keymap is the VIM Ex command keymapping
|
* Provides a custom keymap for the text field. The keymap is the VIM Ex command keymapping
|
||||||
*/
|
*/
|
||||||
public class ExTextField extends JTextField {
|
public class ExTextField extends JTextField {
|
||||||
|
|
||||||
ExTextField() {
|
ExTextField() {
|
||||||
addFocusListener(new FocusListener() {
|
CommandLineCaret caret = new CommandLineCaret();
|
||||||
@Override
|
caret.setBlinkRate(getCaret().getBlinkRate());
|
||||||
public void focusGained(FocusEvent e) {
|
setCaret(caret);
|
||||||
setCaretPosition(getText().length());
|
setNormalModeCaret();
|
||||||
}
|
|
||||||
|
|
||||||
|
addCaretListener(e -> resetCaret());
|
||||||
|
addMouseListener(new MouseAdapter() {
|
||||||
@Override
|
@Override
|
||||||
public void focusLost(FocusEvent e) {
|
public void mouseClicked(MouseEvent e) {
|
||||||
|
// If we're in the middle of an action (e.g. entering a register to paste, or inserting a digraph), cancel it if
|
||||||
|
// the mouse is clicked anywhere. Vim's behaviour is to use the mouse click as an event, which can lead to
|
||||||
|
// something like : !%!C, which I don't believe is documented, or useful
|
||||||
|
if (currentAction != null) {
|
||||||
|
clearCurrentAction();
|
||||||
|
}
|
||||||
|
super.mouseClicked(e);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
// Minimize margins and insets. These get added to the default margins in the UI class that we can't override.
|
void reset() {
|
||||||
// (I.e. DarculaTextFieldUI#getDefaultMargins, MacIntelliJTextFieldUI#getDefaultMargin, WinIntelliJTextFieldUI#getDefaultMargin)
|
clearCurrentAction();
|
||||||
// This is an attempt to mitigate the gap in ExEntryPanel between the label (':', '/', '?') and the text field.
|
setInsertMode();
|
||||||
// See VIM-1485
|
}
|
||||||
|
|
||||||
|
void deactivate() {
|
||||||
|
clearCurrentAction();
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public Insets getMargin() {
|
public Insets getMargin() {
|
||||||
return JBUI.emptyInsets();
|
return JBUI.emptyInsets();
|
||||||
@ -76,10 +90,11 @@ public class ExTextField extends JTextField {
|
|||||||
// Called when the LAF is changed, but only if the control is visible
|
// Called when the LAF is changed, but only if the control is visible
|
||||||
@Override
|
@Override
|
||||||
public void updateUI() {
|
public void updateUI() {
|
||||||
super.updateUI();
|
// Override the default look and feel specific UI so we can have a completely borderless and margin-less text field.
|
||||||
|
// (See TextFieldWithPopupHandlerUI#getDefaultMargins and derived classes). This allows us to draw the text field
|
||||||
Font font = EditorColorsManager.getInstance().getGlobalScheme().getFont(EditorFontType.PLAIN);
|
// directly next to the label
|
||||||
setFont(font);
|
setUI(new BasicTextFieldUI());
|
||||||
|
invalidate();
|
||||||
|
|
||||||
setBorder(null);
|
setBorder(null);
|
||||||
|
|
||||||
@ -98,7 +113,7 @@ public class ExTextField extends JTextField {
|
|||||||
setKeymap(map);
|
setKeymap(map);
|
||||||
}
|
}
|
||||||
|
|
||||||
public void setType(@NotNull String type) {
|
void setType(@NotNull String type) {
|
||||||
String hkey = null;
|
String hkey = null;
|
||||||
switch (type.charAt(0)) {
|
switch (type.charAt(0)) {
|
||||||
case '/':
|
case '/':
|
||||||
@ -116,11 +131,11 @@ public class ExTextField extends JTextField {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public void saveLastEntry() {
|
void saveLastEntry() {
|
||||||
lastEntry = getText();
|
lastEntry = getText();
|
||||||
}
|
}
|
||||||
|
|
||||||
public void selectHistory(boolean isUp, boolean filter) {
|
void selectHistory(boolean isUp, boolean filter) {
|
||||||
int dir = isUp ? -1 : 1;
|
int dir = isUp ? -1 : 1;
|
||||||
if (histIndex + dir < 0 || histIndex + dir > history.size()) {
|
if (histIndex + dir < 0 || histIndex + dir > history.size()) {
|
||||||
VimPlugin.indicateError();
|
VimPlugin.indicateError();
|
||||||
@ -164,7 +179,28 @@ public class ExTextField extends JTextField {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private static final String vimExTextFieldDisposeKey = "vimExTextFieldDisposeKey";
|
private void updateText(String string) {
|
||||||
|
super.setText(string);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setText(String string) {
|
||||||
|
super.setText(string);
|
||||||
|
|
||||||
|
saveLastEntry();
|
||||||
|
}
|
||||||
|
|
||||||
|
void setEditor(Editor editor, DataContext context) {
|
||||||
|
this.editor = editor;
|
||||||
|
this.context = context;
|
||||||
|
String disposeKey = vimExTextFieldDisposeKey + editor.hashCode();
|
||||||
|
Project project = editor.getProject();
|
||||||
|
if (Disposer.get(disposeKey) == null && project != null) {
|
||||||
|
Disposer.register(project, () -> {
|
||||||
|
this.editor = null;
|
||||||
|
this.context = null;
|
||||||
|
}, disposeKey);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
public Editor getEditor() {
|
public Editor getEditor() {
|
||||||
return editor;
|
return editor;
|
||||||
@ -186,21 +222,25 @@ public class ExTextField extends JTextField {
|
|||||||
c = Character.toChars(codePoint)[0];
|
c = Character.toChars(codePoint)[0];
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
KeyEvent event = new KeyEvent(this, keyChar != KeyEvent.CHAR_UNDEFINED ? KeyEvent.KEY_TYPED :
|
|
||||||
(stroke.isOnKeyRelease() ? KeyEvent.KEY_RELEASED : KeyEvent.KEY_PRESSED),
|
|
||||||
(new Date()).getTime(), modifiers, keyCode, c);
|
|
||||||
|
|
||||||
super.processKeyEvent(event);
|
// Make sure the current action sees any subsequent keystrokes, and they're not processed by Swing's action system.
|
||||||
}
|
// Note that this will only handle simple characters and any control characters that are already registered against
|
||||||
|
// ExShortcutKeyAction - any other control characters will can be "stolen" by other IDE actions.
|
||||||
|
// If we need to capture ANY subsequent keystroke (e.g. for ^V<Tab>, or to stop the Swing standard <C-A> going to
|
||||||
|
// start of line), we should replace ExShortcutAction with a dispatcher registered with IdeEventQueue#addDispatcher.
|
||||||
|
// This gets called for ALL events, before the IDE starts to process key events for the action system. We can add a
|
||||||
|
// dispatcher that checks that the plugin is enabled, checks that the component with the focus is ExTextField,
|
||||||
|
// dispatch to ExEntryPanel#handleKey and if it's processed, mark the event as consumed.
|
||||||
|
if (currentAction != null) {
|
||||||
|
currentAction.actionPerformed(new ActionEvent(this, ActionEvent.ACTION_PERFORMED, "" + c, modifiers));
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
KeyEvent event = new KeyEvent(this, keyChar != KeyEvent.CHAR_UNDEFINED ? KeyEvent.KEY_TYPED :
|
||||||
|
(stroke.isOnKeyRelease() ? KeyEvent.KEY_RELEASED : KeyEvent.KEY_PRESSED),
|
||||||
|
(new Date()).getTime(), modifiers, keyCode, c);
|
||||||
|
|
||||||
public void updateText(String string) {
|
super.processKeyEvent(event);
|
||||||
super.setText(string);
|
}
|
||||||
}
|
|
||||||
|
|
||||||
public void setText(String string) {
|
|
||||||
super.setText(string);
|
|
||||||
|
|
||||||
saveLastEntry();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
protected void processKeyEvent(KeyEvent e) {
|
protected void processKeyEvent(KeyEvent e) {
|
||||||
@ -220,182 +260,270 @@ public class ExTextField extends JTextField {
|
|||||||
return new ExDocument();
|
return new ExDocument();
|
||||||
}
|
}
|
||||||
|
|
||||||
public void escape() {
|
/**
|
||||||
|
* Cancels current action, if there is one. If not, cancels entry.
|
||||||
|
*/
|
||||||
|
void escape() {
|
||||||
if (currentAction != null) {
|
if (currentAction != null) {
|
||||||
currentAction = null;
|
clearCurrentAction();
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
VimPlugin.getProcess().cancelExEntry(editor, context);
|
cancel();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public void setCurrentAction(@Nullable Action action) {
|
/**
|
||||||
|
* Cancels entry, including any current action.
|
||||||
|
*/
|
||||||
|
void cancel() {
|
||||||
|
clearCurrentAction();
|
||||||
|
VimPlugin.getProcess().cancelExEntry(editor, context);
|
||||||
|
}
|
||||||
|
|
||||||
|
void setCurrentAction(@NotNull ExEditorKit.MultiStepAction action, char pendingIndicator) {
|
||||||
this.currentAction = action;
|
this.currentAction = action;
|
||||||
|
setCurrentActionPromptCharacter(pendingIndicator);
|
||||||
|
}
|
||||||
|
|
||||||
|
void clearCurrentAction() {
|
||||||
|
if (currentAction != null) {
|
||||||
|
currentAction.reset();
|
||||||
|
}
|
||||||
|
currentAction = null;
|
||||||
|
clearCurrentActionPromptCharacter();
|
||||||
|
}
|
||||||
|
|
||||||
|
void setCurrentActionPromptCharacter(char promptCharacter) {
|
||||||
|
final String text = removePromptCharacter();
|
||||||
|
this.currentActionPromptCharacter = promptCharacter;
|
||||||
|
currentActionPromptCharacterOffset = currentActionPromptCharacterOffset == -1 ? getCaretPosition() : currentActionPromptCharacterOffset;
|
||||||
|
StringBuilder sb = new StringBuilder(text);
|
||||||
|
sb.insert(currentActionPromptCharacterOffset, currentActionPromptCharacter);
|
||||||
|
updateText(sb.toString());
|
||||||
|
setCaretPosition(currentActionPromptCharacterOffset);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void clearCurrentActionPromptCharacter() {
|
||||||
|
final int offset = getCaretPosition();
|
||||||
|
final String text = removePromptCharacter();
|
||||||
|
updateText(text);
|
||||||
|
setCaretPosition(min(offset, text.length()));
|
||||||
|
currentActionPromptCharacter = '\0';
|
||||||
|
currentActionPromptCharacterOffset = -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
private String removePromptCharacter() {
|
||||||
|
return currentActionPromptCharacterOffset == -1
|
||||||
|
? getText()
|
||||||
|
: StringsKt.removeRange(getText(), currentActionPromptCharacterOffset, currentActionPromptCharacterOffset + 1).toString();
|
||||||
}
|
}
|
||||||
|
|
||||||
@Nullable
|
@Nullable
|
||||||
public Action getCurrentAction() {
|
Action getCurrentAction() {
|
||||||
return currentAction;
|
return currentAction;
|
||||||
}
|
}
|
||||||
|
|
||||||
public void toggleInsertReplace() {
|
private void setInsertMode() {
|
||||||
|
ExDocument doc = (ExDocument)getDocument();
|
||||||
|
if (doc.isOverwrite()) {
|
||||||
|
doc.toggleInsertReplace();
|
||||||
|
}
|
||||||
|
resetCaret();
|
||||||
|
}
|
||||||
|
|
||||||
|
void toggleInsertReplace() {
|
||||||
ExDocument doc = (ExDocument)getDocument();
|
ExDocument doc = (ExDocument)getDocument();
|
||||||
doc.toggleInsertReplace();
|
doc.toggleInsertReplace();
|
||||||
|
resetCaret();
|
||||||
/*
|
|
||||||
Caret caret;
|
|
||||||
int width;
|
|
||||||
if (doc.isOverwrite())
|
|
||||||
{
|
|
||||||
caret = blockCaret;
|
|
||||||
width = 8;
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
caret = origCaret;
|
|
||||||
width = 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
setCaret(caret);
|
|
||||||
putClientProperty("caretWidth", new Integer(width));
|
|
||||||
*/
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/*
|
private void resetCaret() {
|
||||||
private static class BlockCaret extends DefaultCaret
|
if (getCaretPosition() == getText().length() || currentActionPromptCharacterOffset == getText().length() - 1) {
|
||||||
{
|
setNormalModeCaret();
|
||||||
public void paint(Graphics g)
|
}
|
||||||
{
|
else {
|
||||||
if(!isVisible())
|
ExDocument doc = (ExDocument)getDocument();
|
||||||
return;
|
if (doc.isOverwrite()) {
|
||||||
|
setReplaceModeCaret();
|
||||||
try
|
|
||||||
{
|
|
||||||
Rectangle rectangle;
|
|
||||||
TextUI textui = getComponent().getUI();
|
|
||||||
rectangle = textui.modelToView(getComponent(), getDot(), Position.Bias.Forward);
|
|
||||||
if (rectangle == null || rectangle.width == 0 && rectangle.height == 0)
|
|
||||||
{
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
if (width > 0 && height > 0 && !_contains(rectangle.x, rectangle.y, rectangle.width, rectangle.height))
|
|
||||||
{
|
|
||||||
Rectangle rectangle1 = g.getClipBounds();
|
|
||||||
if (rectangle1 != null && !rectangle1.contains(this))
|
|
||||||
{
|
|
||||||
repaint();
|
|
||||||
}
|
|
||||||
damage(rectangle);
|
|
||||||
}
|
|
||||||
g.setColor(getComponent().getCaretColor());
|
|
||||||
int i = 8;
|
|
||||||
//rectangle.x -= i >> 1;
|
|
||||||
g.fillRect(rectangle.x, rectangle.y, i, rectangle.height - 1);
|
|
||||||
Document document = getComponent().getDocument();
|
|
||||||
if (document instanceof AbstractDocument)
|
|
||||||
{
|
|
||||||
Element element = ((AbstractDocument)document).getBidiRootElement();
|
|
||||||
if (element != null && element.getElementCount() > 1)
|
|
||||||
{
|
|
||||||
int[] flagXPoints = new int[3];
|
|
||||||
int[] flagYPoints = new int[3];
|
|
||||||
flagXPoints[0] = rectangle.x + i;
|
|
||||||
flagYPoints[0] = rectangle.y;
|
|
||||||
flagXPoints[1] = flagXPoints[0];
|
|
||||||
flagYPoints[1] = flagYPoints[0] + 4;
|
|
||||||
flagXPoints[2] = flagXPoints[0] + 4;
|
|
||||||
flagYPoints[2] = flagYPoints[0];
|
|
||||||
g.fillPolygon(flagXPoints, flagYPoints, 3);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
catch (BadLocationException badlocationexception)
|
|
||||||
{
|
|
||||||
// ignore
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
else {
|
||||||
private boolean _contains(int i, int j, int k, int l)
|
setInsertModeCaret();
|
||||||
{
|
|
||||||
int i1 = width;
|
|
||||||
int j1 = height;
|
|
||||||
if ((i1 | j1 | k | l) < 0)
|
|
||||||
{
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
int k1 = x;
|
|
||||||
int l1 = y;
|
|
||||||
if (i < k1 || j < l1)
|
|
||||||
{
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
if (k > 0)
|
|
||||||
{
|
|
||||||
i1 += k1;
|
|
||||||
k += i;
|
|
||||||
if (k <= i)
|
|
||||||
{
|
|
||||||
if (i1 >= k1 || k > i1)
|
|
||||||
{
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
else if (i1 >= k1 && k > i1)
|
|
||||||
{
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
else if (k1 + i1 < i)
|
|
||||||
{
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
if (l > 0)
|
|
||||||
{
|
|
||||||
j1 += l1;
|
|
||||||
l += j;
|
|
||||||
if (l <= j)
|
|
||||||
{
|
|
||||||
if (j1 >= l1 || l > j1)
|
|
||||||
{
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
else if (j1 >= l1 && l > j1)
|
|
||||||
{
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
else if (l1 + j1 < j)
|
|
||||||
{
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
return true;
|
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// The default cursor shapes for command line are:
|
||||||
|
// 'c' command-line normal is block
|
||||||
|
// 'ci' command-line insert is ver25
|
||||||
|
// 'cr' command-line replace is hor20
|
||||||
|
// see :help 'guicursor'
|
||||||
|
// Note that we can't easily support guicursor because we don't have arbitrary control over the IntelliJ editor caret
|
||||||
|
private void setNormalModeCaret() {
|
||||||
|
CommandLineCaret caret = (CommandLineCaret) getCaret();
|
||||||
|
caret.setBlockMode();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void setInsertModeCaret() {
|
||||||
|
CommandLineCaret caret = (CommandLineCaret) getCaret();
|
||||||
|
caret.setMode(CommandLineCaret.CaretMode.VER, 25);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void setReplaceModeCaret() {
|
||||||
|
CommandLineCaret caret = (CommandLineCaret) getCaret();
|
||||||
|
caret.setMode(CommandLineCaret.CaretMode.HOR, 20);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static class CommandLineCaret extends DefaultCaret {
|
||||||
|
|
||||||
|
private CaretMode mode;
|
||||||
|
private int blockPercentage = 100;
|
||||||
|
private int lastBlinkRate = 0;
|
||||||
|
private boolean hasFocus;
|
||||||
|
|
||||||
|
public enum CaretMode {
|
||||||
|
BLOCK,
|
||||||
|
VER,
|
||||||
|
HOR
|
||||||
|
}
|
||||||
|
|
||||||
|
void setBlockMode() {
|
||||||
|
setMode(CaretMode.BLOCK, 100);
|
||||||
|
}
|
||||||
|
|
||||||
|
void setMode(CaretMode mode, int blockPercentage) {
|
||||||
|
if (this.mode == mode && this.blockPercentage == blockPercentage) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Hide the current caret and redraw without it. Then make the new caret visible, but only if it was already
|
||||||
|
// (logically) visible/active. Always making it visible can start the flasher timer unnecessarily.
|
||||||
|
final boolean active = isActive();
|
||||||
|
if (isVisible()) {
|
||||||
|
setVisible(false);
|
||||||
|
}
|
||||||
|
this.mode = mode;
|
||||||
|
this.blockPercentage = blockPercentage;
|
||||||
|
if (active) {
|
||||||
|
setVisible(true);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void updateDamage() {
|
||||||
|
try {
|
||||||
|
Rectangle r = getComponent().getUI().modelToView(getComponent(), getDot(), getDotBias());
|
||||||
|
damage(r);
|
||||||
|
}
|
||||||
|
catch(BadLocationException e) {
|
||||||
|
// ignore
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void focusGained(FocusEvent e) {
|
||||||
|
if (lastBlinkRate != 0) {
|
||||||
|
setBlinkRate(lastBlinkRate);
|
||||||
|
lastBlinkRate = 0;
|
||||||
|
}
|
||||||
|
super.focusGained(e);
|
||||||
|
updateDamage();
|
||||||
|
hasFocus = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void focusLost(FocusEvent e) {
|
||||||
|
hasFocus = false;
|
||||||
|
lastBlinkRate = getBlinkRate();
|
||||||
|
setBlinkRate(0);
|
||||||
|
// We might be losing focus while the cursor is flashing, and is currently not visible
|
||||||
|
setVisible(true);
|
||||||
|
updateDamage();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void paint(Graphics g) {
|
||||||
|
if (!isVisible()) return;
|
||||||
|
|
||||||
|
try {
|
||||||
|
final JTextComponent component = getComponent();
|
||||||
|
final Color color = g.getColor();
|
||||||
|
|
||||||
|
g.setColor(component.getBackground());
|
||||||
|
g.setXORMode(component.getCaretColor());
|
||||||
|
|
||||||
|
// We have to use the deprecated version because we still support 1.8
|
||||||
|
final Rectangle r = component.getUI().modelToView(component, getDot(), getDotBias());
|
||||||
|
FontMetrics fm = g.getFontMetrics();
|
||||||
|
final int boundsHeight = fm.getHeight();
|
||||||
|
if (!hasFocus) {
|
||||||
|
r.setBounds(r.x, r.y, getCaretWidth(fm, 100), boundsHeight);
|
||||||
|
g.drawRect(r.x, r.y, r.width, r.height);
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
r.setBounds(r.x, r.y, getCaretWidth(fm, blockPercentage), getBlockHeight(boundsHeight));
|
||||||
|
g.fillRect(r.x, r.y + boundsHeight - r.height, r.width, r.height);
|
||||||
|
}
|
||||||
|
g.setPaintMode();
|
||||||
|
g.setColor(color);
|
||||||
|
}
|
||||||
|
catch (BadLocationException e) {
|
||||||
|
// ignore
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
protected synchronized void damage(Rectangle r) {
|
||||||
|
if (r != null) {
|
||||||
|
JTextComponent component = getComponent();
|
||||||
|
Font font = component.getFont();
|
||||||
|
FontMetrics fm = component.getFontMetrics(font);
|
||||||
|
final int blockHeight = fm.getHeight();
|
||||||
|
if (!hasFocus) {
|
||||||
|
width = this.getCaretWidth(fm, 100);
|
||||||
|
height = blockHeight;
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
width = this.getCaretWidth(fm, blockPercentage);
|
||||||
|
height = getBlockHeight(blockHeight);
|
||||||
|
}
|
||||||
|
x = r.x;
|
||||||
|
y = r.y + blockHeight - height;
|
||||||
|
repaint();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private int getCaretWidth(FontMetrics fm, int widthPercentage) {
|
||||||
|
if (mode == CaretMode.VER) {
|
||||||
|
// Don't show a proportional width of a proportional font
|
||||||
|
final int fullWidth = fm.charWidth('o');
|
||||||
|
return max(1, fullWidth * widthPercentage / 100);
|
||||||
|
}
|
||||||
|
|
||||||
|
final char c = ((ExDocument)getComponent().getDocument()).getCharacter(getComponent().getCaretPosition());
|
||||||
|
return fm.charWidth(c);
|
||||||
|
}
|
||||||
|
|
||||||
|
private int getBlockHeight(int fullHeight) {
|
||||||
|
if (mode == CaretMode.HOR) {
|
||||||
|
return max(1, fullHeight * blockPercentage / 100);
|
||||||
|
}
|
||||||
|
return fullHeight;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@TestOnly
|
||||||
|
public String getCaretShape() {
|
||||||
|
CommandLineCaret caret = (CommandLineCaret) getCaret();
|
||||||
|
return String.format("%s %d", caret.mode, caret.blockPercentage);
|
||||||
}
|
}
|
||||||
*/
|
|
||||||
|
|
||||||
private Editor editor;
|
private Editor editor;
|
||||||
private DataContext context;
|
private DataContext context;
|
||||||
private String lastEntry;
|
private String lastEntry;
|
||||||
private List<HistoryGroup.HistoryEntry> history;
|
private List<HistoryGroup.HistoryEntry> history;
|
||||||
private int histIndex = 0;
|
private int histIndex = 0;
|
||||||
@Nullable private Action currentAction;
|
@Nullable private ExEditorKit.MultiStepAction currentAction;
|
||||||
// TODO - support block cursor for overwrite mode
|
private char currentActionPromptCharacter;
|
||||||
//private Caret origCaret;
|
private int currentActionPromptCharacterOffset = -1;
|
||||||
//private Caret blockCaret;
|
|
||||||
|
|
||||||
|
private static final String vimExTextFieldDisposeKey = "vimExTextFieldDisposeKey";
|
||||||
private static final Logger logger = Logger.getInstance(ExTextField.class.getName());
|
private static final Logger logger = Logger.getInstance(ExTextField.class.getName());
|
||||||
|
|
||||||
void setEditor(Editor editor, DataContext context) {
|
|
||||||
this.editor = editor;
|
|
||||||
this.context = context;
|
|
||||||
String disposeKey = vimExTextFieldDisposeKey + editor.hashCode();
|
|
||||||
Project project = editor.getProject();
|
|
||||||
if (Disposer.get(disposeKey) == null && project != null) {
|
|
||||||
Disposer.register(project, () -> {
|
|
||||||
this.editor = null;
|
|
||||||
this.context = null;
|
|
||||||
}, disposeKey);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
@ -79,6 +79,9 @@ public abstract class VimTestCase extends UsefulTestCase {
|
|||||||
KeyHandler.getInstance().fullReset(myFixture.getEditor());
|
KeyHandler.getInstance().fullReset(myFixture.getEditor());
|
||||||
Options.getInstance().resetAllOptions();
|
Options.getInstance().resetAllOptions();
|
||||||
VimPlugin.getKey().resetKeyMappings();
|
VimPlugin.getKey().resetKeyMappings();
|
||||||
|
|
||||||
|
// Make sure the entry text field gets a bounds, or we won't be able to work out caret location
|
||||||
|
ExEntryPanel.getInstance().getEntry().setBounds(0,0, 100, 25);
|
||||||
}
|
}
|
||||||
|
|
||||||
protected String getTestDataPath() {
|
protected String getTestDataPath() {
|
||||||
|
@ -158,41 +158,6 @@ public class MotionActionTest extends VimTestCase {
|
|||||||
myFixture.checkResult(" 0:<caret>_ 1:a 2:b 3:c \n");
|
myFixture.checkResult(" 0:<caret>_ 1:a 2:b 3:c \n");
|
||||||
}
|
}
|
||||||
|
|
||||||
// VIM-326 |d| |v_ib|
|
|
||||||
public void testDeleteInnerBlock() {
|
|
||||||
typeTextInFile(parseKeys("di)"),
|
|
||||||
"foo(\"b<caret>ar\")\n");
|
|
||||||
myFixture.checkResult("foo()\n");
|
|
||||||
}
|
|
||||||
|
|
||||||
// VIM-326 |d| |v_ib|
|
|
||||||
public void testDeleteInnerBlockCaretBeforeString() {
|
|
||||||
typeTextInFile(parseKeys("di)"),
|
|
||||||
"foo(<caret>\"bar\")\n");
|
|
||||||
myFixture.checkResult("foo()\n");
|
|
||||||
}
|
|
||||||
|
|
||||||
// VIM-326 |c| |v_ib|
|
|
||||||
public void testChangeInnerBlockCaretBeforeString() {
|
|
||||||
typeTextInFile(parseKeys("ci)"),
|
|
||||||
"foo(<caret>\"bar\")\n");
|
|
||||||
myFixture.checkResult("foo()\n");
|
|
||||||
}
|
|
||||||
|
|
||||||
// VIM-392 |c| |v_ib|
|
|
||||||
public void testChangeInnerBlockCaretBeforeBlock() {
|
|
||||||
typeTextInFile(parseKeys("ci)"),
|
|
||||||
"foo<caret>(bar)\n");
|
|
||||||
myFixture.checkResult("foo()\n");
|
|
||||||
assertOffset(4);
|
|
||||||
}
|
|
||||||
|
|
||||||
// |v_ib|
|
|
||||||
public void testInnerBlockCrashWhenNoDelimiterFound() {
|
|
||||||
typeTextInFile(parseKeys("di)"), "(x\n");
|
|
||||||
myFixture.checkResult("(x\n");
|
|
||||||
}
|
|
||||||
|
|
||||||
// VIM-314 |d| |v_iB|
|
// VIM-314 |d| |v_iB|
|
||||||
public void testDeleteInnerCurlyBraceBlock() {
|
public void testDeleteInnerCurlyBraceBlock() {
|
||||||
typeTextInFile(parseKeys("di{"),
|
typeTextInFile(parseKeys("di{"),
|
||||||
@ -226,30 +191,6 @@ public class MotionActionTest extends VimTestCase {
|
|||||||
assertOffset(6);
|
assertOffset(6);
|
||||||
}
|
}
|
||||||
|
|
||||||
// VIM-275 |d| |v_ib|
|
|
||||||
public void testDeleteInnerParensBlockBeforeOpen() {
|
|
||||||
typeTextInFile(parseKeys("di)"),
|
|
||||||
"foo<caret>(bar)\n");
|
|
||||||
myFixture.checkResult("foo()\n");
|
|
||||||
assertOffset(4);
|
|
||||||
}
|
|
||||||
|
|
||||||
// |d| |v_ib|
|
|
||||||
public void testDeleteInnerParensBlockBeforeClose() {
|
|
||||||
typeTextInFile(parseKeys("di)"),
|
|
||||||
"foo(bar<caret>)\n");
|
|
||||||
myFixture.checkResult("foo()\n");
|
|
||||||
}
|
|
||||||
|
|
||||||
// |d| |v_ab|
|
|
||||||
public void testDeleteOuterBlock() {
|
|
||||||
typeTextInFile(parseKeys("da)"),
|
|
||||||
"foo(b<caret>ar, baz);\n");
|
|
||||||
myFixture.checkResult("foo;\n");
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
// |d| |v_aw|
|
// |d| |v_aw|
|
||||||
public void testDeleteOuterWord() {
|
public void testDeleteOuterWord() {
|
||||||
typeTextInFile(parseKeys("daw"),
|
typeTextInFile(parseKeys("daw"),
|
||||||
@ -449,350 +390,6 @@ public class MotionActionTest extends VimTestCase {
|
|||||||
myFixture.checkResult("foo = ;\n");
|
myFixture.checkResult("foo = ;\n");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
//|d| |v_it|
|
|
||||||
public void testDeleteInnerTagBlockCaretInHtml() {
|
|
||||||
typeTextInFile(parseKeys("dit"), "<template <caret>name=\"hello\">\n" +
|
|
||||||
" <button>Click Me</button>\n" +
|
|
||||||
" <p>You've pressed the button {{counter}} times.</p>\n" +
|
|
||||||
"</template>\n");
|
|
||||||
myFixture.checkResult("<template name=\"hello\"></template>\n");
|
|
||||||
}
|
|
||||||
|
|
||||||
//|d| |v_it|
|
|
||||||
public void testDeleteInnerTagBlockCaretInHtmlUnclosedTag() {
|
|
||||||
typeTextInFile(parseKeys("dit"), "<template <caret>name=\"hello\">\n" +
|
|
||||||
" <button>Click Me</button>\n" +
|
|
||||||
" <br>\n" +
|
|
||||||
" <p>You've pressed the button {{counter}} times.</p>\n" +
|
|
||||||
"</template>\n");
|
|
||||||
myFixture.checkResult("<template name=\"hello\"></template>\n");
|
|
||||||
}
|
|
||||||
|
|
||||||
public void testDeleteInnerTagBlockCaretEdgeTag() {
|
|
||||||
typeTextInFile(parseKeys("dit"), "<template name=\"hello\"<caret>>\n" +
|
|
||||||
" <button>Click Me</button>\n" +
|
|
||||||
" <br>\n" +
|
|
||||||
" <p>You've pressed the button {{counter}} times.</p>\n" +
|
|
||||||
"</template>\n");
|
|
||||||
myFixture.checkResult("<template name=\"hello\"></template>\n");
|
|
||||||
}
|
|
||||||
|
|
||||||
//|d| |v_it|
|
|
||||||
public void testDeleteInnerTagBlockBefore() {
|
|
||||||
typeTextInFile(parseKeys("dit"), "abc<caret>de<tag>fg</tag>hi");
|
|
||||||
myFixture.checkResult("abcde<tag>fg</tag>hi");
|
|
||||||
}
|
|
||||||
|
|
||||||
//|d| |v_it|
|
|
||||||
public void testDeleteInnerTagBlockInOpen() {
|
|
||||||
typeTextInFile(parseKeys("dit"), "abcde<ta<caret>g>fg</tag>hi");
|
|
||||||
myFixture.checkResult("abcde<tag></tag>hi");
|
|
||||||
}
|
|
||||||
|
|
||||||
//|d| |v_it|
|
|
||||||
public void testDeleteInnerTagBlockInOpenEndOfLine() {
|
|
||||||
typeTextInFile(parseKeys("dit"), "abcde<ta<caret>g>fg</tag>");
|
|
||||||
myFixture.checkResult("abcde<tag></tag>");
|
|
||||||
}
|
|
||||||
|
|
||||||
//|d| |v_it|
|
|
||||||
public void testDeleteInnerTagBlockInOpenStartOfLine() {
|
|
||||||
typeTextInFile(parseKeys("dit"), "<ta<caret>g>fg</tag>hi");
|
|
||||||
myFixture.checkResult("<tag></tag>hi");
|
|
||||||
}
|
|
||||||
|
|
||||||
//|d| |v_it|
|
|
||||||
public void testDeleteInnerTagBlockInOpenWithArgs() {
|
|
||||||
typeTextInFile(parseKeys("dit"), "abcde<ta<caret>g name = \"name\">fg</tag>hi");
|
|
||||||
myFixture.checkResult("abcde<tag name = \"name\"></tag>hi");
|
|
||||||
}
|
|
||||||
|
|
||||||
//|d| |v_it|
|
|
||||||
public void testDeleteInnerTagBlockBetween() {
|
|
||||||
typeTextInFile(parseKeys("dit"), "abcde<tag>f<caret>g</tag>hi");
|
|
||||||
myFixture.checkResult("abcde<tag></tag>hi");
|
|
||||||
}
|
|
||||||
|
|
||||||
//|d| |v_it|
|
|
||||||
public void testDeleteInnerTagBlockBetweenTagWithRegex() {
|
|
||||||
typeTextInFile(parseKeys("dit"), "abcde<[abc]*>af<caret>gbc</[abc]*>hi");
|
|
||||||
myFixture.checkResult("abcde<[abc]*></[abc]*>hi");
|
|
||||||
}
|
|
||||||
|
|
||||||
//|d| |v_it|
|
|
||||||
public void testDeleteInnerTagBlockBetweenCamelCase() {
|
|
||||||
typeTextInFile(parseKeys("dit"), "abcde<tAg>f<caret>g</tag>hi");
|
|
||||||
myFixture.checkResult("abcde<tAg></tag>hi");
|
|
||||||
}
|
|
||||||
|
|
||||||
//|d| |v_it|
|
|
||||||
public void testDeleteInnerTagBlockBetweenCaps() {
|
|
||||||
typeTextInFile(parseKeys("dit"), "abcde<tag>f<caret>g</TAG>hi");
|
|
||||||
myFixture.checkResult("abcde<tag></TAG>hi");
|
|
||||||
}
|
|
||||||
|
|
||||||
//|d| |v_it|
|
|
||||||
public void testDeleteInnerTagBlockBetweenWithSpaceBeforeTag() {
|
|
||||||
typeTextInFile(parseKeys("dit"), "abcde< tag>f<caret>g</ tag>hi");
|
|
||||||
myFixture.checkResult("abcde< tag>fg</ tag>hi");
|
|
||||||
}
|
|
||||||
|
|
||||||
//|d| |v_it|
|
|
||||||
public void testDeleteInnerTagBlockBetweenWithSpaceAfterTag() {
|
|
||||||
typeTextInFile(parseKeys("dit"), "abcde<tag >f<caret>g</tag>hi");
|
|
||||||
myFixture.checkResult("abcde<tag ></tag>hi");
|
|
||||||
}
|
|
||||||
|
|
||||||
//|d| |v_it|
|
|
||||||
public void testDeleteInnerTagBlockBetweenWithArgs() {
|
|
||||||
typeTextInFile(parseKeys("dit"), "abcde<tag name = \"name\">f<caret>g</tag>hi");
|
|
||||||
myFixture.checkResult("abcde<tag name = \"name\"></tag>hi");
|
|
||||||
}
|
|
||||||
|
|
||||||
//|d| |v_it|
|
|
||||||
public void testDeleteInnerTagBlockInClose() {
|
|
||||||
typeTextInFile(parseKeys("dit"), "abcde<tag>fg</ta<caret>g>hi");
|
|
||||||
myFixture.checkResult("abcde<tag></tag>hi");
|
|
||||||
}
|
|
||||||
|
|
||||||
//|d| |v_it|
|
|
||||||
public void testDeleteInnerTagBlockAfter() {
|
|
||||||
typeTextInFile(parseKeys("dit"), "abcde<tag>fg</tag>h<caret>i");
|
|
||||||
myFixture.checkResult("abcde<tag>fg</tag>hi");
|
|
||||||
}
|
|
||||||
|
|
||||||
//|d| |v_it|
|
|
||||||
public void testDeleteInnerTagBlockInAlone() {
|
|
||||||
typeTextInFile(parseKeys("dit"), "abcde<ta<caret>g>fghi");
|
|
||||||
myFixture.checkResult("abcde<tag>fghi");
|
|
||||||
}
|
|
||||||
|
|
||||||
//|d| |v_it|
|
|
||||||
public void testDeleteInnerTagBlockWithoutTags() {
|
|
||||||
typeTextInFile(parseKeys("dit"), "abc<caret>de");
|
|
||||||
myFixture.checkResult("abcde");
|
|
||||||
}
|
|
||||||
|
|
||||||
//|d| |v_it|
|
|
||||||
public void testDeleteInnerTagBlockBeforeWithoutOpenTag() {
|
|
||||||
typeTextInFile(parseKeys("dit"), "abc<caret>defg</tag>hi");
|
|
||||||
myFixture.checkResult("abcdefg</tag>hi");
|
|
||||||
}
|
|
||||||
|
|
||||||
//|d| |v_it|
|
|
||||||
public void testDeleteInnerTagBlockInCloseWithoutOpenTag() {
|
|
||||||
typeTextInFile(parseKeys("dit"), "abcdefg</ta<caret>g>hi");
|
|
||||||
myFixture.checkResult("abcdefg</tag>hi");
|
|
||||||
}
|
|
||||||
|
|
||||||
//|d| |v_it|
|
|
||||||
public void testDeleteInnerTagBlockAfterWithoutOpenTag() {
|
|
||||||
typeTextInFile(parseKeys("dit"), "abcdefg</tag>h<caret>i");
|
|
||||||
myFixture.checkResult("abcdefg</tag>hi");
|
|
||||||
}
|
|
||||||
|
|
||||||
//|d| |v_it|
|
|
||||||
public void testDeleteInnerTagBlockBeforeWithoutCloseTag() {
|
|
||||||
typeTextInFile(parseKeys("dit"), "abc<caret>defg<tag>hi");
|
|
||||||
myFixture.checkResult("abcdefg<tag>hi");
|
|
||||||
}
|
|
||||||
|
|
||||||
//|d| |v_it|
|
|
||||||
public void testDeleteInnerTagBlockInOpenWithoutCloseTag() {
|
|
||||||
typeTextInFile(parseKeys("dit"), "abcdefg<ta<caret>g>hi");
|
|
||||||
myFixture.checkResult("abcdefg<tag>hi");
|
|
||||||
}
|
|
||||||
|
|
||||||
//|d| |v_it|
|
|
||||||
public void testDeleteInnerTagBlockAfterWithoutCloseTag() {
|
|
||||||
typeTextInFile(parseKeys("dit"), "abcdefg<tag>h<caret>i");
|
|
||||||
myFixture.checkResult("abcdefg<tag>hi");
|
|
||||||
}
|
|
||||||
|
|
||||||
//|d| |v_it|
|
|
||||||
public void testDeleteInnerTagBlockBeforeWrongOrder() {
|
|
||||||
typeTextInFile(parseKeys("dit"), "abc<caret>de</tag>fg<tag>hi");
|
|
||||||
myFixture.checkResult("abcde</tag>fg<tag>hi");
|
|
||||||
}
|
|
||||||
|
|
||||||
//|d| |v_it|
|
|
||||||
public void testDeleteInnerTagBlockInOpenWrongOrder() {
|
|
||||||
typeTextInFile(parseKeys("dit"), "abcde</ta<caret>g>fg<tag>hi");
|
|
||||||
myFixture.checkResult("abcde</tag>fg<tag>hi");
|
|
||||||
}
|
|
||||||
|
|
||||||
//|d| |v_it|
|
|
||||||
public void testDeleteInnerTagBlockBetweenWrongOrder() {
|
|
||||||
typeTextInFile(parseKeys("dit"), "abcde</tag>f<caret>g<tag>hi");
|
|
||||||
myFixture.checkResult("abcde</tag>fg<tag>hi");
|
|
||||||
}
|
|
||||||
|
|
||||||
//|d| |v_it|
|
|
||||||
public void testDeleteInnerTagBlockInCloseWrongOrder() {
|
|
||||||
typeTextInFile(parseKeys("dit"), "abcde</tag>fg<ta<caret>g>hi");
|
|
||||||
myFixture.checkResult("abcde</tag>fg<tag>hi");
|
|
||||||
}
|
|
||||||
|
|
||||||
//|d| |v_it|
|
|
||||||
public void testDeleteInnerTagBlockTwoTagsWrongOrder() {
|
|
||||||
typeTextInFile(parseKeys("dit"), "<foo><html>t<caret>ext</foo></html>");
|
|
||||||
myFixture.checkResult("<foo></foo></html>");
|
|
||||||
}
|
|
||||||
|
|
||||||
//|d| |v_it|
|
|
||||||
public void testDeleteInnerTagBlockTwoTagsWrongOrderInClosingTag() {
|
|
||||||
typeTextInFile(parseKeys("dit"), "<foo><html>text</foo></htm<caret>l>");
|
|
||||||
myFixture.checkResult("<foo><html></html>");
|
|
||||||
}
|
|
||||||
|
|
||||||
//|d| |v_it|
|
|
||||||
public void testDeleteInnerTagBlockAfterWrongOrder() {
|
|
||||||
typeTextInFile(parseKeys("dit"), "abcde</tag>fg<tag>h<caret>i");
|
|
||||||
myFixture.checkResult("abcde</tag>fg<tag>hi");
|
|
||||||
}
|
|
||||||
|
|
||||||
//|d| |v_it|
|
|
||||||
public void testDeleteInnerTagBlockBracketInside() {
|
|
||||||
typeTextInFile(parseKeys("dit"), "abcde<tag>f<caret><>g</tag>hi");
|
|
||||||
myFixture.checkResult("abcde<tag></tag>hi");
|
|
||||||
}
|
|
||||||
|
|
||||||
//|d| |v_it|
|
|
||||||
public void testDeleteInnerTagBlockBracketInsideString() {
|
|
||||||
typeTextInFile(parseKeys("dit"), "abcde<tag>f<caret>\"<>\"g</tag>hi");
|
|
||||||
myFixture.checkResult("abcde<tag></tag>hi");
|
|
||||||
}
|
|
||||||
|
|
||||||
//|d| |v_at|
|
|
||||||
public void testDeleteOuterTagBlockBefore() {
|
|
||||||
typeTextInFile(parseKeys("dat"), "abc<caret>de<tag>fg</tag>hi");
|
|
||||||
myFixture.checkResult("abcde<tag>fg</tag>hi");
|
|
||||||
}
|
|
||||||
|
|
||||||
//|d| |v_at|
|
|
||||||
public void testDeleteOuterTagBlockInOpen() {
|
|
||||||
typeTextInFile(parseKeys("dat"), "abcde<ta<caret>g>fg</tag>hi");
|
|
||||||
myFixture.checkResult("abcdehi");
|
|
||||||
}
|
|
||||||
|
|
||||||
//|d| |v_at|
|
|
||||||
public void testDeleteOuterTagBlockInOpenWithArgs() {
|
|
||||||
typeTextInFile(parseKeys("dat"), "abcde<ta<caret>g name = \"name\">fg</tag>hi");
|
|
||||||
myFixture.checkResult("abcdehi");
|
|
||||||
}
|
|
||||||
|
|
||||||
//|d| |v_at|
|
|
||||||
public void testDeleteOuterTagBlockBetween() {
|
|
||||||
typeTextInFile(parseKeys("dat"), "abcde<tag>f<caret>g</tag>hi");
|
|
||||||
myFixture.checkResult("abcdehi");
|
|
||||||
}
|
|
||||||
|
|
||||||
//|d| |v_at|
|
|
||||||
public void testDeleteOuterTagBlockBetweenWithArgs() {
|
|
||||||
typeTextInFile(parseKeys("dat"), "abcde<tag name = \"name\">f<caret>g</tag>hi");
|
|
||||||
myFixture.checkResult("abcdehi");
|
|
||||||
}
|
|
||||||
|
|
||||||
//|d| |v_at|
|
|
||||||
public void testDeleteOuterTagBlockInClose() {
|
|
||||||
typeTextInFile(parseKeys("dat"), "abcde<tag>fg</ta<caret>g>hi");
|
|
||||||
myFixture.checkResult("abcdehi");
|
|
||||||
}
|
|
||||||
|
|
||||||
//|d| |v_at|
|
|
||||||
public void testDeleteOuterTagBlockAfter() {
|
|
||||||
typeTextInFile(parseKeys("dat"), "abcde<tag>fg</tag>h<caret>i");
|
|
||||||
myFixture.checkResult("abcde<tag>fg</tag>hi");
|
|
||||||
}
|
|
||||||
|
|
||||||
//|d| |v_at|
|
|
||||||
public void testDeleteOuterTagBlockInAlone() {
|
|
||||||
typeTextInFile(parseKeys("dat"), "abcde<ta<caret>g>fghi");
|
|
||||||
myFixture.checkResult("abcde<tag>fghi");
|
|
||||||
}
|
|
||||||
|
|
||||||
//|d| |v_at|
|
|
||||||
public void testDeleteOuterTagBlockWithoutTags() {
|
|
||||||
typeTextInFile(parseKeys("dat"), "abc<caret>de");
|
|
||||||
myFixture.checkResult("abcde");
|
|
||||||
}
|
|
||||||
|
|
||||||
//|d| |v_at|
|
|
||||||
public void testDeleteOuterTagBlockBeforeWithoutOpenTag() {
|
|
||||||
typeTextInFile(parseKeys("dat"), "abc<caret>defg</tag>hi");
|
|
||||||
myFixture.checkResult("abcdefg</tag>hi");
|
|
||||||
}
|
|
||||||
|
|
||||||
//|d| |v_at|
|
|
||||||
public void testDeleteOuterTagBlockInCloseWithoutOpenTag() {
|
|
||||||
typeTextInFile(parseKeys("dat"), "abcdefg</ta<caret>g>hi");
|
|
||||||
myFixture.checkResult("abcdefg</tag>hi");
|
|
||||||
}
|
|
||||||
|
|
||||||
//|d| |v_at|
|
|
||||||
public void testDeleteOuterTagBlockAfterWithoutOpenTag() {
|
|
||||||
typeTextInFile(parseKeys("dat"), "abcdefg</tag>h<caret>i");
|
|
||||||
myFixture.checkResult("abcdefg</tag>hi");
|
|
||||||
}
|
|
||||||
|
|
||||||
//|d| |v_at|
|
|
||||||
public void testDeleteOuterTagBlockBeforeWithoutCloseTag() {
|
|
||||||
typeTextInFile(parseKeys("dat"), "abc<caret>defg<tag>hi");
|
|
||||||
myFixture.checkResult("abcdefg<tag>hi");
|
|
||||||
}
|
|
||||||
|
|
||||||
//|d| |v_at|
|
|
||||||
public void testDeleteOuterTagBlockInOpenWithoutCloseTag() {
|
|
||||||
typeTextInFile(parseKeys("dat"), "abcdefg<ta<caret>g>hi");
|
|
||||||
myFixture.checkResult("abcdefg<tag>hi");
|
|
||||||
}
|
|
||||||
|
|
||||||
//|d| |v_at|
|
|
||||||
public void testDeleteOuterTagBlockAfterWithoutCloseTag() {
|
|
||||||
typeTextInFile(parseKeys("dat"), "abcdefg<tag>h<caret>i");
|
|
||||||
myFixture.checkResult("abcdefg<tag>hi");
|
|
||||||
}
|
|
||||||
|
|
||||||
//|d| |v_at|
|
|
||||||
public void testDeleteOuterTagBlockBeforeWrongOrder() {
|
|
||||||
typeTextInFile(parseKeys("dat"), "abc<caret>de</tag>fg<tag>hi");
|
|
||||||
myFixture.checkResult("abcde</tag>fg<tag>hi");
|
|
||||||
}
|
|
||||||
|
|
||||||
//|d| |v_at|
|
|
||||||
public void testDeleteOuterTagBlockInOpenWrongOrder() {
|
|
||||||
typeTextInFile(parseKeys("dat"), "abcde</ta<caret>g>fg<tag>hi");
|
|
||||||
myFixture.checkResult("abcde</tag>fg<tag>hi");
|
|
||||||
}
|
|
||||||
|
|
||||||
//|d| |v_at|
|
|
||||||
public void testDeleteOuterTagBlockBetweenWrongOrder() {
|
|
||||||
typeTextInFile(parseKeys("dat"), "abcde</tag>f<caret>g<tag>hi");
|
|
||||||
myFixture.checkResult("abcde</tag>fg<tag>hi");
|
|
||||||
}
|
|
||||||
|
|
||||||
//|d| |v_at|
|
|
||||||
public void testDeleteOuterTagBlockInCloseWrongOrder() {
|
|
||||||
typeTextInFile(parseKeys("dat"), "abcde</tag>fg<ta<caret>g>hi");
|
|
||||||
myFixture.checkResult("abcde</tag>fg<tag>hi");
|
|
||||||
}
|
|
||||||
|
|
||||||
//|d| |v_at|
|
|
||||||
public void testDeleteOuterTagBlockAfterWrongOrder() {
|
|
||||||
typeTextInFile(parseKeys("dat"), "abcde</tag>fg<tag>h<caret>i");
|
|
||||||
myFixture.checkResult("abcde</tag>fg<tag>hi");
|
|
||||||
}
|
|
||||||
|
|
||||||
// |v_it|
|
|
||||||
public void testFileStartsWithSlash() {
|
|
||||||
configureByText("/*hello\n" +
|
|
||||||
"<caret>foo\n" +
|
|
||||||
"bar>baz\n");
|
|
||||||
typeText(parseKeys("vit"));
|
|
||||||
assertPluginError(true);
|
|
||||||
}
|
|
||||||
|
|
||||||
// VIM-1427
|
// VIM-1427
|
||||||
public void testDeleteOuterTagWithCount() {
|
public void testDeleteOuterTagWithCount() {
|
||||||
typeTextInFile(parseKeys("d2at"),"<a><b><c><caret></c></b></a>");
|
typeTextInFile(parseKeys("d2at"),"<a><b><c><caret></c></b></a>");
|
||||||
|
@ -0,0 +1,73 @@
|
|||||||
|
/*
|
||||||
|
* 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 <http://www.gnu.org/licenses/>.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package org.jetbrains.plugins.ideavim.action
|
||||||
|
|
||||||
|
import org.jetbrains.plugins.ideavim.VimTestCase
|
||||||
|
import com.maddyhome.idea.vim.helper.StringHelper.parseKeys
|
||||||
|
import org.junit.Test
|
||||||
|
|
||||||
|
class RepeatActionTest : VimTestCase() {
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun testSimpleRepeatLastCommand() {
|
||||||
|
configureByText("foo foo")
|
||||||
|
typeText(parseKeys("cw", "bar", "<Esc>", "w", "."))
|
||||||
|
myFixture.checkResult("bar bar")
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun testRepeatChangeToCharInNextLine() {
|
||||||
|
configureByText("The first line.\n" +
|
||||||
|
"This is the second line.\n" +
|
||||||
|
"Third line here, with a comma.\n" +
|
||||||
|
"Last line.")
|
||||||
|
typeText(parseKeys("j", "ct.", "Change the line to point", "<Esc>", "j0", "."))
|
||||||
|
myFixture.checkResult("The first line.\n" +
|
||||||
|
"Change the line to point.\n" +
|
||||||
|
"Change the line to point.\n" +
|
||||||
|
"Last line.")
|
||||||
|
}
|
||||||
|
|
||||||
|
// VIM-1644
|
||||||
|
@Test
|
||||||
|
fun testRepeatChangeInVisualMode() {
|
||||||
|
configureByText("foobar foobar")
|
||||||
|
typeText(parseKeys("<C-V>llc", "fu", "<Esc>", "w", "."))
|
||||||
|
myFixture.checkResult("fubar fubar")
|
||||||
|
}
|
||||||
|
|
||||||
|
// VIM-1644
|
||||||
|
@Test
|
||||||
|
fun testRepeatChangeInVisualModeMultiline() {
|
||||||
|
configureByText(
|
||||||
|
"There is a red house.\n" +
|
||||||
|
"Another red house there.\n" +
|
||||||
|
"They have red windows.\n" +
|
||||||
|
"Good."
|
||||||
|
)
|
||||||
|
typeText(parseKeys("www", "<C-V>ec", "blue", "<Esc>", "j0w.", "j0ww."))
|
||||||
|
myFixture.checkResult(
|
||||||
|
"There is a blue house.\n" +
|
||||||
|
"Another blue house there.\n" +
|
||||||
|
"They have blue windows.\n" +
|
||||||
|
"Good."
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
@ -0,0 +1,52 @@
|
|||||||
|
/*
|
||||||
|
* 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 <http://www.gnu.org/licenses/>.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package org.jetbrains.plugins.ideavim.action.change.insert
|
||||||
|
|
||||||
|
import com.maddyhome.idea.vim.command.CommandState
|
||||||
|
import com.maddyhome.idea.vim.helper.StringHelper.parseKeys
|
||||||
|
import org.jetbrains.plugins.ideavim.VimTestCase
|
||||||
|
|
||||||
|
class InsertDeleteInsertedTextActionTest : VimTestCase() {
|
||||||
|
// VIM-1655
|
||||||
|
fun `test deleted text is not yanked`() {
|
||||||
|
doTest(parseKeys("yiw", "ea", "Hello", "<C-U>", "<ESC>p"), """
|
||||||
|
A Discovery
|
||||||
|
|
||||||
|
I found <caret>it in a legendary land
|
||||||
|
""".trimIndent(), """
|
||||||
|
A Discovery
|
||||||
|
|
||||||
|
I found iti<caret>t in a legendary land
|
||||||
|
""".trimIndent(), CommandState.Mode.COMMAND, CommandState.SubMode.NONE)
|
||||||
|
}
|
||||||
|
|
||||||
|
// VIM-1655
|
||||||
|
// VimBehaviourDiffers. Inserted text is not deleted after <C-U>
|
||||||
|
fun `test deleted text is not yanked after replace`() {
|
||||||
|
doTest(parseKeys("yiw", "eR", "Hello", "<C-U>", "<ESC>p"), """
|
||||||
|
A Discovery
|
||||||
|
|
||||||
|
I found <caret>it in a legendary land
|
||||||
|
""".trimIndent(), """
|
||||||
|
A Discovery
|
||||||
|
|
||||||
|
I found ii<caret>ta legendary land
|
||||||
|
""".trimIndent(), CommandState.Mode.COMMAND, CommandState.SubMode.NONE)
|
||||||
|
}
|
||||||
|
}
|
@ -16,26 +16,23 @@
|
|||||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
package com.maddyhome.idea.vim.action.ex;
|
package org.jetbrains.plugins.ideavim.action.change.insert
|
||||||
|
|
||||||
import com.intellij.openapi.actionSystem.DataContext;
|
import com.maddyhome.idea.vim.command.CommandState
|
||||||
import com.intellij.openapi.editor.Editor;
|
import com.maddyhome.idea.vim.helper.StringHelper.parseKeys
|
||||||
import com.intellij.openapi.editor.actionSystem.EditorAction;
|
import org.jetbrains.plugins.ideavim.VimTestCase
|
||||||
import com.maddyhome.idea.vim.VimPlugin;
|
|
||||||
import com.maddyhome.idea.vim.command.Command;
|
|
||||||
import com.maddyhome.idea.vim.handler.EditorActionHandlerBase;
|
|
||||||
import org.jetbrains.annotations.NotNull;
|
|
||||||
|
|
||||||
/**
|
class InsertDeletePreviousWordActionTest : VimTestCase() {
|
||||||
*/
|
// VIM-1655
|
||||||
public class CancelExEntryAction extends EditorAction {
|
fun `test deleted word is not yanked`() {
|
||||||
public CancelExEntryAction() {
|
doTest(parseKeys("yiw", "3wea", "<C-W>", "<ESC>p"), """
|
||||||
super(new Handler());
|
A Discovery
|
||||||
}
|
|
||||||
|
|
||||||
private static class Handler extends EditorActionHandlerBase {
|
I found <caret>it in a legendary land
|
||||||
protected boolean execute(@NotNull Editor editor, @NotNull DataContext context, @NotNull Command cmd) {
|
""".trimIndent(), """
|
||||||
return VimPlugin.getProcess().cancelExEntry(editor, context);
|
A Discovery
|
||||||
|
|
||||||
|
I found it in a i<caret>t land
|
||||||
|
""".trimIndent(), CommandState.Mode.COMMAND, CommandState.SubMode.NONE)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
|
@ -22,7 +22,6 @@ package org.jetbrains.plugins.ideavim.action.motion.leftright
|
|||||||
|
|
||||||
import com.maddyhome.idea.vim.command.CommandState
|
import com.maddyhome.idea.vim.command.CommandState
|
||||||
import com.maddyhome.idea.vim.helper.StringHelper.parseKeys
|
import com.maddyhome.idea.vim.helper.StringHelper.parseKeys
|
||||||
import com.maddyhome.idea.vim.helper.VimBehaviourDiffers
|
|
||||||
import org.jetbrains.plugins.ideavim.VimTestCase
|
import org.jetbrains.plugins.ideavim.VimTestCase
|
||||||
|
|
||||||
class MotionRightActionTest : VimTestCase() {
|
class MotionRightActionTest : VimTestCase() {
|
||||||
@ -80,14 +79,6 @@ class MotionRightActionTest : VimTestCase() {
|
|||||||
""".trimIndent(), CommandState.Mode.COMMAND, CommandState.SubMode.NONE)
|
""".trimIndent(), CommandState.Mode.COMMAND, CommandState.SubMode.NONE)
|
||||||
}
|
}
|
||||||
|
|
||||||
@VimBehaviourDiffers("""
|
|
||||||
A Discovery
|
|
||||||
|
|
||||||
I found it in a legendar𝛁<caret> land
|
|
||||||
all rocks and lavender and tufted grass,
|
|
||||||
where it was settled on some sodden sand
|
|
||||||
hard by the torrent of a mountain pass.
|
|
||||||
""")
|
|
||||||
fun `test simple motion non-ascii`() {
|
fun `test simple motion non-ascii`() {
|
||||||
doTest(parseKeys("l"), """
|
doTest(parseKeys("l"), """
|
||||||
A Discovery
|
A Discovery
|
||||||
@ -99,21 +90,13 @@ class MotionRightActionTest : VimTestCase() {
|
|||||||
""".trimIndent(), """
|
""".trimIndent(), """
|
||||||
A Discovery
|
A Discovery
|
||||||
|
|
||||||
I found it in a legendar<caret>𝛁 land
|
I found it in a legendar𝛁<caret> land
|
||||||
all rocks and lavender and tufted grass,
|
all rocks and lavender and tufted grass,
|
||||||
where it was settled on some sodden sand
|
where it was settled on some sodden sand
|
||||||
hard by the torrent of a mountain pass.
|
hard by the torrent of a mountain pass.
|
||||||
""".trimIndent(), CommandState.Mode.COMMAND, CommandState.SubMode.NONE)
|
""".trimIndent(), CommandState.Mode.COMMAND, CommandState.SubMode.NONE)
|
||||||
}
|
}
|
||||||
|
|
||||||
@VimBehaviourDiffers("""
|
|
||||||
A Discovery
|
|
||||||
|
|
||||||
I found it in a legendar🐔<caret> land
|
|
||||||
all rocks and lavender and tufted grass,
|
|
||||||
where it was settled on some sodden sand
|
|
||||||
hard by the torrent of a mountain pass.
|
|
||||||
""")
|
|
||||||
fun `test simple motion emoji`() {
|
fun `test simple motion emoji`() {
|
||||||
doTest(parseKeys("l"), """
|
doTest(parseKeys("l"), """
|
||||||
A Discovery
|
A Discovery
|
||||||
@ -125,7 +108,25 @@ class MotionRightActionTest : VimTestCase() {
|
|||||||
""".trimIndent(), """
|
""".trimIndent(), """
|
||||||
A Discovery
|
A Discovery
|
||||||
|
|
||||||
I found it in a legendar<caret>🐔 land
|
I found it in a legendar🐔<caret> land
|
||||||
|
all rocks and lavender and tufted grass,
|
||||||
|
where it was settled on some sodden sand
|
||||||
|
hard by the torrent of a mountain pass.
|
||||||
|
""".trimIndent(), CommandState.Mode.COMMAND, CommandState.SubMode.NONE)
|
||||||
|
}
|
||||||
|
|
||||||
|
fun `test simple motion czech`() {
|
||||||
|
doTest(parseKeys("l"), """
|
||||||
|
A Discovery
|
||||||
|
|
||||||
|
I found it in a legendar<caret>ž land
|
||||||
|
all rocks and lavender and tufted grass,
|
||||||
|
where it was settled on some sodden sand
|
||||||
|
hard by the torrent of a mountain pass.
|
||||||
|
""".trimIndent(), """
|
||||||
|
A Discovery
|
||||||
|
|
||||||
|
I found it in a legendarž<caret> land
|
||||||
all rocks and lavender and tufted grass,
|
all rocks and lavender and tufted grass,
|
||||||
where it was settled on some sodden sand
|
where it was settled on some sodden sand
|
||||||
hard by the torrent of a mountain pass.
|
hard by the torrent of a mountain pass.
|
||||||
|
@ -0,0 +1,129 @@
|
|||||||
|
package org.jetbrains.plugins.ideavim.action.motion.`object`
|
||||||
|
|
||||||
|
import com.maddyhome.idea.vim.helper.StringHelper.parseKeys
|
||||||
|
import org.jetbrains.plugins.ideavim.VimTestCase
|
||||||
|
|
||||||
|
class MotionInnerBlockParenActionTest : VimTestCase() {
|
||||||
|
// VIM-1633 |v_i)|
|
||||||
|
fun `test single letter with single parentheses`() {
|
||||||
|
configureByText("(<caret>a)")
|
||||||
|
typeText(parseKeys("vi)"))
|
||||||
|
assertSelection("a")
|
||||||
|
}
|
||||||
|
|
||||||
|
fun `test single letter with double parentheses`() {
|
||||||
|
configureByText("((<caret>a))")
|
||||||
|
typeText(parseKeys("vi)"))
|
||||||
|
assertSelection("(a)")
|
||||||
|
}
|
||||||
|
|
||||||
|
fun `test multiline outside parentheses`() {
|
||||||
|
configureByText("""(outer
|
||||||
|
|<caret>(inner))""".trimMargin())
|
||||||
|
typeText(parseKeys("vi)"))
|
||||||
|
assertSelection("inner")
|
||||||
|
}
|
||||||
|
|
||||||
|
fun `test multiline in parentheses`() {
|
||||||
|
configureByText("""(outer
|
||||||
|
|(inner<caret>))""".trimMargin())
|
||||||
|
typeText(parseKeys("vi)"))
|
||||||
|
assertSelection("inner")
|
||||||
|
}
|
||||||
|
|
||||||
|
fun `test multiline inside of outer parentheses`() {
|
||||||
|
configureByText("""(outer
|
||||||
|
|<caret> (inner))""".trimMargin())
|
||||||
|
typeText(parseKeys("vi)"))
|
||||||
|
assertSelection("""outer
|
||||||
|
| (inner)""".trimMargin())
|
||||||
|
}
|
||||||
|
|
||||||
|
fun `test double motion`() {
|
||||||
|
configureByText("""(outer
|
||||||
|
|<caret>(inner))""".trimMargin())
|
||||||
|
typeText(parseKeys("vi)i)"))
|
||||||
|
assertSelection("""outer
|
||||||
|
|(inner)""".trimMargin())
|
||||||
|
}
|
||||||
|
|
||||||
|
fun `test motion with count`() {
|
||||||
|
configureByText("""(outer
|
||||||
|
|<caret>(inner))""".trimMargin())
|
||||||
|
typeText(parseKeys("v2i)"))
|
||||||
|
assertSelection("""outer
|
||||||
|
|(inner)""".trimMargin())
|
||||||
|
}
|
||||||
|
|
||||||
|
fun `test text object after motion`() {
|
||||||
|
configureByText("""(outer
|
||||||
|
|<caret>(inner))""".trimMargin())
|
||||||
|
typeText(parseKeys("vlli)"))
|
||||||
|
assertSelection("""outer
|
||||||
|
|(inner)""".trimMargin())
|
||||||
|
}
|
||||||
|
|
||||||
|
fun `test text object after motion outside parentheses`() {
|
||||||
|
configureByText("""(outer
|
||||||
|
|(inner<caret>))""".trimMargin())
|
||||||
|
typeText(parseKeys("vlli)"))
|
||||||
|
assertSelection("inner")
|
||||||
|
}
|
||||||
|
|
||||||
|
fun `test text object after motion inside parentheses`() {
|
||||||
|
configureByText("""(outer
|
||||||
|
|(<caret>inner))""".trimMargin())
|
||||||
|
typeText(parseKeys("vllli)"))
|
||||||
|
assertSelection("inner")
|
||||||
|
}
|
||||||
|
|
||||||
|
// VIM-326 |d| |v_ib|
|
||||||
|
fun testDeleteInnerBlock() {
|
||||||
|
typeTextInFile(parseKeys("di)"),
|
||||||
|
"foo(\"b<caret>ar\")\n")
|
||||||
|
myFixture.checkResult("foo()\n")
|
||||||
|
}
|
||||||
|
|
||||||
|
// VIM-326 |d| |v_ib|
|
||||||
|
fun testDeleteInnerBlockCaretBeforeString() {
|
||||||
|
typeTextInFile(parseKeys("di)"),
|
||||||
|
"foo(<caret>\"bar\")\n")
|
||||||
|
myFixture.checkResult("foo()\n")
|
||||||
|
}
|
||||||
|
|
||||||
|
// VIM-326 |c| |v_ib|
|
||||||
|
fun testChangeInnerBlockCaretBeforeString() {
|
||||||
|
typeTextInFile(parseKeys("ci)"),
|
||||||
|
"foo(<caret>\"bar\")\n")
|
||||||
|
myFixture.checkResult("foo()\n")
|
||||||
|
}
|
||||||
|
|
||||||
|
// VIM-392 |c| |v_ib|
|
||||||
|
fun testChangeInnerBlockCaretBeforeBlock() {
|
||||||
|
typeTextInFile(parseKeys("ci)"),
|
||||||
|
"foo<caret>(bar)\n")
|
||||||
|
myFixture.checkResult("foo()\n")
|
||||||
|
assertOffset(4)
|
||||||
|
}
|
||||||
|
|
||||||
|
// |v_ib|
|
||||||
|
fun testInnerBlockCrashWhenNoDelimiterFound() {
|
||||||
|
typeTextInFile(parseKeys("di)"), "(x\n")
|
||||||
|
myFixture.checkResult("(x\n")
|
||||||
|
}
|
||||||
|
|
||||||
|
// VIM-275 |d| |v_ib|
|
||||||
|
fun testDeleteInnerParensBlockBeforeOpen() {
|
||||||
|
typeTextInFile(parseKeys("di)"),
|
||||||
|
"foo<caret>(bar)\n")
|
||||||
|
myFixture.checkResult("foo()\n")
|
||||||
|
assertOffset(4)
|
||||||
|
}
|
||||||
|
|
||||||
|
// |d| |v_ib|
|
||||||
|
fun testDeleteInnerParensBlockBeforeClose() {
|
||||||
|
typeTextInFile(parseKeys("di)"),
|
||||||
|
"foo(bar<caret>)\n")
|
||||||
|
myFixture.checkResult("foo()\n")
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,374 @@
|
|||||||
|
/*
|
||||||
|
* 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 <http://www.gnu.org/licenses/>.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package org.jetbrains.plugins.ideavim.action.motion.`object`
|
||||||
|
|
||||||
|
import com.maddyhome.idea.vim.helper.StringHelper.parseKeys
|
||||||
|
import org.jetbrains.plugins.ideavim.VimTestCase
|
||||||
|
|
||||||
|
class MotionInnerBlockTagActionTest : VimTestCase() {
|
||||||
|
|
||||||
|
//|d| |v_it|
|
||||||
|
fun testDeleteInnerTagBlockCaretInHtml() {
|
||||||
|
typeTextInFile(parseKeys("dit"), "<template <caret>name=\"hello\">\n" +
|
||||||
|
" <button>Click Me</button>\n" +
|
||||||
|
" <p>You've pressed the button {{counter}} times.</p>\n" +
|
||||||
|
"</template>\n")
|
||||||
|
myFixture.checkResult("<template name=\"hello\"></template>\n")
|
||||||
|
}
|
||||||
|
|
||||||
|
//|d| |v_it|
|
||||||
|
fun testDeleteInnerTagBlockCaretInHtmlUnclosedTag() {
|
||||||
|
typeTextInFile(parseKeys("dit"), "<template <caret>name=\"hello\">\n" +
|
||||||
|
" <button>Click Me</button>\n" +
|
||||||
|
" <br>\n" +
|
||||||
|
" <p>You've pressed the button {{counter}} times.</p>\n" +
|
||||||
|
"</template>\n")
|
||||||
|
myFixture.checkResult("<template name=\"hello\"></template>\n")
|
||||||
|
}
|
||||||
|
|
||||||
|
fun testDeleteInnerTagBlockCaretEdgeTag() {
|
||||||
|
typeTextInFile(parseKeys("dit"), "<template name=\"hello\"<caret>>\n" +
|
||||||
|
" <button>Click Me</button>\n" +
|
||||||
|
" <br>\n" +
|
||||||
|
" <p>You've pressed the button {{counter}} times.</p>\n" +
|
||||||
|
"</template>\n")
|
||||||
|
myFixture.checkResult("<template name=\"hello\"></template>\n")
|
||||||
|
}
|
||||||
|
|
||||||
|
//|d| |v_it|
|
||||||
|
fun testDeleteInnerTagBlockBefore() {
|
||||||
|
typeTextInFile(parseKeys("dit"), "abc<caret>de<tag>fg</tag>hi")
|
||||||
|
myFixture.checkResult("abcde<tag>fg</tag>hi")
|
||||||
|
}
|
||||||
|
|
||||||
|
//|d| |v_it|
|
||||||
|
fun testDeleteInnerTagBlockInOpen() {
|
||||||
|
typeTextInFile(parseKeys("dit"), "abcde<ta<caret>g>fg</tag>hi")
|
||||||
|
myFixture.checkResult("abcde<tag></tag>hi")
|
||||||
|
}
|
||||||
|
|
||||||
|
//|d| |v_it|
|
||||||
|
fun testDeleteInnerTagBlockInOpenEndOfLine() {
|
||||||
|
typeTextInFile(parseKeys("dit"), "abcde<ta<caret>g>fg</tag>")
|
||||||
|
myFixture.checkResult("abcde<tag></tag>")
|
||||||
|
}
|
||||||
|
|
||||||
|
//|d| |v_it|
|
||||||
|
fun testDeleteInnerTagBlockInOpenStartOfLine() {
|
||||||
|
typeTextInFile(parseKeys("dit"), "<ta<caret>g>fg</tag>hi")
|
||||||
|
myFixture.checkResult("<tag></tag>hi")
|
||||||
|
}
|
||||||
|
|
||||||
|
//|d| |v_it|
|
||||||
|
fun testDeleteInnerTagBlockInOpenWithArgs() {
|
||||||
|
typeTextInFile(parseKeys("dit"), "abcde<ta<caret>g name = \"name\">fg</tag>hi")
|
||||||
|
myFixture.checkResult("abcde<tag name = \"name\"></tag>hi")
|
||||||
|
}
|
||||||
|
|
||||||
|
//|d| |v_it|
|
||||||
|
fun testDeleteInnerTagBlockBetween() {
|
||||||
|
typeTextInFile(parseKeys("dit"), "abcde<tag>f<caret>g</tag>hi")
|
||||||
|
myFixture.checkResult("abcde<tag></tag>hi")
|
||||||
|
}
|
||||||
|
|
||||||
|
//|d| |v_it|
|
||||||
|
fun testDeleteInnerTagBlockBetweenTagWithRegex() {
|
||||||
|
typeTextInFile(parseKeys("dit"), "abcde<[abc]*>af<caret>gbc</[abc]*>hi")
|
||||||
|
myFixture.checkResult("abcde<[abc]*></[abc]*>hi")
|
||||||
|
}
|
||||||
|
|
||||||
|
//|d| |v_it|
|
||||||
|
fun testDeleteInnerTagBlockBetweenCamelCase() {
|
||||||
|
typeTextInFile(parseKeys("dit"), "abcde<tAg>f<caret>g</tag>hi")
|
||||||
|
myFixture.checkResult("abcde<tAg></tag>hi")
|
||||||
|
}
|
||||||
|
|
||||||
|
//|d| |v_it|
|
||||||
|
fun testDeleteInnerTagBlockBetweenCaps() {
|
||||||
|
typeTextInFile(parseKeys("dit"), "abcde<tag>f<caret>g</TAG>hi")
|
||||||
|
myFixture.checkResult("abcde<tag></TAG>hi")
|
||||||
|
}
|
||||||
|
|
||||||
|
//|d| |v_it|
|
||||||
|
fun testDeleteInnerTagBlockBetweenWithSpaceBeforeTag() {
|
||||||
|
typeTextInFile(parseKeys("dit"), "abcde< tag>f<caret>g</ tag>hi")
|
||||||
|
myFixture.checkResult("abcde< tag>fg</ tag>hi")
|
||||||
|
}
|
||||||
|
|
||||||
|
//|d| |v_it|
|
||||||
|
fun testDeleteInnerTagBlockBetweenWithSpaceAfterTag() {
|
||||||
|
typeTextInFile(parseKeys("dit"), "abcde<tag >f<caret>g</tag>hi")
|
||||||
|
myFixture.checkResult("abcde<tag ></tag>hi")
|
||||||
|
}
|
||||||
|
|
||||||
|
//|d| |v_it|
|
||||||
|
fun testDeleteInnerTagBlockBetweenWithArgs() {
|
||||||
|
typeTextInFile(parseKeys("dit"), "abcde<tag name = \"name\">f<caret>g</tag>hi")
|
||||||
|
myFixture.checkResult("abcde<tag name = \"name\"></tag>hi")
|
||||||
|
}
|
||||||
|
|
||||||
|
//|d| |v_it|
|
||||||
|
fun testDeleteInnerTagBlockInClose() {
|
||||||
|
typeTextInFile(parseKeys("dit"), "abcde<tag>fg</ta<caret>g>hi")
|
||||||
|
myFixture.checkResult("abcde<tag></tag>hi")
|
||||||
|
}
|
||||||
|
|
||||||
|
//|d| |v_it|
|
||||||
|
fun testDeleteInnerTagBlockAfter() {
|
||||||
|
typeTextInFile(parseKeys("dit"), "abcde<tag>fg</tag>h<caret>i")
|
||||||
|
myFixture.checkResult("abcde<tag>fg</tag>hi")
|
||||||
|
}
|
||||||
|
|
||||||
|
//|d| |v_it|
|
||||||
|
fun testDeleteInnerTagBlockInAlone() {
|
||||||
|
typeTextInFile(parseKeys("dit"), "abcde<ta<caret>g>fghi")
|
||||||
|
myFixture.checkResult("abcde<tag>fghi")
|
||||||
|
}
|
||||||
|
|
||||||
|
//|d| |v_it|
|
||||||
|
fun testDeleteInnerTagBlockWithoutTags() {
|
||||||
|
typeTextInFile(parseKeys("dit"), "abc<caret>de")
|
||||||
|
myFixture.checkResult("abcde")
|
||||||
|
}
|
||||||
|
|
||||||
|
//|d| |v_it|
|
||||||
|
fun testDeleteInnerTagBlockBeforeWithoutOpenTag() {
|
||||||
|
typeTextInFile(parseKeys("dit"), "abc<caret>defg</tag>hi")
|
||||||
|
myFixture.checkResult("abcdefg</tag>hi")
|
||||||
|
}
|
||||||
|
|
||||||
|
//|d| |v_it|
|
||||||
|
fun testDeleteInnerTagBlockInCloseWithoutOpenTag() {
|
||||||
|
typeTextInFile(parseKeys("dit"), "abcdefg</ta<caret>g>hi")
|
||||||
|
myFixture.checkResult("abcdefg</tag>hi")
|
||||||
|
}
|
||||||
|
|
||||||
|
//|d| |v_it|
|
||||||
|
fun testDeleteInnerTagBlockAfterWithoutOpenTag() {
|
||||||
|
typeTextInFile(parseKeys("dit"), "abcdefg</tag>h<caret>i")
|
||||||
|
myFixture.checkResult("abcdefg</tag>hi")
|
||||||
|
}
|
||||||
|
|
||||||
|
//|d| |v_it|
|
||||||
|
fun testDeleteInnerTagBlockBeforeWithoutCloseTag() {
|
||||||
|
typeTextInFile(parseKeys("dit"), "abc<caret>defg<tag>hi")
|
||||||
|
myFixture.checkResult("abcdefg<tag>hi")
|
||||||
|
}
|
||||||
|
|
||||||
|
//|d| |v_it|
|
||||||
|
fun testDeleteInnerTagBlockInOpenWithoutCloseTag() {
|
||||||
|
typeTextInFile(parseKeys("dit"), "abcdefg<ta<caret>g>hi")
|
||||||
|
myFixture.checkResult("abcdefg<tag>hi")
|
||||||
|
}
|
||||||
|
|
||||||
|
//|d| |v_it|
|
||||||
|
fun testDeleteInnerTagBlockAfterWithoutCloseTag() {
|
||||||
|
typeTextInFile(parseKeys("dit"), "abcdefg<tag>h<caret>i")
|
||||||
|
myFixture.checkResult("abcdefg<tag>hi")
|
||||||
|
}
|
||||||
|
|
||||||
|
//|d| |v_it|
|
||||||
|
fun testDeleteInnerTagBlockBeforeWrongOrder() {
|
||||||
|
typeTextInFile(parseKeys("dit"), "abc<caret>de</tag>fg<tag>hi")
|
||||||
|
myFixture.checkResult("abcde</tag>fg<tag>hi")
|
||||||
|
}
|
||||||
|
|
||||||
|
//|d| |v_it|
|
||||||
|
fun testDeleteInnerTagBlockInOpenWrongOrder() {
|
||||||
|
typeTextInFile(parseKeys("dit"), "abcde</ta<caret>g>fg<tag>hi")
|
||||||
|
myFixture.checkResult("abcde</tag>fg<tag>hi")
|
||||||
|
}
|
||||||
|
|
||||||
|
//|d| |v_it|
|
||||||
|
fun testDeleteInnerTagBlockBetweenWrongOrder() {
|
||||||
|
typeTextInFile(parseKeys("dit"), "abcde</tag>f<caret>g<tag>hi")
|
||||||
|
myFixture.checkResult("abcde</tag>fg<tag>hi")
|
||||||
|
}
|
||||||
|
|
||||||
|
//|d| |v_it|
|
||||||
|
fun testDeleteInnerTagBlockInCloseWrongOrder() {
|
||||||
|
typeTextInFile(parseKeys("dit"), "abcde</tag>fg<ta<caret>g>hi")
|
||||||
|
myFixture.checkResult("abcde</tag>fg<tag>hi")
|
||||||
|
}
|
||||||
|
|
||||||
|
//|d| |v_it|
|
||||||
|
fun testDeleteInnerTagBlockTwoTagsWrongOrder() {
|
||||||
|
typeTextInFile(parseKeys("dit"), "<foo><html>t<caret>ext</foo></html>")
|
||||||
|
myFixture.checkResult("<foo></foo></html>")
|
||||||
|
}
|
||||||
|
|
||||||
|
//|d| |v_it|
|
||||||
|
fun testDeleteInnerTagBlockTwoTagsWrongOrderInClosingTag() {
|
||||||
|
typeTextInFile(parseKeys("dit"), "<foo><html>text</foo></htm<caret>l>")
|
||||||
|
myFixture.checkResult("<foo><html></html>")
|
||||||
|
}
|
||||||
|
|
||||||
|
//|d| |v_it|
|
||||||
|
fun testDeleteInnerTagBlockAfterWrongOrder() {
|
||||||
|
typeTextInFile(parseKeys("dit"), "abcde</tag>fg<tag>h<caret>i")
|
||||||
|
myFixture.checkResult("abcde</tag>fg<tag>hi")
|
||||||
|
}
|
||||||
|
|
||||||
|
//|d| |v_it|
|
||||||
|
fun testDeleteInnerTagBlockBracketInside() {
|
||||||
|
typeTextInFile(parseKeys("dit"), "abcde<tag>f<caret><>g</tag>hi")
|
||||||
|
myFixture.checkResult("abcde<tag></tag>hi")
|
||||||
|
}
|
||||||
|
|
||||||
|
//|d| |v_it|
|
||||||
|
fun testDeleteInnerTagBlockBracketInsideString() {
|
||||||
|
typeTextInFile(parseKeys("dit"), "abcde<tag>f<caret>\"<>\"g</tag>hi")
|
||||||
|
myFixture.checkResult("abcde<tag></tag>hi")
|
||||||
|
}
|
||||||
|
|
||||||
|
//|d| |v_it|
|
||||||
|
fun testDeleteInnerTagIsCaseInsensitive() {
|
||||||
|
typeTextInFile(parseKeys("dit"), "<a> <as<caret>df> </A>")
|
||||||
|
myFixture.checkResult("<a></A>")
|
||||||
|
}
|
||||||
|
|
||||||
|
//|d| |v_it|
|
||||||
|
fun testDeleteInnerTagSlashesInAttribute() {
|
||||||
|
typeTextInFile(parseKeys("dit"), "<a href=\"http://isitchristmas.com\" class=\"button\">Bing <caret>Bing bing</a>")
|
||||||
|
myFixture.checkResult("<a href=\"http://isitchristmas.com\" class=\"button\"></a>")
|
||||||
|
}
|
||||||
|
|
||||||
|
// VIM-1090 |d| |v_it|
|
||||||
|
// Adapted from vim source file "test_textobjects.vim"
|
||||||
|
fun testDeleteInnerTagDuplicateTags() {
|
||||||
|
typeTextInFile(parseKeys("dit"), "<b>as<caret>d<i>as<b />df</i>asdf</b>")
|
||||||
|
myFixture.checkResult("<b></b>")
|
||||||
|
}
|
||||||
|
|
||||||
|
// |v_it|
|
||||||
|
fun testFileStartsWithSlash() {
|
||||||
|
configureByText("/*hello\n" +
|
||||||
|
"<caret>foo\n" +
|
||||||
|
"bar>baz\n")
|
||||||
|
typeText(parseKeys("vit"))
|
||||||
|
assertPluginError(true)
|
||||||
|
}
|
||||||
|
|
||||||
|
// |v_it|
|
||||||
|
fun testSelectInnerTagEmptyTag() {
|
||||||
|
configureByText("<a><caret></a>")
|
||||||
|
typeText(parseKeys("vit"))
|
||||||
|
assertSelection("<a></a>")
|
||||||
|
}
|
||||||
|
|
||||||
|
fun `test single character`() {
|
||||||
|
// The whole tag block is also selected if there is only a single character inside
|
||||||
|
configureByText("<a><caret>a</a>")
|
||||||
|
typeText(parseKeys("vit"))
|
||||||
|
assertSelection("<a>a</a>")
|
||||||
|
}
|
||||||
|
|
||||||
|
fun `test single character inside tag`() {
|
||||||
|
configureByText("<a<caret>></a>")
|
||||||
|
typeText(parseKeys("vit"))
|
||||||
|
assertSelection("<")
|
||||||
|
}
|
||||||
|
|
||||||
|
// VIM-1633 |v_it|
|
||||||
|
fun testNestedInTagSelection() {
|
||||||
|
configureByText("<t>Outer\n" +
|
||||||
|
" <t><caret>Inner</t>\n" +
|
||||||
|
"</t>\n")
|
||||||
|
typeText(parseKeys("vit"))
|
||||||
|
assertSelection("Inner")
|
||||||
|
}
|
||||||
|
|
||||||
|
fun `test nested tag double motion`() {
|
||||||
|
configureByText("<o>Outer\n" +
|
||||||
|
" <caret> <t></t>\n" +
|
||||||
|
"</o>\n")
|
||||||
|
typeText(parseKeys("vitit"))
|
||||||
|
assertSelection("<t></t>")
|
||||||
|
}
|
||||||
|
|
||||||
|
fun `test in inner tag double motion`() {
|
||||||
|
configureByText("<o><t><caret></t>\n</o>")
|
||||||
|
typeText(parseKeys("vitit"))
|
||||||
|
assertSelection("<o><t></t>\n</o>")
|
||||||
|
}
|
||||||
|
|
||||||
|
fun `test nested tags between tags`() {
|
||||||
|
configureByText("<t>Outer\n" +
|
||||||
|
" <t>Inner</t> <caret> <t>Inner</t>\n" +
|
||||||
|
"</t>\n")
|
||||||
|
typeText(parseKeys("vit"))
|
||||||
|
assertSelection("Outer\n" + " <t>Inner</t> <t>Inner</t>")
|
||||||
|
}
|
||||||
|
|
||||||
|
fun `test nested tags number motion`() {
|
||||||
|
configureByText("<t>Outer\n" +
|
||||||
|
" <t><caret>Inner</t>\n" +
|
||||||
|
"</t>\n")
|
||||||
|
typeText(parseKeys("v2it"))
|
||||||
|
assertSelection("Outer\n" + " <t>Inner</t>")
|
||||||
|
}
|
||||||
|
|
||||||
|
fun `test nested tags double motion`() {
|
||||||
|
configureByText("<o>Outer\n" +
|
||||||
|
" <t><caret>Inner</t>\n" +
|
||||||
|
"</o>\n")
|
||||||
|
typeText(parseKeys("vitit"))
|
||||||
|
assertSelection("<t>Inner</t>")
|
||||||
|
}
|
||||||
|
|
||||||
|
fun `test nested tags triple motion`() {
|
||||||
|
configureByText("<t>Outer\n" +
|
||||||
|
" <t><caret>Inner</t>\n" +
|
||||||
|
"</t>\n")
|
||||||
|
typeText(parseKeys("vititit"))
|
||||||
|
assertSelection("Outer\n" + " <t>Inner</t>")
|
||||||
|
}
|
||||||
|
|
||||||
|
fun `test nested tags in closing tag`() {
|
||||||
|
configureByText("<t>Outer\n" +
|
||||||
|
" <t>Inner</t>\n" +
|
||||||
|
"</<caret>t>\n")
|
||||||
|
typeText(parseKeys("vit"))
|
||||||
|
assertSelection("Outer\n" + " <t>Inner</t>")
|
||||||
|
}
|
||||||
|
|
||||||
|
fun `test nested tags in opening tag`() {
|
||||||
|
configureByText("<<caret>t>Outer\n" +
|
||||||
|
" <t>Inner</t>\n" +
|
||||||
|
"</t>\n")
|
||||||
|
typeText(parseKeys("vit"))
|
||||||
|
assertSelection("Outer\n" + " <t>Inner</t>")
|
||||||
|
}
|
||||||
|
|
||||||
|
fun `test nested tags ouside tag`() {
|
||||||
|
configureByText("<caret><t>Outer\n" +
|
||||||
|
" <t>Inner</t>\n" +
|
||||||
|
"</t>\n")
|
||||||
|
typeText(parseKeys("vit"))
|
||||||
|
assertSelection("Outer\n" + " <t>Inner</t>")
|
||||||
|
}
|
||||||
|
|
||||||
|
fun `test skip whitespace at start of line`() {
|
||||||
|
configureByText("<o>Outer\n" +
|
||||||
|
" <caret> <t></t>\n" +
|
||||||
|
"</o>\n")
|
||||||
|
typeText(parseKeys("vit"))
|
||||||
|
assertSelection("<")
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,79 @@
|
|||||||
|
package org.jetbrains.plugins.ideavim.action.motion.`object`
|
||||||
|
|
||||||
|
import com.maddyhome.idea.vim.helper.StringHelper.parseKeys
|
||||||
|
import org.jetbrains.plugins.ideavim.VimTestCase
|
||||||
|
|
||||||
|
class MotionOuterBlockParenActionTest : VimTestCase() {
|
||||||
|
// VIM-1633 |v_a)|
|
||||||
|
fun `test single letter with single parentheses`() {
|
||||||
|
configureByText("(<caret>a)")
|
||||||
|
typeText(parseKeys("va)"))
|
||||||
|
assertSelection("(a)")
|
||||||
|
}
|
||||||
|
|
||||||
|
fun `test single letter with double parentheses`() {
|
||||||
|
configureByText("((<caret>a))")
|
||||||
|
typeText(parseKeys("va)"))
|
||||||
|
assertSelection("(a)")
|
||||||
|
}
|
||||||
|
|
||||||
|
fun `test multiline outside parentheses`() {
|
||||||
|
configureByText("""(outer
|
||||||
|
|<caret>(inner))""".trimMargin())
|
||||||
|
typeText(parseKeys("va)"))
|
||||||
|
assertSelection("(inner)")
|
||||||
|
}
|
||||||
|
|
||||||
|
fun `test multiline in parentheses`() {
|
||||||
|
configureByText("""(outer
|
||||||
|
|(inner<caret>))""".trimMargin())
|
||||||
|
typeText(parseKeys("va)"))
|
||||||
|
assertSelection("(inner)")
|
||||||
|
}
|
||||||
|
|
||||||
|
fun `test multiline inside of outer parentheses`() {
|
||||||
|
configureByText("""(outer
|
||||||
|
|<caret> (inner))""".trimMargin())
|
||||||
|
typeText(parseKeys("va)"))
|
||||||
|
assertSelection("""(outer
|
||||||
|
| (inner))""".trimMargin())
|
||||||
|
}
|
||||||
|
|
||||||
|
fun `test double motion`() {
|
||||||
|
configureByText("""(outer
|
||||||
|
|<caret>(inner))""".trimMargin())
|
||||||
|
typeText(parseKeys("va)a)"))
|
||||||
|
assertSelection("""(outer
|
||||||
|
|(inner))""".trimMargin())
|
||||||
|
}
|
||||||
|
|
||||||
|
fun `test motion with count`() {
|
||||||
|
configureByText("""(outer
|
||||||
|
|<caret>(inner))""".trimMargin())
|
||||||
|
typeText(parseKeys("v2a)"))
|
||||||
|
assertSelection("""(outer
|
||||||
|
|(inner))""".trimMargin())
|
||||||
|
}
|
||||||
|
|
||||||
|
fun `test text object after motion`() {
|
||||||
|
configureByText("""(outer
|
||||||
|
|<caret>(inner))""".trimMargin())
|
||||||
|
typeText(parseKeys("vlla)"))
|
||||||
|
assertSelection("""(outer
|
||||||
|
|(inner))""".trimMargin())
|
||||||
|
}
|
||||||
|
|
||||||
|
fun `test text object after motion outside parentheses`() {
|
||||||
|
configureByText("""(outer
|
||||||
|
|(inner<caret>))""".trimMargin())
|
||||||
|
typeText(parseKeys("vlla)"))
|
||||||
|
assertSelection("(inner)")
|
||||||
|
}
|
||||||
|
|
||||||
|
// |d| |v_ab|
|
||||||
|
fun testDeleteOuterBlock() {
|
||||||
|
typeTextInFile(parseKeys("da)"),
|
||||||
|
"foo(b<caret>ar, baz);\n")
|
||||||
|
myFixture.checkResult("foo;\n")
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,254 @@
|
|||||||
|
/*
|
||||||
|
* 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 <http://www.gnu.org/licenses/>.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package org.jetbrains.plugins.ideavim.action.motion.`object`
|
||||||
|
|
||||||
|
import com.maddyhome.idea.vim.helper.StringHelper.parseKeys
|
||||||
|
import org.jetbrains.plugins.ideavim.VimTestCase
|
||||||
|
|
||||||
|
class MotionOuterBlockTagActionTest : VimTestCase() {
|
||||||
|
|
||||||
|
//|d| |v_at|
|
||||||
|
fun testDeleteOuterTagBlockBefore() {
|
||||||
|
typeTextInFile(parseKeys("dat"), "abc<caret>de<tag>fg</tag>hi")
|
||||||
|
myFixture.checkResult("abcde<tag>fg</tag>hi")
|
||||||
|
}
|
||||||
|
|
||||||
|
//|d| |v_at|
|
||||||
|
fun testDeleteOuterTagBlockInOpen() {
|
||||||
|
typeTextInFile(parseKeys("dat"), "abcde<ta<caret>g>fg</tag>hi")
|
||||||
|
myFixture.checkResult("abcdehi")
|
||||||
|
}
|
||||||
|
|
||||||
|
//|d| |v_at|
|
||||||
|
fun testDeleteOuterTagBlockInOpenWithArgs() {
|
||||||
|
typeTextInFile(parseKeys("dat"), "abcde<ta<caret>g name = \"name\">fg</tag>hi")
|
||||||
|
myFixture.checkResult("abcdehi")
|
||||||
|
}
|
||||||
|
|
||||||
|
//|d| |v_at|
|
||||||
|
fun testDeleteOuterTagBlockBetween() {
|
||||||
|
typeTextInFile(parseKeys("dat"), "abcde<tag>f<caret>g</tag>hi")
|
||||||
|
myFixture.checkResult("abcdehi")
|
||||||
|
}
|
||||||
|
|
||||||
|
//|d| |v_at|
|
||||||
|
fun testDeleteOuterTagBlockBetweenWithArgs() {
|
||||||
|
typeTextInFile(parseKeys("dat"), "abcde<tag name = \"name\">f<caret>g</tag>hi")
|
||||||
|
myFixture.checkResult("abcdehi")
|
||||||
|
}
|
||||||
|
|
||||||
|
//|d| |v_at|
|
||||||
|
fun testDeleteOuterTagBlockInClose() {
|
||||||
|
typeTextInFile(parseKeys("dat"), "abcde<tag>fg</ta<caret>g>hi")
|
||||||
|
myFixture.checkResult("abcdehi")
|
||||||
|
}
|
||||||
|
|
||||||
|
//|d| |v_at|
|
||||||
|
fun testDeleteOuterTagBlockAfter() {
|
||||||
|
typeTextInFile(parseKeys("dat"), "abcde<tag>fg</tag>h<caret>i")
|
||||||
|
myFixture.checkResult("abcde<tag>fg</tag>hi")
|
||||||
|
}
|
||||||
|
|
||||||
|
//|d| |v_at|
|
||||||
|
fun testDeleteOuterTagBlockInAlone() {
|
||||||
|
typeTextInFile(parseKeys("dat"), "abcde<ta<caret>g>fghi")
|
||||||
|
myFixture.checkResult("abcde<tag>fghi")
|
||||||
|
}
|
||||||
|
|
||||||
|
//|d| |v_at|
|
||||||
|
fun testDeleteOuterTagBlockWithoutTags() {
|
||||||
|
typeTextInFile(parseKeys("dat"), "abc<caret>de")
|
||||||
|
myFixture.checkResult("abcde")
|
||||||
|
}
|
||||||
|
|
||||||
|
//|d| |v_at|
|
||||||
|
fun testDeleteOuterTagBlockBeforeWithoutOpenTag() {
|
||||||
|
typeTextInFile(parseKeys("dat"), "abc<caret>defg</tag>hi")
|
||||||
|
myFixture.checkResult("abcdefg</tag>hi")
|
||||||
|
}
|
||||||
|
|
||||||
|
//|d| |v_at|
|
||||||
|
fun testDeleteOuterTagBlockInCloseWithoutOpenTag() {
|
||||||
|
typeTextInFile(parseKeys("dat"), "abcdefg</ta<caret>g>hi")
|
||||||
|
myFixture.checkResult("abcdefg</tag>hi")
|
||||||
|
}
|
||||||
|
|
||||||
|
//|d| |v_at|
|
||||||
|
fun testDeleteOuterTagBlockAfterWithoutOpenTag() {
|
||||||
|
typeTextInFile(parseKeys("dat"), "abcdefg</tag>h<caret>i")
|
||||||
|
myFixture.checkResult("abcdefg</tag>hi")
|
||||||
|
}
|
||||||
|
|
||||||
|
//|d| |v_at|
|
||||||
|
fun testDeleteOuterTagBlockBeforeWithoutCloseTag() {
|
||||||
|
typeTextInFile(parseKeys("dat"), "abc<caret>defg<tag>hi")
|
||||||
|
myFixture.checkResult("abcdefg<tag>hi")
|
||||||
|
}
|
||||||
|
|
||||||
|
//|d| |v_at|
|
||||||
|
fun testDeleteOuterTagBlockInOpenWithoutCloseTag() {
|
||||||
|
typeTextInFile(parseKeys("dat"), "abcdefg<ta<caret>g>hi")
|
||||||
|
myFixture.checkResult("abcdefg<tag>hi")
|
||||||
|
}
|
||||||
|
|
||||||
|
//|d| |v_at|
|
||||||
|
fun testDeleteOuterTagBlockAfterWithoutCloseTag() {
|
||||||
|
typeTextInFile(parseKeys("dat"), "abcdefg<tag>h<caret>i")
|
||||||
|
myFixture.checkResult("abcdefg<tag>hi")
|
||||||
|
}
|
||||||
|
|
||||||
|
//|d| |v_at|
|
||||||
|
fun testDeleteOuterTagBlockBeforeWrongOrder() {
|
||||||
|
typeTextInFile(parseKeys("dat"), "abc<caret>de</tag>fg<tag>hi")
|
||||||
|
myFixture.checkResult("abcde</tag>fg<tag>hi")
|
||||||
|
}
|
||||||
|
|
||||||
|
//|d| |v_at|
|
||||||
|
fun testDeleteOuterTagBlockInOpenWrongOrder() {
|
||||||
|
typeTextInFile(parseKeys("dat"), "abcde</ta<caret>g>fg<tag>hi")
|
||||||
|
myFixture.checkResult("abcde</tag>fg<tag>hi")
|
||||||
|
}
|
||||||
|
|
||||||
|
//|d| |v_at|
|
||||||
|
fun testDeleteOuterTagBlockBetweenWrongOrder() {
|
||||||
|
typeTextInFile(parseKeys("dat"), "abcde</tag>f<caret>g<tag>hi")
|
||||||
|
myFixture.checkResult("abcde</tag>fg<tag>hi")
|
||||||
|
}
|
||||||
|
|
||||||
|
//|d| |v_at|
|
||||||
|
fun testDeleteOuterTagBlockInCloseWrongOrder() {
|
||||||
|
typeTextInFile(parseKeys("dat"), "abcde</tag>fg<ta<caret>g>hi")
|
||||||
|
myFixture.checkResult("abcde</tag>fg<tag>hi")
|
||||||
|
}
|
||||||
|
|
||||||
|
//|d| |v_at|
|
||||||
|
fun testDeleteOuterTagBlockAfterWrongOrder() {
|
||||||
|
typeTextInFile(parseKeys("dat"), "abcde</tag>fg<tag>h<caret>i")
|
||||||
|
myFixture.checkResult("abcde</tag>fg<tag>hi")
|
||||||
|
}
|
||||||
|
|
||||||
|
//|d| |v_it|
|
||||||
|
fun testDeleteInnerTagAngleBrackets() {
|
||||||
|
typeTextInFile(parseKeys("dit"), "<div <caret>hello=\"d > hsj < akl\"></div>")
|
||||||
|
myFixture.checkResult("<div hello=\"d ></div>")
|
||||||
|
}
|
||||||
|
|
||||||
|
// VIM-1090 |d| |v_at|
|
||||||
|
fun testDeleteOuterTagDuplicateTags() {
|
||||||
|
typeTextInFile(parseKeys("dat"), "<a><a></a></a<caret>>")
|
||||||
|
myFixture.checkResult("")
|
||||||
|
}
|
||||||
|
|
||||||
|
// |v_it| |v_at|
|
||||||
|
fun testTagSelectionSkipsWhitespaceAtStartOfLine() {
|
||||||
|
// Also skip tabs
|
||||||
|
configureByText("<o>Outer\n" +
|
||||||
|
" <caret> \t <t>Inner</t>\n" +
|
||||||
|
"</o>\n")
|
||||||
|
typeText(parseKeys("vat"))
|
||||||
|
assertSelection("<t>Inner</t>")
|
||||||
|
}
|
||||||
|
|
||||||
|
fun `test skip new line`() {
|
||||||
|
// Newline must not be skipped
|
||||||
|
configureByText("<caret>\n" + " <t>asdf</t>")
|
||||||
|
typeText(parseKeys("vat"))
|
||||||
|
assertSelection(null)
|
||||||
|
}
|
||||||
|
|
||||||
|
fun `test whitespace skip`() {
|
||||||
|
// Whitespace is only skipped if there is nothing else at the start of the line
|
||||||
|
configureByText("<o>Outer\n" +
|
||||||
|
"a <caret> <t>Inner</t>\n" +
|
||||||
|
"</o>\n")
|
||||||
|
typeText(parseKeys("vat"))
|
||||||
|
assertSelection("<o>Outer\n" +
|
||||||
|
"a <t>Inner</t>\n" +
|
||||||
|
"</o>")
|
||||||
|
}
|
||||||
|
|
||||||
|
// |v_at|
|
||||||
|
fun testNestedTagSelection() {
|
||||||
|
configureByText("<t>Outer\n" +
|
||||||
|
" <t><caret>Inner</t>\n" +
|
||||||
|
"</t>\n")
|
||||||
|
typeText(parseKeys("vat"))
|
||||||
|
assertSelection("<t>Inner</t>")
|
||||||
|
}
|
||||||
|
|
||||||
|
fun `test nested tags between tags`() {
|
||||||
|
configureByText("<t>Outer\n" +
|
||||||
|
" <t>Inner</t> <caret> <t>Inner</t>\n" +
|
||||||
|
"</t>\n")
|
||||||
|
typeText(parseKeys("vat"))
|
||||||
|
assertSelection("<t>Outer\n" +
|
||||||
|
" <t>Inner</t> <t>Inner</t>\n" +
|
||||||
|
"</t>")
|
||||||
|
}
|
||||||
|
|
||||||
|
fun `test nested tags double motion`() {
|
||||||
|
configureByText("<t>Outer\n" +
|
||||||
|
" <t><caret>Inner</t>\n" +
|
||||||
|
"</t>\n")
|
||||||
|
typeText(parseKeys("vatat"))
|
||||||
|
assertSelection("<t>Outer\n" +
|
||||||
|
" <t>Inner</t>\n" +
|
||||||
|
"</t>")
|
||||||
|
}
|
||||||
|
|
||||||
|
fun `test nested tags number motion`() {
|
||||||
|
configureByText("<t>Outer\n" +
|
||||||
|
" <t><caret>Inner</t>\n" +
|
||||||
|
"</t>\n")
|
||||||
|
typeText(parseKeys("v2at"))
|
||||||
|
assertSelection("<t>Outer\n" +
|
||||||
|
" <t>Inner</t>\n" +
|
||||||
|
"</t>")
|
||||||
|
}
|
||||||
|
|
||||||
|
fun `test nested tags on outer`() {
|
||||||
|
configureByText("<t>Outer\n" +
|
||||||
|
" <t>Inner</t>\n" +
|
||||||
|
"</<caret>t>\n")
|
||||||
|
typeText(parseKeys("vat"))
|
||||||
|
assertSelection("<t>Outer\n" +
|
||||||
|
" <t>Inner</t>\n" +
|
||||||
|
"</t>")
|
||||||
|
}
|
||||||
|
|
||||||
|
fun `test nested tags on outer start`() {
|
||||||
|
configureByText("<<caret>t>Outer\n" +
|
||||||
|
" <t>Inner</t>\n" +
|
||||||
|
"</t>\n")
|
||||||
|
typeText(parseKeys("vat"))
|
||||||
|
assertSelection("<t>Outer\n" +
|
||||||
|
" <t>Inner</t>\n" +
|
||||||
|
"</t>")
|
||||||
|
}
|
||||||
|
|
||||||
|
fun `test nested tags outside outer`() {
|
||||||
|
configureByText("<caret><t>Outer\n" +
|
||||||
|
" <t>Inner</t>\n" +
|
||||||
|
"</t>\n")
|
||||||
|
typeText(parseKeys("vat"))
|
||||||
|
assertSelection("<t>Outer\n" +
|
||||||
|
" <t>Inner</t>\n" +
|
||||||
|
"</t>")
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,80 @@
|
|||||||
|
/*
|
||||||
|
* 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 <http://www.gnu.org/licenses/>.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package org.jetbrains.plugins.ideavim.ex;
|
||||||
|
|
||||||
|
import com.intellij.openapi.actionSystem.ActionManager;
|
||||||
|
import com.maddyhome.idea.vim.ex.ExOutputModel;
|
||||||
|
import org.jetbrains.plugins.ideavim.VimTestCase;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @author Naoto Ikeno
|
||||||
|
*/
|
||||||
|
public class ActionListCommandTest extends VimTestCase {
|
||||||
|
public void testListAllActions() {
|
||||||
|
configureByText("\n");
|
||||||
|
typeText(commandToKeys("actionlist"));
|
||||||
|
|
||||||
|
String output = ExOutputModel.getInstance(myFixture.getEditor()).getText();
|
||||||
|
assertNotNull(output);
|
||||||
|
|
||||||
|
// Header line
|
||||||
|
String[] displayedLines = output.split("\n");
|
||||||
|
assertEquals("--- Actions ---", displayedLines[0]);
|
||||||
|
|
||||||
|
// Action lines
|
||||||
|
int displayedActionNum = displayedLines.length - 1;
|
||||||
|
String[] actionIds = ActionManager.getInstance().getActionIds("");
|
||||||
|
assertEquals(displayedActionNum, actionIds.length);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void testSearchByActionName() {
|
||||||
|
configureByText("\n");
|
||||||
|
typeText(commandToKeys("actionlist quickimpl"));
|
||||||
|
|
||||||
|
String[] displayedLines = parseActionListOutput();
|
||||||
|
for (int i = 0; i < displayedLines.length; i++) {
|
||||||
|
String line = displayedLines[i];
|
||||||
|
if (i == 0) {
|
||||||
|
assertEquals("--- Actions ---", line);
|
||||||
|
}else {
|
||||||
|
assertTrue(line.toLowerCase().contains("quickimpl"));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public void testSearchByAssignedShortcutKey() {
|
||||||
|
configureByText("\n");
|
||||||
|
typeText(commandToKeys("actionlist <M-S-"));
|
||||||
|
|
||||||
|
String[] displayedLines = parseActionListOutput();
|
||||||
|
for (int i = 0; i < displayedLines.length; i++) {
|
||||||
|
String line = displayedLines[i];
|
||||||
|
if (i == 0) {
|
||||||
|
assertEquals("--- Actions ---", line);
|
||||||
|
}else {
|
||||||
|
assertTrue(line.toLowerCase().contains("<m-s-"));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private String[] parseActionListOutput() {
|
||||||
|
String output = ExOutputModel.getInstance(myFixture.getEditor()).getText();
|
||||||
|
return output == null ? new String[]{} : output.split("\n");
|
||||||
|
}
|
||||||
|
}
|
578
test/org/jetbrains/plugins/ideavim/ex/ExEntryTest.kt
Normal file
578
test/org/jetbrains/plugins/ideavim/ex/ExEntryTest.kt
Normal file
@ -0,0 +1,578 @@
|
|||||||
|
package org.jetbrains.plugins.ideavim.ex
|
||||||
|
|
||||||
|
import com.maddyhome.idea.vim.VimPlugin
|
||||||
|
import com.maddyhome.idea.vim.helper.StringHelper
|
||||||
|
import com.maddyhome.idea.vim.option.Options
|
||||||
|
import com.maddyhome.idea.vim.ui.ExDocument
|
||||||
|
import com.maddyhome.idea.vim.ui.ExEntryPanel
|
||||||
|
import org.jetbrains.plugins.ideavim.VimTestCase
|
||||||
|
import java.awt.event.KeyEvent
|
||||||
|
import javax.swing.KeyStroke
|
||||||
|
|
||||||
|
class ExEntryTest: VimTestCase() {
|
||||||
|
override fun setUp() {
|
||||||
|
super.setUp()
|
||||||
|
configureByText("\n")
|
||||||
|
}
|
||||||
|
|
||||||
|
fun `test cancel entry`() {
|
||||||
|
val options = Options.getInstance()
|
||||||
|
|
||||||
|
assertFalse(options.isSet(Options.INCREMENTAL_SEARCH))
|
||||||
|
typeExInput(":set incsearch<Esc>")
|
||||||
|
assertFalse(options.isSet(Options.INCREMENTAL_SEARCH))
|
||||||
|
assertIsDeactivated()
|
||||||
|
|
||||||
|
deactivateExEntry()
|
||||||
|
|
||||||
|
assertFalse(options.isSet(Options.INCREMENTAL_SEARCH))
|
||||||
|
typeExInput(":set incsearch<C-[>")
|
||||||
|
assertFalse(options.isSet(Options.INCREMENTAL_SEARCH))
|
||||||
|
assertIsDeactivated()
|
||||||
|
|
||||||
|
deactivateExEntry()
|
||||||
|
|
||||||
|
assertFalse(options.isSet(Options.INCREMENTAL_SEARCH))
|
||||||
|
typeExInput(":set incsearch<C-C>")
|
||||||
|
assertFalse(options.isSet(Options.INCREMENTAL_SEARCH))
|
||||||
|
assertIsDeactivated()
|
||||||
|
}
|
||||||
|
|
||||||
|
fun `test complete entry`() {
|
||||||
|
val options = Options.getInstance()
|
||||||
|
|
||||||
|
assertFalse(options.isSet(Options.INCREMENTAL_SEARCH))
|
||||||
|
typeExInput(":set incsearch<Enter>")
|
||||||
|
assertTrue(options.isSet(Options.INCREMENTAL_SEARCH))
|
||||||
|
assertIsDeactivated()
|
||||||
|
|
||||||
|
deactivateExEntry()
|
||||||
|
options.resetAllOptions()
|
||||||
|
|
||||||
|
assertFalse(options.isSet(Options.INCREMENTAL_SEARCH))
|
||||||
|
typeExInput(":set incsearch<C-J>")
|
||||||
|
assertTrue(options.isSet(Options.INCREMENTAL_SEARCH))
|
||||||
|
assertIsDeactivated()
|
||||||
|
|
||||||
|
deactivateExEntry()
|
||||||
|
options.resetAllOptions()
|
||||||
|
|
||||||
|
assertFalse(Options.getInstance().isSet(Options.INCREMENTAL_SEARCH))
|
||||||
|
typeExInput(":set incsearch<C-M>")
|
||||||
|
assertTrue(Options.getInstance().isSet(Options.INCREMENTAL_SEARCH))
|
||||||
|
assertIsDeactivated()
|
||||||
|
}
|
||||||
|
|
||||||
|
fun `test caret shape`() {
|
||||||
|
// Show block at end of input (normal)
|
||||||
|
// Show vertical bar in insert mode
|
||||||
|
// Show horizontal bar in replace mode
|
||||||
|
typeExInput(":")
|
||||||
|
assertEquals("BLOCK 100", exEntryPanel.entry.caretShape)
|
||||||
|
|
||||||
|
typeText("set")
|
||||||
|
assertEquals("BLOCK 100", exEntryPanel.entry.caretShape)
|
||||||
|
|
||||||
|
deactivateExEntry()
|
||||||
|
typeExInput(":set<Home>")
|
||||||
|
assertEquals("VER 25", exEntryPanel.entry.caretShape)
|
||||||
|
|
||||||
|
deactivateExEntry()
|
||||||
|
typeExInput(":set<Home><Insert>")
|
||||||
|
assertEquals("HOR 20", exEntryPanel.entry.caretShape)
|
||||||
|
|
||||||
|
deactivateExEntry()
|
||||||
|
typeExInput(":set<Home><Insert><Insert>")
|
||||||
|
assertEquals("VER 25", exEntryPanel.entry.caretShape)
|
||||||
|
}
|
||||||
|
|
||||||
|
fun `test move caret to beginning of line`() {
|
||||||
|
typeExInput(":set incsearch<C-B>")
|
||||||
|
assertExOffset(0)
|
||||||
|
|
||||||
|
deactivateExEntry()
|
||||||
|
|
||||||
|
typeExInput(":set incsearch<Home>")
|
||||||
|
assertExOffset(0)
|
||||||
|
}
|
||||||
|
|
||||||
|
fun `test move caret to end of line`() {
|
||||||
|
typeExInput(":set incsearch<C-B>")
|
||||||
|
assertExOffset(0)
|
||||||
|
|
||||||
|
typeText("<C-E>")
|
||||||
|
assertExOffset(13)
|
||||||
|
|
||||||
|
deactivateExEntry()
|
||||||
|
typeExInput(":set incsearch<C-B>")
|
||||||
|
assertExOffset(0)
|
||||||
|
|
||||||
|
typeText("<End>")
|
||||||
|
assertExOffset(13)
|
||||||
|
}
|
||||||
|
|
||||||
|
fun `test delete character in front of caret`() {
|
||||||
|
typeExInput(":set incsearch<BS>")
|
||||||
|
assertExText("set incsearc")
|
||||||
|
|
||||||
|
typeText("<C-H>")
|
||||||
|
assertExText("set incsear")
|
||||||
|
}
|
||||||
|
|
||||||
|
fun `test delete character in front of caret cancels entry`() {
|
||||||
|
typeExInput(":<BS>")
|
||||||
|
assertIsDeactivated()
|
||||||
|
|
||||||
|
deactivateExEntry()
|
||||||
|
|
||||||
|
typeExInput(":set<BS><BS><BS><BS>")
|
||||||
|
assertIsDeactivated()
|
||||||
|
|
||||||
|
deactivateExEntry()
|
||||||
|
|
||||||
|
typeExInput(":<C-H>")
|
||||||
|
assertIsDeactivated()
|
||||||
|
|
||||||
|
deactivateExEntry()
|
||||||
|
|
||||||
|
// TODO: Vim behaviour is to NOT deactivate if there is still text
|
||||||
|
typeExInput(":set<C-B>")
|
||||||
|
assertExOffset(0)
|
||||||
|
typeText("<BS>")
|
||||||
|
assertIsDeactivated()
|
||||||
|
}
|
||||||
|
|
||||||
|
fun `test delete character under caret`() {
|
||||||
|
typeExInput(":set<Left>")
|
||||||
|
typeText("<Del>")
|
||||||
|
assertExText("se")
|
||||||
|
}
|
||||||
|
|
||||||
|
fun `test delete word before caret`() {
|
||||||
|
typeExInput(":set incsearch<C-W>")
|
||||||
|
assertExText("set ")
|
||||||
|
|
||||||
|
deactivateExEntry()
|
||||||
|
|
||||||
|
typeExInput(":set incsearch<Left><Left><Left>")
|
||||||
|
typeText("<C-W>")
|
||||||
|
assertExText("set rch")
|
||||||
|
}
|
||||||
|
|
||||||
|
fun `test delete to start of line`() {
|
||||||
|
typeExInput(":set incsearch<C-U>")
|
||||||
|
assertExText("")
|
||||||
|
|
||||||
|
deactivateExEntry()
|
||||||
|
|
||||||
|
typeExInput(":set incsearch<Left><Left><Left><C-U>")
|
||||||
|
assertExText("rch")
|
||||||
|
}
|
||||||
|
|
||||||
|
fun `test command history`() {
|
||||||
|
typeExInput(":set digraph<CR>")
|
||||||
|
typeExInput(":digraph<CR>")
|
||||||
|
typeExInput(":set incsearch<CR>")
|
||||||
|
|
||||||
|
typeExInput(":<Up>")
|
||||||
|
assertExText("set incsearch")
|
||||||
|
typeText("<Up>")
|
||||||
|
assertExText("digraph")
|
||||||
|
typeText("<Up>")
|
||||||
|
assertExText("set digraph")
|
||||||
|
|
||||||
|
deactivateExEntry()
|
||||||
|
|
||||||
|
// TODO: Vim behaviour reorders the history even when cancelling history
|
||||||
|
// typeExInput(":<Up>")
|
||||||
|
// assertExText("set digraph")
|
||||||
|
// typeText("<Up>")
|
||||||
|
// assertExText("set incsearch")
|
||||||
|
|
||||||
|
typeExInput(":<S-Up>")
|
||||||
|
assertExText("set incsearch")
|
||||||
|
typeText("<Up>")
|
||||||
|
assertExText("digraph")
|
||||||
|
typeText("<Up>")
|
||||||
|
assertExText("set digraph")
|
||||||
|
|
||||||
|
deactivateExEntry()
|
||||||
|
|
||||||
|
typeExInput(":<PageUp>")
|
||||||
|
assertExText("set incsearch")
|
||||||
|
typeText("<PageUp>")
|
||||||
|
assertExText("digraph")
|
||||||
|
typeText("<PageUp>")
|
||||||
|
assertExText("set digraph")
|
||||||
|
}
|
||||||
|
|
||||||
|
fun `test matching command history`() {
|
||||||
|
typeExInput(":set digraph<CR>")
|
||||||
|
typeExInput(":digraph<CR>")
|
||||||
|
typeExInput(":set incsearch<CR>")
|
||||||
|
|
||||||
|
typeExInput(":set<Up>")
|
||||||
|
assertExText("set incsearch")
|
||||||
|
typeText("<Up>")
|
||||||
|
assertExText("set digraph")
|
||||||
|
|
||||||
|
deactivateExEntry()
|
||||||
|
|
||||||
|
typeExInput(":set<S-Up>")
|
||||||
|
assertExText("set incsearch")
|
||||||
|
typeText("<S-Up>")
|
||||||
|
assertExText("digraph")
|
||||||
|
typeText("<S-Up>")
|
||||||
|
assertExText("set digraph")
|
||||||
|
|
||||||
|
deactivateExEntry()
|
||||||
|
|
||||||
|
typeExInput(":set<PageUp>")
|
||||||
|
assertExText("set incsearch")
|
||||||
|
typeText("<PageUp>")
|
||||||
|
assertExText("digraph")
|
||||||
|
typeText("<PageUp>")
|
||||||
|
assertExText("set digraph")
|
||||||
|
}
|
||||||
|
|
||||||
|
fun `test search history`() {
|
||||||
|
typeExInput("/something cool<CR>")
|
||||||
|
typeExInput("/not cool<CR>")
|
||||||
|
typeExInput("/so cool<CR>")
|
||||||
|
|
||||||
|
typeExInput("/<Up>")
|
||||||
|
assertExText("so cool")
|
||||||
|
typeText("<Up>")
|
||||||
|
assertExText("not cool")
|
||||||
|
typeText("<Up>")
|
||||||
|
assertExText("something cool")
|
||||||
|
|
||||||
|
deactivateExEntry()
|
||||||
|
|
||||||
|
typeExInput("/<S-Up>")
|
||||||
|
assertExText("so cool")
|
||||||
|
typeText("<S-Up>")
|
||||||
|
assertExText("not cool")
|
||||||
|
typeText("<S-Up>")
|
||||||
|
assertExText("something cool")
|
||||||
|
|
||||||
|
deactivateExEntry()
|
||||||
|
|
||||||
|
typeExInput("/<PageUp>")
|
||||||
|
assertExText("so cool")
|
||||||
|
typeText("<PageUp>")
|
||||||
|
assertExText("not cool")
|
||||||
|
typeText("<PageUp>")
|
||||||
|
assertExText("something cool")
|
||||||
|
}
|
||||||
|
|
||||||
|
fun `test matching search history`() {
|
||||||
|
typeExInput("/something cool<CR>")
|
||||||
|
typeExInput("/not cool<CR>")
|
||||||
|
typeExInput("/so cool<CR>")
|
||||||
|
|
||||||
|
typeExInput("/so<Up>")
|
||||||
|
assertExText("so cool")
|
||||||
|
typeText("<Up>")
|
||||||
|
assertExText("something cool")
|
||||||
|
|
||||||
|
deactivateExEntry()
|
||||||
|
|
||||||
|
// TODO: Vim behaviour reorders the history even when cancelling history
|
||||||
|
// typeExInput(":<Up>")
|
||||||
|
// assertEquals("set digraph", exEntryPanel.text)
|
||||||
|
// typeText("<Up>")
|
||||||
|
// assertEquals("set incsearch", exEntryPanel.text)
|
||||||
|
|
||||||
|
typeExInput("/so<S-Up>")
|
||||||
|
assertExText("so cool")
|
||||||
|
typeText("<S-Up>")
|
||||||
|
assertExText("not cool")
|
||||||
|
typeText("<S-Up>")
|
||||||
|
assertExText("something cool")
|
||||||
|
|
||||||
|
deactivateExEntry()
|
||||||
|
|
||||||
|
typeExInput("/so<PageUp>")
|
||||||
|
assertExText("so cool")
|
||||||
|
typeText("<PageUp>")
|
||||||
|
assertExText("not cool")
|
||||||
|
typeText("<PageUp>")
|
||||||
|
assertExText("something cool")
|
||||||
|
}
|
||||||
|
|
||||||
|
fun `test toggle insert replace`() {
|
||||||
|
val exDocument = exEntryPanel.entry.document as ExDocument
|
||||||
|
assertFalse(exDocument.isOverwrite)
|
||||||
|
typeExInput(":set<C-B>digraph")
|
||||||
|
assertExText("digraphset")
|
||||||
|
|
||||||
|
deactivateExEntry()
|
||||||
|
|
||||||
|
typeExInput(":set<C-B><Insert>digraph")
|
||||||
|
assertTrue(exDocument.isOverwrite)
|
||||||
|
assertExText("digraph")
|
||||||
|
|
||||||
|
typeText("<Insert><C-B>set ")
|
||||||
|
assertFalse(exDocument.isOverwrite)
|
||||||
|
assertExText("set digraph")
|
||||||
|
}
|
||||||
|
|
||||||
|
fun `test move caret one WORD left`() {
|
||||||
|
typeExInput(":set incsearch<S-Left>")
|
||||||
|
assertExOffset(4)
|
||||||
|
typeText("<S-Left>")
|
||||||
|
assertExOffset(0)
|
||||||
|
|
||||||
|
deactivateExEntry()
|
||||||
|
|
||||||
|
typeExInput(":set incsearch<C-Left>")
|
||||||
|
assertExOffset(4)
|
||||||
|
typeText("<C-Left>")
|
||||||
|
assertExOffset(0)
|
||||||
|
}
|
||||||
|
|
||||||
|
fun `test move caret one WORD right`() {
|
||||||
|
typeExInput(":set incsearch")
|
||||||
|
caret.dot = 0
|
||||||
|
typeText("<S-Right>")
|
||||||
|
// TODO: Vim moves caret to "set| ", while we move it to "set |"
|
||||||
|
assertExOffset(4)
|
||||||
|
|
||||||
|
typeText("<S-Right>")
|
||||||
|
assertExOffset(13)
|
||||||
|
|
||||||
|
caret.dot = 0
|
||||||
|
typeText("<C-Right>")
|
||||||
|
// TODO: Vim moves caret to "set| ", while we move it to "set |"
|
||||||
|
assertExOffset(4)
|
||||||
|
|
||||||
|
typeText("<C-Right>")
|
||||||
|
assertExOffset(13)
|
||||||
|
}
|
||||||
|
|
||||||
|
fun `test insert digraph`() {
|
||||||
|
typeExInput(":<C-K>OK")
|
||||||
|
assertExText("✓")
|
||||||
|
assertExOffset(1)
|
||||||
|
|
||||||
|
deactivateExEntry()
|
||||||
|
|
||||||
|
typeExInput(":set<Home><C-K>OK")
|
||||||
|
assertExText("✓set")
|
||||||
|
assertExOffset(1)
|
||||||
|
|
||||||
|
deactivateExEntry()
|
||||||
|
|
||||||
|
typeExInput(":set<Home><Insert><C-K>OK")
|
||||||
|
assertExText("✓et")
|
||||||
|
assertExOffset(1)
|
||||||
|
}
|
||||||
|
|
||||||
|
fun `test prompt while inserting digraph`() {
|
||||||
|
typeExInput(":<C-K>")
|
||||||
|
assertExText("?")
|
||||||
|
assertExOffset(0)
|
||||||
|
|
||||||
|
deactivateExEntry()
|
||||||
|
|
||||||
|
typeExInput(":<C-K>O")
|
||||||
|
assertExText("O")
|
||||||
|
assertExOffset(0)
|
||||||
|
|
||||||
|
deactivateExEntry()
|
||||||
|
|
||||||
|
typeExInput(":set<Home><C-K>")
|
||||||
|
assertExText("?set")
|
||||||
|
assertExOffset(0)
|
||||||
|
|
||||||
|
deactivateExEntry()
|
||||||
|
|
||||||
|
typeExInput(":set<Home><C-K>O")
|
||||||
|
assertExText("Oset")
|
||||||
|
assertExOffset(0)
|
||||||
|
}
|
||||||
|
|
||||||
|
fun `test escape cancels digraph`() {
|
||||||
|
typeExInput(":<C-K><Esc>OK")
|
||||||
|
assertIsActive()
|
||||||
|
assertExText("OK")
|
||||||
|
|
||||||
|
deactivateExEntry()
|
||||||
|
|
||||||
|
// Note that the docs state that hitting escape stops digraph entry and cancels command line mode. In practice,
|
||||||
|
// this isn't true - digraph entry is stopped, but command line mode continues
|
||||||
|
typeExInput(":<C-K>O<Esc>K")
|
||||||
|
assertIsActive()
|
||||||
|
assertEquals("K", exEntryPanel.text)
|
||||||
|
|
||||||
|
deactivateExEntry()
|
||||||
|
}
|
||||||
|
|
||||||
|
// TODO: Test inserting control characters, if/when supported
|
||||||
|
|
||||||
|
fun `test insert literal character`() {
|
||||||
|
typeExInput(":<C-V>123<C-V>080")
|
||||||
|
assertExText("{P")
|
||||||
|
|
||||||
|
deactivateExEntry()
|
||||||
|
|
||||||
|
typeExInput(":<C-V>o123")
|
||||||
|
assertExText("S")
|
||||||
|
|
||||||
|
deactivateExEntry()
|
||||||
|
|
||||||
|
typeExInput(":<C-V>u00A9")
|
||||||
|
assertExText("©")
|
||||||
|
|
||||||
|
deactivateExEntry()
|
||||||
|
|
||||||
|
typeExInput(":<C-Q>123<C-Q>080")
|
||||||
|
assertExText("{P")
|
||||||
|
|
||||||
|
deactivateExEntry()
|
||||||
|
|
||||||
|
typeExInput(":<C-Q>o123")
|
||||||
|
assertExText("S")
|
||||||
|
|
||||||
|
deactivateExEntry()
|
||||||
|
|
||||||
|
typeExInput(":<C-Q>u00a9")
|
||||||
|
assertExText("©")
|
||||||
|
|
||||||
|
deactivateExEntry()
|
||||||
|
|
||||||
|
typeExInput(":set<Home><C-V>u00A9")
|
||||||
|
assertExText("©set")
|
||||||
|
assertExOffset(1)
|
||||||
|
}
|
||||||
|
|
||||||
|
fun `test prompt while inserting literal character`() {
|
||||||
|
typeExInput(":<C-V>")
|
||||||
|
assertExText("^")
|
||||||
|
assertExOffset(0)
|
||||||
|
|
||||||
|
deactivateExEntry()
|
||||||
|
|
||||||
|
typeExInput(":<C-V>o")
|
||||||
|
assertExText("^")
|
||||||
|
assertExOffset(0)
|
||||||
|
|
||||||
|
typeText("1")
|
||||||
|
assertExText("^")
|
||||||
|
assertExOffset(0)
|
||||||
|
|
||||||
|
typeText("2")
|
||||||
|
assertExText("^")
|
||||||
|
assertExOffset(0)
|
||||||
|
|
||||||
|
typeText("3")
|
||||||
|
assertExText("S")
|
||||||
|
assertExOffset(1)
|
||||||
|
}
|
||||||
|
|
||||||
|
fun `test insert register`() {
|
||||||
|
VimPlugin.getRegister().setKeys('c', StringHelper.parseKeys("hello world"))
|
||||||
|
VimPlugin.getRegister().setKeys('5', StringHelper.parseKeys("greetings programs"))
|
||||||
|
|
||||||
|
typeExInput(":<C-R>c")
|
||||||
|
assertExText("hello world")
|
||||||
|
|
||||||
|
deactivateExEntry()
|
||||||
|
|
||||||
|
typeExInput(":<C-R>5")
|
||||||
|
assertExText("greetings programs")
|
||||||
|
|
||||||
|
deactivateExEntry()
|
||||||
|
|
||||||
|
typeExInput(":set<Home><C-R>c")
|
||||||
|
assertExText("hello worldset")
|
||||||
|
assertExOffset(11) // Just before 'set'
|
||||||
|
|
||||||
|
// TODO: Test caret feedback
|
||||||
|
// Vim shows " after hitting <C-R>
|
||||||
|
}
|
||||||
|
|
||||||
|
fun `test insert multi-line register`() {
|
||||||
|
// parseKeys parses <CR> in a way that Register#getText doesn't like
|
||||||
|
val keys = mutableListOf<KeyStroke>()
|
||||||
|
keys.addAll(StringHelper.parseKeys("hello"))
|
||||||
|
keys.add(KeyStroke.getKeyStroke('\n'))
|
||||||
|
keys.addAll(StringHelper.parseKeys("world"))
|
||||||
|
VimPlugin.getRegister().setKeys('c', keys)
|
||||||
|
|
||||||
|
typeExInput(":<C-R>c")
|
||||||
|
assertExText("hello world")
|
||||||
|
}
|
||||||
|
|
||||||
|
// TODO: Test other special registers, if/when supported
|
||||||
|
// E.g. '.' '%' '#', etc.
|
||||||
|
|
||||||
|
fun `test insert last command`() {
|
||||||
|
typeExInput(":set incsearch<CR>")
|
||||||
|
typeExInput(":<C-R>:")
|
||||||
|
assertExText("set incsearch")
|
||||||
|
}
|
||||||
|
|
||||||
|
fun `test insert last search command`() {
|
||||||
|
typeExInput("/hello<CR>")
|
||||||
|
typeExInput(":<C-R>/")
|
||||||
|
assertExText("hello")
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun typeExInput(text: String) {
|
||||||
|
assertTrue("Ex command must start with ':', '/' or '?'",
|
||||||
|
text.startsWith(":") || text.startsWith('/') || text.startsWith('?'))
|
||||||
|
|
||||||
|
val keys = mutableListOf<KeyStroke>()
|
||||||
|
StringHelper.parseKeys(text).forEach {
|
||||||
|
// <Left> doesn't work correctly in tests. The DefaultEditorKit.NextVisualPositionAction action is correctly
|
||||||
|
// called, but fails to move the caret correctly because the text component has never been painted
|
||||||
|
if (it.keyCode == KeyEvent.VK_LEFT && it.modifiers == 0) {
|
||||||
|
if (keys.count() > 0) {
|
||||||
|
typeText(keys)
|
||||||
|
keys.clear()
|
||||||
|
}
|
||||||
|
|
||||||
|
exEntryPanel.entry.caret.dot--
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
keys.add(it)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (keys.count() > 0)
|
||||||
|
typeText(keys)
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun typeText(text: String) {
|
||||||
|
typeText(StringHelper.parseKeys(text))
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun deactivateExEntry() {
|
||||||
|
// We don't need to reset text, that's handled by #active
|
||||||
|
if (exEntryPanel.isActive)
|
||||||
|
typeText("<C-C>")
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun assertExText(expected: String) {
|
||||||
|
assertEquals(expected, exEntryPanel.text)
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun assertIsActive() {
|
||||||
|
assertTrue(exEntryPanel.isActive)
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun assertIsDeactivated() {
|
||||||
|
assertFalse(exEntryPanel.isActive)
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun assertExOffset(expected: Int) {
|
||||||
|
assertEquals(expected, caret.dot)
|
||||||
|
}
|
||||||
|
|
||||||
|
private val exEntryPanel
|
||||||
|
get() = ExEntryPanel.getInstance()
|
||||||
|
|
||||||
|
private val caret
|
||||||
|
get() = exEntryPanel.entry.caret
|
||||||
|
}
|
@ -0,0 +1,56 @@
|
|||||||
|
/*
|
||||||
|
* 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 <http://www.gnu.org/licenses/>.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package org.jetbrains.plugins.ideavim.ex.handler
|
||||||
|
import com.maddyhome.idea.vim.VimPlugin
|
||||||
|
import org.jetbrains.plugins.ideavim.VimFileEditorTestCase
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @author Elliot Courant
|
||||||
|
*/
|
||||||
|
class CmdClearHandlerTest : VimFileEditorTestCase() {
|
||||||
|
fun `test clear aliases`() {
|
||||||
|
VimPlugin.getCommand().resetAliases()
|
||||||
|
configureByText("\n")
|
||||||
|
typeText(commandToKeys("command"))
|
||||||
|
assertPluginError(false)
|
||||||
|
assertExOutput("Name Args Definition\n") // There should not be any aliases.
|
||||||
|
|
||||||
|
typeText(commandToKeys("command Vs vs"))
|
||||||
|
assertPluginError(false)
|
||||||
|
typeText(commandToKeys("command Wq wq"))
|
||||||
|
assertPluginError(false)
|
||||||
|
typeText(commandToKeys("command WQ wq"))
|
||||||
|
assertPluginError(false)
|
||||||
|
typeText(commandToKeys("command"))
|
||||||
|
assertPluginError(false)
|
||||||
|
// The added alias should be listed
|
||||||
|
assertExOutput("""Name Args Definition
|
||||||
|
|Vs 0 vs
|
||||||
|
|Wq 0 wq
|
||||||
|
|WQ 0 wq
|
||||||
|
""".trimMargin())
|
||||||
|
|
||||||
|
// Delete all of the aliases and then list aliases again.
|
||||||
|
typeText(commandToKeys("comclear"))
|
||||||
|
assertPluginError(false)
|
||||||
|
typeText(commandToKeys("command"))
|
||||||
|
assertPluginError(false)
|
||||||
|
assertExOutput("Name Args Definition\n") // There should not be any aliases.
|
||||||
|
}
|
||||||
|
}
|
207
test/org/jetbrains/plugins/ideavim/ex/handler/CmdHandlerTest.kt
Normal file
207
test/org/jetbrains/plugins/ideavim/ex/handler/CmdHandlerTest.kt
Normal file
@ -0,0 +1,207 @@
|
|||||||
|
/*
|
||||||
|
* 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 <http://www.gnu.org/licenses/>.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package org.jetbrains.plugins.ideavim.ex.handler
|
||||||
|
|
||||||
|
import com.maddyhome.idea.vim.VimPlugin
|
||||||
|
import org.jetbrains.plugins.ideavim.VimFileEditorTestCase
|
||||||
|
import org.jetbrains.plugins.ideavim.VimTestCase
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @author Elliot Courant
|
||||||
|
*/
|
||||||
|
class CmdHandlerTest : VimFileEditorTestCase() {
|
||||||
|
fun `test recursive`() {
|
||||||
|
VimPlugin.getCommand().resetAliases()
|
||||||
|
configureByText("\n")
|
||||||
|
typeText(VimTestCase.commandToKeys("command Recur1 Recur2"))
|
||||||
|
assertPluginError(false)
|
||||||
|
typeText(VimTestCase.commandToKeys("command Recur2 Recur1"))
|
||||||
|
assertPluginError(false)
|
||||||
|
typeText(VimTestCase.commandToKeys("Recur1"))
|
||||||
|
assertPluginError(true) // Recursive command should error.
|
||||||
|
}
|
||||||
|
|
||||||
|
fun `test list aliases`() {
|
||||||
|
VimPlugin.getCommand().resetAliases()
|
||||||
|
configureByText("\n")
|
||||||
|
typeText(VimTestCase.commandToKeys("command"))
|
||||||
|
assertPluginError(false)
|
||||||
|
assertExOutput("Name Args Definition\n") // There should not be any aliases.
|
||||||
|
|
||||||
|
typeText(VimTestCase.commandToKeys("command Vs vs"))
|
||||||
|
assertPluginError(false)
|
||||||
|
typeText(VimTestCase.commandToKeys("command Wq wq"))
|
||||||
|
assertPluginError(false)
|
||||||
|
typeText(VimTestCase.commandToKeys("command WQ wq"))
|
||||||
|
assertPluginError(false)
|
||||||
|
typeText(VimTestCase.commandToKeys("command-nargs=* Test1 echo"))
|
||||||
|
assertPluginError(false)
|
||||||
|
typeText(VimTestCase.commandToKeys("command-nargs=? Test2 echo"))
|
||||||
|
assertPluginError(false)
|
||||||
|
typeText(VimTestCase.commandToKeys("command-nargs=+ Test3 echo"))
|
||||||
|
assertPluginError(false)
|
||||||
|
typeText(VimTestCase.commandToKeys("command-nargs=1 Test4 echo"))
|
||||||
|
assertPluginError(false)
|
||||||
|
typeText(VimTestCase.commandToKeys("command"))
|
||||||
|
assertPluginError(false)
|
||||||
|
// The added alias should be listed
|
||||||
|
assertExOutput("""Name Args Definition
|
||||||
|
|Test1 * echo
|
||||||
|
|Test2 ? echo
|
||||||
|
|Test3 + echo
|
||||||
|
|Test4 1 echo
|
||||||
|
|Vs 0 vs
|
||||||
|
|Wq 0 wq
|
||||||
|
|WQ 0 wq
|
||||||
|
""".trimMargin())
|
||||||
|
|
||||||
|
typeText(VimTestCase.commandToKeys("command W"))
|
||||||
|
assertPluginError(false)
|
||||||
|
// The filtered aliases should be listed
|
||||||
|
assertExOutput("""Name Args Definition
|
||||||
|
|Wq 0 wq
|
||||||
|
|WQ 0 wq
|
||||||
|
""".trimMargin())
|
||||||
|
}
|
||||||
|
|
||||||
|
fun `test bad alias`() {
|
||||||
|
VimPlugin.getCommand().resetAliases()
|
||||||
|
configureByText("\n")
|
||||||
|
typeText(VimTestCase.commandToKeys("Vs"))
|
||||||
|
assertPluginError(true)
|
||||||
|
typeText(VimTestCase.commandToKeys("command Vs vs"))
|
||||||
|
assertPluginError(false)
|
||||||
|
typeText(VimTestCase.commandToKeys("Vs"))
|
||||||
|
assertPluginError(false)
|
||||||
|
}
|
||||||
|
|
||||||
|
fun `test lowercase should fail`() {
|
||||||
|
VimPlugin.getCommand().resetAliases()
|
||||||
|
configureByText("\n")
|
||||||
|
typeText(VimTestCase.commandToKeys("command lowercase vs"))
|
||||||
|
assertPluginError(true)
|
||||||
|
}
|
||||||
|
|
||||||
|
fun `test blacklisted alias should fail`() {
|
||||||
|
VimPlugin.getCommand().resetAliases()
|
||||||
|
configureByText("\n")
|
||||||
|
typeText(VimTestCase.commandToKeys("command X vs"))
|
||||||
|
assertPluginError(true)
|
||||||
|
typeText(VimTestCase.commandToKeys("command Next vs"))
|
||||||
|
assertPluginError(true)
|
||||||
|
typeText(VimTestCase.commandToKeys("command Print vs"))
|
||||||
|
assertPluginError(true)
|
||||||
|
}
|
||||||
|
|
||||||
|
fun `test add an existing alias and overwrite`() {
|
||||||
|
VimPlugin.getCommand().resetAliases()
|
||||||
|
configureByText("\n")
|
||||||
|
typeText(VimTestCase.commandToKeys("command Existing1 vs"))
|
||||||
|
assertPluginError(false)
|
||||||
|
typeText(VimTestCase.commandToKeys("command Existing1 wq"))
|
||||||
|
assertPluginError(true)
|
||||||
|
typeText(VimTestCase.commandToKeys("command! Existing1 wq"))
|
||||||
|
assertPluginError(false)
|
||||||
|
}
|
||||||
|
|
||||||
|
fun `test add command with arguments`() {
|
||||||
|
VimPlugin.getCommand().resetAliases()
|
||||||
|
configureByText("\n")
|
||||||
|
typeText(VimTestCase.commandToKeys("command -nargs=* Error echo <args>"))
|
||||||
|
assertPluginError(false)
|
||||||
|
}
|
||||||
|
|
||||||
|
fun `test add command with arguments short`() {
|
||||||
|
VimPlugin.getCommand().resetAliases()
|
||||||
|
configureByText("\n")
|
||||||
|
typeText(VimTestCase.commandToKeys("command-nargs=* Error echo <args>"))
|
||||||
|
assertPluginError(false)
|
||||||
|
}
|
||||||
|
|
||||||
|
fun `test add command with arguments even shorter`() {
|
||||||
|
VimPlugin.getCommand().resetAliases()
|
||||||
|
configureByText("\n")
|
||||||
|
typeText(VimTestCase.commandToKeys("com-nargs=* Error echo <args>"))
|
||||||
|
assertPluginError(false)
|
||||||
|
}
|
||||||
|
|
||||||
|
fun `test add command with various arguments`() {
|
||||||
|
VimPlugin.getCommand().resetAliases()
|
||||||
|
configureByText("\n")
|
||||||
|
typeText(VimTestCase.commandToKeys("command! -nargs=0 Error echo <args>"))
|
||||||
|
assertPluginError(false)
|
||||||
|
typeText(VimTestCase.commandToKeys("command! -nargs=1 Error echo <args>"))
|
||||||
|
assertPluginError(false)
|
||||||
|
typeText(VimTestCase.commandToKeys("command! -nargs=* Error echo <args>"))
|
||||||
|
assertPluginError(false)
|
||||||
|
typeText(VimTestCase.commandToKeys("command! -nargs=? Error echo <args>"))
|
||||||
|
assertPluginError(false)
|
||||||
|
typeText(VimTestCase.commandToKeys("command! -nargs=+ Error echo <args>"))
|
||||||
|
assertPluginError(false)
|
||||||
|
}
|
||||||
|
|
||||||
|
fun `test add command with invalid arguments`() {
|
||||||
|
VimPlugin.getCommand().resetAliases()
|
||||||
|
configureByText("\n")
|
||||||
|
typeText(VimTestCase.commandToKeys("command! -nargs= Error echo <args>"))
|
||||||
|
assertPluginError(true)
|
||||||
|
typeText(VimTestCase.commandToKeys("command! -nargs=-1 Error echo <args>"))
|
||||||
|
assertPluginError(true)
|
||||||
|
typeText(VimTestCase.commandToKeys("command! -nargs=# Error echo <args>"))
|
||||||
|
assertPluginError(true)
|
||||||
|
}
|
||||||
|
|
||||||
|
fun `test run command with arguments`() {
|
||||||
|
VimPlugin.getCommand().resetAliases()
|
||||||
|
configureByText("\n")
|
||||||
|
typeText(VimTestCase.commandToKeys("let test = \"Hello!\""))
|
||||||
|
assertPluginError(false)
|
||||||
|
typeText(VimTestCase.commandToKeys("command! -nargs=1 Error echo <args>"))
|
||||||
|
assertPluginError(false)
|
||||||
|
typeText(VimTestCase.commandToKeys("Error test"))
|
||||||
|
assertPluginError(false)
|
||||||
|
assertExOutput("Hello!\n")
|
||||||
|
|
||||||
|
typeText(VimTestCase.commandToKeys("command! -nargs=1 Error echo <q-args>"))
|
||||||
|
assertPluginError(false)
|
||||||
|
typeText(VimTestCase.commandToKeys("Error test message"))
|
||||||
|
assertPluginError(false)
|
||||||
|
assertExOutput("test message\n")
|
||||||
|
}
|
||||||
|
|
||||||
|
fun `test run command that creates another command`() {
|
||||||
|
VimPlugin.getCommand().resetAliases()
|
||||||
|
configureByText("\n")
|
||||||
|
typeText(VimTestCase.commandToKeys("command! -nargs=1 CreateCommand command -nargs=1 <args> <lt>q-args>"))
|
||||||
|
assertPluginError(false)
|
||||||
|
typeText(VimTestCase.commandToKeys("CreateCommand Show echo"))
|
||||||
|
assertPluginError(false)
|
||||||
|
typeText(VimTestCase.commandToKeys("Show test"))
|
||||||
|
assertPluginError(false)
|
||||||
|
assertExOutput("test\n")
|
||||||
|
}
|
||||||
|
|
||||||
|
fun `test run command missing required argument`() {
|
||||||
|
VimPlugin.getCommand().resetAliases()
|
||||||
|
configureByText("\n")
|
||||||
|
typeText(VimTestCase.commandToKeys("command! -nargs=1 Error echo <q-args>"))
|
||||||
|
assertPluginError(false)
|
||||||
|
typeText(VimTestCase.commandToKeys("Error"))
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,75 @@
|
|||||||
|
/*
|
||||||
|
* IdeaVim - Vim emulator for IDEs based on the IntelliJ platform
|
||||||
|
* Copyright (C) 2003-2019 The IdeaVim authors
|
||||||
|
*
|
||||||
|
* This program is free software: you can redistribute it and/or modify
|
||||||
|
* it under the terms of the GNU General Public License as published by
|
||||||
|
* the Free Software Foundation, either version 2 of the License, or
|
||||||
|
* (at your option) any later version.
|
||||||
|
*
|
||||||
|
* This program is distributed in the hope that it will be useful,
|
||||||
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
* GNU General Public License for more details.
|
||||||
|
*
|
||||||
|
* You should have received a copy of the GNU General Public License
|
||||||
|
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package org.jetbrains.plugins.ideavim.ex.handler
|
||||||
|
import com.maddyhome.idea.vim.VimPlugin
|
||||||
|
import org.jetbrains.plugins.ideavim.VimFileEditorTestCase
|
||||||
|
import org.jetbrains.plugins.ideavim.VimTestCase
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @author Elliot Courant
|
||||||
|
*/
|
||||||
|
class DelCmdHandlerTest : VimFileEditorTestCase() {
|
||||||
|
fun `test remove alias`() {
|
||||||
|
VimPlugin.getCommand().resetAliases()
|
||||||
|
configureByText("\n")
|
||||||
|
typeText(VimTestCase.commandToKeys("command"))
|
||||||
|
assertPluginError(false)
|
||||||
|
assertExOutput("Name Args Definition\n") // There should not be any aliases.
|
||||||
|
|
||||||
|
typeText(VimTestCase.commandToKeys("command Vs vs"))
|
||||||
|
assertPluginError(false)
|
||||||
|
typeText(VimTestCase.commandToKeys("command Wq wq"))
|
||||||
|
assertPluginError(false)
|
||||||
|
typeText(VimTestCase.commandToKeys("command WQ wq"))
|
||||||
|
assertPluginError(false)
|
||||||
|
typeText(VimTestCase.commandToKeys("command"))
|
||||||
|
assertPluginError(false)
|
||||||
|
// The added alias should be listed
|
||||||
|
assertExOutput("""Name Args Definition
|
||||||
|
|Vs 0 vs
|
||||||
|
|Wq 0 wq
|
||||||
|
|WQ 0 wq
|
||||||
|
""".trimMargin())
|
||||||
|
|
||||||
|
typeText(VimTestCase.commandToKeys("command W"))
|
||||||
|
assertPluginError(false)
|
||||||
|
// The filtered aliases should be listed
|
||||||
|
assertExOutput("""Name Args Definition
|
||||||
|
|Wq 0 wq
|
||||||
|
|WQ 0 wq
|
||||||
|
""".trimMargin())
|
||||||
|
|
||||||
|
// Delete one of the aliases and then list all aliases again.
|
||||||
|
typeText(VimTestCase.commandToKeys("delcommand Wq"))
|
||||||
|
assertPluginError(false)
|
||||||
|
typeText(VimTestCase.commandToKeys("command"))
|
||||||
|
assertPluginError(false)
|
||||||
|
assertExOutput("""Name Args Definition
|
||||||
|
|Vs 0 vs
|
||||||
|
|WQ 0 wq
|
||||||
|
""".trimMargin())
|
||||||
|
}
|
||||||
|
|
||||||
|
fun `test remove non-existant alias`() {
|
||||||
|
VimPlugin.getCommand().resetAliases()
|
||||||
|
configureByText("\n")
|
||||||
|
typeText(VimTestCase.commandToKeys("delcommand VS"))
|
||||||
|
assertPluginError(true)
|
||||||
|
}
|
||||||
|
}
|
Loading…
Reference in New Issue
Block a user