mirror of
https://github.com/chylex/IntelliJ-IdeaVim.git
synced 2025-08-01 15:59:06 +02:00
Merge branch 'master' into ideavim-search
# Conflicts: # src/com/maddyhome/idea/vim/ui/ExEntryPanel.java
This commit is contained in:
commit
71504d0ded
.editorconfigAUTHORS.mdCHANGES.mdindex.txt
resources/META-INF
src/com/maddyhome/idea/vim
KeyHandler.javaRegisterActions.javaVimTypedActionHandler.java
action
ex
window
ex/handler
group
helper
package-info.javaui
test/org/jetbrains/plugins/ideavim
@ -5,5 +5,5 @@ indent_size = 2
|
||||
indent_style = space
|
||||
|
||||
[*.kt]
|
||||
indent_size = 4
|
||||
indent_size = 2
|
||||
indent_style = space
|
||||
|
@ -69,6 +69,7 @@ Contributors:
|
||||
* [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
|
||||
contact the maintainer.
|
||||
|
@ -42,6 +42,14 @@ To Be Released
|
||||
* [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
|
||||
----------------
|
||||
|
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
|
@ -357,10 +357,7 @@
|
||||
<action id="VimPlaybackLastRegister" class="com.maddyhome.idea.vim.action.macro.PlaybackLastRegisterAction" text="Playback Last Register"/>
|
||||
|
||||
<!-- 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="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 -->
|
||||
<action id="VimLastSearchReplace" class="com.maddyhome.idea.vim.action.change.change.ChangeLastSearchReplaceAction" text="Repeat Last :s"/>
|
||||
|
@ -242,15 +242,15 @@ public class KeyHandler {
|
||||
final CommandState commandState = CommandState.getInstance(editor);
|
||||
commandState.stopMappingTimer();
|
||||
|
||||
final List<KeyStroke> mappingKeys = commandState.getMappingKeys();
|
||||
final List<KeyStroke> fromKeys = new ArrayList<KeyStroke>(mappingKeys);
|
||||
fromKeys.add(key);
|
||||
|
||||
final MappingMode mappingMode = commandState.getMappingMode();
|
||||
if (MappingMode.NVO.contains(mappingMode) && (state != State.NEW_COMMAND || currentArg != Argument.Type.NONE)) {
|
||||
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 MappingInfo currentMappingInfo = mapping.get(fromKeys);
|
||||
final MappingInfo prevMappingInfo = mapping.get(mappingKeys);
|
||||
|
@ -31,11 +31,11 @@ import javax.swing.*;
|
||||
import java.awt.event.KeyEvent;
|
||||
import java.util.EnumSet;
|
||||
|
||||
public class RegisterActions {
|
||||
class RegisterActions {
|
||||
/**
|
||||
* Register all the key/action mappings for the plugin.
|
||||
*/
|
||||
public static void registerActions() {
|
||||
static void registerActions() {
|
||||
registerVimCommandActions();
|
||||
|
||||
registerInsertModeActions();
|
||||
|
@ -40,11 +40,9 @@ import java.awt.event.KeyEvent;
|
||||
public class VimTypedActionHandler implements TypedActionHandlerEx {
|
||||
private static final Logger logger = Logger.getInstance(VimTypedActionHandler.class.getName());
|
||||
|
||||
private final TypedActionHandler origHandler;
|
||||
@NotNull private final KeyHandler handler;
|
||||
|
||||
public VimTypedActionHandler(TypedActionHandler origHandler) {
|
||||
this.origHandler = origHandler;
|
||||
VimTypedActionHandler(TypedActionHandler origHandler) {
|
||||
handler = KeyHandler.getInstance();
|
||||
handler.setOriginalHandler(origHandler);
|
||||
}
|
||||
@ -55,7 +53,7 @@ public class VimTypedActionHandler implements TypedActionHandlerEx {
|
||||
handler.beforeHandleKey(editor, KeyStroke.getKeyStroke(charTyped), context, plan);
|
||||
}
|
||||
else {
|
||||
TypedActionHandler originalHandler = KeyHandler.getInstance().getOriginalHandler();
|
||||
TypedActionHandler originalHandler = handler.getOriginalHandler();
|
||||
if (originalHandler instanceof TypedActionHandlerEx) {
|
||||
((TypedActionHandlerEx)originalHandler).beforeExecute(editor, charTyped, context, plan);
|
||||
}
|
||||
@ -73,6 +71,7 @@ public class VimTypedActionHandler implements TypedActionHandlerEx {
|
||||
}
|
||||
}
|
||||
else {
|
||||
TypedActionHandler origHandler = handler.getOriginalHandler();
|
||||
origHandler.execute(editor, charTyped, context);
|
||||
}
|
||||
}
|
||||
|
@ -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 BackspaceAction extends EditorAction {
|
||||
public BackspaceAction() {
|
||||
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));
|
||||
}
|
||||
}
|
||||
}
|
@ -27,6 +27,9 @@ import com.maddyhome.idea.vim.handler.EditorActionHandlerBase;
|
||||
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 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
|
||||
@Override
|
||||
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
|
||||
|
@ -54,7 +54,7 @@ public class WindowLeftAction extends VimCommandAction {
|
||||
@NotNull
|
||||
@Override
|
||||
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
|
||||
|
@ -54,7 +54,7 @@ public class WindowRightAction extends VimCommandAction {
|
||||
@NotNull
|
||||
@Override
|
||||
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
|
||||
|
@ -54,7 +54,7 @@ public class WindowUpAction extends VimCommandAction {
|
||||
@NotNull
|
||||
@Override
|
||||
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
|
||||
|
@ -34,7 +34,7 @@ class ActionListHandler : CommandHandler(
|
||||
flags(RangeFlag.RANGE_FORBIDDEN, ArgumentFlag.ARGUMENT_OPTIONAL, DONT_REOPEN)
|
||||
) {
|
||||
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 actionManager = ActionManager.getInstance()
|
||||
|
||||
|
@ -60,7 +60,7 @@ class CmdHandler : CommandHandler(
|
||||
}
|
||||
|
||||
private fun listAlias(editor: Editor, filter: String): Boolean {
|
||||
val lineSeparator = System.lineSeparator()
|
||||
val lineSeparator = "\n"
|
||||
val allAliases = VimPlugin.getCommand().listAliases()
|
||||
val aliases = allAliases.filter {
|
||||
(filter.isEmpty() || it.key.startsWith(filter))
|
||||
|
@ -1605,7 +1605,7 @@ public class ChangeGroup {
|
||||
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 int[] startOffsets = range.getStartOffsets();
|
||||
final int[] endOffsets = range.getEndOffsets();
|
||||
|
@ -68,13 +68,12 @@ public class KeyGroup {
|
||||
@NotNull private final Map<MappingMode, KeyMapping> keyMappings = new HashMap<>();
|
||||
@Nullable private OperatorFunction operatorFunction = null;
|
||||
|
||||
public void registerRequiredShortcutKeys(@NotNull Editor editor) {
|
||||
final Set<KeyStroke> requiredKeys = VimPlugin.getKey().requiredShortcutKeys;
|
||||
void registerRequiredShortcutKeys(@NotNull Editor editor) {
|
||||
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());
|
||||
}
|
||||
|
||||
|
@ -146,7 +146,7 @@ public class MotionGroup {
|
||||
*/
|
||||
private void processMouseClick(@NotNull Editor editor, @NotNull MouseEvent event) {
|
||||
if (ExEntryPanel.getInstance().isActive()) {
|
||||
ExEntryPanel.getInstance().deactivate(false);
|
||||
VimPlugin.getProcess().cancelExEntry(editor, ExEntryPanel.getInstance().getEntry().getContext());
|
||||
}
|
||||
|
||||
ExOutputModel.getInstance(editor).clear();
|
||||
|
@ -90,7 +90,7 @@ public class ProcessGroup {
|
||||
|
||||
ExEntryPanel panel = ExEntryPanel.getInstance();
|
||||
if (panel.isActive()) {
|
||||
UiHelper.requestFocus(panel);
|
||||
UiHelper.requestFocus(panel.getEntry());
|
||||
panel.handleKey(stroke);
|
||||
|
||||
return true;
|
||||
@ -144,13 +144,11 @@ public class ProcessGroup {
|
||||
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();
|
||||
KeyHandler.getInstance().reset(editor);
|
||||
ExEntryPanel panel = ExEntryPanel.getInstance();
|
||||
panel.deactivate(true);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
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) {
|
||||
logger.debug("found Ctrl-K");
|
||||
digraphState = DIG_STATE_DIG_ONE;
|
||||
return DigraphResult.OK;
|
||||
return DigraphResult.OK_DIGRAPH;
|
||||
}
|
||||
else if ((key.getKeyCode() == KeyEvent.VK_V || key.getKeyCode() == KeyEvent.VK_Q) &&
|
||||
(key.getModifiers() & KeyEvent.CTRL_MASK) != 0) {
|
||||
@ -57,7 +57,7 @@ public class DigraphSequence {
|
||||
digraphState = DIG_STATE_CODE_START;
|
||||
codeChars = new char[8];
|
||||
codeCnt = 0;
|
||||
return DigraphResult.OK;
|
||||
return DigraphResult.OK_LITERAL;
|
||||
}
|
||||
else {
|
||||
return new DigraphResult(key);
|
||||
@ -68,7 +68,7 @@ public class DigraphSequence {
|
||||
digraphChar = key.getKeyChar();
|
||||
digraphState = DIG_STATE_DIG_TWO;
|
||||
|
||||
return DigraphResult.OK;
|
||||
return new DigraphResult(DigraphResult.RES_OK, digraphChar);
|
||||
}
|
||||
else {
|
||||
digraphState = DIG_STATE_START;
|
||||
@ -93,26 +93,26 @@ public class DigraphSequence {
|
||||
digraphState = DIG_STATE_CODE_CHAR;
|
||||
codeType = 8;
|
||||
logger.debug("Octal");
|
||||
return DigraphResult.OK;
|
||||
return DigraphResult.OK_LITERAL;
|
||||
case 'x':
|
||||
case 'X':
|
||||
codeMax = 2;
|
||||
digraphState = DIG_STATE_CODE_CHAR;
|
||||
codeType = 16;
|
||||
logger.debug("hex2");
|
||||
return DigraphResult.OK;
|
||||
return DigraphResult.OK_LITERAL;
|
||||
case 'u':
|
||||
codeMax = 4;
|
||||
digraphState = DIG_STATE_CODE_CHAR;
|
||||
codeType = 16;
|
||||
logger.debug("hex4");
|
||||
return DigraphResult.OK;
|
||||
return DigraphResult.OK_LITERAL;
|
||||
case 'U':
|
||||
codeMax = 8;
|
||||
digraphState = DIG_STATE_CODE_CHAR;
|
||||
codeType = 16;
|
||||
logger.debug("hex8");
|
||||
return DigraphResult.OK;
|
||||
return DigraphResult.OK_LITERAL;
|
||||
case '0':
|
||||
case '1':
|
||||
case '2':
|
||||
@ -128,7 +128,7 @@ public class DigraphSequence {
|
||||
codeType = 10;
|
||||
codeChars[codeCnt++] = key.getKeyChar();
|
||||
logger.debug("decimal");
|
||||
return DigraphResult.OK;
|
||||
return DigraphResult.OK_LITERAL;
|
||||
default:
|
||||
switch (key.getKeyCode()) {
|
||||
case KeyEvent.VK_TAB:
|
||||
@ -177,7 +177,7 @@ public class DigraphSequence {
|
||||
return new DigraphResult(code);
|
||||
}
|
||||
else {
|
||||
return DigraphResult.OK;
|
||||
return DigraphResult.OK_LITERAL;
|
||||
}
|
||||
}
|
||||
else if (codeCnt > 0) {
|
||||
@ -204,14 +204,21 @@ public class DigraphSequence {
|
||||
public static final int RES_BAD = 1;
|
||||
public static final int RES_DONE = 2;
|
||||
|
||||
public static final DigraphResult OK = new DigraphResult(RES_OK);
|
||||
public static final DigraphResult BAD = new DigraphResult(RES_BAD);
|
||||
static final DigraphResult OK_DIGRAPH = new DigraphResult(RES_OK, '?');
|
||||
static final DigraphResult OK_LITERAL = new DigraphResult(RES_OK, '^');
|
||||
static final DigraphResult BAD = new DigraphResult(RES_BAD);
|
||||
|
||||
DigraphResult(int result) {
|
||||
this.result = result;
|
||||
stroke = null;
|
||||
}
|
||||
|
||||
DigraphResult(int result, char promptCharacter) {
|
||||
this.result = result;
|
||||
this.promptCharacter = promptCharacter;
|
||||
stroke = null;
|
||||
}
|
||||
|
||||
DigraphResult(@Nullable KeyStroke stroke) {
|
||||
result = RES_DONE;
|
||||
this.stroke = stroke;
|
||||
@ -226,8 +233,13 @@ public class DigraphSequence {
|
||||
return result;
|
||||
}
|
||||
|
||||
public char getPromptCharacter() {
|
||||
return promptCharacter;
|
||||
}
|
||||
|
||||
private final int result;
|
||||
@Nullable private final KeyStroke stroke;
|
||||
private char promptCharacter;
|
||||
}
|
||||
|
||||
private int digraphState = DIG_STATE_START;
|
||||
|
@ -307,6 +307,10 @@
|
||||
* |CTRL-W_<Up>| {@link com.maddyhome.idea.vim.action.window.WindowUpAction}
|
||||
* |CTRL-W_<Left>| {@link com.maddyhome.idea.vim.action.window.WindowLeftAction}
|
||||
* |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
|
||||
@ -575,7 +579,72 @@
|
||||
*
|
||||
* 4. 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
|
||||
*
|
||||
*
|
||||
* 5. Ex commands
|
||||
*
|
||||
|
@ -20,10 +20,7 @@ package com.maddyhome.idea.vim.ui;
|
||||
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
|
||||
import javax.swing.text.AttributeSet;
|
||||
import javax.swing.text.BadLocationException;
|
||||
import javax.swing.text.Document;
|
||||
import javax.swing.text.PlainDocument;
|
||||
import javax.swing.text.*;
|
||||
|
||||
/**
|
||||
* This document provides insert/overwrite mode
|
||||
@ -32,7 +29,7 @@ public class ExDocument extends PlainDocument {
|
||||
/**
|
||||
* Toggles the insert/overwrite state
|
||||
*/
|
||||
public void toggleInsertReplace() {
|
||||
void toggleInsertReplace() {
|
||||
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
|
||||
public static KeyStroke convert(@NotNull ActionEvent event) {
|
||||
private static KeyStroke convert(@NotNull ActionEvent event) {
|
||||
String cmd = event.getActionCommand();
|
||||
int mods = event.getModifiers();
|
||||
if (cmd != null && cmd.length() > 0) {
|
||||
char ch = cmd.charAt(0);
|
||||
if (ch < ' ') {
|
||||
if (mods == KeyEvent.CTRL_MASK) {
|
||||
if ((mods & KeyEvent.CTRL_MASK) != 0) {
|
||||
return KeyStroke.getKeyStroke(KeyEvent.VK_A + ch - 1, mods);
|
||||
}
|
||||
}
|
||||
@ -99,25 +99,22 @@ public class ExEditorKit extends DefaultEditorKit {
|
||||
return null;
|
||||
}
|
||||
|
||||
public static final String DefaultExKey = "default-ex-key";
|
||||
public static final String CancelEntry = "cancel-entry";
|
||||
public static final String CompleteEntry = "complete-entry";
|
||||
public static final String EscapeChar = "escape";
|
||||
public static final String DeletePreviousChar = "delete-prev-char";
|
||||
public static final String DeletePreviousWord = "delete-prev-word";
|
||||
public static final String DeleteToCursor = "delete-to-cursor";
|
||||
public static final String DeleteFromCursor = "delete-from-cursor";
|
||||
public static final String ToggleInsertReplace = "toggle-insert";
|
||||
public static final String InsertRegister = "insert-register";
|
||||
public static final String InsertWord = "insert-word";
|
||||
public static final String InsertWORD = "insert-WORD";
|
||||
public static final String HistoryUp = "history-up";
|
||||
public static final String HistoryDown = "history-down";
|
||||
public static final String HistoryUpFilter = "history-up-filter";
|
||||
public static final String HistoryDownFilter = "history-down-filter";
|
||||
public static final String StartDigraph = "start-digraph";
|
||||
static final String CancelEntry = "cancel-entry";
|
||||
static final String CompleteEntry = "complete-entry";
|
||||
static final String EscapeChar = "escape";
|
||||
static final String DeletePreviousChar = "delete-prev-char";
|
||||
static final String DeletePreviousWord = "delete-prev-word";
|
||||
static final String DeleteToCursor = "delete-to-cursor";
|
||||
static final String DeleteFromCursor = "delete-from-cursor";
|
||||
static final String ToggleInsertReplace = "toggle-insert";
|
||||
static final String InsertRegister = "insert-register";
|
||||
static final String HistoryUp = "history-up";
|
||||
static final String HistoryDown = "history-down";
|
||||
static final String HistoryUpFilter = "history-up-filter";
|
||||
static final String HistoryDownFilter = "history-down-filter";
|
||||
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.CompleteEntryAction(),
|
||||
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 HistoryUpAction() {
|
||||
HistoryUpAction() {
|
||||
super(HistoryUp);
|
||||
}
|
||||
|
||||
@ -172,7 +173,7 @@ public class ExEditorKit extends DefaultEditorKit {
|
||||
}
|
||||
|
||||
public static class HistoryDownAction extends TextAction {
|
||||
public HistoryDownAction() {
|
||||
HistoryDownAction() {
|
||||
super(HistoryDown);
|
||||
}
|
||||
|
||||
@ -183,7 +184,7 @@ public class ExEditorKit extends DefaultEditorKit {
|
||||
}
|
||||
|
||||
public static class HistoryUpFilterAction extends TextAction {
|
||||
public HistoryUpFilterAction() {
|
||||
HistoryUpFilterAction() {
|
||||
super(HistoryUpFilter);
|
||||
}
|
||||
|
||||
@ -194,7 +195,7 @@ public class ExEditorKit extends DefaultEditorKit {
|
||||
}
|
||||
|
||||
public static class HistoryDownFilterAction extends TextAction {
|
||||
public HistoryDownFilterAction() {
|
||||
HistoryDownFilterAction() {
|
||||
super(HistoryDownFilter);
|
||||
}
|
||||
|
||||
@ -204,15 +205,15 @@ public class ExEditorKit extends DefaultEditorKit {
|
||||
}
|
||||
}
|
||||
|
||||
public static class InsertRegisterAction extends TextAction {
|
||||
private static enum State {
|
||||
public static class InsertRegisterAction extends TextAction implements MultiStepAction {
|
||||
private enum State {
|
||||
SKIP_CTRL_R,
|
||||
WAIT_REGISTER,
|
||||
}
|
||||
|
||||
@NotNull private State state = State.SKIP_CTRL_R;
|
||||
|
||||
public InsertRegisterAction() {
|
||||
InsertRegisterAction() {
|
||||
super(InsertRegister);
|
||||
}
|
||||
|
||||
@ -223,11 +224,12 @@ public class ExEditorKit extends DefaultEditorKit {
|
||||
switch (state) {
|
||||
case SKIP_CTRL_R:
|
||||
state = State.WAIT_REGISTER;
|
||||
target.setCurrentAction(this);
|
||||
target.setCurrentAction(this, '\"');
|
||||
break;
|
||||
|
||||
case WAIT_REGISTER:
|
||||
state = State.SKIP_CTRL_R;
|
||||
target.setCurrentAction(null);
|
||||
target.clearCurrentAction();
|
||||
final char c = key.getKeyChar();
|
||||
if (c != KeyEvent.CHAR_UNDEFINED) {
|
||||
final Register register = VimPlugin.getRegister().getRegister(c);
|
||||
@ -235,20 +237,27 @@ public class ExEditorKit extends DefaultEditorKit {
|
||||
final String oldText = target.getText();
|
||||
final String text = register.getText();
|
||||
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 {
|
||||
} else if ((key.getModifiers() & KeyEvent.CTRL_MASK) != 0 && key.getKeyCode() == KeyEvent.VK_C) {
|
||||
// Eat any unused keys, unless it's <C-C>, in which case forward on and cancel entry
|
||||
target.handleKey(key);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void reset() {
|
||||
state = State.SKIP_CTRL_R;
|
||||
}
|
||||
}
|
||||
|
||||
public static class CompleteEntryAction extends TextAction {
|
||||
public CompleteEntryAction() {
|
||||
CompleteEntryAction() {
|
||||
super(CompleteEntry);
|
||||
}
|
||||
|
||||
@ -256,27 +265,29 @@ public class ExEditorKit extends DefaultEditorKit {
|
||||
logger.debug("complete entry");
|
||||
KeyStroke stroke = KeyStroke.getKeyStroke(KeyEvent.VK_ENTER, 0);
|
||||
|
||||
KeyHandler.getInstance().handleKey(
|
||||
ExEntryPanel.getInstance().getEntry().getEditor(),
|
||||
stroke,
|
||||
ExEntryPanel.getInstance().getEntry().getContext());
|
||||
// We send the <Enter> keystroke through the key handler rather than calling ProcessGroup#processExEntry directly.
|
||||
// We do this for a couple of reasons:
|
||||
// * The C mode mapping for ProcessExEntryAction handles the actual entry, and most importantly, it does so as a
|
||||
// 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 CancelEntryAction() {
|
||||
CancelEntryAction() {
|
||||
super(CancelEntry);
|
||||
}
|
||||
|
||||
public void actionPerformed(ActionEvent e) {
|
||||
VimPlugin.getProcess().cancelExEntry(
|
||||
ExEntryPanel.getInstance().getEntry().getEditor(),
|
||||
ExEntryPanel.getInstance().getEntry().getContext());
|
||||
ExTextField target = (ExTextField)getTextComponent(e);
|
||||
target.cancel();
|
||||
}
|
||||
}
|
||||
|
||||
public static class EscapeCharAction extends TextAction {
|
||||
public EscapeCharAction() {
|
||||
EscapeCharAction() {
|
||||
super(EscapeChar);
|
||||
}
|
||||
|
||||
@ -287,7 +298,7 @@ public class ExEditorKit extends DefaultEditorKit {
|
||||
}
|
||||
|
||||
public static class DeletePreviousCharAction extends TextAction {
|
||||
public DeletePreviousCharAction() {
|
||||
DeletePreviousCharAction() {
|
||||
super(DeletePreviousChar);
|
||||
}
|
||||
|
||||
@ -323,9 +334,7 @@ public class ExEditorKit extends DefaultEditorKit {
|
||||
doc.remove(dot - delChars, delChars);
|
||||
}
|
||||
else {
|
||||
VimPlugin.getProcess().cancelExEntry(
|
||||
ExEntryPanel.getInstance().getEntry().getEditor(),
|
||||
ExEntryPanel.getInstance().getEntry().getContext());
|
||||
target.cancel();
|
||||
}
|
||||
}
|
||||
catch (BadLocationException bl) {
|
||||
@ -335,7 +344,7 @@ public class ExEditorKit extends DefaultEditorKit {
|
||||
}
|
||||
|
||||
public static class DeletePreviousWordAction extends TextAction {
|
||||
public DeletePreviousWordAction() {
|
||||
DeletePreviousWordAction() {
|
||||
super(DeletePreviousWord);
|
||||
}
|
||||
|
||||
@ -362,7 +371,7 @@ public class ExEditorKit extends DefaultEditorKit {
|
||||
}
|
||||
|
||||
public static class DeleteToCursorAction extends TextAction {
|
||||
public DeleteToCursorAction() {
|
||||
DeleteToCursorAction() {
|
||||
super(DeleteToCursor);
|
||||
}
|
||||
|
||||
@ -385,7 +394,7 @@ public class ExEditorKit extends DefaultEditorKit {
|
||||
}
|
||||
|
||||
public static class DeleteFromCursorAction extends TextAction {
|
||||
public DeleteFromCursorAction() {
|
||||
DeleteFromCursorAction() {
|
||||
super(DeleteFromCursor);
|
||||
}
|
||||
|
||||
@ -408,7 +417,7 @@ public class ExEditorKit extends DefaultEditorKit {
|
||||
}
|
||||
|
||||
public static class ToggleInsertReplaceAction extends TextAction {
|
||||
public ToggleInsertReplaceAction() {
|
||||
ToggleInsertReplaceAction() {
|
||||
super(ToggleInsertReplace);
|
||||
|
||||
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;
|
||||
|
||||
public StartDigraphAction() {
|
||||
StartDigraphAction() {
|
||||
super(StartDigraph);
|
||||
}
|
||||
|
||||
@ -437,14 +446,23 @@ public class ExEditorKit extends DefaultEditorKit {
|
||||
if (key != null && digraphSequence != null) {
|
||||
DigraphSequence.DigraphResult res = digraphSequence.processKey(key, target.getEditor());
|
||||
switch (res.getResult()) {
|
||||
case DigraphSequence.DigraphResult.RES_BAD:
|
||||
target.setCurrentAction(null);
|
||||
target.handleKey(key);
|
||||
case DigraphSequence.DigraphResult.RES_OK:
|
||||
target.setCurrentActionPromptCharacter(res.getPromptCharacter());
|
||||
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:
|
||||
final KeyStroke digraph = res.getStroke();
|
||||
digraphSequence = null;
|
||||
target.setCurrentAction(null);
|
||||
target.clearCurrentAction();
|
||||
if (digraph != null) {
|
||||
target.handleKey(digraph);
|
||||
}
|
||||
@ -452,11 +470,16 @@ public class ExEditorKit extends DefaultEditorKit {
|
||||
}
|
||||
}
|
||||
else if (key != null && DigraphSequence.isDigraphStart(key)) {
|
||||
target.setCurrentAction(this);
|
||||
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;
|
||||
|
@ -79,6 +79,8 @@ public class ExEntryPanel extends JPanel implements LafManagerListener {
|
||||
}
|
||||
};
|
||||
|
||||
new ExShortcutKeyAction(this).registerCustomShortcutSet();
|
||||
|
||||
LafManager.getInstance().addLafManagerListener(this);
|
||||
|
||||
updateUI();
|
||||
@ -93,6 +95,7 @@ public class ExEntryPanel extends JPanel implements LafManagerListener {
|
||||
private void setFontForElements() {
|
||||
final Font font = UiHelper.getEditorFont();
|
||||
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
|
||||
*/
|
||||
public void activate(@NotNull Editor editor, DataContext context, @NotNull String label, String initText, int count) {
|
||||
entry.setEditor(editor, context);
|
||||
this.label.setText(label);
|
||||
this.count = count;
|
||||
setFontForElements();
|
||||
entry.setDocument(entry.createDefaultModel());
|
||||
entry.reset();
|
||||
entry.setEditor(editor, context);
|
||||
entry.setText(initText);
|
||||
entry.setType(label);
|
||||
parent = editor.getContentComponent();
|
||||
@ -140,7 +143,7 @@ public class ExEntryPanel extends JPanel implements LafManagerListener {
|
||||
public void updateUI() {
|
||||
super.updateUI();
|
||||
|
||||
setBorder(BorderFactory.createEtchedBorder());
|
||||
setBorder(new ExPanelBorder());
|
||||
|
||||
// Can be null when called from base constructor
|
||||
//noinspection ConstantConditions
|
||||
@ -230,6 +233,7 @@ public class ExEntryPanel extends JPanel implements LafManagerListener {
|
||||
logger.info("deactivate");
|
||||
if (!active) return;
|
||||
active = false;
|
||||
entry.deactivate();
|
||||
|
||||
if (!ApplicationManager.getApplication().isUnitTestMode()) {
|
||||
if (refocusOwningEditor && parent != null) {
|
||||
@ -310,6 +314,5 @@ public class ExEntryPanel extends JPanel implements LafManagerListener {
|
||||
private boolean active;
|
||||
|
||||
private static ExEntryPanel instance;
|
||||
|
||||
private static final Logger logger = Logger.getInstance(ExEntryPanel.class.getName());
|
||||
}
|
||||
|
@ -24,20 +24,20 @@ import javax.swing.*;
|
||||
import javax.swing.text.JTextComponent.KeyBinding;
|
||||
import java.awt.event.KeyEvent;
|
||||
|
||||
/**
|
||||
*
|
||||
*/
|
||||
public class ExKeyBindings {
|
||||
@NotNull
|
||||
public static KeyBinding[] getBindings() {
|
||||
static KeyBinding[] getBindings() {
|
||||
return bindings;
|
||||
}
|
||||
|
||||
// TODO - add the following keys:
|
||||
// Ctrl-\ Ctrl-N - abort
|
||||
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_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_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_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_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() {
|
||||
super.updateUI();
|
||||
|
||||
setBorder(BorderFactory.createEtchedBorder());
|
||||
setBorder(new ExPanelBorder());
|
||||
|
||||
// Can be null when called from base constructor
|
||||
//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.diagnostic.Logger;
|
||||
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.util.Disposer;
|
||||
import com.intellij.util.ui.JBUI;
|
||||
import com.maddyhome.idea.vim.VimPlugin;
|
||||
import com.maddyhome.idea.vim.group.HistoryGroup;
|
||||
import kotlin.text.StringsKt;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
import org.jetbrains.annotations.TestOnly;
|
||||
|
||||
import javax.swing.*;
|
||||
import javax.swing.text.Document;
|
||||
import javax.swing.text.Keymap;
|
||||
import javax.swing.plaf.basic.BasicTextFieldUI;
|
||||
import javax.swing.text.*;
|
||||
import java.awt.*;
|
||||
import java.awt.event.FocusEvent;
|
||||
import java.awt.event.FocusListener;
|
||||
import java.awt.event.KeyEvent;
|
||||
import java.awt.event.*;
|
||||
import java.util.Date;
|
||||
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
|
||||
*/
|
||||
public class ExTextField extends JTextField {
|
||||
|
||||
ExTextField() {
|
||||
addFocusListener(new FocusListener() {
|
||||
@Override
|
||||
public void focusGained(FocusEvent e) {
|
||||
setCaretPosition(getText().length());
|
||||
}
|
||||
CommandLineCaret caret = new CommandLineCaret();
|
||||
caret.setBlinkRate(getCaret().getBlinkRate());
|
||||
setCaret(caret);
|
||||
setNormalModeCaret();
|
||||
|
||||
addCaretListener(e -> resetCaret());
|
||||
addMouseListener(new MouseAdapter() {
|
||||
@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.
|
||||
// (I.e. DarculaTextFieldUI#getDefaultMargins, MacIntelliJTextFieldUI#getDefaultMargin, WinIntelliJTextFieldUI#getDefaultMargin)
|
||||
// This is an attempt to mitigate the gap in ExEntryPanel between the label (':', '/', '?') and the text field.
|
||||
// See VIM-1485
|
||||
void reset() {
|
||||
clearCurrentAction();
|
||||
setInsertMode();
|
||||
}
|
||||
|
||||
void deactivate() {
|
||||
clearCurrentAction();
|
||||
}
|
||||
|
||||
@Override
|
||||
public Insets getMargin() {
|
||||
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
|
||||
@Override
|
||||
public void updateUI() {
|
||||
super.updateUI();
|
||||
|
||||
Font font = EditorColorsManager.getInstance().getGlobalScheme().getFont(EditorFontType.PLAIN);
|
||||
setFont(font);
|
||||
// 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
|
||||
// directly next to the label
|
||||
setUI(new BasicTextFieldUI());
|
||||
invalidate();
|
||||
|
||||
setBorder(null);
|
||||
|
||||
@ -98,7 +113,7 @@ public class ExTextField extends JTextField {
|
||||
setKeymap(map);
|
||||
}
|
||||
|
||||
public void setType(@NotNull String type) {
|
||||
void setType(@NotNull String type) {
|
||||
String hkey = null;
|
||||
switch (type.charAt(0)) {
|
||||
case '/':
|
||||
@ -116,11 +131,11 @@ public class ExTextField extends JTextField {
|
||||
}
|
||||
}
|
||||
|
||||
public void saveLastEntry() {
|
||||
void saveLastEntry() {
|
||||
lastEntry = getText();
|
||||
}
|
||||
|
||||
public void selectHistory(boolean isUp, boolean filter) {
|
||||
void selectHistory(boolean isUp, boolean filter) {
|
||||
int dir = isUp ? -1 : 1;
|
||||
if (histIndex + dir < 0 || histIndex + dir > history.size()) {
|
||||
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() {
|
||||
return editor;
|
||||
@ -186,21 +222,25 @@ public class ExTextField extends JTextField {
|
||||
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.setText(string);
|
||||
}
|
||||
|
||||
public void setText(String string) {
|
||||
super.setText(string);
|
||||
|
||||
saveLastEntry();
|
||||
super.processKeyEvent(event);
|
||||
}
|
||||
}
|
||||
|
||||
protected void processKeyEvent(KeyEvent e) {
|
||||
@ -220,182 +260,270 @@ public class ExTextField extends JTextField {
|
||||
return new ExDocument();
|
||||
}
|
||||
|
||||
public void escape() {
|
||||
/**
|
||||
* Cancels current action, if there is one. If not, cancels entry.
|
||||
*/
|
||||
void escape() {
|
||||
if (currentAction != null) {
|
||||
currentAction = null;
|
||||
clearCurrentAction();
|
||||
}
|
||||
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;
|
||||
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
|
||||
public Action getCurrentAction() {
|
||||
Action getCurrentAction() {
|
||||
return currentAction;
|
||||
}
|
||||
|
||||
public void toggleInsertReplace() {
|
||||
private void setInsertMode() {
|
||||
ExDocument doc = (ExDocument)getDocument();
|
||||
if (doc.isOverwrite()) {
|
||||
doc.toggleInsertReplace();
|
||||
}
|
||||
resetCaret();
|
||||
}
|
||||
|
||||
void toggleInsertReplace() {
|
||||
ExDocument doc = (ExDocument)getDocument();
|
||||
doc.toggleInsertReplace();
|
||||
|
||||
/*
|
||||
Caret caret;
|
||||
int width;
|
||||
if (doc.isOverwrite())
|
||||
{
|
||||
caret = blockCaret;
|
||||
width = 8;
|
||||
}
|
||||
else
|
||||
{
|
||||
caret = origCaret;
|
||||
width = 1;
|
||||
}
|
||||
|
||||
setCaret(caret);
|
||||
putClientProperty("caretWidth", new Integer(width));
|
||||
*/
|
||||
resetCaret();
|
||||
}
|
||||
|
||||
/*
|
||||
private static class BlockCaret extends DefaultCaret
|
||||
{
|
||||
public void paint(Graphics g)
|
||||
{
|
||||
if(!isVisible())
|
||||
return;
|
||||
|
||||
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
|
||||
}
|
||||
private void resetCaret() {
|
||||
if (getCaretPosition() == getText().length() || currentActionPromptCharacterOffset == getText().length() - 1) {
|
||||
setNormalModeCaret();
|
||||
}
|
||||
else {
|
||||
ExDocument doc = (ExDocument)getDocument();
|
||||
if (doc.isOverwrite()) {
|
||||
setReplaceModeCaret();
|
||||
}
|
||||
|
||||
private boolean _contains(int i, int j, int k, int l)
|
||||
{
|
||||
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;
|
||||
else {
|
||||
setInsertModeCaret();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 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 DataContext context;
|
||||
private String lastEntry;
|
||||
private List<HistoryGroup.HistoryEntry> history;
|
||||
private int histIndex = 0;
|
||||
@Nullable private Action currentAction;
|
||||
// TODO - support block cursor for overwrite mode
|
||||
//private Caret origCaret;
|
||||
//private Caret blockCaret;
|
||||
@Nullable private ExEditorKit.MultiStepAction currentAction;
|
||||
private char currentActionPromptCharacter;
|
||||
private int currentActionPromptCharacterOffset = -1;
|
||||
|
||||
private static final String vimExTextFieldDisposeKey = "vimExTextFieldDisposeKey";
|
||||
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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -68,6 +68,9 @@ public abstract class VimTestCase extends UsefulTestCase {
|
||||
KeyHandler.getInstance().fullReset(myFixture.getEditor());
|
||||
Options.getInstance().resetAllOptions();
|
||||
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() {
|
||||
|
@ -0,0 +1,51 @@
|
||||
/*
|
||||
* 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.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())
|
||||
}
|
||||
|
||||
// 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())
|
||||
}
|
||||
}
|
@ -16,26 +16,22 @@
|
||||
* 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.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;
|
||||
import com.maddyhome.idea.vim.helper.StringHelper.parseKeys
|
||||
import org.jetbrains.plugins.ideavim.VimTestCase
|
||||
|
||||
/**
|
||||
*/
|
||||
public class CancelExEntryAction extends EditorAction {
|
||||
public CancelExEntryAction() {
|
||||
super(new Handler());
|
||||
}
|
||||
class InsertDeletePreviousWordActionTest : VimTestCase() {
|
||||
// VIM-1655
|
||||
fun `test deleted word is not yanked`() {
|
||||
doTest(parseKeys("yiw", "3wea", "<C-W>", "<ESC>p"), """
|
||||
A Discovery
|
||||
|
||||
private static class Handler extends EditorActionHandlerBase {
|
||||
protected boolean execute(@NotNull Editor editor, @NotNull DataContext context, @NotNull Command cmd) {
|
||||
return VimPlugin.getProcess().cancelExEntry(editor, context);
|
||||
I found <caret>it in a legendary land
|
||||
""".trimIndent(), """
|
||||
A Discovery
|
||||
|
||||
I found it in a i<caret>t land
|
||||
""".trimIndent())
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@ -1,11 +1,27 @@
|
||||
/*
|
||||
* 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;
|
||||
|
||||
import java.util.Arrays;
|
||||
|
||||
/**
|
||||
* @author Naoto Ikeno
|
||||
*/
|
||||
@ -19,7 +35,7 @@ public class ActionListCommandTest extends VimTestCase {
|
||||
|
||||
// Header line
|
||||
String[] displayedLines = output.split("\n");
|
||||
assertEquals(displayedLines[0], "--- Actions ---");
|
||||
assertEquals("--- Actions ---", displayedLines[0]);
|
||||
|
||||
// Action lines
|
||||
int displayedActionNum = displayedLines.length - 1;
|
||||
@ -35,7 +51,7 @@ public class ActionListCommandTest extends VimTestCase {
|
||||
for (int i = 0; i < displayedLines.length; i++) {
|
||||
String line = displayedLines[i];
|
||||
if (i == 0) {
|
||||
assertEquals(line, "--- Actions ---");
|
||||
assertEquals("--- Actions ---", line);
|
||||
}else {
|
||||
assertTrue(line.toLowerCase().contains("quickimpl"));
|
||||
}
|
||||
@ -50,7 +66,7 @@ public class ActionListCommandTest extends VimTestCase {
|
||||
for (int i = 0; i < displayedLines.length; i++) {
|
||||
String line = displayedLines[i];
|
||||
if (i == 0) {
|
||||
assertEquals(line, "--- Actions ---");
|
||||
assertEquals("--- Actions ---", line);
|
||||
}else {
|
||||
assertTrue(line.toLowerCase().contains("<m-s-"));
|
||||
}
|
||||
|
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
|
||||
}
|
Loading…
Reference in New Issue
Block a user