mirror of
https://github.com/chylex/IntelliJ-IdeaVim.git
synced 2025-08-18 10:31:44 +02:00
Compare commits
67 Commits
0.55.1-EAP
...
0.55.2-EAP
Author | SHA1 | Date | |
---|---|---|---|
![]() |
2f5946640e | ||
![]() |
7cdb7dc308 | ||
![]() |
c0038d0373 | ||
![]() |
2820decb5e | ||
![]() |
c64f368e6a | ||
![]() |
b7c8e84f5e | ||
![]() |
5acf6c9158 | ||
![]() |
a8197b0c84 | ||
![]() |
2e03062c24 | ||
![]() |
7fb60a185b | ||
![]() |
0327ea972b | ||
![]() |
561cc77ecc | ||
![]() |
a1ab4acd14 | ||
![]() |
d4939803da | ||
![]() |
730ce3aca9 | ||
![]() |
1893dc6afd | ||
![]() |
c87528939b | ||
![]() |
e56646105d | ||
![]() |
b8a40d93f7 | ||
![]() |
4bfc025248 | ||
![]() |
36f6027b0e | ||
![]() |
e032377e68 | ||
![]() |
929eee4a12 | ||
![]() |
61ce50264a | ||
![]() |
48927b1207 | ||
![]() |
0820893dc6 | ||
![]() |
dd6079cfa6 | ||
![]() |
3d7d75bae4 | ||
![]() |
6da4d0ce5e | ||
![]() |
4994d70b1a | ||
![]() |
c873081dc3 | ||
![]() |
070237f77f | ||
![]() |
eb01b25f35 | ||
![]() |
c0c9cfaf86 | ||
![]() |
304f860eb2 | ||
![]() |
2f18b25593 | ||
![]() |
adaa683e58 | ||
![]() |
5ee0a93675 | ||
![]() |
767b3c4a39 | ||
![]() |
bb948a463c | ||
![]() |
e4e9a03d0a | ||
![]() |
50ba386f59 | ||
![]() |
79d0565c2d | ||
![]() |
bcc9b0a7b1 | ||
![]() |
2c8f4940b9 | ||
![]() |
41876cf8fd | ||
![]() |
f6fd0b52f0 | ||
![]() |
843faa7cc6 | ||
![]() |
a8af2c3242 | ||
![]() |
e5bfad974e | ||
![]() |
59d87e0c94 | ||
![]() |
50c2d04503 | ||
![]() |
480de62686 | ||
![]() |
955b501058 | ||
![]() |
d985527624 | ||
![]() |
afbe7f0e69 | ||
![]() |
94e65ddce6 | ||
![]() |
cb9f144255 | ||
![]() |
ac84624faa | ||
![]() |
c2196785e7 | ||
![]() |
30097fbae6 | ||
![]() |
c295dd5c62 | ||
![]() |
373fef2824 | ||
![]() |
cfc255bf2b | ||
![]() |
ea7e58535b | ||
![]() |
ff209d0120 | ||
![]() |
ea2fe618b5 |
12
AUTHORS.md
12
AUTHORS.md
@@ -271,6 +271,18 @@ Contributors:
|
||||
[![icon][github]](https://github.com/igrekster)
|
||||
|
||||
igrekster
|
||||
* [![icon][mail]](mailto:lokomot476@gmail.com)
|
||||
[![icon][github]](https://github.com/fan-tom)
|
||||
|
||||
Alexey Gerasimov
|
||||
* [![icon][mail]](mailto:a.grison+github@gmail.com)
|
||||
[![icon][github]](https://github.com/agrison)
|
||||
|
||||
Alexandre Grison
|
||||
* [![icon][mail]](mailto:angel@knight-industries.com)
|
||||
[![icon][github]](https://github.com/angelbot)
|
||||
|
||||
John Weigel
|
||||
|
||||
If you are a contributor and your name is not listed here, feel free to
|
||||
contact the maintainers.
|
||||
|
13
CHANGES.md
13
CHANGES.md
@@ -29,6 +29,19 @@ _Available since 0.55.1 EAP:_
|
||||
* [VIM-1835](https://youtrack.jetbrains.com/issue/VIM-1835) Macros record input keystrokes instead of mapped keystrokes
|
||||
* [VIM-1900](https://youtrack.jetbrains.com/issue/VIM-1900) Ensure non-printable output for `:registers`, `:marks` and `:jumps` is encoded correctly
|
||||
|
||||
_Available since 0.55.2 EAP:_
|
||||
|
||||
**Features:**
|
||||
* `argtextobj.vim` plugin emulation ([argtextobj.vim](https://vim.sourceforge.io/scripts/script.php?script_id=2699))
|
||||
* `vim-textobj-entire` plugin emulation ([vim-textobj-entire](https://github.com/kana/vim-textobj-entire))
|
||||
* Support `ls/buffers/files` commands
|
||||
|
||||
**Changes:**
|
||||
* Replace `ideastatusbar` option with `ideastatusicon`. Now you can make the icon gray.
|
||||
|
||||
**Fixes:**
|
||||
* [VIM-1008](https://youtrack.jetbrains.com/issue/VIM-1008) Correct `ci{` behavior
|
||||
|
||||
0.55, 2020-01-20
|
||||
--------------
|
||||
|
||||
|
@@ -16,6 +16,18 @@
|
||||
</a>
|
||||
<span>2019.2 Tests</span>
|
||||
</div>
|
||||
<div>
|
||||
<a href="https://teamcity.jetbrains.com/viewType.html?buildTypeId=IdeaVim_TestsForIntelliJ20193&guest=1">
|
||||
<img src="https://teamcity.jetbrains.com/app/rest/builds/buildType:(id:IdeaVim_TestsForIntelliJ20193)/statusIcon.svg?guest=1"/>
|
||||
</a>
|
||||
<span>2019.3 Tests</span>
|
||||
</div>
|
||||
<div>
|
||||
<a href="https://teamcity.jetbrains.com/viewType.html?buildTypeId=IdeaVim_TestsForIntelliJ20201&guest=1">
|
||||
<img src="https://teamcity.jetbrains.com/app/rest/builds/buildType:(id:IdeaVim_TestsForIntelliJ20201)/statusIcon.svg?guest=1"/>
|
||||
</a>
|
||||
<span>2020.1 Tests</span>
|
||||
</div>
|
||||
|
||||
|
||||
### Where to Start
|
||||
|
16
README.md
16
README.md
@@ -7,8 +7,8 @@ IdeaVim
|
||||
<a href="https://confluence.jetbrains.com/display/ALL/JetBrains+on+GitHub">
|
||||
<img src="https://jb.gg/badges/official.svg" alt="official JetBrains project"/>
|
||||
</a>
|
||||
<a href="https://teamcity.jetbrains.com/viewType.html?buildTypeId=IdeaVim_TestsForIntelliJ20191&guest=1">
|
||||
<img src="https://teamcity.jetbrains.com/app/rest/builds/buildType:(id:IdeaVim_TestsForIntelliJ20191)/statusIcon.svg?guest=1"/>
|
||||
<a href="https://teamcity.jetbrains.com/viewType.html?buildTypeId=IdeaVim_TestsForIntelliJ20201&guest=1">
|
||||
<img src="https://teamcity.jetbrains.com/app/rest/builds/buildType:(id:IdeaVim_TestsForIntelliJ20201)/statusIcon.svg?guest=1" alt="TeamCity Build"/>
|
||||
</a>
|
||||
</div>
|
||||
|
||||
@@ -97,6 +97,8 @@ Emulated Vim plugins:
|
||||
* vim-surround
|
||||
* vim-multiple-cursors
|
||||
* vim-commentary
|
||||
* argtextobj.vim [To Be Released]
|
||||
* vim-textobj-entire [To Be Released]
|
||||
|
||||
Not supported (yet):
|
||||
|
||||
@@ -165,6 +167,16 @@ Available extensions:
|
||||
* Emulates [commentary.vim](https://github.com/tpope/vim-commentary)
|
||||
* Commands: `gcc`, `gc + motion`, `v_gc`
|
||||
|
||||
* argtextobj [To Be Released]
|
||||
* Setup: `set argtextobj`
|
||||
* Emulates [argtextobj.vim](https://www.vim.org/scripts/script.php?script_id=2699)
|
||||
* Additional text objects: `aa`, `ia`
|
||||
|
||||
* textobj-entire [To Be Released]
|
||||
* Setup: `set textobj-entire`
|
||||
* Emulates [vim-textobj-entire](https://github.com/kana/vim-textobj-entire)
|
||||
* Additional text objects: `ae`, `ie`
|
||||
|
||||
|
||||
Changes to the IDE
|
||||
------------------
|
||||
|
@@ -9,7 +9,7 @@ buildscript {
|
||||
}
|
||||
|
||||
plugins {
|
||||
id 'org.jetbrains.intellij' version '0.4.9'
|
||||
id 'org.jetbrains.intellij' version '0.4.16'
|
||||
}
|
||||
|
||||
apply plugin: 'java'
|
||||
@@ -56,7 +56,7 @@ repositories {
|
||||
|
||||
dependencies {
|
||||
compileOnly "org.jetbrains.kotlin:kotlin-stdlib-jdk8:$kotlinVersion"
|
||||
compileOnly "org.jetbrains:annotations:17.0.0"
|
||||
compileOnly "org.jetbrains:annotations:19.0.0"
|
||||
}
|
||||
|
||||
compileKotlin {
|
||||
|
@@ -104,10 +104,22 @@ The following `:set` commands can appear in `~/.ideavimrc` or be set manually in
|
||||
See wiki/`ideajoin` examples
|
||||
|
||||
`ideastatusbar` `ideastatusbar` Boolean (default true)
|
||||
DEPRECATED. Please use `ideastatusicon`
|
||||
|
||||
If false, IdeaVim icon won't be shown in the status bar.
|
||||
Works only from `~/.ideavimrc` after the IDE restart.
|
||||
|
||||
`ideastatusicon` `ideastatusicon` String(default "enabled") [To Be Released]
|
||||
|
||||
Define the behavior of IdeaVim icon in the status bar.
|
||||
|
||||
Use one of the following values:
|
||||
- enabled - icon is shown in the status bar
|
||||
- gray - use the gray version of the icon
|
||||
- disabled - hide the icon
|
||||
|
||||
Works only from `~/.ideavimrc` after the IDE restart.
|
||||
|
||||
`lookupkeys` `lookupkeys` List of strings
|
||||
|
||||
List of keys that should be processed by the IDE during the active lookup (autocompletion).
|
||||
|
BIN
gradle/wrapper/gradle-wrapper.jar
vendored
BIN
gradle/wrapper/gradle-wrapper.jar
vendored
Binary file not shown.
5
gradle/wrapper/gradle-wrapper.properties
vendored
5
gradle/wrapper/gradle-wrapper.properties
vendored
@@ -1,6 +1,5 @@
|
||||
#Fri Nov 22 14:42:01 MSK 2019
|
||||
distributionUrl=https\://services.gradle.org/distributions/gradle-6.0.1-all.zip
|
||||
distributionBase=GRADLE_USER_HOME
|
||||
distributionPath=wrapper/dists
|
||||
zipStorePath=wrapper/dists
|
||||
distributionUrl=https\://services.gradle.org/distributions/gradle-6.2.1-bin.zip
|
||||
zipStoreBase=GRADLE_USER_HOME
|
||||
zipStorePath=wrapper/dists
|
||||
|
22
gradlew
vendored
22
gradlew
vendored
@@ -1,5 +1,21 @@
|
||||
#!/usr/bin/env sh
|
||||
|
||||
#
|
||||
# Copyright 2015 the original author or authors.
|
||||
#
|
||||
# Licensed under the Apache License, Version 2.0 (the "License");
|
||||
# you may not use this file except in compliance with the License.
|
||||
# You may obtain a copy of the License at
|
||||
#
|
||||
# https://www.apache.org/licenses/LICENSE-2.0
|
||||
#
|
||||
# Unless required by applicable law or agreed to in writing, software
|
||||
# distributed under the License is distributed on an "AS IS" BASIS,
|
||||
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
# See the License for the specific language governing permissions and
|
||||
# limitations under the License.
|
||||
#
|
||||
|
||||
##############################################################################
|
||||
##
|
||||
## Gradle start up script for UN*X
|
||||
@@ -28,7 +44,7 @@ APP_NAME="Gradle"
|
||||
APP_BASE_NAME=`basename "$0"`
|
||||
|
||||
# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
|
||||
DEFAULT_JVM_OPTS='"-Xmx64m"'
|
||||
DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"'
|
||||
|
||||
# Use the maximum available, or set MAX_FD != -1 to use that value.
|
||||
MAX_FD="maximum"
|
||||
@@ -109,8 +125,8 @@ if $darwin; then
|
||||
GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\""
|
||||
fi
|
||||
|
||||
# For Cygwin, switch paths to Windows format before running java
|
||||
if $cygwin ; then
|
||||
# For Cygwin or MSYS, switch paths to Windows format before running java
|
||||
if [ "$cygwin" = "true" -o "$msys" = "true" ] ; then
|
||||
APP_HOME=`cygpath --path --mixed "$APP_HOME"`
|
||||
CLASSPATH=`cygpath --path --mixed "$CLASSPATH"`
|
||||
JAVACMD=`cygpath --unix "$JAVACMD"`
|
||||
|
18
gradlew.bat
vendored
18
gradlew.bat
vendored
@@ -1,3 +1,19 @@
|
||||
@rem
|
||||
@rem Copyright 2015 the original author or authors.
|
||||
@rem
|
||||
@rem Licensed under the Apache License, Version 2.0 (the "License");
|
||||
@rem you may not use this file except in compliance with the License.
|
||||
@rem You may obtain a copy of the License at
|
||||
@rem
|
||||
@rem https://www.apache.org/licenses/LICENSE-2.0
|
||||
@rem
|
||||
@rem Unless required by applicable law or agreed to in writing, software
|
||||
@rem distributed under the License is distributed on an "AS IS" BASIS,
|
||||
@rem WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
@rem See the License for the specific language governing permissions and
|
||||
@rem limitations under the License.
|
||||
@rem
|
||||
|
||||
@if "%DEBUG%" == "" @echo off
|
||||
@rem ##########################################################################
|
||||
@rem
|
||||
@@ -14,7 +30,7 @@ set APP_BASE_NAME=%~n0
|
||||
set APP_HOME=%DIRNAME%
|
||||
|
||||
@rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
|
||||
set DEFAULT_JVM_OPTS="-Xmx64m"
|
||||
set DEFAULT_JVM_OPTS="-Xmx64m" "-Xms64m"
|
||||
|
||||
@rem Find java.exe
|
||||
if defined JAVA_HOME goto findJavaFromJavaHome
|
||||
|
@@ -60,5 +60,6 @@
|
||||
<vimExCommand implementation="com.maddyhome.idea.vim.ex.handler.NextTabHandler" names="tabn[ext]"/>
|
||||
<vimExCommand implementation="com.maddyhome.idea.vim.ex.handler.PreviousTabHandler" names="tabp[revious],tabN[ext]"/>
|
||||
<vimExCommand implementation="com.maddyhome.idea.vim.ex.handler.TabOnlyHandler" names="tabo[nly]"/>
|
||||
<vimExCommand implementation="com.maddyhome.idea.vim.ex.handler.BufferListHandler" names="buffers,ls,files"/>
|
||||
</extensions>
|
||||
</idea-plugin>
|
||||
|
@@ -3,5 +3,7 @@
|
||||
<vimExtension implementation="com.maddyhome.idea.vim.extension.surround.VimSurroundExtension"/>
|
||||
<vimExtension implementation="com.maddyhome.idea.vim.extension.multiplecursors.VimMultipleCursorsExtension"/>
|
||||
<vimExtension implementation="com.maddyhome.idea.vim.extension.commentary.CommentaryExtension"/>
|
||||
<vimExtension implementation="com.maddyhome.idea.vim.extension.textobjentire.VimTextObjEntireExtension"/>
|
||||
<vimExtension implementation="com.maddyhome.idea.vim.extension.argtextobj.VimArgTextObjExtension"/>
|
||||
</extensions>
|
||||
</idea-plugin>
|
||||
|
@@ -3,9 +3,9 @@
|
||||
<id>IdeaVIM</id>
|
||||
<change-notes><![CDATA[
|
||||
<ul>
|
||||
<li>Support dot command for Surround and Commentary extensions</li>
|
||||
<li>Support XDG settings standard</li>
|
||||
<li>Add option to remove the status bar icon</li>
|
||||
<li>Support argtextobj.vim plugin emulation</li>
|
||||
<li>Support vim-textobj-entire plugin emulation</li>
|
||||
<li>Support ls/buffers/files command</li>
|
||||
<li>Various bug fixes</li>
|
||||
</ul>
|
||||
<p>See also the complete <a href="https://github.com/JetBrains/ideavim/blob/master/CHANGES.md">changelog</a>.</p>
|
||||
@@ -34,25 +34,16 @@
|
||||
<component>
|
||||
<implementation-class>com.maddyhome.idea.vim.DynamicLoaderStopper</implementation-class>
|
||||
</component>
|
||||
<component>
|
||||
<implementation-class>com.maddyhome.idea.vim.VimPlugin</implementation-class>
|
||||
</component>
|
||||
<component>
|
||||
<implementation-class>com.maddyhome.idea.vim.VimLocalConfig</implementation-class>
|
||||
</component>
|
||||
</application-components>
|
||||
<project-components>
|
||||
<component>
|
||||
<implementation-class>com.maddyhome.idea.vim.VimProjectComponent</implementation-class>
|
||||
</component>
|
||||
</project-components>
|
||||
|
||||
<extensionPoints>
|
||||
<extensionPoint name="vimExtension" interface="com.maddyhome.idea.vim.extension.VimExtension"/>
|
||||
<extensionPoint name="vimExtension" interface="com.maddyhome.idea.vim.extension.VimExtension" dynamic="true"/>
|
||||
|
||||
<!-- For internal use only -->
|
||||
<extensionPoint name="vimExCommand" beanClass="com.maddyhome.idea.vim.ex.ExBeanClass" dynamic="true">
|
||||
<with attribute="implementation" implements="com.maddyhome.idea.vim.ex.CommandHandler"/>
|
||||
</extensionPoint>
|
||||
<!-- For internal use only -->
|
||||
<extensionPoint name="vimAction" beanClass="com.maddyhome.idea.vim.handler.ActionBeanClass" dynamic="true">
|
||||
<with attribute="implementation" implements="com.maddyhome.idea.vim.handler.EditorActionHandlerBase"/>
|
||||
</extensionPoint>
|
||||
@@ -62,6 +53,11 @@
|
||||
<applicationConfigurable groupId="editor" instance="com.maddyhome.idea.vim.ui.VimEmulationConfigurable"/>
|
||||
<projectService serviceImplementation="com.maddyhome.idea.vim.group.NotificationService"/>
|
||||
<statusBarWidgetProvider implementation="com.maddyhome.idea.vim.StatusBarIconProvider"/>
|
||||
|
||||
<applicationService serviceImplementation="com.maddyhome.idea.vim.VimLocalConfig"/>
|
||||
<applicationService serviceImplementation="com.maddyhome.idea.vim.VimPlugin"/>
|
||||
|
||||
<postStartupActivity implementation="com.maddyhome.idea.vim.PluginStartup"/>
|
||||
</extensions>
|
||||
|
||||
<xi:include href="/META-INF/includes/ApplicationServices.xml" xpointer="xpointer(/idea-plugin/*)"/>
|
||||
|
@@ -53,16 +53,15 @@ import java.util.Map;
|
||||
* @author vlan
|
||||
*/
|
||||
public class EventFacade {
|
||||
@NotNull private static final EventFacade ourInstance = new EventFacade();
|
||||
private static final @NotNull EventFacade ourInstance = new EventFacade();
|
||||
|
||||
@Nullable private TypedActionHandler myOriginalTypedActionHandler;
|
||||
private @Nullable TypedActionHandler myOriginalTypedActionHandler;
|
||||
private Map<Project, MessageBusConnection> connections = new HashMap<>();
|
||||
|
||||
private EventFacade() {
|
||||
}
|
||||
|
||||
@NotNull
|
||||
public static EventFacade getInstance() {
|
||||
public static @NotNull EventFacade getInstance() {
|
||||
return ourInstance;
|
||||
}
|
||||
|
||||
@@ -193,8 +192,7 @@ public class EventFacade {
|
||||
return connections.get(project);
|
||||
}
|
||||
|
||||
@NotNull
|
||||
private TypedAction getTypedAction() {
|
||||
private @NotNull TypedAction getTypedAction() {
|
||||
return EditorActionManager.getInstance().getTypedAction();
|
||||
}
|
||||
}
|
||||
|
@@ -56,8 +56,10 @@ import org.jetbrains.annotations.Nullable;
|
||||
import javax.swing.*;
|
||||
import java.awt.*;
|
||||
import java.awt.event.KeyEvent;
|
||||
import java.util.ArrayList;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.*;
|
||||
import java.util.Map;
|
||||
import java.util.function.Function;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
@@ -74,8 +76,7 @@ public class KeyHandler {
|
||||
*
|
||||
* @return A reference to the singleton
|
||||
*/
|
||||
@NotNull
|
||||
public static KeyHandler getInstance() {
|
||||
public static @NotNull KeyHandler getInstance() {
|
||||
if (instance == null) {
|
||||
instance = new KeyHandler();
|
||||
}
|
||||
@@ -306,7 +307,7 @@ public class KeyHandler {
|
||||
return true;
|
||||
}
|
||||
|
||||
private void handleEditorReset(@NotNull Editor editor, @NotNull KeyStroke key, @NotNull final DataContext context, @NotNull CommandState editorState) {
|
||||
private void handleEditorReset(@NotNull Editor editor, @NotNull KeyStroke key, final @NotNull DataContext context, @NotNull CommandState editorState) {
|
||||
if (editorState.getCommandBuilder().isAtDefaultState()) {
|
||||
RegisterGroup register = VimPlugin.getRegister();
|
||||
if (register.getCurrentRegister() == register.getDefaultRegister()) {
|
||||
@@ -321,9 +322,9 @@ public class KeyHandler {
|
||||
ChangeGroup.resetCaret(editor, false);
|
||||
}
|
||||
|
||||
private boolean handleKeyMapping(@NotNull final Editor editor,
|
||||
@NotNull final KeyStroke key,
|
||||
@NotNull final DataContext context) {
|
||||
private boolean handleKeyMapping(final @NotNull Editor editor,
|
||||
final @NotNull KeyStroke key,
|
||||
final @NotNull DataContext context) {
|
||||
|
||||
final CommandState commandState = CommandState.getInstance(editor);
|
||||
final MappingState mappingState = commandState.getMappingState();
|
||||
@@ -430,10 +431,8 @@ public class KeyHandler {
|
||||
|
||||
final EditorDataContext currentContext = new EditorDataContext(editor);
|
||||
|
||||
final List<KeyStroke> toKeys = mappingInfo.getToKeys();
|
||||
final VimExtensionHandler extensionHandler = mappingInfo.getExtensionHandler();
|
||||
|
||||
if (toKeys != null) {
|
||||
if (mappingInfo instanceof ToKeysMappingInfo) {
|
||||
final List<KeyStroke> toKeys = ((ToKeysMappingInfo)mappingInfo).getToKeys();
|
||||
final boolean fromIsPrefix = isPrefix(mappingInfo.getFromKeys(), toKeys);
|
||||
boolean first = true;
|
||||
for (KeyStroke keyStroke : toKeys) {
|
||||
@@ -442,7 +441,8 @@ public class KeyHandler {
|
||||
first = false;
|
||||
}
|
||||
}
|
||||
else if (extensionHandler != null) {
|
||||
else if (mappingInfo instanceof ToHandlerMappingInfo) {
|
||||
final VimExtensionHandler extensionHandler = ((ToHandlerMappingInfo)mappingInfo).getExtensionHandler();
|
||||
final CommandProcessor processor = CommandProcessor.getInstance();
|
||||
|
||||
// Cache isOperatorPending in case the extension changes the mode while moving the caret
|
||||
@@ -810,8 +810,7 @@ public class KeyHandler {
|
||||
editorState.getCommandBuilder().resetAll(getKeyRoot(editorState.getMappingState().getMappingMode()));
|
||||
}
|
||||
|
||||
@NotNull
|
||||
private CommandPartNode getKeyRoot(MappingMode mappingMode) {
|
||||
private @NotNull CommandPartNode getKeyRoot(MappingMode mappingMode) {
|
||||
return VimPlugin.getKey().getKeyRoot(mappingMode);
|
||||
}
|
||||
|
||||
@@ -825,7 +824,10 @@ public class KeyHandler {
|
||||
VimPlugin.clearError();
|
||||
CommandState.getInstance(editor).reset();
|
||||
reset(editor);
|
||||
VimPlugin.getRegister().resetRegister();
|
||||
RegisterGroup registerGroup = VimPlugin.getRegisterIfCreated();
|
||||
if (registerGroup != null) {
|
||||
registerGroup.resetRegister();
|
||||
}
|
||||
if (editor != null) {
|
||||
VisualGroupKt.updateCaretState(editor);
|
||||
editor.getSelectionModel().removeSelection();
|
||||
@@ -833,9 +835,8 @@ public class KeyHandler {
|
||||
}
|
||||
|
||||
// This method is copied from com.intellij.openapi.editor.actionSystem.EditorAction.getProjectAwareDataContext
|
||||
@NotNull
|
||||
private static DataContext getProjectAwareDataContext(@NotNull final Editor editor,
|
||||
@NotNull final DataContext original) {
|
||||
private static @NotNull DataContext getProjectAwareDataContext(final @NotNull Editor editor,
|
||||
final @NotNull DataContext original) {
|
||||
if (PROJECT.getData(original) == editor.getProject()) {
|
||||
return new DialogAwareDataContext(original);
|
||||
}
|
||||
@@ -853,7 +854,7 @@ public class KeyHandler {
|
||||
}
|
||||
|
||||
// This class is copied from com.intellij.openapi.editor.actionSystem.DialogAwareDataContext.DialogAwareDataContext
|
||||
private final static class DialogAwareDataContext implements DataContext {
|
||||
private static final class DialogAwareDataContext implements DataContext {
|
||||
@SuppressWarnings("rawtypes")
|
||||
private static final DataKey[] keys = {PROJECT, PROJECT_FILE_DIRECTORY, EDITOR, VIRTUAL_FILE, PSI_FILE};
|
||||
private final Map<String, Object> values = new HashMap<>();
|
||||
@@ -865,9 +866,8 @@ public class KeyHandler {
|
||||
}
|
||||
}
|
||||
|
||||
@Nullable
|
||||
@Override
|
||||
public Object getData(@NotNull @NonNls String dataId) {
|
||||
public @Nullable Object getData(@NotNull @NonNls String dataId) {
|
||||
if (values.containsKey(dataId)) {
|
||||
return values.get(dataId);
|
||||
}
|
||||
|
48
src/com/maddyhome/idea/vim/PluginStartup.kt
Normal file
48
src/com/maddyhome/idea/vim/PluginStartup.kt
Normal file
@@ -0,0 +1,48 @@
|
||||
/*
|
||||
* IdeaVim - Vim emulator for IDEs based on the IntelliJ platform
|
||||
* Copyright (C) 2003-2020 The IdeaVim authors
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 2 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package com.maddyhome.idea.vim
|
||||
|
||||
import com.intellij.openapi.project.DumbAware
|
||||
import com.intellij.openapi.project.Project
|
||||
import com.intellij.openapi.startup.StartupActivity
|
||||
import com.maddyhome.idea.vim.listener.VimListenerManager
|
||||
|
||||
/**
|
||||
* @author Alex Plate
|
||||
*
|
||||
* [VERSION UPDATE] 193+ Use StartupActivity.DumbAware
|
||||
*/
|
||||
class PluginStartup : StartupActivity, DumbAware {
|
||||
|
||||
private var firstInitializationOccurred = false
|
||||
|
||||
override fun runActivity(project: Project) {
|
||||
if (firstInitializationOccurred && VimPlugin.isEnabled()) {
|
||||
// This code should be executed on every project open
|
||||
// Project listeners are self-disposable, so there is no need to unregister them on project close
|
||||
VimListenerManager.ProjectListeners.add(project)
|
||||
}
|
||||
|
||||
if (firstInitializationOccurred) return
|
||||
firstInitializationOccurred = true
|
||||
|
||||
// This code should be executed once
|
||||
VimPlugin.getInstance().initialize()
|
||||
}
|
||||
}
|
@@ -23,6 +23,7 @@ import com.intellij.openapi.extensions.PluginDescriptor;
|
||||
import com.maddyhome.idea.vim.group.KeyGroup;
|
||||
import com.maddyhome.idea.vim.handler.ActionBeanClass;
|
||||
import com.maddyhome.idea.vim.handler.EditorActionHandlerBase;
|
||||
import com.maddyhome.idea.vim.key.MappingOwner;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
|
||||
@@ -44,7 +45,7 @@ public class RegisterActions {
|
||||
// ExtensionPoint.addExtensionPointListener(ExtensionPointChangeListener, boolean, Disposable)
|
||||
VIM_ACTIONS_EP.getPoint(null).addExtensionPointListener(new ExtensionPointListener<ActionBeanClass>() {
|
||||
@Override
|
||||
public void extensionAdded(@NotNull ActionBeanClass extension, @NotNull PluginDescriptor pluginDescriptor) {
|
||||
public void extensionAdded(@NotNull ActionBeanClass extension, PluginDescriptor pluginDescriptor) {
|
||||
// Suppress listener before the `VimPlugin.turnOn()` function execution. This logic should be rewritten after
|
||||
// version update (or earlier).
|
||||
if (!initialRegistration) return;
|
||||
@@ -53,7 +54,7 @@ public class RegisterActions {
|
||||
}
|
||||
|
||||
@Override
|
||||
public void extensionRemoved(@NotNull ActionBeanClass extension, @NotNull PluginDescriptor pluginDescriptor) {
|
||||
public void extensionRemoved(@NotNull ActionBeanClass extension, PluginDescriptor pluginDescriptor) {
|
||||
if (!initialRegistration) return;
|
||||
unregisterActions();
|
||||
registerActions();
|
||||
@@ -64,27 +65,28 @@ public class RegisterActions {
|
||||
/**
|
||||
* Register all the key/action mappings for the plugin.
|
||||
*/
|
||||
static void registerActions() {
|
||||
public static void registerActions() {
|
||||
registerVimCommandActions();
|
||||
registerEmptyShortcuts();
|
||||
initialRegistration = true;
|
||||
}
|
||||
|
||||
@Nullable
|
||||
public static EditorActionHandlerBase findAction(@NotNull String id) {
|
||||
public static @Nullable EditorActionHandlerBase findAction(@NotNull String id) {
|
||||
return VIM_ACTIONS_EP.extensions().filter(vimActionBean -> vimActionBean.getActionId().equals(id)).findFirst()
|
||||
.map(ActionBeanClass::getAction).orElse(null);
|
||||
}
|
||||
|
||||
@NotNull
|
||||
public static EditorActionHandlerBase findActionOrDie(@NotNull String id) {
|
||||
public static @NotNull EditorActionHandlerBase findActionOrDie(@NotNull String id) {
|
||||
EditorActionHandlerBase action = findAction(id);
|
||||
if (action == null) throw new RuntimeException("Action " + id + " is not registered");
|
||||
return action;
|
||||
}
|
||||
|
||||
public static void unregisterActions() {
|
||||
VimPlugin.getKey().unregisterCommandActions();
|
||||
KeyGroup keyGroup = VimPlugin.getKeyIfCreated();
|
||||
if (keyGroup != null) {
|
||||
keyGroup.unregisterCommandActions();
|
||||
}
|
||||
}
|
||||
|
||||
private static void registerVimCommandActions() {
|
||||
@@ -97,6 +99,6 @@ public class RegisterActions {
|
||||
|
||||
// The {char1} <BS> {char2} shortcut is handled directly by KeyHandler#handleKey, so doesn't have an action. But we
|
||||
// still need to register the shortcut, to make sure the editor doesn't swallow it.
|
||||
parser.registerShortcutWithoutAction(KeyStroke.getKeyStroke(KeyEvent.VK_BACK_SPACE, 0));
|
||||
parser.registerShortcutWithoutAction(KeyStroke.getKeyStroke(KeyEvent.VK_BACK_SPACE, 0), MappingOwner.IdeaVim.INSTANCE);
|
||||
}
|
||||
}
|
||||
|
@@ -50,6 +50,7 @@ import com.intellij.ui.awt.RelativePoint
|
||||
import com.intellij.util.Consumer
|
||||
import com.intellij.util.text.VersionComparatorUtil
|
||||
import com.maddyhome.idea.vim.group.NotificationService
|
||||
import com.maddyhome.idea.vim.option.IdeaStatusIcon
|
||||
import com.maddyhome.idea.vim.option.OptionsManager
|
||||
import com.maddyhome.idea.vim.ui.VimEmulationConfigurable
|
||||
import icons.VimIcons
|
||||
@@ -59,7 +60,11 @@ import javax.swing.Icon
|
||||
import javax.swing.SwingConstants
|
||||
|
||||
private class StatusBarIconProvider : StatusBarWidgetProvider {
|
||||
override fun getWidget(project: Project): VimStatusBar? = if (OptionsManager.ideastatusbar.isSet) VimStatusBar else null
|
||||
override fun getWidget(project: Project): VimStatusBar? {
|
||||
if (!OptionsManager.ideastatusbar.isSet) return null
|
||||
if (OptionsManager.ideastatusicon.value == IdeaStatusIcon.disabled) return null
|
||||
return VimStatusBar
|
||||
}
|
||||
}
|
||||
|
||||
object VimStatusBar : StatusBarWidget, StatusBarWidget.IconPresentation {
|
||||
@@ -76,7 +81,10 @@ object VimStatusBar : StatusBarWidget, StatusBarWidget.IconPresentation {
|
||||
|
||||
override fun getTooltipText() = "IdeaVim"
|
||||
|
||||
override fun getIcon(): Icon = if (VimPlugin.isEnabled()) VimIcons.IDEAVIM else VimIcons.IDEAVIM_DISABLED
|
||||
override fun getIcon(): Icon {
|
||||
if (OptionsManager.ideastatusicon.value == IdeaStatusIcon.gray) return VimIcons.IDEAVIM_DISABLED
|
||||
return if (VimPlugin.isEnabled()) VimIcons.IDEAVIM else VimIcons.IDEAVIM_DISABLED
|
||||
}
|
||||
|
||||
override fun getClickConsumer() = Consumer<MouseEvent> { event ->
|
||||
val component = event.component
|
||||
|
@@ -20,9 +20,9 @@ package com.maddyhome.idea.vim
|
||||
|
||||
import com.intellij.openapi.components.PersistentStateComponent
|
||||
import com.intellij.openapi.components.RoamingType
|
||||
import com.intellij.openapi.components.ServiceManager
|
||||
import com.intellij.openapi.components.State
|
||||
import com.intellij.openapi.components.Storage
|
||||
import com.maddyhome.idea.vim.VimPlugin.STATE_VERSION
|
||||
import org.jdom.Element
|
||||
|
||||
/**
|
||||
@@ -31,28 +31,26 @@ import org.jdom.Element
|
||||
|
||||
@State(name = "VimLocalSettings", storages = [
|
||||
Storage("\$APP_CONFIG$$/vim_local_settings.xml", roamingType = RoamingType.DISABLED, deprecated = true),
|
||||
Storage("\$APP_CONFIG$/vim_local_settings.xml", roamingType = RoamingType.DISABLED)
|
||||
Storage("\$APP_CONFIG$/vim_local_settings.xml", roamingType = RoamingType.DISABLED, deprecated = true)
|
||||
])
|
||||
// TODO: 27.01.2020 [VERSION UPDATE] 2019.3 https://www.jetbrains.org/intellij/sdk/docs/basics/plugin_structure/plugin_services.html#light-services
|
||||
@Deprecated("The data from this class will be stored in vim_settings")
|
||||
class VimLocalConfig : PersistentStateComponent<Element> {
|
||||
override fun getState(): Element {
|
||||
val element = Element("ideavim-local")
|
||||
|
||||
val state = Element("state")
|
||||
state.setAttribute("version", STATE_VERSION.toString())
|
||||
element.addContent(state)
|
||||
|
||||
VimPlugin.getMark().saveData(element)
|
||||
VimPlugin.getRegister().saveData(element)
|
||||
VimPlugin.getSearch().saveData(element)
|
||||
VimPlugin.getHistory().saveData(element)
|
||||
return element
|
||||
}
|
||||
override fun getState(): Element? = null
|
||||
|
||||
override fun loadState(state: Element) {
|
||||
// This is initialization of state from the legacy configuration structure.
|
||||
// This code should be performed only once on settings migration.
|
||||
// After the migration is done, the file with settings gets removed and this method won't be called again.
|
||||
VimPlugin.getMark().readData(state)
|
||||
VimPlugin.getRegister().readData(state)
|
||||
VimPlugin.getSearch().readData(state)
|
||||
VimPlugin.getHistory().readData(state)
|
||||
}
|
||||
|
||||
companion object {
|
||||
fun initialize() {
|
||||
ServiceManager.getService(VimLocalConfig::class.java)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@@ -23,10 +23,14 @@ import com.intellij.ide.util.PropertiesComponent;
|
||||
import com.intellij.notification.Notification;
|
||||
import com.intellij.notification.NotificationListener;
|
||||
import com.intellij.openapi.Disposable;
|
||||
import com.intellij.openapi.application.Application;
|
||||
import com.intellij.openapi.application.ApplicationInfo;
|
||||
import com.intellij.openapi.application.ApplicationManager;
|
||||
import com.intellij.openapi.application.PermanentInstallationID;
|
||||
import com.intellij.openapi.components.*;
|
||||
import com.intellij.openapi.components.PersistentStateComponent;
|
||||
import com.intellij.openapi.components.ServiceManager;
|
||||
import com.intellij.openapi.components.State;
|
||||
import com.intellij.openapi.components.Storage;
|
||||
import com.intellij.openapi.diagnostic.Logger;
|
||||
import com.intellij.openapi.extensions.PluginId;
|
||||
import com.intellij.openapi.keymap.Keymap;
|
||||
@@ -66,6 +70,9 @@ import java.io.IOException;
|
||||
import java.net.URLEncoder;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
|
||||
import static com.maddyhome.idea.vim.group.EditorGroup.EDITOR_STORE_ELEMENT;
|
||||
import static com.maddyhome.idea.vim.group.KeyGroup.SHORTCUT_CONFLICTS_ELEMENT;
|
||||
|
||||
/**
|
||||
* This plugin attempts to emulate the key binding and general functionality of Vim and gVim. See the supplied
|
||||
* documentation for a complete list of supported and unsupported Vim emulation. The code base contains some debugging
|
||||
@@ -75,11 +82,10 @@ import java.util.concurrent.TimeUnit;
|
||||
* Registers and marks are shared across open projects so you can copy and paste between files of different projects.
|
||||
*/
|
||||
@State(name = "VimSettings", storages = {@Storage("$APP_CONFIG$/vim_settings.xml")})
|
||||
public class VimPlugin implements BaseComponent, PersistentStateComponent<Element>, Disposable {
|
||||
private static final String IDEAVIM_COMPONENT_NAME = "VimPlugin";
|
||||
public class VimPlugin implements PersistentStateComponent<Element>, Disposable {
|
||||
private static final String IDEAVIM_PLUGIN_ID = "IdeaVIM";
|
||||
private static final String IDEAVIM_STATISTICS_TIMESTAMP_KEY = "ideavim.statistics.timestamp";
|
||||
public static final int STATE_VERSION = 5;
|
||||
private static final int STATE_VERSION = 6;
|
||||
|
||||
private static long lastBeepTimeMillis;
|
||||
|
||||
@@ -94,21 +100,24 @@ public class VimPlugin implements BaseComponent, PersistentStateComponent<Elemen
|
||||
|
||||
private static final Logger LOG = Logger.getInstance(VimPlugin.class);
|
||||
|
||||
@NotNull
|
||||
@Override
|
||||
public String getComponentName() {
|
||||
return IDEAVIM_COMPONENT_NAME;
|
||||
}
|
||||
private final @NotNull VimState state = new VimState();
|
||||
|
||||
|
||||
@NotNull private final VimState state = new VimState();
|
||||
|
||||
// [VERSION UPDATE] 193+ replace with com.intellij.openapi.components.PersistentStateComponent.initializeComponent
|
||||
@Override
|
||||
public void initComponent() {
|
||||
public void initialize() {
|
||||
LOG.debug("initComponent");
|
||||
|
||||
if (enabled) turnOnPlugin();
|
||||
// Initialize a legacy local config.
|
||||
if (previousStateVersion == 5) {
|
||||
//noinspection deprecation
|
||||
VimLocalConfig.Companion.initialize();
|
||||
}
|
||||
if (enabled) {
|
||||
Application application = ApplicationManager.getApplication();
|
||||
if (application.isUnitTestMode()) {
|
||||
application.invokeAndWait(this::turnOnPlugin);
|
||||
} else {
|
||||
application.invokeLater(this::turnOnPlugin);
|
||||
}
|
||||
}
|
||||
|
||||
LOG.debug("done");
|
||||
}
|
||||
@@ -123,23 +132,21 @@ public class VimPlugin implements BaseComponent, PersistentStateComponent<Elemen
|
||||
/**
|
||||
* @return NotificationService as applicationService if project is null and projectService otherwise
|
||||
*/
|
||||
@NotNull
|
||||
public static NotificationService getNotifications(@Nullable Project project) {
|
||||
public static @NotNull NotificationService getNotifications(@Nullable Project project) {
|
||||
if (project == null) {
|
||||
return ServiceManager.getService(NotificationService.class);
|
||||
} else {
|
||||
}
|
||||
else {
|
||||
return ServiceManager.getService(project, NotificationService.class);
|
||||
}
|
||||
}
|
||||
|
||||
@NotNull
|
||||
public static VimState getVimState() {
|
||||
public static @NotNull VimState getVimState() {
|
||||
return getInstance().state;
|
||||
}
|
||||
|
||||
|
||||
@NotNull
|
||||
public static MotionGroup getMotion() {
|
||||
public static @NotNull MotionGroup getMotion() {
|
||||
return ServiceManager.getService(MotionGroup.class);
|
||||
}
|
||||
|
||||
@@ -151,18 +158,17 @@ public class VimPlugin implements BaseComponent, PersistentStateComponent<Elemen
|
||||
public static void statisticReport() {
|
||||
final PropertiesComponent propertiesComponent = PropertiesComponent.getInstance();
|
||||
final long lastUpdate = propertiesComponent.getOrInitLong(IDEAVIM_STATISTICS_TIMESTAMP_KEY, 0);
|
||||
final boolean outOfDate =
|
||||
lastUpdate == 0 || System.currentTimeMillis() - lastUpdate > TimeUnit.DAYS.toMillis(1);
|
||||
final boolean outOfDate = lastUpdate == 0 || System.currentTimeMillis() - lastUpdate > TimeUnit.DAYS.toMillis(1);
|
||||
if (outOfDate && isEnabled()) {
|
||||
ApplicationManager.getApplication().executeOnPooledThread(() -> {
|
||||
try {
|
||||
final String buildNumber = ApplicationInfo.getInstance().getBuild().asString();
|
||||
final String version = URLEncoder.encode(getVersion(), CharsetToolkit.UTF8);
|
||||
final String os =
|
||||
URLEncoder.encode(SystemInfo.OS_NAME + " " + SystemInfo.OS_VERSION, CharsetToolkit.UTF8);
|
||||
final String os = URLEncoder.encode(SystemInfo.OS_NAME + " " + SystemInfo.OS_VERSION, CharsetToolkit.UTF8);
|
||||
final String uid = PermanentInstallationID.get();
|
||||
final String url = "https://plugins.jetbrains.com/plugins/list" +
|
||||
"?pluginId=" + IDEAVIM_PLUGIN_ID +
|
||||
"?pluginId=" +
|
||||
IDEAVIM_PLUGIN_ID +
|
||||
"&build=" +
|
||||
buildNumber +
|
||||
"&pluginVersion=" +
|
||||
@@ -191,106 +197,87 @@ public class VimPlugin implements BaseComponent, PersistentStateComponent<Elemen
|
||||
}
|
||||
}
|
||||
|
||||
@NotNull
|
||||
public static ChangeGroup getChange() {
|
||||
public static @NotNull ChangeGroup getChange() {
|
||||
return ServiceManager.getService(ChangeGroup.class);
|
||||
}
|
||||
|
||||
@NotNull
|
||||
public static CommandGroup getCommand() {
|
||||
public static @NotNull CommandGroup getCommand() {
|
||||
return ServiceManager.getService(CommandGroup.class);
|
||||
}
|
||||
|
||||
@NotNull
|
||||
public static MarkGroup getMark() {
|
||||
public static @NotNull MarkGroup getMark() {
|
||||
return ServiceManager.getService(MarkGroup.class);
|
||||
}
|
||||
|
||||
@NotNull
|
||||
public static RegisterGroup getRegister() {
|
||||
public static @NotNull RegisterGroup getRegister() {
|
||||
return ServiceManager.getService(RegisterGroup.class);
|
||||
}
|
||||
|
||||
@NotNull
|
||||
public static FileGroup getFile() {
|
||||
public static @Nullable RegisterGroup getRegisterIfCreated() {
|
||||
return ServiceManager.getServiceIfCreated(RegisterGroup.class);
|
||||
}
|
||||
|
||||
public static @NotNull FileGroup getFile() {
|
||||
return ServiceManager.getService(FileGroup.class);
|
||||
}
|
||||
|
||||
@NotNull
|
||||
public static SearchGroup getSearch() {
|
||||
public static @NotNull SearchGroup getSearch() {
|
||||
return ServiceManager.getService(SearchGroup.class);
|
||||
}
|
||||
|
||||
@NotNull
|
||||
public static ProcessGroup getProcess() {
|
||||
public static @Nullable SearchGroup getSearchIfCreated() {
|
||||
return ServiceManager.getServiceIfCreated(SearchGroup.class);
|
||||
}
|
||||
|
||||
public static @NotNull ProcessGroup getProcess() {
|
||||
return ServiceManager.getService(ProcessGroup.class);
|
||||
}
|
||||
|
||||
@NotNull
|
||||
public static MacroGroup getMacro() {
|
||||
public static @NotNull MacroGroup getMacro() {
|
||||
return ServiceManager.getService(MacroGroup.class);
|
||||
}
|
||||
|
||||
@NotNull
|
||||
public static DigraphGroup getDigraph() {
|
||||
public static @NotNull DigraphGroup getDigraph() {
|
||||
return ServiceManager.getService(DigraphGroup.class);
|
||||
}
|
||||
|
||||
@NotNull
|
||||
public static HistoryGroup getHistory() {
|
||||
public static @NotNull HistoryGroup getHistory() {
|
||||
return ServiceManager.getService(HistoryGroup.class);
|
||||
}
|
||||
|
||||
@NotNull
|
||||
public static KeyGroup getKey() {
|
||||
public static @NotNull KeyGroup getKey() {
|
||||
return ServiceManager.getService(KeyGroup.class);
|
||||
}
|
||||
|
||||
@NotNull
|
||||
public static WindowGroup getWindow() {
|
||||
public static @Nullable KeyGroup getKeyIfCreated() {
|
||||
return ServiceManager.getServiceIfCreated(KeyGroup.class);
|
||||
}
|
||||
|
||||
public static @NotNull WindowGroup getWindow() {
|
||||
return ServiceManager.getService(WindowGroup.class);
|
||||
}
|
||||
|
||||
@NotNull
|
||||
public static EditorGroup getEditor() {
|
||||
public static @NotNull EditorGroup getEditor() {
|
||||
return ServiceManager.getService(EditorGroup.class);
|
||||
}
|
||||
|
||||
@NotNull
|
||||
public static VisualMotionGroup getVisualMotion() {
|
||||
public static @Nullable EditorGroup getEditorIfCreated() {
|
||||
return ServiceManager.getServiceIfCreated(EditorGroup.class);
|
||||
}
|
||||
|
||||
public static @NotNull VisualMotionGroup getVisualMotion() {
|
||||
return ServiceManager.getService(VisualMotionGroup.class);
|
||||
}
|
||||
|
||||
@NotNull
|
||||
public static YankGroup getYank() {
|
||||
public static @NotNull YankGroup getYank() {
|
||||
return ServiceManager.getService(YankGroup.class);
|
||||
}
|
||||
|
||||
@NotNull
|
||||
public static PutGroup getPut() {
|
||||
public static @NotNull PutGroup getPut() {
|
||||
return ServiceManager.getService(PutGroup.class);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Element getState() {
|
||||
LOG.debug("Saving state");
|
||||
|
||||
final Element element = new Element("ideavim");
|
||||
// Save whether the plugin is enabled or not
|
||||
final Element state = new Element("state");
|
||||
state.setAttribute("version", Integer.toString(STATE_VERSION));
|
||||
state.setAttribute("enabled", Boolean.toString(enabled));
|
||||
element.addContent(state);
|
||||
|
||||
getKey().saveData(element);
|
||||
getEditor().saveData(element);
|
||||
this.state.saveData(element);
|
||||
|
||||
return element;
|
||||
}
|
||||
|
||||
@NotNull
|
||||
private static NotificationService getNotifications() {
|
||||
private static @NotNull NotificationService getNotifications() {
|
||||
return getNotifications(null);
|
||||
}
|
||||
|
||||
@@ -308,15 +295,13 @@ public class VimPlugin implements BaseComponent, PersistentStateComponent<Elemen
|
||||
}
|
||||
}
|
||||
|
||||
@NotNull
|
||||
public static PluginId getPluginId() {
|
||||
public static @NotNull PluginId getPluginId() {
|
||||
return PluginId.getId(IDEAVIM_PLUGIN_ID);
|
||||
}
|
||||
|
||||
// [VERSION UPDATE] 193+ remove suppress
|
||||
@SuppressWarnings({"MissingRecentApi", "UnstableApiUsage"})
|
||||
@NotNull
|
||||
public static String getVersion() {
|
||||
public static @NotNull String getVersion() {
|
||||
final IdeaPluginDescriptor plugin = PluginManager.getPlugin(getPluginId());
|
||||
if (!ApplicationManager.getApplication().isInternal()) {
|
||||
return plugin != null ? plugin.getVersion() : "SNAPSHOT";
|
||||
@@ -400,9 +385,8 @@ public class VimPlugin implements BaseComponent, PersistentStateComponent<Elemen
|
||||
}
|
||||
}
|
||||
|
||||
@NotNull
|
||||
private static VimPlugin getInstance() {
|
||||
return ApplicationManager.getApplication().getComponent(VimPlugin.class);
|
||||
public static @NotNull VimPlugin getInstance() {
|
||||
return ServiceManager.getService(VimPlugin.class);
|
||||
}
|
||||
|
||||
private void turnOnPlugin() {
|
||||
@@ -434,8 +418,14 @@ public class VimPlugin implements BaseComponent, PersistentStateComponent<Elemen
|
||||
// Unregister ex handlers
|
||||
CommandParser.getInstance().unregisterHandlers();
|
||||
|
||||
getEditor().turnOff();
|
||||
getSearch().turnOff();
|
||||
EditorGroup editorGroup = getEditorIfCreated();
|
||||
if (editorGroup != null) {
|
||||
editorGroup.turnOff();
|
||||
}
|
||||
SearchGroup searchGroup = getSearchIfCreated();
|
||||
if (searchGroup != null) {
|
||||
searchGroup.turnOff();
|
||||
}
|
||||
VimListenerManager.INSTANCE.turnOff();
|
||||
ExEntryPanel.fullReset();
|
||||
}
|
||||
@@ -485,7 +475,7 @@ public class VimPlugin implements BaseComponent, PersistentStateComponent<Elemen
|
||||
}
|
||||
|
||||
@Override
|
||||
public void loadState(@NotNull final Element element) {
|
||||
public void loadState(final @NotNull Element element) {
|
||||
LOG.debug("Loading state");
|
||||
|
||||
// Restore whether the plugin is enabled or not
|
||||
@@ -500,6 +490,27 @@ public class VimPlugin implements BaseComponent, PersistentStateComponent<Elemen
|
||||
previousKeyMap = state.getAttributeValue("keymap");
|
||||
}
|
||||
|
||||
legacyStateLoading(element);
|
||||
this.state.readData(element);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Element getState() {
|
||||
LOG.debug("Saving state");
|
||||
|
||||
final Element element = new Element("ideavim");
|
||||
// Save whether the plugin is enabled or not
|
||||
final Element state = new Element("state");
|
||||
state.setAttribute("version", Integer.toString(STATE_VERSION));
|
||||
state.setAttribute("enabled", Boolean.toString(enabled));
|
||||
element.addContent(state);
|
||||
|
||||
this.state.saveData(element);
|
||||
|
||||
return element;
|
||||
}
|
||||
|
||||
private void legacyStateLoading(@NotNull Element element) {
|
||||
if (previousStateVersion > 0 && previousStateVersion < 5) {
|
||||
// Migrate settings from 4 to 5 version
|
||||
getMark().readData(element);
|
||||
@@ -507,8 +518,11 @@ public class VimPlugin implements BaseComponent, PersistentStateComponent<Elemen
|
||||
getSearch().readData(element);
|
||||
getHistory().readData(element);
|
||||
}
|
||||
if (element.getChild(SHORTCUT_CONFLICTS_ELEMENT) != null) {
|
||||
getKey().readData(element);
|
||||
}
|
||||
if (element.getChild(EDITOR_STORE_ELEMENT) != null) {
|
||||
getEditor().readData(element);
|
||||
this.state.readData(element);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@@ -37,7 +37,7 @@ import javax.swing.*;
|
||||
public class VimTypedActionHandler implements TypedActionHandlerEx {
|
||||
private static final Logger logger = Logger.getInstance(VimTypedActionHandler.class.getName());
|
||||
|
||||
@NotNull private final KeyHandler handler;
|
||||
private final @NotNull KeyHandler handler;
|
||||
|
||||
public VimTypedActionHandler(TypedActionHandler origHandler) {
|
||||
handler = KeyHandler.getInstance();
|
||||
@@ -50,7 +50,7 @@ public class VimTypedActionHandler implements TypedActionHandlerEx {
|
||||
}
|
||||
|
||||
@Override
|
||||
public void execute(@NotNull final Editor editor, final char charTyped, @NotNull final DataContext context) {
|
||||
public void execute(final @NotNull Editor editor, final char charTyped, final @NotNull DataContext context) {
|
||||
try {
|
||||
handler.handleKey(editor, KeyStroke.getKeyStroke(charTyped), new EditorDataContext(editor));
|
||||
}
|
||||
|
@@ -60,15 +60,14 @@ public class CommandState {
|
||||
*
|
||||
* This field is reset after the command has been executed.
|
||||
*/
|
||||
@Nullable private Command executingCommand;
|
||||
private @Nullable Command executingCommand;
|
||||
|
||||
private CommandState() {
|
||||
pushModes(defaultModeState.getMode(), defaultModeState.getSubMode());
|
||||
}
|
||||
|
||||
@Contract("null -> new")
|
||||
@NotNull
|
||||
public static CommandState getInstance(@Nullable Editor editor) {
|
||||
public static @NotNull CommandState getInstance(@Nullable Editor editor) {
|
||||
if (editor == null) {
|
||||
return new CommandState();
|
||||
}
|
||||
@@ -82,8 +81,7 @@ public class CommandState {
|
||||
return res;
|
||||
}
|
||||
|
||||
@NotNull
|
||||
private static CommandPartNode getKeyRootNode(MappingMode mappingMode) {
|
||||
private static @NotNull CommandPartNode getKeyRootNode(MappingMode mappingMode) {
|
||||
return VimPlugin.getKey().getKeyRoot(mappingMode);
|
||||
}
|
||||
|
||||
@@ -106,13 +104,11 @@ public class CommandState {
|
||||
return commandBuilder;
|
||||
}
|
||||
|
||||
@NotNull
|
||||
public MappingState getMappingState() {
|
||||
public @NotNull MappingState getMappingState() {
|
||||
return mappingState;
|
||||
}
|
||||
|
||||
@Nullable
|
||||
public Command getExecutingCommand() {
|
||||
public @Nullable Command getExecutingCommand() {
|
||||
return executingCommand;
|
||||
}
|
||||
|
||||
@@ -202,13 +198,11 @@ public class CommandState {
|
||||
throw new IllegalArgumentException("Unexpected mode: " + mode);
|
||||
}
|
||||
|
||||
@NotNull
|
||||
public Mode getMode() {
|
||||
public @NotNull Mode getMode() {
|
||||
return currentModeState().getMode();
|
||||
}
|
||||
|
||||
@NotNull
|
||||
public SubMode getSubMode() {
|
||||
public @NotNull SubMode getSubMode() {
|
||||
return currentModeState().getSubMode();
|
||||
}
|
||||
|
||||
@@ -294,8 +288,7 @@ public class CommandState {
|
||||
VimPlugin.showMode(msg.toString());
|
||||
}
|
||||
|
||||
@NotNull
|
||||
private String getStatusString(int pos) {
|
||||
private @NotNull String getStatusString(int pos) {
|
||||
ModeState modeState;
|
||||
if (pos >= 0 && pos < modeStates.size()) {
|
||||
modeState = modeStates.get(pos);
|
||||
@@ -354,8 +347,8 @@ public class CommandState {
|
||||
}
|
||||
|
||||
private static class ModeState {
|
||||
@NotNull private final Mode myMode;
|
||||
@NotNull private final SubMode mySubMode;
|
||||
private final @NotNull Mode myMode;
|
||||
private final @NotNull SubMode mySubMode;
|
||||
|
||||
@Contract(pure = true)
|
||||
public ModeState(@NotNull Mode mode, @NotNull SubMode subMode) {
|
||||
@@ -363,13 +356,11 @@ public class CommandState {
|
||||
this.mySubMode = subMode;
|
||||
}
|
||||
|
||||
@NotNull
|
||||
public Mode getMode() {
|
||||
public @NotNull Mode getMode() {
|
||||
return myMode;
|
||||
}
|
||||
|
||||
@NotNull
|
||||
public SubMode getSubMode() {
|
||||
public @NotNull SubMode getSubMode() {
|
||||
return mySubMode;
|
||||
}
|
||||
|
||||
|
@@ -59,7 +59,7 @@ public class CommandParser {
|
||||
*
|
||||
* @return The singleton instance
|
||||
*/
|
||||
public synchronized static CommandParser getInstance() {
|
||||
public static synchronized CommandParser getInstance() {
|
||||
return CommandParserHolder.INSTANCE;
|
||||
}
|
||||
|
||||
@@ -76,7 +76,7 @@ public class CommandParser {
|
||||
//noinspection deprecation
|
||||
EX_COMMAND_EP.getPoint(null).addExtensionPointListener(new ExtensionPointListener<ExBeanClass>() {
|
||||
@Override
|
||||
public void extensionAdded(@NotNull ExBeanClass extension, @NotNull PluginDescriptor pluginDescriptor) {
|
||||
public void extensionAdded(@NotNull ExBeanClass extension, PluginDescriptor pluginDescriptor) {
|
||||
// Suppress listener before the `VimPlugin.turnOn()` function execution. This logic should be rewritten after
|
||||
// version update (or earlier).
|
||||
if (!initialRegistration) return;
|
||||
@@ -85,7 +85,7 @@ public class CommandParser {
|
||||
}
|
||||
|
||||
@Override
|
||||
public void extensionRemoved(@NotNull ExBeanClass extension, @NotNull PluginDescriptor pluginDescriptor) {
|
||||
public void extensionRemoved(@NotNull ExBeanClass extension, PluginDescriptor pluginDescriptor) {
|
||||
if (!initialRegistration) return;
|
||||
unregisterHandlers();
|
||||
registerHandlers();
|
||||
@@ -215,8 +215,7 @@ public class CommandParser {
|
||||
}
|
||||
}
|
||||
|
||||
@Nullable
|
||||
public CommandHandler getCommandHandler(@NotNull ExCommand command) {
|
||||
public @Nullable CommandHandler getCommandHandler(@NotNull ExCommand command) {
|
||||
final String cmd = command.getCommand();
|
||||
// If there is no command, just a range, use the 'goto line' handler
|
||||
if (cmd.length() == 0) {
|
||||
@@ -241,8 +240,7 @@ public class CommandParser {
|
||||
* @return The parse result
|
||||
* @throws ExException if the text is syntactically incorrect
|
||||
*/
|
||||
@NotNull
|
||||
public ExCommand parse(@NotNull String cmd) throws ExException {
|
||||
public @NotNull ExCommand parse(@NotNull String cmd) throws ExException {
|
||||
// This is a complicated state machine that should probably be rewritten
|
||||
if (logger.isDebugEnabled()) {
|
||||
logger.debug("processing `" + cmd + "'");
|
||||
@@ -622,7 +620,7 @@ public class CommandParser {
|
||||
}
|
||||
}
|
||||
|
||||
@NotNull private final CommandNode root = new CommandNode();
|
||||
private final @NotNull CommandNode root = new CommandNode();
|
||||
|
||||
private enum State {
|
||||
START,
|
||||
|
148
src/com/maddyhome/idea/vim/ex/handler/BufferListHandler.kt
Normal file
148
src/com/maddyhome/idea/vim/ex/handler/BufferListHandler.kt
Normal file
@@ -0,0 +1,148 @@
|
||||
/*
|
||||
* IdeaVim - Vim emulator for IDEs based on the IntelliJ platform
|
||||
* Copyright (C) 2003-2020 The IdeaVim authors
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 2 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package com.maddyhome.idea.vim.ex.handler
|
||||
|
||||
import com.intellij.openapi.actionSystem.DataContext
|
||||
import com.intellij.openapi.actionSystem.PlatformDataKeys
|
||||
import com.intellij.openapi.editor.Document
|
||||
import com.intellij.openapi.editor.Editor
|
||||
import com.intellij.openapi.fileEditor.FileEditorManager
|
||||
import com.intellij.openapi.project.Project
|
||||
import com.intellij.openapi.vfs.VirtualFile
|
||||
import com.maddyhome.idea.vim.VimPlugin
|
||||
import com.maddyhome.idea.vim.ex.CommandHandler
|
||||
import com.maddyhome.idea.vim.ex.ExCommand
|
||||
import com.maddyhome.idea.vim.ex.ExOutputModel
|
||||
import com.maddyhome.idea.vim.ex.flags
|
||||
import com.maddyhome.idea.vim.helper.EditorHelper
|
||||
import java.io.File
|
||||
|
||||
/**
|
||||
* Handles buffers, files, ls command. Supports +, =, a, %, # filters.
|
||||
*
|
||||
* @author John Weigel
|
||||
*/
|
||||
class BufferListHandler : CommandHandler.SingleExecution() {
|
||||
override val argFlags = flags(RangeFlag.RANGE_FORBIDDEN, ArgumentFlag.ARGUMENT_OPTIONAL, Access.READ_ONLY)
|
||||
|
||||
companion object {
|
||||
const val FILE_NAME_PAD = 30
|
||||
val SUPPORTED_FILTERS = setOf('+', '=', 'a', '%', '#')
|
||||
}
|
||||
|
||||
override fun execute(editor: Editor, context: DataContext, cmd: ExCommand): Boolean {
|
||||
val arg = cmd.argument.trim()
|
||||
val filter = pruneUnsupportedFilters(arg)
|
||||
val bufferList = getBufferList(context, filter)
|
||||
|
||||
ExOutputModel.getInstance(editor).output(bufferList.joinToString(separator = "\n"))
|
||||
|
||||
return true
|
||||
}
|
||||
|
||||
private fun pruneUnsupportedFilters(filter: String) = filter.filter { it in SUPPORTED_FILTERS }
|
||||
|
||||
private fun getBufferList(context: DataContext, filter: String): List<String> {
|
||||
val bufferList = mutableListOf<String>()
|
||||
val project = PlatformDataKeys.PROJECT.getData(context) ?: return emptyList()
|
||||
|
||||
val fem = FileEditorManager.getInstance(project)
|
||||
val openFiles = fem.openFiles
|
||||
val bufNumPad = openFiles.size.toString().length
|
||||
val currentFile = fem.selectedFiles[0]
|
||||
val previousFile = VimPlugin.getFile().getPreviousTab(context)
|
||||
val virtualFileDisplayMap = buildVirtualFileDisplayMap(project)
|
||||
|
||||
var index = 1
|
||||
for ((file, displayFileName) in virtualFileDisplayMap) {
|
||||
val editor = EditorHelper.getEditor(file) ?: continue
|
||||
|
||||
val bufStatus = getBufferStatus(editor, file, currentFile, previousFile)
|
||||
|
||||
if (bufStatusMatchesFilter(filter, bufStatus)) {
|
||||
val lineNum = editor.caretModel.currentCaret.logicalPosition.line + 1
|
||||
val lineNumPad = if (displayFileName.length < FILE_NAME_PAD) (FILE_NAME_PAD - displayFileName.length).toString() else ""
|
||||
|
||||
bufferList.add(String.format(
|
||||
" %${bufNumPad}s %s %s%${lineNumPad}s line: %d", index, bufStatus, displayFileName, "", lineNum)
|
||||
)
|
||||
}
|
||||
index++
|
||||
}
|
||||
|
||||
return bufferList
|
||||
}
|
||||
|
||||
private fun buildVirtualFileDisplayMap(project: Project): Map<VirtualFile, String> {
|
||||
val openFiles = FileEditorManager.getInstance(project).openFiles
|
||||
val basePath = if (project.basePath != null) project.basePath + File.separator else ""
|
||||
val filePaths = mutableMapOf<VirtualFile, String>()
|
||||
|
||||
for (file in openFiles) {
|
||||
val filePath = file.path
|
||||
|
||||
// If the file is under the project path, then remove the project base path from the file.
|
||||
val displayFilePath = if (basePath.isNotEmpty() && filePath.startsWith(basePath)) {
|
||||
filePath.replace(basePath, "")
|
||||
} else {
|
||||
// File is not under the project base path so add the full path.
|
||||
filePath
|
||||
}
|
||||
|
||||
filePaths[file] = '"' + displayFilePath + '"'
|
||||
}
|
||||
|
||||
return filePaths
|
||||
}
|
||||
|
||||
private fun bufStatusMatchesFilter(filter: String, bufStatus: String) = filter.all { it in bufStatus }
|
||||
}
|
||||
|
||||
private fun getBufferStatus(editor: Editor, file: VirtualFile, currentFile: VirtualFile, previousFile: VirtualFile?): String {
|
||||
val bufStatus = StringBuilder()
|
||||
|
||||
when(file) {
|
||||
currentFile -> bufStatus.append("%a ")
|
||||
previousFile -> bufStatus.append("# ")
|
||||
else -> bufStatus.append(" ")
|
||||
}
|
||||
|
||||
if (!file.isWritable) {
|
||||
bufStatus.setCharAt(2, '=')
|
||||
}
|
||||
|
||||
if (isDocumentDirty(editor.document)) {
|
||||
bufStatus.setCharAt(3, '+')
|
||||
}
|
||||
|
||||
return bufStatus.toString()
|
||||
}
|
||||
|
||||
private fun isDocumentDirty(document: Document): Boolean {
|
||||
var line = 0
|
||||
|
||||
while (line < document.lineCount) {
|
||||
if (document.isLineModified(line)) {
|
||||
return true
|
||||
}
|
||||
line++
|
||||
}
|
||||
|
||||
return false
|
||||
}
|
@@ -34,6 +34,7 @@ import com.maddyhome.idea.vim.ex.handler.MapHandler.SpecialArgument.EXPR
|
||||
import com.maddyhome.idea.vim.ex.handler.MapHandler.SpecialArgument.SCRIPT
|
||||
import com.maddyhome.idea.vim.ex.vimscript.VimScriptCommandHandler
|
||||
import com.maddyhome.idea.vim.helper.StringHelper.parseKeys
|
||||
import com.maddyhome.idea.vim.key.MappingOwner
|
||||
import java.util.*
|
||||
import javax.swing.KeyStroke
|
||||
|
||||
@@ -75,8 +76,7 @@ class MapHandler : CommandHandler.SingleExecution(), VimScriptCommandHandler, Co
|
||||
throw ExException("Unsupported map argument: $unsupportedArgument")
|
||||
}
|
||||
}
|
||||
VimPlugin.getKey().putKeyMapping(modes, arguments.fromKeys, arguments.toKeys, null,
|
||||
commandInfo.isRecursive)
|
||||
VimPlugin.getKey().putKeyMapping(modes, arguments.fromKeys, MappingOwner.IdeaVim, arguments.toKeys, commandInfo.isRecursive)
|
||||
return true
|
||||
}
|
||||
}
|
||||
|
@@ -33,13 +33,11 @@ public class VimScriptGlobalEnvironment {
|
||||
|
||||
private VimScriptGlobalEnvironment() {}
|
||||
|
||||
@NotNull
|
||||
public static VimScriptGlobalEnvironment getInstance() {
|
||||
public static @NotNull VimScriptGlobalEnvironment getInstance() {
|
||||
return ourInstance;
|
||||
}
|
||||
|
||||
@NotNull
|
||||
public Map<String, Object> getVariables() {
|
||||
public @NotNull Map<String, Object> getVariables() {
|
||||
return myVariables;
|
||||
}
|
||||
}
|
||||
|
@@ -52,8 +52,7 @@ public class VimScriptParser {
|
||||
private VimScriptParser() {
|
||||
}
|
||||
|
||||
@Nullable
|
||||
public static File findIdeaVimRc() {
|
||||
public static @Nullable File findIdeaVimRc() {
|
||||
final String homeDirName = System.getProperty("user.home");
|
||||
// Check whether file exists in home dir
|
||||
if (homeDirName != null) {
|
||||
@@ -82,8 +81,7 @@ public class VimScriptParser {
|
||||
return null;
|
||||
}
|
||||
|
||||
@Nullable
|
||||
public static File findOrCreateIdeaVimRc() {
|
||||
public static @Nullable File findOrCreateIdeaVimRc() {
|
||||
final File found = findIdeaVimRc();
|
||||
if (found != null) return found;
|
||||
|
||||
@@ -137,8 +135,7 @@ public class VimScriptParser {
|
||||
}
|
||||
}
|
||||
|
||||
@NotNull
|
||||
public static Object evaluate(@NotNull String expression, @NotNull Map<String, Object> globals) throws ExException {
|
||||
public static @NotNull Object evaluate(@NotNull String expression, @NotNull Map<String, Object> globals) throws ExException {
|
||||
// This evaluator is very basic, no proper parsing whatsoever. It is here as the very first step necessary to
|
||||
// support mapleader, VIM-650. See also VIM-669.
|
||||
Matcher m;
|
||||
@@ -168,8 +165,7 @@ public class VimScriptParser {
|
||||
throw new ExException(String.format("Invalid expression: %s", expression));
|
||||
}
|
||||
|
||||
@NotNull
|
||||
public static String expressionToString(@NotNull Object value) throws ExException {
|
||||
public static @NotNull String expressionToString(@NotNull Object value) throws ExException {
|
||||
// TODO: Return meaningful value representations
|
||||
if (value instanceof String) {
|
||||
return (String)value;
|
||||
@@ -179,8 +175,7 @@ public class VimScriptParser {
|
||||
throw new ExException(String.format("Cannot convert '%s' to string", value));
|
||||
}
|
||||
|
||||
@NotNull
|
||||
private static String readFile(@NotNull File file) throws IOException {
|
||||
private static @NotNull String readFile(@NotNull File file) throws IOException {
|
||||
final BufferedReader reader = new BufferedReader(new FileReader(file));
|
||||
final StringBuilder builder = new StringBuilder();
|
||||
final char[] buffer = new char[BUFSIZE];
|
||||
|
@@ -19,6 +19,8 @@
|
||||
package com.maddyhome.idea.vim.extension;
|
||||
|
||||
import com.intellij.openapi.extensions.ExtensionPointName;
|
||||
import com.maddyhome.idea.vim.VimPlugin;
|
||||
import com.maddyhome.idea.vim.key.MappingOwner;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
|
||||
/**
|
||||
@@ -30,7 +32,13 @@ public interface VimExtension {
|
||||
@NotNull
|
||||
String getName();
|
||||
|
||||
default MappingOwner getOwner() {
|
||||
return MappingOwner.Plugin.Companion.get(getName());
|
||||
}
|
||||
|
||||
void init();
|
||||
|
||||
void dispose();
|
||||
default void dispose() {
|
||||
VimPlugin.getKey().removeKeyMapping(getOwner());
|
||||
};
|
||||
}
|
||||
|
@@ -27,9 +27,11 @@ import com.maddyhome.idea.vim.helper.EditorDataContext
|
||||
import com.maddyhome.idea.vim.helper.StringHelper
|
||||
import com.maddyhome.idea.vim.helper.TestInputModel
|
||||
import com.maddyhome.idea.vim.helper.commandState
|
||||
import com.maddyhome.idea.vim.key.MappingOwner
|
||||
import com.maddyhome.idea.vim.key.OperatorFunction
|
||||
import com.maddyhome.idea.vim.ui.ExEntryPanel
|
||||
import com.maddyhome.idea.vim.ui.ModalEntry
|
||||
import org.jetbrains.annotations.ApiStatus.ScheduledForRemoval
|
||||
import java.awt.event.KeyEvent
|
||||
import javax.swing.KeyStroke
|
||||
|
||||
@@ -41,18 +43,34 @@ import javax.swing.KeyStroke
|
||||
* @author vlan
|
||||
*/
|
||||
object VimExtensionFacade {
|
||||
/** The 'map' command for mapping keys to handlers defined in extensions. */
|
||||
@JvmStatic
|
||||
@ScheduledForRemoval(inVersion = "0.57")
|
||||
@Deprecated("Only for EasyMotion support")
|
||||
fun putExtensionHandlerMapping(modes: Set<MappingMode>, fromKeys: List<KeyStroke>, extensionHandler: VimExtensionHandler, recursive: Boolean) {
|
||||
VimPlugin.getKey().putKeyMapping(modes, fromKeys, MappingOwner.Plugin.get("easymotion"), extensionHandler, recursive)
|
||||
}
|
||||
|
||||
@ScheduledForRemoval(inVersion = "0.57")
|
||||
@Deprecated("Only for EasyMotion support")
|
||||
@JvmStatic
|
||||
fun putKeyMapping(modes: Set<MappingMode>, fromKeys: List<KeyStroke>,
|
||||
toKeys: List<KeyStroke>, recursive: Boolean) {
|
||||
VimPlugin.getKey().putKeyMapping(modes, fromKeys, MappingOwner.Plugin.get("easymotion"), toKeys, recursive)
|
||||
}
|
||||
|
||||
/** The 'map' command for mapping keys to handlers defined in extensions. */
|
||||
@JvmStatic
|
||||
fun putExtensionHandlerMapping(modes: Set<MappingMode>, fromKeys: List<KeyStroke>,
|
||||
extensionHandler: VimExtensionHandler, recursive: Boolean) {
|
||||
VimPlugin.getKey().putKeyMapping(modes, fromKeys, null, extensionHandler, recursive)
|
||||
pluginOwner: MappingOwner, extensionHandler: VimExtensionHandler, recursive: Boolean) {
|
||||
VimPlugin.getKey().putKeyMapping(modes, fromKeys, pluginOwner, extensionHandler, recursive)
|
||||
}
|
||||
|
||||
/** The 'map' command for mapping keys to other keys. */
|
||||
@JvmStatic
|
||||
fun putKeyMapping(modes: Set<MappingMode>, fromKeys: List<KeyStroke>,
|
||||
toKeys: List<KeyStroke>, recursive: Boolean) {
|
||||
VimPlugin.getKey().putKeyMapping(modes, fromKeys, toKeys, null, recursive)
|
||||
pluginOwner: MappingOwner, toKeys: List<KeyStroke>, recursive: Boolean) {
|
||||
VimPlugin.getKey().putKeyMapping(modes, fromKeys, pluginOwner, toKeys, recursive)
|
||||
}
|
||||
|
||||
/** Sets the value of 'operatorfunc' to be used as the operator function in 'g@'. */
|
||||
@@ -78,7 +96,8 @@ object VimExtensionFacade {
|
||||
fun inputKeyStroke(editor: Editor): KeyStroke {
|
||||
if (editor.commandState.isDotRepeatInProgress) {
|
||||
val input = VimRepeater.Extension.consumeKeystroke()
|
||||
return input ?: throw RuntimeException("Not enough keystrokes saved: ${VimRepeater.Extension.lastExtensionHandler}")
|
||||
return input
|
||||
?: throw RuntimeException("Not enough keystrokes saved: ${VimRepeater.Extension.lastExtensionHandler}")
|
||||
}
|
||||
|
||||
val key: KeyStroke? = if (ApplicationManager.getApplication().isUnitTestMode) {
|
||||
|
@@ -21,6 +21,7 @@ package com.maddyhome.idea.vim.extension;
|
||||
import com.intellij.openapi.diagnostic.Logger;
|
||||
import com.intellij.openapi.extensions.ExtensionPointListener;
|
||||
import com.intellij.openapi.extensions.PluginDescriptor;
|
||||
import com.maddyhome.idea.vim.key.MappingOwner;
|
||||
import com.maddyhome.idea.vim.option.OptionsManager;
|
||||
import com.maddyhome.idea.vim.option.ToggleOption;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
@@ -34,24 +35,32 @@ import java.util.Set;
|
||||
*/
|
||||
public class VimExtensionRegistrar {
|
||||
|
||||
private static Set<String> registeredExtensions = new HashSet<>();
|
||||
private static final Set<String> registeredExtensions = new HashSet<>();
|
||||
private static boolean extensionRegistered = false;
|
||||
|
||||
private static final Logger logger = Logger.getInstance(VimExtensionRegistrar.class);
|
||||
|
||||
public static void registerExtensions() {
|
||||
if (extensionRegistered) return;
|
||||
extensionRegistered = true;
|
||||
|
||||
// TODO: [VERSION UPDATE] since 191 use
|
||||
// ExtensionPoint.addExtensionPointListener(ExtensionPointListener<T>, boolean, Disposable)
|
||||
//noinspection deprecation
|
||||
VimExtension.EP_NAME.getPoint(null).addExtensionPointListener(new ExtensionPointListener<VimExtension>() {
|
||||
@Override
|
||||
public void extensionAdded(@NotNull VimExtension extension, @NotNull PluginDescriptor pluginDescriptor) {
|
||||
public void extensionAdded(@NotNull VimExtension extension, PluginDescriptor pluginDescriptor) {
|
||||
registerExtension(extension);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void extensionRemoved(@NotNull VimExtension extension, PluginDescriptor pluginDescriptor) {
|
||||
unregisterExtension(extension);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
synchronized private static void registerExtension(@NotNull VimExtension extension) {
|
||||
private static synchronized void registerExtension(@NotNull VimExtension extension) {
|
||||
String name = extension.getName();
|
||||
|
||||
if (registeredExtensions.contains(name)) return;
|
||||
@@ -74,5 +83,15 @@ public class VimExtensionRegistrar {
|
||||
OptionsManager.INSTANCE.addOption(option);
|
||||
}
|
||||
|
||||
private static Logger logger = Logger.getInstance(VimExtensionRegistrar.class);
|
||||
private static synchronized void unregisterExtension(@NotNull VimExtension extension) {
|
||||
String name = extension.getName();
|
||||
|
||||
if (!registeredExtensions.contains(name)) return;
|
||||
|
||||
registeredExtensions.remove(name);
|
||||
extension.dispose();
|
||||
OptionsManager.INSTANCE.removeOption(name);
|
||||
MappingOwner.Plugin.Companion.remove(name);
|
||||
logger.info("IdeaVim extension '" + name + "' disposed");
|
||||
}
|
||||
}
|
||||
|
@@ -18,30 +18,19 @@
|
||||
|
||||
package com.maddyhome.idea.vim.extension;
|
||||
|
||||
import com.intellij.openapi.application.ApplicationManager;
|
||||
import org.jetbrains.annotations.ApiStatus;
|
||||
|
||||
/**
|
||||
* @author vlan
|
||||
*/
|
||||
public abstract class VimNonDisposableExtension implements VimExtension {
|
||||
private boolean myInitialized = false;
|
||||
|
||||
@ApiStatus.ScheduledForRemoval(inVersion = "0.57")
|
||||
@Deprecated
|
||||
public abstract class VimNonDisposableExtension implements VimExtension {
|
||||
@Override
|
||||
public final void init() {
|
||||
if (ApplicationManager.getApplication().isUnitTestMode()) {
|
||||
initOnce();
|
||||
}
|
||||
else {
|
||||
if (!myInitialized) {
|
||||
myInitialized = true;
|
||||
initOnce();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public final void dispose() {
|
||||
}
|
||||
|
||||
protected abstract void initOnce();
|
||||
}
|
||||
|
@@ -0,0 +1,614 @@
|
||||
package com.maddyhome.idea.vim.extension.argtextobj;
|
||||
|
||||
import com.intellij.openapi.actionSystem.DataContext;
|
||||
import com.intellij.openapi.editor.Caret;
|
||||
import com.intellij.openapi.editor.Document;
|
||||
import com.intellij.openapi.editor.Editor;
|
||||
import com.maddyhome.idea.vim.VimPlugin;
|
||||
import com.maddyhome.idea.vim.command.*;
|
||||
import com.maddyhome.idea.vim.common.TextRange;
|
||||
import com.maddyhome.idea.vim.extension.VimExtension;
|
||||
import com.maddyhome.idea.vim.extension.VimExtensionHandler;
|
||||
import com.maddyhome.idea.vim.handler.TextObjectActionHandler;
|
||||
import com.maddyhome.idea.vim.listener.SelectionVimListenerSuppressor;
|
||||
import com.maddyhome.idea.vim.listener.VimListenerSuppressor;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
|
||||
import java.util.EnumSet;
|
||||
import java.util.Stack;
|
||||
|
||||
import static com.maddyhome.idea.vim.extension.VimExtensionFacade.putExtensionHandlerMapping;
|
||||
import static com.maddyhome.idea.vim.extension.VimExtensionFacade.putKeyMapping;
|
||||
import static com.maddyhome.idea.vim.group.visual.VisualGroupKt.vimSetSelection;
|
||||
import static com.maddyhome.idea.vim.helper.StringHelper.parseKeys;
|
||||
import static java.util.Collections.emptyList;
|
||||
|
||||
/**
|
||||
* @author igrekster
|
||||
*/
|
||||
|
||||
public class VimArgTextObjExtension implements VimExtension {
|
||||
|
||||
@Override
|
||||
public @NotNull String getName() {
|
||||
return "argtextobj";
|
||||
}
|
||||
|
||||
@Override
|
||||
public void init() {
|
||||
|
||||
putExtensionHandlerMapping(MappingMode.XO, parseKeys("<Plug>InnerArgument"), getOwner(), new VimArgTextObjExtension.ArgumentHandler(true), false);
|
||||
putExtensionHandlerMapping(MappingMode.XO, parseKeys("<Plug>OuterArgument"), getOwner(), new VimArgTextObjExtension.ArgumentHandler(false), false);
|
||||
|
||||
putKeyMapping(MappingMode.XO, parseKeys("ia"), getOwner(), parseKeys("<Plug>InnerArgument"), true);
|
||||
putKeyMapping(MappingMode.XO, parseKeys("aa"), getOwner(), parseKeys("<Plug>OuterArgument"), true);
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* A text object for an argument to a function definition or a call.
|
||||
*/
|
||||
static class ArgumentHandler implements VimExtensionHandler {
|
||||
final boolean isInner;
|
||||
|
||||
ArgumentHandler(boolean isInner) {
|
||||
super();
|
||||
this.isInner = isInner;
|
||||
}
|
||||
|
||||
static class ArgumentTextObjectHandler extends TextObjectActionHandler {
|
||||
private final boolean isInner;
|
||||
|
||||
ArgumentTextObjectHandler(boolean isInner) {
|
||||
this.isInner = isInner;
|
||||
}
|
||||
|
||||
@Override
|
||||
public @Nullable TextRange getRange(@NotNull Editor editor, @NotNull Caret caret, @NotNull DataContext context, int count, int rawCount, @Nullable Argument argument) {
|
||||
final ArgBoundsFinder finder = new ArgBoundsFinder(editor.getDocument());
|
||||
int pos = caret.getOffset();
|
||||
|
||||
for (int i = 0; i < count; ++i) {
|
||||
if (!finder.findBoundsAt(pos)) {
|
||||
VimPlugin.showMessage(finder.errorMessage());
|
||||
VimPlugin.indicateError();
|
||||
return null;
|
||||
}
|
||||
if (i + 1 < count) {
|
||||
finder.extendTillNext();
|
||||
}
|
||||
|
||||
pos = finder.getRightBound();
|
||||
}
|
||||
|
||||
if (isInner) {
|
||||
finder.adjustForInner();
|
||||
} else {
|
||||
finder.adjustForOuter();
|
||||
}
|
||||
return new TextRange(finder.getLeftBound(), finder.getRightBound());
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void execute(@NotNull Editor editor, @NotNull DataContext context) {
|
||||
|
||||
@NotNull CommandState commandState = CommandState.getInstance(editor);
|
||||
int count = Math.max(1, commandState.getCommandBuilder().getCount());
|
||||
|
||||
final ArgumentTextObjectHandler textObjectHandler = new ArgumentTextObjectHandler(isInner);
|
||||
if (!commandState.isOperatorPending()) {
|
||||
editor.getCaretModel().runForEachCaret((Caret caret) -> {
|
||||
final TextRange range = textObjectHandler.getRange(editor, caret, context, count, 0, null);
|
||||
if (range != null) {
|
||||
try (VimListenerSuppressor.Locked ignored = SelectionVimListenerSuppressor.INSTANCE.lock()) {
|
||||
if (commandState.getMode() == CommandState.Mode.VISUAL) {
|
||||
vimSetSelection(caret, range.getStartOffset(), range.getEndOffset() - 1, true);
|
||||
} else {
|
||||
caret.moveToOffset(range.getStartOffset());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
});
|
||||
} else {
|
||||
commandState.getCommandBuilder().completeCommandPart(new Argument(new Command(count,
|
||||
textObjectHandler, Command.Type.MOTION, EnumSet.of(CommandFlags.FLAG_MOT_CHARACTERWISE),
|
||||
emptyList()
|
||||
)));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Helper class to find argument boundaries starting at the specified
|
||||
* position
|
||||
*/
|
||||
private static class ArgBoundsFinder {
|
||||
private final CharSequence text;
|
||||
private final Document document;
|
||||
private int leftBound = Integer.MAX_VALUE;
|
||||
private int rightBound = Integer.MIN_VALUE;
|
||||
private int leftBracket;
|
||||
private int rightBracket;
|
||||
private String error = null;
|
||||
private static final String QUOTES = "\"'";
|
||||
|
||||
// NOTE: brackets must match by the position, and ordered by rank.
|
||||
// NOTE2: The original implementation worked for ], } and > as well, but because of some broken cases this feature
|
||||
// was removed.
|
||||
private static final String OPEN_BRACKETS = "("; // "[{(<"
|
||||
private static final String CLOSE_BRACKETS = ")"; // "]})>"
|
||||
|
||||
private static final int MAX_SEARCH_LINES = 10;
|
||||
private static final int MAX_SEARCH_OFFSET = MAX_SEARCH_LINES * 80;
|
||||
|
||||
ArgBoundsFinder(@NotNull Document document) {
|
||||
this.text = document.getImmutableCharSequence();
|
||||
this.document = document;
|
||||
}
|
||||
|
||||
/**
|
||||
* Finds left and right boundaries of an argument at the specified
|
||||
* position. If successful @ref getLeftBound() will point to the left
|
||||
* argument delimiter and @ref getRightBound() will point to the right
|
||||
* argument delimiter. Use @ref adjustForInner or @ref adjustForOuter to
|
||||
* fix the boundaries based on the type of text object.
|
||||
*
|
||||
* @param position starting position.
|
||||
*/
|
||||
boolean findBoundsAt(int position) throws IllegalStateException {
|
||||
if (text.length() == 0) {
|
||||
error = "empty document";
|
||||
return false;
|
||||
}
|
||||
leftBound = Math.min(position, leftBound);
|
||||
rightBound = Math.max(position, rightBound);
|
||||
getOutOfQuotedText();
|
||||
if (rightBound == leftBound) {
|
||||
if (isCloseBracket(getCharAt(rightBound))) {
|
||||
--leftBound;
|
||||
} else {
|
||||
++rightBound;
|
||||
}
|
||||
}
|
||||
int nextLeft = leftBound;
|
||||
int nextRight = rightBound;
|
||||
final int leftLimit = leftLimit(position);
|
||||
final int rightLimit = rightLimit(position);
|
||||
//
|
||||
// Try to extend the bounds until one of the bounds is a comma.
|
||||
// This handles cases like: fun(a, (30 + <cursor>x) * 20, c)
|
||||
//
|
||||
boolean bothBrackets;
|
||||
do {
|
||||
leftBracket = nextLeft;
|
||||
rightBracket = nextRight;
|
||||
if (!findOuterBrackets(leftLimit, rightLimit)) {
|
||||
error = "not inside argument list";
|
||||
return false;
|
||||
}
|
||||
leftBound = nextLeft;
|
||||
findLeftBound();
|
||||
nextLeft = leftBound - 1;
|
||||
rightBound = nextRight;
|
||||
findRightBound();
|
||||
nextRight = rightBound + 1;
|
||||
//
|
||||
// If reached text boundaries or there is nothing between delimiters.
|
||||
//
|
||||
if (nextLeft < leftLimit || nextRight > rightLimit || (rightBound - leftBound) == 1) {
|
||||
error = "not an argument";
|
||||
return false;
|
||||
}
|
||||
bothBrackets = getCharAt(leftBound) != ',' && getCharAt(rightBound) != ',';
|
||||
if (bothBrackets && isIdentPreceding()) {
|
||||
// Looking at a pair of brackets preceded by an
|
||||
// identifier -- single argument function call.
|
||||
break;
|
||||
}
|
||||
}
|
||||
while (leftBound > leftLimit && rightBound < rightLimit && bothBrackets);
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Skip left delimiter character and any following whitespace.
|
||||
*/
|
||||
void adjustForInner() {
|
||||
++leftBound;
|
||||
while (leftBound < rightBound && Character.isWhitespace(getCharAt(leftBound))) {
|
||||
++leftBound;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Exclude left bound character for the first argument, include the
|
||||
* right bound character and any following whitespace.
|
||||
*/
|
||||
void adjustForOuter() {
|
||||
if (getCharAt(leftBound) != ',') {
|
||||
++leftBound;
|
||||
extendTillNext();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Extend the right bound to the beginning of the next argument (if any).
|
||||
*/
|
||||
void extendTillNext() {
|
||||
if (rightBound + 1 < rightBracket && getCharAt(rightBound) == ',') {
|
||||
++rightBound;
|
||||
while (rightBound + 1 < rightBracket && Character.isWhitespace(getCharAt(rightBound))) {
|
||||
++rightBound;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
int getLeftBound() {
|
||||
return leftBound;
|
||||
}
|
||||
|
||||
int getRightBound() {
|
||||
return rightBound;
|
||||
}
|
||||
|
||||
private boolean isIdentPreceding() {
|
||||
int i = leftBound - 1;
|
||||
final int idEnd = i;
|
||||
while (i > 0 && Character.isJavaIdentifierPart(getCharAt(i))) {
|
||||
--i;
|
||||
}
|
||||
return (idEnd - i) > 0 && Character.isJavaIdentifierStart(getCharAt(i + 1));
|
||||
}
|
||||
|
||||
/**
|
||||
* Detects if current position is inside a quoted string and adjusts
|
||||
* left and right bounds to the boundaries of the string.
|
||||
*
|
||||
* NOTE: Does not support line continuations for quoted string ('\' at the end of line).
|
||||
*/
|
||||
private void getOutOfQuotedText() {
|
||||
// TODO this method should use IdeaVim methods to determine if the current position is in the string
|
||||
final int lineNo = document.getLineNumber(leftBound);
|
||||
final int lineStartOffset = document.getLineStartOffset(lineNo);
|
||||
final int lineEndOffset = document.getLineEndOffset(lineNo);
|
||||
int i = lineStartOffset;
|
||||
while (i <= leftBound) {
|
||||
if (isQuote(i)) {
|
||||
final int endOfQuotedText = skipQuotedTextForward(i, lineEndOffset);
|
||||
if (endOfQuotedText >= leftBound) {
|
||||
leftBound = i - 1;
|
||||
rightBound = endOfQuotedText + 1;
|
||||
break;
|
||||
} else {
|
||||
i = endOfQuotedText;
|
||||
}
|
||||
}
|
||||
++i;
|
||||
}
|
||||
}
|
||||
|
||||
private void findRightBound() {
|
||||
while (rightBound < rightBracket) {
|
||||
final char ch = getCharAt(rightBound);
|
||||
if (ch == ',') {
|
||||
break;
|
||||
}
|
||||
if (isOpenBracket(ch)) {
|
||||
rightBound = skipSexp(rightBound, rightBracket, SexpDirection.FORWARD);
|
||||
} else {
|
||||
if (isQuoteChar(ch)) {
|
||||
rightBound = skipQuotedTextForward(rightBound, rightBracket);
|
||||
}
|
||||
++rightBound;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private static char matchingBracket(char ch) {
|
||||
int idx = CLOSE_BRACKETS.indexOf(ch);
|
||||
if (idx != -1) {
|
||||
return OPEN_BRACKETS.charAt(idx);
|
||||
} else {
|
||||
assert isOpenBracket(ch);
|
||||
idx = OPEN_BRACKETS.indexOf(ch);
|
||||
return CLOSE_BRACKETS.charAt(idx);
|
||||
}
|
||||
}
|
||||
|
||||
private static boolean isCloseBracket(final int ch) {
|
||||
return CLOSE_BRACKETS.indexOf(ch) != -1;
|
||||
}
|
||||
|
||||
private static boolean isOpenBracket(final int ch) {
|
||||
return OPEN_BRACKETS.indexOf(ch) != -1;
|
||||
}
|
||||
|
||||
private void findLeftBound() {
|
||||
while (leftBound > leftBracket) {
|
||||
final char ch = getCharAt(leftBound);
|
||||
if (ch == ',') {
|
||||
break;
|
||||
}
|
||||
if (isCloseBracket(ch)) {
|
||||
leftBound = skipSexp(leftBound, leftBracket, SexpDirection.BACKWARD);
|
||||
} else {
|
||||
if (isQuoteChar(ch)) {
|
||||
leftBound = skipQuotedTextBackward(leftBound, leftBracket);
|
||||
}
|
||||
--leftBound;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private boolean isQuote(final int i) {
|
||||
return QUOTES.indexOf(getCharAt(i)) != -1;
|
||||
}
|
||||
|
||||
private static boolean isQuoteChar(final int ch) {
|
||||
return QUOTES.indexOf(ch) != -1;
|
||||
}
|
||||
|
||||
private char getCharAt(int logicalOffset) {
|
||||
assert logicalOffset < text.length();
|
||||
return text.charAt(logicalOffset);
|
||||
}
|
||||
|
||||
private int skipQuotedTextForward(final int start, final int end) {
|
||||
assert start < end;
|
||||
final char quoteChar = getCharAt(start);
|
||||
boolean backSlash = false;
|
||||
int i = start + 1;
|
||||
|
||||
while (i <= end) {
|
||||
final char ch = getCharAt(i);
|
||||
if (ch == quoteChar && !backSlash) {
|
||||
// Found matching quote and it's not escaped.
|
||||
break;
|
||||
} else {
|
||||
backSlash = ch == '\\' && !backSlash;
|
||||
}
|
||||
++i;
|
||||
}
|
||||
return i;
|
||||
}
|
||||
|
||||
private int skipQuotedTextBackward(final int start, final int end) {
|
||||
assert start > end;
|
||||
final char quoteChar = getCharAt(start);
|
||||
int i = start - 1;
|
||||
|
||||
while (i > end) {
|
||||
final char ch = getCharAt(i);
|
||||
final char prevChar = getCharAt(i - 1);
|
||||
// NOTE: doesn't handle cases like \\"str", but they make no
|
||||
// sense anyway.
|
||||
if (ch == quoteChar && prevChar != '\\') {
|
||||
// Found matching quote and it's not escaped.
|
||||
break;
|
||||
}
|
||||
--i;
|
||||
}
|
||||
return i;
|
||||
}
|
||||
|
||||
private int leftLimit(final int pos) {
|
||||
final int offsetLimit = Math.max(pos - MAX_SEARCH_OFFSET, 0);
|
||||
final int lineNo = document.getLineNumber(pos);
|
||||
final int lineOffsetLimit = document.getLineStartOffset(Math.max(0, lineNo - MAX_SEARCH_LINES));
|
||||
return Math.max(offsetLimit, lineOffsetLimit);
|
||||
}
|
||||
|
||||
private int rightLimit(final int pos) {
|
||||
final int offsetLimit = Math.min(pos + MAX_SEARCH_OFFSET, text.length());
|
||||
final int lineNo = document.getLineNumber(pos);
|
||||
final int lineOffsetLimit = document.getLineEndOffset(Math.min(document.getLineCount() - 1, lineNo + MAX_SEARCH_LINES));
|
||||
return Math.min(offsetLimit, lineOffsetLimit);
|
||||
}
|
||||
|
||||
String errorMessage() {
|
||||
return error;
|
||||
}
|
||||
|
||||
/**
|
||||
* Interface to parametrise S-expression traversal direction.
|
||||
*/
|
||||
abstract static class SexpDirection {
|
||||
abstract int delta();
|
||||
|
||||
abstract boolean isOpenBracket(char ch);
|
||||
|
||||
abstract boolean isCloseBracket(char ch);
|
||||
|
||||
abstract int skipQuotedText(int pos, int end, ArgBoundsFinder self);
|
||||
|
||||
static final SexpDirection FORWARD = new SexpDirection() {
|
||||
@Override
|
||||
int delta() {
|
||||
return 1;
|
||||
}
|
||||
|
||||
@Override
|
||||
boolean isOpenBracket(char ch) {
|
||||
return ArgBoundsFinder.isOpenBracket(ch);
|
||||
}
|
||||
|
||||
@Override
|
||||
boolean isCloseBracket(char ch) {
|
||||
return ArgBoundsFinder.isCloseBracket(ch);
|
||||
}
|
||||
|
||||
@Override
|
||||
int skipQuotedText(int pos, int end, ArgBoundsFinder self) {
|
||||
return self.skipQuotedTextForward(pos, end);
|
||||
}
|
||||
};
|
||||
static final SexpDirection BACKWARD = new SexpDirection() {
|
||||
@Override
|
||||
int delta() {
|
||||
return -1;
|
||||
}
|
||||
|
||||
@Override
|
||||
boolean isOpenBracket(char ch) {
|
||||
return ArgBoundsFinder.isCloseBracket(ch);
|
||||
}
|
||||
|
||||
@Override
|
||||
boolean isCloseBracket(char ch) {
|
||||
return ArgBoundsFinder.isOpenBracket(ch);
|
||||
}
|
||||
|
||||
@Override
|
||||
int skipQuotedText(int pos, int end, ArgBoundsFinder self) {
|
||||
return self.skipQuotedTextBackward(pos, end);
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Skip over an S-expression considering priorities when unbalanced.
|
||||
*
|
||||
* @param start position of the starting bracket.
|
||||
* @param end maximum position
|
||||
* @param dir direction instance
|
||||
* @return position after the S-expression, or the next to the start position if
|
||||
* unbalanced.
|
||||
*/
|
||||
private int skipSexp(final int start, final int end, SexpDirection dir) {
|
||||
char lastChar = getCharAt(start);
|
||||
assert dir.isOpenBracket(lastChar);
|
||||
Stack<Character> bracketStack = new Stack<>();
|
||||
bracketStack.push(lastChar);
|
||||
int i = start + dir.delta();
|
||||
while (!bracketStack.empty() && i != end) {
|
||||
final char ch = getCharAt(i);
|
||||
if (dir.isOpenBracket(ch)) {
|
||||
bracketStack.push(ch);
|
||||
} else {
|
||||
if (dir.isCloseBracket(ch)) {
|
||||
if (bracketStack.lastElement() == matchingBracket(ch)) {
|
||||
bracketStack.pop();
|
||||
} else {
|
||||
//noinspection StatementWithEmptyBody
|
||||
if (getBracketPrio(ch) < getBracketPrio(bracketStack.lastElement())) {
|
||||
// (<...) -> (...)
|
||||
bracketStack.pop();
|
||||
// Retry the same character again for cases like (...<<...).
|
||||
continue;
|
||||
} else { // Unbalanced brackets -- check ranking.
|
||||
// Ignore lower-priority closing brackets.
|
||||
// (...> -> (....
|
||||
}
|
||||
}
|
||||
} else {
|
||||
if (isQuoteChar(ch)) {
|
||||
i = dir.skipQuotedText(i, end, this);
|
||||
}
|
||||
}
|
||||
}
|
||||
i += dir.delta();
|
||||
}
|
||||
if (bracketStack.empty()) {
|
||||
return i;
|
||||
} else {
|
||||
return start + dir.delta();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @return rank of a bracket.
|
||||
*/
|
||||
static int getBracketPrio(char ch) {
|
||||
return Math.max(OPEN_BRACKETS.indexOf(ch), CLOSE_BRACKETS.indexOf(ch));
|
||||
}
|
||||
|
||||
/**
|
||||
* Find a pair of brackets surrounding (leftBracket..rightBracket) block.
|
||||
*
|
||||
* @param start minimum position to look for
|
||||
* @param end maximum position
|
||||
* @return true if found
|
||||
*/
|
||||
boolean findOuterBrackets(final int start, final int end) {
|
||||
boolean hasNewBracket = findPrevOpenBracket(start) && findNextCloseBracket(end);
|
||||
while (hasNewBracket) {
|
||||
final int leftPrio = getBracketPrio(getCharAt(leftBracket));
|
||||
final int rightPrio = getBracketPrio(getCharAt(rightBracket));
|
||||
if (leftPrio == rightPrio) {
|
||||
// matching brackets
|
||||
return true;
|
||||
} else {
|
||||
if (leftPrio < rightPrio) {
|
||||
if (rightBracket + 1 < end) {
|
||||
++rightBracket;
|
||||
hasNewBracket = findNextCloseBracket(end);
|
||||
} else {
|
||||
hasNewBracket = false;
|
||||
}
|
||||
} else {
|
||||
if (leftBracket > 1) {
|
||||
--leftBracket;
|
||||
hasNewBracket = findPrevOpenBracket(start);
|
||||
} else {
|
||||
hasNewBracket = false;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Finds unmatched open bracket starting at @a leftBracket.
|
||||
*
|
||||
* @param start minimum position.
|
||||
* @return true if found
|
||||
*/
|
||||
private boolean findPrevOpenBracket(final int start) {
|
||||
char ch;
|
||||
while (!isOpenBracket(ch = getCharAt(leftBracket))) {
|
||||
if (isCloseBracket(ch)) {
|
||||
leftBracket = skipSexp(leftBracket, start, SexpDirection.BACKWARD);
|
||||
} else {
|
||||
if (isQuoteChar(ch)) {
|
||||
leftBracket = skipQuotedTextBackward(leftBracket, start);
|
||||
} else {
|
||||
if (leftBracket == start) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
--leftBracket;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Finds unmatched close bracket starting at @a rightBracket.
|
||||
*
|
||||
* @param end maximum position.
|
||||
* @return true if found
|
||||
*/
|
||||
private boolean findNextCloseBracket(final int end) {
|
||||
char ch;
|
||||
while (!isCloseBracket(ch = getCharAt(rightBracket))) {
|
||||
if (isOpenBracket(ch)) {
|
||||
rightBracket = skipSexp(rightBracket, end, SexpDirection.FORWARD);
|
||||
} else {
|
||||
if (isQuoteChar(ch)) {
|
||||
rightBracket = skipQuotedTextForward(rightBracket, end);
|
||||
}
|
||||
++rightBracket;
|
||||
}
|
||||
if (rightBracket >= end) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
@@ -33,8 +33,8 @@ import com.maddyhome.idea.vim.command.CommandState;
|
||||
import com.maddyhome.idea.vim.command.MappingMode;
|
||||
import com.maddyhome.idea.vim.command.SelectionType;
|
||||
import com.maddyhome.idea.vim.common.TextRange;
|
||||
import com.maddyhome.idea.vim.extension.VimExtension;
|
||||
import com.maddyhome.idea.vim.extension.VimExtensionHandler;
|
||||
import com.maddyhome.idea.vim.extension.VimNonDisposableExtension;
|
||||
import com.maddyhome.idea.vim.key.OperatorFunction;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
@@ -45,23 +45,22 @@ import static com.maddyhome.idea.vim.helper.StringHelper.parseKeys;
|
||||
/**
|
||||
* @author dhleong
|
||||
*/
|
||||
public class CommentaryExtension extends VimNonDisposableExtension {
|
||||
public class CommentaryExtension implements VimExtension {
|
||||
|
||||
@NotNull
|
||||
@Override
|
||||
public String getName() {
|
||||
public @NotNull String getName() {
|
||||
return "commentary";
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void initOnce() {
|
||||
putExtensionHandlerMapping(MappingMode.N, parseKeys("<Plug>(CommentMotion)"), new CommentMotionHandler(), false);
|
||||
putExtensionHandlerMapping(MappingMode.N, parseKeys("<Plug>(CommentLine)"), new CommentLineHandler(), false);
|
||||
putExtensionHandlerMapping(MappingMode.XO, parseKeys("<Plug>(CommentMotionV)"), new CommentMotionVHandler(), false);
|
||||
public void init() {
|
||||
putExtensionHandlerMapping(MappingMode.N, parseKeys("<Plug>(CommentMotion)"), getOwner(), new CommentMotionHandler(), false);
|
||||
putExtensionHandlerMapping(MappingMode.N, parseKeys("<Plug>(CommentLine)"), getOwner(), new CommentLineHandler(), false);
|
||||
putExtensionHandlerMapping(MappingMode.XO, parseKeys("<Plug>(CommentMotionV)"), getOwner(), new CommentMotionVHandler(), false);
|
||||
|
||||
putKeyMapping(MappingMode.N, parseKeys("gc"), parseKeys("<Plug>(CommentMotion)"), true);
|
||||
putKeyMapping(MappingMode.N, parseKeys("gcc"), parseKeys("<Plug>(CommentLine)"), true);
|
||||
putKeyMapping(MappingMode.XO, parseKeys("gc"), parseKeys("<Plug>(CommentMotionV)"), true);
|
||||
putKeyMapping(MappingMode.N, parseKeys("gc"), getOwner(), parseKeys("<Plug>(CommentMotion)"), true);
|
||||
putKeyMapping(MappingMode.N, parseKeys("gcc"), getOwner(), parseKeys("<Plug>(CommentLine)"), true);
|
||||
putKeyMapping(MappingMode.XO, parseKeys("gc"), getOwner(), parseKeys("<Plug>(CommentMotionV)"), true);
|
||||
}
|
||||
|
||||
private static class CommentMotionHandler implements VimExtensionHandler {
|
||||
@@ -135,8 +134,7 @@ public class CommentaryExtension extends VimNonDisposableExtension {
|
||||
});
|
||||
}
|
||||
|
||||
@Nullable
|
||||
private TextRange getCommentRange(@NotNull Editor editor) {
|
||||
private @Nullable TextRange getCommentRange(@NotNull Editor editor) {
|
||||
final CommandState.Mode mode = CommandState.getInstance(editor).getMode();
|
||||
switch (mode) {
|
||||
case COMMAND:
|
||||
|
@@ -27,10 +27,10 @@ import com.maddyhome.idea.vim.VimPlugin
|
||||
import com.maddyhome.idea.vim.command.CommandFlags
|
||||
import com.maddyhome.idea.vim.command.MappingMode
|
||||
import com.maddyhome.idea.vim.common.TextRange
|
||||
import com.maddyhome.idea.vim.extension.VimExtension
|
||||
import com.maddyhome.idea.vim.extension.VimExtensionFacade.putExtensionHandlerMapping
|
||||
import com.maddyhome.idea.vim.extension.VimExtensionFacade.putKeyMapping
|
||||
import com.maddyhome.idea.vim.extension.VimExtensionHandler
|
||||
import com.maddyhome.idea.vim.extension.VimNonDisposableExtension
|
||||
import com.maddyhome.idea.vim.group.MotionGroup
|
||||
import com.maddyhome.idea.vim.group.visual.vimSetSelection
|
||||
import com.maddyhome.idea.vim.helper.EditorHelper
|
||||
@@ -56,21 +56,21 @@ private const val ALL_OCCURRENCES = "<Plug>AllOccurrences"
|
||||
*
|
||||
* See https://github.com/terryma/vim-multiple-cursors
|
||||
* */
|
||||
class VimMultipleCursorsExtension : VimNonDisposableExtension() {
|
||||
class VimMultipleCursorsExtension : VimExtension {
|
||||
override fun getName() = "multiple-cursors"
|
||||
|
||||
override fun initOnce() {
|
||||
putExtensionHandlerMapping(MappingMode.NXO, parseKeys(NEXT_WHOLE_OCCURRENCE), NextOccurrenceHandler(), false)
|
||||
putExtensionHandlerMapping(MappingMode.NXO, parseKeys(NEXT_OCCURRENCE), NextOccurrenceHandler(whole = false), false)
|
||||
putExtensionHandlerMapping(MappingMode.NXO, parseKeys(ALL_WHOLE_OCCURRENCES), AllOccurrencesHandler(), false)
|
||||
putExtensionHandlerMapping(MappingMode.NXO, parseKeys(ALL_OCCURRENCES), AllOccurrencesHandler(whole = false), false)
|
||||
putExtensionHandlerMapping(MappingMode.X, parseKeys(SKIP_OCCURRENCE), SkipOccurrenceHandler(), false)
|
||||
putExtensionHandlerMapping(MappingMode.X, parseKeys(REMOVE_OCCURRENCE), RemoveOccurrenceHandler(), false)
|
||||
override fun init() {
|
||||
putExtensionHandlerMapping(MappingMode.NXO, parseKeys(NEXT_WHOLE_OCCURRENCE), owner, NextOccurrenceHandler(), false)
|
||||
putExtensionHandlerMapping(MappingMode.NXO, parseKeys(NEXT_OCCURRENCE), owner, NextOccurrenceHandler(whole = false), false)
|
||||
putExtensionHandlerMapping(MappingMode.NXO, parseKeys(ALL_WHOLE_OCCURRENCES), owner, AllOccurrencesHandler(), false )
|
||||
putExtensionHandlerMapping(MappingMode.NXO, parseKeys(ALL_OCCURRENCES), owner, AllOccurrencesHandler(whole = false), false )
|
||||
putExtensionHandlerMapping(MappingMode.X, parseKeys(SKIP_OCCURRENCE), owner, SkipOccurrenceHandler(), false )
|
||||
putExtensionHandlerMapping(MappingMode.X, parseKeys(REMOVE_OCCURRENCE), owner, RemoveOccurrenceHandler(), false )
|
||||
|
||||
putKeyMapping(MappingMode.NXO, parseKeys("<A-n>"), parseKeys(NEXT_WHOLE_OCCURRENCE), true)
|
||||
putKeyMapping(MappingMode.NXO, parseKeys("g<A-n>"), parseKeys(NEXT_OCCURRENCE), true)
|
||||
putKeyMapping(MappingMode.X, parseKeys("<A-x>"), parseKeys(SKIP_OCCURRENCE), true)
|
||||
putKeyMapping(MappingMode.X, parseKeys("<A-p>"), parseKeys(REMOVE_OCCURRENCE), true)
|
||||
putKeyMapping(MappingMode.NXO, parseKeys("<A-n>"), owner, parseKeys(NEXT_WHOLE_OCCURRENCE), true)
|
||||
putKeyMapping(MappingMode.NXO, parseKeys("g<A-n>"), owner, parseKeys(NEXT_OCCURRENCE), true)
|
||||
putKeyMapping(MappingMode.X, parseKeys("<A-x>"), owner, parseKeys(SKIP_OCCURRENCE), true)
|
||||
putKeyMapping(MappingMode.X, parseKeys("<A-p>"), owner, parseKeys(REMOVE_OCCURRENCE), true)
|
||||
}
|
||||
|
||||
abstract class WriteActionHandler : VimExtensionHandler {
|
||||
|
@@ -25,6 +25,7 @@ import com.maddyhome.idea.vim.command.CommandState
|
||||
import com.maddyhome.idea.vim.command.MappingMode
|
||||
import com.maddyhome.idea.vim.command.SelectionType
|
||||
import com.maddyhome.idea.vim.common.TextRange
|
||||
import com.maddyhome.idea.vim.extension.VimExtension
|
||||
import com.maddyhome.idea.vim.extension.VimExtensionFacade.executeNormal
|
||||
import com.maddyhome.idea.vim.extension.VimExtensionFacade.getRegister
|
||||
import com.maddyhome.idea.vim.extension.VimExtensionFacade.inputKeyStroke
|
||||
@@ -34,7 +35,6 @@ import com.maddyhome.idea.vim.extension.VimExtensionFacade.putKeyMapping
|
||||
import com.maddyhome.idea.vim.extension.VimExtensionFacade.setOperatorFunction
|
||||
import com.maddyhome.idea.vim.extension.VimExtensionFacade.setRegister
|
||||
import com.maddyhome.idea.vim.extension.VimExtensionHandler
|
||||
import com.maddyhome.idea.vim.extension.VimNonDisposableExtension
|
||||
import com.maddyhome.idea.vim.helper.EditorHelper
|
||||
import com.maddyhome.idea.vim.helper.StringHelper
|
||||
import com.maddyhome.idea.vim.helper.mode
|
||||
@@ -51,19 +51,19 @@ import javax.swing.KeyStroke
|
||||
* @author dhleong
|
||||
* @author vlan
|
||||
*/
|
||||
class VimSurroundExtension : VimNonDisposableExtension() {
|
||||
class VimSurroundExtension : VimExtension {
|
||||
override fun getName() = "surround"
|
||||
|
||||
override fun initOnce() {
|
||||
putExtensionHandlerMapping(MappingMode.N, StringHelper.parseKeys("<Plug>YSurround"), YSurroundHandler(), false)
|
||||
putExtensionHandlerMapping(MappingMode.N, StringHelper.parseKeys("<Plug>CSurround"), CSurroundHandler(), false)
|
||||
putExtensionHandlerMapping(MappingMode.N, StringHelper.parseKeys("<Plug>DSurround"), DSurroundHandler(), false)
|
||||
putExtensionHandlerMapping(MappingMode.XO, StringHelper.parseKeys("<Plug>VSurround"), VSurroundHandler(), false)
|
||||
override fun init() {
|
||||
putExtensionHandlerMapping(MappingMode.N, StringHelper.parseKeys("<Plug>YSurround"), owner, YSurroundHandler(), false)
|
||||
putExtensionHandlerMapping(MappingMode.N, StringHelper.parseKeys("<Plug>CSurround"), owner, CSurroundHandler(), false)
|
||||
putExtensionHandlerMapping(MappingMode.N, StringHelper.parseKeys("<Plug>DSurround"), owner, DSurroundHandler(), false)
|
||||
putExtensionHandlerMapping(MappingMode.XO, StringHelper.parseKeys("<Plug>VSurround"), owner, VSurroundHandler(), false)
|
||||
|
||||
putKeyMapping(MappingMode.N, StringHelper.parseKeys("ys"), StringHelper.parseKeys("<Plug>YSurround"), true)
|
||||
putKeyMapping(MappingMode.N, StringHelper.parseKeys("cs"), StringHelper.parseKeys("<Plug>CSurround"), true)
|
||||
putKeyMapping(MappingMode.N, StringHelper.parseKeys("ds"), StringHelper.parseKeys("<Plug>DSurround"), true)
|
||||
putKeyMapping(MappingMode.XO, StringHelper.parseKeys("S"), StringHelper.parseKeys("<Plug>VSurround"), true)
|
||||
putKeyMapping(MappingMode.N, StringHelper.parseKeys("ys"), owner, StringHelper.parseKeys("<Plug>YSurround"), true)
|
||||
putKeyMapping(MappingMode.N, StringHelper.parseKeys("cs"), owner, StringHelper.parseKeys("<Plug>CSurround"), true)
|
||||
putKeyMapping(MappingMode.N, StringHelper.parseKeys("ds"), owner, StringHelper.parseKeys("<Plug>DSurround"), true)
|
||||
putKeyMapping(MappingMode.XO, StringHelper.parseKeys("S"), owner, StringHelper.parseKeys("<Plug>VSurround"), true)
|
||||
}
|
||||
|
||||
private class YSurroundHandler : VimExtensionHandler {
|
||||
|
@@ -0,0 +1,152 @@
|
||||
/*
|
||||
* IdeaVim - Vim emulator for IDEs based on the IntelliJ platform
|
||||
* Copyright (C) 2003-2020 The IdeaVim authors
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 2 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package com.maddyhome.idea.vim.extension.textobjentire;
|
||||
|
||||
import com.intellij.openapi.actionSystem.DataContext;
|
||||
import com.intellij.openapi.editor.Caret;
|
||||
import com.intellij.openapi.editor.Editor;
|
||||
import com.maddyhome.idea.vim.command.*;
|
||||
import com.maddyhome.idea.vim.common.TextRange;
|
||||
import com.maddyhome.idea.vim.extension.VimExtension;
|
||||
import com.maddyhome.idea.vim.extension.VimExtensionHandler;
|
||||
import com.maddyhome.idea.vim.handler.TextObjectActionHandler;
|
||||
import com.maddyhome.idea.vim.listener.SelectionVimListenerSuppressor;
|
||||
import com.maddyhome.idea.vim.listener.VimListenerSuppressor;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
|
||||
import java.util.EnumSet;
|
||||
|
||||
import static com.maddyhome.idea.vim.extension.VimExtensionFacade.putExtensionHandlerMapping;
|
||||
import static com.maddyhome.idea.vim.extension.VimExtensionFacade.putKeyMapping;
|
||||
import static com.maddyhome.idea.vim.group.visual.VisualGroupKt.vimSetSelection;
|
||||
import static com.maddyhome.idea.vim.helper.StringHelper.parseKeys;
|
||||
import static java.util.Collections.emptyList;
|
||||
|
||||
/**
|
||||
* Port of vim-entire:
|
||||
* https://github.com/kana/vim-textobj-entire
|
||||
*
|
||||
* <p>
|
||||
* vim-textobj-entire provides two text objects:
|
||||
* <ul>
|
||||
* <li>ae targets the entire content of the current buffer.</li>
|
||||
* <li>ie is similar to ae, but ie does not include leading and trailing empty lines. ie is handy for some situations. For example,</li>
|
||||
* <ul>
|
||||
* <li>Paste some text into a new buffer (<C-w>n"*P) -- note that the initial empty line is left as the last line.</li>
|
||||
* <li>Edit the text (:%s/foo/bar/g etc)</li>
|
||||
* <li>Then copy the resulting text to another application ("*yie)</li>
|
||||
* </ul>
|
||||
* </ul>
|
||||
*
|
||||
* See also the reference manual for more details at:
|
||||
* https://github.com/kana/vim-textobj-entire/blob/master/doc/textobj-entire.txt
|
||||
*
|
||||
* @author Alexandre Grison (@agrison)
|
||||
*/
|
||||
public class VimTextObjEntireExtension implements VimExtension {
|
||||
@Override
|
||||
public @NotNull
|
||||
String getName() {
|
||||
return "textobj-entire";
|
||||
}
|
||||
|
||||
@Override
|
||||
public void init() {
|
||||
putExtensionHandlerMapping(MappingMode.XO, parseKeys("<Plug>textobj-entire-a"), getOwner(),
|
||||
new VimTextObjEntireExtension.EntireHandler(false), false);
|
||||
putExtensionHandlerMapping(MappingMode.XO, parseKeys("<Plug>textobj-entire-i"), getOwner(),
|
||||
new VimTextObjEntireExtension.EntireHandler(true), false);
|
||||
|
||||
putKeyMapping(MappingMode.XO, parseKeys("ae"), getOwner(), parseKeys("<Plug>textobj-entire-a"), true);
|
||||
putKeyMapping(MappingMode.XO, parseKeys("ie"), getOwner(), parseKeys("<Plug>textobj-entire-i"), true);
|
||||
}
|
||||
|
||||
static class EntireHandler implements VimExtensionHandler {
|
||||
final boolean ignoreLeadingAndTrailing;
|
||||
|
||||
EntireHandler(boolean ignoreLeadingAndTrailing) {
|
||||
this.ignoreLeadingAndTrailing = ignoreLeadingAndTrailing;
|
||||
}
|
||||
|
||||
static class EntireTextObjectHandler extends TextObjectActionHandler {
|
||||
final boolean ignoreLeadingAndTrailing;
|
||||
|
||||
EntireTextObjectHandler(boolean ignoreLeadingAndTrailing) {
|
||||
this.ignoreLeadingAndTrailing = ignoreLeadingAndTrailing;
|
||||
}
|
||||
|
||||
@Override
|
||||
public @Nullable
|
||||
TextRange getRange(@NotNull Editor editor, @NotNull Caret caret, @NotNull DataContext context,
|
||||
int count, int rawCount, @Nullable Argument argument) {
|
||||
int start = 0, end = editor.getDocument().getTextLength();
|
||||
|
||||
// for the `ie` text object we don't want leading an trailing spaces
|
||||
// so we have to scan the document text to find the correct start & end
|
||||
if (ignoreLeadingAndTrailing) {
|
||||
String content = editor.getDocument().getText();
|
||||
for (int i = 0; i < content.length(); ++i) {
|
||||
if (!Character.isWhitespace(content.charAt(i))) {
|
||||
start = i;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
for (int i = content.length() - 1; i >= start; --i) {
|
||||
if (!Character.isWhitespace(content.charAt(i))) {
|
||||
end = i + 1;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return new TextRange(start, end);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void execute(@NotNull Editor editor, @NotNull DataContext context) {
|
||||
@NotNull CommandState commandState = CommandState.getInstance(editor);
|
||||
int count = Math.max(1, commandState.getCommandBuilder().getCount());
|
||||
|
||||
final EntireTextObjectHandler textObjectHandler = new EntireTextObjectHandler(ignoreLeadingAndTrailing);
|
||||
if (!commandState.isOperatorPending()) {
|
||||
editor.getCaretModel().runForEachCaret((Caret caret) -> {
|
||||
final TextRange range = textObjectHandler.getRange(editor, caret, context, count, 0, null);
|
||||
if (range != null) {
|
||||
try (VimListenerSuppressor.Locked ignored = SelectionVimListenerSuppressor.INSTANCE.lock()) {
|
||||
if (commandState.getMode() == CommandState.Mode.VISUAL) {
|
||||
vimSetSelection(caret, range.getStartOffset(), range.getEndOffset() - 1, true);
|
||||
} else {
|
||||
caret.moveToOffset(range.getStartOffset());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
});
|
||||
} else {
|
||||
commandState.getCommandBuilder().completeCommandPart(new Argument(new Command(count,
|
||||
textObjectHandler, Command.Type.MOTION,
|
||||
EnumSet.of(CommandFlags.FLAG_MOT_CHARACTERWISE),
|
||||
emptyList())));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@@ -85,7 +85,7 @@ public class ChangeGroup {
|
||||
private static final String VIM_MOTION_BIG_WORD_END_RIGHT = "VimMotionBigWordEndRightAction";
|
||||
private static final String VIM_MOTION_CAMEL_END_RIGHT = "VimMotionCamelEndRightAction";
|
||||
|
||||
@Nullable private Command lastInsert;
|
||||
private @Nullable Command lastInsert;
|
||||
|
||||
private void setInsertRepeat(int lines, int column, boolean append) {
|
||||
repeatLines = lines;
|
||||
@@ -154,7 +154,7 @@ public class ChangeGroup {
|
||||
*
|
||||
* @param editor The editor to insert into
|
||||
*/
|
||||
public void insertNewLineAbove(@NotNull final Editor editor, @NotNull DataContext context) {
|
||||
public void insertNewLineAbove(final @NotNull Editor editor, @NotNull DataContext context) {
|
||||
if (editor.isOneLineMode()) return;
|
||||
|
||||
Set<Caret> firstLiners = new HashSet<>();
|
||||
@@ -214,7 +214,7 @@ public class ChangeGroup {
|
||||
* @param editor The editor to insert into
|
||||
* @param context The data context
|
||||
*/
|
||||
public void insertNewLineBelow(@NotNull final Editor editor, @NotNull final DataContext context) {
|
||||
public void insertNewLineBelow(final @NotNull Editor editor, final @NotNull DataContext context) {
|
||||
if (editor.isOneLineMode()) return;
|
||||
|
||||
for (Caret caret : editor.getCaretModel().getAllCarets()) {
|
||||
@@ -308,7 +308,7 @@ public class ChangeGroup {
|
||||
return false;
|
||||
}
|
||||
|
||||
@Nullable private DocumentListener documentListener;
|
||||
private @Nullable DocumentListener documentListener;
|
||||
|
||||
/**
|
||||
* If the cursor is currently after the start of the current insert this deletes all the newly inserted text.
|
||||
@@ -421,7 +421,7 @@ public class ChangeGroup {
|
||||
// Workaround for VIM-1546. Another solution is highly appreciated.
|
||||
public boolean tabAction = false;
|
||||
|
||||
@NotNull private final EditorMouseListener listener = new EditorMouseListener() {
|
||||
private final @NotNull EditorMouseListener listener = new EditorMouseListener() {
|
||||
@Override
|
||||
public void mouseClicked(@NotNull EditorMouseEvent event) {
|
||||
Editor editor = event.getEditor();
|
||||
@@ -737,9 +737,9 @@ public class ChangeGroup {
|
||||
* @param key The user entered keystroke
|
||||
* @param plan the current action plan draft
|
||||
*/
|
||||
public void beforeProcessKey(@NotNull final Editor editor,
|
||||
@NotNull final DataContext context,
|
||||
@NotNull final KeyStroke key,
|
||||
public void beforeProcessKey(final @NotNull Editor editor,
|
||||
final @NotNull DataContext context,
|
||||
final @NotNull KeyStroke key,
|
||||
@NotNull ActionPlan plan) {
|
||||
|
||||
final TypedActionHandler originalHandler = KeyHandler.getInstance().getOriginalHandler();
|
||||
@@ -805,9 +805,9 @@ public class ChangeGroup {
|
||||
* @param key The user entered keystroke
|
||||
* @return true if this was a regular character, false if not
|
||||
*/
|
||||
public boolean processKey(@NotNull final Editor editor,
|
||||
@NotNull final DataContext context,
|
||||
@NotNull final KeyStroke key) {
|
||||
public boolean processKey(final @NotNull Editor editor,
|
||||
final @NotNull DataContext context,
|
||||
final @NotNull KeyStroke key) {
|
||||
if (logger.isDebugEnabled()) {
|
||||
logger.debug("processKey(" + key + ")");
|
||||
}
|
||||
@@ -824,9 +824,9 @@ public class ChangeGroup {
|
||||
return false;
|
||||
}
|
||||
|
||||
public boolean processKeyInSelectMode(@NotNull final Editor editor,
|
||||
@NotNull final DataContext context,
|
||||
@NotNull final KeyStroke key) {
|
||||
public boolean processKeyInSelectMode(final @NotNull Editor editor,
|
||||
final @NotNull DataContext context,
|
||||
final @NotNull KeyStroke key) {
|
||||
boolean res;
|
||||
try (VimListenerSuppressor.Locked ignored = SelectionVimListenerSuppressor.INSTANCE.lock()) {
|
||||
res = processKey(editor, context, key);
|
||||
@@ -1061,13 +1061,12 @@ public class ChangeGroup {
|
||||
return true;
|
||||
}
|
||||
|
||||
@Nullable
|
||||
public Pair<TextRange, SelectionType> getDeleteRangeAndType(@NotNull Editor editor,
|
||||
public @Nullable Pair<TextRange, SelectionType> getDeleteRangeAndType(@NotNull Editor editor,
|
||||
@NotNull Caret caret,
|
||||
@NotNull DataContext context,
|
||||
int count,
|
||||
int rawCount,
|
||||
@NotNull final Argument argument,
|
||||
final @NotNull Argument argument,
|
||||
boolean isChange) {
|
||||
final TextRange range = MotionGroup.getMotionRange(editor, caret, context, count, rawCount, argument);
|
||||
if (range == null) return null;
|
||||
@@ -1163,7 +1162,8 @@ public class ChangeGroup {
|
||||
final LogicalPosition lp =
|
||||
editor.offsetToLogicalPosition(VimPlugin.getMotion().moveCaretToLineStartSkipLeading(editor, caret));
|
||||
|
||||
if (editor.getDocument().getText().isEmpty()) {
|
||||
// Please don't use `getDocument().getText().isEmpty()`
|
||||
if (editor.getDocument().getTextLength() == 0) {
|
||||
insertBeforeCursor(editor, context);
|
||||
return true;
|
||||
}
|
||||
@@ -1456,7 +1456,8 @@ public class ChangeGroup {
|
||||
boolean res = deleteRange(editor, caret, range, type, true);
|
||||
if (res) {
|
||||
if (type == SelectionType.LINE_WISE) {
|
||||
if (editor.getDocument().getText().isEmpty()) {
|
||||
// Please don't use `getDocument().getText().isEmpty()`
|
||||
if (editor.getDocument().getTextLength() == 0) {
|
||||
insertBeforeCursor(editor, context);
|
||||
}
|
||||
else if (after) {
|
||||
@@ -1729,8 +1730,8 @@ public class ChangeGroup {
|
||||
* @param type The type of deletion
|
||||
* @return true if able to delete the text, false if not
|
||||
*/
|
||||
private boolean deleteText(@NotNull final Editor editor,
|
||||
@NotNull final TextRange range,
|
||||
private boolean deleteText(final @NotNull Editor editor,
|
||||
final @NotNull TextRange range,
|
||||
@Nullable SelectionType type) {
|
||||
// Fix for https://youtrack.jetbrains.net/issue/VIM-35
|
||||
if (!range.normalize(EditorHelper.getFileSize(editor, true))) {
|
||||
@@ -1838,7 +1839,7 @@ public class ChangeGroup {
|
||||
*
|
||||
* @return true
|
||||
*/
|
||||
public boolean changeNumberVisualMode(@NotNull final Editor editor,
|
||||
public boolean changeNumberVisualMode(final @NotNull Editor editor,
|
||||
@NotNull Caret caret,
|
||||
@NotNull TextRange selectedRange,
|
||||
final int count,
|
||||
@@ -1882,7 +1883,7 @@ public class ChangeGroup {
|
||||
private int repeatCharsCount;
|
||||
private List<Object> lastStrokes;
|
||||
|
||||
public boolean changeNumber(@NotNull final Editor editor, @NotNull Caret caret, final int count) {
|
||||
public boolean changeNumber(final @NotNull Editor editor, @NotNull Caret caret, final int count) {
|
||||
final BoundListOption nf = OptionsManager.INSTANCE.getNrformats();
|
||||
final boolean alpha = nf.contains("alpha");
|
||||
final boolean hex = nf.contains("hex");
|
||||
@@ -1911,8 +1912,7 @@ public class ChangeGroup {
|
||||
private boolean lastLower = true;
|
||||
private Document document;
|
||||
|
||||
@Nullable
|
||||
public String changeNumberInRange(@NotNull final Editor editor,
|
||||
public @Nullable String changeNumberInRange(final @NotNull Editor editor,
|
||||
@NotNull TextRange range,
|
||||
final int count,
|
||||
boolean alpha,
|
||||
@@ -2035,8 +2035,7 @@ public class ChangeGroup {
|
||||
oldOffset = e.getOffset() + newFragmentLength;
|
||||
}
|
||||
|
||||
@NotNull
|
||||
private List<EditorActionHandlerBase> getAdjustCaretActions(@NotNull DocumentEvent e) {
|
||||
private @NotNull List<EditorActionHandlerBase> getAdjustCaretActions(@NotNull DocumentEvent e) {
|
||||
final int delta = e.getOffset() - oldOffset;
|
||||
if (oldOffset >= 0 && delta != 0) {
|
||||
final List<EditorActionHandlerBase> positionCaretActions = new ArrayList<>();
|
||||
|
@@ -1749,8 +1749,8 @@ public class DigraphGroup {
|
||||
'f', 't', '\ufb05', // LATIN SMALL LIGATURE FT
|
||||
's', 't', '\ufb06', // LATIN SMALL LIGATURE ST
|
||||
};
|
||||
@NotNull private final HashMap<String, Character> digraphs = new HashMap<>(defaultDigraphs.length);
|
||||
@NotNull private final TreeMap<Character, String> keys = new TreeMap<>();
|
||||
private final @NotNull HashMap<String, Character> digraphs = new HashMap<>(defaultDigraphs.length);
|
||||
private final @NotNull TreeMap<Character, String> keys = new TreeMap<>();
|
||||
|
||||
private static final Logger logger = Logger.getInstance(DigraphGroup.class.getName());
|
||||
}
|
||||
|
@@ -21,6 +21,9 @@ package com.maddyhome.idea.vim.group;
|
||||
import com.intellij.find.EditorSearchSession;
|
||||
import com.intellij.openapi.actionSystem.AnAction;
|
||||
import com.intellij.openapi.application.ApplicationManager;
|
||||
import com.intellij.openapi.components.PersistentStateComponent;
|
||||
import com.intellij.openapi.components.State;
|
||||
import com.intellij.openapi.components.Storage;
|
||||
import com.intellij.openapi.editor.*;
|
||||
import com.intellij.openapi.editor.colors.ColorKey;
|
||||
import com.intellij.openapi.editor.colors.EditorColors;
|
||||
@@ -47,9 +50,11 @@ import java.util.List;
|
||||
/**
|
||||
* @author vlan
|
||||
*/
|
||||
public class EditorGroup {
|
||||
@State(name = "VimEditorSettings", storages = {@Storage(value = "$APP_CONFIG$/vim_settings.xml")})
|
||||
public class EditorGroup implements PersistentStateComponent<Element> {
|
||||
private static final boolean ANIMATED_SCROLLING_VIM_VALUE = false;
|
||||
private static final boolean REFRAIN_FROM_SCROLLING_VIM_VALUE = true;
|
||||
public static final String EDITOR_STORE_ELEMENT = "editor";
|
||||
|
||||
private boolean isBlockCursor = false;
|
||||
private boolean isAnimatedScrolling = false;
|
||||
@@ -76,7 +81,7 @@ public class EditorGroup {
|
||||
}
|
||||
}
|
||||
|
||||
private void initLineNumbers(@NotNull final Editor editor) {
|
||||
private void initLineNumbers(final @NotNull Editor editor) {
|
||||
if (!supportsVimLineNumbers(editor) || UserDataManager.getVimEditorGroup(editor)) {
|
||||
return;
|
||||
}
|
||||
@@ -107,13 +112,13 @@ public class EditorGroup {
|
||||
}
|
||||
}
|
||||
|
||||
private static boolean supportsVimLineNumbers(@NotNull final Editor editor) {
|
||||
private static boolean supportsVimLineNumbers(final @NotNull Editor editor) {
|
||||
// We only support line numbers in editors that are file based, and that aren't for diffs, which control their
|
||||
// own line numbers, often using EditorGutterComponentEx#setLineNumberConvertor
|
||||
return EditorHelper.isFileEditor(editor) && !EditorHelper.isDiffEditor(editor);
|
||||
}
|
||||
|
||||
private static void updateLineNumbers(@NotNull final Editor editor, final boolean requiresRepaint) {
|
||||
private static void updateLineNumbers(final @NotNull Editor editor, final boolean requiresRepaint) {
|
||||
final boolean relativeNumber = OptionsManager.INSTANCE.getRelativenumber().isSet();
|
||||
final boolean number = OptionsManager.INSTANCE.getNumber().isSet();
|
||||
|
||||
@@ -142,7 +147,7 @@ public class EditorGroup {
|
||||
}
|
||||
}
|
||||
|
||||
private static boolean shouldShowBuiltinLineNumbers(@NotNull final Editor editor, boolean number, boolean relativeNumber) {
|
||||
private static boolean shouldShowBuiltinLineNumbers(final @NotNull Editor editor, boolean number, boolean relativeNumber) {
|
||||
final boolean initialState = UserDataManager.getVimLineNumbersInitialState(editor);
|
||||
|
||||
// Builtin relative line numbers requires EditorGutterComponentEx#setLineNumberConvertor. If we don't have that,
|
||||
@@ -155,15 +160,15 @@ public class EditorGroup {
|
||||
return (initialState || number) && !relativeNumber;
|
||||
}
|
||||
|
||||
private static void setBuiltinLineNumbers(@NotNull final Editor editor, boolean show) {
|
||||
private static void setBuiltinLineNumbers(final @NotNull Editor editor, boolean show) {
|
||||
editor.getSettings().setLineNumbersShown(show);
|
||||
}
|
||||
|
||||
private static boolean hasRelativeLineNumbersInstalled(@NotNull final Editor editor) {
|
||||
private static boolean hasRelativeLineNumbersInstalled(final @NotNull Editor editor) {
|
||||
return UserDataManager.getVimHasRelativeLineNumbersInstalled(editor);
|
||||
}
|
||||
|
||||
private static void installRelativeLineNumbers(@NotNull final Editor editor) {
|
||||
private static void installRelativeLineNumbers(final @NotNull Editor editor) {
|
||||
if (!hasRelativeLineNumbersInstalled(editor)) {
|
||||
final EditorGutter gutter = editor.getGutter();
|
||||
if (gutter instanceof EditorGutterComponentEx) {
|
||||
@@ -176,7 +181,7 @@ public class EditorGroup {
|
||||
}
|
||||
}
|
||||
|
||||
private static void removeRelativeLineNumbers(@NotNull final Editor editor) {
|
||||
private static void removeRelativeLineNumbers(final @NotNull Editor editor) {
|
||||
if (hasRelativeLineNumbersInstalled(editor)) {
|
||||
final EditorGutter gutter = editor.getGutter();
|
||||
if (gutter instanceof EditorGutterComponentEx) {
|
||||
@@ -190,7 +195,7 @@ public class EditorGroup {
|
||||
}
|
||||
}
|
||||
|
||||
private static void repaintRelativeLineNumbers(@NotNull final Editor editor) {
|
||||
private static void repaintRelativeLineNumbers(final @NotNull Editor editor) {
|
||||
final EditorGutter gutter = editor.getGutter();
|
||||
final EditorGutterComponentEx gutterComponent = gutter instanceof EditorGutterComponentEx ? (EditorGutterComponentEx) gutter : null;
|
||||
if (gutterComponent != null) {
|
||||
@@ -210,7 +215,7 @@ public class EditorGroup {
|
||||
}
|
||||
|
||||
public void readData(@NotNull Element element) {
|
||||
final Element editor = element.getChild("editor");
|
||||
final Element editor = element.getChild(EDITOR_STORE_ELEMENT);
|
||||
if (editor != null) {
|
||||
final Element keyRepeat = editor.getChild("key-repeat");
|
||||
if (keyRepeat != null) {
|
||||
@@ -222,8 +227,7 @@ public class EditorGroup {
|
||||
}
|
||||
}
|
||||
|
||||
@Nullable
|
||||
public Boolean isKeyRepeat() {
|
||||
public @Nullable Boolean isKeyRepeat() {
|
||||
return isKeyRepeat;
|
||||
}
|
||||
|
||||
@@ -275,6 +279,19 @@ public class EditorGroup {
|
||||
VimPlugin.getNotifications(project).notifyAboutIdeaJoin();
|
||||
}
|
||||
|
||||
@Nullable
|
||||
@Override
|
||||
public Element getState() {
|
||||
Element element = new Element("editor");
|
||||
saveData(element);
|
||||
return element;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void loadState(@NotNull Element state) {
|
||||
readData(state);
|
||||
}
|
||||
|
||||
public static class NumberChangeListener implements OptionChangeListener<Boolean> {
|
||||
public static NumberChangeListener INSTANCE = new NumberChangeListener();
|
||||
|
||||
@@ -293,11 +310,10 @@ public class EditorGroup {
|
||||
}
|
||||
|
||||
private static class RelativeLineNumberConverter implements TIntFunction {
|
||||
@NotNull
|
||||
private final Editor editor;
|
||||
private final @NotNull Editor editor;
|
||||
|
||||
@Contract(pure = true)
|
||||
RelativeLineNumberConverter(@NotNull final Editor editor) {
|
||||
RelativeLineNumberConverter(final @NotNull Editor editor) {
|
||||
this.editor = editor;
|
||||
}
|
||||
|
||||
@@ -322,17 +338,15 @@ public class EditorGroup {
|
||||
}
|
||||
|
||||
private static class RelativeLineNumberGutterProvider implements TextAnnotationGutterProvider {
|
||||
@NotNull
|
||||
private final Editor editor;
|
||||
private final @NotNull Editor editor;
|
||||
|
||||
@Contract(pure = true)
|
||||
RelativeLineNumberGutterProvider(@NotNull final Editor editor) {
|
||||
RelativeLineNumberGutterProvider(final @NotNull Editor editor) {
|
||||
this.editor = editor;
|
||||
}
|
||||
|
||||
@Nullable
|
||||
@Override
|
||||
public String getLineText(int line, @NotNull Editor editor) {
|
||||
public @Nullable String getLineText(int line, @NotNull Editor editor) {
|
||||
final boolean number = OptionsManager.INSTANCE.getNumber().isSet();
|
||||
if (number && isCaretLine(line, editor)) {
|
||||
return lineNumberToString(line + 1, editor, true);
|
||||
@@ -360,9 +374,8 @@ public class EditorGroup {
|
||||
: StringsKt.padStart(Integer.toString(lineNumber), digitsCount, ' ');
|
||||
}
|
||||
|
||||
@Nullable
|
||||
@Override
|
||||
public String getToolTip(int line, Editor editor) {
|
||||
public @Nullable String getToolTip(int line, Editor editor) {
|
||||
return null;
|
||||
}
|
||||
|
||||
@@ -371,15 +384,13 @@ public class EditorGroup {
|
||||
return isCaretLine(line, editor) ? EditorFontType.BOLD: null;
|
||||
}
|
||||
|
||||
@Nullable
|
||||
@Override
|
||||
public ColorKey getColor(int line, Editor editor) {
|
||||
public @Nullable ColorKey getColor(int line, Editor editor) {
|
||||
return isCaretLine(line, editor) ? EditorColors.LINE_NUMBER_ON_CARET_ROW_COLOR : EditorColors.LINE_NUMBERS_COLOR;
|
||||
}
|
||||
|
||||
@Nullable
|
||||
@Override
|
||||
public Color getBgColor(int line, Editor editor) {
|
||||
public @Nullable Color getBgColor(int line, Editor editor) {
|
||||
return null;
|
||||
}
|
||||
|
||||
|
@@ -118,8 +118,7 @@ public class FileGroup {
|
||||
return found;
|
||||
}
|
||||
|
||||
@Nullable
|
||||
private VirtualFile findFile(@NotNull VirtualFile root, @NotNull String filename) {
|
||||
private @Nullable VirtualFile findFile(@NotNull VirtualFile root, @NotNull String filename) {
|
||||
VirtualFile res = root.findFileByRelativePath(filename);
|
||||
if (res != null) {
|
||||
return res;
|
||||
@@ -223,6 +222,20 @@ public class FileGroup {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the previous tab.
|
||||
*/
|
||||
public @Nullable VirtualFile getPreviousTab(@NotNull DataContext context) {
|
||||
Project project = PlatformDataKeys.PROJECT.getData(context);
|
||||
if (project == null) return null;
|
||||
FileEditorManager fem = FileEditorManager.getInstance(project); // API change - don't merge
|
||||
VirtualFile vf = lastSelections.get(fem);
|
||||
if (vf != null && vf.isValid()) {
|
||||
return vf;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
@Nullable
|
||||
Editor selectEditor(Project project, @NotNull VirtualFile file) {
|
||||
FileEditorManager fMgr = FileEditorManager.getInstance(project);
|
||||
@@ -382,10 +395,10 @@ public class FileGroup {
|
||||
VimPlugin.showMessage(msg.toString());
|
||||
}
|
||||
|
||||
@NotNull private static final String disposableKey = "VimFileGroupDisposable";
|
||||
private static final @NotNull String disposableKey = "VimFileGroupDisposable";
|
||||
|
||||
@NotNull private static final HashMap<FileEditorManager, VirtualFile> lastSelections = new HashMap<>();
|
||||
@NotNull private static final Logger logger = Logger.getInstance(FileGroup.class.getName());
|
||||
private static final @NotNull HashMap<FileEditorManager, VirtualFile> lastSelections = new HashMap<>();
|
||||
private static final @NotNull Logger logger = Logger.getInstance(FileGroup.class.getName());
|
||||
|
||||
/**
|
||||
* This method listens for editor tab changes so any insert/replace modes that need to be reset can be.
|
||||
|
@@ -18,19 +18,27 @@
|
||||
|
||||
package com.maddyhome.idea.vim.group;
|
||||
|
||||
import com.intellij.openapi.components.PersistentStateComponent;
|
||||
import com.intellij.openapi.components.RoamingType;
|
||||
import com.intellij.openapi.components.State;
|
||||
import com.intellij.openapi.components.Storage;
|
||||
import com.intellij.openapi.diagnostic.Logger;
|
||||
import com.maddyhome.idea.vim.helper.StringHelper;
|
||||
import com.maddyhome.idea.vim.option.NumberOption;
|
||||
import com.maddyhome.idea.vim.option.OptionsManager;
|
||||
import org.jdom.Element;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
public class HistoryGroup {
|
||||
@State(name = "VimHistorySettings", storages = {
|
||||
@Storage(value = "$APP_CONFIG$/vim_settings.xml", roamingType = RoamingType.DISABLED)
|
||||
})
|
||||
public class HistoryGroup implements PersistentStateComponent<Element> {
|
||||
public static final String SEARCH = "search";
|
||||
public static final String COMMAND = "cmd";
|
||||
public static final String EXPRESSION = "expr";
|
||||
@@ -45,8 +53,7 @@ public class HistoryGroup {
|
||||
block.addEntry(text);
|
||||
}
|
||||
|
||||
@NotNull
|
||||
public List<HistoryEntry> getEntries(String key, int first, int last) {
|
||||
public @NotNull List<HistoryEntry> getEntries(String key, int first, int last) {
|
||||
HistoryBlock block = blocks(key);
|
||||
|
||||
List<HistoryEntry> entries = block.getEntries();
|
||||
@@ -167,6 +174,19 @@ public class HistoryGroup {
|
||||
return opt.value();
|
||||
}
|
||||
|
||||
@Nullable
|
||||
@Override
|
||||
public Element getState() {
|
||||
Element element = new Element("history");
|
||||
saveData(element);
|
||||
return element;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void loadState(@NotNull Element state) {
|
||||
readData(state);
|
||||
}
|
||||
|
||||
private static class HistoryBlock {
|
||||
public void addEntry(@NotNull String text) {
|
||||
for (int i = 0; i < entries.size(); i++) {
|
||||
@@ -184,12 +204,11 @@ public class HistoryGroup {
|
||||
}
|
||||
}
|
||||
|
||||
@NotNull
|
||||
public List<HistoryEntry> getEntries() {
|
||||
public @NotNull List<HistoryEntry> getEntries() {
|
||||
return entries;
|
||||
}
|
||||
|
||||
@NotNull private final List<HistoryEntry> entries = new ArrayList<>();
|
||||
private final @NotNull List<HistoryEntry> entries = new ArrayList<>();
|
||||
private int counter;
|
||||
}
|
||||
|
||||
@@ -203,16 +222,15 @@ public class HistoryGroup {
|
||||
return number;
|
||||
}
|
||||
|
||||
@NotNull
|
||||
public String getEntry() {
|
||||
public @NotNull String getEntry() {
|
||||
return entry;
|
||||
}
|
||||
|
||||
private final int number;
|
||||
@NotNull private final String entry;
|
||||
private final @NotNull String entry;
|
||||
}
|
||||
|
||||
@NotNull private final Map<String, HistoryBlock> histories = new HashMap<>();
|
||||
private final @NotNull Map<String, HistoryBlock> histories = new HashMap<>();
|
||||
|
||||
private static final Logger logger = Logger.getInstance(HistoryGroup.class.getName());
|
||||
}
|
||||
|
@@ -24,6 +24,9 @@ import com.intellij.openapi.actionSystem.*;
|
||||
import com.intellij.openapi.actionSystem.ex.ActionManagerEx;
|
||||
import com.intellij.openapi.actionSystem.ex.ActionUtil;
|
||||
import com.intellij.openapi.application.ApplicationManager;
|
||||
import com.intellij.openapi.components.PersistentStateComponent;
|
||||
import com.intellij.openapi.components.State;
|
||||
import com.intellij.openapi.components.Storage;
|
||||
import com.intellij.openapi.diagnostic.Logger;
|
||||
import com.intellij.openapi.editor.Editor;
|
||||
import com.intellij.openapi.editor.EditorFactory;
|
||||
@@ -41,6 +44,7 @@ import com.maddyhome.idea.vim.handler.ActionBeanClass;
|
||||
import com.maddyhome.idea.vim.handler.EditorActionHandlerBase;
|
||||
import com.maddyhome.idea.vim.helper.StringHelper;
|
||||
import com.maddyhome.idea.vim.key.*;
|
||||
import kotlin.Pair;
|
||||
import kotlin.text.StringsKt;
|
||||
import org.jdom.Element;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
@@ -54,27 +58,30 @@ import java.util.*;
|
||||
|
||||
import static com.maddyhome.idea.vim.helper.StringHelper.toKeyNotation;
|
||||
import static java.util.stream.Collectors.joining;
|
||||
import static java.util.stream.Collectors.toList;
|
||||
|
||||
/**
|
||||
* @author vlan
|
||||
*/
|
||||
public class KeyGroup {
|
||||
private static final String SHORTCUT_CONFLICTS_ELEMENT = "shortcut-conflicts";
|
||||
@State(name = "VimKeySettings", storages = {@Storage(value = "$APP_CONFIG$/vim_settings.xml")})
|
||||
public class KeyGroup implements PersistentStateComponent<Element> {
|
||||
public static final String SHORTCUT_CONFLICTS_ELEMENT = "shortcut-conflicts";
|
||||
private static final String SHORTCUT_CONFLICT_ELEMENT = "shortcut-conflict";
|
||||
private static final String OWNER_ATTRIBUTE = "owner";
|
||||
private static final String TEXT_ELEMENT = "text";
|
||||
|
||||
private static final Logger logger = Logger.getInstance(KeyGroup.class);
|
||||
|
||||
@NotNull private final Map<KeyStroke, ShortcutOwner> shortcutConflicts = new LinkedHashMap<>();
|
||||
@NotNull private final Set<KeyStroke> requiredShortcutKeys = new HashSet<>(300);
|
||||
@NotNull private final Map<MappingMode, CommandPartNode> keyRoots = new EnumMap<>(MappingMode.class);
|
||||
@NotNull private final Map<MappingMode, KeyMapping> keyMappings = new EnumMap<>(MappingMode.class);
|
||||
@Nullable private OperatorFunction operatorFunction = null;
|
||||
private final @NotNull Map<KeyStroke, ShortcutOwner> shortcutConflicts = new LinkedHashMap<>();
|
||||
private final @NotNull Set<RequiredShortcut> requiredShortcutKeys = new HashSet<>(300);
|
||||
private final @NotNull Map<MappingMode, CommandPartNode> keyRoots = new EnumMap<>(MappingMode.class);
|
||||
private final @NotNull Map<MappingMode, KeyMapping> keyMappings = new EnumMap<>(MappingMode.class);
|
||||
private @Nullable OperatorFunction operatorFunction = null;
|
||||
|
||||
void registerRequiredShortcutKeys(@NotNull Editor editor) {
|
||||
EventFacade.getInstance().registerCustomShortcutSet(VimShortcutKeyAction.getInstance(),
|
||||
toShortcutSet(requiredShortcutKeys), editor.getComponent());
|
||||
EventFacade.getInstance()
|
||||
.registerCustomShortcutSet(VimShortcutKeyAction.getInstance(), toShortcutSet(requiredShortcutKeys),
|
||||
editor.getComponent());
|
||||
}
|
||||
|
||||
public void registerShortcutsForLookup(@NotNull LookupImpl lookup) {
|
||||
@@ -88,44 +95,75 @@ public class KeyGroup {
|
||||
}
|
||||
|
||||
public boolean showKeyMappings(@NotNull Set<MappingMode> modes, @NotNull Editor editor) {
|
||||
final List<MappingInfo> rows = getKeyMappingRows(modes);
|
||||
List<Pair<EnumSet<MappingMode>, MappingInfo>> rows = getKeyMappingRows(modes);
|
||||
final StringBuilder builder = new StringBuilder();
|
||||
for (MappingInfo row : rows) {
|
||||
builder.append(StringsKt.padEnd(getModesStringCode(row.getMappingModes()), 2, ' '));
|
||||
for (Pair<EnumSet<MappingMode>, MappingInfo> row : rows) {
|
||||
MappingInfo mappingInfo = row.getSecond();
|
||||
builder.append(StringsKt.padEnd(getModesStringCode(row.getFirst()), 2, ' '));
|
||||
builder.append(" ");
|
||||
builder.append(StringsKt.padEnd(toKeyNotation(row.getFromKeys()), 11, ' '));
|
||||
builder.append(StringsKt.padEnd(toKeyNotation(mappingInfo.getFromKeys()), 11, ' '));
|
||||
builder.append(" ");
|
||||
builder.append(row.isRecursive() ? " " : "*");
|
||||
builder.append(mappingInfo.isRecursive() ? " " : "*");
|
||||
builder.append(" ");
|
||||
final List<KeyStroke> toKeys = row.getToKeys();
|
||||
final VimExtensionHandler extensionHandler = row.getExtensionHandler();
|
||||
if (toKeys != null) {
|
||||
if (mappingInfo instanceof ToKeysMappingInfo) {
|
||||
List<KeyStroke> toKeys = ((ToKeysMappingInfo)mappingInfo).getToKeys();
|
||||
builder.append(toKeyNotation(toKeys));
|
||||
}
|
||||
else if (extensionHandler != null) {
|
||||
else if (mappingInfo instanceof ToHandlerMappingInfo) {
|
||||
final VimExtensionHandler extensionHandler = ((ToHandlerMappingInfo)mappingInfo).getExtensionHandler();
|
||||
builder.append("call ");
|
||||
builder.append(extensionHandler.getClass().getCanonicalName());
|
||||
}
|
||||
else {
|
||||
builder.append("<Unknown>");
|
||||
}
|
||||
builder.append("\n");
|
||||
}
|
||||
ExOutputModel.getInstance(editor).output(builder.toString());
|
||||
return true;
|
||||
}
|
||||
|
||||
public void putKeyMapping(@NotNull Set<MappingMode> modes, @NotNull List<KeyStroke> fromKeys,
|
||||
@Nullable List<KeyStroke> toKeys, @Nullable VimExtensionHandler extensionHandler,
|
||||
boolean recursive) {
|
||||
for (MappingMode mode : modes) {
|
||||
final KeyMapping mapping = getKeyMapping(mode);
|
||||
mapping.put(EnumSet.of(mode), fromKeys, toKeys, extensionHandler, recursive);
|
||||
public void removeKeyMapping(@NotNull MappingOwner owner) {
|
||||
Arrays.stream(MappingMode.values()).map(this::getKeyMapping).forEach(o -> o.delete(owner));
|
||||
unregisterKeyMapping(owner);
|
||||
}
|
||||
|
||||
public void putKeyMapping(@NotNull Set<MappingMode> modes,
|
||||
@NotNull List<KeyStroke> fromKeys,
|
||||
@NotNull MappingOwner owner,
|
||||
@NotNull VimExtensionHandler extensionHandler,
|
||||
boolean recursive) {
|
||||
modes.stream().map(this::getKeyMapping).forEach(o -> o.put(fromKeys, owner, extensionHandler, recursive));
|
||||
registerKeyMapping(fromKeys, owner);
|
||||
}
|
||||
|
||||
public void putKeyMapping(@NotNull Set<MappingMode> modes,
|
||||
@NotNull List<KeyStroke> fromKeys,
|
||||
@NotNull MappingOwner owner,
|
||||
@NotNull List<KeyStroke> toKeys,
|
||||
boolean recursive) {
|
||||
modes.stream().map(this::getKeyMapping).forEach(o -> o.put(fromKeys, toKeys, owner, recursive));
|
||||
registerKeyMapping(fromKeys, owner);
|
||||
}
|
||||
|
||||
public List<Pair<List<KeyStroke>, MappingInfo>> getKeyMappingByOwner(@NotNull MappingOwner owner) {
|
||||
return Arrays.stream(MappingMode.values()).map(this::getKeyMapping).flatMap(o -> o.getByOwner(owner).stream())
|
||||
.collect(toList());
|
||||
}
|
||||
|
||||
private void unregisterKeyMapping(MappingOwner owner) {
|
||||
final int oldSize = requiredShortcutKeys.size();
|
||||
requiredShortcutKeys.removeIf(requiredShortcut -> requiredShortcut.getOwner().equals(owner));
|
||||
if (requiredShortcutKeys.size() != oldSize) {
|
||||
for (Editor editor : EditorFactory.getInstance().getAllEditors()) {
|
||||
unregisterShortcutKeys(editor);
|
||||
registerRequiredShortcutKeys(editor);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void registerKeyMapping(@NotNull List<KeyStroke> fromKeys, MappingOwner owner) {
|
||||
final int oldSize = requiredShortcutKeys.size();
|
||||
for (KeyStroke key : fromKeys) {
|
||||
if (key.getKeyChar() == KeyEvent.CHAR_UNDEFINED) {
|
||||
requiredShortcutKeys.add(key);
|
||||
requiredShortcutKeys.add(new RequiredShortcut(key, owner));
|
||||
}
|
||||
}
|
||||
if (requiredShortcutKeys.size() != oldSize) {
|
||||
@@ -136,8 +174,7 @@ public class KeyGroup {
|
||||
}
|
||||
}
|
||||
|
||||
@Nullable
|
||||
public OperatorFunction getOperatorFunction() {
|
||||
public @Nullable OperatorFunction getOperatorFunction() {
|
||||
return operatorFunction;
|
||||
}
|
||||
|
||||
@@ -187,8 +224,7 @@ public class KeyGroup {
|
||||
}
|
||||
}
|
||||
|
||||
@NotNull
|
||||
public List<AnAction> getKeymapConflicts(@NotNull KeyStroke keyStroke) {
|
||||
public @NotNull List<AnAction> getKeymapConflicts(@NotNull KeyStroke keyStroke) {
|
||||
final KeymapManagerEx keymapManager = KeymapManagerEx.getInstanceEx();
|
||||
final Keymap keymap = keymapManager.getActiveKeymap();
|
||||
final KeyboardShortcut shortcut = new KeyboardShortcut(keyStroke, null);
|
||||
@@ -203,12 +239,12 @@ public class KeyGroup {
|
||||
return actions;
|
||||
}
|
||||
|
||||
@NotNull
|
||||
public Map<KeyStroke, ShortcutOwner> getShortcutConflicts() {
|
||||
final Set<KeyStroke> requiredShortcutKeys = this.requiredShortcutKeys;
|
||||
public @NotNull Map<KeyStroke, ShortcutOwner> getShortcutConflicts() {
|
||||
final Set<RequiredShortcut> requiredShortcutKeys = this.requiredShortcutKeys;
|
||||
final Map<KeyStroke, ShortcutOwner> savedConflicts = getSavedShortcutConflicts();
|
||||
final Map<KeyStroke, ShortcutOwner> results = new HashMap<>();
|
||||
for (KeyStroke keyStroke : requiredShortcutKeys) {
|
||||
for (RequiredShortcut requiredShortcut : requiredShortcutKeys) {
|
||||
KeyStroke keyStroke = requiredShortcut.getKeyStroke();
|
||||
if (!VimShortcutKeyAction.VIM_ONLY_EDITOR_KEYS.contains(keyStroke)) {
|
||||
final List<AnAction> conflicts = getKeymapConflicts(keyStroke);
|
||||
if (!conflicts.isEmpty()) {
|
||||
@@ -220,13 +256,11 @@ public class KeyGroup {
|
||||
return results;
|
||||
}
|
||||
|
||||
@NotNull
|
||||
public Map<KeyStroke, ShortcutOwner> getSavedShortcutConflicts() {
|
||||
public @NotNull Map<KeyStroke, ShortcutOwner> getSavedShortcutConflicts() {
|
||||
return shortcutConflicts;
|
||||
}
|
||||
|
||||
@NotNull
|
||||
public KeyMapping getKeyMapping(@NotNull MappingMode mode) {
|
||||
public @NotNull KeyMapping getKeyMapping(@NotNull MappingMode mode) {
|
||||
KeyMapping mapping = keyMappings.get(mode);
|
||||
if (mapping == null) {
|
||||
mapping = new KeyMapping();
|
||||
@@ -245,8 +279,7 @@ public class KeyGroup {
|
||||
* @param mappingMode The mapping mode
|
||||
* @return The key mapping tree root
|
||||
*/
|
||||
@NotNull
|
||||
public CommandPartNode getKeyRoot(@NotNull MappingMode mappingMode) {
|
||||
public @NotNull CommandPartNode getKeyRoot(@NotNull MappingMode mappingMode) {
|
||||
return keyRoots.computeIfAbsent(mappingMode, (key) -> new RootNode());
|
||||
}
|
||||
|
||||
@@ -257,10 +290,11 @@ public class KeyGroup {
|
||||
* Digraphs are handled directly by KeyHandler#handleKey instead of via an action, but we need to still make sure the
|
||||
* shortcuts are registered, or the key handler won't see them
|
||||
* </p>
|
||||
*
|
||||
* @param keyStroke The shortcut to register
|
||||
*/
|
||||
public void registerShortcutWithoutAction(KeyStroke keyStroke) {
|
||||
registerRequiredShortcut(Collections.singletonList(keyStroke));
|
||||
public void registerShortcutWithoutAction(KeyStroke keyStroke, MappingOwner owner) {
|
||||
registerRequiredShortcut(Collections.singletonList(keyStroke), owner);
|
||||
}
|
||||
|
||||
public void unregisterCommandActions() {
|
||||
@@ -275,7 +309,8 @@ public class KeyGroup {
|
||||
if (!VimPlugin.getPluginId().equals(actionHolder.getPluginId())) {
|
||||
logger.error("IdeaVim doesn't accept contributions to `vimActions` extension points. " +
|
||||
"Please create a plugin using `VimExtension`. " +
|
||||
"Plugin to blame: " + actionHolder.getPluginId());
|
||||
"Plugin to blame: " +
|
||||
actionHolder.getPluginId());
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -284,7 +319,8 @@ public class KeyGroup {
|
||||
final EditorActionHandlerBase action = actionHolder.getAction();
|
||||
if (action instanceof ComplicatedKeysAction) {
|
||||
actionKeys = ((ComplicatedKeysAction)action).getKeyStrokesSet();
|
||||
} else {
|
||||
}
|
||||
else {
|
||||
throw new RuntimeException("Cannot register action: " + action.getClass().getName());
|
||||
}
|
||||
}
|
||||
@@ -305,7 +341,7 @@ public class KeyGroup {
|
||||
}
|
||||
|
||||
for (List<KeyStroke> keyStrokes : actionKeys) {
|
||||
registerRequiredShortcut(keyStrokes);
|
||||
registerRequiredShortcut(keyStrokes, MappingOwner.IdeaVim.INSTANCE);
|
||||
|
||||
for (MappingMode mappingMode : actionModes) {
|
||||
Node node = getKeyRoot(mappingMode);
|
||||
@@ -322,15 +358,17 @@ public class KeyGroup {
|
||||
}
|
||||
}
|
||||
|
||||
private void registerRequiredShortcut(@NotNull List<KeyStroke> keys) {
|
||||
private void registerRequiredShortcut(@NotNull List<KeyStroke> keys, MappingOwner owner) {
|
||||
for (KeyStroke key : keys) {
|
||||
if (key.getKeyChar() == KeyEvent.CHAR_UNDEFINED) {
|
||||
requiredShortcutKeys.add(key);
|
||||
requiredShortcutKeys.add(new RequiredShortcut(key, owner));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void checkCommand(@NotNull Set<MappingMode> mappingModes, EditorActionHandlerBase action, List<KeyStroke> keys) {
|
||||
private void checkCommand(@NotNull Set<MappingMode> mappingModes,
|
||||
EditorActionHandlerBase action,
|
||||
List<KeyStroke> keys) {
|
||||
for (MappingMode mappingMode : mappingModes) {
|
||||
checkIdentity(mappingMode, action.getId(), keys);
|
||||
}
|
||||
@@ -340,8 +378,8 @@ public class KeyGroup {
|
||||
private void checkIdentity(MappingMode mappingMode, String actName, List<KeyStroke> keys) {
|
||||
Set<List<KeyStroke>> keySets = identityChecker.computeIfAbsent(mappingMode, k -> new HashSet<>());
|
||||
if (keySets.contains(keys)) {
|
||||
throw new RuntimeException("This keymap already exists: " + mappingMode + " keys: " +
|
||||
keys + " action:" + actName);
|
||||
throw new RuntimeException(
|
||||
"This keymap already exists: " + mappingMode + " keys: " + keys + " action:" + actName);
|
||||
}
|
||||
keySets.add(keys);
|
||||
}
|
||||
@@ -356,7 +394,9 @@ public class KeyGroup {
|
||||
if (!prefix.get(i).equals(keys.get(i))) break;
|
||||
}
|
||||
|
||||
List<String> actionExceptions = Arrays.asList("VimInsertDeletePreviousWordAction", "VimInsertAfterCursorAction", "VimInsertBeforeCursorAction", "VimFilterVisualLinesAction", "VimAutoIndentMotionAction");
|
||||
List<String> actionExceptions = Arrays
|
||||
.asList("VimInsertDeletePreviousWordAction", "VimInsertAfterCursorAction", "VimInsertBeforeCursorAction",
|
||||
"VimFilterVisualLinesAction", "VimAutoIndentMotionAction");
|
||||
if (i == shortOne && !actionExceptions.contains(action.getId()) && !actionExceptions.contains(entry.getValue())) {
|
||||
throw new RuntimeException("Prefix found! " +
|
||||
keys +
|
||||
@@ -371,11 +411,10 @@ public class KeyGroup {
|
||||
prefixes.put(keys, action.getId());
|
||||
}
|
||||
|
||||
@Nullable private Map<MappingMode, Set<List<KeyStroke>>> identityChecker;
|
||||
@Nullable private Map<List<KeyStroke>, String> prefixes;
|
||||
private @Nullable Map<MappingMode, Set<List<KeyStroke>>> identityChecker;
|
||||
private @Nullable Map<List<KeyStroke>, String> prefixes;
|
||||
|
||||
@NotNull
|
||||
private Node addMNode(@NotNull CommandPartNode base,
|
||||
private @NotNull Node addMNode(@NotNull CommandPartNode base,
|
||||
ActionBeanClass actionHolder,
|
||||
@NotNull KeyStroke key,
|
||||
boolean isLastInSequence) {
|
||||
@@ -385,33 +424,32 @@ public class KeyGroup {
|
||||
Node newNode;
|
||||
if (isLastInSequence) {
|
||||
newNode = new CommandNode(actionHolder);
|
||||
} else {
|
||||
}
|
||||
else {
|
||||
newNode = new CommandPartNode();
|
||||
}
|
||||
base.put(key, newNode);
|
||||
return newNode;
|
||||
}
|
||||
|
||||
@NotNull
|
||||
private static ShortcutSet toShortcutSet(@NotNull Collection<KeyStroke> keyStrokes) {
|
||||
final List<com.intellij.openapi.actionSystem.Shortcut> shortcuts = new ArrayList<>();
|
||||
for (KeyStroke key : keyStrokes) {
|
||||
shortcuts.add(new KeyboardShortcut(key, null));
|
||||
private static @NotNull ShortcutSet toShortcutSet(@NotNull Collection<RequiredShortcut> requiredShortcuts) {
|
||||
final List<Shortcut> shortcuts = new ArrayList<>();
|
||||
for (RequiredShortcut key : requiredShortcuts) {
|
||||
shortcuts.add(new KeyboardShortcut(key.getKeyStroke(), null));
|
||||
}
|
||||
return new CustomShortcutSet(shortcuts.toArray(new com.intellij.openapi.actionSystem.Shortcut[0]));
|
||||
return new CustomShortcutSet(shortcuts.toArray(new Shortcut[0]));
|
||||
}
|
||||
|
||||
@NotNull
|
||||
private static List<MappingInfo> getKeyMappingRows(@NotNull Set<MappingMode> modes) {
|
||||
final Map<ImmutableList<KeyStroke>, Set<MappingMode>> actualModes = new HashMap<>();
|
||||
private static @NotNull List<Pair<EnumSet<MappingMode>, MappingInfo>> getKeyMappingRows(@NotNull Set<MappingMode> modes) {
|
||||
final Map<ImmutableList<KeyStroke>, EnumSet<MappingMode>> actualModes = new HashMap<>();
|
||||
for (MappingMode mode : modes) {
|
||||
final KeyMapping mapping = VimPlugin.getKey().getKeyMapping(mode);
|
||||
for (List<KeyStroke> fromKeys : mapping) {
|
||||
final ImmutableList<KeyStroke> key = ImmutableList.copyOf(fromKeys);
|
||||
final Set<MappingMode> value = actualModes.get(key);
|
||||
final Set<MappingMode> newValue;
|
||||
final EnumSet<MappingMode> value = actualModes.get(key);
|
||||
final EnumSet<MappingMode> newValue;
|
||||
if (value != null) {
|
||||
newValue = new HashSet<>(value);
|
||||
newValue = value.clone();
|
||||
newValue.add(mode);
|
||||
}
|
||||
else {
|
||||
@@ -420,26 +458,24 @@ public class KeyGroup {
|
||||
actualModes.put(key, newValue);
|
||||
}
|
||||
}
|
||||
final List<MappingInfo> rows = new ArrayList<>();
|
||||
for (Map.Entry<ImmutableList<KeyStroke>, Set<MappingMode>> entry : actualModes.entrySet()) {
|
||||
final List<Pair<EnumSet<MappingMode>, MappingInfo>> rows = new ArrayList<>();
|
||||
for (Map.Entry<ImmutableList<KeyStroke>, EnumSet<MappingMode>> entry : actualModes.entrySet()) {
|
||||
final ArrayList<KeyStroke> fromKeys = new ArrayList<>(entry.getKey());
|
||||
final Set<MappingMode> mappingModes = entry.getValue();
|
||||
final EnumSet<MappingMode> mappingModes = entry.getValue();
|
||||
if (!mappingModes.isEmpty()) {
|
||||
final MappingMode mode = mappingModes.iterator().next();
|
||||
final KeyMapping mapping = VimPlugin.getKey().getKeyMapping(mode);
|
||||
final MappingInfo mappingInfo = mapping.get(fromKeys);
|
||||
if (mappingInfo != null) {
|
||||
rows.add(new MappingInfo(mappingModes, mappingInfo.getFromKeys(), mappingInfo.getToKeys(),
|
||||
mappingInfo.getExtensionHandler(), mappingInfo.isRecursive()));
|
||||
rows.add(new Pair<>(mappingModes, mappingInfo));
|
||||
}
|
||||
}
|
||||
}
|
||||
Collections.sort(rows);
|
||||
rows.sort(Comparator.comparing(Pair<EnumSet<MappingMode>, MappingInfo>::getSecond));
|
||||
return rows;
|
||||
}
|
||||
|
||||
@NotNull
|
||||
private static String getModesStringCode(@NotNull Set<MappingMode> modes) {
|
||||
private static @NotNull String getModesStringCode(@NotNull Set<MappingMode> modes) {
|
||||
if (modes.equals(MappingMode.NVO)) {
|
||||
return "";
|
||||
}
|
||||
@@ -453,16 +489,14 @@ public class KeyGroup {
|
||||
return "";
|
||||
}
|
||||
|
||||
@NotNull
|
||||
public List<AnAction> getActions(@NotNull Component component, @NotNull KeyStroke keyStroke) {
|
||||
public @NotNull List<AnAction> getActions(@NotNull Component component, @NotNull KeyStroke keyStroke) {
|
||||
final List<AnAction> results = new ArrayList<>();
|
||||
results.addAll(getLocalActions(component, keyStroke));
|
||||
results.addAll(getKeymapActions(keyStroke));
|
||||
return results;
|
||||
}
|
||||
|
||||
@NotNull
|
||||
private static List<AnAction> getLocalActions(@NotNull Component component, @NotNull KeyStroke keyStroke) {
|
||||
private static @NotNull List<AnAction> getLocalActions(@NotNull Component component, @NotNull KeyStroke keyStroke) {
|
||||
final List<AnAction> results = new ArrayList<>();
|
||||
final KeyboardShortcut keyStrokeShortcut = new KeyboardShortcut(keyStroke, null);
|
||||
for (Component c = component; c != null; c = c.getParent()) {
|
||||
@@ -484,8 +518,7 @@ public class KeyGroup {
|
||||
return results;
|
||||
}
|
||||
|
||||
@NotNull
|
||||
private static List<AnAction> getKeymapActions(@NotNull KeyStroke keyStroke) {
|
||||
private static @NotNull List<AnAction> getKeymapActions(@NotNull KeyStroke keyStroke) {
|
||||
final List<AnAction> results = new ArrayList<>();
|
||||
final Keymap keymap = KeymapManager.getInstance().getActiveKeymap();
|
||||
for (String id : keymap.getActionIds(keyStroke)) {
|
||||
@@ -496,4 +529,17 @@ public class KeyGroup {
|
||||
}
|
||||
return results;
|
||||
}
|
||||
|
||||
@Nullable
|
||||
@Override
|
||||
public Element getState() {
|
||||
Element element = new Element("key");
|
||||
saveData(element);
|
||||
return element;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void loadState(@NotNull Element state) {
|
||||
readData(state);
|
||||
}
|
||||
}
|
||||
|
@@ -90,8 +90,8 @@ public class MacroGroup {
|
||||
* @param cnt count
|
||||
* @param total total
|
||||
*/
|
||||
public void playbackKeys(@NotNull final Editor editor, @NotNull final DataContext context, @Nullable final Project project,
|
||||
@NotNull final List<KeyStroke> keys, final int pos, final int cnt, final int total) {
|
||||
public void playbackKeys(final @NotNull Editor editor, final @NotNull DataContext context, final @Nullable Project project,
|
||||
final @NotNull List<KeyStroke> keys, final int pos, final int cnt, final int total) {
|
||||
if (logger.isDebugEnabled()) {
|
||||
logger.debug("playbackKeys " + pos);
|
||||
}
|
||||
@@ -138,8 +138,7 @@ public class MacroGroup {
|
||||
});
|
||||
}
|
||||
|
||||
@NotNull
|
||||
private KeyEvent createKeyEvent(@NotNull KeyStroke stroke, Component component) {
|
||||
private @NotNull KeyEvent createKeyEvent(@NotNull KeyStroke stroke, Component component) {
|
||||
return new KeyEvent(component,
|
||||
stroke.getKeyChar() == KeyEvent.CHAR_UNDEFINED ? KeyEvent.KEY_PRESSED : KeyEvent.KEY_TYPED,
|
||||
System.currentTimeMillis(), stroke.getModifiers(), stroke.getKeyCode(), stroke.getKeyChar());
|
||||
|
@@ -21,6 +21,10 @@ package com.maddyhome.idea.vim.group;
|
||||
import com.intellij.ide.bookmarks.Bookmark;
|
||||
import com.intellij.ide.bookmarks.BookmarkManager;
|
||||
import com.intellij.ide.bookmarks.BookmarksListener;
|
||||
import com.intellij.openapi.components.PersistentStateComponent;
|
||||
import com.intellij.openapi.components.RoamingType;
|
||||
import com.intellij.openapi.components.State;
|
||||
import com.intellij.openapi.components.Storage;
|
||||
import com.intellij.openapi.diagnostic.Logger;
|
||||
import com.intellij.openapi.editor.Document;
|
||||
import com.intellij.openapi.editor.Editor;
|
||||
@@ -53,7 +57,10 @@ import java.util.stream.Collectors;
|
||||
/**
|
||||
* This class contains all the mark related functionality
|
||||
*/
|
||||
public class MarkGroup {
|
||||
@State(name = "VimMarksSettings", storages = {
|
||||
@Storage(value = "$APP_CONFIG$/vim_settings.xml", roamingType = RoamingType.DISABLED)
|
||||
})
|
||||
public class MarkGroup implements PersistentStateComponent<Element> {
|
||||
public static final char MARK_VISUAL_START = '<';
|
||||
public static final char MARK_VISUAL_END = '>';
|
||||
public static final char MARK_CHANGE_START = '[';
|
||||
@@ -83,8 +90,7 @@ public class MarkGroup {
|
||||
* @param ch The desired mark
|
||||
* @return The requested mark if set, null if not set
|
||||
*/
|
||||
@Nullable
|
||||
public Mark getMark(@NotNull Editor editor, char ch) {
|
||||
public @Nullable Mark getMark(@NotNull Editor editor, char ch) {
|
||||
Mark mark = null;
|
||||
if (ch == '`') ch = '\'';
|
||||
|
||||
@@ -135,8 +141,7 @@ public class MarkGroup {
|
||||
* @param count Postive for next jump (Ctrl-I), negative for previous jump (Ctrl-O).
|
||||
* @return The jump or null if out of range.
|
||||
*/
|
||||
@Nullable
|
||||
public Jump getJump(int count) {
|
||||
public @Nullable Jump getJump(int count) {
|
||||
int index = jumps.size() - 1 - (jumpSpot - count);
|
||||
if (index < 0 || index >= jumps.size()) {
|
||||
return null;
|
||||
@@ -154,8 +159,7 @@ public class MarkGroup {
|
||||
* @param ch The mark to get
|
||||
* @return The mark in the current file, if set, null if no such mark
|
||||
*/
|
||||
@Nullable
|
||||
public Mark getFileMark(@NotNull Editor editor, char ch) {
|
||||
public @Nullable Mark getFileMark(@NotNull Editor editor, char ch) {
|
||||
if (ch == '`') ch = '\'';
|
||||
final HashMap fmarks = getFileMarks(editor.getDocument());
|
||||
if (fmarks == null) {
|
||||
@@ -228,8 +232,7 @@ public class MarkGroup {
|
||||
return true;
|
||||
}
|
||||
|
||||
@Nullable
|
||||
private Bookmark createOrGetSystemMark(char ch, int line, @NotNull Editor editor) {
|
||||
private @Nullable Bookmark createOrGetSystemMark(char ch, int line, @NotNull Editor editor) {
|
||||
if (!OptionsManager.INSTANCE.getIdeamarks().isSet()) return null;
|
||||
final Project project = editor.getProject();
|
||||
if (project == null) return null;
|
||||
@@ -259,18 +262,15 @@ public class MarkGroup {
|
||||
setMark(editor, MARK_CHANGE_END, range.getEndOffset());
|
||||
}
|
||||
|
||||
@Nullable
|
||||
public TextRange getChangeMarks(@NotNull Editor editor) {
|
||||
public @Nullable TextRange getChangeMarks(@NotNull Editor editor) {
|
||||
return getMarksRange(editor, MARK_CHANGE_START, MARK_CHANGE_END);
|
||||
}
|
||||
|
||||
@Nullable
|
||||
public TextRange getVisualSelectionMarks(@NotNull Editor editor) {
|
||||
public @Nullable TextRange getVisualSelectionMarks(@NotNull Editor editor) {
|
||||
return getMarksRange(editor, MARK_VISUAL_START, MARK_VISUAL_END);
|
||||
}
|
||||
|
||||
@Nullable
|
||||
private TextRange getMarksRange(@NotNull Editor editor, char startMark, char endMark) {
|
||||
private @Nullable TextRange getMarksRange(@NotNull Editor editor, char startMark, char endMark) {
|
||||
final Mark start = getMark(editor, startMark);
|
||||
final Mark end = getMark(editor, endMark);
|
||||
if (start != null && end != null) {
|
||||
@@ -338,8 +338,7 @@ public class MarkGroup {
|
||||
mark.clear();
|
||||
}
|
||||
|
||||
@NotNull
|
||||
public List<Mark> getMarks(@NotNull Editor editor) {
|
||||
public @NotNull List<Mark> getMarks(@NotNull Editor editor) {
|
||||
HashSet<Mark> res = new HashSet<>();
|
||||
|
||||
final FileMarks<Character, Mark> marks = getFileMarks(editor.getDocument());
|
||||
@@ -355,8 +354,7 @@ public class MarkGroup {
|
||||
return list;
|
||||
}
|
||||
|
||||
@NotNull
|
||||
public List<Jump> getJumps() {
|
||||
public @NotNull List<Jump> getJumps() {
|
||||
return jumps;
|
||||
}
|
||||
|
||||
@@ -371,8 +369,7 @@ public class MarkGroup {
|
||||
* @return The map of marks. The keys are <code>Character</code>s of the mark names, the values are
|
||||
* <code>Mark</code>s.
|
||||
*/
|
||||
@Nullable
|
||||
private FileMarks<Character, Mark> getFileMarks(@NotNull final Document doc) {
|
||||
private @Nullable FileMarks<Character, Mark> getFileMarks(final @NotNull Document doc) {
|
||||
VirtualFile vf = FileDocumentManager.getInstance().getFile(doc);
|
||||
if (vf == null) {
|
||||
return null;
|
||||
@@ -381,8 +378,7 @@ public class MarkGroup {
|
||||
return getFileMarks(vf.getPath());
|
||||
}
|
||||
|
||||
@Nullable
|
||||
private HashMap<Character, Mark> getAllFileMarks(@NotNull final Document doc) {
|
||||
private @Nullable HashMap<Character, Mark> getAllFileMarks(final @NotNull Document doc) {
|
||||
VirtualFile vf = FileDocumentManager.getInstance().getFile(doc);
|
||||
if (vf == null) {
|
||||
return null;
|
||||
@@ -659,6 +655,19 @@ public class MarkGroup {
|
||||
}
|
||||
}
|
||||
|
||||
@Nullable
|
||||
@Override
|
||||
public Element getState() {
|
||||
Element element = new Element("marks");
|
||||
saveData(element);
|
||||
return element;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void loadState(@NotNull Element state) {
|
||||
readData(state);
|
||||
}
|
||||
|
||||
private static class FileMarks<K, V> extends HashMap<K, V> {
|
||||
public void setTimestamp(Date timestamp) {
|
||||
this.timestamp = timestamp;
|
||||
@@ -723,8 +732,7 @@ public class MarkGroup {
|
||||
// TODO - update jumps
|
||||
}
|
||||
|
||||
@Nullable
|
||||
private Editor getAnEditor(@NotNull Document doc) {
|
||||
private @Nullable Editor getAnEditor(@NotNull Document doc) {
|
||||
Editor[] editors = EditorFactory.getInstance().getEditors(doc);
|
||||
|
||||
if (editors.length > 0) {
|
||||
@@ -785,9 +793,9 @@ public class MarkGroup {
|
||||
}
|
||||
}
|
||||
|
||||
@NotNull private final HashMap<String, FileMarks<Character, Mark>> fileMarks = new HashMap<>();
|
||||
@NotNull private final HashMap<Character, Mark> globalMarks = new HashMap<>();
|
||||
@NotNull private final List<Jump> jumps = new ArrayList<>();
|
||||
private final @NotNull HashMap<String, FileMarks<Character, Mark>> fileMarks = new HashMap<>();
|
||||
private final @NotNull HashMap<Character, Mark> globalMarks = new HashMap<>();
|
||||
private final @NotNull List<Jump> jumps = new ArrayList<>();
|
||||
private int jumpSpot = -1;
|
||||
|
||||
private static final int SAVE_MARK_COUNT = 20;
|
||||
|
@@ -102,8 +102,7 @@ public class MotionGroup {
|
||||
* @param argument Any argument needed by the motion
|
||||
* @return The motion's range
|
||||
*/
|
||||
@Nullable
|
||||
public static TextRange getMotionRange(@NotNull Editor editor,
|
||||
public static @Nullable TextRange getMotionRange(@NotNull Editor editor,
|
||||
@NotNull Caret caret,
|
||||
DataContext context,
|
||||
int count,
|
||||
@@ -239,32 +238,27 @@ public class MotionGroup {
|
||||
}
|
||||
}
|
||||
|
||||
@Nullable
|
||||
public TextRange getBlockQuoteRange(@NotNull Editor editor, @NotNull Caret caret, char quote, boolean isOuter) {
|
||||
public @Nullable TextRange getBlockQuoteRange(@NotNull Editor editor, @NotNull Caret caret, char quote, boolean isOuter) {
|
||||
return SearchHelper.findBlockQuoteInLineRange(editor, caret, quote, isOuter);
|
||||
}
|
||||
|
||||
@Nullable
|
||||
public TextRange getBlockRange(@NotNull Editor editor, @NotNull Caret caret, int count, boolean isOuter, char type) {
|
||||
public @Nullable TextRange getBlockRange(@NotNull Editor editor, @NotNull Caret caret, int count, boolean isOuter, char type) {
|
||||
return SearchHelper.findBlockRange(editor, caret, type, count, isOuter);
|
||||
}
|
||||
|
||||
@Nullable
|
||||
public TextRange getBlockTagRange(@NotNull Editor editor, @NotNull Caret caret, int count, boolean isOuter) {
|
||||
public @Nullable TextRange getBlockTagRange(@NotNull Editor editor, @NotNull Caret caret, int count, boolean isOuter) {
|
||||
return SearchHelper.findBlockTagRange(editor, caret, count, isOuter);
|
||||
}
|
||||
|
||||
@NotNull
|
||||
public TextRange getSentenceRange(@NotNull Editor editor, @NotNull Caret caret, int count, boolean isOuter) {
|
||||
public @NotNull TextRange getSentenceRange(@NotNull Editor editor, @NotNull Caret caret, int count, boolean isOuter) {
|
||||
return SearchHelper.findSentenceRange(editor, caret, count, isOuter);
|
||||
}
|
||||
|
||||
@Nullable
|
||||
public TextRange getParagraphRange(@NotNull Editor editor, @NotNull Caret caret, int count, boolean isOuter) {
|
||||
public @Nullable TextRange getParagraphRange(@NotNull Editor editor, @NotNull Caret caret, int count, boolean isOuter) {
|
||||
return SearchHelper.findParagraphRange(editor, caret, count, isOuter);
|
||||
}
|
||||
|
||||
private static int getScrollScreenTargetCaretVisualLine(@NotNull final Editor editor, int rawCount, boolean down) {
|
||||
private static int getScrollScreenTargetCaretVisualLine(final @NotNull Editor editor, int rawCount, boolean down) {
|
||||
final Rectangle visibleArea = editor.getScrollingModel().getVisibleArea();
|
||||
final int caretVisualLine = editor.getCaretModel().getVisualPosition().line;
|
||||
final int scrollOption = getScrollOption(rawCount);
|
||||
@@ -296,7 +290,7 @@ public class MotionGroup {
|
||||
return rawCount;
|
||||
}
|
||||
|
||||
private static int getNormalizedScrollOffset(@NotNull final Editor editor) {
|
||||
private static int getNormalizedScrollOffset(final @NotNull Editor editor) {
|
||||
int scrollOffset = OptionsManager.INSTANCE.getScrolloff().value();
|
||||
return EditorHelper.normalizeScrollOffset(editor, scrollOffset);
|
||||
}
|
||||
@@ -328,8 +322,7 @@ public class MotionGroup {
|
||||
}
|
||||
}
|
||||
|
||||
@Nullable
|
||||
private Editor selectEditor(@NotNull Editor editor, @NotNull Mark mark) {
|
||||
private @Nullable Editor selectEditor(@NotNull Editor editor, @NotNull Mark mark) {
|
||||
final VirtualFile virtualFile = markToVirtualFile(mark);
|
||||
if (virtualFile != null) {
|
||||
return selectEditor(editor, virtualFile);
|
||||
@@ -339,15 +332,13 @@ public class MotionGroup {
|
||||
}
|
||||
}
|
||||
|
||||
@Nullable
|
||||
private VirtualFile markToVirtualFile(@NotNull Mark mark) {
|
||||
private @Nullable VirtualFile markToVirtualFile(@NotNull Mark mark) {
|
||||
String protocol = mark.getProtocol();
|
||||
VirtualFileSystem fileSystem = VirtualFileManager.getInstance().getFileSystem(protocol);
|
||||
return fileSystem.findFileByPath(mark.getFilename());
|
||||
}
|
||||
|
||||
@Nullable
|
||||
private Editor selectEditor(@NotNull Editor editor, @NotNull VirtualFile file) {
|
||||
private @Nullable Editor selectEditor(@NotNull Editor editor, @NotNull VirtualFile file) {
|
||||
return VimPlugin.getFile().selectEditor(editor.getProject(), file);
|
||||
}
|
||||
|
||||
@@ -778,8 +769,7 @@ public class MotionGroup {
|
||||
return true;
|
||||
}
|
||||
|
||||
@NotNull
|
||||
public TextRange getWordRange(@NotNull Editor editor,
|
||||
public @NotNull TextRange getWordRange(@NotNull Editor editor,
|
||||
@NotNull Caret caret,
|
||||
int count,
|
||||
boolean isOuter,
|
||||
@@ -1035,13 +1025,13 @@ public class MotionGroup {
|
||||
public int moveCaretHorizontal(@NotNull Editor editor, @NotNull Caret caret, int count, boolean allowPastEnd) {
|
||||
int oldOffset = caret.getOffset();
|
||||
int diff = 0;
|
||||
String text = editor.getDocument().getText();
|
||||
CharSequence text = editor.getDocument().getCharsSequence();
|
||||
int sign = (int)Math.signum(count);
|
||||
for (Integer pointer : new IntProgression(0, count - sign, sign)) {
|
||||
int textPointer = oldOffset + pointer;
|
||||
if (textPointer < text.length() && textPointer >= 0) {
|
||||
// Actual char size can differ from 1 if unicode characters are used (like 🐔)
|
||||
diff += Character.charCount(text.codePointAt(textPointer));
|
||||
diff += Character.charCount(Character.codePointAt(text, textPointer));
|
||||
}
|
||||
else {
|
||||
diff += 1;
|
||||
@@ -1108,7 +1098,7 @@ public class MotionGroup {
|
||||
return editor.logicalPositionToOffset(newPos);
|
||||
}
|
||||
|
||||
public boolean scrollScreen(@NotNull final Editor editor, int rawCount, boolean down) {
|
||||
public boolean scrollScreen(final @NotNull Editor editor, int rawCount, boolean down) {
|
||||
final CaretModel caretModel = editor.getCaretModel();
|
||||
final int currentLogicalLine = caretModel.getLogicalPosition().line;
|
||||
|
||||
|
@@ -33,6 +33,7 @@ import com.intellij.openapi.actionSystem.KeyboardShortcut
|
||||
import com.intellij.openapi.keymap.Keymap
|
||||
import com.intellij.openapi.keymap.KeymapUtil
|
||||
import com.intellij.openapi.options.ShowSettingsUtil
|
||||
import com.intellij.openapi.project.DumbAwareAction
|
||||
import com.intellij.openapi.project.Project
|
||||
import com.intellij.openapi.ui.Messages
|
||||
import com.intellij.openapi.util.SystemInfo
|
||||
@@ -48,6 +49,9 @@ import javax.swing.event.HyperlinkEvent
|
||||
|
||||
/**
|
||||
* @author Alex Plate
|
||||
*
|
||||
* This service is can be used as application level and as project level service.
|
||||
* If project is null, this means that this is an application level service and notification will be shown for all projects
|
||||
*/
|
||||
class NotificationService(private val project: Project?) {
|
||||
// This constructor is used to create an applicationService
|
||||
@@ -143,7 +147,7 @@ class NotificationService(private val project: Project?) {
|
||||
NotificationType.INFORMATION).notify(project)
|
||||
}
|
||||
|
||||
class OpenIdeaVimRcAction(private val notification: Notification?) : AnAction("Open ~/.ideavimrc") {
|
||||
class OpenIdeaVimRcAction(private val notification: Notification?) : DumbAwareAction("Open ~/.ideavimrc") {
|
||||
override fun actionPerformed(e: AnActionEvent) {
|
||||
val eventProject = e.project
|
||||
if (eventProject != null) {
|
||||
|
@@ -62,8 +62,7 @@ public class ProcessGroup {
|
||||
return ExEntryPanel.getInstance().getLabel().equals("/");
|
||||
}
|
||||
|
||||
@NotNull
|
||||
public String endSearchCommand(@NotNull final Editor editor) {
|
||||
public @NotNull String endSearchCommand(final @NotNull Editor editor) {
|
||||
ExEntryPanel panel = ExEntryPanel.getInstance();
|
||||
panel.deactivate(true);
|
||||
|
||||
@@ -100,7 +99,7 @@ public class ProcessGroup {
|
||||
}
|
||||
}
|
||||
|
||||
public boolean processExEntry(@NotNull final Editor editor, @NotNull final DataContext context) {
|
||||
public boolean processExEntry(final @NotNull Editor editor, final @NotNull DataContext context) {
|
||||
ExEntryPanel panel = ExEntryPanel.getInstance();
|
||||
panel.deactivate(true);
|
||||
boolean res = true;
|
||||
@@ -143,7 +142,7 @@ public class ProcessGroup {
|
||||
return res;
|
||||
}
|
||||
|
||||
public void cancelExEntry(@NotNull final Editor editor, boolean resetCaret) {
|
||||
public void cancelExEntry(final @NotNull Editor editor, boolean resetCaret) {
|
||||
CommandState.getInstance(editor).popModes();
|
||||
KeyHandler.getInstance().reset(editor);
|
||||
ExEntryPanel panel = ExEntryPanel.getInstance();
|
||||
@@ -163,8 +162,7 @@ public class ProcessGroup {
|
||||
panel.activate(editor, context, ":", initText, 1);
|
||||
}
|
||||
|
||||
@NotNull
|
||||
private String getRange(Editor editor, @NotNull Command cmd) {
|
||||
private @NotNull String getRange(Editor editor, @NotNull Command cmd) {
|
||||
String initText = "";
|
||||
if (CommandState.getInstance(editor).getMode() == CommandState.Mode.VISUAL) {
|
||||
initText = "'<,'>";
|
||||
@@ -191,8 +189,7 @@ public class ProcessGroup {
|
||||
return true;
|
||||
}
|
||||
|
||||
@NotNull
|
||||
public String executeCommand(@NotNull String command, @Nullable CharSequence input) throws IOException {
|
||||
public @NotNull String executeCommand(@NotNull String command, @Nullable CharSequence input) throws IOException {
|
||||
if (logger.isDebugEnabled()) {
|
||||
logger.debug("command=" + command);
|
||||
}
|
||||
|
@@ -23,6 +23,10 @@ import com.intellij.codeInsight.editorActions.CopyPastePostProcessor;
|
||||
import com.intellij.codeInsight.editorActions.CopyPastePreProcessor;
|
||||
import com.intellij.codeInsight.editorActions.TextBlockTransferable;
|
||||
import com.intellij.codeInsight.editorActions.TextBlockTransferableData;
|
||||
import com.intellij.openapi.components.PersistentStateComponent;
|
||||
import com.intellij.openapi.components.RoamingType;
|
||||
import com.intellij.openapi.components.State;
|
||||
import com.intellij.openapi.components.Storage;
|
||||
import com.intellij.openapi.diagnostic.Logger;
|
||||
import com.intellij.openapi.editor.CaretStateTransferableData;
|
||||
import com.intellij.openapi.editor.Editor;
|
||||
@@ -72,7 +76,10 @@ import java.util.stream.Collectors;
|
||||
/**
|
||||
* This group works with command associated with copying and pasting text
|
||||
*/
|
||||
public class RegisterGroup {
|
||||
@State(name = "VimRegisterSettings", storages = {
|
||||
@Storage(value = "$APP_CONFIG$/vim_settings.xml", roamingType = RoamingType.DISABLED)
|
||||
})
|
||||
public class RegisterGroup implements PersistentStateComponent<Element> {
|
||||
private static final String WRITABLE_REGISTERS = "0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ-*+_/\"";
|
||||
private static final String READONLY_REGISTERS = ":.%#=/";
|
||||
private static final String RECORDABLE_REGISTER = "0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ";
|
||||
@@ -83,9 +90,9 @@ public class RegisterGroup {
|
||||
|
||||
private char defaultRegister = '"';
|
||||
private char lastRegister = defaultRegister;
|
||||
@NotNull private final HashMap<Character, Register> registers = new HashMap<>();
|
||||
private final @NotNull HashMap<Character, Register> registers = new HashMap<>();
|
||||
private char recordRegister = 0;
|
||||
@Nullable private List<KeyStroke> recordList = null;
|
||||
private @Nullable List<KeyStroke> recordList = null;
|
||||
|
||||
public RegisterGroup() {
|
||||
final ListOption clipboardOption = OptionsManager.INSTANCE.getClipboard();
|
||||
@@ -246,8 +253,7 @@ public class RegisterGroup {
|
||||
return true;
|
||||
}
|
||||
|
||||
@NotNull
|
||||
public List<TextBlockTransferableData> getTransferableData(@NotNull Editor editor,
|
||||
public @NotNull List<TextBlockTransferableData> getTransferableData(@NotNull Editor editor,
|
||||
@NotNull TextRange textRange,
|
||||
String text) {
|
||||
final List<TextBlockTransferableData> transferableDatas = new ArrayList<>();
|
||||
@@ -316,13 +322,11 @@ public class RegisterGroup {
|
||||
*
|
||||
* @return The register, null if no such register
|
||||
*/
|
||||
@Nullable
|
||||
public Register getLastRegister() {
|
||||
public @Nullable Register getLastRegister() {
|
||||
return getRegister(lastRegister);
|
||||
}
|
||||
|
||||
@Nullable
|
||||
public Register getPlaybackRegister(char r) {
|
||||
public @Nullable Register getPlaybackRegister(char r) {
|
||||
if (PLAYBACK_REGISTER.indexOf(r) != 0) {
|
||||
return getRegister(r);
|
||||
}
|
||||
@@ -331,8 +335,7 @@ public class RegisterGroup {
|
||||
}
|
||||
}
|
||||
|
||||
@Nullable
|
||||
public Register getRegister(char r) {
|
||||
public @Nullable Register getRegister(char r) {
|
||||
// Uppercase registers actually get the lowercase register
|
||||
if (Character.isUpperCase(r)) {
|
||||
r = Character.toLowerCase(r);
|
||||
@@ -356,8 +359,7 @@ public class RegisterGroup {
|
||||
return defaultRegister;
|
||||
}
|
||||
|
||||
@NotNull
|
||||
public List<Register> getRegisters() {
|
||||
public @NotNull List<Register> getRegisters() {
|
||||
final List<Register> res = new ArrayList<>(registers.values());
|
||||
for (Character r : CLIPBOARD_REGISTERS) {
|
||||
final Register register = refreshClipboardRegister(r);
|
||||
@@ -419,7 +421,7 @@ public class RegisterGroup {
|
||||
recordRegister = 0;
|
||||
}
|
||||
|
||||
public void saveData(@NotNull final Element element) {
|
||||
public void saveData(final @NotNull Element element) {
|
||||
logger.debug("saveData");
|
||||
final Element registersElement = new Element("registers");
|
||||
for (Character key : registers.keySet()) {
|
||||
@@ -451,7 +453,7 @@ public class RegisterGroup {
|
||||
element.addContent(registersElement);
|
||||
}
|
||||
|
||||
public void readData(@NotNull final Element element) {
|
||||
public void readData(final @NotNull Element element) {
|
||||
logger.debug("readData");
|
||||
final Element registersElement = element.getChild("registers");
|
||||
if (registersElement != null) {
|
||||
@@ -491,8 +493,7 @@ public class RegisterGroup {
|
||||
}
|
||||
}
|
||||
|
||||
@Nullable
|
||||
private Register refreshClipboardRegister(char r) {
|
||||
private @Nullable Register refreshClipboardRegister(char r) {
|
||||
final Pair<String, List<TextBlockTransferableData>> clipboardData = ClipboardHandler.getClipboardTextAndTransferableData();
|
||||
final Register currentRegister = registers.get(r);
|
||||
final String text = clipboardData.getFirst();
|
||||
@@ -506,8 +507,7 @@ public class RegisterGroup {
|
||||
return null;
|
||||
}
|
||||
|
||||
@NotNull
|
||||
private SelectionType guessSelectionType(@NotNull String text) {
|
||||
private @NotNull SelectionType guessSelectionType(@NotNull String text) {
|
||||
if (text.endsWith("\n")) {
|
||||
return SelectionType.LINE_WISE;
|
||||
}
|
||||
@@ -515,4 +515,17 @@ public class RegisterGroup {
|
||||
return SelectionType.CHARACTER_WISE;
|
||||
}
|
||||
}
|
||||
|
||||
@Nullable
|
||||
@Override
|
||||
public Element getState() {
|
||||
Element element = new Element("registers");
|
||||
saveData(element);
|
||||
return element;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void loadState(@NotNull Element state) {
|
||||
readData(state);
|
||||
}
|
||||
}
|
||||
|
@@ -19,6 +19,10 @@ package com.maddyhome.idea.vim.group;
|
||||
|
||||
import com.google.common.collect.Lists;
|
||||
import com.intellij.openapi.application.ApplicationManager;
|
||||
import com.intellij.openapi.components.PersistentStateComponent;
|
||||
import com.intellij.openapi.components.RoamingType;
|
||||
import com.intellij.openapi.components.State;
|
||||
import com.intellij.openapi.components.Storage;
|
||||
import com.intellij.openapi.diagnostic.Logger;
|
||||
import com.intellij.openapi.editor.*;
|
||||
import com.intellij.openapi.editor.colors.EditorColors;
|
||||
@@ -60,7 +64,10 @@ import java.text.ParsePosition;
|
||||
import java.util.List;
|
||||
import java.util.*;
|
||||
|
||||
public class SearchGroup {
|
||||
@State(name = "VimSearchSettings", storages = {
|
||||
@Storage(value = "$APP_CONFIG$/vim_settings.xml", roamingType = RoamingType.DISABLED)
|
||||
})
|
||||
public class SearchGroup implements PersistentStateComponent<Element> {
|
||||
public SearchGroup() {
|
||||
final OptionsManager options = OptionsManager.INSTANCE;
|
||||
options.getHlsearch().addOptionChangeListener((oldValue, newValue) -> {
|
||||
@@ -90,8 +97,7 @@ public class SearchGroup {
|
||||
showSearchHighlight = show;
|
||||
}
|
||||
|
||||
@Nullable
|
||||
public String getLastSearch() {
|
||||
public @Nullable String getLastSearch() {
|
||||
return lastSearch;
|
||||
}
|
||||
|
||||
@@ -101,8 +107,7 @@ public class SearchGroup {
|
||||
return lastDir;
|
||||
}
|
||||
|
||||
@Nullable
|
||||
public String getLastPattern() {
|
||||
public @Nullable String getLastPattern() {
|
||||
return lastPattern;
|
||||
}
|
||||
|
||||
@@ -123,8 +128,7 @@ public class SearchGroup {
|
||||
|
||||
// This method should not be private because it's used in external plugins
|
||||
@SuppressWarnings("WeakerAccess")
|
||||
@NotNull
|
||||
public static List<TextRange> findAll(@NotNull Editor editor,
|
||||
public static @NotNull List<TextRange> findAll(@NotNull Editor editor,
|
||||
@NotNull String pattern,
|
||||
int startLine,
|
||||
int endLine,
|
||||
@@ -172,8 +176,7 @@ public class SearchGroup {
|
||||
return results;
|
||||
}
|
||||
|
||||
@NotNull
|
||||
private static ReplaceConfirmationChoice confirmChoice(@NotNull Editor editor, @NotNull String match, @NotNull Caret caret, int startoff) {
|
||||
private static @NotNull ReplaceConfirmationChoice confirmChoice(@NotNull Editor editor, @NotNull String match, @NotNull Caret caret, int startoff) {
|
||||
final Ref<ReplaceConfirmationChoice> result = Ref.create(ReplaceConfirmationChoice.QUIT);
|
||||
final Function1<KeyStroke, Boolean> keyStrokeProcessor = key -> {
|
||||
final ReplaceConfirmationChoice choice;
|
||||
@@ -520,8 +523,7 @@ public class SearchGroup {
|
||||
return max.getStartOffset();
|
||||
}
|
||||
|
||||
@Nullable
|
||||
public TextRange getNextSearchRange(@NotNull Editor editor, int count, boolean forwards) {
|
||||
public @Nullable TextRange getNextSearchRange(@NotNull Editor editor, int count, boolean forwards) {
|
||||
editor.getCaretModel().removeSecondaryCarets();
|
||||
TextRange current = findUnderCaret(editor);
|
||||
|
||||
@@ -544,8 +546,7 @@ public class SearchGroup {
|
||||
}
|
||||
}
|
||||
|
||||
@Nullable
|
||||
private TextRange findNextSearchForGn(@NotNull Editor editor, int count, boolean forwards) {
|
||||
private @Nullable TextRange findNextSearchForGn(@NotNull Editor editor, int count, boolean forwards) {
|
||||
if (forwards) {
|
||||
final EnumSet<SearchOptions> searchOptions = EnumSet.of(SearchOptions.WRAP, SearchOptions.WHOLE_FILE);
|
||||
return findIt(editor, lastSearch, editor.getCaretModel().getOffset(), count, searchOptions);
|
||||
@@ -554,15 +555,13 @@ public class SearchGroup {
|
||||
}
|
||||
}
|
||||
|
||||
@Nullable
|
||||
private TextRange findUnderCaret(@NotNull Editor editor) {
|
||||
private @Nullable TextRange findUnderCaret(@NotNull Editor editor) {
|
||||
final TextRange backSearch = searchBackward(editor, editor.getCaretModel().getOffset() + 1, 1);
|
||||
if (backSearch == null) return null;
|
||||
return backSearch.contains(editor.getCaretModel().getOffset()) ? backSearch : null;
|
||||
}
|
||||
|
||||
@Nullable
|
||||
private TextRange searchBackward(@NotNull Editor editor, int offset, int count) {
|
||||
private @Nullable TextRange searchBackward(@NotNull Editor editor, int offset, int count) {
|
||||
// Backward search returns wrongs end offset for some cases. That's why we should perform additional forward search
|
||||
final EnumSet<SearchOptions> searchOptions = EnumSet.of(SearchOptions.WRAP, SearchOptions.WHOLE_FILE, SearchOptions.BACKWARDS);
|
||||
final TextRange foundBackward = findIt(editor, lastSearch, offset, count, searchOptions);
|
||||
@@ -1320,8 +1319,7 @@ public class SearchGroup {
|
||||
return true;
|
||||
}
|
||||
|
||||
@NotNull
|
||||
private RangeHighlighter highlightConfirm(@NotNull Editor editor, int start, int end) {
|
||||
private @NotNull RangeHighlighter highlightConfirm(@NotNull Editor editor, int start, int end) {
|
||||
TextAttributes color = new TextAttributes(
|
||||
editor.getColorsScheme().getColor(EditorColors.SELECTION_FOREGROUND_COLOR),
|
||||
editor.getColorsScheme().getColor(EditorColors.SELECTION_BACKGROUND_COLOR),
|
||||
@@ -1332,8 +1330,7 @@ public class SearchGroup {
|
||||
color, HighlighterTargetArea.EXACT_RANGE);
|
||||
}
|
||||
|
||||
@NotNull
|
||||
private static RangeHighlighter highlightMatch(@NotNull Editor editor, int start, int end, boolean current, String tooltip) {
|
||||
private static @NotNull RangeHighlighter highlightMatch(@NotNull Editor editor, int start, int end, boolean current, String tooltip) {
|
||||
TextAttributes attributes = editor.getColorsScheme().getAttributes(EditorColors.TEXT_SEARCH_RESULT_ATTRIBUTES);
|
||||
if (current) {
|
||||
// This mimics what IntelliJ does with the Find live preview
|
||||
@@ -1411,8 +1408,7 @@ public class SearchGroup {
|
||||
element.addContent(search);
|
||||
}
|
||||
|
||||
@NotNull
|
||||
private static Element createElementWithText(@NotNull String name, @NotNull String text) {
|
||||
private static @NotNull Element createElementWithText(@NotNull String name, @NotNull String text) {
|
||||
return StringHelper.setSafeXmlText(new Element(name), text);
|
||||
}
|
||||
|
||||
@@ -1442,8 +1438,7 @@ public class SearchGroup {
|
||||
}
|
||||
}
|
||||
|
||||
@Nullable
|
||||
private static String getSafeChildText(@NotNull Element element, @NotNull String name) {
|
||||
private static @Nullable String getSafeChildText(@NotNull Element element, @NotNull String name) {
|
||||
final Element child = element.getChild(name);
|
||||
return child != null ? StringHelper.getSafeXmlText(child) : null;
|
||||
}
|
||||
@@ -1456,6 +1451,19 @@ public class SearchGroup {
|
||||
VimPlugin.getSearch().updateSearchHighlights();
|
||||
}
|
||||
|
||||
@Nullable
|
||||
@Override
|
||||
public Element getState() {
|
||||
Element element = new Element("search");
|
||||
saveData(element);
|
||||
return element;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void loadState(@NotNull Element state) {
|
||||
readData(state);
|
||||
}
|
||||
|
||||
public static class DocumentSearchListener implements DocumentListener {
|
||||
|
||||
public static DocumentSearchListener INSTANCE = new DocumentSearchListener();
|
||||
@@ -1524,11 +1532,11 @@ public class SearchGroup {
|
||||
IGNORE_SMARTCASE,
|
||||
}
|
||||
|
||||
@Nullable private String lastSearch;
|
||||
@Nullable private String lastPattern;
|
||||
@Nullable private String lastSubstitute;
|
||||
@Nullable private String lastReplace;
|
||||
@Nullable private String lastOffset;
|
||||
private @Nullable String lastSearch;
|
||||
private @Nullable String lastPattern;
|
||||
private @Nullable String lastSubstitute;
|
||||
private @Nullable String lastReplace;
|
||||
private @Nullable String lastOffset;
|
||||
private boolean lastIgnoreSmartCase;
|
||||
private int lastDir;
|
||||
private boolean showSearchHighlight = OptionsManager.INSTANCE.getHlsearch().isSet();
|
||||
|
@@ -114,8 +114,7 @@ public class WindowGroup {
|
||||
windows.get(normalized).setAsCurrentWindow(true);
|
||||
}
|
||||
|
||||
@NotNull
|
||||
private static List<EditorWindow> findWindowsInRow(@NotNull EditorWindow anchor,
|
||||
private static @NotNull List<EditorWindow> findWindowsInRow(@NotNull EditorWindow anchor,
|
||||
@NotNull List<EditorWindow> windows, final boolean vertical) {
|
||||
final Rectangle anchorRect = getEditorWindowRectangle(anchor);
|
||||
if (anchorRect != null) {
|
||||
@@ -145,8 +144,7 @@ public class WindowGroup {
|
||||
return Collections.singletonList(anchor);
|
||||
}
|
||||
|
||||
@NotNull
|
||||
private static FileEditorManagerEx getFileEditorManager(@NotNull DataContext context) {
|
||||
private static @NotNull FileEditorManagerEx getFileEditorManager(@NotNull DataContext context) {
|
||||
final Project project = PlatformDataKeys.PROJECT.getData(context);
|
||||
return FileEditorManagerEx.getInstanceEx(Objects.requireNonNull(project));
|
||||
}
|
||||
@@ -171,8 +169,7 @@ public class WindowGroup {
|
||||
}
|
||||
}
|
||||
|
||||
@Nullable
|
||||
private static Rectangle getEditorWindowRectangle(@NotNull EditorWindow window) {
|
||||
private static @Nullable Rectangle getEditorWindowRectangle(@NotNull EditorWindow window) {
|
||||
final EditorWithProviderComposite editor = window.getSelectedEditor();
|
||||
if (editor != null) {
|
||||
final Point point = editor.getComponent().getLocationOnScreen();
|
||||
|
@@ -40,6 +40,12 @@ import javax.swing.KeyStroke
|
||||
* If you want to use exactly `<` character, replace it with `<`. E.g. `i<` - i<
|
||||
* If you want to use comma in mapping, use `«COMMA»`
|
||||
* Do not place a whitespace around the comma!
|
||||
*
|
||||
*
|
||||
* !! IMPORTANT !!
|
||||
* You may wonder why the extension points are used instead of any other approach to register actions.
|
||||
* The reason is startup performance. Using the extension points you don't even have to load classes of actions.
|
||||
* So, all actions are loaded on demand, including classes in classloader.
|
||||
*/
|
||||
class ActionBeanClass : AbstractExtensionPointBean() {
|
||||
@Attribute("implementation")
|
||||
|
@@ -39,8 +39,7 @@ public class DigraphResult {
|
||||
return new DigraphResult(RES_HANDLED, promptCharacter);
|
||||
}
|
||||
|
||||
@Nullable
|
||||
public KeyStroke getStroke() {
|
||||
public @Nullable KeyStroke getStroke() {
|
||||
return stroke;
|
||||
}
|
||||
|
||||
@@ -53,6 +52,6 @@ public class DigraphResult {
|
||||
}
|
||||
|
||||
private final int result;
|
||||
@Nullable private final KeyStroke stroke;
|
||||
private final @Nullable KeyStroke stroke;
|
||||
private char promptCharacter;
|
||||
}
|
||||
|
@@ -55,8 +55,7 @@ public class DigraphSequence {
|
||||
return DigraphResult.HANDLED_LITERAL;
|
||||
}
|
||||
|
||||
@NotNull
|
||||
public DigraphResult processKey(@NotNull KeyStroke key, @NotNull Editor editor) {
|
||||
public @NotNull DigraphResult processKey(@NotNull KeyStroke key, @NotNull Editor editor) {
|
||||
switch (digraphState) {
|
||||
case DIG_STATE_PENDING:
|
||||
logger.debug("DIG_STATE_PENDING");
|
||||
|
@@ -42,18 +42,18 @@ import static java.lang.Integer.max;
|
||||
* This is a set of helper methods for working with editors. All line and column values are zero based.
|
||||
*/
|
||||
public class EditorHelper {
|
||||
public static int getVisualLineAtTopOfScreen(@NotNull final Editor editor) {
|
||||
public static int getVisualLineAtTopOfScreen(final @NotNull Editor editor) {
|
||||
final Rectangle visibleArea = editor.getScrollingModel().getVisibleArea();
|
||||
return getFullVisualLine(editor, visibleArea.y, visibleArea.y, visibleArea.y + visibleArea.height);
|
||||
}
|
||||
|
||||
public static int getVisualLineAtMiddleOfScreen(@NotNull final Editor editor) {
|
||||
public static int getVisualLineAtMiddleOfScreen(final @NotNull Editor editor) {
|
||||
final ScrollingModel scrollingModel = editor.getScrollingModel();
|
||||
final Rectangle visibleArea = scrollingModel.getVisibleArea();
|
||||
return editor.yToVisualLine(visibleArea.y + (visibleArea.height / 2));
|
||||
}
|
||||
|
||||
public static int getVisualLineAtBottomOfScreen(@NotNull final Editor editor) {
|
||||
public static int getVisualLineAtBottomOfScreen(final @NotNull Editor editor) {
|
||||
final Rectangle visibleArea = editor.getScrollingModel().getVisibleArea();
|
||||
return getFullVisualLine(editor, visibleArea.y + visibleArea.height, visibleArea.y, visibleArea.y + visibleArea.height);
|
||||
}
|
||||
@@ -65,7 +65,7 @@ public class EditorHelper {
|
||||
* @param editor The editor
|
||||
* @return The number of characters in the current line
|
||||
*/
|
||||
public static int getLineLength(@NotNull final Editor editor) {
|
||||
public static int getLineLength(final @NotNull Editor editor) {
|
||||
return getLineLength(editor, editor.getCaretModel().getLogicalPosition().line);
|
||||
}
|
||||
|
||||
@@ -77,7 +77,7 @@ public class EditorHelper {
|
||||
* @param line The logical line within the file
|
||||
* @return The number of characters in the specified line
|
||||
*/
|
||||
public static int getLineLength(@NotNull final Editor editor, final int line) {
|
||||
public static int getLineLength(final @NotNull Editor editor, final int line) {
|
||||
if (getLineCount(editor) == 0) {
|
||||
return 0;
|
||||
}
|
||||
@@ -94,7 +94,7 @@ public class EditorHelper {
|
||||
* @param line The visual line within the file
|
||||
* @return The number of characters in the specified line
|
||||
*/
|
||||
public static int getVisualLineLength(@NotNull final Editor editor, final int line) {
|
||||
public static int getVisualLineLength(final @NotNull Editor editor, final int line) {
|
||||
return getLineLength(editor, visualLineToLogicalLine(editor, line));
|
||||
}
|
||||
|
||||
@@ -105,7 +105,7 @@ public class EditorHelper {
|
||||
* @param editor The editor
|
||||
* @return The number of visible lines in the file
|
||||
*/
|
||||
public static int getVisualLineCount(@NotNull final Editor editor) {
|
||||
public static int getVisualLineCount(final @NotNull Editor editor) {
|
||||
int count = getLineCount(editor);
|
||||
return count == 0 ? 0 : logicalLineToVisualLine(editor, count - 1) + 1;
|
||||
}
|
||||
@@ -116,7 +116,7 @@ public class EditorHelper {
|
||||
* @param editor The editor
|
||||
* @return The file line count
|
||||
*/
|
||||
public static int getLineCount(@NotNull final Editor editor) {
|
||||
public static int getLineCount(final @NotNull Editor editor) {
|
||||
int len = editor.getDocument().getLineCount();
|
||||
if (editor.getDocument().getTextLength() > 0 &&
|
||||
editor.getDocument().getCharsSequence().charAt(editor.getDocument().getTextLength() - 1) == '\n') {
|
||||
@@ -132,7 +132,7 @@ public class EditorHelper {
|
||||
* @param editor The editor
|
||||
* @return The file's character count
|
||||
*/
|
||||
public static int getFileSize(@NotNull final Editor editor) {
|
||||
public static int getFileSize(final @NotNull Editor editor) {
|
||||
return getFileSize(editor, false);
|
||||
}
|
||||
|
||||
@@ -143,7 +143,7 @@ public class EditorHelper {
|
||||
* @param includeEndNewLine True include newline
|
||||
* @return The file's character count
|
||||
*/
|
||||
public static int getFileSize(@NotNull final Editor editor, final boolean includeEndNewLine) {
|
||||
public static int getFileSize(final @NotNull Editor editor, final boolean includeEndNewLine) {
|
||||
final int len = editor.getDocument().getTextLength();
|
||||
return includeEndNewLine || len == 0 || editor.getDocument().getCharsSequence().charAt(len - 1) != '\n' ? len : len - 1;
|
||||
}
|
||||
@@ -163,7 +163,7 @@ public class EditorHelper {
|
||||
* @param scrollOffset The value of the 'scrolloff' option
|
||||
* @return The scroll offset value to use
|
||||
*/
|
||||
public static int normalizeScrollOffset(@NotNull final Editor editor, int scrollOffset) {
|
||||
public static int normalizeScrollOffset(final @NotNull Editor editor, int scrollOffset) {
|
||||
return Math.min(scrollOffset, getApproximateScreenHeight(editor) / 2);
|
||||
}
|
||||
|
||||
@@ -176,7 +176,7 @@ public class EditorHelper {
|
||||
* @param editor The editor
|
||||
* @return The number of screen lines
|
||||
*/
|
||||
private static int getApproximateScreenHeight(@NotNull final Editor editor) {
|
||||
private static int getApproximateScreenHeight(final @NotNull Editor editor) {
|
||||
int lh = editor.getLineHeight();
|
||||
int height = editor.getScrollingModel().getVisibleArea().y +
|
||||
editor.getScrollingModel().getVisibleArea().height -
|
||||
@@ -190,7 +190,7 @@ public class EditorHelper {
|
||||
* @param editor The editor
|
||||
* @return The number of screen columns
|
||||
*/
|
||||
public static int getScreenWidth(@NotNull final Editor editor) {
|
||||
public static int getScreenWidth(final @NotNull Editor editor) {
|
||||
Rectangle rect = editor.getScrollingModel().getVisibleArea();
|
||||
Point pt = new Point(rect.width, 0);
|
||||
VisualPosition vp = editor.xyToVisualPosition(pt);
|
||||
@@ -204,7 +204,7 @@ public class EditorHelper {
|
||||
* @param editor The editor
|
||||
* @return The number of pixels
|
||||
*/
|
||||
public static int getColumnWidth(@NotNull final Editor editor) {
|
||||
public static int getColumnWidth(final @NotNull Editor editor) {
|
||||
Rectangle rect = editor.getScrollingModel().getVisibleArea();
|
||||
if (rect.width == 0) return 0;
|
||||
Point pt = new Point(rect.width, 0);
|
||||
@@ -220,7 +220,7 @@ public class EditorHelper {
|
||||
* @param editor The editor
|
||||
* @return The column number
|
||||
*/
|
||||
public static int getVisualColumnAtLeftOfScreen(@NotNull final Editor editor) {
|
||||
public static int getVisualColumnAtLeftOfScreen(final @NotNull Editor editor) {
|
||||
int cw = getColumnWidth(editor);
|
||||
if (cw == 0) return 0;
|
||||
return (editor.getScrollingModel().getHorizontalScrollOffset() + cw - 1) / cw;
|
||||
@@ -233,7 +233,7 @@ public class EditorHelper {
|
||||
* @param line The visual line number to convert
|
||||
* @return The logical line number
|
||||
*/
|
||||
public static int visualLineToLogicalLine(@NotNull final Editor editor, final int line) {
|
||||
public static int visualLineToLogicalLine(final @NotNull Editor editor, final int line) {
|
||||
int logicalLine = editor.visualToLogicalPosition(new VisualPosition(line, 0)).line;
|
||||
return normalizeLine(editor, logicalLine);
|
||||
}
|
||||
@@ -246,7 +246,7 @@ public class EditorHelper {
|
||||
* @param line The logical line number to convert
|
||||
* @return The visual line number
|
||||
*/
|
||||
public static int logicalLineToVisualLine(@NotNull final Editor editor, final int line) {
|
||||
public static int logicalLineToVisualLine(final @NotNull Editor editor, final int line) {
|
||||
if (editor instanceof EditorImpl) {
|
||||
// This is faster than simply calling Editor#logicalToVisualPosition
|
||||
return ((EditorImpl) editor).offsetToVisualLine(editor.getDocument().getLineStartOffset(line));
|
||||
@@ -254,7 +254,7 @@ public class EditorHelper {
|
||||
return editor.logicalToVisualPosition(new LogicalPosition(line, 0)).line;
|
||||
}
|
||||
|
||||
public static int getOffset(@NotNull final Editor editor, final int line, final int column) {
|
||||
public static int getOffset(final @NotNull Editor editor, final int line, final int column) {
|
||||
return editor.logicalPositionToOffset(new LogicalPosition(line, column));
|
||||
}
|
||||
|
||||
@@ -265,7 +265,7 @@ public class EditorHelper {
|
||||
* @param line The logical line to get the start offset for.
|
||||
* @return 0 if line is < 0, file size of line is bigger than file, else the start offset for the line
|
||||
*/
|
||||
public static int getLineStartOffset(@NotNull final Editor editor, final int line) {
|
||||
public static int getLineStartOffset(final @NotNull Editor editor, final int line) {
|
||||
if (line < 0) {
|
||||
return 0;
|
||||
}
|
||||
@@ -285,7 +285,7 @@ public class EditorHelper {
|
||||
* @param allowEnd True include newline
|
||||
* @return 0 if line is < 0, file size of line is bigger than file, else the end offset for the line
|
||||
*/
|
||||
public static int getLineEndOffset(@NotNull final Editor editor, final int line, final boolean allowEnd) {
|
||||
public static int getLineEndOffset(final @NotNull Editor editor, final int line, final boolean allowEnd) {
|
||||
if (line < 0) {
|
||||
return 0;
|
||||
}
|
||||
@@ -307,7 +307,7 @@ public class EditorHelper {
|
||||
* @param line The visual line number to normalize
|
||||
* @return The normalized visual line number
|
||||
*/
|
||||
public static int normalizeVisualLine(@NotNull final Editor editor, final int line) {
|
||||
public static int normalizeVisualLine(final @NotNull Editor editor, final int line) {
|
||||
return Math.max(0, Math.min(line, getVisualLineCount(editor) - 1));
|
||||
}
|
||||
|
||||
@@ -319,7 +319,7 @@ public class EditorHelper {
|
||||
* @param line The logical line number to normalize
|
||||
* @return The normalized logical line number
|
||||
*/
|
||||
public static int normalizeLine(@NotNull final Editor editor, final int line) {
|
||||
public static int normalizeLine(final @NotNull Editor editor, final int line) {
|
||||
return Math.max(0, Math.min(line, getLineCount(editor) - 1));
|
||||
}
|
||||
|
||||
@@ -333,7 +333,7 @@ public class EditorHelper {
|
||||
* @param allowEnd True if newline allowed
|
||||
* @return The normalized column number
|
||||
*/
|
||||
public static int normalizeVisualColumn(@NotNull final Editor editor, final int line, final int col, final boolean allowEnd) {
|
||||
public static int normalizeVisualColumn(final @NotNull Editor editor, final int line, final int col, final boolean allowEnd) {
|
||||
return Math.max(0, Math.min(col, getVisualLineLength(editor, line) - (allowEnd ? 0 : 1)));
|
||||
}
|
||||
|
||||
@@ -347,7 +347,7 @@ public class EditorHelper {
|
||||
* @param allowEnd True if newline allowed
|
||||
* @return The normalized column number
|
||||
*/
|
||||
public static int normalizeColumn(@NotNull final Editor editor, final int line, final int col, final boolean allowEnd) {
|
||||
public static int normalizeColumn(final @NotNull Editor editor, final int line, final int col, final boolean allowEnd) {
|
||||
return Math.min(Math.max(0, getLineLength(editor, line) - (allowEnd ? 0 : 1)), col);
|
||||
}
|
||||
|
||||
@@ -361,7 +361,7 @@ public class EditorHelper {
|
||||
* @param allowEnd true if the offset can be one past the last character on the line, false if not
|
||||
* @return The normalized column number
|
||||
*/
|
||||
public static int normalizeOffset(@NotNull final Editor editor, final int line, final int offset, final boolean allowEnd) {
|
||||
public static int normalizeOffset(final @NotNull Editor editor, final int line, final int offset, final boolean allowEnd) {
|
||||
if (getFileSize(editor, allowEnd) == 0) {
|
||||
return 0;
|
||||
}
|
||||
@@ -371,11 +371,11 @@ public class EditorHelper {
|
||||
return Math.max(Math.min(offset, max), min);
|
||||
}
|
||||
|
||||
public static int normalizeOffset(@NotNull final Editor editor, final int offset) {
|
||||
public static int normalizeOffset(final @NotNull Editor editor, final int offset) {
|
||||
return normalizeOffset(editor, offset, true);
|
||||
}
|
||||
|
||||
public static int normalizeOffset(@NotNull final Editor editor, int offset, final boolean allowEnd) {
|
||||
public static int normalizeOffset(final @NotNull Editor editor, int offset, final boolean allowEnd) {
|
||||
if (offset <= 0) {
|
||||
offset = 0;
|
||||
}
|
||||
@@ -388,11 +388,11 @@ public class EditorHelper {
|
||||
}
|
||||
|
||||
|
||||
public static int getLeadingCharacterOffset(@NotNull final Editor editor, final int line) {
|
||||
public static int getLeadingCharacterOffset(final @NotNull Editor editor, final int line) {
|
||||
return getLeadingCharacterOffset(editor, line, 0);
|
||||
}
|
||||
|
||||
public static int getLeadingCharacterOffset(@NotNull final Editor editor, final int line, final int col) {
|
||||
public static int getLeadingCharacterOffset(final @NotNull Editor editor, final int line, final int col) {
|
||||
int start = getLineStartOffset(editor, line) + col;
|
||||
int end = getLineEndOffset(editor, line, true);
|
||||
CharSequence chars = editor.getDocument().getCharsSequence();
|
||||
@@ -411,8 +411,7 @@ public class EditorHelper {
|
||||
return pos;
|
||||
}
|
||||
|
||||
@NotNull
|
||||
public static String getLeadingWhitespace(@NotNull final Editor editor, final int line) {
|
||||
public static @NotNull String getLeadingWhitespace(final @NotNull Editor editor, final int line) {
|
||||
int start = getLineStartOffset(editor, line);
|
||||
int end = getLeadingCharacterOffset(editor, line);
|
||||
|
||||
@@ -425,8 +424,7 @@ public class EditorHelper {
|
||||
* @param file The virtual file get the editor for
|
||||
* @return The matching editor or null if no match was found
|
||||
*/
|
||||
@Nullable
|
||||
public static Editor getEditor(@Nullable final VirtualFile file) {
|
||||
public static @Nullable Editor getEditor(final @Nullable VirtualFile file) {
|
||||
if (file == null) {
|
||||
return null;
|
||||
}
|
||||
@@ -450,7 +448,7 @@ public class EditorHelper {
|
||||
* @param pos The visual position to convert
|
||||
* @return The file offset of the visual position
|
||||
*/
|
||||
public static int visualPositionToOffset(@NotNull final Editor editor, @NotNull final VisualPosition pos) {
|
||||
public static int visualPositionToOffset(final @NotNull Editor editor, final @NotNull VisualPosition pos) {
|
||||
return editor.logicalPositionToOffset(editor.visualToLogicalPosition(pos));
|
||||
}
|
||||
|
||||
@@ -462,15 +460,13 @@ public class EditorHelper {
|
||||
* @param end The ending offset (exclusive)
|
||||
* @return The string, never null but empty if start == end
|
||||
*/
|
||||
@NotNull
|
||||
public static String getText(@NotNull final Editor editor, final int start, final int end) {
|
||||
public static @NotNull String getText(final @NotNull Editor editor, final int start, final int end) {
|
||||
if (start == end) return "";
|
||||
final CharSequence documentChars = editor.getDocument().getCharsSequence();
|
||||
return documentChars.subSequence(normalizeOffset(editor, start), normalizeOffset(editor, end)).toString();
|
||||
}
|
||||
|
||||
@NotNull
|
||||
public static String getText(@NotNull final Editor editor, @NotNull final TextRange range) {
|
||||
public static @NotNull String getText(final @NotNull Editor editor, final @NotNull TextRange range) {
|
||||
int len = range.size();
|
||||
if (len == 1) {
|
||||
return getText(editor, range.getStartOffset(), range.getEndOffset());
|
||||
@@ -505,7 +501,7 @@ public class EditorHelper {
|
||||
* @param offset The offset within the line
|
||||
* @return The offset of the line start
|
||||
*/
|
||||
public static int getLineStartForOffset(@NotNull final Editor editor, final int offset) {
|
||||
public static int getLineStartForOffset(final @NotNull Editor editor, final int offset) {
|
||||
LogicalPosition pos = editor.offsetToLogicalPosition(normalizeOffset(editor, offset));
|
||||
return editor.getDocument().getLineStartOffset(pos.line);
|
||||
}
|
||||
@@ -517,12 +513,12 @@ public class EditorHelper {
|
||||
* @param offset The offset within the line
|
||||
* @return The offset of the line end
|
||||
*/
|
||||
public static int getLineEndForOffset(@NotNull final Editor editor, final int offset) {
|
||||
public static int getLineEndForOffset(final @NotNull Editor editor, final int offset) {
|
||||
LogicalPosition pos = editor.offsetToLogicalPosition(normalizeOffset(editor, offset));
|
||||
return editor.getDocument().getLineEndOffset(pos.line);
|
||||
}
|
||||
|
||||
private static int getLineCharCount(@NotNull final Editor editor, final int line) {
|
||||
private static int getLineCharCount(final @NotNull Editor editor, final int line) {
|
||||
return getLineEndOffset(editor, line, true) - getLineStartOffset(editor, line);
|
||||
}
|
||||
|
||||
@@ -533,18 +529,16 @@ public class EditorHelper {
|
||||
* @param line The logical line to get the text for
|
||||
* @return The requested line
|
||||
*/
|
||||
@NotNull
|
||||
public static String getLineText(@NotNull final Editor editor, final int line) {
|
||||
public static @NotNull String getLineText(final @NotNull Editor editor, final int line) {
|
||||
return getText(editor, getLineStartOffset(editor, line), getLineEndOffset(editor, line, true));
|
||||
}
|
||||
|
||||
@NotNull
|
||||
public static CharBuffer getLineBuffer(@NotNull final Editor editor, final int line) {
|
||||
public static @NotNull CharBuffer getLineBuffer(final @NotNull Editor editor, final int line) {
|
||||
int start = getLineStartOffset(editor, line);
|
||||
return CharBuffer.wrap(editor.getDocument().getCharsSequence(), start, start + getLineCharCount(editor, line));
|
||||
}
|
||||
|
||||
public static boolean isLineEmpty(@NotNull final Editor editor, final int line, final boolean allowBlanks) {
|
||||
public static boolean isLineEmpty(final @NotNull Editor editor, final int line, final boolean allowBlanks) {
|
||||
CharSequence chars = editor.getDocument().getCharsSequence();
|
||||
if (chars.length() == 0) return true;
|
||||
int offset = getLineStartOffset(editor, line);
|
||||
@@ -565,8 +559,7 @@ public class EditorHelper {
|
||||
return false;
|
||||
}
|
||||
|
||||
@NotNull
|
||||
public static String pad(@NotNull final Editor editor, @NotNull DataContext context, int line, final int to) {
|
||||
public static @NotNull String pad(final @NotNull Editor editor, @NotNull DataContext context, int line, final int to) {
|
||||
final int len = getLineLength(editor, line);
|
||||
if(len >= to) return "";
|
||||
|
||||
@@ -579,8 +572,7 @@ public class EditorHelper {
|
||||
*
|
||||
* @param editor The editor from which the carets are taken
|
||||
*/
|
||||
@NotNull
|
||||
public static List<Caret> getOrderedCaretsList(@NotNull Editor editor) {
|
||||
public static @NotNull List<Caret> getOrderedCaretsList(@NotNull Editor editor) {
|
||||
@NotNull List<Caret> carets = editor.getCaretModel().getAllCarets();
|
||||
|
||||
carets.sort(Comparator.comparingInt(Caret::getOffset));
|
||||
@@ -603,7 +595,7 @@ public class EditorHelper {
|
||||
* @param editor The editor to scroll
|
||||
* @param visualLine The visual line to scroll to the current caret location
|
||||
*/
|
||||
public static void scrollVisualLineToCaretLocation(@NotNull final Editor editor, int visualLine) {
|
||||
public static void scrollVisualLineToCaretLocation(final @NotNull Editor editor, int visualLine) {
|
||||
final ScrollingModel scrollingModel = editor.getScrollingModel();
|
||||
final Rectangle visibleArea = scrollingModel.getVisibleArea();
|
||||
final int caretScreenOffset = editor.visualLineToY(editor.getCaretModel().getVisualPosition().line) - visibleArea.y;
|
||||
@@ -634,7 +626,7 @@ public class EditorHelper {
|
||||
* @param visualLine The visual line to place at the top of the current window
|
||||
* @return Returns true if the window was moved
|
||||
*/
|
||||
public static boolean scrollVisualLineToTopOfScreen(@NotNull final Editor editor, int visualLine) {
|
||||
public static boolean scrollVisualLineToTopOfScreen(final @NotNull Editor editor, int visualLine) {
|
||||
final ScrollingModel scrollingModel = editor.getScrollingModel();
|
||||
int inlayHeight = getHeightOfVisualLineInlays(editor, visualLine, true);
|
||||
int y = editor.visualLineToY(visualLine) - inlayHeight;
|
||||
@@ -697,7 +689,7 @@ public class EditorHelper {
|
||||
* @param pages The number of pages to scroll. Positive is scroll down (lines move up). Negative is scroll up.
|
||||
* @return The visual line to place the caret on. -1 if the page wasn't scrolled at all.
|
||||
*/
|
||||
public static int scrollFullPage(@NotNull final Editor editor, int pages) {
|
||||
public static int scrollFullPage(final @NotNull Editor editor, int pages) {
|
||||
if (pages > 0) {
|
||||
return scrollFullPageDown(editor, pages);
|
||||
}
|
||||
@@ -707,7 +699,7 @@ public class EditorHelper {
|
||||
return -1; // visual lines are 1-based
|
||||
}
|
||||
|
||||
public static int lastColumnForLine(@NotNull final Editor editor, int line, boolean allowEnd) {
|
||||
public static int lastColumnForLine(final @NotNull Editor editor, int line, boolean allowEnd) {
|
||||
return editor.offsetToVisualPosition(EditorHelper.getLineEndOffset(editor, line, allowEnd)).column;
|
||||
}
|
||||
|
||||
@@ -741,7 +733,7 @@ public class EditorHelper {
|
||||
UserDataManager.setVimLastColumn(caret, targetColumn);
|
||||
}
|
||||
|
||||
private static int scrollFullPageDown(@NotNull final Editor editor, int pages) {
|
||||
private static int scrollFullPageDown(final @NotNull Editor editor, int pages) {
|
||||
final Rectangle visibleArea = editor.getScrollingModel().getVisibleArea();
|
||||
final int lineCount = getVisualLineCount(editor);
|
||||
|
||||
@@ -784,7 +776,7 @@ public class EditorHelper {
|
||||
return caretLine;
|
||||
}
|
||||
|
||||
private static int scrollFullPageUp(@NotNull final Editor editor, int pages) {
|
||||
private static int scrollFullPageUp(final @NotNull Editor editor, int pages) {
|
||||
final Rectangle visibleArea = editor.getScrollingModel().getVisibleArea();
|
||||
final int lineHeight = editor.getLineHeight();
|
||||
|
||||
@@ -813,7 +805,7 @@ public class EditorHelper {
|
||||
return caretLine;
|
||||
}
|
||||
|
||||
private static int getFullVisualLine(@NotNull final Editor editor, int y, int topBound, int bottomBound) {
|
||||
private static int getFullVisualLine(final @NotNull Editor editor, int y, int topBound, int bottomBound) {
|
||||
int line = editor.yToVisualLine(y);
|
||||
int yActual = editor.visualLineToY(line);
|
||||
if (yActual < topBound) {
|
||||
@@ -825,7 +817,7 @@ public class EditorHelper {
|
||||
return line;
|
||||
}
|
||||
|
||||
private static int getHeightOfVisualLineInlays(@NotNull final Editor editor, int visualLine, boolean above) {
|
||||
private static int getHeightOfVisualLineInlays(final @NotNull Editor editor, int visualLine, boolean above) {
|
||||
InlayModel inlayModel = editor.getInlayModel();
|
||||
List<Inlay> inlays = inlayModel.getBlockElementsForVisualLine(visualLine, above);
|
||||
int inlayHeight = 0;
|
||||
@@ -841,8 +833,7 @@ public class EditorHelper {
|
||||
* @param editor The editor
|
||||
* @return The virtual file for the editor
|
||||
*/
|
||||
@Nullable
|
||||
public static VirtualFile getVirtualFile(@NotNull Editor editor) {
|
||||
public static @Nullable VirtualFile getVirtualFile(@NotNull Editor editor) {
|
||||
return FileDocumentManager.getInstance().getFile(editor.getDocument());
|
||||
}
|
||||
|
||||
|
@@ -31,15 +31,13 @@ import java.io.InputStreamReader;
|
||||
*/
|
||||
public class MacKeyRepeat {
|
||||
public static final String FMT = "defaults %s -globalDomain ApplePressAndHoldEnabled";
|
||||
@NotNull private static final MacKeyRepeat INSTANCE = new MacKeyRepeat();
|
||||
private static final @NotNull MacKeyRepeat INSTANCE = new MacKeyRepeat();
|
||||
|
||||
@NotNull
|
||||
public static MacKeyRepeat getInstance() {
|
||||
public static @NotNull MacKeyRepeat getInstance() {
|
||||
return INSTANCE;
|
||||
}
|
||||
|
||||
@Nullable
|
||||
public Boolean isEnabled() {
|
||||
public @Nullable Boolean isEnabled() {
|
||||
final String command = String.format(FMT, "read");
|
||||
try {
|
||||
final Process process = Runtime.getRuntime().exec(command);
|
||||
@@ -77,8 +75,7 @@ public class MacKeyRepeat {
|
||||
}
|
||||
}
|
||||
|
||||
@NotNull
|
||||
private static String read(@NotNull InputStream stream) throws IOException {
|
||||
private static @NotNull String read(@NotNull InputStream stream) throws IOException {
|
||||
return CharStreams.toString(new InputStreamReader(stream));
|
||||
}
|
||||
}
|
||||
|
@@ -30,7 +30,7 @@ import java.util.ResourceBundle;
|
||||
|
||||
|
||||
public class MessageHelper {
|
||||
@Nullable private static Reference<ResourceBundle> ourBundle;
|
||||
private static @Nullable Reference<ResourceBundle> ourBundle;
|
||||
|
||||
@NonNls
|
||||
private static final String BUNDLE = "messages";
|
||||
@@ -38,21 +38,18 @@ public class MessageHelper {
|
||||
private MessageHelper() {
|
||||
}
|
||||
|
||||
@NotNull
|
||||
public static String message(@NotNull @PropertyKey(resourceBundle = BUNDLE)String key, Object... params) {
|
||||
public static @NotNull String message(@NotNull @PropertyKey(resourceBundle = BUNDLE)String key, Object... params) {
|
||||
return CommonBundle.message(getBundle(), key, params);
|
||||
}
|
||||
|
||||
/*
|
||||
* This method added for jruby access
|
||||
*/
|
||||
@NotNull
|
||||
public static String message(@NotNull @PropertyKey(resourceBundle = BUNDLE)String key) {
|
||||
public static @NotNull String message(@NotNull @PropertyKey(resourceBundle = BUNDLE)String key) {
|
||||
return CommonBundle.message(getBundle(), key);
|
||||
}
|
||||
|
||||
@NotNull
|
||||
protected static ResourceBundle getBundle() {
|
||||
protected static @NotNull ResourceBundle getBundle() {
|
||||
ResourceBundle bundle = null;
|
||||
if (ourBundle != null) bundle = ourBundle.get();
|
||||
if (bundle == null) {
|
||||
|
@@ -95,6 +95,7 @@ public class PsiHelper {
|
||||
if (element.getLanguage().getID().equals("JAVA")) {
|
||||
// HACK: for Java classes and methods, we want to jump to the opening brace
|
||||
int textOffset = element.getTextOffset();
|
||||
// TODO: Try to get rid of `getText()` because it takes a lot of time to calculate the string
|
||||
int braceIndex = element.getText().indexOf('{', textOffset - offset);
|
||||
if (braceIndex >= 0) {
|
||||
offset += braceIndex;
|
||||
@@ -113,8 +114,7 @@ public class PsiHelper {
|
||||
}
|
||||
}
|
||||
|
||||
@Nullable
|
||||
public static PsiFile getFile(@NotNull Editor editor) {
|
||||
public static @Nullable PsiFile getFile(@NotNull Editor editor) {
|
||||
VirtualFile vf = EditorHelper.getVirtualFile(editor);
|
||||
if (vf != null) {
|
||||
Project proj = editor.getProject();
|
||||
|
@@ -1,76 +0,0 @@
|
||||
/*
|
||||
* IdeaVim - Vim emulator for IDEs based on the IntelliJ platform
|
||||
* Copyright (C) 2003-2020 The IdeaVim authors
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 2 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package com.maddyhome.idea.vim.helper;
|
||||
|
||||
import com.intellij.openapi.application.ApplicationManager;
|
||||
import com.intellij.openapi.command.CommandProcessor;
|
||||
import com.intellij.openapi.diagnostic.Logger;
|
||||
import com.intellij.openapi.project.Project;
|
||||
import com.maddyhome.idea.vim.KeyHandler;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
|
||||
/**
|
||||
* This provides some helper methods to run code as a command and an application write action
|
||||
*/
|
||||
public class RunnableHelper {
|
||||
private static final Logger logger = Logger.getInstance(KeyHandler.class.getName());
|
||||
|
||||
private RunnableHelper() {}
|
||||
|
||||
public static void runReadCommand(@Nullable Project project, @NotNull Runnable cmd, @Nullable String name, @Nullable Object groupId) {
|
||||
if (logger.isDebugEnabled()) {
|
||||
logger.debug("Run read command: " + name);
|
||||
}
|
||||
CommandProcessor.getInstance().executeCommand(project, new ReadAction(cmd), name, groupId);
|
||||
}
|
||||
|
||||
public static void runWriteCommand(@Nullable Project project, @NotNull Runnable cmd, @Nullable String name, @Nullable Object groupId) {
|
||||
if (logger.isDebugEnabled()) {
|
||||
logger.debug("Run write command " + name);
|
||||
}
|
||||
CommandProcessor.getInstance().executeCommand(project, new WriteAction(cmd), name, groupId);
|
||||
}
|
||||
|
||||
static class ReadAction implements Runnable {
|
||||
@NotNull private final Runnable cmd;
|
||||
|
||||
ReadAction(@NotNull Runnable cmd) {
|
||||
this.cmd = cmd;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void run() {
|
||||
ApplicationManager.getApplication().runReadAction(cmd);
|
||||
}
|
||||
}
|
||||
|
||||
static class WriteAction implements Runnable {
|
||||
@NotNull private final Runnable cmd;
|
||||
|
||||
WriteAction(@NotNull Runnable cmd) {
|
||||
this.cmd = cmd;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void run() {
|
||||
ApplicationManager.getApplication().runWriteAction(cmd);
|
||||
}
|
||||
}
|
||||
}
|
43
src/com/maddyhome/idea/vim/helper/RunnableHelper.kt
Normal file
43
src/com/maddyhome/idea/vim/helper/RunnableHelper.kt
Normal file
@@ -0,0 +1,43 @@
|
||||
/*
|
||||
* IdeaVim - Vim emulator for IDEs based on the IntelliJ platform
|
||||
* Copyright (C) 2003-2020 The IdeaVim authors
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 2 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
package com.maddyhome.idea.vim.helper
|
||||
|
||||
import com.intellij.openapi.application.ApplicationManager
|
||||
import com.intellij.openapi.command.CommandProcessor
|
||||
import com.intellij.openapi.diagnostic.debug
|
||||
import com.intellij.openapi.diagnostic.logger
|
||||
import com.intellij.openapi.project.Project
|
||||
|
||||
/**
|
||||
* This provides some helper methods to run code as a command and an application write action
|
||||
*/
|
||||
object RunnableHelper {
|
||||
private val logger = logger<RunnableHelper>()
|
||||
|
||||
@JvmStatic
|
||||
fun runReadCommand(project: Project?, cmd: Runnable, name: String?, groupId: Any?) {
|
||||
logger.debug { "Run read command: $name" }
|
||||
CommandProcessor.getInstance().executeCommand(project, { ApplicationManager.getApplication().runReadAction(cmd) }, name, groupId)
|
||||
}
|
||||
|
||||
@JvmStatic
|
||||
fun runWriteCommand(project: Project?, cmd: Runnable, name: String?, groupId: Any?) {
|
||||
logger.debug { "Run write command: $name" }
|
||||
CommandProcessor.getInstance().executeCommand(project, { ApplicationManager.getApplication().runWriteAction(cmd) }, name, groupId)
|
||||
}
|
||||
}
|
@@ -37,12 +37,13 @@ import org.jetbrains.annotations.Contract;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.Stack;
|
||||
import java.util.*;
|
||||
import java.util.function.Function;
|
||||
import java.util.regex.Matcher;
|
||||
import java.util.regex.Pattern;
|
||||
|
||||
import static com.maddyhome.idea.vim.helper.SearchHelperKtKt.checkInString;
|
||||
|
||||
/**
|
||||
* Helper methods for searching text
|
||||
*/
|
||||
@@ -98,16 +99,15 @@ public class SearchHelper {
|
||||
int pos = caret.getOffset();
|
||||
int loc = blockChars.indexOf(type);
|
||||
// What direction should we go now (-1 is backward, 1 is forward)
|
||||
int dir = loc % 2 == 0 ? -1 : 1;
|
||||
Direction dir = loc % 2 == 0 ? Direction.BACK : Direction.FORWARD;
|
||||
// Which character did we find and which should we now search for
|
||||
char match = blockChars.charAt(loc);
|
||||
char found = blockChars.charAt(loc - dir);
|
||||
char found = blockChars.charAt(loc - dir.toInt());
|
||||
|
||||
return findBlockLocation(chars, found, match, dir, pos, count, false);
|
||||
}
|
||||
|
||||
@Nullable
|
||||
public static TextRange findBlockRange(@NotNull Editor editor, @NotNull Caret caret, char type, int count,
|
||||
public static @Nullable TextRange findBlockRange(@NotNull Editor editor, @NotNull Caret caret, char type, int count,
|
||||
boolean isOuter) {
|
||||
CharSequence chars = editor.getDocument().getCharsSequence();
|
||||
int pos = caret.getOffset();
|
||||
@@ -153,10 +153,10 @@ public class SearchHelper {
|
||||
int endOffset = quoteRange.getEndOffset();
|
||||
CharSequence subSequence = chars.subSequence(startOffset, endOffset);
|
||||
int inQuotePos = pos - startOffset;
|
||||
int inQuoteStart = findBlockLocation(subSequence, close, type, -1, inQuotePos, count, false);
|
||||
int inQuoteStart = findBlockLocation(subSequence, close, type, Direction.BACK, inQuotePos, count, false);
|
||||
if (inQuoteStart != -1) {
|
||||
startPosInStringFound = true;
|
||||
int inQuoteEnd = findBlockLocation(subSequence, type, close, 1, inQuoteStart, 1, false);
|
||||
int inQuoteEnd = findBlockLocation(subSequence, type, close, Direction.FORWARD, inQuoteStart, 1, false);
|
||||
if (inQuoteEnd != -1) {
|
||||
bstart = inQuoteStart + startOffset;
|
||||
bend = inQuoteEnd + startOffset;
|
||||
@@ -166,9 +166,9 @@ public class SearchHelper {
|
||||
}
|
||||
|
||||
if (!startPosInStringFound) {
|
||||
bstart = findBlockLocation(chars, close, type, -1, pos, count, false);
|
||||
bstart = findBlockLocation(chars, close, type, Direction.BACK, pos, count, false);
|
||||
if (bstart != -1) {
|
||||
bend = findBlockLocation(chars, type, close, 1, bstart, 1, false);
|
||||
bend = findBlockLocation(chars, type, close, Direction.FORWARD, bstart, 1, false);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -206,6 +206,7 @@ public class SearchHelper {
|
||||
private static int findMatchingBlockCommentPair(@NotNull PsiComment comment, int pos, @Nullable String prefix,
|
||||
@Nullable String suffix) {
|
||||
if (prefix != null && suffix != null) {
|
||||
// TODO: Try to get rid of `getText()` because it takes a lot of time to calculate the string
|
||||
final String commentText = comment.getText();
|
||||
if (commentText.startsWith(prefix) && commentText.endsWith(suffix)) {
|
||||
final int endOffset = comment.getTextOffset() + comment.getTextLength();
|
||||
@@ -281,10 +282,10 @@ public class SearchHelper {
|
||||
// If we found one ...
|
||||
if (loc >= 0) {
|
||||
// What direction should we go now (-1 is backward, 1 is forward)
|
||||
int dir = loc % 2 == 0 ? 1 : -1;
|
||||
Direction dir = loc % 2 == 0 ? Direction.FORWARD : Direction.BACK;
|
||||
// Which character did we find and which should we now search for
|
||||
char found = getPairChars().charAt(loc);
|
||||
char match = getPairChars().charAt(loc + dir);
|
||||
char match = getPairChars().charAt(loc + dir.toInt());
|
||||
res = findBlockLocation(chars, found, match, dir, pos, 1, true);
|
||||
}
|
||||
|
||||
@@ -308,21 +309,29 @@ public class SearchHelper {
|
||||
private static int findBlockLocation(@NotNull CharSequence chars,
|
||||
char found,
|
||||
char match,
|
||||
int dir,
|
||||
@NotNull Direction dir,
|
||||
int pos,
|
||||
int cnt,
|
||||
boolean allowInString) {
|
||||
int res = -1;
|
||||
final int inCheckPos = dir < 0 && pos > 0 ? pos - 1 : pos;
|
||||
int initialPos = pos;
|
||||
Function<Integer, Integer> inCheckPosF = x -> dir == Direction.BACK && x > 0 ? x - 1 : x + 1;
|
||||
final int inCheckPos = inCheckPosF.apply(pos);
|
||||
boolean inString = checkInString(chars, inCheckPos, true);
|
||||
boolean initialInString = inString;
|
||||
boolean inChar = checkInString(chars, inCheckPos, false);
|
||||
boolean initial = true;
|
||||
int stack = 0;
|
||||
// Search to start or end of file, as appropriate
|
||||
Set<Character> charsToSearch = new HashSet<>(Arrays.asList('\'', '"', '\n', match, found));
|
||||
while (pos >= 0 && pos < chars.length() && cnt > 0) {
|
||||
Pair<Character, Integer> ci = findPositionOfFirstCharacter(chars, pos, charsToSearch, false, dir);
|
||||
if (ci == null) {
|
||||
return -1;
|
||||
}
|
||||
Character c = ci.first;
|
||||
pos = ci.second;
|
||||
// If we found a match and we're not in a string...
|
||||
if (chars.charAt(pos) == match && (allowInString ? initialInString == inString : !inString) && !inChar) {
|
||||
if (c == match && (allowInString ? initialInString == inString : !inString) && !inChar) {
|
||||
// We found our match
|
||||
if (stack == 0) {
|
||||
res = pos;
|
||||
@@ -334,26 +343,24 @@ public class SearchHelper {
|
||||
}
|
||||
}
|
||||
// End of line - mark not in a string any more (in case we started in the middle of one
|
||||
else if (chars.charAt(pos) == '\n') {
|
||||
else if (c == '\n') {
|
||||
inString = false;
|
||||
inChar = false;
|
||||
}
|
||||
else if (!initial) {
|
||||
else if (pos != initialPos) {
|
||||
// We found another character like our original - belongs to another pair
|
||||
if (!inString && !inChar && chars.charAt(pos) == found) {
|
||||
if (!inString && !inChar && c == found) {
|
||||
stack++;
|
||||
}
|
||||
// We found the start/end of a string
|
||||
else if (!inChar && isQuoteWithoutEscape(chars, pos, '"')) {
|
||||
inString = !inString;
|
||||
else if (!inChar) {
|
||||
inString = checkInString(chars, inCheckPosF.apply(pos), true);
|
||||
}
|
||||
else if (!inString && isQuoteWithoutEscape(chars, pos, '\'')) {
|
||||
inChar = !inChar;
|
||||
else if (!inString) {
|
||||
inChar = checkInString(chars, inCheckPosF.apply(pos), false);
|
||||
}
|
||||
}
|
||||
|
||||
pos += dir;
|
||||
initial = false;
|
||||
pos += dir.toInt();
|
||||
}
|
||||
|
||||
return res;
|
||||
@@ -366,18 +373,13 @@ public class SearchHelper {
|
||||
if (chars.charAt(pos) != quote) return false;
|
||||
|
||||
int backslashCounter = 0;
|
||||
while (pos-- > 0) {
|
||||
if (chars.charAt(pos) == '\\') {
|
||||
while (pos-- > 0 && chars.charAt(pos) == '\\') {
|
||||
backslashCounter++;
|
||||
}
|
||||
else {
|
||||
break;
|
||||
}
|
||||
}
|
||||
return backslashCounter % 2 == 0;
|
||||
}
|
||||
|
||||
private enum Direction {
|
||||
public enum Direction {
|
||||
BACK(-1), FORWARD(1);
|
||||
|
||||
private final int value;
|
||||
@@ -386,7 +388,7 @@ public class SearchHelper {
|
||||
value = i;
|
||||
}
|
||||
|
||||
private int toInt() {
|
||||
public int toInt() {
|
||||
return value;
|
||||
}
|
||||
}
|
||||
@@ -419,10 +421,28 @@ public class SearchHelper {
|
||||
return cnt;
|
||||
}
|
||||
|
||||
public static @Nullable Pair<Character, Integer> findPositionOfFirstCharacter(
|
||||
@NotNull CharSequence chars,
|
||||
int pos,
|
||||
final Set<Character> needles,
|
||||
boolean searchEscaped,
|
||||
@NotNull Direction direction
|
||||
) {
|
||||
int dir = direction.toInt();
|
||||
while (pos >= 0 && pos < chars.length()) {
|
||||
final char c = chars.charAt(pos);
|
||||
if (needles.contains(c) && (pos == 0 || searchEscaped || isQuoteWithoutEscape(chars, pos, c))) {
|
||||
return Pair.create(c, pos);
|
||||
}
|
||||
pos += dir;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
private static int findCharacterPosition(@NotNull CharSequence chars, int pos, final char c, boolean currentLineOnly,
|
||||
boolean searchEscaped, @NotNull Direction direction) {
|
||||
while (pos >= 0 && pos < chars.length() && (!currentLineOnly || chars.charAt(pos) != '\n')) {
|
||||
if (chars.charAt(pos) == c && (pos == 0 || searchEscaped || chars.charAt(pos - 1) != '\\')) {
|
||||
if (chars.charAt(pos) == c && (pos == 0 || searchEscaped || isQuoteWithoutEscape(chars, pos, c))) {
|
||||
return pos;
|
||||
}
|
||||
pos += direction.toInt();
|
||||
@@ -441,8 +461,7 @@ public class SearchHelper {
|
||||
}
|
||||
|
||||
|
||||
@Nullable
|
||||
public static TextRange findBlockTagRange(@NotNull Editor editor, @NotNull Caret caret, int count, boolean isOuter) {
|
||||
public static @Nullable TextRange findBlockTagRange(@NotNull Editor editor, @NotNull Caret caret, int count, boolean isOuter) {
|
||||
final int position = caret.getOffset();
|
||||
final CharSequence sequence = editor.getDocument().getCharsSequence();
|
||||
|
||||
@@ -519,7 +538,7 @@ public class SearchHelper {
|
||||
/**
|
||||
* Returns true if there is a html at the given position. Ignores tags with a trailing slash like <aaa/>.
|
||||
*/
|
||||
private static boolean isInHTMLTag(@NotNull final CharSequence sequence, final int position, final boolean isEndtag) {
|
||||
private static boolean isInHTMLTag(final @NotNull CharSequence sequence, final int position, final boolean isEndtag) {
|
||||
int openingBracket = -1;
|
||||
for (int i = position; i >= 0 && i < sequence.length(); i--) {
|
||||
if (sequence.charAt(i) == '<') {
|
||||
@@ -551,8 +570,7 @@ public class SearchHelper {
|
||||
return closingBracket != -1 && sequence.charAt(closingBracket - 1) != '/';
|
||||
}
|
||||
|
||||
@Nullable
|
||||
private static Pair<TextRange,String> findUnmatchedClosingTag(@NotNull final CharSequence sequence, final int position, int count) {
|
||||
private static @Nullable Pair<TextRange,String> findUnmatchedClosingTag(final @NotNull CharSequence sequence, final int position, int count) {
|
||||
// The tag name may contain any characters except slashes, whitespace and '>'
|
||||
final String tagNamePattern = "([^/\\s>]+)";
|
||||
// An opening tag consists of '<' followed by a tag name, optionally some additional text after whitespace and a '>'
|
||||
@@ -588,8 +606,7 @@ public class SearchHelper {
|
||||
return null;
|
||||
}
|
||||
|
||||
@Nullable
|
||||
private static TextRange findUnmatchedOpeningTag(@NotNull CharSequence sequence, int position, @NotNull String tagName) {
|
||||
private static @Nullable TextRange findUnmatchedOpeningTag(@NotNull CharSequence sequence, int position, @NotNull String tagName) {
|
||||
final String quotedTagName = Pattern.quote(tagName);
|
||||
final String patternString = "(</%s>)" // match closing tags
|
||||
+ "|(<%s" // or opening tags starting with tagName
|
||||
@@ -619,8 +636,7 @@ public class SearchHelper {
|
||||
}
|
||||
|
||||
|
||||
@Nullable
|
||||
public static TextRange findBlockQuoteInLineRange(@NotNull Editor editor, @NotNull Caret caret, char quote,
|
||||
public static @Nullable TextRange findBlockQuoteInLineRange(@NotNull Editor editor, @NotNull Caret caret, char quote,
|
||||
boolean isOuter) {
|
||||
final CharSequence chars = editor.getDocument().getCharsSequence();
|
||||
final int pos = caret.getOffset();
|
||||
@@ -664,27 +680,6 @@ public class SearchHelper {
|
||||
return new TextRange(start, end + 1);
|
||||
}
|
||||
|
||||
private static boolean checkInString(@NotNull CharSequence chars, int pos, boolean str) {
|
||||
if (chars.length() == 0) return false;
|
||||
int offset = pos;
|
||||
while (offset > 0 && chars.charAt(offset) != '\n') {
|
||||
offset--;
|
||||
}
|
||||
|
||||
boolean inString = false;
|
||||
boolean inChar = false;
|
||||
for (int i = offset; i <= pos; i++) {
|
||||
if (!inChar && isQuoteWithoutEscape(chars, i, '"')) {
|
||||
inString = !inString;
|
||||
}
|
||||
else if (!inString && isQuoteWithoutEscape(chars, i, '\'')) {
|
||||
inChar = !inChar;
|
||||
}
|
||||
}
|
||||
|
||||
return str ? inString : inChar;
|
||||
}
|
||||
|
||||
public static int findNextCamelStart(@NotNull Editor editor, @NotNull Caret caret, int count) {
|
||||
CharSequence chars = editor.getDocument().getCharsSequence();
|
||||
int pos = caret.getOffset();
|
||||
@@ -779,8 +774,7 @@ public class SearchHelper {
|
||||
/**
|
||||
* This counts all the words in the file.
|
||||
*/
|
||||
@NotNull
|
||||
public static CountPosition countWords(@NotNull Editor editor) {
|
||||
public static @NotNull CountPosition countWords(@NotNull Editor editor) {
|
||||
int size = EditorHelper.getFileSize(editor);
|
||||
|
||||
return countWords(editor, 0, size);
|
||||
@@ -789,16 +783,14 @@ public class SearchHelper {
|
||||
/**
|
||||
* This counts all the words in the file.
|
||||
*/
|
||||
@NotNull
|
||||
public static CountPosition countWords(@NotNull Editor editor, int start, int end) {
|
||||
public static @NotNull CountPosition countWords(@NotNull Editor editor, int start, int end) {
|
||||
CharSequence chars = editor.getDocument().getCharsSequence();
|
||||
int offset = editor.getCaretModel().getOffset();
|
||||
|
||||
return countWords(chars, start, end, offset);
|
||||
}
|
||||
|
||||
@NotNull
|
||||
public static CountPosition countWords(@NotNull CharSequence chars, int start, int end, int offset) {
|
||||
public static @NotNull CountPosition countWords(@NotNull CharSequence chars, int start, int end, int offset) {
|
||||
int count = 1;
|
||||
int position = 0;
|
||||
int last = -1;
|
||||
@@ -924,8 +916,7 @@ public class SearchHelper {
|
||||
return res;
|
||||
}
|
||||
|
||||
@NotNull
|
||||
public static List<TextRange> findNumbersInRange(@NotNull final Editor editor, @NotNull TextRange textRange,
|
||||
public static @NotNull List<TextRange> findNumbersInRange(final @NotNull Editor editor, @NotNull TextRange textRange,
|
||||
final boolean alpha, final boolean hex, final boolean octal) {
|
||||
List<TextRange> result = new ArrayList<>();
|
||||
int firstLine = editor.offsetToLogicalPosition(textRange.getStartOffset()).line;
|
||||
@@ -950,8 +941,7 @@ public class SearchHelper {
|
||||
return result;
|
||||
}
|
||||
|
||||
@Nullable
|
||||
public static TextRange findNumberUnderCursor(@NotNull final Editor editor, @NotNull Caret caret, final boolean alpha,
|
||||
public static @Nullable TextRange findNumberUnderCursor(final @NotNull Editor editor, @NotNull Caret caret, final boolean alpha,
|
||||
final boolean hex, final boolean octal) {
|
||||
int lline = caret.getLogicalPosition().line;
|
||||
String text = EditorHelper.getLineText(editor, lline).toLowerCase();
|
||||
@@ -974,8 +964,7 @@ public class SearchHelper {
|
||||
* @param startPosOnLine - start offset to search
|
||||
* @return - text range with number
|
||||
*/
|
||||
@Nullable
|
||||
public static TextRange findNumberInText(@NotNull final String textInRange, int startPosOnLine, final boolean alpha,
|
||||
public static @Nullable TextRange findNumberInText(final @NotNull String textInRange, int startPosOnLine, final boolean alpha,
|
||||
final boolean hex, final boolean octal) {
|
||||
|
||||
if (logger.isDebugEnabled()) {
|
||||
@@ -1066,8 +1055,7 @@ public class SearchHelper {
|
||||
/**
|
||||
* Searches for digits block that matches parameters
|
||||
*/
|
||||
@NotNull
|
||||
private static Pair<Integer, Integer> findRange(@NotNull final String text, final int pos, final boolean alpha,
|
||||
private static @NotNull Pair<Integer, Integer> findRange(final @NotNull String text, final int pos, final boolean alpha,
|
||||
final boolean hex, final boolean octal, final boolean decimal) {
|
||||
int end = pos;
|
||||
while (end < text.length() && isNumberChar(text.charAt(end), alpha, hex, octal, decimal)) {
|
||||
@@ -1108,8 +1096,7 @@ public class SearchHelper {
|
||||
* @param caret The caret to find word under
|
||||
* @return The text range of the found word or null if there is no word under/after the cursor on the line
|
||||
*/
|
||||
@Nullable
|
||||
public static TextRange findWordUnderCursor(@NotNull Editor editor, @NotNull Caret caret) {
|
||||
public static @Nullable TextRange findWordUnderCursor(@NotNull Editor editor, @NotNull Caret caret) {
|
||||
CharSequence chars = editor.getDocument().getCharsSequence();
|
||||
int stop = EditorHelper.getLineEndOffset(editor, caret.getLogicalPosition().line, true);
|
||||
|
||||
@@ -1159,8 +1146,7 @@ public class SearchHelper {
|
||||
}
|
||||
|
||||
@Contract("_, _, _, _, _, _, _ -> new")
|
||||
@NotNull
|
||||
public static TextRange findWordUnderCursor(@NotNull Editor editor, @NotNull Caret caret, int count, int dir,
|
||||
public static @NotNull TextRange findWordUnderCursor(@NotNull Editor editor, @NotNull Caret caret, int count, int dir,
|
||||
boolean isOuter, boolean isBig, boolean hasSelection) {
|
||||
if (logger.isDebugEnabled()) {
|
||||
logger.debug("count=" + count);
|
||||
@@ -1892,8 +1878,7 @@ public class SearchHelper {
|
||||
}
|
||||
|
||||
@Contract("_, _, _, _ -> new")
|
||||
@NotNull
|
||||
public static TextRange findSentenceRange(@NotNull Editor editor, @NotNull Caret caret, int count, boolean isOuter) {
|
||||
public static @NotNull TextRange findSentenceRange(@NotNull Editor editor, @NotNull Caret caret, int count, boolean isOuter) {
|
||||
CharSequence chars = editor.getDocument().getCharsSequence();
|
||||
if (chars.length() == 0) return new TextRange(0, 0);
|
||||
int max = EditorHelper.getFileSize(editor);
|
||||
@@ -2030,8 +2015,7 @@ public class SearchHelper {
|
||||
return line;
|
||||
}
|
||||
|
||||
@Nullable
|
||||
public static TextRange findParagraphRange(@NotNull Editor editor, @NotNull Caret caret, int count, boolean isOuter) {
|
||||
public static @Nullable TextRange findParagraphRange(@NotNull Editor editor, @NotNull Caret caret, int count, boolean isOuter) {
|
||||
int line = caret.getLogicalPosition().line;
|
||||
int maxline = EditorHelper.getLineCount(editor);
|
||||
if (logger.isDebugEnabled()) logger.debug("starting on line " + line);
|
||||
@@ -2150,8 +2134,7 @@ public class SearchHelper {
|
||||
return PsiHelper.findMethodEnd(editor, caret.getOffset(), count);
|
||||
}
|
||||
|
||||
@NotNull
|
||||
private static String getPairChars() {
|
||||
private static @NotNull String getPairChars() {
|
||||
if (pairsChars == null) {
|
||||
ListOption lo = OptionsManager.INSTANCE.getMatchpairs();
|
||||
lo.addOptionChangeListenerAndExecute((oldValue, newValue) -> pairsChars = parseOption(lo));
|
||||
@@ -2160,8 +2143,7 @@ public class SearchHelper {
|
||||
return pairsChars;
|
||||
}
|
||||
|
||||
@NotNull
|
||||
private static String parseOption(@NotNull ListOption option) {
|
||||
private static @NotNull String parseOption(@NotNull ListOption option) {
|
||||
List<String> vals = option.values();
|
||||
StringBuilder res = new StringBuilder();
|
||||
for (String s : vals) {
|
||||
@@ -2191,8 +2173,8 @@ public class SearchHelper {
|
||||
private final int position;
|
||||
}
|
||||
|
||||
@Nullable private static String pairsChars = null;
|
||||
@NotNull private static final String blockChars = "{}()[]<>";
|
||||
private static @Nullable String pairsChars = null;
|
||||
private static final @NotNull String blockChars = "{}()[]<>";
|
||||
|
||||
private static final Logger logger = Logger.getInstance(SearchHelper.class.getName());
|
||||
}
|
||||
|
163
src/com/maddyhome/idea/vim/helper/SearchHelperKt.kt
Normal file
163
src/com/maddyhome/idea/vim/helper/SearchHelperKt.kt
Normal file
@@ -0,0 +1,163 @@
|
||||
/*
|
||||
* IdeaVim - Vim emulator for IDEs based on the IntelliJ platform
|
||||
* Copyright (C) 2003-2020 The IdeaVim authors
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 2 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package com.maddyhome.idea.vim.helper
|
||||
|
||||
import com.maddyhome.idea.vim.helper.SearchHelper.Direction
|
||||
import com.maddyhome.idea.vim.helper.SearchHelper.findPositionOfFirstCharacter
|
||||
|
||||
private data class State(val position: Int, val trigger: Char, val inQuote: Boolean?, val lastOpenSingleQuotePos: Int)
|
||||
|
||||
// bounds are considered inside corresponding quotes
|
||||
fun checkInString(chars: CharSequence, currentPos: Int, str: Boolean): Boolean {
|
||||
val begin = findPositionOfFirstCharacter(chars, currentPos, setOf('\n'), false, Direction.BACK)?.second ?: 0
|
||||
val changes = quoteChanges(chars, begin)
|
||||
// TODO: here we need to keep only the latest element in beforePos (if any) and
|
||||
// don't need atAndAfterPos to be eagerly collected
|
||||
var (beforePos, atAndAfterPos) = changes.partition { it.position < currentPos }
|
||||
|
||||
var (atPos, afterPos) = atAndAfterPos.partition { it.position == currentPos }
|
||||
assert(atPos.size <= 1) { "Multiple characters at position $currentPos in string $chars" }
|
||||
if (atPos.isNotEmpty()) {
|
||||
val atPosChange = atPos[0]
|
||||
if (afterPos.isEmpty()) {
|
||||
// it is situation when cursor is on closing quote, so we must consider that we are inside quotes pair
|
||||
afterPos = afterPos.toMutableList()
|
||||
afterPos.add(atPosChange)
|
||||
} else {
|
||||
// it is situation when cursor is on opening quote, so we must consider that we are inside quotes pair
|
||||
beforePos = beforePos.toMutableList()
|
||||
beforePos.add(atPosChange)
|
||||
}
|
||||
}
|
||||
|
||||
val lastBeforePos = beforePos.lastOrNull()
|
||||
|
||||
// if opening quote was found before pos (inQuote=true), it doesn't mean pos is in string, we need
|
||||
// to find closing quote to be sure
|
||||
var posInQuote = lastBeforePos?.inQuote?.let { if (it) null else it }
|
||||
|
||||
val lastOpenSingleQuotePosBeforeCurrentPos = lastBeforePos?.lastOpenSingleQuotePos ?: -1
|
||||
var posInChar = if (lastOpenSingleQuotePosBeforeCurrentPos == -1) false else null
|
||||
|
||||
var inQuote: Boolean? = null
|
||||
|
||||
for((_, trigger, inQuoteAfter, lastOpenSingleQuotePosAfter) in afterPos) {
|
||||
inQuote = inQuoteAfter
|
||||
if (posInQuote != null && posInChar != null) break
|
||||
if (posInQuote == null && inQuoteAfter != null) {
|
||||
// if we found double quote
|
||||
if (trigger == '"') {
|
||||
// then previously it has opposite value
|
||||
posInQuote = !inQuoteAfter
|
||||
// if we found single quote
|
||||
} else if (trigger == '\'') {
|
||||
// then we found closing single quote
|
||||
posInQuote = inQuoteAfter
|
||||
}
|
||||
}
|
||||
if (posInChar == null && lastOpenSingleQuotePosAfter != lastOpenSingleQuotePosBeforeCurrentPos) {
|
||||
// if we found double quote and we reset position of last single quote
|
||||
if (trigger == '"' && lastOpenSingleQuotePosAfter == -1) {
|
||||
// then it means previously there supposed to be open single quote
|
||||
posInChar = false
|
||||
// if we found single quote
|
||||
} else if (trigger == '\'') {
|
||||
// if we reset position of last single quote
|
||||
// it means we found closing single quote
|
||||
// else it means we found opening single quote
|
||||
posInChar = lastOpenSingleQuotePosAfter == -1
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return if (str) posInQuote != null && posInQuote && (inQuote == null || !inQuote) else posInChar != null && posInChar
|
||||
}
|
||||
|
||||
// yields changes of inQuote and lastOpenSingleQuotePos during while iterating over chars
|
||||
// rules are that:
|
||||
// - escaped quotes are skipped
|
||||
// - single quoted group may enclose only one character, maybe escaped,
|
||||
// - so distance between opening and closing single quotes cannot be more than 3
|
||||
// - bounds are considered inside corresponding quotes
|
||||
private fun quoteChanges(chars: CharSequence, begin: Int) = sequence {
|
||||
// position of last found unpaired single quote
|
||||
var lastOpenSingleQuotePos = -1
|
||||
// whether we are in double quotes
|
||||
// true - definitely yes
|
||||
// false - definitely no
|
||||
// null - maybe yes, in case we found such combination: '"
|
||||
// in that situation it may be double quote inside single quotes, so we cannot threat it as double quote pair open/close
|
||||
var inQuote: Boolean? = false
|
||||
val charsToSearch = setOf('\'', '"', '\n')
|
||||
var found = findPositionOfFirstCharacter(chars, begin, charsToSearch, false, Direction.FORWARD)
|
||||
while (found != null && found.first != '\n') {
|
||||
val i = found.second
|
||||
|
||||
val c = found.first
|
||||
when (c) {
|
||||
'"' -> {
|
||||
// if [maybe] in quote, then we know we found closing quote, so now we surely are not in quote
|
||||
if (inQuote == null || inQuote) {
|
||||
// we just found closing double quote
|
||||
inQuote = false
|
||||
// reset last found single quote, as it was in string literal
|
||||
lastOpenSingleQuotePos = -1
|
||||
// if we previously found unclosed single quote
|
||||
} else if (lastOpenSingleQuotePos >= 0) {
|
||||
// ...but we are too far from it
|
||||
if (i - lastOpenSingleQuotePos > 2) {
|
||||
// then it definitely was not opening single quote
|
||||
lastOpenSingleQuotePos = -1
|
||||
// and we found opening double quote
|
||||
inQuote = true
|
||||
} else {
|
||||
// else we don't know if we inside double or single quotes or not
|
||||
inQuote = null
|
||||
}
|
||||
// we were not in double nor in single quote, so now we are in double quote
|
||||
} else {
|
||||
inQuote = true
|
||||
}
|
||||
}
|
||||
'\'' -> {
|
||||
// if we previously found unclosed single quote
|
||||
if (lastOpenSingleQuotePos >= 0) {
|
||||
// ...but we are too far from it
|
||||
if (i - lastOpenSingleQuotePos > 3) {
|
||||
// ... forget about it and threat current one as unclosed
|
||||
lastOpenSingleQuotePos = i
|
||||
} else {
|
||||
// else we found closing single quote
|
||||
lastOpenSingleQuotePos = -1
|
||||
// and if we didn't know whether we are in double quote or not
|
||||
if (inQuote == null) {
|
||||
// then now we are definitely not in
|
||||
inQuote = false
|
||||
}
|
||||
}
|
||||
} else {
|
||||
// we found opening single quote
|
||||
lastOpenSingleQuotePos = i
|
||||
}
|
||||
}
|
||||
}
|
||||
yield(State(i, c, inQuote, lastOpenSingleQuotePos))
|
||||
found = findPositionOfFirstCharacter(chars, i + Direction.FORWARD.toInt(), charsToSearch, false, Direction.FORWARD)
|
||||
}
|
||||
}
|
@@ -46,8 +46,7 @@ public class StringHelper {
|
||||
|
||||
private StringHelper() {}
|
||||
|
||||
@Nullable
|
||||
private static String toEscapeNotation(@NotNull KeyStroke key) {
|
||||
private static @Nullable String toEscapeNotation(@NotNull KeyStroke key) {
|
||||
final char c = key.getKeyChar();
|
||||
if (isControlCharacter(c)) {
|
||||
return "^" + (char)(c + 'A' - 1);
|
||||
@@ -58,8 +57,7 @@ public class StringHelper {
|
||||
return null;
|
||||
}
|
||||
|
||||
@NotNull
|
||||
public static List<KeyStroke> stringToKeys(@NotNull String s) {
|
||||
public static @NotNull List<KeyStroke> stringToKeys(@NotNull String s) {
|
||||
final List<KeyStroke> res = new ArrayList<>();
|
||||
for (int i = 0; i < s.length(); i++) {
|
||||
res.add(getKeyStroke(s.charAt(i)));
|
||||
@@ -67,8 +65,7 @@ public class StringHelper {
|
||||
return res;
|
||||
}
|
||||
|
||||
@NotNull
|
||||
public static final KeyStroke PlugKeyStroke = parseKeys("<Plug>").get(0);
|
||||
public static final @NotNull KeyStroke PlugKeyStroke = parseKeys("<Plug>").get(0);
|
||||
|
||||
private enum KeyParserState {
|
||||
INIT,
|
||||
@@ -82,8 +79,7 @@ public class StringHelper {
|
||||
* @throws java.lang.IllegalArgumentException if the mapping doesn't make sense for Vim emulation
|
||||
* @see :help <>
|
||||
*/
|
||||
@NotNull
|
||||
public static List<KeyStroke> parseKeys(@NotNull String... strings) {
|
||||
public static @NotNull List<KeyStroke> parseKeys(@NotNull String... strings) {
|
||||
final List<KeyStroke> result = new ArrayList<>();
|
||||
for (String s : strings) {
|
||||
KeyParserState state = KeyParserState.INIT;
|
||||
@@ -167,8 +163,7 @@ public class StringHelper {
|
||||
return result;
|
||||
}
|
||||
|
||||
@Nullable
|
||||
private static List<KeyStroke> parseMapLeader(@NotNull String s) {
|
||||
private static @Nullable List<KeyStroke> parseMapLeader(@NotNull String s) {
|
||||
if ("leader".equals(s.toLowerCase())) {
|
||||
final Object mapLeader = VimScriptGlobalEnvironment.getInstance().getVariables().get("mapleader");
|
||||
if (mapLeader instanceof String) {
|
||||
@@ -189,8 +184,7 @@ public class StringHelper {
|
||||
return key.getKeyChar() == CHAR_UNDEFINED && key.getKeyCode() < 0x20 && key.getModifiers() == 0;
|
||||
}
|
||||
|
||||
@NotNull
|
||||
public static String toKeyNotation(@NotNull List<KeyStroke> keys) {
|
||||
public static @NotNull String toKeyNotation(@NotNull List<KeyStroke> keys) {
|
||||
if (keys.isEmpty()) {
|
||||
return "<Nop>";
|
||||
}
|
||||
@@ -201,8 +195,7 @@ public class StringHelper {
|
||||
return builder.toString();
|
||||
}
|
||||
|
||||
@NotNull
|
||||
public static String toKeyNotation(@NotNull KeyStroke key) {
|
||||
public static @NotNull String toKeyNotation(@NotNull KeyStroke key) {
|
||||
final char c = key.getKeyChar();
|
||||
final int keyCode = key.getKeyCode();
|
||||
final int modifiers = key.getModifiers();
|
||||
@@ -318,8 +311,7 @@ public class StringHelper {
|
||||
/**
|
||||
* Set the text of an XML element, safely encode it if needed.
|
||||
*/
|
||||
@NotNull
|
||||
public static Element setSafeXmlText(@NotNull Element element, @NotNull String text) {
|
||||
public static @NotNull Element setSafeXmlText(@NotNull Element element, @NotNull String text) {
|
||||
final Character first = firstCharacter(text);
|
||||
final Character last = lastCharacter(text);
|
||||
if (!StringHelper.isXmlCharacterData(text) ||
|
||||
@@ -338,8 +330,7 @@ public class StringHelper {
|
||||
/**
|
||||
* Get the (potentially safely encoded) text of an XML element.
|
||||
*/
|
||||
@Nullable
|
||||
public static String getSafeXmlText(@NotNull Element element) {
|
||||
public static @Nullable String getSafeXmlText(@NotNull Element element) {
|
||||
final String text = element.getText();
|
||||
final String encoding = element.getAttributeValue("encoding");
|
||||
if (encoding == null) {
|
||||
@@ -365,8 +356,7 @@ public class StringHelper {
|
||||
return true;
|
||||
}
|
||||
|
||||
@Nullable
|
||||
private static KeyStroke parseSpecialKey(@NotNull String s, int modifiers) {
|
||||
private static @Nullable KeyStroke parseSpecialKey(@NotNull String s, int modifiers) {
|
||||
final String lower = s.toLowerCase();
|
||||
final Integer keyCode = getVimKeyName(lower);
|
||||
final Character typedChar = getVimTypedKeyName(lower);
|
||||
@@ -541,8 +531,7 @@ public class StringHelper {
|
||||
}
|
||||
}
|
||||
|
||||
@NotNull
|
||||
private static KeyStroke getTypedOrPressedKeyStroke(char c, int modifiers) {
|
||||
private static @NotNull KeyStroke getTypedOrPressedKeyStroke(char c, int modifiers) {
|
||||
if (modifiers == 0) {
|
||||
return getKeyStroke(c);
|
||||
}
|
||||
@@ -554,13 +543,11 @@ public class StringHelper {
|
||||
}
|
||||
}
|
||||
|
||||
@Nullable
|
||||
private static Character lastCharacter(@NotNull String text) {
|
||||
private static @Nullable Character lastCharacter(@NotNull String text) {
|
||||
return text.length() > 0 ? text.charAt(text.length() - 1) : null;
|
||||
}
|
||||
|
||||
@Nullable
|
||||
private static Character firstCharacter(@NotNull String text) {
|
||||
private static @Nullable Character firstCharacter(@NotNull String text) {
|
||||
return text.length() > 0 ? text.charAt(0) : null;
|
||||
}
|
||||
|
||||
|
@@ -30,7 +30,7 @@ import java.util.List;
|
||||
* @author vlan
|
||||
*/
|
||||
public class TestInputModel {
|
||||
@NotNull private final List<KeyStroke> myKeyStrokes = Lists.newArrayList();
|
||||
private final @NotNull List<KeyStroke> myKeyStrokes = Lists.newArrayList();
|
||||
|
||||
private TestInputModel() {}
|
||||
|
||||
@@ -48,8 +48,7 @@ public class TestInputModel {
|
||||
myKeyStrokes.addAll(keyStrokes);
|
||||
}
|
||||
|
||||
@Nullable
|
||||
public KeyStroke nextKeyStroke() {
|
||||
public @Nullable KeyStroke nextKeyStroke() {
|
||||
if (!myKeyStrokes.isEmpty()) {
|
||||
return myKeyStrokes.remove(0);
|
||||
}
|
||||
|
@@ -20,13 +20,14 @@ package com.maddyhome.idea.vim.key;
|
||||
|
||||
import com.google.common.collect.HashMultiset;
|
||||
import com.google.common.collect.Multiset;
|
||||
import com.maddyhome.idea.vim.command.MappingMode;
|
||||
import com.maddyhome.idea.vim.extension.VimExtensionHandler;
|
||||
import kotlin.Pair;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
|
||||
import javax.swing.*;
|
||||
import java.util.*;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
/**
|
||||
* Container for key mappings for some mode
|
||||
@@ -35,36 +36,46 @@ import java.util.*;
|
||||
* @author vlan
|
||||
*/
|
||||
public class KeyMapping implements Iterable<List<KeyStroke>> {
|
||||
/** Contains all key mapping for some mode. */
|
||||
@NotNull private final Map<List<KeyStroke>, MappingInfo> myKeys = new HashMap<>();
|
||||
/**
|
||||
* Contains all key mapping for some mode.
|
||||
*/
|
||||
private final @NotNull Map<List<KeyStroke>, MappingInfo> myKeys = new HashMap<>();
|
||||
/**
|
||||
* Set the contains all possible prefixes for mappings.
|
||||
* E.g. if there is mapping for "hello", this set will contain "h", "he", "hel", etc.
|
||||
* Multiset is used to correctly remove the mappings.
|
||||
*/
|
||||
@NotNull private final Multiset<List<KeyStroke>> myPrefixes = HashMultiset.create();
|
||||
private final @NotNull Multiset<List<KeyStroke>> myPrefixes = HashMultiset.create();
|
||||
|
||||
@NotNull
|
||||
@Override
|
||||
public Iterator<List<KeyStroke>> iterator() {
|
||||
public @NotNull Iterator<List<KeyStroke>> iterator() {
|
||||
return new ArrayList<>(myKeys.keySet()).iterator();
|
||||
}
|
||||
|
||||
@Nullable
|
||||
public MappingInfo get(@NotNull Iterable<KeyStroke> keys) {
|
||||
public @Nullable MappingInfo get(@NotNull Iterable<KeyStroke> keys) {
|
||||
// Having a parameter of Iterable allows for a nicer API, because we know when a given list is immutable.
|
||||
// TODO: Should we change this to be a trie?
|
||||
assert (keys instanceof List) : "keys must be of type List<KeyStroke>";
|
||||
return myKeys.get(keys);
|
||||
}
|
||||
|
||||
public void put(@NotNull Set<MappingMode> mappingModes,
|
||||
@NotNull List<KeyStroke> fromKeys,
|
||||
@Nullable List<KeyStroke> toKeys,
|
||||
@Nullable VimExtensionHandler extensionHandler,
|
||||
public void put(@NotNull List<KeyStroke> fromKeys,
|
||||
@NotNull MappingOwner owner,
|
||||
@NotNull VimExtensionHandler extensionHandler,
|
||||
boolean recursive) {
|
||||
myKeys.put(new ArrayList<>(fromKeys),
|
||||
new MappingInfo(mappingModes, fromKeys, toKeys, extensionHandler, recursive));
|
||||
myKeys.put(new ArrayList<>(fromKeys), new ToHandlerMappingInfo(extensionHandler, fromKeys, recursive, owner));
|
||||
fillPrefixes(fromKeys);
|
||||
}
|
||||
|
||||
public void put(@NotNull List<KeyStroke> fromKeys,
|
||||
@NotNull List<KeyStroke> toKeys,
|
||||
@NotNull MappingOwner owner,
|
||||
boolean recursive) {
|
||||
myKeys.put(new ArrayList<>(fromKeys), new ToKeysMappingInfo(toKeys, fromKeys, recursive, owner));
|
||||
fillPrefixes(fromKeys);
|
||||
}
|
||||
|
||||
private void fillPrefixes(@NotNull List<KeyStroke> fromKeys) {
|
||||
List<KeyStroke> prefix = new ArrayList<>();
|
||||
final int prefixLength = fromKeys.size() - 1;
|
||||
for (int i = 0; i < prefixLength; i++) {
|
||||
@@ -73,14 +84,24 @@ public class KeyMapping implements Iterable<List<KeyStroke>> {
|
||||
}
|
||||
}
|
||||
|
||||
public void delete(@NotNull List<KeyStroke> keys) {
|
||||
myKeys.remove(keys);
|
||||
public void delete(@NotNull MappingOwner owner) {
|
||||
List<Map.Entry<List<KeyStroke>, MappingInfo>> toRemove =
|
||||
myKeys.entrySet().stream().filter(o -> o.getValue().getOwner().equals(owner)).collect(Collectors.toList());
|
||||
|
||||
toRemove.forEach(o -> myKeys.remove(o.getKey(), o.getValue()));
|
||||
toRemove.stream().map(Map.Entry::getKey).forEach(keys -> {
|
||||
List<KeyStroke> prefix = new ArrayList<>();
|
||||
final int prefixLength = keys.size() - 1;
|
||||
for (int i = 0; i < prefixLength; i++) {
|
||||
prefix.add(keys.get(i));
|
||||
myPrefixes.remove(prefix);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
public List<Pair<List<KeyStroke>, MappingInfo>> getByOwner(@NotNull MappingOwner owner) {
|
||||
return myKeys.entrySet().stream().filter(o -> o.getValue().getOwner().equals(owner))
|
||||
.map(o -> new Pair<>(o.getKey(), o.getValue())).collect(Collectors.toList());
|
||||
}
|
||||
|
||||
public boolean isPrefix(@NotNull Iterable<KeyStroke> keys) {
|
||||
|
@@ -1,113 +0,0 @@
|
||||
/*
|
||||
* IdeaVim - Vim emulator for IDEs based on the IntelliJ platform
|
||||
* Copyright (C) 2003-2020 The IdeaVim authors
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 2 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package com.maddyhome.idea.vim.key;
|
||||
|
||||
import com.maddyhome.idea.vim.command.MappingMode;
|
||||
import com.maddyhome.idea.vim.extension.VimExtensionHandler;
|
||||
import org.jetbrains.annotations.Contract;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
|
||||
import javax.swing.*;
|
||||
import java.awt.event.KeyEvent;
|
||||
import java.util.List;
|
||||
import java.util.Set;
|
||||
|
||||
/**
|
||||
* @author vlan
|
||||
*/
|
||||
public class MappingInfo implements Comparable<MappingInfo> {
|
||||
@NotNull private final Set<MappingMode> myMappingModes;
|
||||
@NotNull private final List<KeyStroke> myFromKeys;
|
||||
@Nullable private final List<KeyStroke> myToKeys;
|
||||
@Nullable private final VimExtensionHandler myExtensionHandler;
|
||||
private final boolean myRecursive;
|
||||
|
||||
@Contract(pure = true)
|
||||
public MappingInfo(@NotNull Set<MappingMode> mappingModes,
|
||||
@NotNull List<KeyStroke> fromKeys,
|
||||
@Nullable List<KeyStroke> toKeys,
|
||||
@Nullable VimExtensionHandler extensionHandler,
|
||||
boolean recursive) {
|
||||
myMappingModes = mappingModes;
|
||||
myFromKeys = fromKeys;
|
||||
myToKeys = toKeys;
|
||||
myExtensionHandler = extensionHandler;
|
||||
myRecursive = recursive;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int compareTo(@NotNull MappingInfo other) {
|
||||
final int size = myFromKeys.size();
|
||||
final int otherSize = other.myFromKeys.size();
|
||||
final int n = Math.min(size, otherSize);
|
||||
for (int i = 0; i < n; i++) {
|
||||
final int diff = compareKeys(myFromKeys.get(i), other.myFromKeys.get(i));
|
||||
if (diff != 0) {
|
||||
return diff;
|
||||
}
|
||||
}
|
||||
return size - otherSize;
|
||||
}
|
||||
|
||||
@NotNull
|
||||
public Set<MappingMode> getMappingModes() {
|
||||
return myMappingModes;
|
||||
}
|
||||
|
||||
@NotNull
|
||||
public List<KeyStroke> getFromKeys() {
|
||||
return myFromKeys;
|
||||
}
|
||||
|
||||
@Nullable
|
||||
public List<KeyStroke> getToKeys() {
|
||||
return myToKeys;
|
||||
}
|
||||
|
||||
@Nullable
|
||||
public VimExtensionHandler getExtensionHandler() {
|
||||
return myExtensionHandler;
|
||||
}
|
||||
|
||||
public boolean isRecursive() {
|
||||
return myRecursive;
|
||||
}
|
||||
|
||||
private int compareKeys(@NotNull KeyStroke key1, @NotNull KeyStroke key2) {
|
||||
final char c1 = key1.getKeyChar();
|
||||
final char c2 = key2.getKeyChar();
|
||||
if (c1 == KeyEvent.CHAR_UNDEFINED && c2 == KeyEvent.CHAR_UNDEFINED) {
|
||||
final int keyCodeDiff = key1.getKeyCode() - key2.getKeyCode();
|
||||
if (keyCodeDiff != 0) {
|
||||
return keyCodeDiff;
|
||||
}
|
||||
return key1.getModifiers() - key2.getModifiers();
|
||||
}
|
||||
else if (c1 == KeyEvent.CHAR_UNDEFINED) {
|
||||
return -1;
|
||||
}
|
||||
else if (c2 == KeyEvent.CHAR_UNDEFINED) {
|
||||
return 1;
|
||||
}
|
||||
else {
|
||||
return c1 - c2;
|
||||
}
|
||||
}
|
||||
}
|
67
src/com/maddyhome/idea/vim/key/MappingInfo.kt
Normal file
67
src/com/maddyhome/idea/vim/key/MappingInfo.kt
Normal file
@@ -0,0 +1,67 @@
|
||||
/*
|
||||
* IdeaVim - Vim emulator for IDEs based on the IntelliJ platform
|
||||
* Copyright (C) 2003-2020 The IdeaVim authors
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 2 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
package com.maddyhome.idea.vim.key
|
||||
|
||||
import com.maddyhome.idea.vim.extension.VimExtensionHandler
|
||||
import java.awt.event.KeyEvent
|
||||
import javax.swing.KeyStroke
|
||||
import kotlin.math.min
|
||||
|
||||
/**
|
||||
* @author vlan
|
||||
*/
|
||||
sealed class MappingInfo(val fromKeys: List<KeyStroke>, val isRecursive: Boolean, val owner: MappingOwner) : Comparable<MappingInfo> {
|
||||
override fun compareTo(other: MappingInfo): Int {
|
||||
val size = fromKeys.size
|
||||
val otherSize = other.fromKeys.size
|
||||
val n = min(size, otherSize)
|
||||
for (i in 0 until n) {
|
||||
val diff = compareKeys(fromKeys[i], other.fromKeys[i])
|
||||
if (diff != 0) return diff
|
||||
}
|
||||
return size - otherSize
|
||||
}
|
||||
|
||||
private fun compareKeys(key1: KeyStroke, key2: KeyStroke): Int {
|
||||
val c1 = key1.keyChar
|
||||
val c2 = key2.keyChar
|
||||
return when {
|
||||
c1 == KeyEvent.CHAR_UNDEFINED && c2 == KeyEvent.CHAR_UNDEFINED -> {
|
||||
val keyCodeDiff = key1.keyCode - key2.keyCode
|
||||
if (keyCodeDiff != 0) keyCodeDiff else key1.modifiers - key2.modifiers
|
||||
}
|
||||
c1 == KeyEvent.CHAR_UNDEFINED -> -1
|
||||
c2 == KeyEvent.CHAR_UNDEFINED -> 1
|
||||
else -> c1 - c2
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
class ToKeysMappingInfo(
|
||||
val toKeys: List<KeyStroke>,
|
||||
fromKeys: List<KeyStroke>,
|
||||
isRecursive: Boolean,
|
||||
owner: MappingOwner
|
||||
) : MappingInfo(fromKeys, isRecursive, owner)
|
||||
|
||||
class ToHandlerMappingInfo(
|
||||
val extensionHandler: VimExtensionHandler,
|
||||
fromKeys: List<KeyStroke>,
|
||||
isRecursive: Boolean,
|
||||
owner: MappingOwner
|
||||
) : MappingInfo(fromKeys, isRecursive, owner)
|
38
src/com/maddyhome/idea/vim/key/RequiredShortcut.kt
Normal file
38
src/com/maddyhome/idea/vim/key/RequiredShortcut.kt
Normal file
@@ -0,0 +1,38 @@
|
||||
/*
|
||||
* IdeaVim - Vim emulator for IDEs based on the IntelliJ platform
|
||||
* Copyright (C) 2003-2020 The IdeaVim authors
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 2 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package com.maddyhome.idea.vim.key
|
||||
|
||||
import javax.swing.KeyStroke
|
||||
|
||||
class RequiredShortcut(val keyStroke: KeyStroke, val owner: MappingOwner)
|
||||
|
||||
sealed class MappingOwner {
|
||||
object IdeaVim : MappingOwner()
|
||||
|
||||
@Suppress("DataClassPrivateConstructor")
|
||||
data class Plugin private constructor(val name: String) : MappingOwner() {
|
||||
companion object {
|
||||
fun get(name: String): Plugin = allOwners.computeIfAbsent(name) { Plugin(it) }
|
||||
|
||||
fun remove(name: String) = allOwners.remove(name)
|
||||
|
||||
private val allOwners: MutableMap<String, Plugin> = mutableMapOf()
|
||||
}
|
||||
}
|
||||
}
|
@@ -28,27 +28,24 @@ public enum ShortcutOwner {
|
||||
IDE("ide", "IDE"),
|
||||
VIM("vim", "Vim");
|
||||
|
||||
@NotNull private final String name;
|
||||
@NotNull private final String title;
|
||||
private final @NotNull String name;
|
||||
private final @NotNull String title;
|
||||
|
||||
ShortcutOwner(@NotNull String name, @NotNull String title) {
|
||||
this.name = name;
|
||||
this.title = title;
|
||||
}
|
||||
|
||||
@NotNull
|
||||
@Override
|
||||
public String toString() {
|
||||
public @NotNull String toString() {
|
||||
return title;
|
||||
}
|
||||
|
||||
@NotNull
|
||||
public String getName() {
|
||||
public @NotNull String getName() {
|
||||
return name;
|
||||
}
|
||||
|
||||
@NotNull
|
||||
public static ShortcutOwner fromString(@NotNull String s) {
|
||||
public static @NotNull ShortcutOwner fromString(@NotNull String s) {
|
||||
if ("ide".equals(s)) {
|
||||
return IDE;
|
||||
}
|
||||
|
@@ -72,5 +72,5 @@ public class BoundListOption extends ListOption {
|
||||
return true;
|
||||
}
|
||||
|
||||
@NotNull protected final List<String> values;
|
||||
protected final @NotNull List<String> values;
|
||||
}
|
||||
|
@@ -30,10 +30,10 @@ import java.util.regex.Pattern;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
public final class KeywordOption extends ListOption {
|
||||
@NotNull private final Pattern validationPattern;
|
||||
private final @NotNull Pattern validationPattern;
|
||||
|
||||
// KeywordSpecs are the option values in reverse order
|
||||
@NotNull private List<KeywordSpec> keywordSpecs = new ArrayList<>();
|
||||
private @NotNull List<KeywordSpec> keywordSpecs = new ArrayList<>();
|
||||
|
||||
public KeywordOption(@NotNull String name, @NotNull String abbrev, @NotNull String[] defaultValue) {
|
||||
super(name, abbrev, defaultValue,
|
||||
@@ -116,8 +116,7 @@ public final class KeywordOption extends ListOption {
|
||||
}
|
||||
}
|
||||
|
||||
@NotNull
|
||||
private List<KeywordSpec> valsToReversedSpecs(@NotNull List<String> vals) {
|
||||
private @NotNull List<KeywordSpec> valsToReversedSpecs(@NotNull List<String> vals) {
|
||||
final ArrayList<KeywordSpec> res = new ArrayList<>();
|
||||
for (int i = vals.size() - 1; i >= 0; i--) {
|
||||
res.add(new KeywordSpec(vals.get(i)));
|
||||
@@ -125,8 +124,7 @@ public final class KeywordOption extends ListOption {
|
||||
return res;
|
||||
}
|
||||
|
||||
@Nullable
|
||||
private List<KeywordSpec> valsToValidatedAndReversedSpecs(@Nullable List<String> vals) {
|
||||
private @Nullable List<KeywordSpec> valsToValidatedAndReversedSpecs(@Nullable List<String> vals) {
|
||||
final List<KeywordSpec> specs = new ArrayList<>();
|
||||
if (vals != null) {
|
||||
for (String val : vals) {
|
||||
@@ -141,9 +139,8 @@ public final class KeywordOption extends ListOption {
|
||||
return specs;
|
||||
}
|
||||
|
||||
@Nullable
|
||||
@Override
|
||||
protected List<String> parseVals(@NotNull String content) {
|
||||
protected @Nullable List<String> parseVals(@NotNull String content) {
|
||||
if (!validationPattern.matcher(content).matches()) {
|
||||
return null;
|
||||
}
|
||||
|
@@ -30,7 +30,7 @@ import java.util.StringTokenizer;
|
||||
* This is an option that accepts an arbitrary list of values
|
||||
*/
|
||||
public class ListOption extends TextOption {
|
||||
@NotNull public final static ListOption empty = new ListOption("", "", new String[0], "");
|
||||
public static final @NotNull ListOption empty = new ListOption("", "", new String[0], "");
|
||||
|
||||
/**
|
||||
* Gets the value of the option as a comma separated list of values
|
||||
@@ -38,8 +38,7 @@ public class ListOption extends TextOption {
|
||||
* @return The option's value
|
||||
*/
|
||||
@Override
|
||||
@NotNull
|
||||
public String getValue() {
|
||||
public @NotNull String getValue() {
|
||||
StringBuilder res = new StringBuilder();
|
||||
int cnt = 0;
|
||||
for (String s : value) {
|
||||
@@ -59,8 +58,7 @@ public class ListOption extends TextOption {
|
||||
*
|
||||
* @return The option's values
|
||||
*/
|
||||
@NotNull
|
||||
public List<String> values() {
|
||||
public @NotNull List<String> values() {
|
||||
return value;
|
||||
}
|
||||
|
||||
@@ -197,8 +195,7 @@ public class ListOption extends TextOption {
|
||||
return dflt.equals(value);
|
||||
}
|
||||
|
||||
@Nullable
|
||||
protected List<String> parseVals(String val) {
|
||||
protected @Nullable List<String> parseVals(String val) {
|
||||
List<String> res = new ArrayList<>();
|
||||
StringTokenizer tokenizer = new StringTokenizer(val, ",");
|
||||
while (tokenizer.hasMoreTokens()) {
|
||||
@@ -219,13 +216,12 @@ public class ListOption extends TextOption {
|
||||
*
|
||||
* @return The option as a string {name}={value list}
|
||||
*/
|
||||
@NotNull
|
||||
public String toString() {
|
||||
public @NotNull String toString() {
|
||||
return " " + getName() + "=" + getValue();
|
||||
}
|
||||
|
||||
@NotNull protected final List<String> dflt;
|
||||
@NotNull protected List<String> value;
|
||||
protected final @NotNull List<String> dflt;
|
||||
protected @NotNull List<String> value;
|
||||
protected final String pattern;
|
||||
|
||||
/**
|
||||
|
@@ -213,8 +213,7 @@ public class NumberOption extends TextOption {
|
||||
}
|
||||
}
|
||||
|
||||
@Nullable
|
||||
protected Integer asNumber(String val) {
|
||||
protected @Nullable Integer asNumber(String val) {
|
||||
try {
|
||||
return Integer.decode(val);
|
||||
}
|
||||
@@ -232,8 +231,7 @@ public class NumberOption extends TextOption {
|
||||
*
|
||||
* @return The option as a string
|
||||
*/
|
||||
@NotNull
|
||||
public String toString() {
|
||||
public @NotNull String toString() {
|
||||
|
||||
return " " + getName() + "=" + value;
|
||||
}
|
||||
|
@@ -113,5 +113,5 @@ public abstract class Option<T> {
|
||||
|
||||
protected final String name;
|
||||
protected final String abbrev;
|
||||
@NotNull private final List<OptionChangeListener<T>> listeners = new ArrayList<>();
|
||||
private final @NotNull List<OptionChangeListener<T>> listeners = new ArrayList<>();
|
||||
}
|
||||
|
@@ -36,6 +36,7 @@ import com.maddyhome.idea.vim.helper.isBlockCaret
|
||||
import com.maddyhome.idea.vim.helper.mode
|
||||
import com.maddyhome.idea.vim.helper.subMode
|
||||
import com.maddyhome.idea.vim.listener.SelectionVimListenerSuppressor
|
||||
import org.jetbrains.annotations.ApiStatus
|
||||
import org.jetbrains.annotations.Contract
|
||||
import java.util.*
|
||||
import kotlin.math.ceil
|
||||
@@ -82,6 +83,10 @@ object OptionsManager {
|
||||
val wrapscan = addOption(ToggleOption("wrapscan", "ws", true))
|
||||
val visualEnterDelay = addOption(NumberOption("visualdelay", "visualdelay", 100, 0, Int.MAX_VALUE))
|
||||
val idearefactormode = addOption(BoundStringOption(IdeaRefactorMode.name, IdeaRefactorMode.name, IdeaRefactorMode.select, IdeaRefactorMode.availableValues))
|
||||
val ideastatusicon = addOption(BoundStringOption(IdeaStatusIcon.name, IdeaStatusIcon.name, IdeaStatusIcon.enabled, IdeaStatusIcon.allValues))
|
||||
|
||||
@ApiStatus.ScheduledForRemoval(inVersion = "0.58")
|
||||
@Deprecated("please use ideastatusicon")
|
||||
val ideastatusbar = addOption(ToggleOption("ideastatusbar", "ideastatusbar", true))
|
||||
|
||||
fun isSet(name: String): Boolean {
|
||||
@@ -353,6 +358,11 @@ object OptionsManager {
|
||||
abbrevs += option.abbrev to option
|
||||
return option
|
||||
}
|
||||
|
||||
fun removeOption(name: String) {
|
||||
options.remove(name)
|
||||
abbrevs.values.find { it.name == name }?.let { option -> abbrevs.remove(option.abbrev) }
|
||||
}
|
||||
}
|
||||
|
||||
object KeyModelOptionData {
|
||||
@@ -379,8 +389,11 @@ object SelectModeOptionData {
|
||||
const val key = "key"
|
||||
const val cmd = "cmd"
|
||||
|
||||
@ApiStatus.ScheduledForRemoval(inVersion = "0.57")
|
||||
@Deprecated("Please, use `idearefactormode` option")
|
||||
const val template = "template"
|
||||
|
||||
@ApiStatus.ScheduledForRemoval(inVersion = "0.57")
|
||||
@Deprecated("Please, use `ideaselection`")
|
||||
const val refactoring = "refactoring"
|
||||
|
||||
@@ -520,3 +533,12 @@ object LookupKeysData {
|
||||
"<C-Q>"
|
||||
)
|
||||
}
|
||||
|
||||
object IdeaStatusIcon {
|
||||
const val enabled = "enabled"
|
||||
const val gray = "gray"
|
||||
const val disabled = "disabled"
|
||||
|
||||
val name = "ideastatusicon"
|
||||
val allValues = arrayOf(enabled, gray, disabled)
|
||||
}
|
||||
|
@@ -139,8 +139,7 @@ public class StringOption extends TextOption {
|
||||
*
|
||||
* @return The option as a string for display
|
||||
*/
|
||||
@NotNull
|
||||
public String toString() {
|
||||
public @NotNull String toString() {
|
||||
|
||||
return " " + getName() + "=" + value;
|
||||
}
|
||||
|
@@ -86,8 +86,7 @@ public class ToggleOption extends Option<Boolean> {
|
||||
*
|
||||
* @return The option's display value
|
||||
*/
|
||||
@NotNull
|
||||
public String toString() {
|
||||
public @NotNull String toString() {
|
||||
StringBuilder res = new StringBuilder();
|
||||
if (!value) {
|
||||
res.append("no");
|
||||
|
@@ -26,7 +26,7 @@ import java.util.Objects;
|
||||
|
||||
|
||||
public class CharPointer {
|
||||
@NotNull private CharSequence seq;
|
||||
private @NotNull CharSequence seq;
|
||||
private int pointer;
|
||||
private boolean readonly;
|
||||
|
||||
@@ -55,13 +55,11 @@ public class CharPointer {
|
||||
return pointer;
|
||||
}
|
||||
|
||||
@NotNull
|
||||
public CharPointer set(char ch) {
|
||||
public @NotNull CharPointer set(char ch) {
|
||||
return set(ch, 0);
|
||||
}
|
||||
|
||||
@NotNull
|
||||
public CharPointer set(char ch, int offset) {
|
||||
public @NotNull CharPointer set(char ch, int offset) {
|
||||
if (readonly) {
|
||||
throw new IllegalStateException("readonly string");
|
||||
}
|
||||
@@ -94,32 +92,27 @@ public class CharPointer {
|
||||
return seq.charAt(pointer + offset);
|
||||
}
|
||||
|
||||
@NotNull
|
||||
public CharPointer inc() {
|
||||
public @NotNull CharPointer inc() {
|
||||
return inc(1);
|
||||
}
|
||||
|
||||
@NotNull
|
||||
public CharPointer inc(int cnt) {
|
||||
public @NotNull CharPointer inc(int cnt) {
|
||||
pointer += cnt;
|
||||
|
||||
return this;
|
||||
}
|
||||
|
||||
@NotNull
|
||||
public CharPointer dec() {
|
||||
public @NotNull CharPointer dec() {
|
||||
return dec(1);
|
||||
}
|
||||
|
||||
@NotNull
|
||||
public CharPointer dec(int cnt) {
|
||||
public @NotNull CharPointer dec(int cnt) {
|
||||
pointer -= cnt;
|
||||
|
||||
return this;
|
||||
}
|
||||
|
||||
@NotNull
|
||||
public CharPointer assign(@NotNull CharPointer ptr) {
|
||||
public @NotNull CharPointer assign(@NotNull CharPointer ptr) {
|
||||
seq = ptr.seq;
|
||||
pointer = ptr.pointer;
|
||||
readonly = ptr.readonly;
|
||||
@@ -127,13 +120,11 @@ public class CharPointer {
|
||||
return this;
|
||||
}
|
||||
|
||||
@NotNull
|
||||
public CharPointer ref(int offset) {
|
||||
public @NotNull CharPointer ref(int offset) {
|
||||
return new CharPointer(this, offset);
|
||||
}
|
||||
|
||||
@NotNull
|
||||
public String substring(int len) {
|
||||
public @NotNull String substring(int len) {
|
||||
if (end()) return "";
|
||||
|
||||
int start = pointer;
|
||||
@@ -188,8 +179,7 @@ public class CharPointer {
|
||||
return 0;
|
||||
}
|
||||
|
||||
@Nullable
|
||||
public CharPointer strchr(char c) {
|
||||
public @Nullable CharPointer strchr(char c) {
|
||||
if (end()) {
|
||||
return null;
|
||||
}
|
||||
@@ -208,8 +198,7 @@ public class CharPointer {
|
||||
return null;
|
||||
}
|
||||
|
||||
@Nullable
|
||||
public CharPointer istrchr(char c) {
|
||||
public @Nullable CharPointer istrchr(char c) {
|
||||
if (end()) {
|
||||
return null;
|
||||
}
|
||||
@@ -247,8 +236,7 @@ public class CharPointer {
|
||||
return charAt();
|
||||
}
|
||||
|
||||
@NotNull
|
||||
public CharPointer OPERAND() {
|
||||
public @NotNull CharPointer OPERAND() {
|
||||
return ref(3);
|
||||
}
|
||||
|
||||
@@ -306,8 +294,7 @@ public class CharPointer {
|
||||
return Math.min(seq.length(), pos);
|
||||
}
|
||||
|
||||
@NotNull
|
||||
public String toString() {
|
||||
public @NotNull String toString() {
|
||||
return substring(strlen());
|
||||
}
|
||||
}
|
||||
|
@@ -29,8 +29,8 @@ import org.jetbrains.annotations.NotNull;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
|
||||
public class RegExp {
|
||||
@Nullable public static reg_extmatch_T re_extmatch_out = null;
|
||||
@Nullable public static reg_extmatch_T re_extmatch_in = null;
|
||||
public static @Nullable reg_extmatch_T re_extmatch_out = null;
|
||||
public static @Nullable reg_extmatch_T re_extmatch_in = null;
|
||||
|
||||
/*
|
||||
* The opcodes are:
|
||||
@@ -356,8 +356,7 @@ public class RegExp {
|
||||
* "p" must point to the character after the '['.
|
||||
* The returned pointer is on the matching ']', or the terminating NUL.
|
||||
*/
|
||||
@NotNull
|
||||
private static CharPointer skip_anyof(@NotNull CharPointer p) {
|
||||
private static @NotNull CharPointer skip_anyof(@NotNull CharPointer p) {
|
||||
if (p.charAt() == '^') /* Complement of range. */ {
|
||||
p.inc();
|
||||
}
|
||||
@@ -401,8 +400,7 @@ public class RegExp {
|
||||
* Take care of characters with a backslash in front of it.
|
||||
* Skip strings inside [ and ].
|
||||
*/
|
||||
@NotNull
|
||||
public static CharPointer skip_regexp(@NotNull CharPointer p, char dirc, boolean magic) {
|
||||
public static @NotNull CharPointer skip_regexp(@NotNull CharPointer p, char dirc, boolean magic) {
|
||||
int mymagic;
|
||||
|
||||
if (magic) {
|
||||
@@ -455,8 +453,7 @@ public class RegExp {
|
||||
* Beware that the optimization-preparation code in here knows about some
|
||||
* of the structure of the compiled regexp.
|
||||
*/
|
||||
@Nullable
|
||||
public regprog_T vim_regcomp(@Nullable String expr, int magic) {
|
||||
public @Nullable regprog_T vim_regcomp(@Nullable String expr, int magic) {
|
||||
regprog_T r;
|
||||
CharPointer scan;
|
||||
CharPointer longest;
|
||||
@@ -587,8 +584,7 @@ public class RegExp {
|
||||
* is a trifle forced, but the need to tie the tails of the branches to what
|
||||
* follows makes it hard to avoid.
|
||||
*/
|
||||
@Nullable
|
||||
private CharPointer reg(int paren, @NotNull Flags flagp) {
|
||||
private @Nullable CharPointer reg(int paren, @NotNull Flags flagp) {
|
||||
CharPointer ret;
|
||||
CharPointer br;
|
||||
CharPointer ender;
|
||||
@@ -708,8 +704,7 @@ public class RegExp {
|
||||
*
|
||||
* Implements the & operator.
|
||||
*/
|
||||
@Nullable
|
||||
private CharPointer regbranch(@NotNull Flags flagp) {
|
||||
private @Nullable CharPointer regbranch(@NotNull Flags flagp) {
|
||||
CharPointer ret;
|
||||
CharPointer chain = null;
|
||||
CharPointer latest;
|
||||
@@ -750,8 +745,7 @@ public class RegExp {
|
||||
*
|
||||
* Implements the concatenation operator.
|
||||
*/
|
||||
@Nullable
|
||||
private CharPointer regconcat(@NotNull Flags flagp) {
|
||||
private @Nullable CharPointer regconcat(@NotNull Flags flagp) {
|
||||
CharPointer first = null;
|
||||
CharPointer chain = null;
|
||||
CharPointer latest;
|
||||
@@ -831,8 +825,7 @@ public class RegExp {
|
||||
* It might seem that this node could be dispensed with entirely, but the
|
||||
* endmarker role is not redundant.
|
||||
*/
|
||||
@Nullable
|
||||
private CharPointer regpiece(@NotNull Flags flagp) {
|
||||
private @Nullable CharPointer regpiece(@NotNull Flags flagp) {
|
||||
CharPointer ret;
|
||||
int op;
|
||||
CharPointer next;
|
||||
@@ -992,8 +985,7 @@ public class RegExp {
|
||||
* it can turn them into a single node, which is smaller to store and
|
||||
* faster to run. Don't do this when one_exactly is set.
|
||||
*/
|
||||
@Nullable
|
||||
private CharPointer regatom(@NotNull Flags flagp) {
|
||||
private @Nullable CharPointer regatom(@NotNull Flags flagp) {
|
||||
CharPointer ret = null;
|
||||
Flags flags = new Flags();
|
||||
boolean cpo_lit = false; /* 'cpoptions' contains 'l' flag */
|
||||
@@ -1695,8 +1687,7 @@ public class RegExp {
|
||||
/*
|
||||
* Write a long as four bytes at "p" and return pointer to the next char.
|
||||
*/
|
||||
@NotNull
|
||||
private CharPointer re_put_long(@NotNull CharPointer p, int val) {
|
||||
private @NotNull CharPointer re_put_long(@NotNull CharPointer p, int val) {
|
||||
p.set((char)((val >> 24) & 0xff)).inc();
|
||||
p.set((char)((val >> 16) & 0xff)).inc();
|
||||
p.set((char)((val >> 8) & 0xff)).inc();
|
||||
@@ -1939,8 +1930,7 @@ public class RegExp {
|
||||
* Should end with 'end'. If minval is missing, zero is default, if maxval is
|
||||
* missing, a very big number is the default.
|
||||
*/
|
||||
@Nullable
|
||||
private MinMax read_limits() {
|
||||
private @Nullable MinMax read_limits() {
|
||||
boolean reverse = false;
|
||||
CharPointer first_char;
|
||||
int minval;
|
||||
@@ -2022,8 +2012,7 @@ public class RegExp {
|
||||
/*
|
||||
* Get pointer to the line "lnum", which is relative to "reg_firstlnum".
|
||||
*/
|
||||
@Nullable
|
||||
private CharPointer reg_getline(int lnum) {
|
||||
private @Nullable CharPointer reg_getline(int lnum) {
|
||||
/* when looking behind for a match/no-match lnum is negative. But we
|
||||
* can't go before line 1 */
|
||||
if (reg_firstlnum + lnum < 0) {
|
||||
@@ -2208,8 +2197,7 @@ public class RegExp {
|
||||
/*
|
||||
* Create a new extmatch and mark it as referenced once.
|
||||
*/
|
||||
@NotNull
|
||||
private reg_extmatch_T make_extmatch() {
|
||||
private @NotNull reg_extmatch_T make_extmatch() {
|
||||
return new reg_extmatch_T();
|
||||
}
|
||||
|
||||
@@ -3555,8 +3543,7 @@ public class RegExp {
|
||||
/*
|
||||
* regnext - dig the "next" pointer out of a node
|
||||
*/
|
||||
@Nullable
|
||||
private CharPointer regnext(@NotNull CharPointer p) {
|
||||
private @Nullable CharPointer regnext(@NotNull CharPointer p) {
|
||||
int offset;
|
||||
|
||||
offset = p.NEXT();
|
||||
@@ -3739,8 +3726,7 @@ public class RegExp {
|
||||
/*
|
||||
* cstrchr: This function is used a lot for simple searches, keep it fast!
|
||||
*/
|
||||
@Nullable
|
||||
private CharPointer cstrchr(@NotNull CharPointer s, char c) {
|
||||
private @Nullable CharPointer cstrchr(@NotNull CharPointer s, char c) {
|
||||
if (!ireg_ic) {
|
||||
return s.strchr(c);
|
||||
}
|
||||
@@ -3882,8 +3868,7 @@ public class RegExp {
|
||||
* <p/>
|
||||
* Returns the size of the replacement, including terminating '\u0000'.
|
||||
*/
|
||||
@Nullable
|
||||
public String vim_regsub(regmatch_T rmp, CharPointer source, int magic, boolean backslash) {
|
||||
public @Nullable String vim_regsub(regmatch_T rmp, CharPointer source, int magic, boolean backslash) {
|
||||
reg_match = rmp;
|
||||
reg_mmatch = null;
|
||||
reg_maxline = 0;
|
||||
@@ -3891,8 +3876,7 @@ public class RegExp {
|
||||
return vim_regsub_both(source, magic, backslash);
|
||||
}
|
||||
|
||||
@Nullable
|
||||
public String vim_regsub_multi(regmmatch_T rmp, int lnum, CharPointer source, int magic, boolean backslash) {
|
||||
public @Nullable String vim_regsub_multi(regmmatch_T rmp, int lnum, CharPointer source, int magic, boolean backslash) {
|
||||
reg_match = null;
|
||||
reg_mmatch = rmp;
|
||||
//reg_buf = curbuf; /* always works on the current buffer! */
|
||||
@@ -3924,8 +3908,7 @@ public class RegExp {
|
||||
return mode;
|
||||
}
|
||||
|
||||
@Nullable
|
||||
private String vim_regsub_both(@Nullable CharPointer source, int magic, boolean backslash) {
|
||||
private @Nullable String vim_regsub_both(@Nullable CharPointer source, int magic, boolean backslash) {
|
||||
CharPointer src;
|
||||
StringBuffer dst = new StringBuffer();
|
||||
CharPointer s;
|
||||
@@ -4211,8 +4194,7 @@ public class RegExp {
|
||||
/*
|
||||
* regdump - dump a regexp onto stdout in vaguely comprehensible form
|
||||
*/
|
||||
@NotNull
|
||||
private String regdump(String pattern, @NotNull regprog_T r) {
|
||||
private @NotNull String regdump(String pattern, @NotNull regprog_T r) {
|
||||
CharPointer start;
|
||||
CharPointer s;
|
||||
int op = EXACTLY; /* Arbitrary non-END op. */
|
||||
@@ -4290,8 +4272,7 @@ public class RegExp {
|
||||
/*
|
||||
* regprop - printable representation of opcode
|
||||
*/
|
||||
@NotNull
|
||||
private String regprop(@NotNull CharPointer op) {
|
||||
private @NotNull String regprop(@NotNull CharPointer op) {
|
||||
String p;
|
||||
StringBuffer buf = new StringBuffer();
|
||||
|
||||
@@ -4727,15 +4708,15 @@ public class RegExp {
|
||||
}
|
||||
}
|
||||
|
||||
@Nullable public regprog_T regprog;
|
||||
@NotNull public lpos_T[] startpos = new lpos_T[NSUBEXP];
|
||||
@NotNull public lpos_T[] endpos = new lpos_T[NSUBEXP];
|
||||
public @Nullable regprog_T regprog;
|
||||
public @NotNull lpos_T[] startpos = new lpos_T[NSUBEXP];
|
||||
public @NotNull lpos_T[] endpos = new lpos_T[NSUBEXP];
|
||||
public boolean rmm_ic;
|
||||
}
|
||||
|
||||
private int reg_do_extmatch = 0;
|
||||
|
||||
@Nullable private CharPointer reg_prev_sub = null;
|
||||
private @Nullable CharPointer reg_prev_sub = null;
|
||||
|
||||
private CharPointer regparse; /* Input-scan pointer. */
|
||||
private int prevchr_len; /* byte length of previous char */
|
||||
@@ -4744,11 +4725,11 @@ public class RegExp {
|
||||
private int regnzpar; /* \z() count. */
|
||||
private char re_has_z; /* \z item detected */
|
||||
private CharPointer regcode; /* Code-emit pointer */
|
||||
@NotNull private boolean[] had_endbrace = new boolean[NSUBEXP]; /* flags, true if end of () found */
|
||||
private @NotNull boolean[] had_endbrace = new boolean[NSUBEXP]; /* flags, true if end of () found */
|
||||
private int regflags; /* RF_ flags for prog */
|
||||
@NotNull private int[] brace_min = new int[10]; /* Minimums for complex brace repeats */
|
||||
@NotNull private int[] brace_max = new int[10]; /* Maximums for complex brace repeats */
|
||||
@NotNull private int[] brace_count = new int[10]; /* Current counts for complex brace repeats */
|
||||
private @NotNull int[] brace_min = new int[10]; /* Minimums for complex brace repeats */
|
||||
private @NotNull int[] brace_max = new int[10]; /* Maximums for complex brace repeats */
|
||||
private @NotNull int[] brace_count = new int[10]; /* Current counts for complex brace repeats */
|
||||
private boolean had_eol; /* true when EOL found by vim_regcomp() */
|
||||
private boolean one_exactly = false; /* only do one char for EXACTLY */
|
||||
|
||||
@@ -4779,7 +4760,7 @@ public class RegExp {
|
||||
|
||||
/* The current match-position is remembered with these variables: */
|
||||
private int reglnum; /* line number, relative to first line */
|
||||
@Nullable private CharPointer regline; /* start of current line */
|
||||
private @Nullable CharPointer regline; /* start of current line */
|
||||
private CharPointer reginput; /* current input, points into "regline" */
|
||||
|
||||
private boolean need_clear_subexpr; /* subexpressions still need to be
|
||||
@@ -4801,7 +4782,7 @@ public class RegExp {
|
||||
* slow, we keep one allocated piece of memory and only re-allocate it when
|
||||
* it's too small. It's freed in vim_regexec_both() when finished.
|
||||
*/
|
||||
@Nullable private CharPointer reg_tofree;
|
||||
private @Nullable CharPointer reg_tofree;
|
||||
//private int reg_tofreelen;
|
||||
|
||||
/*
|
||||
@@ -4820,12 +4801,12 @@ public class RegExp {
|
||||
* reg_firstlnum <invalid> first line in which to search
|
||||
* reg_maxline 0 last line nr
|
||||
*/
|
||||
@Nullable private regmatch_T reg_match;
|
||||
@Nullable private regmmatch_T reg_mmatch;
|
||||
@NotNull private CharPointer[] reg_startp = new CharPointer[NSUBEXP];
|
||||
@NotNull private CharPointer[] reg_endp = new CharPointer[NSUBEXP];
|
||||
@NotNull private lpos_T[] reg_startpos = new lpos_T[NSUBEXP];
|
||||
@NotNull private lpos_T[] reg_endpos = new lpos_T[NSUBEXP];
|
||||
private @Nullable regmatch_T reg_match;
|
||||
private @Nullable regmmatch_T reg_mmatch;
|
||||
private @NotNull CharPointer[] reg_startp = new CharPointer[NSUBEXP];
|
||||
private @NotNull CharPointer[] reg_endp = new CharPointer[NSUBEXP];
|
||||
private @NotNull lpos_T[] reg_startpos = new lpos_T[NSUBEXP];
|
||||
private @NotNull lpos_T[] reg_endpos = new lpos_T[NSUBEXP];
|
||||
//static win_T *reg_win;
|
||||
private Editor reg_buf;
|
||||
private int reg_firstlnum;
|
||||
@@ -4833,10 +4814,10 @@ public class RegExp {
|
||||
|
||||
private regsave_T behind_pos;
|
||||
|
||||
@NotNull private CharPointer[] reg_startzp = new CharPointer[NSUBEXP]; /* Workspace to mark beginning */
|
||||
@NotNull private CharPointer[] reg_endzp = new CharPointer[NSUBEXP]; /* and end of \z(...\) matches */
|
||||
@NotNull private lpos_T[] reg_startzpos = new lpos_T[NSUBEXP]; /* idem, beginning pos */
|
||||
@NotNull private lpos_T[] reg_endzpos = new lpos_T[NSUBEXP]; /* idem, end pos */
|
||||
private @NotNull CharPointer[] reg_startzp = new CharPointer[NSUBEXP]; /* Workspace to mark beginning */
|
||||
private @NotNull CharPointer[] reg_endzp = new CharPointer[NSUBEXP]; /* and end of \z(...\) matches */
|
||||
private @NotNull lpos_T[] reg_startzpos = new lpos_T[NSUBEXP]; /* idem, beginning pos */
|
||||
private @NotNull lpos_T[] reg_endzpos = new lpos_T[NSUBEXP]; /* idem, end pos */
|
||||
|
||||
private boolean got_int = false;
|
||||
|
||||
|
@@ -45,8 +45,7 @@ public class ClipboardHandler {
|
||||
*
|
||||
* @return The clipboard string or null if data isn't plain text
|
||||
*/
|
||||
@NotNull
|
||||
public static Pair<String, List<TextBlockTransferableData>> getClipboardTextAndTransferableData() {
|
||||
public static @NotNull Pair<String, List<TextBlockTransferableData>> getClipboardTextAndTransferableData() {
|
||||
String res = null;
|
||||
List<TextBlockTransferableData> transferableData = new ArrayList<>();
|
||||
try {
|
||||
|
@@ -50,8 +50,7 @@ public class ExEditorKit extends DefaultEditorKit {
|
||||
* @return the type
|
||||
*/
|
||||
@Override
|
||||
@NotNull
|
||||
public String getContentType() {
|
||||
public @NotNull String getContentType() {
|
||||
return "text/ideavim";
|
||||
}
|
||||
|
||||
@@ -77,13 +76,11 @@ public class ExEditorKit extends DefaultEditorKit {
|
||||
* @return the model
|
||||
*/
|
||||
@Override
|
||||
@NotNull
|
||||
public Document createDefaultDocument() {
|
||||
public @NotNull Document createDefaultDocument() {
|
||||
return new ExDocument();
|
||||
}
|
||||
|
||||
@Nullable
|
||||
private static KeyStroke convert(@NotNull ActionEvent event) {
|
||||
private static @Nullable KeyStroke convert(@NotNull ActionEvent event) {
|
||||
String cmd = event.getActionCommand();
|
||||
int mods = event.getModifiers();
|
||||
if (cmd != null && cmd.length() > 0) {
|
||||
@@ -114,7 +111,7 @@ public class ExEditorKit extends DefaultEditorKit {
|
||||
static final String StartDigraph = "start-digraph";
|
||||
static final String StartLiteral = "start-literal";
|
||||
|
||||
@NotNull private final Action[] exActions = new Action[]{
|
||||
private final @NotNull Action[] exActions = new Action[]{
|
||||
new CancelEntryAction(),
|
||||
new CompleteEntryAction(),
|
||||
new EscapeCharAction(),
|
||||
@@ -217,7 +214,7 @@ public class ExEditorKit extends DefaultEditorKit {
|
||||
WAIT_REGISTER,
|
||||
}
|
||||
|
||||
@NotNull private State state = State.SKIP_CTRL_R;
|
||||
private @NotNull State state = State.SKIP_CTRL_R;
|
||||
|
||||
InsertRegisterAction() {
|
||||
super(InsertRegister);
|
||||
@@ -308,7 +305,7 @@ public class ExEditorKit extends DefaultEditorKit {
|
||||
}
|
||||
|
||||
@SuppressWarnings("BooleanMethodIsAlwaysInverted")
|
||||
private static abstract class DeleteCharAction extends TextAction {
|
||||
private abstract static class DeleteCharAction extends TextAction {
|
||||
|
||||
DeleteCharAction(String name) {
|
||||
super(name);
|
||||
@@ -488,9 +485,8 @@ public class ExEditorKit extends DefaultEditorKit {
|
||||
}
|
||||
}
|
||||
|
||||
private static abstract class StartDigraphLiteralActionBase extends TextAction implements MultiStepAction {
|
||||
@Nullable
|
||||
private DigraphSequence digraphSequence;
|
||||
private abstract static class StartDigraphLiteralActionBase extends TextAction implements MultiStepAction {
|
||||
private @Nullable DigraphSequence digraphSequence;
|
||||
|
||||
public StartDigraphLiteralActionBase(String name) {
|
||||
super(name);
|
||||
|
@@ -236,7 +236,7 @@ public class ExEntryPanel extends JPanel implements LafManagerListener {
|
||||
}
|
||||
}
|
||||
|
||||
@NotNull private final DocumentListener incSearchDocumentListener = new DocumentAdapter() {
|
||||
private final @NotNull DocumentListener incSearchDocumentListener = new DocumentAdapter() {
|
||||
@Override
|
||||
protected void textChanged(@NotNull DocumentEvent e) {
|
||||
final Editor editor = entry.getEditor();
|
||||
@@ -284,8 +284,7 @@ public class ExEntryPanel extends JPanel implements LafManagerListener {
|
||||
}
|
||||
|
||||
@Contract("null -> null")
|
||||
@Nullable
|
||||
private ExCommand getIncsearchCommand(@Nullable String commandText) {
|
||||
private @Nullable ExCommand getIncsearchCommand(@Nullable String commandText) {
|
||||
if (commandText == null) return null;
|
||||
try {
|
||||
final ExCommand exCommand = CommandParser.getInstance().parse(commandText);
|
||||
@@ -335,13 +334,11 @@ public class ExEntryPanel extends JPanel implements LafManagerListener {
|
||||
*
|
||||
* @return The user entered text
|
||||
*/
|
||||
@NotNull
|
||||
public String getText() {
|
||||
public @NotNull String getText() {
|
||||
return entry.getActualText();
|
||||
}
|
||||
|
||||
@NotNull
|
||||
public ExTextField getEntry() {
|
||||
public @NotNull ExTextField getEntry() {
|
||||
return entry;
|
||||
}
|
||||
|
||||
@@ -421,9 +418,9 @@ public class ExEntryPanel extends JPanel implements LafManagerListener {
|
||||
private int count;
|
||||
|
||||
// UI stuff
|
||||
@Nullable private JComponent parent;
|
||||
@NotNull private final JLabel label;
|
||||
@NotNull private final ExTextField entry;
|
||||
private @Nullable JComponent parent;
|
||||
private final @NotNull JLabel label;
|
||||
private final @NotNull ExTextField entry;
|
||||
private JComponent oldGlass;
|
||||
private LayoutManager oldLayout;
|
||||
private boolean wasOpaque;
|
||||
@@ -433,7 +430,7 @@ public class ExEntryPanel extends JPanel implements LafManagerListener {
|
||||
private int horizontalOffset;
|
||||
private int caretOffset;
|
||||
|
||||
@NotNull private final ComponentListener resizePanelListener = new ComponentAdapter() {
|
||||
private final @NotNull ComponentListener resizePanelListener = new ComponentAdapter() {
|
||||
@Override
|
||||
public void componentResized(ComponentEvent e) {
|
||||
positionPanel();
|
||||
|
@@ -46,18 +46,18 @@ import java.util.List;
|
||||
* This panel displays text in a <code>more</code> like window.
|
||||
*/
|
||||
public class ExOutputPanel extends JPanel implements LafManagerListener {
|
||||
@NotNull private final Editor myEditor;
|
||||
private final @NotNull Editor myEditor;
|
||||
|
||||
@NotNull private final JLabel myLabel = new JLabel("more");
|
||||
@NotNull private final JTextArea myText = new JTextArea();
|
||||
@NotNull private final JScrollPane myScrollPane =
|
||||
private final @NotNull JLabel myLabel = new JLabel("more");
|
||||
private final @NotNull JTextArea myText = new JTextArea();
|
||||
private final @NotNull JScrollPane myScrollPane =
|
||||
new JBScrollPane(myText, JScrollPane.VERTICAL_SCROLLBAR_AS_NEEDED, JScrollPane.HORIZONTAL_SCROLLBAR_NEVER);
|
||||
@NotNull private final ComponentAdapter myAdapter;
|
||||
private final @NotNull ComponentAdapter myAdapter;
|
||||
private boolean myAtEnd = false;
|
||||
private int myLineHeight = 0;
|
||||
|
||||
@Nullable private JComponent myOldGlass = null;
|
||||
@Nullable private LayoutManager myOldLayout = null;
|
||||
private @Nullable JComponent myOldGlass = null;
|
||||
private @Nullable LayoutManager myOldLayout = null;
|
||||
private boolean myWasOpaque = false;
|
||||
|
||||
private boolean myActive = false;
|
||||
@@ -95,8 +95,7 @@ public class ExOutputPanel extends JPanel implements LafManagerListener {
|
||||
updateUI();
|
||||
}
|
||||
|
||||
@NotNull
|
||||
public static ExOutputPanel getInstance(@NotNull Editor editor) {
|
||||
public static @NotNull ExOutputPanel getInstance(@NotNull Editor editor) {
|
||||
ExOutputPanel panel = UserDataManager.getVimMorePanel(editor);
|
||||
if (panel == null) {
|
||||
panel = new ExOutputPanel(editor);
|
||||
@@ -298,7 +297,7 @@ public class ExOutputPanel extends JPanel implements LafManagerListener {
|
||||
close(null);
|
||||
}
|
||||
|
||||
private void close(@Nullable final KeyEvent e) {
|
||||
private void close(final @Nullable KeyEvent e) {
|
||||
ApplicationManager.getApplication().invokeLater(() -> {
|
||||
deactivate(true);
|
||||
|
||||
|
@@ -286,8 +286,7 @@ public class ExTextField extends JTextField {
|
||||
* @return the default model implementation
|
||||
*/
|
||||
@Override
|
||||
@NotNull
|
||||
protected Document createDefaultModel() {
|
||||
protected @NotNull Document createDefaultModel() {
|
||||
return new ExDocument();
|
||||
}
|
||||
|
||||
@@ -547,7 +546,7 @@ public class ExTextField extends JTextField {
|
||||
private String actualText;
|
||||
private List<HistoryGroup.HistoryEntry> history;
|
||||
private int histIndex = 0;
|
||||
@Nullable private ExEditorKit.MultiStepAction currentAction;
|
||||
private @Nullable ExEditorKit.MultiStepAction currentAction;
|
||||
private char currentActionPromptCharacter;
|
||||
private int currentActionPromptCharacterOffset = -1;
|
||||
|
||||
|
@@ -30,9 +30,9 @@ import java.awt.event.KeyListener;
|
||||
* @author vlan
|
||||
*/
|
||||
public class ModalEntryDialog extends JDialog {
|
||||
@NotNull private final JTextField myEntry;
|
||||
@NotNull private final JLabel myLabel;
|
||||
@NotNull private final JComponent myParent;
|
||||
private final @NotNull JTextField myEntry;
|
||||
private final @NotNull JLabel myLabel;
|
||||
private final @NotNull JComponent myParent;
|
||||
|
||||
public ModalEntryDialog(@NotNull Editor editor, @NotNull String prompt) {
|
||||
super((Frame)null, true);
|
||||
@@ -89,8 +89,7 @@ public class ModalEntryDialog extends JDialog {
|
||||
myEntry.addKeyListener(listener);
|
||||
}
|
||||
|
||||
@NotNull
|
||||
public String getText() {
|
||||
public @NotNull String getText() {
|
||||
return myEntry.getText();
|
||||
}
|
||||
}
|
||||
|
@@ -47,25 +47,22 @@ import java.util.*;
|
||||
* @author vlan
|
||||
*/
|
||||
public class VimEmulationConfigurable implements Configurable {
|
||||
@NotNull private final VimShortcutConflictsTable.Model myConflictsTableModel = new VimShortcutConflictsTable.Model();
|
||||
@NotNull private final VimSettingsPanel myPanel = new VimSettingsPanel(myConflictsTableModel);
|
||||
private final @NotNull VimShortcutConflictsTable.Model myConflictsTableModel = new VimShortcutConflictsTable.Model();
|
||||
private final @NotNull VimSettingsPanel myPanel = new VimSettingsPanel(myConflictsTableModel);
|
||||
|
||||
@NotNull
|
||||
@Nls
|
||||
@Override
|
||||
public String getDisplayName() {
|
||||
public @NotNull String getDisplayName() {
|
||||
return "Vim Emulation";
|
||||
}
|
||||
|
||||
@Nullable
|
||||
@Override
|
||||
public String getHelpTopic() {
|
||||
public @Nullable String getHelpTopic() {
|
||||
return null;
|
||||
}
|
||||
|
||||
@Nullable
|
||||
@Override
|
||||
public JComponent createComponent() {
|
||||
public @Nullable JComponent createComponent() {
|
||||
return myPanel;
|
||||
}
|
||||
|
||||
@@ -89,7 +86,7 @@ public class VimEmulationConfigurable implements Configurable {
|
||||
}
|
||||
|
||||
private static final class VimSettingsPanel extends JPanel {
|
||||
@NotNull private final VimShortcutConflictsTable myShortcutConflictsTable;
|
||||
private final @NotNull VimShortcutConflictsTable myShortcutConflictsTable;
|
||||
|
||||
public VimSettingsPanel(@NotNull VimShortcutConflictsTable.Model model) {
|
||||
myShortcutConflictsTable = new VimShortcutConflictsTable(model);
|
||||
@@ -116,20 +113,17 @@ public class VimEmulationConfigurable implements Configurable {
|
||||
ownerColumn.setCellEditor(renderer);
|
||||
}
|
||||
|
||||
@NotNull
|
||||
@Override
|
||||
public Dimension getMinimumSize() {
|
||||
public @NotNull Dimension getMinimumSize() {
|
||||
return calcSize(super.getMinimumSize());
|
||||
}
|
||||
|
||||
@NotNull
|
||||
@Override
|
||||
public Dimension getPreferredSize() {
|
||||
public @NotNull Dimension getPreferredSize() {
|
||||
return calcSize(super.getPreferredSize());
|
||||
}
|
||||
|
||||
@NotNull
|
||||
private Dimension calcSize(@NotNull Dimension dimension) {
|
||||
private @NotNull Dimension calcSize(@NotNull Dimension dimension) {
|
||||
final Container container = getParent();
|
||||
if (container != null) {
|
||||
final Dimension size = container.getSize();
|
||||
@@ -138,8 +132,7 @@ public class VimEmulationConfigurable implements Configurable {
|
||||
return dimension;
|
||||
}
|
||||
|
||||
@NotNull
|
||||
private TableColumn getTableColumn(@NotNull Column column) {
|
||||
private @NotNull TableColumn getTableColumn(@NotNull Column column) {
|
||||
return getColumnModel().getColumn(column.getIndex());
|
||||
}
|
||||
|
||||
@@ -167,7 +160,7 @@ public class VimEmulationConfigurable implements Configurable {
|
||||
IDE_ACTION(1, "IDE Action"),
|
||||
OWNER(2, "Handler");
|
||||
|
||||
@NotNull private static final Map<Integer, Column> ourMembers = new HashMap<>();
|
||||
private static final @NotNull Map<Integer, Column> ourMembers = new HashMap<>();
|
||||
|
||||
static {
|
||||
for (Column column : values()) {
|
||||
@@ -176,15 +169,14 @@ public class VimEmulationConfigurable implements Configurable {
|
||||
}
|
||||
|
||||
private final int myIndex;
|
||||
@NotNull private final String myTitle;
|
||||
private final @NotNull String myTitle;
|
||||
|
||||
Column(int index, @NotNull String title) {
|
||||
myIndex = index;
|
||||
myTitle = title;
|
||||
}
|
||||
|
||||
@Nullable
|
||||
public static Column fromIndex(int index) {
|
||||
public static @Nullable Column fromIndex(int index) {
|
||||
return ourMembers.get(index);
|
||||
}
|
||||
|
||||
@@ -192,16 +184,15 @@ public class VimEmulationConfigurable implements Configurable {
|
||||
return myIndex;
|
||||
}
|
||||
|
||||
@NotNull
|
||||
public String getTitle() {
|
||||
public @NotNull String getTitle() {
|
||||
return myTitle;
|
||||
}
|
||||
}
|
||||
|
||||
private static final class Row implements Comparable<Row> {
|
||||
@NotNull private final KeyStroke myKeyStroke;
|
||||
@NotNull private final AnAction myAction;
|
||||
@NotNull private ShortcutOwner myOwner;
|
||||
private final @NotNull KeyStroke myKeyStroke;
|
||||
private final @NotNull AnAction myAction;
|
||||
private @NotNull ShortcutOwner myOwner;
|
||||
|
||||
private Row(@NotNull KeyStroke keyStroke, @NotNull AnAction action, @NotNull ShortcutOwner owner) {
|
||||
myKeyStroke = keyStroke;
|
||||
@@ -209,18 +200,15 @@ public class VimEmulationConfigurable implements Configurable {
|
||||
myOwner = owner;
|
||||
}
|
||||
|
||||
@NotNull
|
||||
public KeyStroke getKeyStroke() {
|
||||
public @NotNull KeyStroke getKeyStroke() {
|
||||
return myKeyStroke;
|
||||
}
|
||||
|
||||
@NotNull
|
||||
public AnAction getAction() {
|
||||
public @NotNull AnAction getAction() {
|
||||
return myAction;
|
||||
}
|
||||
|
||||
@NotNull
|
||||
public ShortcutOwner getOwner() {
|
||||
public @NotNull ShortcutOwner getOwner() {
|
||||
return myOwner;
|
||||
}
|
||||
|
||||
@@ -237,7 +225,7 @@ public class VimEmulationConfigurable implements Configurable {
|
||||
}
|
||||
|
||||
private static final class Model extends AbstractTableModel {
|
||||
@NotNull private final List<Row> myRows = new ArrayList<>();
|
||||
private final @NotNull List<Row> myRows = new ArrayList<>();
|
||||
|
||||
public Model() {
|
||||
reset();
|
||||
@@ -253,9 +241,8 @@ public class VimEmulationConfigurable implements Configurable {
|
||||
return Column.values().length;
|
||||
}
|
||||
|
||||
@Nullable
|
||||
@Override
|
||||
public Object getValueAt(int rowIndex, int columnIndex) {
|
||||
public @Nullable Object getValueAt(int rowIndex, int columnIndex) {
|
||||
final Column column = Column.fromIndex(columnIndex);
|
||||
if (column != null && rowIndex >= 0 && rowIndex < myRows.size()) {
|
||||
final Row row = myRows.get(rowIndex);
|
||||
@@ -285,9 +272,8 @@ public class VimEmulationConfigurable implements Configurable {
|
||||
return Column.fromIndex(columnIndex) == Column.OWNER;
|
||||
}
|
||||
|
||||
@Nullable
|
||||
@Override
|
||||
public String getColumnName(int index) {
|
||||
public @Nullable String getColumnName(int index) {
|
||||
final Column column = Column.fromIndex(index);
|
||||
return column != null ? column.getTitle() : null;
|
||||
}
|
||||
@@ -312,8 +298,7 @@ public class VimEmulationConfigurable implements Configurable {
|
||||
Collections.sort(myRows);
|
||||
}
|
||||
|
||||
@NotNull
|
||||
private Map<KeyStroke, ShortcutOwner> getCurrentData() {
|
||||
private @NotNull Map<KeyStroke, ShortcutOwner> getCurrentData() {
|
||||
final Map<KeyStroke, ShortcutOwner> result = new HashMap<>();
|
||||
for (Row row : myRows) {
|
||||
result.put(row.getKeyStroke(), row.getOwner());
|
||||
|
@@ -21,7 +21,13 @@ package org.jetbrains.plugins.ideavim
|
||||
import com.maddyhome.idea.vim.RegisterActions.VIM_ACTIONS_EP
|
||||
import com.maddyhome.idea.vim.VimPlugin
|
||||
import com.maddyhome.idea.vim.command.CommandState
|
||||
import com.maddyhome.idea.vim.command.MappingMode
|
||||
import com.maddyhome.idea.vim.handler.ActionBeanClass
|
||||
import com.maddyhome.idea.vim.helper.StringHelper
|
||||
import com.maddyhome.idea.vim.key.CommandNode
|
||||
import com.maddyhome.idea.vim.key.CommandPartNode
|
||||
import junit.framework.TestCase
|
||||
import javax.swing.KeyStroke
|
||||
|
||||
class RegisterActionsTest : VimTestCase() {
|
||||
fun `test simple action`() {
|
||||
@@ -65,10 +71,24 @@ class RegisterActionsTest : VimTestCase() {
|
||||
val keys = StringHelper.parseKeys("l")
|
||||
val before = "I ${c}found it in a legendary land"
|
||||
val after = "I f${c}ound it in a legendary land"
|
||||
var motionRightAction: ActionBeanClass? = null
|
||||
doTest(keys, before, after, CommandState.Mode.COMMAND, CommandState.SubMode.NONE) {
|
||||
val motionRightAction = VIM_ACTIONS_EP.extensions().findAny().get();
|
||||
motionRightAction = VIM_ACTIONS_EP.extensions().findAny().get();
|
||||
|
||||
TestCase.assertNotNull(getCommandNode())
|
||||
|
||||
@Suppress("DEPRECATION")
|
||||
VIM_ACTIONS_EP.getPoint(null).unregisterExtension(motionRightAction)
|
||||
}
|
||||
VIM_ACTIONS_EP.getPoint(null).unregisterExtension(motionRightAction!!)
|
||||
TestCase.assertNull(getCommandNode())
|
||||
}
|
||||
@Suppress("DEPRECATION")
|
||||
VIM_ACTIONS_EP.getPoint(null).registerExtension(motionRightAction!!)
|
||||
TestCase.assertNotNull(getCommandNode())
|
||||
}
|
||||
|
||||
private fun getCommandNode(): CommandNode? {
|
||||
// TODO: 08.02.2020 Sorry if your tests will fail because of this test
|
||||
val node = VimPlugin.getKey().getKeyRoot(MappingMode.NORMAL)[KeyStroke.getKeyStroke('g')] as CommandPartNode
|
||||
return node[KeyStroke.getKeyStroke('T')] as CommandNode?
|
||||
}
|
||||
}
|
@@ -136,6 +136,12 @@ public abstract class VimTestCase extends UsefulTestCase {
|
||||
return myFixture.getEditor();
|
||||
}
|
||||
|
||||
@NotNull
|
||||
protected Editor configureByFileName(@NotNull String fileName) {
|
||||
myFixture.configureByText(fileName, "\n");
|
||||
return myFixture.getEditor();
|
||||
}
|
||||
|
||||
@NotNull
|
||||
protected Editor configureByJavaText(@NotNull String content) {
|
||||
myFixture.configureByText(JavaFileType.INSTANCE, content);
|
||||
|
@@ -292,6 +292,27 @@ public class MotionActionTest extends VimTestCase {
|
||||
myFixture.checkResult("a{<caret>}b}");
|
||||
}
|
||||
|
||||
// VIM-1008 |c| |v_i{|
|
||||
public void testDeleteInsideDoubleQuotesSurroundedBlockWithSingleQuote() {
|
||||
configureByText("\"{do<caret>esn't work}\"");
|
||||
typeText(parseKeys("ci{"));
|
||||
myFixture.checkResult("\"{<caret>}\"");
|
||||
}
|
||||
|
||||
// VIM-1008 |c| |v_i{|
|
||||
public void testDeleteInsideSingleQuotesSurroundedBlock() {
|
||||
configureByText("'{does n<caret>ot work}'");
|
||||
typeText(parseKeys("ci{"));
|
||||
myFixture.checkResult("'{<caret>}'");
|
||||
}
|
||||
|
||||
// VIM-1008 |c| |v_i{|
|
||||
public void testDeleteInsideDoublySurroundedBlock() {
|
||||
configureByText("<p class=\"{{ $ctrl.so<caret>meClassName }}\"></p>");
|
||||
typeText(parseKeys("ci{"));
|
||||
myFixture.checkResult("<p class=\"{{<caret>}}\"></p>");
|
||||
}
|
||||
|
||||
// |d| |v_i>|
|
||||
public void testDeleteInnerAngleBracketBlock() {
|
||||
typeTextInFile(parseKeys("di>"),
|
||||
@@ -348,6 +369,31 @@ public class MotionActionTest extends VimTestCase {
|
||||
myFixture.checkResult("foo = [\"\", \"two\", \"three\"];\n");
|
||||
}
|
||||
|
||||
public void testDeleteDoubleQuotedStringOddNumberOfQuotes() {
|
||||
typeTextInFile(parseKeys("di\""),
|
||||
"abc\"def<caret>\"gh\"i");
|
||||
myFixture.checkResult("abc\"\"gh\"i");
|
||||
}
|
||||
|
||||
public void testDeleteDoubleQuotedStringBetweenEvenNumberOfQuotes() {
|
||||
typeTextInFile(parseKeys("di\""),
|
||||
"abc\"def\"g<caret>h\"ijk\"l");
|
||||
myFixture.checkResult("abc\"def\"\"ijk\"l");
|
||||
}
|
||||
|
||||
public void testDeleteDoubleQuotedStringOddNumberOfQuotesOnLast() {
|
||||
typeTextInFile(parseKeys("di\""),
|
||||
"abcdef\"gh\"ij<caret>\"kl");
|
||||
myFixture.checkResult("abcdef\"gh\"ij\"kl");
|
||||
}
|
||||
|
||||
public void testDeleteDoubleQuotedStringEvenNumberOfQuotesOnLast() {
|
||||
typeTextInFile(parseKeys("di\""),
|
||||
"abc\"def\"gh\"ij<caret>\"kl");
|
||||
myFixture.checkResult("abc\"def\"gh\"\"kl");
|
||||
}
|
||||
|
||||
|
||||
// VIM-132 |v_i"|
|
||||
public void testInnerDoubleQuotedStringSelection() {
|
||||
typeTextInFile(parseKeys("vi\""),
|
||||
|
@@ -102,6 +102,22 @@ class MotionInnerBlockParenActionTest : VimTestCase() {
|
||||
myFixture.checkResult("foo()\n")
|
||||
}
|
||||
|
||||
// VIM-1008 |d| |v_ib|
|
||||
fun testDeleteInnerBlockWithQuote() {
|
||||
typeTextInFile(parseKeys("di)"),
|
||||
"(abc${c}def'ghi)"
|
||||
)
|
||||
myFixture.checkResult("()")
|
||||
}
|
||||
|
||||
// VIM-1008 |d| |v_ib|
|
||||
fun testDeleteInnerBlockWithDoubleQuote() {
|
||||
typeTextInFile(parseKeys("di)"),
|
||||
"""(abc${c}def"ghi)"""
|
||||
)
|
||||
myFixture.checkResult("()")
|
||||
}
|
||||
|
||||
// VIM-326 |d| |v_ib|
|
||||
fun testDeleteInnerBlockCaretBeforeString() {
|
||||
typeTextInFile(parseKeys("di)"),
|
||||
|
@@ -20,8 +20,12 @@ package org.jetbrains.plugins.ideavim.ex
|
||||
|
||||
import com.maddyhome.idea.vim.VimPlugin
|
||||
import com.maddyhome.idea.vim.command.CommandState
|
||||
import com.maddyhome.idea.vim.ex.CommandParser
|
||||
import com.maddyhome.idea.vim.ex.CommandParser.EX_COMMAND_EP
|
||||
import com.maddyhome.idea.vim.ex.ExBeanClass
|
||||
import com.maddyhome.idea.vim.ex.ExCommand
|
||||
import com.maddyhome.idea.vim.ex.commands
|
||||
import com.maddyhome.idea.vim.ex.ranges.Ranges
|
||||
import junit.framework.TestCase
|
||||
import org.jetbrains.plugins.ideavim.VimTestCase
|
||||
|
||||
@@ -91,10 +95,21 @@ class CommandParserTest : VimTestCase() {
|
||||
val keys = commandToKeys(">>")
|
||||
val before = "I ${c}found it in a legendary land"
|
||||
val after = " ${c}I found it in a legendary land"
|
||||
var extension: ExBeanClass? = null
|
||||
doTest(keys, before, after, CommandState.Mode.COMMAND, CommandState.SubMode.NONE) {
|
||||
val extension = EX_COMMAND_EP.extensions().findFirst().get()
|
||||
extension = EX_COMMAND_EP.extensions().findFirst().get()
|
||||
|
||||
// TODO: 08.02.2020 I'm sorry if your tests have been failed because of this code. Please update it properly
|
||||
TestCase.assertNotNull(CommandParser.getInstance().getCommandHandler(ExCommand(Ranges(), "actionlist", "")))
|
||||
|
||||
@Suppress("DEPRECATION")
|
||||
EX_COMMAND_EP.getPoint(null).unregisterExtension(extension)
|
||||
}
|
||||
EX_COMMAND_EP.getPoint(null).unregisterExtension(extension!!)
|
||||
|
||||
TestCase.assertNull(CommandParser.getInstance().getCommandHandler(ExCommand(Ranges(), "actionlist", "")))
|
||||
}
|
||||
@Suppress("DEPRECATION")
|
||||
EX_COMMAND_EP.getPoint(null).registerExtension(extension!!)
|
||||
|
||||
TestCase.assertNotNull(CommandParser.getInstance().getCommandHandler(ExCommand(Ranges(), "actionlist", "")))
|
||||
}
|
||||
}
|
||||
|
@@ -0,0 +1,109 @@
|
||||
/*
|
||||
* IdeaVim - Vim emulator for IDEs based on the IntelliJ platform
|
||||
* Copyright (C) 2003-2020 The IdeaVim authors
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 2 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package org.jetbrains.plugins.ideavim.ex.handler
|
||||
|
||||
import com.maddyhome.idea.vim.ex.ExOutputModel.Companion.getInstance
|
||||
import com.maddyhome.idea.vim.helper.StringHelper
|
||||
import junit.framework.TestCase
|
||||
import org.jetbrains.plugins.ideavim.VimTestCase
|
||||
|
||||
/**
|
||||
* @author John Weigel
|
||||
*/
|
||||
class BufferListHandlerTest : VimTestCase() {
|
||||
companion object {
|
||||
const val DEFAULT_LS_OUTPUT = " 1 %a \"/src/aaa.txt\" line: 1"
|
||||
}
|
||||
|
||||
fun testLsAction() {
|
||||
configureByText("\n")
|
||||
typeText(commandToKeys("ls"))
|
||||
|
||||
val output = getInstance(myFixture.editor).text
|
||||
TestCase.assertNotNull(output)
|
||||
val displayedLines = output!!.split("\n".toRegex()).toTypedArray()
|
||||
TestCase.assertEquals(DEFAULT_LS_OUTPUT, displayedLines[0])
|
||||
|
||||
assertPluginError(false)
|
||||
}
|
||||
|
||||
fun testLsActionWithLongFileName() {
|
||||
configureByFileName("aaaaaaaaaaaaaaaaaaaaaaaaaaaaa.txt")
|
||||
typeText(commandToKeys("ls"))
|
||||
|
||||
val output = getInstance(myFixture.editor).text
|
||||
TestCase.assertNotNull(output)
|
||||
val displayedLines = output!!.split("\n".toRegex()).toTypedArray()
|
||||
TestCase.assertEquals(" 1 %a \"/src/aaaaaaaaaaaaaaaaaaaaaaaaaaaaa.txt\" line: 1", displayedLines[0])
|
||||
|
||||
assertPluginError(false)
|
||||
}
|
||||
|
||||
fun testFilesAction() {
|
||||
configureByText("\n")
|
||||
typeText(commandToKeys("files"))
|
||||
assertPluginError(false)
|
||||
}
|
||||
|
||||
fun testBuffersAction() {
|
||||
configureByText("\n")
|
||||
typeText(commandToKeys("buffers"))
|
||||
assertPluginError(false)
|
||||
}
|
||||
|
||||
fun testBuffersActionWithSupportedFilterMatch() {
|
||||
configureByFileName("aaa.txt")
|
||||
configureByFileName("bbb.txt")
|
||||
typeText(StringHelper.parseKeys("aa<esc>:buffers +<enter>"))
|
||||
|
||||
val output = getInstance(myFixture.editor).text
|
||||
TestCase.assertNotNull(output)
|
||||
val displayedLines = output!!.split("\n".toRegex()).toTypedArray()
|
||||
|
||||
// Ignore buffer number because IJ sometimes returns different order of buffers
|
||||
val line = displayedLines[0].replaceRange(3, 4, "_")
|
||||
TestCase.assertEquals(" _ %a + \"/src/bbb.txt\" line: 1", line)
|
||||
|
||||
assertPluginError(false)
|
||||
}
|
||||
|
||||
fun testBuffersActionWithSupportedFilterDoesNotMatch() {
|
||||
configureByText("\n")
|
||||
typeText(StringHelper.parseKeys("aa<esc>:buffers #<enter>"))
|
||||
|
||||
val output = getInstance(myFixture.editor).text
|
||||
TestCase.assertNotNull(output)
|
||||
val displayedLines = output!!.split("\n".toRegex()).toTypedArray()
|
||||
TestCase.assertEquals("", displayedLines[0])
|
||||
|
||||
assertPluginError(false)
|
||||
}
|
||||
|
||||
fun testBuffersActionWithUnSupportedFilter() {
|
||||
configureByText("\n")
|
||||
typeText(commandToKeys("buffers x"))
|
||||
|
||||
val output = getInstance(myFixture.editor).text
|
||||
TestCase.assertNotNull(output)
|
||||
val displayedLines = output!!.split("\n".toRegex()).toTypedArray()
|
||||
TestCase.assertEquals(DEFAULT_LS_OUTPUT, displayedLines[0])
|
||||
|
||||
assertPluginError(false)
|
||||
}
|
||||
}
|
@@ -20,13 +20,14 @@ package org.jetbrains.plugins.ideavim.extension
|
||||
|
||||
import com.intellij.openapi.actionSystem.DataContext
|
||||
import com.intellij.openapi.editor.Editor
|
||||
import com.intellij.testFramework.UsefulTestCase
|
||||
import com.maddyhome.idea.vim.VimPlugin
|
||||
import com.maddyhome.idea.vim.command.CommandState
|
||||
import com.maddyhome.idea.vim.command.MappingMode
|
||||
import com.maddyhome.idea.vim.extension.VimExtension
|
||||
import com.maddyhome.idea.vim.extension.VimExtensionFacade.putExtensionHandlerMapping
|
||||
import com.maddyhome.idea.vim.extension.VimExtensionFacade.putKeyMapping
|
||||
import com.maddyhome.idea.vim.extension.VimExtensionHandler
|
||||
import com.maddyhome.idea.vim.extension.VimNonDisposableExtension
|
||||
import com.maddyhome.idea.vim.group.MotionGroup
|
||||
import com.maddyhome.idea.vim.helper.StringHelper.parseKeys
|
||||
import com.maddyhome.idea.vim.helper.isEndAllowed
|
||||
@@ -36,11 +37,14 @@ import org.jetbrains.plugins.ideavim.VimTestCase
|
||||
class OpMappingTest : VimTestCase() {
|
||||
private var initialized = false
|
||||
|
||||
private val extension = TestExtension()
|
||||
|
||||
override fun setUp() {
|
||||
super.setUp()
|
||||
if (!initialized) {
|
||||
initialized = true
|
||||
TestExtension().init()
|
||||
VimExtension.EP_NAME.getPoint(null).registerExtension(extension)
|
||||
enableExtensions("TestExtension")
|
||||
}
|
||||
}
|
||||
|
||||
@@ -87,21 +91,70 @@ class OpMappingTest : VimTestCase() {
|
||||
CommandState.Mode.COMMAND,
|
||||
CommandState.SubMode.NONE)
|
||||
}
|
||||
|
||||
fun `test disable extension via set`() {
|
||||
configureByText("${c}I found it in a legendary land")
|
||||
typeText(parseKeys("Q"))
|
||||
myFixture.checkResult("I${c} found it in a legendary land")
|
||||
|
||||
enterCommand("set noTestExtension")
|
||||
typeText(parseKeys("Q"))
|
||||
myFixture.checkResult("I${c} found it in a legendary land")
|
||||
|
||||
enterCommand("set TestExtension")
|
||||
typeText(parseKeys("Q"))
|
||||
myFixture.checkResult("I ${c}found it in a legendary land")
|
||||
}
|
||||
|
||||
private class TestExtension : VimNonDisposableExtension() {
|
||||
fun `test disable extension as extension point`() {
|
||||
configureByText("${c}I found it in a legendary land")
|
||||
typeText(parseKeys("Q"))
|
||||
myFixture.checkResult("I${c} found it in a legendary land")
|
||||
|
||||
VimExtension.EP_NAME.getPoint(null).unregisterExtension(TestExtension::class.java)
|
||||
UsefulTestCase.assertEmpty(VimPlugin.getKey().getKeyMappingByOwner(extension.owner))
|
||||
typeText(parseKeys("Q"))
|
||||
myFixture.checkResult("I${c} found it in a legendary land")
|
||||
|
||||
VimExtension.EP_NAME.getPoint(null).registerExtension(extension)
|
||||
UsefulTestCase.assertEmpty(VimPlugin.getKey().getKeyMappingByOwner(extension.owner))
|
||||
enableExtensions("TestExtension")
|
||||
typeText(parseKeys("Q"))
|
||||
myFixture.checkResult("I ${c}found it in a legendary land")
|
||||
}
|
||||
|
||||
fun `test disable disposed extension`() {
|
||||
configureByText("${c}I found it in a legendary land")
|
||||
typeText(parseKeys("Q"))
|
||||
myFixture.checkResult("I${c} found it in a legendary land")
|
||||
|
||||
enterCommand("set noTestExtension")
|
||||
VimExtension.EP_NAME.getPoint(null).unregisterExtension(TestExtension::class.java)
|
||||
typeText(parseKeys("Q"))
|
||||
myFixture.checkResult("I${c} found it in a legendary land")
|
||||
|
||||
VimExtension.EP_NAME.getPoint(null).registerExtension(extension)
|
||||
enableExtensions("TestExtension")
|
||||
typeText(parseKeys("Q"))
|
||||
myFixture.checkResult("I ${c}found it in a legendary land")
|
||||
}
|
||||
}
|
||||
|
||||
private class TestExtension : VimExtension {
|
||||
override fun getName(): String = "TestExtension"
|
||||
|
||||
override fun initOnce() {
|
||||
putExtensionHandlerMapping(MappingMode.O, parseKeys("<Plug>TestExtensionEmulateInclusive"), MoveEmulateInclusive(), false)
|
||||
putExtensionHandlerMapping(MappingMode.O, parseKeys("<Plug>TestExtensionBackwardsCharacter"), MoveBackwards(), false)
|
||||
putExtensionHandlerMapping(MappingMode.O, parseKeys("<Plug>TestExtensionCharacter"), Move(), false)
|
||||
putExtensionHandlerMapping(MappingMode.O, parseKeys("<Plug>TestExtensionLinewise"), MoveLinewise(), false)
|
||||
override fun init() {
|
||||
putExtensionHandlerMapping(MappingMode.O, parseKeys("<Plug>TestExtensionEmulateInclusive"), owner, MoveEmulateInclusive(), false)
|
||||
putExtensionHandlerMapping(MappingMode.O, parseKeys("<Plug>TestExtensionBackwardsCharacter"), owner, MoveBackwards(), false)
|
||||
putExtensionHandlerMapping(MappingMode.O, parseKeys("<Plug>TestExtensionCharacter"), owner, Move(), false)
|
||||
putExtensionHandlerMapping(MappingMode.O, parseKeys("<Plug>TestExtensionLinewise"), owner, MoveLinewise(), false)
|
||||
putExtensionHandlerMapping(MappingMode.N, parseKeys("<Plug>TestMotion"), owner, MoveLinewiseInNormal(), false)
|
||||
|
||||
putKeyMapping(MappingMode.O, parseKeys("U"), parseKeys("<Plug>TestExtensionEmulateInclusive"), true)
|
||||
putKeyMapping(MappingMode.O, parseKeys("P"), parseKeys("<Plug>TestExtensionBackwardsCharacter"), true)
|
||||
putKeyMapping(MappingMode.O, parseKeys("I"), parseKeys("<Plug>TestExtensionCharacter"), true)
|
||||
putKeyMapping(MappingMode.O, parseKeys("O"), parseKeys("<Plug>TestExtensionLinewise"), true)
|
||||
putKeyMapping(MappingMode.O, parseKeys("U"), owner, parseKeys("<Plug>TestExtensionEmulateInclusive"), true)
|
||||
putKeyMapping(MappingMode.O, parseKeys("P"), owner, parseKeys("<Plug>TestExtensionBackwardsCharacter"), true)
|
||||
putKeyMapping(MappingMode.O, parseKeys("I"), owner, parseKeys("<Plug>TestExtensionCharacter"), true)
|
||||
putKeyMapping(MappingMode.O, parseKeys("O"), owner, parseKeys("<Plug>TestExtensionLinewise"), true)
|
||||
putKeyMapping(MappingMode.N, parseKeys("Q"), owner, parseKeys("<Plug>TestMotion"), true)
|
||||
}
|
||||
|
||||
private class MoveEmulateInclusive : VimExtensionHandler {
|
||||
@@ -133,4 +186,12 @@ private class TestExtension : VimNonDisposableExtension() {
|
||||
MotionGroup.moveCaret(editor, caret, newOffset)
|
||||
}
|
||||
}
|
||||
|
||||
private class MoveLinewiseInNormal : VimExtensionHandler {
|
||||
override fun execute(editor: Editor, context: DataContext) {
|
||||
val caret = editor.caretModel.currentCaret
|
||||
val newOffset = VimPlugin.getMotion().moveCaretHorizontal(editor, caret, 1, true)
|
||||
MotionGroup.moveCaret(editor, caret, newOffset)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@@ -0,0 +1,144 @@
|
||||
/*
|
||||
* IdeaVim - Vim emulator for IDEs based on the IntelliJ platform
|
||||
* Copyright (C) 2003-2020 The IdeaVim authors
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 2 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package org.jetbrains.plugins.ideavim.extension.entiretextobj
|
||||
|
||||
import com.maddyhome.idea.vim.command.CommandState
|
||||
import com.maddyhome.idea.vim.helper.StringHelper
|
||||
import org.jetbrains.plugins.ideavim.JavaVimTestCase
|
||||
|
||||
/**
|
||||
* @author Alexandre Grison (@agrison)
|
||||
*/
|
||||
class VimTextObjEntireExtensionTest : JavaVimTestCase() {
|
||||
override fun setUp() {
|
||||
super.setUp()
|
||||
enableExtensions("textobj-entire")
|
||||
}
|
||||
|
||||
// |gU| |ae|
|
||||
fun testUpperCaseEntireBuffer() {
|
||||
doTest(StringHelper.parseKeys("gUae"), poem,"<caret>${poemUC}")
|
||||
assertMode(CommandState.Mode.COMMAND)
|
||||
assertSelection(null)
|
||||
}
|
||||
|
||||
// |gu| |ae|
|
||||
fun testLowerCaseEntireBuffer() {
|
||||
doTest(StringHelper.parseKeys("guae"), poem, "<caret>${poemLC}");
|
||||
assertMode(CommandState.Mode.COMMAND)
|
||||
assertSelection(null)
|
||||
}
|
||||
|
||||
// |c| |ae|
|
||||
fun testChangeEntireBuffer() {
|
||||
doTest(StringHelper.parseKeys("cae"), poem, "<caret>");
|
||||
assertMode(CommandState.Mode.INSERT);
|
||||
assertSelection(null);
|
||||
}
|
||||
|
||||
// |d| |ae|
|
||||
fun testDeleteEntireBuffer() {
|
||||
doTest(StringHelper.parseKeys("dae"), poem, "<caret>");
|
||||
assertMode(CommandState.Mode.COMMAND);
|
||||
assertSelection(null);
|
||||
}
|
||||
|
||||
// |y| |ae|
|
||||
fun testYankEntireBuffer() {
|
||||
doTest(StringHelper.parseKeys("yae"), poem, "<caret>${poemNoCaret}");
|
||||
assertMode(CommandState.Mode.COMMAND);
|
||||
myFixture.checkResult(poemNoCaret);
|
||||
assertSelection(null);
|
||||
}
|
||||
|
||||
|
||||
// |gU| |ie|
|
||||
fun testUpperCaseEntireBufferIgnoreLeadingTrailing() {
|
||||
doTest(StringHelper.parseKeys("gUie"),
|
||||
"\n \n \n${poem}\n \n \n",
|
||||
"\n \n \n<caret>${poemUC}\n \n \n")
|
||||
assertMode(CommandState.Mode.COMMAND)
|
||||
assertSelection(null)
|
||||
}
|
||||
|
||||
// |gu| |ae|
|
||||
fun testLowerCaseEntireBufferIgnoreLeadingTrailing() {
|
||||
doTest(StringHelper.parseKeys("guie"),
|
||||
"\n \n \n${poem}\n \n \n",
|
||||
"\n \n \n<caret>${poemLC}\n \n \n")
|
||||
assertMode(CommandState.Mode.COMMAND)
|
||||
assertSelection(null)
|
||||
}
|
||||
|
||||
// |c| |ae|
|
||||
fun testChangeEntireBufferIgnoreLeadingTrailing() {
|
||||
doTest(StringHelper.parseKeys("cie"),
|
||||
"\n \n \n${poem}\n \n \n",
|
||||
"\n \n \n<caret>\n\n \n \n"); // additional \n because poem ends with a \n
|
||||
assertMode(CommandState.Mode.INSERT);
|
||||
assertSelection(null);
|
||||
}
|
||||
|
||||
// |d| |ae|
|
||||
fun testDeleteEntireBufferIgnoreLeadingTrailing() {
|
||||
doTest(StringHelper.parseKeys("die"),
|
||||
"\n \n \n${poem}\n \n \n",
|
||||
"\n \n \n<caret>\n\n \n \n"); // additional \n because poem ends with a \n
|
||||
assertMode(CommandState.Mode.COMMAND);
|
||||
assertSelection(null);
|
||||
}
|
||||
|
||||
// |y| |ae|
|
||||
fun testYankEntireBufferIgnoreLeadingTrailing() {
|
||||
doTest(StringHelper.parseKeys("yie"),
|
||||
"\n \n \n${poem}\n \n \n",
|
||||
"\n \n \n<caret>${poemNoCaret}\n \n \n");
|
||||
assertMode(CommandState.Mode.COMMAND);
|
||||
myFixture.checkResult("\n \n \n${poemNoCaret}\n \n \n");
|
||||
assertSelection(null);
|
||||
}
|
||||
|
||||
val poem: String = """Two roads diverged in a yellow wood,
|
||||
And sorry I could not travel both
|
||||
And be one traveler, long I stood
|
||||
And looked down one as far as I could
|
||||
To where it bent in the undergrowth;
|
||||
|
||||
Then took the other, as just as fair,
|
||||
And having perhaps the better claim,
|
||||
Because it was grassy and wanted wear;
|
||||
Though as for that the passing there
|
||||
Had worn them really about the same,
|
||||
|
||||
And both that morning equally lay
|
||||
In leaves no step had trodden black.
|
||||
Oh, I kept the first for another day!
|
||||
Yet knowing how way leads on to way,
|
||||
<caret>I doubted if I should ever come back.
|
||||
|
||||
I shall be telling this with a sigh
|
||||
Somewhere ages and ages hence:
|
||||
Two roads diverged in a wood, and I—
|
||||
I took the one less traveled by,
|
||||
And that has made all the difference.
|
||||
"""
|
||||
val poemNoCaret = poem.replace("<caret>", "")
|
||||
val poemUC = poemNoCaret.toUpperCase()
|
||||
val poemLC = poemNoCaret.toLowerCase()
|
||||
}
|
@@ -0,0 +1,299 @@
|
||||
package org.jetbrains.plugins.ideavim.extesion.argtextobj;
|
||||
|
||||
import com.maddyhome.idea.vim.command.CommandState;
|
||||
import com.maddyhome.idea.vim.helper.VimBehaviorDiffers;
|
||||
import org.jetbrains.plugins.ideavim.VimTestCase;
|
||||
|
||||
import java.util.Collections;
|
||||
|
||||
import static com.maddyhome.idea.vim.helper.StringHelper.parseKeys;
|
||||
|
||||
public class VimArgTextObjExtensionTest extends VimTestCase {
|
||||
|
||||
@Override
|
||||
protected void setUp() throws Exception {
|
||||
super.setUp();
|
||||
enableExtensions("argtextobj");
|
||||
}
|
||||
|
||||
public void testDeleteAnArgument() {
|
||||
doTest(parseKeys("daa"),
|
||||
"function(int arg1, char<caret>* arg2=\"a,b,c(d,e)\")",
|
||||
"function(int arg1<caret>)",
|
||||
CommandState.Mode.COMMAND, CommandState.SubMode.NONE);
|
||||
doTest(parseKeys("daa"),
|
||||
"function(int arg1<caret>)",
|
||||
"function(<caret>)",
|
||||
CommandState.Mode.COMMAND, CommandState.SubMode.NONE);
|
||||
}
|
||||
|
||||
public void testChangeInnerArgument() {
|
||||
doTest(parseKeys("cia"),
|
||||
"function(int arg1, char<caret>* arg2=\"a,b,c(d,e)\")",
|
||||
"function(int arg1, <caret>)",
|
||||
CommandState.Mode.INSERT, CommandState.SubMode.NONE);
|
||||
}
|
||||
|
||||
public void testSmartArgumentRecognition() {
|
||||
doTest(parseKeys("dia"),
|
||||
"function(1, (20<caret>*30)+40, somefunc2(3, 4))",
|
||||
"function(1, <caret>, somefunc2(3, 4))",
|
||||
CommandState.Mode.COMMAND, CommandState.SubMode.NONE);
|
||||
doTest(parseKeys("daa"),
|
||||
"function(1, (20*30)+40, somefunc2(<caret>3, 4))",
|
||||
"function(1, (20*30)+40, somefunc2(<caret>4))",
|
||||
CommandState.Mode.COMMAND, CommandState.SubMode.NONE);
|
||||
}
|
||||
|
||||
public void testIgnoreQuotedArguments() {
|
||||
doTest(parseKeys("daa"),
|
||||
"function(int arg1, char* arg2=a,b,c(<caret>arg,e))",
|
||||
"function(int arg1, char* arg2=a,b,c(<caret>e))",
|
||||
CommandState.Mode.COMMAND, CommandState.SubMode.NONE);
|
||||
doTest(parseKeys("daa"),
|
||||
"function(int arg1, char* arg2=\"a,b,c(<caret>arg,e)\")",
|
||||
"function(int arg1<caret>)",
|
||||
CommandState.Mode.COMMAND, CommandState.SubMode.NONE);
|
||||
doTest(parseKeys("daa"),
|
||||
"function(int arg1, char* arg2=\"a,b,c(arg,e\"<caret>)",
|
||||
"function(int arg1<caret>)",
|
||||
CommandState.Mode.COMMAND, CommandState.SubMode.NONE);
|
||||
doTest(parseKeys("daa"),
|
||||
"function(int arg1, char* a<caret>rg2={\"a,b},c(arg,e\"})",
|
||||
"function(int arg1<caret>)",
|
||||
CommandState.Mode.COMMAND, CommandState.SubMode.NONE);
|
||||
}
|
||||
|
||||
public void testDeleteTwoArguments() {
|
||||
doTest(parseKeys("d2aa"),
|
||||
"function(int <caret>arg1, char* arg2=\"a,b,c(d,e)\")",
|
||||
"function(<caret>)",
|
||||
CommandState.Mode.COMMAND, CommandState.SubMode.NONE);
|
||||
doTest(parseKeys("d2ia"),
|
||||
"function(int <caret>arg1, char* arg2=\"a,b,c(d,e)\")",
|
||||
"function(<caret>)",
|
||||
CommandState.Mode.COMMAND, CommandState.SubMode.NONE);
|
||||
doTest(parseKeys("d2aa"),
|
||||
"function(int <caret>arg1, char* arg2=\"a,b,c(d,e)\", bool arg3)",
|
||||
"function(<caret>bool arg3)",
|
||||
CommandState.Mode.COMMAND, CommandState.SubMode.NONE);
|
||||
doTest(parseKeys("d2ia"),
|
||||
"function(int <caret>arg1, char* arg2=\"a,b,c(d,e)\", bool arg3)",
|
||||
"function(<caret>, bool arg3)",
|
||||
CommandState.Mode.COMMAND, CommandState.SubMode.NONE);
|
||||
doTest(parseKeys("d2aa"),
|
||||
"function(int arg1, char* arg<caret>2=\"a,b,c(d,e)\", bool arg3)",
|
||||
"function(int arg1<caret>)",
|
||||
CommandState.Mode.COMMAND, CommandState.SubMode.NONE);
|
||||
doTest(parseKeys("d2ia"),
|
||||
"function(int arg1, char* arg<caret>2=\"a,b,c(d,e)\", bool arg3)",
|
||||
"function(int arg1, <caret>)",
|
||||
CommandState.Mode.COMMAND, CommandState.SubMode.NONE);
|
||||
}
|
||||
|
||||
public void testSelectTwoArguments() {
|
||||
doTest(parseKeys("v2aa"),
|
||||
"function(int <caret>arg1, char* arg2=\"a,b,c(d,e)\", bool arg3)",
|
||||
"function(<selection>int arg1, char* arg2=\"a,b,c(d,e)\", </selection>bool arg3)",
|
||||
CommandState.Mode.VISUAL, CommandState.SubMode.VISUAL_CHARACTER);
|
||||
doTest(parseKeys("v2ia"),
|
||||
"function(int <caret>arg1, char* arg2=\"a,b,c(d,e)\", bool arg3)",
|
||||
"function(<selection>int arg1, char* arg2=\"a,b,c(d,e)\"</selection>, bool arg3)",
|
||||
CommandState.Mode.VISUAL, CommandState.SubMode.VISUAL_CHARACTER);
|
||||
}
|
||||
|
||||
// The original author of this extension wanted this case to work
|
||||
/*
|
||||
public void testArgumentsInsideAngleBrackets() {
|
||||
doTest(parseKeys("dia"),
|
||||
"std::vector<int, std::unique_p<caret>tr<bool>> v{};",
|
||||
"std::vector<int, <caret>> v{};",
|
||||
CommandState.Mode.COMMAND, CommandState.SubMode.NONE);
|
||||
}
|
||||
*/
|
||||
|
||||
public void testBracketPriorityToHangleShiftOperators() {
|
||||
doTest(parseKeys("dia"),
|
||||
"foo(30 << 10, 20 << <caret>3) >> 17",
|
||||
"foo(30 << 10, <caret>) >> 17",
|
||||
CommandState.Mode.COMMAND, CommandState.SubMode.NONE);
|
||||
doTest(parseKeys("dia"),
|
||||
"foo(30 << <caret>10, 20 * 3) >> 17",
|
||||
"foo(<caret>, 20 * 3) >> 17",
|
||||
CommandState.Mode.COMMAND, CommandState.SubMode.NONE);
|
||||
doTest(parseKeys("dia"),
|
||||
"foo(<caret>30 >> 10, 20 * 3) << 17",
|
||||
"foo(<caret>, 20 * 3) << 17",
|
||||
CommandState.Mode.COMMAND, CommandState.SubMode.NONE);
|
||||
}
|
||||
|
||||
public void testEmptyFile() {
|
||||
assertPluginError(false);
|
||||
doTest(parseKeys("daa"),
|
||||
"<caret>",
|
||||
"<caret>",
|
||||
CommandState.Mode.COMMAND, CommandState.SubMode.NONE);
|
||||
assertPluginError(true);
|
||||
doTest(parseKeys("dia"),
|
||||
"<caret>",
|
||||
"<caret>",
|
||||
CommandState.Mode.COMMAND, CommandState.SubMode.NONE);
|
||||
assertPluginError(true);
|
||||
}
|
||||
|
||||
public void testEmptyLine() {
|
||||
assertPluginError(false);
|
||||
doTest(parseKeys("daa"),
|
||||
"<caret>\n",
|
||||
"<caret>\n",
|
||||
CommandState.Mode.COMMAND, CommandState.SubMode.NONE);
|
||||
assertPluginError(true);
|
||||
doTest(parseKeys("dia"),
|
||||
"<caret>\n",
|
||||
"<caret>\n",
|
||||
CommandState.Mode.COMMAND, CommandState.SubMode.NONE);
|
||||
assertPluginError(true);
|
||||
}
|
||||
|
||||
public void testEmptyArg() {
|
||||
assertPluginError(false);
|
||||
doTest(parseKeys("daa"),
|
||||
"foo(<caret>)",
|
||||
"foo(<caret>)",
|
||||
CommandState.Mode.COMMAND, CommandState.SubMode.NONE);
|
||||
assertPluginError(true);
|
||||
doTest(parseKeys("dia"),
|
||||
"foo(<caret>)",
|
||||
"foo(<caret>)",
|
||||
CommandState.Mode.COMMAND, CommandState.SubMode.NONE);
|
||||
assertPluginError(true);
|
||||
}
|
||||
|
||||
public void testSkipCommasInsideNestedPairs() {
|
||||
final String before = "void foo(int arg1)\n{" +
|
||||
" methodCall(arg1, \"{ arg1 , 2\");\n" +
|
||||
" otherMeth<caret>odcall(arg, 3);\n" +
|
||||
"}";
|
||||
doTest(parseKeys("dia"), before, before,
|
||||
CommandState.Mode.COMMAND, CommandState.SubMode.NONE);
|
||||
assertPluginError(true);
|
||||
}
|
||||
|
||||
public void testHandleNestedPairs() {
|
||||
doTest(parseKeys("dia"),
|
||||
"foo(arg1, arr<caret>ay[someexpr(Class{arg1 << 3, arg2})] + 3)\n{",
|
||||
"foo(arg1, <caret>)\n{",
|
||||
CommandState.Mode.COMMAND, CommandState.SubMode.NONE);
|
||||
}
|
||||
|
||||
public void testHandleImbalancedPairs() {
|
||||
doTest(parseKeys("dia"),
|
||||
"foo(arg1, ba<caret>r(not-an-arg{body",
|
||||
"foo(arg1, ba<caret>r(not-an-arg{body",
|
||||
CommandState.Mode.COMMAND, CommandState.SubMode.NONE);
|
||||
assertPluginError(true);
|
||||
doTest(parseKeys("dia"),
|
||||
"foo(arg1, ba<caret>r ( x > 3 )",
|
||||
"foo(arg1, ba<caret>r ( x > 3 )",
|
||||
CommandState.Mode.COMMAND, CommandState.SubMode.NONE);
|
||||
assertPluginError(true);
|
||||
doTest(parseKeys("dia"),
|
||||
"foo(arg1, ba<caret>r + x >",
|
||||
"foo(arg1, ba<caret>r + x >",
|
||||
CommandState.Mode.COMMAND, CommandState.SubMode.NONE);
|
||||
assertPluginError(true);
|
||||
doTest(parseKeys("dia"),
|
||||
"<arg1, ba<caret>r + x)",
|
||||
"<arg1, ba<caret>r + x)",
|
||||
CommandState.Mode.COMMAND, CommandState.SubMode.NONE);
|
||||
assertPluginError(true);
|
||||
}
|
||||
|
||||
public void testArgumentBoundsSearchIsLimitedByLineCount() {
|
||||
final String before = "foo(\n" +
|
||||
String.join("", Collections.nCopies(10, " arg,\n")) +
|
||||
" last<caret>Arg" +
|
||||
")";
|
||||
doTest(parseKeys("dia"), before, before,
|
||||
CommandState.Mode.COMMAND, CommandState.SubMode.NONE);
|
||||
assertPluginError(true);
|
||||
}
|
||||
|
||||
public void testExtendVisualSelection() {
|
||||
doTest(parseKeys("vllia"),
|
||||
"function(int arg1, ch<caret>ar* arg2=\"a,b,c(d,e)\")",
|
||||
"function(int arg1, <selection>char* arg2=\"a,b,c(d,e)\"</selection>)",
|
||||
CommandState.Mode.VISUAL, CommandState.SubMode.VISUAL_CHARACTER);
|
||||
doTest(parseKeys("vhhia"),
|
||||
"function(int arg1, char<caret>* arg2=\"a,b,c(d,e)\")",
|
||||
"function(int arg1, <selection>char* arg2=\"a,b,c(d,e)\"</selection>)",
|
||||
CommandState.Mode.VISUAL, CommandState.SubMode.VISUAL_CHARACTER);
|
||||
}
|
||||
|
||||
public void testExtendVisualSelectionUsesCaretPos() {
|
||||
doTest(parseKeys("vllia"),
|
||||
"fu<caret>n(arg)",
|
||||
"fun(<selection>arg</selection>)",
|
||||
CommandState.Mode.VISUAL, CommandState.SubMode.VISUAL_CHARACTER);
|
||||
}
|
||||
|
||||
public void testDeleteArrayArgument() {
|
||||
doTest(parseKeys("dia"),
|
||||
"function(int a, String[<caret>] b)",
|
||||
"function(int a, <caret>)",
|
||||
CommandState.Mode.COMMAND, CommandState.SubMode.NONE);
|
||||
doTest(parseKeys("daa"),
|
||||
"function(int a, String[<caret>] b)",
|
||||
"function(int a)",
|
||||
CommandState.Mode.COMMAND, CommandState.SubMode.NONE);
|
||||
}
|
||||
|
||||
public void testDeleteInClass() {
|
||||
doTest(parseKeys("dia"),
|
||||
"class MyClass{ public int myFun() { some<caret>Call(); } }",
|
||||
"class MyClass{ public int myFun() { some<caret>Call(); } }",
|
||||
CommandState.Mode.COMMAND, CommandState.SubMode.NONE);
|
||||
doTest(parseKeys("daa"),
|
||||
"class MyClass{ public int myFun() { some<caret>Call(); } }",
|
||||
"class MyClass{ public int myFun() { some<caret>Call(); } }",
|
||||
CommandState.Mode.COMMAND, CommandState.SubMode.NONE);
|
||||
}
|
||||
|
||||
// Original plugin doesn't remove the argument in case of space after function name
|
||||
public void testFunctionWithSpaceAfterName() {
|
||||
doTest(parseKeys("dia"),
|
||||
"function (int <caret>a)",
|
||||
"function (int <caret>a)",
|
||||
CommandState.Mode.COMMAND, CommandState.SubMode.NONE);
|
||||
doTest(parseKeys("daa"),
|
||||
"function (int <caret>a)",
|
||||
"function (int <caret>a)",
|
||||
CommandState.Mode.COMMAND, CommandState.SubMode.NONE);
|
||||
}
|
||||
|
||||
@VimBehaviorDiffers(
|
||||
originalVimAfter = "function (int <caret>a, int b)",
|
||||
description = "Should work the same as testFunctionWithSpaceAfterName"
|
||||
)
|
||||
public void testFunctionWithSpaceAfterNameWithTwoArgs() {
|
||||
doTest(parseKeys("dia"),
|
||||
"function (int <caret>a, int b)",
|
||||
"function (, int b)",
|
||||
CommandState.Mode.COMMAND, CommandState.SubMode.NONE);
|
||||
doTest(parseKeys("daa"),
|
||||
"function (int <caret>a, int b)",
|
||||
"function (int b)",
|
||||
CommandState.Mode.COMMAND, CommandState.SubMode.NONE);
|
||||
}
|
||||
|
||||
public void testDeleteInIf() {
|
||||
doTest(parseKeys("dia"),
|
||||
"class MyClass{ public int myFun() { if (tr<caret>ue) { somFunction(); } } }",
|
||||
"class MyClass{ public int myFun() { if (tr<caret>ue) { somFunction(); } } }",
|
||||
CommandState.Mode.COMMAND, CommandState.SubMode.NONE);
|
||||
doTest(parseKeys("daa"),
|
||||
"class MyClass{ public int myFun() { if (tr<caret>ue) { somFunction(); } } }",
|
||||
"class MyClass{ public int myFun() { if (tr<caret>ue) { somFunction(); } } }",
|
||||
CommandState.Mode.COMMAND, CommandState.SubMode.NONE);
|
||||
}
|
||||
}
|
66
test/org/jetbrains/plugins/ideavim/group/KeyGroupTest.kt
Normal file
66
test/org/jetbrains/plugins/ideavim/group/KeyGroupTest.kt
Normal file
@@ -0,0 +1,66 @@
|
||||
/*
|
||||
* IdeaVim - Vim emulator for IDEs based on the IntelliJ platform
|
||||
* Copyright (C) 2003-2020 The IdeaVim authors
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 2 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package org.jetbrains.plugins.ideavim.group
|
||||
|
||||
import com.maddyhome.idea.vim.VimPlugin
|
||||
import com.maddyhome.idea.vim.command.MappingMode
|
||||
import com.maddyhome.idea.vim.helper.StringHelper.parseKeys
|
||||
import com.maddyhome.idea.vim.key.MappingOwner
|
||||
import org.jetbrains.plugins.ideavim.VimTestCase
|
||||
|
||||
class KeyGroupTest : VimTestCase() {
|
||||
private val owner = MappingOwner.Plugin.get("KeyGroupTest")
|
||||
|
||||
fun `test remove key mapping`() {
|
||||
val keyGroup = VimPlugin.getKey()
|
||||
val keys = parseKeys("<C-S-B>")
|
||||
|
||||
configureByText("I ${c}found it in a legendary land")
|
||||
typeText(keys)
|
||||
myFixture.checkResult("I ${c}found it in a legendary land")
|
||||
|
||||
keyGroup.putKeyMapping(MappingMode.N, keys, owner, parseKeys("h"), false)
|
||||
typeText(keys)
|
||||
myFixture.checkResult("I${c} found it in a legendary land")
|
||||
|
||||
keyGroup.removeKeyMapping(owner)
|
||||
typeText(keys)
|
||||
myFixture.checkResult("I${c} found it in a legendary land")
|
||||
}
|
||||
|
||||
fun `test remove and add key mapping`() {
|
||||
val keyGroup = VimPlugin.getKey()
|
||||
val keys = parseKeys("<C-S-B>")
|
||||
|
||||
configureByText("I ${c}found it in a legendary land")
|
||||
typeText(keys)
|
||||
myFixture.checkResult("I ${c}found it in a legendary land")
|
||||
|
||||
keyGroup.putKeyMapping(MappingMode.N, keys, owner, parseKeys("h"), false)
|
||||
typeText(keys)
|
||||
myFixture.checkResult("I${c} found it in a legendary land")
|
||||
|
||||
repeat(10) {
|
||||
keyGroup.removeKeyMapping(owner)
|
||||
keyGroup.putKeyMapping(MappingMode.N, keys, owner, parseKeys("h"), false)
|
||||
}
|
||||
typeText(keys)
|
||||
myFixture.checkResult("${c}I found it in a legendary land")
|
||||
}
|
||||
}
|
@@ -1205,7 +1205,7 @@ class SearchGroupTest : VimTestCase() {
|
||||
val project = myFixture.project
|
||||
val searchGroup = VimPlugin.getSearch()
|
||||
val ref = Ref.create(-1)
|
||||
RunnableHelper.runReadCommand(project, {
|
||||
RunnableHelper.runReadCommand(project, Runnable {
|
||||
val n = searchGroup.search(editor, pattern, 1, EnumSet.of(CommandFlags.FLAG_SEARCH_FWD), false)
|
||||
ref.set(n)
|
||||
}, null, null)
|
||||
|
@@ -19,6 +19,7 @@
|
||||
package org.jetbrains.plugins.ideavim.helper;
|
||||
|
||||
import com.maddyhome.idea.vim.helper.SearchHelper;
|
||||
import com.maddyhome.idea.vim.helper.SearchHelperKtKt;
|
||||
import org.jetbrains.plugins.ideavim.VimTestCase;
|
||||
|
||||
import static com.maddyhome.idea.vim.helper.StringHelper.parseKeys;
|
||||
@@ -77,4 +78,100 @@ public class SearchHelperTest extends VimTestCase {
|
||||
typeTextInFile(parseKeys("v", "a("), "((int) nu<caret>m)");
|
||||
myFixture.checkResult("<selection>((int) num)</selection>");
|
||||
}
|
||||
|
||||
public void testCheckInStringInsideDoubleQuotes() {
|
||||
String text = "abc\"def\"ghi";
|
||||
boolean inString = SearchHelperKtKt.checkInString(text, 5, true);
|
||||
assertTrue(inString);
|
||||
}
|
||||
|
||||
public void testCheckInStringWithoutClosingDoubleQuote() {
|
||||
String text = "abcdef\"ghi";
|
||||
boolean inString = SearchHelperKtKt.checkInString(text, 5, true);
|
||||
assertFalse(inString);
|
||||
}
|
||||
|
||||
public void testCheckInStringOnUnpairedSingleQuote() {
|
||||
String text = "abc\"d'ef\"ghi";
|
||||
boolean inString = SearchHelperKtKt.checkInString(text, 5, true);
|
||||
assertTrue(inString);
|
||||
}
|
||||
|
||||
public void testCheckInStringOutsideOfDoubleQuotesPair() {
|
||||
String text = "abc\"def\"ghi";
|
||||
boolean inString = SearchHelperKtKt.checkInString(text, 2, true);
|
||||
assertFalse(inString);
|
||||
}
|
||||
|
||||
public void testCheckInStringEscapedDoubleQuote() {
|
||||
String text = "abc\\\"def\"ghi";
|
||||
boolean inString = SearchHelperKtKt.checkInString(text, 5, true);
|
||||
assertFalse(inString);
|
||||
}
|
||||
|
||||
public void testCheckInStringOddNumberOfDoubleQuotes() {
|
||||
String text = "abc\"def\"gh\"i";
|
||||
boolean inString = SearchHelperKtKt.checkInString(text, 5, true);
|
||||
assertFalse(inString);
|
||||
}
|
||||
|
||||
public void testCheckInStringInsideSingleQuotesPair() {
|
||||
String text = "abc\"d'e'f\"ghi";
|
||||
boolean inString = SearchHelperKtKt.checkInString(text, 6, false);
|
||||
assertTrue(inString);
|
||||
}
|
||||
|
||||
public void testCheckInStringOnOpeningDoubleQuote() {
|
||||
String text = "abc\"def\"ghi";
|
||||
boolean inString = SearchHelperKtKt.checkInString(text, 3, true);
|
||||
assertTrue(inString);
|
||||
}
|
||||
|
||||
public void testCheckInStringOnClosingDoubleQuote() {
|
||||
String text = "abc\"def\"ghi";
|
||||
boolean inString = SearchHelperKtKt.checkInString(text, 7, true);
|
||||
assertTrue(inString);
|
||||
}
|
||||
|
||||
public void testCheckInStringWithoutQuotes() {
|
||||
String text = "abcdefghi";
|
||||
boolean inString = SearchHelperKtKt.checkInString(text, 5, true);
|
||||
assertFalse(inString);
|
||||
}
|
||||
|
||||
public void testCheckInStringDoubleQuoteInsideSingleQuotes() {
|
||||
String text = "abc'\"'ef\"ghi";
|
||||
boolean inString = SearchHelperKtKt.checkInString(text, 5, true);
|
||||
assertFalse(inString);
|
||||
}
|
||||
|
||||
public void testCheckInStringSingleQuotesAreTooFarFromEachOtherToMakePair() {
|
||||
String text = "abc'\"de'f\"ghi";
|
||||
boolean inString = SearchHelperKtKt.checkInString(text, 5, true);
|
||||
assertTrue(inString);
|
||||
}
|
||||
|
||||
public void testCheckInStringDoubleQuoteInsideSingleQuotesIsInsideSingleQuotedString() {
|
||||
String text = "abc'\"'def\"ghi";
|
||||
boolean inString = SearchHelperKtKt.checkInString(text, 4, false);
|
||||
assertTrue(inString);
|
||||
}
|
||||
|
||||
public void testCheckInStringAfterClosingDoubleQuote() {
|
||||
String text = "abc\"def\"ghi";
|
||||
boolean inString = SearchHelperKtKt.checkInString(text, 9, true);
|
||||
assertFalse(inString);
|
||||
}
|
||||
|
||||
public void testCheckInStringOnMiddleDoubleQuote() {
|
||||
String text = "abc\"def\"gh\"i";
|
||||
boolean inString = SearchHelperKtKt.checkInString(text, 7, true);
|
||||
assertFalse(inString);
|
||||
}
|
||||
|
||||
public void testCheckInStringBetweenPairs() {
|
||||
String text = "abc\"def\"gh\"ij\"k";
|
||||
boolean inString = SearchHelperKtKt.checkInString(text, 8, true);
|
||||
assertFalse(inString);
|
||||
}
|
||||
}
|
||||
|
@@ -16,19 +16,17 @@
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package com.maddyhome.idea.vim
|
||||
package org.jetbrains.plugins.ideavim.key
|
||||
|
||||
import com.intellij.openapi.components.ProjectComponent
|
||||
import com.intellij.openapi.project.Project
|
||||
import com.maddyhome.idea.vim.listener.VimListenerManager
|
||||
import com.maddyhome.idea.vim.key.MappingOwner
|
||||
import junit.framework.TestCase
|
||||
import org.jetbrains.plugins.ideavim.VimTestCase
|
||||
|
||||
/**
|
||||
* @author Alex Plate
|
||||
*/
|
||||
class VimProjectComponent(private val project: Project) : ProjectComponent {
|
||||
override fun projectOpened() {
|
||||
if (!VimPlugin.isEnabled()) return
|
||||
// Project listeners are self-disposable, so there is no need to unregister them on project close
|
||||
VimListenerManager.ProjectListeners.add(project)
|
||||
class MappingOwnerTest : VimTestCase() {
|
||||
fun `test get two plugin owners`() {
|
||||
val pluginName = "MyPlugin"
|
||||
val firstOwner = MappingOwner.Plugin.get(pluginName)
|
||||
val secondOwner = MappingOwner.Plugin.get(pluginName)
|
||||
TestCase.assertSame(firstOwner, secondOwner)
|
||||
}
|
||||
}
|
Reference in New Issue
Block a user