1
0
mirror of https://github.com/chylex/IntelliJ-IdeaVim.git synced 2025-08-18 10:31:44 +02:00

Compare commits

...

67 Commits

Author SHA1 Message Date
Alex Plate
2f5946640e Update changelog 2020-03-04 10:44:28 +03:00
Alex Plate
7cdb7dc308 Fix some tests for older versions of IDE 2020-03-04 10:32:05 +03:00
Alex Plate
c0038d0373 Add John Weigel to contributors list 2020-03-03 11:11:09 +03:00
Alex Plate
2820decb5e Rename variable 2020-03-03 11:07:07 +03:00
Alex Pláte
c64f368e6a Merge pull request #217 from angelbot/master
Add support for buffer list (buffers, files, ls)
2020-03-03 11:05:43 +03:00
Alex Plate
b7c8e84f5e Minor cleanup 2020-03-03 11:03:36 +03:00
Alex Plate
5acf6c9158 Convert VimPlugin to service 2020-02-28 21:11:12 +03:00
Alex Plate
a8197b0c84 Convert runnableHelper to kt 2020-02-28 18:15:40 +03:00
Alex Plate
2e03062c24 Rename .java to .kt 2020-02-28 18:15:39 +03:00
Alex Plate
7fb60a185b Update gradle version 2020-02-28 17:35:46 +03:00
Alex Plate
0327ea972b Make Open ideavimrc dumb aware 2020-02-28 10:29:38 +03:00
Alex Plate
561cc77ecc Move related methods closer to each other 2020-02-28 09:42:22 +03:00
Alex Plate
a1ab4acd14 Add comment for EPs 2020-02-28 09:39:33 +03:00
Alex Plate
d4939803da Update changelist 2020-02-27 14:27:13 +03:00
Alex Pláte
730ce3aca9 Merge pull request #226 from agrison/master
Implement the vim-textobj-entire plugin emulation.
2020-02-27 14:23:31 +03:00
Alexandre Grison
1893dc6afd Fixes from feedback.
Renamed `entiretextobj` to `textobj-entire` including packages and class name.
Renamed `<Plug>IncludingLeadingTrailing` to `<Plug>textobj-entire-a`.
Renamed `<Plug>IgnoringLeadingTrailing` to `<Plug>textobj-entire-i`.
Avoid iterating too much the buffer content.
2020-02-27 11:46:23 +01:00
John Weigel
c87528939b Fix buffer numbering bug with filters.
Update test to cover fix.
2020-02-23 21:11:36 -06:00
Alex Plate
e56646105d Add test bages 2020-02-21 12:05:05 +03:00
Alex Plate
b8a40d93f7 Now every service handles it's state separately. VimLocalConfig is a service 2020-02-21 12:03:02 +03:00
Alexandre Grison
4bfc025248 Fixes typo 2020-02-20 12:24:38 +01:00
Alexandre Grison
36f6027b0e Implement the vim-textobj-entire plugin emulation. 2020-02-20 12:13:55 +01:00
Alex Plate
e032377e68 Update annotations 2020-02-20 10:35:09 +03:00
Alex Plate
929eee4a12 Add comments for NotificationService.kt 2020-02-20 10:13:47 +03:00
Alex Plate
61ce50264a Add Alexey Gerasimov to contributors list 2020-02-19 12:02:10 +03:00
Alex Plate
48927b1207 Small corrections after merge 2020-02-19 11:58:37 +03:00
Alex Plate
0820893dc6 Update annotations to java 8 style 2020-02-19 11:58:27 +03:00
Alex Pláte
dd6079cfa6 Merge pull request #219 from fan-tom/bugifx/1008
Fix block actions (i.e ci{) in presence of quotes (VIM-1008)
2020-02-19 11:53:19 +03:00
John Weigel
3d7d75bae4 Merge remote-tracking branch 'upstream/master' 2020-02-16 21:11:02 -06:00
John Weigel
6da4d0ce5e Rework buffer list to more closely mimic vim. 2020-02-16 20:40:17 -06:00
Alex Plate
4994d70b1a Update changelog 2020-02-14 12:42:22 +03:00
Alex Plate
c873081dc3 Merge pull request #133 from igrekster/master
Add argtextobj.vim plugin emulation
2020-02-14 12:30:13 +03:00
Alex Plate
070237f77f Add [To Be Released] label 2020-02-14 12:24:34 +03:00
Alex Plate
eb01b25f35 Fix some cases by disabling [, { and < support (what is not supported in the original plugin) 2020-02-14 12:23:32 +03:00
Alex Plate
c0c9cfaf86 Get rid of several getText methods 2020-02-14 10:54:22 +03:00
Alex Plate
304f860eb2 Use java 8 JetBrains annotations 2020-02-14 10:32:18 +03:00
Alex Plate
2f18b25593 Update gradle dependencies 2020-02-14 10:17:00 +03:00
Alex Plate
adaa683e58 Use 2020.1 build for README label 2020-02-11 10:11:52 +03:00
igrekster
5ee0a93675 Add argtextobj.vim plugin emulation 2020-02-09 11:57:54 +11:00
Alex Plate
767b3c4a39 Add some scheduled for removal annotations 2020-02-08 20:57:44 +03:00
Alex Plate
bb948a463c Add option to make status bar icon gray 2020-02-08 20:56:13 +03:00
Alex Plate
e4e9a03d0a Add information about why EPs are used to register actions and ex handlers. 2020-02-08 18:14:04 +03:00
Alex Plate
50ba386f59 Write tests for dynamic extensions 2020-02-08 18:07:20 +03:00
Alex Plate
79d0565c2d Update some tests 2020-02-08 16:09:39 +03:00
Alex Plate
bcc9b0a7b1 Remove plugin owner after extension removal 2020-02-08 15:38:54 +03:00
Alex Plate
2c8f4940b9 Support EasyMotion extension 2020-02-08 15:25:24 +03:00
Alex Plate
41876cf8fd Make vimExtension dynamic 2020-02-08 14:56:39 +03:00
Alex Plate
f6fd0b52f0 Rename RequiredShortcutOwner to MappingOwner 2020-02-08 14:36:35 +03:00
Alex Plate
843faa7cc6 Make plugins disposable 2020-02-08 14:36:01 +03:00
Alexey Gerasimov
a8af2c3242 Fix Set creation 2020-02-07 22:24:46 +05:00
Alexey Gerasimov
e5bfad974e Copyright and comment 2020-02-07 21:50:06 +05:00
Alexey Gerasimov
59d87e0c94 More tests 2020-02-07 19:48:40 +05:00
Alexey Gerasimov
50c2d04503 Migrate to new checkInString 2020-02-07 19:48:40 +05:00
Alexey Gerasimov
480de62686 Improve existing checkInString 2020-02-07 19:48:40 +05:00
Alexey Gerasimov
955b501058 Make Direction enum public 2020-02-07 19:48:40 +05:00
Alexey Gerasimov
d985527624 Rewrite checkInString in Kotlin 2020-02-07 19:48:40 +05:00
Alexey Gerasimov
afbe7f0e69 Add findPositionOfFirstCharacter function 2020-02-07 19:48:40 +05:00
Alexey Gerasimov
94e65ddce6 Use isQuoteWithoutEscape when findCharacterPosition to detect escaped char 2020-02-07 19:48:40 +05:00
Alexey Gerasimov
cb9f144255 isQuoteWithoutEscape small improvement 2020-02-07 19:48:40 +05:00
Alexey Gerasimov
ac84624faa Use Direction enum instead of int 2020-02-07 19:48:40 +05:00
Alexey Gerasimov
c2196785e7 Add tests 2020-02-07 19:48:40 +05:00
Alexey Gerasimov
30097fbae6 Assume that caret is in string/char only if there is closing char 2020-02-07 19:48:40 +05:00
Alex Plate
c295dd5c62 Use special class for storing requiredShortcuts 2020-02-07 16:07:14 +03:00
Alex Plate
373fef2824 Refactor MappingInfo 2020-02-07 12:42:36 +03:00
Alex Plate
cfc255bf2b Rename .java to .kt 2020-02-07 12:41:57 +03:00
Alex Plate
ea7e58535b Fix tests 2020-02-07 12:41:47 +03:00
John Weigel
ff209d0120 Merge remote-tracking branch 'origin/master' 2020-02-01 22:38:18 -06:00
John Weigel
ea2fe618b5 Add support for buffer list (buffers, files, ls). 2020-02-01 22:33:12 -06:00
98 changed files with 3358 additions and 1240 deletions

View File

@@ -271,6 +271,18 @@ Contributors:
[![icon][github]](https://github.com/igrekster) [![icon][github]](https://github.com/igrekster)
&nbsp; &nbsp;
igrekster igrekster
* [![icon][mail]](mailto:lokomot476@gmail.com)
[![icon][github]](https://github.com/fan-tom)
&nbsp;
Alexey Gerasimov
* [![icon][mail]](mailto:a.grison+github@gmail.com)
[![icon][github]](https://github.com/agrison)
&nbsp;
Alexandre Grison
* [![icon][mail]](mailto:angel@knight-industries.com)
[![icon][github]](https://github.com/angelbot)
&nbsp;
John Weigel
If you are a contributor and your name is not listed here, feel free to If you are a contributor and your name is not listed here, feel free to
contact the maintainers. contact the maintainers.

View File

@@ -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-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 * [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 0.55, 2020-01-20
-------------- --------------

View File

@@ -16,6 +16,18 @@
</a> </a>
<span>2019.2 Tests</span> <span>2019.2 Tests</span>
</div> </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 ### Where to Start

View File

@@ -1,4 +1,4 @@
<img src="resources/META-INF/pluginIcon.svg" width="80" height="80" alt="icon" align="left"/> <img src="resources/META-INF/pluginIcon.svg" width="80" height="80" alt="icon" align="left"/>
IdeaVim IdeaVim
=== ===
@@ -7,8 +7,8 @@ IdeaVim
<a href="https://confluence.jetbrains.com/display/ALL/JetBrains+on+GitHub"> <a href="https://confluence.jetbrains.com/display/ALL/JetBrains+on+GitHub">
<img src="https://jb.gg/badges/official.svg" alt="official JetBrains project"/> <img src="https://jb.gg/badges/official.svg" alt="official JetBrains project"/>
</a> </a>
<a href="https://teamcity.jetbrains.com/viewType.html?buildTypeId=IdeaVim_TestsForIntelliJ20191&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_TestsForIntelliJ20191)/statusIcon.svg?guest=1"/> <img src="https://teamcity.jetbrains.com/app/rest/builds/buildType:(id:IdeaVim_TestsForIntelliJ20201)/statusIcon.svg?guest=1" alt="TeamCity Build"/>
</a> </a>
</div> </div>
@@ -97,6 +97,8 @@ Emulated Vim plugins:
* vim-surround * vim-surround
* vim-multiple-cursors * vim-multiple-cursors
* vim-commentary * vim-commentary
* argtextobj.vim [To Be Released]
* vim-textobj-entire [To Be Released]
Not supported (yet): Not supported (yet):
@@ -165,6 +167,16 @@ Available extensions:
* Emulates [commentary.vim](https://github.com/tpope/vim-commentary) * Emulates [commentary.vim](https://github.com/tpope/vim-commentary)
* Commands: `gcc`, `gc + motion`, `v_gc` * 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 Changes to the IDE
------------------ ------------------

View File

@@ -9,7 +9,7 @@ buildscript {
} }
plugins { plugins {
id 'org.jetbrains.intellij' version '0.4.9' id 'org.jetbrains.intellij' version '0.4.16'
} }
apply plugin: 'java' apply plugin: 'java'
@@ -56,7 +56,7 @@ repositories {
dependencies { dependencies {
compileOnly "org.jetbrains.kotlin:kotlin-stdlib-jdk8:$kotlinVersion" compileOnly "org.jetbrains.kotlin:kotlin-stdlib-jdk8:$kotlinVersion"
compileOnly "org.jetbrains:annotations:17.0.0" compileOnly "org.jetbrains:annotations:19.0.0"
} }
compileKotlin { compileKotlin {

View File

@@ -104,10 +104,22 @@ The following `:set` commands can appear in `~/.ideavimrc` or be set manually in
See wiki/`ideajoin` examples See wiki/`ideajoin` examples
`ideastatusbar` `ideastatusbar` Boolean (default true) `ideastatusbar` `ideastatusbar` Boolean (default true)
DEPRECATED. Please use `ideastatusicon`
If false, IdeaVim icon won't be shown in the status bar. If false, IdeaVim icon won't be shown in the status bar.
Works only from `~/.ideavimrc` after the IDE restart. Works only from `~/.ideavimrc` after the IDE restart.
`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 `lookupkeys` `lookupkeys` List of strings
List of keys that should be processed by the IDE during the active lookup (autocompletion). List of keys that should be processed by the IDE during the active lookup (autocompletion).

Binary file not shown.

View File

@@ -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 distributionBase=GRADLE_USER_HOME
distributionPath=wrapper/dists distributionPath=wrapper/dists
zipStorePath=wrapper/dists distributionUrl=https\://services.gradle.org/distributions/gradle-6.2.1-bin.zip
zipStoreBase=GRADLE_USER_HOME zipStoreBase=GRADLE_USER_HOME
zipStorePath=wrapper/dists

22
gradlew vendored
View File

@@ -1,5 +1,21 @@
#!/usr/bin/env sh #!/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 ## Gradle start up script for UN*X
@@ -28,7 +44,7 @@ APP_NAME="Gradle"
APP_BASE_NAME=`basename "$0"` 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. # 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. # Use the maximum available, or set MAX_FD != -1 to use that value.
MAX_FD="maximum" 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\"" GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\""
fi fi
# For Cygwin, switch paths to Windows format before running java # For Cygwin or MSYS, switch paths to Windows format before running java
if $cygwin ; then if [ "$cygwin" = "true" -o "$msys" = "true" ] ; then
APP_HOME=`cygpath --path --mixed "$APP_HOME"` APP_HOME=`cygpath --path --mixed "$APP_HOME"`
CLASSPATH=`cygpath --path --mixed "$CLASSPATH"` CLASSPATH=`cygpath --path --mixed "$CLASSPATH"`
JAVACMD=`cygpath --unix "$JAVACMD"` JAVACMD=`cygpath --unix "$JAVACMD"`

18
gradlew.bat vendored
View File

@@ -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 @if "%DEBUG%" == "" @echo off
@rem ########################################################################## @rem ##########################################################################
@rem @rem
@@ -14,7 +30,7 @@ set APP_BASE_NAME=%~n0
set APP_HOME=%DIRNAME% 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. @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 @rem Find java.exe
if defined JAVA_HOME goto findJavaFromJavaHome if defined JAVA_HOME goto findJavaFromJavaHome

View File

@@ -60,5 +60,6 @@
<vimExCommand implementation="com.maddyhome.idea.vim.ex.handler.NextTabHandler" names="tabn[ext]"/> <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.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.TabOnlyHandler" names="tabo[nly]"/>
<vimExCommand implementation="com.maddyhome.idea.vim.ex.handler.BufferListHandler" names="buffers,ls,files"/>
</extensions> </extensions>
</idea-plugin> </idea-plugin>

View File

@@ -3,5 +3,7 @@
<vimExtension implementation="com.maddyhome.idea.vim.extension.surround.VimSurroundExtension"/> <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.multiplecursors.VimMultipleCursorsExtension"/>
<vimExtension implementation="com.maddyhome.idea.vim.extension.commentary.CommentaryExtension"/> <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> </extensions>
</idea-plugin> </idea-plugin>

View File

@@ -3,9 +3,9 @@
<id>IdeaVIM</id> <id>IdeaVIM</id>
<change-notes><![CDATA[ <change-notes><![CDATA[
<ul> <ul>
<li>Support dot command for Surround and Commentary extensions</li> <li>Support argtextobj.vim plugin emulation</li>
<li>Support XDG settings standard</li> <li>Support vim-textobj-entire plugin emulation</li>
<li>Add option to remove the status bar icon</li> <li>Support ls/buffers/files command</li>
<li>Various bug fixes</li> <li>Various bug fixes</li>
</ul> </ul>
<p>See also the complete <a href="https://github.com/JetBrains/ideavim/blob/master/CHANGES.md">changelog</a>.</p> <p>See also the complete <a href="https://github.com/JetBrains/ideavim/blob/master/CHANGES.md">changelog</a>.</p>
@@ -34,25 +34,16 @@
<component> <component>
<implementation-class>com.maddyhome.idea.vim.DynamicLoaderStopper</implementation-class> <implementation-class>com.maddyhome.idea.vim.DynamicLoaderStopper</implementation-class>
</component> </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> </application-components>
<project-components>
<component>
<implementation-class>com.maddyhome.idea.vim.VimProjectComponent</implementation-class>
</component>
</project-components>
<extensionPoints> <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"> <extensionPoint name="vimExCommand" beanClass="com.maddyhome.idea.vim.ex.ExBeanClass" dynamic="true">
<with attribute="implementation" implements="com.maddyhome.idea.vim.ex.CommandHandler"/> <with attribute="implementation" implements="com.maddyhome.idea.vim.ex.CommandHandler"/>
</extensionPoint> </extensionPoint>
<!-- For internal use only -->
<extensionPoint name="vimAction" beanClass="com.maddyhome.idea.vim.handler.ActionBeanClass" dynamic="true"> <extensionPoint name="vimAction" beanClass="com.maddyhome.idea.vim.handler.ActionBeanClass" dynamic="true">
<with attribute="implementation" implements="com.maddyhome.idea.vim.handler.EditorActionHandlerBase"/> <with attribute="implementation" implements="com.maddyhome.idea.vim.handler.EditorActionHandlerBase"/>
</extensionPoint> </extensionPoint>
@@ -62,6 +53,11 @@
<applicationConfigurable groupId="editor" instance="com.maddyhome.idea.vim.ui.VimEmulationConfigurable"/> <applicationConfigurable groupId="editor" instance="com.maddyhome.idea.vim.ui.VimEmulationConfigurable"/>
<projectService serviceImplementation="com.maddyhome.idea.vim.group.NotificationService"/> <projectService serviceImplementation="com.maddyhome.idea.vim.group.NotificationService"/>
<statusBarWidgetProvider implementation="com.maddyhome.idea.vim.StatusBarIconProvider"/> <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> </extensions>
<xi:include href="/META-INF/includes/ApplicationServices.xml" xpointer="xpointer(/idea-plugin/*)"/> <xi:include href="/META-INF/includes/ApplicationServices.xml" xpointer="xpointer(/idea-plugin/*)"/>

View File

@@ -53,16 +53,15 @@ import java.util.Map;
* @author vlan * @author vlan
*/ */
public class EventFacade { 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 Map<Project, MessageBusConnection> connections = new HashMap<>();
private EventFacade() { private EventFacade() {
} }
@NotNull public static @NotNull EventFacade getInstance() {
public static EventFacade getInstance() {
return ourInstance; return ourInstance;
} }
@@ -193,8 +192,7 @@ public class EventFacade {
return connections.get(project); return connections.get(project);
} }
@NotNull private @NotNull TypedAction getTypedAction() {
private TypedAction getTypedAction() {
return EditorActionManager.getInstance().getTypedAction(); return EditorActionManager.getInstance().getTypedAction();
} }
} }

View File

@@ -56,8 +56,10 @@ import org.jetbrains.annotations.Nullable;
import javax.swing.*; import javax.swing.*;
import java.awt.*; import java.awt.*;
import java.awt.event.KeyEvent; import java.awt.event.KeyEvent;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List; import java.util.List;
import java.util.*; import java.util.Map;
import java.util.function.Function; import java.util.function.Function;
import java.util.stream.Collectors; import java.util.stream.Collectors;
@@ -74,8 +76,7 @@ public class KeyHandler {
* *
* @return A reference to the singleton * @return A reference to the singleton
*/ */
@NotNull public static @NotNull KeyHandler getInstance() {
public static KeyHandler getInstance() {
if (instance == null) { if (instance == null) {
instance = new KeyHandler(); instance = new KeyHandler();
} }
@@ -306,7 +307,7 @@ public class KeyHandler {
return true; 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()) { if (editorState.getCommandBuilder().isAtDefaultState()) {
RegisterGroup register = VimPlugin.getRegister(); RegisterGroup register = VimPlugin.getRegister();
if (register.getCurrentRegister() == register.getDefaultRegister()) { if (register.getCurrentRegister() == register.getDefaultRegister()) {
@@ -321,9 +322,9 @@ public class KeyHandler {
ChangeGroup.resetCaret(editor, false); ChangeGroup.resetCaret(editor, false);
} }
private boolean handleKeyMapping(@NotNull final Editor editor, private boolean handleKeyMapping(final @NotNull Editor editor,
@NotNull final KeyStroke key, final @NotNull KeyStroke key,
@NotNull final DataContext context) { final @NotNull DataContext context) {
final CommandState commandState = CommandState.getInstance(editor); final CommandState commandState = CommandState.getInstance(editor);
final MappingState mappingState = commandState.getMappingState(); final MappingState mappingState = commandState.getMappingState();
@@ -430,10 +431,8 @@ public class KeyHandler {
final EditorDataContext currentContext = new EditorDataContext(editor); final EditorDataContext currentContext = new EditorDataContext(editor);
final List<KeyStroke> toKeys = mappingInfo.getToKeys(); if (mappingInfo instanceof ToKeysMappingInfo) {
final VimExtensionHandler extensionHandler = mappingInfo.getExtensionHandler(); final List<KeyStroke> toKeys = ((ToKeysMappingInfo)mappingInfo).getToKeys();
if (toKeys != null) {
final boolean fromIsPrefix = isPrefix(mappingInfo.getFromKeys(), toKeys); final boolean fromIsPrefix = isPrefix(mappingInfo.getFromKeys(), toKeys);
boolean first = true; boolean first = true;
for (KeyStroke keyStroke : toKeys) { for (KeyStroke keyStroke : toKeys) {
@@ -442,7 +441,8 @@ public class KeyHandler {
first = false; first = false;
} }
} }
else if (extensionHandler != null) { else if (mappingInfo instanceof ToHandlerMappingInfo) {
final VimExtensionHandler extensionHandler = ((ToHandlerMappingInfo)mappingInfo).getExtensionHandler();
final CommandProcessor processor = CommandProcessor.getInstance(); final CommandProcessor processor = CommandProcessor.getInstance();
// Cache isOperatorPending in case the extension changes the mode while moving the caret // 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())); editorState.getCommandBuilder().resetAll(getKeyRoot(editorState.getMappingState().getMappingMode()));
} }
@NotNull private @NotNull CommandPartNode getKeyRoot(MappingMode mappingMode) {
private CommandPartNode getKeyRoot(MappingMode mappingMode) {
return VimPlugin.getKey().getKeyRoot(mappingMode); return VimPlugin.getKey().getKeyRoot(mappingMode);
} }
@@ -825,7 +824,10 @@ public class KeyHandler {
VimPlugin.clearError(); VimPlugin.clearError();
CommandState.getInstance(editor).reset(); CommandState.getInstance(editor).reset();
reset(editor); reset(editor);
VimPlugin.getRegister().resetRegister(); RegisterGroup registerGroup = VimPlugin.getRegisterIfCreated();
if (registerGroup != null) {
registerGroup.resetRegister();
}
if (editor != null) { if (editor != null) {
VisualGroupKt.updateCaretState(editor); VisualGroupKt.updateCaretState(editor);
editor.getSelectionModel().removeSelection(); editor.getSelectionModel().removeSelection();
@@ -833,9 +835,8 @@ public class KeyHandler {
} }
// This method is copied from com.intellij.openapi.editor.actionSystem.EditorAction.getProjectAwareDataContext // This method is copied from com.intellij.openapi.editor.actionSystem.EditorAction.getProjectAwareDataContext
@NotNull private static @NotNull DataContext getProjectAwareDataContext(final @NotNull Editor editor,
private static DataContext getProjectAwareDataContext(@NotNull final Editor editor, final @NotNull DataContext original) {
@NotNull final DataContext original) {
if (PROJECT.getData(original) == editor.getProject()) { if (PROJECT.getData(original) == editor.getProject()) {
return new DialogAwareDataContext(original); return new DialogAwareDataContext(original);
} }
@@ -853,7 +854,7 @@ public class KeyHandler {
} }
// This class is copied from com.intellij.openapi.editor.actionSystem.DialogAwareDataContext.DialogAwareDataContext // 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") @SuppressWarnings("rawtypes")
private static final DataKey[] keys = {PROJECT, PROJECT_FILE_DIRECTORY, EDITOR, VIRTUAL_FILE, PSI_FILE}; private static final DataKey[] keys = {PROJECT, PROJECT_FILE_DIRECTORY, EDITOR, VIRTUAL_FILE, PSI_FILE};
private final Map<String, Object> values = new HashMap<>(); private final Map<String, Object> values = new HashMap<>();
@@ -865,9 +866,8 @@ public class KeyHandler {
} }
} }
@Nullable
@Override @Override
public Object getData(@NotNull @NonNls String dataId) { public @Nullable Object getData(@NotNull @NonNls String dataId) {
if (values.containsKey(dataId)) { if (values.containsKey(dataId)) {
return values.get(dataId); return values.get(dataId);
} }

View 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()
}
}

View File

@@ -23,6 +23,7 @@ import com.intellij.openapi.extensions.PluginDescriptor;
import com.maddyhome.idea.vim.group.KeyGroup; import com.maddyhome.idea.vim.group.KeyGroup;
import com.maddyhome.idea.vim.handler.ActionBeanClass; import com.maddyhome.idea.vim.handler.ActionBeanClass;
import com.maddyhome.idea.vim.handler.EditorActionHandlerBase; import com.maddyhome.idea.vim.handler.EditorActionHandlerBase;
import com.maddyhome.idea.vim.key.MappingOwner;
import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable; import org.jetbrains.annotations.Nullable;
@@ -44,7 +45,7 @@ public class RegisterActions {
// ExtensionPoint.addExtensionPointListener(ExtensionPointChangeListener, boolean, Disposable) // ExtensionPoint.addExtensionPointListener(ExtensionPointChangeListener, boolean, Disposable)
VIM_ACTIONS_EP.getPoint(null).addExtensionPointListener(new ExtensionPointListener<ActionBeanClass>() { VIM_ACTIONS_EP.getPoint(null).addExtensionPointListener(new ExtensionPointListener<ActionBeanClass>() {
@Override @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 // Suppress listener before the `VimPlugin.turnOn()` function execution. This logic should be rewritten after
// version update (or earlier). // version update (or earlier).
if (!initialRegistration) return; if (!initialRegistration) return;
@@ -53,7 +54,7 @@ public class RegisterActions {
} }
@Override @Override
public void extensionRemoved(@NotNull ActionBeanClass extension, @NotNull PluginDescriptor pluginDescriptor) { public void extensionRemoved(@NotNull ActionBeanClass extension, PluginDescriptor pluginDescriptor) {
if (!initialRegistration) return; if (!initialRegistration) return;
unregisterActions(); unregisterActions();
registerActions(); registerActions();
@@ -64,27 +65,28 @@ public class RegisterActions {
/** /**
* Register all the key/action mappings for the plugin. * Register all the key/action mappings for the plugin.
*/ */
static void registerActions() { public static void registerActions() {
registerVimCommandActions(); registerVimCommandActions();
registerEmptyShortcuts(); registerEmptyShortcuts();
initialRegistration = true; initialRegistration = true;
} }
@Nullable public static @Nullable EditorActionHandlerBase findAction(@NotNull String id) {
public static EditorActionHandlerBase findAction(@NotNull String id) {
return VIM_ACTIONS_EP.extensions().filter(vimActionBean -> vimActionBean.getActionId().equals(id)).findFirst() return VIM_ACTIONS_EP.extensions().filter(vimActionBean -> vimActionBean.getActionId().equals(id)).findFirst()
.map(ActionBeanClass::getAction).orElse(null); .map(ActionBeanClass::getAction).orElse(null);
} }
@NotNull public static @NotNull EditorActionHandlerBase findActionOrDie(@NotNull String id) {
public static EditorActionHandlerBase findActionOrDie(@NotNull String id) {
EditorActionHandlerBase action = findAction(id); EditorActionHandlerBase action = findAction(id);
if (action == null) throw new RuntimeException("Action " + id + " is not registered"); if (action == null) throw new RuntimeException("Action " + id + " is not registered");
return action; return action;
} }
public static void unregisterActions() { public static void unregisterActions() {
VimPlugin.getKey().unregisterCommandActions(); KeyGroup keyGroup = VimPlugin.getKeyIfCreated();
if (keyGroup != null) {
keyGroup.unregisterCommandActions();
}
} }
private static void registerVimCommandActions() { 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 // 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. // 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);
} }
} }

View File

@@ -50,6 +50,7 @@ import com.intellij.ui.awt.RelativePoint
import com.intellij.util.Consumer import com.intellij.util.Consumer
import com.intellij.util.text.VersionComparatorUtil import com.intellij.util.text.VersionComparatorUtil
import com.maddyhome.idea.vim.group.NotificationService 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.option.OptionsManager
import com.maddyhome.idea.vim.ui.VimEmulationConfigurable import com.maddyhome.idea.vim.ui.VimEmulationConfigurable
import icons.VimIcons import icons.VimIcons
@@ -59,7 +60,11 @@ import javax.swing.Icon
import javax.swing.SwingConstants import javax.swing.SwingConstants
private class StatusBarIconProvider : StatusBarWidgetProvider { 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 { object VimStatusBar : StatusBarWidget, StatusBarWidget.IconPresentation {
@@ -76,7 +81,10 @@ object VimStatusBar : StatusBarWidget, StatusBarWidget.IconPresentation {
override fun getTooltipText() = "IdeaVim" 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 -> override fun getClickConsumer() = Consumer<MouseEvent> { event ->
val component = event.component val component = event.component

View File

@@ -20,9 +20,9 @@ package com.maddyhome.idea.vim
import com.intellij.openapi.components.PersistentStateComponent import com.intellij.openapi.components.PersistentStateComponent
import com.intellij.openapi.components.RoamingType import com.intellij.openapi.components.RoamingType
import com.intellij.openapi.components.ServiceManager
import com.intellij.openapi.components.State import com.intellij.openapi.components.State
import com.intellij.openapi.components.Storage import com.intellij.openapi.components.Storage
import com.maddyhome.idea.vim.VimPlugin.STATE_VERSION
import org.jdom.Element import org.jdom.Element
/** /**
@@ -30,29 +30,27 @@ import org.jdom.Element
*/ */
@State(name = "VimLocalSettings", storages = [ @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, 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 // 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> { class VimLocalConfig : PersistentStateComponent<Element> {
override fun getState(): Element { override fun getState(): Element? = null
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 loadState(state: Element) { 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.getMark().readData(state)
VimPlugin.getRegister().readData(state) VimPlugin.getRegister().readData(state)
VimPlugin.getSearch().readData(state) VimPlugin.getSearch().readData(state)
VimPlugin.getHistory().readData(state) VimPlugin.getHistory().readData(state)
} }
companion object {
fun initialize() {
ServiceManager.getService(VimLocalConfig::class.java)
}
}
} }

View File

@@ -23,10 +23,14 @@ import com.intellij.ide.util.PropertiesComponent;
import com.intellij.notification.Notification; import com.intellij.notification.Notification;
import com.intellij.notification.NotificationListener; import com.intellij.notification.NotificationListener;
import com.intellij.openapi.Disposable; import com.intellij.openapi.Disposable;
import com.intellij.openapi.application.Application;
import com.intellij.openapi.application.ApplicationInfo; import com.intellij.openapi.application.ApplicationInfo;
import com.intellij.openapi.application.ApplicationManager; import com.intellij.openapi.application.ApplicationManager;
import com.intellij.openapi.application.PermanentInstallationID; 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.diagnostic.Logger;
import com.intellij.openapi.extensions.PluginId; import com.intellij.openapi.extensions.PluginId;
import com.intellij.openapi.keymap.Keymap; import com.intellij.openapi.keymap.Keymap;
@@ -66,6 +70,9 @@ import java.io.IOException;
import java.net.URLEncoder; import java.net.URLEncoder;
import java.util.concurrent.TimeUnit; 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 * 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 * 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. * 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")}) @State(name = "VimSettings", storages = {@Storage("$APP_CONFIG$/vim_settings.xml")})
public class VimPlugin implements BaseComponent, PersistentStateComponent<Element>, Disposable { public class VimPlugin implements PersistentStateComponent<Element>, Disposable {
private static final String IDEAVIM_COMPONENT_NAME = "VimPlugin";
private static final String IDEAVIM_PLUGIN_ID = "IdeaVIM"; private static final String IDEAVIM_PLUGIN_ID = "IdeaVIM";
private static final String IDEAVIM_STATISTICS_TIMESTAMP_KEY = "ideavim.statistics.timestamp"; 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; private static long lastBeepTimeMillis;
@@ -94,21 +100,24 @@ public class VimPlugin implements BaseComponent, PersistentStateComponent<Elemen
private static final Logger LOG = Logger.getInstance(VimPlugin.class); private static final Logger LOG = Logger.getInstance(VimPlugin.class);
@NotNull private final @NotNull VimState state = new VimState();
@Override
public String getComponentName() {
return IDEAVIM_COMPONENT_NAME;
}
public void initialize() {
@NotNull private final VimState state = new VimState();
// [VERSION UPDATE] 193+ replace with com.intellij.openapi.components.PersistentStateComponent.initializeComponent
@Override
public void initComponent() {
LOG.debug("initComponent"); 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"); 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 * @return NotificationService as applicationService if project is null and projectService otherwise
*/ */
@NotNull public static @NotNull NotificationService getNotifications(@Nullable Project project) {
public static NotificationService getNotifications(@Nullable Project project) {
if (project == null) { if (project == null) {
return ServiceManager.getService(NotificationService.class); return ServiceManager.getService(NotificationService.class);
} else { }
else {
return ServiceManager.getService(project, NotificationService.class); return ServiceManager.getService(project, NotificationService.class);
} }
} }
@NotNull public static @NotNull VimState getVimState() {
public static VimState getVimState() {
return getInstance().state; return getInstance().state;
} }
@NotNull public static @NotNull MotionGroup getMotion() {
public static MotionGroup getMotion() {
return ServiceManager.getService(MotionGroup.class); return ServiceManager.getService(MotionGroup.class);
} }
@@ -151,18 +158,17 @@ public class VimPlugin implements BaseComponent, PersistentStateComponent<Elemen
public static void statisticReport() { public static void statisticReport() {
final PropertiesComponent propertiesComponent = PropertiesComponent.getInstance(); final PropertiesComponent propertiesComponent = PropertiesComponent.getInstance();
final long lastUpdate = propertiesComponent.getOrInitLong(IDEAVIM_STATISTICS_TIMESTAMP_KEY, 0); final long lastUpdate = propertiesComponent.getOrInitLong(IDEAVIM_STATISTICS_TIMESTAMP_KEY, 0);
final boolean outOfDate = final boolean outOfDate = lastUpdate == 0 || System.currentTimeMillis() - lastUpdate > TimeUnit.DAYS.toMillis(1);
lastUpdate == 0 || System.currentTimeMillis() - lastUpdate > TimeUnit.DAYS.toMillis(1);
if (outOfDate && isEnabled()) { if (outOfDate && isEnabled()) {
ApplicationManager.getApplication().executeOnPooledThread(() -> { ApplicationManager.getApplication().executeOnPooledThread(() -> {
try { try {
final String buildNumber = ApplicationInfo.getInstance().getBuild().asString(); final String buildNumber = ApplicationInfo.getInstance().getBuild().asString();
final String version = URLEncoder.encode(getVersion(), CharsetToolkit.UTF8); final String version = URLEncoder.encode(getVersion(), CharsetToolkit.UTF8);
final String os = final String os = URLEncoder.encode(SystemInfo.OS_NAME + " " + SystemInfo.OS_VERSION, CharsetToolkit.UTF8);
URLEncoder.encode(SystemInfo.OS_NAME + " " + SystemInfo.OS_VERSION, CharsetToolkit.UTF8);
final String uid = PermanentInstallationID.get(); final String uid = PermanentInstallationID.get();
final String url = "https://plugins.jetbrains.com/plugins/list" + final String url = "https://plugins.jetbrains.com/plugins/list" +
"?pluginId=" + IDEAVIM_PLUGIN_ID + "?pluginId=" +
IDEAVIM_PLUGIN_ID +
"&build=" + "&build=" +
buildNumber + buildNumber +
"&pluginVersion=" + "&pluginVersion=" +
@@ -191,106 +197,87 @@ public class VimPlugin implements BaseComponent, PersistentStateComponent<Elemen
} }
} }
@NotNull public static @NotNull ChangeGroup getChange() {
public static ChangeGroup getChange() {
return ServiceManager.getService(ChangeGroup.class); return ServiceManager.getService(ChangeGroup.class);
} }
@NotNull public static @NotNull CommandGroup getCommand() {
public static CommandGroup getCommand() {
return ServiceManager.getService(CommandGroup.class); return ServiceManager.getService(CommandGroup.class);
} }
@NotNull public static @NotNull MarkGroup getMark() {
public static MarkGroup getMark() {
return ServiceManager.getService(MarkGroup.class); return ServiceManager.getService(MarkGroup.class);
} }
@NotNull public static @NotNull RegisterGroup getRegister() {
public static RegisterGroup getRegister() {
return ServiceManager.getService(RegisterGroup.class); return ServiceManager.getService(RegisterGroup.class);
} }
@NotNull public static @Nullable RegisterGroup getRegisterIfCreated() {
public static FileGroup getFile() { return ServiceManager.getServiceIfCreated(RegisterGroup.class);
}
public static @NotNull FileGroup getFile() {
return ServiceManager.getService(FileGroup.class); return ServiceManager.getService(FileGroup.class);
} }
@NotNull public static @NotNull SearchGroup getSearch() {
public static SearchGroup getSearch() {
return ServiceManager.getService(SearchGroup.class); return ServiceManager.getService(SearchGroup.class);
} }
@NotNull public static @Nullable SearchGroup getSearchIfCreated() {
public static ProcessGroup getProcess() { return ServiceManager.getServiceIfCreated(SearchGroup.class);
}
public static @NotNull ProcessGroup getProcess() {
return ServiceManager.getService(ProcessGroup.class); return ServiceManager.getService(ProcessGroup.class);
} }
@NotNull public static @NotNull MacroGroup getMacro() {
public static MacroGroup getMacro() {
return ServiceManager.getService(MacroGroup.class); return ServiceManager.getService(MacroGroup.class);
} }
@NotNull public static @NotNull DigraphGroup getDigraph() {
public static DigraphGroup getDigraph() {
return ServiceManager.getService(DigraphGroup.class); return ServiceManager.getService(DigraphGroup.class);
} }
@NotNull public static @NotNull HistoryGroup getHistory() {
public static HistoryGroup getHistory() {
return ServiceManager.getService(HistoryGroup.class); return ServiceManager.getService(HistoryGroup.class);
} }
@NotNull public static @NotNull KeyGroup getKey() {
public static KeyGroup getKey() {
return ServiceManager.getService(KeyGroup.class); return ServiceManager.getService(KeyGroup.class);
} }
@NotNull public static @Nullable KeyGroup getKeyIfCreated() {
public static WindowGroup getWindow() { return ServiceManager.getServiceIfCreated(KeyGroup.class);
}
public static @NotNull WindowGroup getWindow() {
return ServiceManager.getService(WindowGroup.class); return ServiceManager.getService(WindowGroup.class);
} }
@NotNull public static @NotNull EditorGroup getEditor() {
public static EditorGroup getEditor() {
return ServiceManager.getService(EditorGroup.class); return ServiceManager.getService(EditorGroup.class);
} }
@NotNull public static @Nullable EditorGroup getEditorIfCreated() {
public static VisualMotionGroup getVisualMotion() { return ServiceManager.getServiceIfCreated(EditorGroup.class);
}
public static @NotNull VisualMotionGroup getVisualMotion() {
return ServiceManager.getService(VisualMotionGroup.class); return ServiceManager.getService(VisualMotionGroup.class);
} }
@NotNull public static @NotNull YankGroup getYank() {
public static YankGroup getYank() {
return ServiceManager.getService(YankGroup.class); return ServiceManager.getService(YankGroup.class);
} }
@NotNull public static @NotNull PutGroup getPut() {
public static PutGroup getPut() {
return ServiceManager.getService(PutGroup.class); return ServiceManager.getService(PutGroup.class);
} }
@Override private static @NotNull NotificationService getNotifications() {
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() {
return getNotifications(null); return getNotifications(null);
} }
@@ -308,15 +295,13 @@ public class VimPlugin implements BaseComponent, PersistentStateComponent<Elemen
} }
} }
@NotNull public static @NotNull PluginId getPluginId() {
public static PluginId getPluginId() {
return PluginId.getId(IDEAVIM_PLUGIN_ID); return PluginId.getId(IDEAVIM_PLUGIN_ID);
} }
// [VERSION UPDATE] 193+ remove suppress // [VERSION UPDATE] 193+ remove suppress
@SuppressWarnings({"MissingRecentApi", "UnstableApiUsage"}) @SuppressWarnings({"MissingRecentApi", "UnstableApiUsage"})
@NotNull public static @NotNull String getVersion() {
public static String getVersion() {
final IdeaPluginDescriptor plugin = PluginManager.getPlugin(getPluginId()); final IdeaPluginDescriptor plugin = PluginManager.getPlugin(getPluginId());
if (!ApplicationManager.getApplication().isInternal()) { if (!ApplicationManager.getApplication().isInternal()) {
return plugin != null ? plugin.getVersion() : "SNAPSHOT"; return plugin != null ? plugin.getVersion() : "SNAPSHOT";
@@ -400,9 +385,8 @@ public class VimPlugin implements BaseComponent, PersistentStateComponent<Elemen
} }
} }
@NotNull public static @NotNull VimPlugin getInstance() {
private static VimPlugin getInstance() { return ServiceManager.getService(VimPlugin.class);
return ApplicationManager.getApplication().getComponent(VimPlugin.class);
} }
private void turnOnPlugin() { private void turnOnPlugin() {
@@ -434,8 +418,14 @@ public class VimPlugin implements BaseComponent, PersistentStateComponent<Elemen
// Unregister ex handlers // Unregister ex handlers
CommandParser.getInstance().unregisterHandlers(); CommandParser.getInstance().unregisterHandlers();
getEditor().turnOff(); EditorGroup editorGroup = getEditorIfCreated();
getSearch().turnOff(); if (editorGroup != null) {
editorGroup.turnOff();
}
SearchGroup searchGroup = getSearchIfCreated();
if (searchGroup != null) {
searchGroup.turnOff();
}
VimListenerManager.INSTANCE.turnOff(); VimListenerManager.INSTANCE.turnOff();
ExEntryPanel.fullReset(); ExEntryPanel.fullReset();
} }
@@ -485,7 +475,7 @@ public class VimPlugin implements BaseComponent, PersistentStateComponent<Elemen
} }
@Override @Override
public void loadState(@NotNull final Element element) { public void loadState(final @NotNull Element element) {
LOG.debug("Loading state"); LOG.debug("Loading state");
// Restore whether the plugin is enabled or not // Restore whether the plugin is enabled or not
@@ -500,6 +490,27 @@ public class VimPlugin implements BaseComponent, PersistentStateComponent<Elemen
previousKeyMap = state.getAttributeValue("keymap"); 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) { if (previousStateVersion > 0 && previousStateVersion < 5) {
// Migrate settings from 4 to 5 version // Migrate settings from 4 to 5 version
getMark().readData(element); getMark().readData(element);
@@ -507,8 +518,11 @@ public class VimPlugin implements BaseComponent, PersistentStateComponent<Elemen
getSearch().readData(element); getSearch().readData(element);
getHistory().readData(element); getHistory().readData(element);
} }
getKey().readData(element); if (element.getChild(SHORTCUT_CONFLICTS_ELEMENT) != null) {
getEditor().readData(element); getKey().readData(element);
this.state.readData(element); }
if (element.getChild(EDITOR_STORE_ELEMENT) != null) {
getEditor().readData(element);
}
} }
} }

View File

@@ -37,7 +37,7 @@ import javax.swing.*;
public class VimTypedActionHandler implements TypedActionHandlerEx { public class VimTypedActionHandler implements TypedActionHandlerEx {
private static final Logger logger = Logger.getInstance(VimTypedActionHandler.class.getName()); private static final Logger logger = Logger.getInstance(VimTypedActionHandler.class.getName());
@NotNull private final KeyHandler handler; private final @NotNull KeyHandler handler;
public VimTypedActionHandler(TypedActionHandler origHandler) { public VimTypedActionHandler(TypedActionHandler origHandler) {
handler = KeyHandler.getInstance(); handler = KeyHandler.getInstance();
@@ -50,7 +50,7 @@ public class VimTypedActionHandler implements TypedActionHandlerEx {
} }
@Override @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 { try {
handler.handleKey(editor, KeyStroke.getKeyStroke(charTyped), new EditorDataContext(editor)); handler.handleKey(editor, KeyStroke.getKeyStroke(charTyped), new EditorDataContext(editor));
} }

View File

@@ -60,15 +60,14 @@ public class CommandState {
* *
* This field is reset after the command has been executed. * This field is reset after the command has been executed.
*/ */
@Nullable private Command executingCommand; private @Nullable Command executingCommand;
private CommandState() { private CommandState() {
pushModes(defaultModeState.getMode(), defaultModeState.getSubMode()); pushModes(defaultModeState.getMode(), defaultModeState.getSubMode());
} }
@Contract("null -> new") @Contract("null -> new")
@NotNull public static @NotNull CommandState getInstance(@Nullable Editor editor) {
public static CommandState getInstance(@Nullable Editor editor) {
if (editor == null) { if (editor == null) {
return new CommandState(); return new CommandState();
} }
@@ -82,8 +81,7 @@ public class CommandState {
return res; return res;
} }
@NotNull private static @NotNull CommandPartNode getKeyRootNode(MappingMode mappingMode) {
private static CommandPartNode getKeyRootNode(MappingMode mappingMode) {
return VimPlugin.getKey().getKeyRoot(mappingMode); return VimPlugin.getKey().getKeyRoot(mappingMode);
} }
@@ -106,13 +104,11 @@ public class CommandState {
return commandBuilder; return commandBuilder;
} }
@NotNull public @NotNull MappingState getMappingState() {
public MappingState getMappingState() {
return mappingState; return mappingState;
} }
@Nullable public @Nullable Command getExecutingCommand() {
public Command getExecutingCommand() {
return executingCommand; return executingCommand;
} }
@@ -202,13 +198,11 @@ public class CommandState {
throw new IllegalArgumentException("Unexpected mode: " + mode); throw new IllegalArgumentException("Unexpected mode: " + mode);
} }
@NotNull public @NotNull Mode getMode() {
public Mode getMode() {
return currentModeState().getMode(); return currentModeState().getMode();
} }
@NotNull public @NotNull SubMode getSubMode() {
public SubMode getSubMode() {
return currentModeState().getSubMode(); return currentModeState().getSubMode();
} }
@@ -294,8 +288,7 @@ public class CommandState {
VimPlugin.showMode(msg.toString()); VimPlugin.showMode(msg.toString());
} }
@NotNull private @NotNull String getStatusString(int pos) {
private String getStatusString(int pos) {
ModeState modeState; ModeState modeState;
if (pos >= 0 && pos < modeStates.size()) { if (pos >= 0 && pos < modeStates.size()) {
modeState = modeStates.get(pos); modeState = modeStates.get(pos);
@@ -354,8 +347,8 @@ public class CommandState {
} }
private static class ModeState { private static class ModeState {
@NotNull private final Mode myMode; private final @NotNull Mode myMode;
@NotNull private final SubMode mySubMode; private final @NotNull SubMode mySubMode;
@Contract(pure = true) @Contract(pure = true)
public ModeState(@NotNull Mode mode, @NotNull SubMode subMode) { public ModeState(@NotNull Mode mode, @NotNull SubMode subMode) {
@@ -363,13 +356,11 @@ public class CommandState {
this.mySubMode = subMode; this.mySubMode = subMode;
} }
@NotNull public @NotNull Mode getMode() {
public Mode getMode() {
return myMode; return myMode;
} }
@NotNull public @NotNull SubMode getSubMode() {
public SubMode getSubMode() {
return mySubMode; return mySubMode;
} }

View File

@@ -59,7 +59,7 @@ public class CommandParser {
* *
* @return The singleton instance * @return The singleton instance
*/ */
public synchronized static CommandParser getInstance() { public static synchronized CommandParser getInstance() {
return CommandParserHolder.INSTANCE; return CommandParserHolder.INSTANCE;
} }
@@ -76,7 +76,7 @@ public class CommandParser {
//noinspection deprecation //noinspection deprecation
EX_COMMAND_EP.getPoint(null).addExtensionPointListener(new ExtensionPointListener<ExBeanClass>() { EX_COMMAND_EP.getPoint(null).addExtensionPointListener(new ExtensionPointListener<ExBeanClass>() {
@Override @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 // Suppress listener before the `VimPlugin.turnOn()` function execution. This logic should be rewritten after
// version update (or earlier). // version update (or earlier).
if (!initialRegistration) return; if (!initialRegistration) return;
@@ -85,7 +85,7 @@ public class CommandParser {
} }
@Override @Override
public void extensionRemoved(@NotNull ExBeanClass extension, @NotNull PluginDescriptor pluginDescriptor) { public void extensionRemoved(@NotNull ExBeanClass extension, PluginDescriptor pluginDescriptor) {
if (!initialRegistration) return; if (!initialRegistration) return;
unregisterHandlers(); unregisterHandlers();
registerHandlers(); registerHandlers();
@@ -215,8 +215,7 @@ public class CommandParser {
} }
} }
@Nullable public @Nullable CommandHandler getCommandHandler(@NotNull ExCommand command) {
public CommandHandler getCommandHandler(@NotNull ExCommand command) {
final String cmd = command.getCommand(); final String cmd = command.getCommand();
// If there is no command, just a range, use the 'goto line' handler // If there is no command, just a range, use the 'goto line' handler
if (cmd.length() == 0) { if (cmd.length() == 0) {
@@ -241,8 +240,7 @@ public class CommandParser {
* @return The parse result * @return The parse result
* @throws ExException if the text is syntactically incorrect * @throws ExException if the text is syntactically incorrect
*/ */
@NotNull public @NotNull ExCommand parse(@NotNull String cmd) throws ExException {
public ExCommand parse(@NotNull String cmd) throws ExException {
// This is a complicated state machine that should probably be rewritten // This is a complicated state machine that should probably be rewritten
if (logger.isDebugEnabled()) { if (logger.isDebugEnabled()) {
logger.debug("processing `" + cmd + "'"); 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 { private enum State {
START, START,

View 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
}

View File

@@ -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.handler.MapHandler.SpecialArgument.SCRIPT
import com.maddyhome.idea.vim.ex.vimscript.VimScriptCommandHandler import com.maddyhome.idea.vim.ex.vimscript.VimScriptCommandHandler
import com.maddyhome.idea.vim.helper.StringHelper.parseKeys import com.maddyhome.idea.vim.helper.StringHelper.parseKeys
import com.maddyhome.idea.vim.key.MappingOwner
import java.util.* import java.util.*
import javax.swing.KeyStroke import javax.swing.KeyStroke
@@ -75,8 +76,7 @@ class MapHandler : CommandHandler.SingleExecution(), VimScriptCommandHandler, Co
throw ExException("Unsupported map argument: $unsupportedArgument") throw ExException("Unsupported map argument: $unsupportedArgument")
} }
} }
VimPlugin.getKey().putKeyMapping(modes, arguments.fromKeys, arguments.toKeys, null, VimPlugin.getKey().putKeyMapping(modes, arguments.fromKeys, MappingOwner.IdeaVim, arguments.toKeys, commandInfo.isRecursive)
commandInfo.isRecursive)
return true return true
} }
} }

View File

@@ -33,13 +33,11 @@ public class VimScriptGlobalEnvironment {
private VimScriptGlobalEnvironment() {} private VimScriptGlobalEnvironment() {}
@NotNull public static @NotNull VimScriptGlobalEnvironment getInstance() {
public static VimScriptGlobalEnvironment getInstance() {
return ourInstance; return ourInstance;
} }
@NotNull public @NotNull Map<String, Object> getVariables() {
public Map<String, Object> getVariables() {
return myVariables; return myVariables;
} }
} }

View File

@@ -52,8 +52,7 @@ public class VimScriptParser {
private VimScriptParser() { private VimScriptParser() {
} }
@Nullable public static @Nullable File findIdeaVimRc() {
public static File findIdeaVimRc() {
final String homeDirName = System.getProperty("user.home"); final String homeDirName = System.getProperty("user.home");
// Check whether file exists in home dir // Check whether file exists in home dir
if (homeDirName != null) { if (homeDirName != null) {
@@ -82,8 +81,7 @@ public class VimScriptParser {
return null; return null;
} }
@Nullable public static @Nullable File findOrCreateIdeaVimRc() {
public static File findOrCreateIdeaVimRc() {
final File found = findIdeaVimRc(); final File found = findIdeaVimRc();
if (found != null) return found; if (found != null) return found;
@@ -137,8 +135,7 @@ public class VimScriptParser {
} }
} }
@NotNull public static @NotNull Object evaluate(@NotNull String expression, @NotNull Map<String, Object> globals) throws ExException {
public static 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 // 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. // support mapleader, VIM-650. See also VIM-669.
Matcher m; Matcher m;
@@ -168,8 +165,7 @@ public class VimScriptParser {
throw new ExException(String.format("Invalid expression: %s", expression)); throw new ExException(String.format("Invalid expression: %s", expression));
} }
@NotNull public static @NotNull String expressionToString(@NotNull Object value) throws ExException {
public static String expressionToString(@NotNull Object value) throws ExException {
// TODO: Return meaningful value representations // TODO: Return meaningful value representations
if (value instanceof String) { if (value instanceof String) {
return (String)value; return (String)value;
@@ -179,8 +175,7 @@ public class VimScriptParser {
throw new ExException(String.format("Cannot convert '%s' to string", value)); throw new ExException(String.format("Cannot convert '%s' to string", value));
} }
@NotNull private static @NotNull String readFile(@NotNull File file) throws IOException {
private static String readFile(@NotNull File file) throws IOException {
final BufferedReader reader = new BufferedReader(new FileReader(file)); final BufferedReader reader = new BufferedReader(new FileReader(file));
final StringBuilder builder = new StringBuilder(); final StringBuilder builder = new StringBuilder();
final char[] buffer = new char[BUFSIZE]; final char[] buffer = new char[BUFSIZE];

View File

@@ -19,6 +19,8 @@
package com.maddyhome.idea.vim.extension; package com.maddyhome.idea.vim.extension;
import com.intellij.openapi.extensions.ExtensionPointName; import com.intellij.openapi.extensions.ExtensionPointName;
import com.maddyhome.idea.vim.VimPlugin;
import com.maddyhome.idea.vim.key.MappingOwner;
import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.NotNull;
/** /**
@@ -30,7 +32,13 @@ public interface VimExtension {
@NotNull @NotNull
String getName(); String getName();
default MappingOwner getOwner() {
return MappingOwner.Plugin.Companion.get(getName());
}
void init(); void init();
void dispose(); default void dispose() {
VimPlugin.getKey().removeKeyMapping(getOwner());
};
} }

View File

@@ -27,9 +27,11 @@ import com.maddyhome.idea.vim.helper.EditorDataContext
import com.maddyhome.idea.vim.helper.StringHelper import com.maddyhome.idea.vim.helper.StringHelper
import com.maddyhome.idea.vim.helper.TestInputModel import com.maddyhome.idea.vim.helper.TestInputModel
import com.maddyhome.idea.vim.helper.commandState 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.key.OperatorFunction
import com.maddyhome.idea.vim.ui.ExEntryPanel import com.maddyhome.idea.vim.ui.ExEntryPanel
import com.maddyhome.idea.vim.ui.ModalEntry import com.maddyhome.idea.vim.ui.ModalEntry
import org.jetbrains.annotations.ApiStatus.ScheduledForRemoval
import java.awt.event.KeyEvent import java.awt.event.KeyEvent
import javax.swing.KeyStroke import javax.swing.KeyStroke
@@ -41,18 +43,34 @@ import javax.swing.KeyStroke
* @author vlan * @author vlan
*/ */
object VimExtensionFacade { 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. */ /** The 'map' command for mapping keys to handlers defined in extensions. */
@JvmStatic @JvmStatic
fun putExtensionHandlerMapping(modes: Set<MappingMode>, fromKeys: List<KeyStroke>, fun putExtensionHandlerMapping(modes: Set<MappingMode>, fromKeys: List<KeyStroke>,
extensionHandler: VimExtensionHandler, recursive: Boolean) { pluginOwner: MappingOwner, extensionHandler: VimExtensionHandler, recursive: Boolean) {
VimPlugin.getKey().putKeyMapping(modes, fromKeys, null, extensionHandler, recursive) VimPlugin.getKey().putKeyMapping(modes, fromKeys, pluginOwner, extensionHandler, recursive)
} }
/** The 'map' command for mapping keys to other keys. */ /** The 'map' command for mapping keys to other keys. */
@JvmStatic @JvmStatic
fun putKeyMapping(modes: Set<MappingMode>, fromKeys: List<KeyStroke>, fun putKeyMapping(modes: Set<MappingMode>, fromKeys: List<KeyStroke>,
toKeys: List<KeyStroke>, recursive: Boolean) { pluginOwner: MappingOwner, toKeys: List<KeyStroke>, recursive: Boolean) {
VimPlugin.getKey().putKeyMapping(modes, fromKeys, toKeys, null, recursive) VimPlugin.getKey().putKeyMapping(modes, fromKeys, pluginOwner, toKeys, recursive)
} }
/** Sets the value of 'operatorfunc' to be used as the operator function in 'g@'. */ /** 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 { fun inputKeyStroke(editor: Editor): KeyStroke {
if (editor.commandState.isDotRepeatInProgress) { if (editor.commandState.isDotRepeatInProgress) {
val input = VimRepeater.Extension.consumeKeystroke() 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) { val key: KeyStroke? = if (ApplicationManager.getApplication().isUnitTestMode) {

View File

@@ -21,6 +21,7 @@ package com.maddyhome.idea.vim.extension;
import com.intellij.openapi.diagnostic.Logger; import com.intellij.openapi.diagnostic.Logger;
import com.intellij.openapi.extensions.ExtensionPointListener; import com.intellij.openapi.extensions.ExtensionPointListener;
import com.intellij.openapi.extensions.PluginDescriptor; 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.OptionsManager;
import com.maddyhome.idea.vim.option.ToggleOption; import com.maddyhome.idea.vim.option.ToggleOption;
import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.NotNull;
@@ -34,24 +35,32 @@ import java.util.Set;
*/ */
public class VimExtensionRegistrar { 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 boolean extensionRegistered = false;
private static final Logger logger = Logger.getInstance(VimExtensionRegistrar.class);
public static void registerExtensions() { public static void registerExtensions() {
if (extensionRegistered) return; if (extensionRegistered) return;
extensionRegistered = true; extensionRegistered = true;
// TODO: [VERSION UPDATE] since 191 use // TODO: [VERSION UPDATE] since 191 use
// ExtensionPoint.addExtensionPointListener(ExtensionPointListener<T>, boolean, Disposable) // ExtensionPoint.addExtensionPointListener(ExtensionPointListener<T>, boolean, Disposable)
//noinspection deprecation
VimExtension.EP_NAME.getPoint(null).addExtensionPointListener(new ExtensionPointListener<VimExtension>() { VimExtension.EP_NAME.getPoint(null).addExtensionPointListener(new ExtensionPointListener<VimExtension>() {
@Override @Override
public void extensionAdded(@NotNull VimExtension extension, @NotNull PluginDescriptor pluginDescriptor) { public void extensionAdded(@NotNull VimExtension extension, PluginDescriptor pluginDescriptor) {
registerExtension(extension); 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(); String name = extension.getName();
if (registeredExtensions.contains(name)) return; if (registeredExtensions.contains(name)) return;
@@ -74,5 +83,15 @@ public class VimExtensionRegistrar {
OptionsManager.INSTANCE.addOption(option); 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");
}
} }

View File

@@ -18,29 +18,18 @@
package com.maddyhome.idea.vim.extension; package com.maddyhome.idea.vim.extension;
import com.intellij.openapi.application.ApplicationManager; import org.jetbrains.annotations.ApiStatus;
/** /**
* @author vlan * @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 @Override
public final void init() { public final void init() {
if (ApplicationManager.getApplication().isUnitTestMode()) { initOnce();
initOnce();
}
else {
if (!myInitialized) {
myInitialized = true;
initOnce();
}
}
}
@Override
public final void dispose() {
} }
protected abstract void initOnce(); protected abstract void initOnce();

View File

@@ -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;
}
}
}

View File

@@ -33,8 +33,8 @@ import com.maddyhome.idea.vim.command.CommandState;
import com.maddyhome.idea.vim.command.MappingMode; import com.maddyhome.idea.vim.command.MappingMode;
import com.maddyhome.idea.vim.command.SelectionType; import com.maddyhome.idea.vim.command.SelectionType;
import com.maddyhome.idea.vim.common.TextRange; import com.maddyhome.idea.vim.common.TextRange;
import com.maddyhome.idea.vim.extension.VimExtension;
import com.maddyhome.idea.vim.extension.VimExtensionHandler; import com.maddyhome.idea.vim.extension.VimExtensionHandler;
import com.maddyhome.idea.vim.extension.VimNonDisposableExtension;
import com.maddyhome.idea.vim.key.OperatorFunction; import com.maddyhome.idea.vim.key.OperatorFunction;
import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable; import org.jetbrains.annotations.Nullable;
@@ -45,23 +45,22 @@ import static com.maddyhome.idea.vim.helper.StringHelper.parseKeys;
/** /**
* @author dhleong * @author dhleong
*/ */
public class CommentaryExtension extends VimNonDisposableExtension { public class CommentaryExtension implements VimExtension {
@NotNull
@Override @Override
public String getName() { public @NotNull String getName() {
return "commentary"; return "commentary";
} }
@Override @Override
protected void initOnce() { public void init() {
putExtensionHandlerMapping(MappingMode.N, parseKeys("<Plug>(CommentMotion)"), new CommentMotionHandler(), false); putExtensionHandlerMapping(MappingMode.N, parseKeys("<Plug>(CommentMotion)"), getOwner(), new CommentMotionHandler(), false);
putExtensionHandlerMapping(MappingMode.N, parseKeys("<Plug>(CommentLine)"), new CommentLineHandler(), false); putExtensionHandlerMapping(MappingMode.N, parseKeys("<Plug>(CommentLine)"), getOwner(), new CommentLineHandler(), false);
putExtensionHandlerMapping(MappingMode.XO, parseKeys("<Plug>(CommentMotionV)"), new CommentMotionVHandler(), false); putExtensionHandlerMapping(MappingMode.XO, parseKeys("<Plug>(CommentMotionV)"), getOwner(), new CommentMotionVHandler(), false);
putKeyMapping(MappingMode.N, parseKeys("gc"), parseKeys("<Plug>(CommentMotion)"), true); putKeyMapping(MappingMode.N, parseKeys("gc"), getOwner(), parseKeys("<Plug>(CommentMotion)"), true);
putKeyMapping(MappingMode.N, parseKeys("gcc"), parseKeys("<Plug>(CommentLine)"), true); putKeyMapping(MappingMode.N, parseKeys("gcc"), getOwner(), parseKeys("<Plug>(CommentLine)"), true);
putKeyMapping(MappingMode.XO, parseKeys("gc"), parseKeys("<Plug>(CommentMotionV)"), true); putKeyMapping(MappingMode.XO, parseKeys("gc"), getOwner(), parseKeys("<Plug>(CommentMotionV)"), true);
} }
private static class CommentMotionHandler implements VimExtensionHandler { private static class CommentMotionHandler implements VimExtensionHandler {
@@ -135,8 +134,7 @@ public class CommentaryExtension extends VimNonDisposableExtension {
}); });
} }
@Nullable private @Nullable TextRange getCommentRange(@NotNull Editor editor) {
private TextRange getCommentRange(@NotNull Editor editor) {
final CommandState.Mode mode = CommandState.getInstance(editor).getMode(); final CommandState.Mode mode = CommandState.getInstance(editor).getMode();
switch (mode) { switch (mode) {
case COMMAND: case COMMAND:

View File

@@ -27,10 +27,10 @@ import com.maddyhome.idea.vim.VimPlugin
import com.maddyhome.idea.vim.command.CommandFlags import com.maddyhome.idea.vim.command.CommandFlags
import com.maddyhome.idea.vim.command.MappingMode import com.maddyhome.idea.vim.command.MappingMode
import com.maddyhome.idea.vim.common.TextRange 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.putExtensionHandlerMapping
import com.maddyhome.idea.vim.extension.VimExtensionFacade.putKeyMapping import com.maddyhome.idea.vim.extension.VimExtensionFacade.putKeyMapping
import com.maddyhome.idea.vim.extension.VimExtensionHandler 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.MotionGroup
import com.maddyhome.idea.vim.group.visual.vimSetSelection import com.maddyhome.idea.vim.group.visual.vimSetSelection
import com.maddyhome.idea.vim.helper.EditorHelper 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 * See https://github.com/terryma/vim-multiple-cursors
* */ * */
class VimMultipleCursorsExtension : VimNonDisposableExtension() { class VimMultipleCursorsExtension : VimExtension {
override fun getName() = "multiple-cursors" override fun getName() = "multiple-cursors"
override fun initOnce() { override fun init() {
putExtensionHandlerMapping(MappingMode.NXO, parseKeys(NEXT_WHOLE_OCCURRENCE), NextOccurrenceHandler(), false) putExtensionHandlerMapping(MappingMode.NXO, parseKeys(NEXT_WHOLE_OCCURRENCE), owner, NextOccurrenceHandler(), false)
putExtensionHandlerMapping(MappingMode.NXO, parseKeys(NEXT_OCCURRENCE), NextOccurrenceHandler(whole = false), false) putExtensionHandlerMapping(MappingMode.NXO, parseKeys(NEXT_OCCURRENCE), owner, NextOccurrenceHandler(whole = false), false)
putExtensionHandlerMapping(MappingMode.NXO, parseKeys(ALL_WHOLE_OCCURRENCES), AllOccurrencesHandler(), false) putExtensionHandlerMapping(MappingMode.NXO, parseKeys(ALL_WHOLE_OCCURRENCES), owner, AllOccurrencesHandler(), false )
putExtensionHandlerMapping(MappingMode.NXO, parseKeys(ALL_OCCURRENCES), AllOccurrencesHandler(whole = false), false) putExtensionHandlerMapping(MappingMode.NXO, parseKeys(ALL_OCCURRENCES), owner, AllOccurrencesHandler(whole = false), false )
putExtensionHandlerMapping(MappingMode.X, parseKeys(SKIP_OCCURRENCE), SkipOccurrenceHandler(), false) putExtensionHandlerMapping(MappingMode.X, parseKeys(SKIP_OCCURRENCE), owner, SkipOccurrenceHandler(), false )
putExtensionHandlerMapping(MappingMode.X, parseKeys(REMOVE_OCCURRENCE), RemoveOccurrenceHandler(), 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("<A-n>"), owner, parseKeys(NEXT_WHOLE_OCCURRENCE), true)
putKeyMapping(MappingMode.NXO, parseKeys("g<A-n>"), parseKeys(NEXT_OCCURRENCE), true) putKeyMapping(MappingMode.NXO, parseKeys("g<A-n>"), owner, parseKeys(NEXT_OCCURRENCE), true)
putKeyMapping(MappingMode.X, parseKeys("<A-x>"), parseKeys(SKIP_OCCURRENCE), true) putKeyMapping(MappingMode.X, parseKeys("<A-x>"), owner, parseKeys(SKIP_OCCURRENCE), true)
putKeyMapping(MappingMode.X, parseKeys("<A-p>"), parseKeys(REMOVE_OCCURRENCE), true) putKeyMapping(MappingMode.X, parseKeys("<A-p>"), owner, parseKeys(REMOVE_OCCURRENCE), true)
} }
abstract class WriteActionHandler : VimExtensionHandler { abstract class WriteActionHandler : VimExtensionHandler {

View File

@@ -25,6 +25,7 @@ import com.maddyhome.idea.vim.command.CommandState
import com.maddyhome.idea.vim.command.MappingMode import com.maddyhome.idea.vim.command.MappingMode
import com.maddyhome.idea.vim.command.SelectionType import com.maddyhome.idea.vim.command.SelectionType
import com.maddyhome.idea.vim.common.TextRange import com.maddyhome.idea.vim.common.TextRange
import com.maddyhome.idea.vim.extension.VimExtension
import com.maddyhome.idea.vim.extension.VimExtensionFacade.executeNormal import com.maddyhome.idea.vim.extension.VimExtensionFacade.executeNormal
import com.maddyhome.idea.vim.extension.VimExtensionFacade.getRegister import com.maddyhome.idea.vim.extension.VimExtensionFacade.getRegister
import com.maddyhome.idea.vim.extension.VimExtensionFacade.inputKeyStroke 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.setOperatorFunction
import com.maddyhome.idea.vim.extension.VimExtensionFacade.setRegister import com.maddyhome.idea.vim.extension.VimExtensionFacade.setRegister
import com.maddyhome.idea.vim.extension.VimExtensionHandler 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.EditorHelper
import com.maddyhome.idea.vim.helper.StringHelper import com.maddyhome.idea.vim.helper.StringHelper
import com.maddyhome.idea.vim.helper.mode import com.maddyhome.idea.vim.helper.mode
@@ -51,19 +51,19 @@ import javax.swing.KeyStroke
* @author dhleong * @author dhleong
* @author vlan * @author vlan
*/ */
class VimSurroundExtension : VimNonDisposableExtension() { class VimSurroundExtension : VimExtension {
override fun getName() = "surround" override fun getName() = "surround"
override fun initOnce() { override fun init() {
putExtensionHandlerMapping(MappingMode.N, StringHelper.parseKeys("<Plug>YSurround"), YSurroundHandler(), false) putExtensionHandlerMapping(MappingMode.N, StringHelper.parseKeys("<Plug>YSurround"), owner, YSurroundHandler(), false)
putExtensionHandlerMapping(MappingMode.N, StringHelper.parseKeys("<Plug>CSurround"), CSurroundHandler(), false) putExtensionHandlerMapping(MappingMode.N, StringHelper.parseKeys("<Plug>CSurround"), owner, CSurroundHandler(), false)
putExtensionHandlerMapping(MappingMode.N, StringHelper.parseKeys("<Plug>DSurround"), DSurroundHandler(), false) putExtensionHandlerMapping(MappingMode.N, StringHelper.parseKeys("<Plug>DSurround"), owner, DSurroundHandler(), false)
putExtensionHandlerMapping(MappingMode.XO, StringHelper.parseKeys("<Plug>VSurround"), VSurroundHandler(), 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("ys"), owner, StringHelper.parseKeys("<Plug>YSurround"), true)
putKeyMapping(MappingMode.N, StringHelper.parseKeys("cs"), StringHelper.parseKeys("<Plug>CSurround"), true) putKeyMapping(MappingMode.N, StringHelper.parseKeys("cs"), owner, StringHelper.parseKeys("<Plug>CSurround"), true)
putKeyMapping(MappingMode.N, StringHelper.parseKeys("ds"), StringHelper.parseKeys("<Plug>DSurround"), true) putKeyMapping(MappingMode.N, StringHelper.parseKeys("ds"), owner, StringHelper.parseKeys("<Plug>DSurround"), true)
putKeyMapping(MappingMode.XO, StringHelper.parseKeys("S"), StringHelper.parseKeys("<Plug>VSurround"), true) putKeyMapping(MappingMode.XO, StringHelper.parseKeys("S"), owner, StringHelper.parseKeys("<Plug>VSurround"), true)
} }
private class YSurroundHandler : VimExtensionHandler { private class YSurroundHandler : VimExtensionHandler {

View File

@@ -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())));
}
}
}
}

View File

@@ -85,7 +85,7 @@ public class ChangeGroup {
private static final String VIM_MOTION_BIG_WORD_END_RIGHT = "VimMotionBigWordEndRightAction"; private static final String VIM_MOTION_BIG_WORD_END_RIGHT = "VimMotionBigWordEndRightAction";
private static final String VIM_MOTION_CAMEL_END_RIGHT = "VimMotionCamelEndRightAction"; 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) { private void setInsertRepeat(int lines, int column, boolean append) {
repeatLines = lines; repeatLines = lines;
@@ -154,7 +154,7 @@ public class ChangeGroup {
* *
* @param editor The editor to insert into * @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; if (editor.isOneLineMode()) return;
Set<Caret> firstLiners = new HashSet<>(); Set<Caret> firstLiners = new HashSet<>();
@@ -214,7 +214,7 @@ public class ChangeGroup {
* @param editor The editor to insert into * @param editor The editor to insert into
* @param context The data context * @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; if (editor.isOneLineMode()) return;
for (Caret caret : editor.getCaretModel().getAllCarets()) { for (Caret caret : editor.getCaretModel().getAllCarets()) {
@@ -308,7 +308,7 @@ public class ChangeGroup {
return false; 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. * 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. // Workaround for VIM-1546. Another solution is highly appreciated.
public boolean tabAction = false; public boolean tabAction = false;
@NotNull private final EditorMouseListener listener = new EditorMouseListener() { private final @NotNull EditorMouseListener listener = new EditorMouseListener() {
@Override @Override
public void mouseClicked(@NotNull EditorMouseEvent event) { public void mouseClicked(@NotNull EditorMouseEvent event) {
Editor editor = event.getEditor(); Editor editor = event.getEditor();
@@ -737,9 +737,9 @@ public class ChangeGroup {
* @param key The user entered keystroke * @param key The user entered keystroke
* @param plan the current action plan draft * @param plan the current action plan draft
*/ */
public void beforeProcessKey(@NotNull final Editor editor, public void beforeProcessKey(final @NotNull Editor editor,
@NotNull final DataContext context, final @NotNull DataContext context,
@NotNull final KeyStroke key, final @NotNull KeyStroke key,
@NotNull ActionPlan plan) { @NotNull ActionPlan plan) {
final TypedActionHandler originalHandler = KeyHandler.getInstance().getOriginalHandler(); final TypedActionHandler originalHandler = KeyHandler.getInstance().getOriginalHandler();
@@ -805,9 +805,9 @@ public class ChangeGroup {
* @param key The user entered keystroke * @param key The user entered keystroke
* @return true if this was a regular character, false if not * @return true if this was a regular character, false if not
*/ */
public boolean processKey(@NotNull final Editor editor, public boolean processKey(final @NotNull Editor editor,
@NotNull final DataContext context, final @NotNull DataContext context,
@NotNull final KeyStroke key) { final @NotNull KeyStroke key) {
if (logger.isDebugEnabled()) { if (logger.isDebugEnabled()) {
logger.debug("processKey(" + key + ")"); logger.debug("processKey(" + key + ")");
} }
@@ -824,9 +824,9 @@ public class ChangeGroup {
return false; return false;
} }
public boolean processKeyInSelectMode(@NotNull final Editor editor, public boolean processKeyInSelectMode(final @NotNull Editor editor,
@NotNull final DataContext context, final @NotNull DataContext context,
@NotNull final KeyStroke key) { final @NotNull KeyStroke key) {
boolean res; boolean res;
try (VimListenerSuppressor.Locked ignored = SelectionVimListenerSuppressor.INSTANCE.lock()) { try (VimListenerSuppressor.Locked ignored = SelectionVimListenerSuppressor.INSTANCE.lock()) {
res = processKey(editor, context, key); res = processKey(editor, context, key);
@@ -1061,14 +1061,13 @@ public class ChangeGroup {
return true; return true;
} }
@Nullable public @Nullable Pair<TextRange, SelectionType> getDeleteRangeAndType(@NotNull Editor editor,
public Pair<TextRange, SelectionType> getDeleteRangeAndType(@NotNull Editor editor, @NotNull Caret caret,
@NotNull Caret caret, @NotNull DataContext context,
@NotNull DataContext context, int count,
int count, int rawCount,
int rawCount, final @NotNull Argument argument,
@NotNull final Argument argument, boolean isChange) {
boolean isChange) {
final TextRange range = MotionGroup.getMotionRange(editor, caret, context, count, rawCount, argument); final TextRange range = MotionGroup.getMotionRange(editor, caret, context, count, rawCount, argument);
if (range == null) return null; if (range == null) return null;
@@ -1163,7 +1162,8 @@ public class ChangeGroup {
final LogicalPosition lp = final LogicalPosition lp =
editor.offsetToLogicalPosition(VimPlugin.getMotion().moveCaretToLineStartSkipLeading(editor, caret)); 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); insertBeforeCursor(editor, context);
return true; return true;
} }
@@ -1456,7 +1456,8 @@ public class ChangeGroup {
boolean res = deleteRange(editor, caret, range, type, true); boolean res = deleteRange(editor, caret, range, type, true);
if (res) { if (res) {
if (type == SelectionType.LINE_WISE) { 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); insertBeforeCursor(editor, context);
} }
else if (after) { else if (after) {
@@ -1729,8 +1730,8 @@ public class ChangeGroup {
* @param type The type of deletion * @param type The type of deletion
* @return true if able to delete the text, false if not * @return true if able to delete the text, false if not
*/ */
private boolean deleteText(@NotNull final Editor editor, private boolean deleteText(final @NotNull Editor editor,
@NotNull final TextRange range, final @NotNull TextRange range,
@Nullable SelectionType type) { @Nullable SelectionType type) {
// Fix for https://youtrack.jetbrains.net/issue/VIM-35 // Fix for https://youtrack.jetbrains.net/issue/VIM-35
if (!range.normalize(EditorHelper.getFileSize(editor, true))) { if (!range.normalize(EditorHelper.getFileSize(editor, true))) {
@@ -1838,7 +1839,7 @@ public class ChangeGroup {
* *
* @return true * @return true
*/ */
public boolean changeNumberVisualMode(@NotNull final Editor editor, public boolean changeNumberVisualMode(final @NotNull Editor editor,
@NotNull Caret caret, @NotNull Caret caret,
@NotNull TextRange selectedRange, @NotNull TextRange selectedRange,
final int count, final int count,
@@ -1882,7 +1883,7 @@ public class ChangeGroup {
private int repeatCharsCount; private int repeatCharsCount;
private List<Object> lastStrokes; 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 BoundListOption nf = OptionsManager.INSTANCE.getNrformats();
final boolean alpha = nf.contains("alpha"); final boolean alpha = nf.contains("alpha");
final boolean hex = nf.contains("hex"); final boolean hex = nf.contains("hex");
@@ -1911,13 +1912,12 @@ public class ChangeGroup {
private boolean lastLower = true; private boolean lastLower = true;
private Document document; private Document document;
@Nullable public @Nullable String changeNumberInRange(final @NotNull Editor editor,
public String changeNumberInRange(@NotNull final Editor editor, @NotNull TextRange range,
@NotNull TextRange range, final int count,
final int count, boolean alpha,
boolean alpha, boolean hex,
boolean hex, boolean octal) {
boolean octal) {
String text = EditorHelper.getText(editor, range); String text = EditorHelper.getText(editor, range);
if (logger.isDebugEnabled()) { if (logger.isDebugEnabled()) {
logger.debug("found range " + range); logger.debug("found range " + range);
@@ -2035,8 +2035,7 @@ public class ChangeGroup {
oldOffset = e.getOffset() + newFragmentLength; oldOffset = e.getOffset() + newFragmentLength;
} }
@NotNull private @NotNull List<EditorActionHandlerBase> getAdjustCaretActions(@NotNull DocumentEvent e) {
private List<EditorActionHandlerBase> getAdjustCaretActions(@NotNull DocumentEvent e) {
final int delta = e.getOffset() - oldOffset; final int delta = e.getOffset() - oldOffset;
if (oldOffset >= 0 && delta != 0) { if (oldOffset >= 0 && delta != 0) {
final List<EditorActionHandlerBase> positionCaretActions = new ArrayList<>(); final List<EditorActionHandlerBase> positionCaretActions = new ArrayList<>();

View File

@@ -1749,8 +1749,8 @@ public class DigraphGroup {
'f', 't', '\ufb05', // LATIN SMALL LIGATURE FT 'f', 't', '\ufb05', // LATIN SMALL LIGATURE FT
's', 't', '\ufb06', // LATIN SMALL LIGATURE ST 's', 't', '\ufb06', // LATIN SMALL LIGATURE ST
}; };
@NotNull private final HashMap<String, Character> digraphs = new HashMap<>(defaultDigraphs.length); private final @NotNull HashMap<String, Character> digraphs = new HashMap<>(defaultDigraphs.length);
@NotNull private final TreeMap<Character, String> keys = new TreeMap<>(); private final @NotNull TreeMap<Character, String> keys = new TreeMap<>();
private static final Logger logger = Logger.getInstance(DigraphGroup.class.getName()); private static final Logger logger = Logger.getInstance(DigraphGroup.class.getName());
} }

View File

@@ -21,6 +21,9 @@ package com.maddyhome.idea.vim.group;
import com.intellij.find.EditorSearchSession; import com.intellij.find.EditorSearchSession;
import com.intellij.openapi.actionSystem.AnAction; import com.intellij.openapi.actionSystem.AnAction;
import com.intellij.openapi.application.ApplicationManager; 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.*;
import com.intellij.openapi.editor.colors.ColorKey; import com.intellij.openapi.editor.colors.ColorKey;
import com.intellij.openapi.editor.colors.EditorColors; import com.intellij.openapi.editor.colors.EditorColors;
@@ -47,9 +50,11 @@ import java.util.List;
/** /**
* @author vlan * @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 ANIMATED_SCROLLING_VIM_VALUE = false;
private static final boolean REFRAIN_FROM_SCROLLING_VIM_VALUE = true; private static final boolean REFRAIN_FROM_SCROLLING_VIM_VALUE = true;
public static final String EDITOR_STORE_ELEMENT = "editor";
private boolean isBlockCursor = false; private boolean isBlockCursor = false;
private boolean isAnimatedScrolling = 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)) { if (!supportsVimLineNumbers(editor) || UserDataManager.getVimEditorGroup(editor)) {
return; 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 // 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 // own line numbers, often using EditorGutterComponentEx#setLineNumberConvertor
return EditorHelper.isFileEditor(editor) && !EditorHelper.isDiffEditor(editor); 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 relativeNumber = OptionsManager.INSTANCE.getRelativenumber().isSet();
final boolean number = OptionsManager.INSTANCE.getNumber().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); final boolean initialState = UserDataManager.getVimLineNumbersInitialState(editor);
// Builtin relative line numbers requires EditorGutterComponentEx#setLineNumberConvertor. If we don't have that, // Builtin relative line numbers requires EditorGutterComponentEx#setLineNumberConvertor. If we don't have that,
@@ -155,15 +160,15 @@ public class EditorGroup {
return (initialState || number) && !relativeNumber; 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); editor.getSettings().setLineNumbersShown(show);
} }
private static boolean hasRelativeLineNumbersInstalled(@NotNull final Editor editor) { private static boolean hasRelativeLineNumbersInstalled(final @NotNull Editor editor) {
return UserDataManager.getVimHasRelativeLineNumbersInstalled(editor); return UserDataManager.getVimHasRelativeLineNumbersInstalled(editor);
} }
private static void installRelativeLineNumbers(@NotNull final Editor editor) { private static void installRelativeLineNumbers(final @NotNull Editor editor) {
if (!hasRelativeLineNumbersInstalled(editor)) { if (!hasRelativeLineNumbersInstalled(editor)) {
final EditorGutter gutter = editor.getGutter(); final EditorGutter gutter = editor.getGutter();
if (gutter instanceof EditorGutterComponentEx) { 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)) { if (hasRelativeLineNumbersInstalled(editor)) {
final EditorGutter gutter = editor.getGutter(); final EditorGutter gutter = editor.getGutter();
if (gutter instanceof EditorGutterComponentEx) { 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 EditorGutter gutter = editor.getGutter();
final EditorGutterComponentEx gutterComponent = gutter instanceof EditorGutterComponentEx ? (EditorGutterComponentEx) gutter : null; final EditorGutterComponentEx gutterComponent = gutter instanceof EditorGutterComponentEx ? (EditorGutterComponentEx) gutter : null;
if (gutterComponent != null) { if (gutterComponent != null) {
@@ -210,7 +215,7 @@ public class EditorGroup {
} }
public void readData(@NotNull Element element) { public void readData(@NotNull Element element) {
final Element editor = element.getChild("editor"); final Element editor = element.getChild(EDITOR_STORE_ELEMENT);
if (editor != null) { if (editor != null) {
final Element keyRepeat = editor.getChild("key-repeat"); final Element keyRepeat = editor.getChild("key-repeat");
if (keyRepeat != null) { if (keyRepeat != null) {
@@ -222,8 +227,7 @@ public class EditorGroup {
} }
} }
@Nullable public @Nullable Boolean isKeyRepeat() {
public Boolean isKeyRepeat() {
return isKeyRepeat; return isKeyRepeat;
} }
@@ -275,6 +279,19 @@ public class EditorGroup {
VimPlugin.getNotifications(project).notifyAboutIdeaJoin(); 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 class NumberChangeListener implements OptionChangeListener<Boolean> {
public static NumberChangeListener INSTANCE = new NumberChangeListener(); public static NumberChangeListener INSTANCE = new NumberChangeListener();
@@ -293,11 +310,10 @@ public class EditorGroup {
} }
private static class RelativeLineNumberConverter implements TIntFunction { private static class RelativeLineNumberConverter implements TIntFunction {
@NotNull private final @NotNull Editor editor;
private final Editor editor;
@Contract(pure = true) @Contract(pure = true)
RelativeLineNumberConverter(@NotNull final Editor editor) { RelativeLineNumberConverter(final @NotNull Editor editor) {
this.editor = editor; this.editor = editor;
} }
@@ -322,17 +338,15 @@ public class EditorGroup {
} }
private static class RelativeLineNumberGutterProvider implements TextAnnotationGutterProvider { private static class RelativeLineNumberGutterProvider implements TextAnnotationGutterProvider {
@NotNull private final @NotNull Editor editor;
private final Editor editor;
@Contract(pure = true) @Contract(pure = true)
RelativeLineNumberGutterProvider(@NotNull final Editor editor) { RelativeLineNumberGutterProvider(final @NotNull Editor editor) {
this.editor = editor; this.editor = editor;
} }
@Nullable
@Override @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(); final boolean number = OptionsManager.INSTANCE.getNumber().isSet();
if (number && isCaretLine(line, editor)) { if (number && isCaretLine(line, editor)) {
return lineNumberToString(line + 1, editor, true); return lineNumberToString(line + 1, editor, true);
@@ -360,9 +374,8 @@ public class EditorGroup {
: StringsKt.padStart(Integer.toString(lineNumber), digitsCount, ' '); : StringsKt.padStart(Integer.toString(lineNumber), digitsCount, ' ');
} }
@Nullable
@Override @Override
public String getToolTip(int line, Editor editor) { public @Nullable String getToolTip(int line, Editor editor) {
return null; return null;
} }
@@ -371,15 +384,13 @@ public class EditorGroup {
return isCaretLine(line, editor) ? EditorFontType.BOLD: null; return isCaretLine(line, editor) ? EditorFontType.BOLD: null;
} }
@Nullable
@Override @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; return isCaretLine(line, editor) ? EditorColors.LINE_NUMBER_ON_CARET_ROW_COLOR : EditorColors.LINE_NUMBERS_COLOR;
} }
@Nullable
@Override @Override
public Color getBgColor(int line, Editor editor) { public @Nullable Color getBgColor(int line, Editor editor) {
return null; return null;
} }

View File

@@ -118,8 +118,7 @@ public class FileGroup {
return found; return found;
} }
@Nullable private @Nullable VirtualFile findFile(@NotNull VirtualFile root, @NotNull String filename) {
private VirtualFile findFile(@NotNull VirtualFile root, @NotNull String filename) {
VirtualFile res = root.findFileByRelativePath(filename); VirtualFile res = root.findFileByRelativePath(filename);
if (res != null) { if (res != null) {
return res; 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 @Nullable
Editor selectEditor(Project project, @NotNull VirtualFile file) { Editor selectEditor(Project project, @NotNull VirtualFile file) {
FileEditorManager fMgr = FileEditorManager.getInstance(project); FileEditorManager fMgr = FileEditorManager.getInstance(project);
@@ -382,10 +395,10 @@ public class FileGroup {
VimPlugin.showMessage(msg.toString()); 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<>(); private static final @NotNull HashMap<FileEditorManager, VirtualFile> lastSelections = new HashMap<>();
@NotNull private static final Logger logger = Logger.getInstance(FileGroup.class.getName()); 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. * This method listens for editor tab changes so any insert/replace modes that need to be reset can be.

View File

@@ -18,19 +18,27 @@
package com.maddyhome.idea.vim.group; 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.intellij.openapi.diagnostic.Logger;
import com.maddyhome.idea.vim.helper.StringHelper; import com.maddyhome.idea.vim.helper.StringHelper;
import com.maddyhome.idea.vim.option.NumberOption; import com.maddyhome.idea.vim.option.NumberOption;
import com.maddyhome.idea.vim.option.OptionsManager; import com.maddyhome.idea.vim.option.OptionsManager;
import org.jdom.Element; import org.jdom.Element;
import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.HashMap; import java.util.HashMap;
import java.util.List; import java.util.List;
import java.util.Map; 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 SEARCH = "search";
public static final String COMMAND = "cmd"; public static final String COMMAND = "cmd";
public static final String EXPRESSION = "expr"; public static final String EXPRESSION = "expr";
@@ -45,8 +53,7 @@ public class HistoryGroup {
block.addEntry(text); block.addEntry(text);
} }
@NotNull public @NotNull List<HistoryEntry> getEntries(String key, int first, int last) {
public List<HistoryEntry> getEntries(String key, int first, int last) {
HistoryBlock block = blocks(key); HistoryBlock block = blocks(key);
List<HistoryEntry> entries = block.getEntries(); List<HistoryEntry> entries = block.getEntries();
@@ -167,6 +174,19 @@ public class HistoryGroup {
return opt.value(); 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 { private static class HistoryBlock {
public void addEntry(@NotNull String text) { public void addEntry(@NotNull String text) {
for (int i = 0; i < entries.size(); i++) { for (int i = 0; i < entries.size(); i++) {
@@ -184,12 +204,11 @@ public class HistoryGroup {
} }
} }
@NotNull public @NotNull List<HistoryEntry> getEntries() {
public List<HistoryEntry> getEntries() {
return entries; return entries;
} }
@NotNull private final List<HistoryEntry> entries = new ArrayList<>(); private final @NotNull List<HistoryEntry> entries = new ArrayList<>();
private int counter; private int counter;
} }
@@ -203,16 +222,15 @@ public class HistoryGroup {
return number; return number;
} }
@NotNull public @NotNull String getEntry() {
public String getEntry() {
return entry; return entry;
} }
private final int number; 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()); private static final Logger logger = Logger.getInstance(HistoryGroup.class.getName());
} }

View File

@@ -24,6 +24,9 @@ import com.intellij.openapi.actionSystem.*;
import com.intellij.openapi.actionSystem.ex.ActionManagerEx; import com.intellij.openapi.actionSystem.ex.ActionManagerEx;
import com.intellij.openapi.actionSystem.ex.ActionUtil; import com.intellij.openapi.actionSystem.ex.ActionUtil;
import com.intellij.openapi.application.ApplicationManager; 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.diagnostic.Logger;
import com.intellij.openapi.editor.Editor; import com.intellij.openapi.editor.Editor;
import com.intellij.openapi.editor.EditorFactory; 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.handler.EditorActionHandlerBase;
import com.maddyhome.idea.vim.helper.StringHelper; import com.maddyhome.idea.vim.helper.StringHelper;
import com.maddyhome.idea.vim.key.*; import com.maddyhome.idea.vim.key.*;
import kotlin.Pair;
import kotlin.text.StringsKt; import kotlin.text.StringsKt;
import org.jdom.Element; import org.jdom.Element;
import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.NotNull;
@@ -54,27 +58,30 @@ import java.util.*;
import static com.maddyhome.idea.vim.helper.StringHelper.toKeyNotation; import static com.maddyhome.idea.vim.helper.StringHelper.toKeyNotation;
import static java.util.stream.Collectors.joining; import static java.util.stream.Collectors.joining;
import static java.util.stream.Collectors.toList;
/** /**
* @author vlan * @author vlan
*/ */
public class KeyGroup { @State(name = "VimKeySettings", storages = {@Storage(value = "$APP_CONFIG$/vim_settings.xml")})
private static final String SHORTCUT_CONFLICTS_ELEMENT = "shortcut-conflicts"; 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 SHORTCUT_CONFLICT_ELEMENT = "shortcut-conflict";
private static final String OWNER_ATTRIBUTE = "owner"; private static final String OWNER_ATTRIBUTE = "owner";
private static final String TEXT_ELEMENT = "text"; private static final String TEXT_ELEMENT = "text";
private static final Logger logger = Logger.getInstance(KeyGroup.class); private static final Logger logger = Logger.getInstance(KeyGroup.class);
@NotNull private final Map<KeyStroke, ShortcutOwner> shortcutConflicts = new LinkedHashMap<>(); private final @NotNull Map<KeyStroke, ShortcutOwner> shortcutConflicts = new LinkedHashMap<>();
@NotNull private final Set<KeyStroke> requiredShortcutKeys = new HashSet<>(300); private final @NotNull Set<RequiredShortcut> requiredShortcutKeys = new HashSet<>(300);
@NotNull private final Map<MappingMode, CommandPartNode> keyRoots = new EnumMap<>(MappingMode.class); private final @NotNull Map<MappingMode, CommandPartNode> keyRoots = new EnumMap<>(MappingMode.class);
@NotNull private final Map<MappingMode, KeyMapping> keyMappings = new EnumMap<>(MappingMode.class); private final @NotNull Map<MappingMode, KeyMapping> keyMappings = new EnumMap<>(MappingMode.class);
@Nullable private OperatorFunction operatorFunction = null; private @Nullable OperatorFunction operatorFunction = null;
void registerRequiredShortcutKeys(@NotNull Editor editor) { void registerRequiredShortcutKeys(@NotNull Editor editor) {
EventFacade.getInstance().registerCustomShortcutSet(VimShortcutKeyAction.getInstance(), EventFacade.getInstance()
toShortcutSet(requiredShortcutKeys), editor.getComponent()); .registerCustomShortcutSet(VimShortcutKeyAction.getInstance(), toShortcutSet(requiredShortcutKeys),
editor.getComponent());
} }
public void registerShortcutsForLookup(@NotNull LookupImpl lookup) { public void registerShortcutsForLookup(@NotNull LookupImpl lookup) {
@@ -88,44 +95,75 @@ public class KeyGroup {
} }
public boolean showKeyMappings(@NotNull Set<MappingMode> modes, @NotNull Editor editor) { 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(); final StringBuilder builder = new StringBuilder();
for (MappingInfo row : rows) { for (Pair<EnumSet<MappingMode>, MappingInfo> row : rows) {
builder.append(StringsKt.padEnd(getModesStringCode(row.getMappingModes()), 2, ' ')); MappingInfo mappingInfo = row.getSecond();
builder.append(StringsKt.padEnd(getModesStringCode(row.getFirst()), 2, ' '));
builder.append(" "); builder.append(" ");
builder.append(StringsKt.padEnd(toKeyNotation(row.getFromKeys()), 11, ' ')); builder.append(StringsKt.padEnd(toKeyNotation(mappingInfo.getFromKeys()), 11, ' '));
builder.append(" "); builder.append(" ");
builder.append(row.isRecursive() ? " " : "*"); builder.append(mappingInfo.isRecursive() ? " " : "*");
builder.append(" "); builder.append(" ");
final List<KeyStroke> toKeys = row.getToKeys(); if (mappingInfo instanceof ToKeysMappingInfo) {
final VimExtensionHandler extensionHandler = row.getExtensionHandler(); List<KeyStroke> toKeys = ((ToKeysMappingInfo)mappingInfo).getToKeys();
if (toKeys != null) {
builder.append(toKeyNotation(toKeys)); builder.append(toKeyNotation(toKeys));
} }
else if (extensionHandler != null) { else if (mappingInfo instanceof ToHandlerMappingInfo) {
final VimExtensionHandler extensionHandler = ((ToHandlerMappingInfo)mappingInfo).getExtensionHandler();
builder.append("call "); builder.append("call ");
builder.append(extensionHandler.getClass().getCanonicalName()); builder.append(extensionHandler.getClass().getCanonicalName());
} }
else {
builder.append("<Unknown>");
}
builder.append("\n"); builder.append("\n");
} }
ExOutputModel.getInstance(editor).output(builder.toString()); ExOutputModel.getInstance(editor).output(builder.toString());
return true; return true;
} }
public void putKeyMapping(@NotNull Set<MappingMode> modes, @NotNull List<KeyStroke> fromKeys, public void removeKeyMapping(@NotNull MappingOwner owner) {
@Nullable List<KeyStroke> toKeys, @Nullable VimExtensionHandler extensionHandler, 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) { boolean recursive) {
for (MappingMode mode : modes) { modes.stream().map(this::getKeyMapping).forEach(o -> o.put(fromKeys, owner, extensionHandler, recursive));
final KeyMapping mapping = getKeyMapping(mode); registerKeyMapping(fromKeys, owner);
mapping.put(EnumSet.of(mode), fromKeys, toKeys, extensionHandler, recursive); }
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(); final int oldSize = requiredShortcutKeys.size();
for (KeyStroke key : fromKeys) { for (KeyStroke key : fromKeys) {
if (key.getKeyChar() == KeyEvent.CHAR_UNDEFINED) { if (key.getKeyChar() == KeyEvent.CHAR_UNDEFINED) {
requiredShortcutKeys.add(key); requiredShortcutKeys.add(new RequiredShortcut(key, owner));
} }
} }
if (requiredShortcutKeys.size() != oldSize) { if (requiredShortcutKeys.size() != oldSize) {
@@ -136,8 +174,7 @@ public class KeyGroup {
} }
} }
@Nullable public @Nullable OperatorFunction getOperatorFunction() {
public OperatorFunction getOperatorFunction() {
return operatorFunction; return operatorFunction;
} }
@@ -187,8 +224,7 @@ public class KeyGroup {
} }
} }
@NotNull public @NotNull List<AnAction> getKeymapConflicts(@NotNull KeyStroke keyStroke) {
public List<AnAction> getKeymapConflicts(@NotNull KeyStroke keyStroke) {
final KeymapManagerEx keymapManager = KeymapManagerEx.getInstanceEx(); final KeymapManagerEx keymapManager = KeymapManagerEx.getInstanceEx();
final Keymap keymap = keymapManager.getActiveKeymap(); final Keymap keymap = keymapManager.getActiveKeymap();
final KeyboardShortcut shortcut = new KeyboardShortcut(keyStroke, null); final KeyboardShortcut shortcut = new KeyboardShortcut(keyStroke, null);
@@ -203,12 +239,12 @@ public class KeyGroup {
return actions; return actions;
} }
@NotNull public @NotNull Map<KeyStroke, ShortcutOwner> getShortcutConflicts() {
public Map<KeyStroke, ShortcutOwner> getShortcutConflicts() { final Set<RequiredShortcut> requiredShortcutKeys = this.requiredShortcutKeys;
final Set<KeyStroke> requiredShortcutKeys = this.requiredShortcutKeys;
final Map<KeyStroke, ShortcutOwner> savedConflicts = getSavedShortcutConflicts(); final Map<KeyStroke, ShortcutOwner> savedConflicts = getSavedShortcutConflicts();
final Map<KeyStroke, ShortcutOwner> results = new HashMap<>(); 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)) { if (!VimShortcutKeyAction.VIM_ONLY_EDITOR_KEYS.contains(keyStroke)) {
final List<AnAction> conflicts = getKeymapConflicts(keyStroke); final List<AnAction> conflicts = getKeymapConflicts(keyStroke);
if (!conflicts.isEmpty()) { if (!conflicts.isEmpty()) {
@@ -220,13 +256,11 @@ public class KeyGroup {
return results; return results;
} }
@NotNull public @NotNull Map<KeyStroke, ShortcutOwner> getSavedShortcutConflicts() {
public Map<KeyStroke, ShortcutOwner> getSavedShortcutConflicts() {
return shortcutConflicts; return shortcutConflicts;
} }
@NotNull public @NotNull KeyMapping getKeyMapping(@NotNull MappingMode mode) {
public KeyMapping getKeyMapping(@NotNull MappingMode mode) {
KeyMapping mapping = keyMappings.get(mode); KeyMapping mapping = keyMappings.get(mode);
if (mapping == null) { if (mapping == null) {
mapping = new KeyMapping(); mapping = new KeyMapping();
@@ -245,8 +279,7 @@ public class KeyGroup {
* @param mappingMode The mapping mode * @param mappingMode The mapping mode
* @return The key mapping tree root * @return The key mapping tree root
*/ */
@NotNull public @NotNull CommandPartNode getKeyRoot(@NotNull MappingMode mappingMode) {
public CommandPartNode getKeyRoot(@NotNull MappingMode mappingMode) {
return keyRoots.computeIfAbsent(mappingMode, (key) -> new RootNode()); 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 * 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 * shortcuts are registered, or the key handler won't see them
* </p> * </p>
*
* @param keyStroke The shortcut to register * @param keyStroke The shortcut to register
*/ */
public void registerShortcutWithoutAction(KeyStroke keyStroke) { public void registerShortcutWithoutAction(KeyStroke keyStroke, MappingOwner owner) {
registerRequiredShortcut(Collections.singletonList(keyStroke)); registerRequiredShortcut(Collections.singletonList(keyStroke), owner);
} }
public void unregisterCommandActions() { public void unregisterCommandActions() {
@@ -274,8 +308,9 @@ public class KeyGroup {
if (!VimPlugin.getPluginId().equals(actionHolder.getPluginId())) { if (!VimPlugin.getPluginId().equals(actionHolder.getPluginId())) {
logger.error("IdeaVim doesn't accept contributions to `vimActions` extension points. " + logger.error("IdeaVim doesn't accept contributions to `vimActions` extension points. " +
"Please create a plugin using `VimExtension`. " + "Please create a plugin using `VimExtension`. " +
"Plugin to blame: " + actionHolder.getPluginId()); "Plugin to blame: " +
actionHolder.getPluginId());
return; return;
} }
@@ -284,7 +319,8 @@ public class KeyGroup {
final EditorActionHandlerBase action = actionHolder.getAction(); final EditorActionHandlerBase action = actionHolder.getAction();
if (action instanceof ComplicatedKeysAction) { if (action instanceof ComplicatedKeysAction) {
actionKeys = ((ComplicatedKeysAction)action).getKeyStrokesSet(); actionKeys = ((ComplicatedKeysAction)action).getKeyStrokesSet();
} else { }
else {
throw new RuntimeException("Cannot register action: " + action.getClass().getName()); throw new RuntimeException("Cannot register action: " + action.getClass().getName());
} }
} }
@@ -305,7 +341,7 @@ public class KeyGroup {
} }
for (List<KeyStroke> keyStrokes : actionKeys) { for (List<KeyStroke> keyStrokes : actionKeys) {
registerRequiredShortcut(keyStrokes); registerRequiredShortcut(keyStrokes, MappingOwner.IdeaVim.INSTANCE);
for (MappingMode mappingMode : actionModes) { for (MappingMode mappingMode : actionModes) {
Node node = getKeyRoot(mappingMode); 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) { for (KeyStroke key : keys) {
if (key.getKeyChar() == KeyEvent.CHAR_UNDEFINED) { 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) { for (MappingMode mappingMode : mappingModes) {
checkIdentity(mappingMode, action.getId(), keys); checkIdentity(mappingMode, action.getId(), keys);
} }
@@ -340,8 +378,8 @@ public class KeyGroup {
private void checkIdentity(MappingMode mappingMode, String actName, List<KeyStroke> keys) { private void checkIdentity(MappingMode mappingMode, String actName, List<KeyStroke> keys) {
Set<List<KeyStroke>> keySets = identityChecker.computeIfAbsent(mappingMode, k -> new HashSet<>()); Set<List<KeyStroke>> keySets = identityChecker.computeIfAbsent(mappingMode, k -> new HashSet<>());
if (keySets.contains(keys)) { if (keySets.contains(keys)) {
throw new RuntimeException("This keymap already exists: " + mappingMode + " keys: " + throw new RuntimeException(
keys + " action:" + actName); "This keymap already exists: " + mappingMode + " keys: " + keys + " action:" + actName);
} }
keySets.add(keys); keySets.add(keys);
} }
@@ -356,7 +394,9 @@ public class KeyGroup {
if (!prefix.get(i).equals(keys.get(i))) break; 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())) { if (i == shortOne && !actionExceptions.contains(action.getId()) && !actionExceptions.contains(entry.getValue())) {
throw new RuntimeException("Prefix found! " + throw new RuntimeException("Prefix found! " +
keys + keys +
@@ -371,47 +411,45 @@ public class KeyGroup {
prefixes.put(keys, action.getId()); prefixes.put(keys, action.getId());
} }
@Nullable private Map<MappingMode, Set<List<KeyStroke>>> identityChecker; private @Nullable Map<MappingMode, Set<List<KeyStroke>>> identityChecker;
@Nullable private Map<List<KeyStroke>, String> prefixes; private @Nullable Map<List<KeyStroke>, String> prefixes;
@NotNull private @NotNull Node addMNode(@NotNull CommandPartNode base,
private Node addMNode(@NotNull CommandPartNode base, ActionBeanClass actionHolder,
ActionBeanClass actionHolder, @NotNull KeyStroke key,
@NotNull KeyStroke key, boolean isLastInSequence) {
boolean isLastInSequence) {
Node existing = base.get(key); Node existing = base.get(key);
if (existing != null) return existing; if (existing != null) return existing;
Node newNode; Node newNode;
if (isLastInSequence) { if (isLastInSequence) {
newNode = new CommandNode(actionHolder); newNode = new CommandNode(actionHolder);
} else { }
else {
newNode = new CommandPartNode(); newNode = new CommandPartNode();
} }
base.put(key, newNode); base.put(key, newNode);
return newNode; return newNode;
} }
@NotNull private static @NotNull ShortcutSet toShortcutSet(@NotNull Collection<RequiredShortcut> requiredShortcuts) {
private static ShortcutSet toShortcutSet(@NotNull Collection<KeyStroke> keyStrokes) { final List<Shortcut> shortcuts = new ArrayList<>();
final List<com.intellij.openapi.actionSystem.Shortcut> shortcuts = new ArrayList<>(); for (RequiredShortcut key : requiredShortcuts) {
for (KeyStroke key : keyStrokes) { shortcuts.add(new KeyboardShortcut(key.getKeyStroke(), null));
shortcuts.add(new KeyboardShortcut(key, null));
} }
return new CustomShortcutSet(shortcuts.toArray(new com.intellij.openapi.actionSystem.Shortcut[0])); return new CustomShortcutSet(shortcuts.toArray(new Shortcut[0]));
} }
@NotNull private static @NotNull List<Pair<EnumSet<MappingMode>, MappingInfo>> getKeyMappingRows(@NotNull Set<MappingMode> modes) {
private static List<MappingInfo> getKeyMappingRows(@NotNull Set<MappingMode> modes) { final Map<ImmutableList<KeyStroke>, EnumSet<MappingMode>> actualModes = new HashMap<>();
final Map<ImmutableList<KeyStroke>, Set<MappingMode>> actualModes = new HashMap<>();
for (MappingMode mode : modes) { for (MappingMode mode : modes) {
final KeyMapping mapping = VimPlugin.getKey().getKeyMapping(mode); final KeyMapping mapping = VimPlugin.getKey().getKeyMapping(mode);
for (List<KeyStroke> fromKeys : mapping) { for (List<KeyStroke> fromKeys : mapping) {
final ImmutableList<KeyStroke> key = ImmutableList.copyOf(fromKeys); final ImmutableList<KeyStroke> key = ImmutableList.copyOf(fromKeys);
final Set<MappingMode> value = actualModes.get(key); final EnumSet<MappingMode> value = actualModes.get(key);
final Set<MappingMode> newValue; final EnumSet<MappingMode> newValue;
if (value != null) { if (value != null) {
newValue = new HashSet<>(value); newValue = value.clone();
newValue.add(mode); newValue.add(mode);
} }
else { else {
@@ -420,26 +458,24 @@ public class KeyGroup {
actualModes.put(key, newValue); actualModes.put(key, newValue);
} }
} }
final List<MappingInfo> rows = new ArrayList<>(); final List<Pair<EnumSet<MappingMode>, MappingInfo>> rows = new ArrayList<>();
for (Map.Entry<ImmutableList<KeyStroke>, Set<MappingMode>> entry : actualModes.entrySet()) { for (Map.Entry<ImmutableList<KeyStroke>, EnumSet<MappingMode>> entry : actualModes.entrySet()) {
final ArrayList<KeyStroke> fromKeys = new ArrayList<>(entry.getKey()); final ArrayList<KeyStroke> fromKeys = new ArrayList<>(entry.getKey());
final Set<MappingMode> mappingModes = entry.getValue(); final EnumSet<MappingMode> mappingModes = entry.getValue();
if (!mappingModes.isEmpty()) { if (!mappingModes.isEmpty()) {
final MappingMode mode = mappingModes.iterator().next(); final MappingMode mode = mappingModes.iterator().next();
final KeyMapping mapping = VimPlugin.getKey().getKeyMapping(mode); final KeyMapping mapping = VimPlugin.getKey().getKeyMapping(mode);
final MappingInfo mappingInfo = mapping.get(fromKeys); final MappingInfo mappingInfo = mapping.get(fromKeys);
if (mappingInfo != null) { if (mappingInfo != null) {
rows.add(new MappingInfo(mappingModes, mappingInfo.getFromKeys(), mappingInfo.getToKeys(), rows.add(new Pair<>(mappingModes, mappingInfo));
mappingInfo.getExtensionHandler(), mappingInfo.isRecursive()));
} }
} }
} }
Collections.sort(rows); rows.sort(Comparator.comparing(Pair<EnumSet<MappingMode>, MappingInfo>::getSecond));
return rows; return rows;
} }
@NotNull private static @NotNull String getModesStringCode(@NotNull Set<MappingMode> modes) {
private static String getModesStringCode(@NotNull Set<MappingMode> modes) {
if (modes.equals(MappingMode.NVO)) { if (modes.equals(MappingMode.NVO)) {
return ""; return "";
} }
@@ -453,16 +489,14 @@ public class KeyGroup {
return ""; return "";
} }
@NotNull public @NotNull List<AnAction> getActions(@NotNull Component component, @NotNull KeyStroke keyStroke) {
public List<AnAction> getActions(@NotNull Component component, @NotNull KeyStroke keyStroke) {
final List<AnAction> results = new ArrayList<>(); final List<AnAction> results = new ArrayList<>();
results.addAll(getLocalActions(component, keyStroke)); results.addAll(getLocalActions(component, keyStroke));
results.addAll(getKeymapActions(keyStroke)); results.addAll(getKeymapActions(keyStroke));
return results; return results;
} }
@NotNull private static @NotNull List<AnAction> getLocalActions(@NotNull Component component, @NotNull KeyStroke keyStroke) {
private static List<AnAction> getLocalActions(@NotNull Component component, @NotNull KeyStroke keyStroke) {
final List<AnAction> results = new ArrayList<>(); final List<AnAction> results = new ArrayList<>();
final KeyboardShortcut keyStrokeShortcut = new KeyboardShortcut(keyStroke, null); final KeyboardShortcut keyStrokeShortcut = new KeyboardShortcut(keyStroke, null);
for (Component c = component; c != null; c = c.getParent()) { for (Component c = component; c != null; c = c.getParent()) {
@@ -484,8 +518,7 @@ public class KeyGroup {
return results; return results;
} }
@NotNull private static @NotNull List<AnAction> getKeymapActions(@NotNull KeyStroke keyStroke) {
private static List<AnAction> getKeymapActions(@NotNull KeyStroke keyStroke) {
final List<AnAction> results = new ArrayList<>(); final List<AnAction> results = new ArrayList<>();
final Keymap keymap = KeymapManager.getInstance().getActiveKeymap(); final Keymap keymap = KeymapManager.getInstance().getActiveKeymap();
for (String id : keymap.getActionIds(keyStroke)) { for (String id : keymap.getActionIds(keyStroke)) {
@@ -496,4 +529,17 @@ public class KeyGroup {
} }
return results; 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);
}
} }

View File

@@ -90,8 +90,8 @@ public class MacroGroup {
* @param cnt count * @param cnt count
* @param total total * @param total total
*/ */
public void playbackKeys(@NotNull final Editor editor, @NotNull final DataContext context, @Nullable final Project project, public void playbackKeys(final @NotNull Editor editor, final @NotNull DataContext context, final @Nullable Project project,
@NotNull final List<KeyStroke> keys, final int pos, final int cnt, final int total) { final @NotNull List<KeyStroke> keys, final int pos, final int cnt, final int total) {
if (logger.isDebugEnabled()) { if (logger.isDebugEnabled()) {
logger.debug("playbackKeys " + pos); logger.debug("playbackKeys " + pos);
} }
@@ -138,8 +138,7 @@ public class MacroGroup {
}); });
} }
@NotNull private @NotNull KeyEvent createKeyEvent(@NotNull KeyStroke stroke, Component component) {
private KeyEvent createKeyEvent(@NotNull KeyStroke stroke, Component component) {
return new KeyEvent(component, return new KeyEvent(component,
stroke.getKeyChar() == KeyEvent.CHAR_UNDEFINED ? KeyEvent.KEY_PRESSED : KeyEvent.KEY_TYPED, stroke.getKeyChar() == KeyEvent.CHAR_UNDEFINED ? KeyEvent.KEY_PRESSED : KeyEvent.KEY_TYPED,
System.currentTimeMillis(), stroke.getModifiers(), stroke.getKeyCode(), stroke.getKeyChar()); System.currentTimeMillis(), stroke.getModifiers(), stroke.getKeyCode(), stroke.getKeyChar());

View File

@@ -21,6 +21,10 @@ package com.maddyhome.idea.vim.group;
import com.intellij.ide.bookmarks.Bookmark; import com.intellij.ide.bookmarks.Bookmark;
import com.intellij.ide.bookmarks.BookmarkManager; import com.intellij.ide.bookmarks.BookmarkManager;
import com.intellij.ide.bookmarks.BookmarksListener; 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.diagnostic.Logger;
import com.intellij.openapi.editor.Document; import com.intellij.openapi.editor.Document;
import com.intellij.openapi.editor.Editor; import com.intellij.openapi.editor.Editor;
@@ -53,7 +57,10 @@ import java.util.stream.Collectors;
/** /**
* This class contains all the mark related functionality * 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_START = '<';
public static final char MARK_VISUAL_END = '>'; public static final char MARK_VISUAL_END = '>';
public static final char MARK_CHANGE_START = '['; public static final char MARK_CHANGE_START = '[';
@@ -83,8 +90,7 @@ public class MarkGroup {
* @param ch The desired mark * @param ch The desired mark
* @return The requested mark if set, null if not set * @return The requested mark if set, null if not set
*/ */
@Nullable public @Nullable Mark getMark(@NotNull Editor editor, char ch) {
public Mark getMark(@NotNull Editor editor, char ch) {
Mark mark = null; Mark mark = null;
if (ch == '`') ch = '\''; if (ch == '`') ch = '\'';
@@ -135,8 +141,7 @@ public class MarkGroup {
* @param count Postive for next jump (Ctrl-I), negative for previous jump (Ctrl-O). * @param count Postive for next jump (Ctrl-I), negative for previous jump (Ctrl-O).
* @return The jump or null if out of range. * @return The jump or null if out of range.
*/ */
@Nullable public @Nullable Jump getJump(int count) {
public Jump getJump(int count) {
int index = jumps.size() - 1 - (jumpSpot - count); int index = jumps.size() - 1 - (jumpSpot - count);
if (index < 0 || index >= jumps.size()) { if (index < 0 || index >= jumps.size()) {
return null; return null;
@@ -154,8 +159,7 @@ public class MarkGroup {
* @param ch The mark to get * @param ch The mark to get
* @return The mark in the current file, if set, null if no such mark * @return The mark in the current file, if set, null if no such mark
*/ */
@Nullable public @Nullable Mark getFileMark(@NotNull Editor editor, char ch) {
public Mark getFileMark(@NotNull Editor editor, char ch) {
if (ch == '`') ch = '\''; if (ch == '`') ch = '\'';
final HashMap fmarks = getFileMarks(editor.getDocument()); final HashMap fmarks = getFileMarks(editor.getDocument());
if (fmarks == null) { if (fmarks == null) {
@@ -228,8 +232,7 @@ public class MarkGroup {
return true; return true;
} }
@Nullable private @Nullable Bookmark createOrGetSystemMark(char ch, int line, @NotNull Editor editor) {
private Bookmark createOrGetSystemMark(char ch, int line, @NotNull Editor editor) {
if (!OptionsManager.INSTANCE.getIdeamarks().isSet()) return null; if (!OptionsManager.INSTANCE.getIdeamarks().isSet()) return null;
final Project project = editor.getProject(); final Project project = editor.getProject();
if (project == null) return null; if (project == null) return null;
@@ -259,18 +262,15 @@ public class MarkGroup {
setMark(editor, MARK_CHANGE_END, range.getEndOffset()); setMark(editor, MARK_CHANGE_END, range.getEndOffset());
} }
@Nullable public @Nullable TextRange getChangeMarks(@NotNull Editor editor) {
public TextRange getChangeMarks(@NotNull Editor editor) {
return getMarksRange(editor, MARK_CHANGE_START, MARK_CHANGE_END); return getMarksRange(editor, MARK_CHANGE_START, MARK_CHANGE_END);
} }
@Nullable public @Nullable TextRange getVisualSelectionMarks(@NotNull Editor editor) {
public TextRange getVisualSelectionMarks(@NotNull Editor editor) {
return getMarksRange(editor, MARK_VISUAL_START, MARK_VISUAL_END); return getMarksRange(editor, MARK_VISUAL_START, MARK_VISUAL_END);
} }
@Nullable private @Nullable TextRange getMarksRange(@NotNull Editor editor, char startMark, char endMark) {
private TextRange getMarksRange(@NotNull Editor editor, char startMark, char endMark) {
final Mark start = getMark(editor, startMark); final Mark start = getMark(editor, startMark);
final Mark end = getMark(editor, endMark); final Mark end = getMark(editor, endMark);
if (start != null && end != null) { if (start != null && end != null) {
@@ -338,8 +338,7 @@ public class MarkGroup {
mark.clear(); mark.clear();
} }
@NotNull public @NotNull List<Mark> getMarks(@NotNull Editor editor) {
public List<Mark> getMarks(@NotNull Editor editor) {
HashSet<Mark> res = new HashSet<>(); HashSet<Mark> res = new HashSet<>();
final FileMarks<Character, Mark> marks = getFileMarks(editor.getDocument()); final FileMarks<Character, Mark> marks = getFileMarks(editor.getDocument());
@@ -355,8 +354,7 @@ public class MarkGroup {
return list; return list;
} }
@NotNull public @NotNull List<Jump> getJumps() {
public List<Jump> getJumps() {
return jumps; 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 * @return The map of marks. The keys are <code>Character</code>s of the mark names, the values are
* <code>Mark</code>s. * <code>Mark</code>s.
*/ */
@Nullable private @Nullable FileMarks<Character, Mark> getFileMarks(final @NotNull Document doc) {
private FileMarks<Character, Mark> getFileMarks(@NotNull final Document doc) {
VirtualFile vf = FileDocumentManager.getInstance().getFile(doc); VirtualFile vf = FileDocumentManager.getInstance().getFile(doc);
if (vf == null) { if (vf == null) {
return null; return null;
@@ -381,8 +378,7 @@ public class MarkGroup {
return getFileMarks(vf.getPath()); return getFileMarks(vf.getPath());
} }
@Nullable private @Nullable HashMap<Character, Mark> getAllFileMarks(final @NotNull Document doc) {
private HashMap<Character, Mark> getAllFileMarks(@NotNull final Document doc) {
VirtualFile vf = FileDocumentManager.getInstance().getFile(doc); VirtualFile vf = FileDocumentManager.getInstance().getFile(doc);
if (vf == null) { if (vf == null) {
return 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> { private static class FileMarks<K, V> extends HashMap<K, V> {
public void setTimestamp(Date timestamp) { public void setTimestamp(Date timestamp) {
this.timestamp = timestamp; this.timestamp = timestamp;
@@ -723,8 +732,7 @@ public class MarkGroup {
// TODO - update jumps // TODO - update jumps
} }
@Nullable private @Nullable Editor getAnEditor(@NotNull Document doc) {
private Editor getAnEditor(@NotNull Document doc) {
Editor[] editors = EditorFactory.getInstance().getEditors(doc); Editor[] editors = EditorFactory.getInstance().getEditors(doc);
if (editors.length > 0) { if (editors.length > 0) {
@@ -785,9 +793,9 @@ public class MarkGroup {
} }
} }
@NotNull private final HashMap<String, FileMarks<Character, Mark>> fileMarks = new HashMap<>(); private final @NotNull HashMap<String, FileMarks<Character, Mark>> fileMarks = new HashMap<>();
@NotNull private final HashMap<Character, Mark> globalMarks = new HashMap<>(); private final @NotNull HashMap<Character, Mark> globalMarks = new HashMap<>();
@NotNull private final List<Jump> jumps = new ArrayList<>(); private final @NotNull List<Jump> jumps = new ArrayList<>();
private int jumpSpot = -1; private int jumpSpot = -1;
private static final int SAVE_MARK_COUNT = 20; private static final int SAVE_MARK_COUNT = 20;

View File

@@ -102,8 +102,7 @@ public class MotionGroup {
* @param argument Any argument needed by the motion * @param argument Any argument needed by the motion
* @return The motion's range * @return The motion's range
*/ */
@Nullable public static @Nullable TextRange getMotionRange(@NotNull Editor editor,
public static TextRange getMotionRange(@NotNull Editor editor,
@NotNull Caret caret, @NotNull Caret caret,
DataContext context, DataContext context,
int count, int count,
@@ -239,32 +238,27 @@ public class MotionGroup {
} }
} }
@Nullable public @Nullable TextRange getBlockQuoteRange(@NotNull Editor editor, @NotNull Caret caret, char quote, boolean isOuter) {
public TextRange getBlockQuoteRange(@NotNull Editor editor, @NotNull Caret caret, char quote, boolean isOuter) {
return SearchHelper.findBlockQuoteInLineRange(editor, caret, quote, isOuter); return SearchHelper.findBlockQuoteInLineRange(editor, caret, quote, isOuter);
} }
@Nullable public @Nullable TextRange getBlockRange(@NotNull Editor editor, @NotNull Caret caret, int count, boolean isOuter, char type) {
public TextRange getBlockRange(@NotNull Editor editor, @NotNull Caret caret, int count, boolean isOuter, char type) {
return SearchHelper.findBlockRange(editor, caret, type, count, isOuter); return SearchHelper.findBlockRange(editor, caret, type, count, isOuter);
} }
@Nullable public @Nullable TextRange getBlockTagRange(@NotNull Editor editor, @NotNull Caret caret, int count, boolean isOuter) {
public TextRange getBlockTagRange(@NotNull Editor editor, @NotNull Caret caret, int count, boolean isOuter) {
return SearchHelper.findBlockTagRange(editor, caret, count, isOuter); return SearchHelper.findBlockTagRange(editor, caret, count, isOuter);
} }
@NotNull public @NotNull TextRange getSentenceRange(@NotNull Editor editor, @NotNull Caret caret, int count, boolean isOuter) {
public TextRange getSentenceRange(@NotNull Editor editor, @NotNull Caret caret, int count, boolean isOuter) {
return SearchHelper.findSentenceRange(editor, caret, count, isOuter); return SearchHelper.findSentenceRange(editor, caret, count, isOuter);
} }
@Nullable public @Nullable TextRange getParagraphRange(@NotNull Editor editor, @NotNull Caret caret, int count, boolean isOuter) {
public TextRange getParagraphRange(@NotNull Editor editor, @NotNull Caret caret, int count, boolean isOuter) {
return SearchHelper.findParagraphRange(editor, caret, count, 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 Rectangle visibleArea = editor.getScrollingModel().getVisibleArea();
final int caretVisualLine = editor.getCaretModel().getVisualPosition().line; final int caretVisualLine = editor.getCaretModel().getVisualPosition().line;
final int scrollOption = getScrollOption(rawCount); final int scrollOption = getScrollOption(rawCount);
@@ -296,7 +290,7 @@ public class MotionGroup {
return rawCount; return rawCount;
} }
private static int getNormalizedScrollOffset(@NotNull final Editor editor) { private static int getNormalizedScrollOffset(final @NotNull Editor editor) {
int scrollOffset = OptionsManager.INSTANCE.getScrolloff().value(); int scrollOffset = OptionsManager.INSTANCE.getScrolloff().value();
return EditorHelper.normalizeScrollOffset(editor, scrollOffset); return EditorHelper.normalizeScrollOffset(editor, scrollOffset);
} }
@@ -328,8 +322,7 @@ public class MotionGroup {
} }
} }
@Nullable private @Nullable Editor selectEditor(@NotNull Editor editor, @NotNull Mark mark) {
private Editor selectEditor(@NotNull Editor editor, @NotNull Mark mark) {
final VirtualFile virtualFile = markToVirtualFile(mark); final VirtualFile virtualFile = markToVirtualFile(mark);
if (virtualFile != null) { if (virtualFile != null) {
return selectEditor(editor, virtualFile); return selectEditor(editor, virtualFile);
@@ -339,15 +332,13 @@ public class MotionGroup {
} }
} }
@Nullable private @Nullable VirtualFile markToVirtualFile(@NotNull Mark mark) {
private VirtualFile markToVirtualFile(@NotNull Mark mark) {
String protocol = mark.getProtocol(); String protocol = mark.getProtocol();
VirtualFileSystem fileSystem = VirtualFileManager.getInstance().getFileSystem(protocol); VirtualFileSystem fileSystem = VirtualFileManager.getInstance().getFileSystem(protocol);
return fileSystem.findFileByPath(mark.getFilename()); return fileSystem.findFileByPath(mark.getFilename());
} }
@Nullable private @Nullable Editor selectEditor(@NotNull Editor editor, @NotNull VirtualFile file) {
private Editor selectEditor(@NotNull Editor editor, @NotNull VirtualFile file) {
return VimPlugin.getFile().selectEditor(editor.getProject(), file); return VimPlugin.getFile().selectEditor(editor.getProject(), file);
} }
@@ -778,12 +769,11 @@ public class MotionGroup {
return true; return true;
} }
@NotNull public @NotNull TextRange getWordRange(@NotNull Editor editor,
public TextRange getWordRange(@NotNull Editor editor, @NotNull Caret caret,
@NotNull Caret caret, int count,
int count, boolean isOuter,
boolean isOuter, boolean isBig) {
boolean isBig) {
int dir = 1; int dir = 1;
boolean selection = false; boolean selection = false;
if (CommandState.getInstance(editor).getMode() == CommandState.Mode.VISUAL) { if (CommandState.getInstance(editor).getMode() == CommandState.Mode.VISUAL) {
@@ -1035,13 +1025,13 @@ public class MotionGroup {
public int moveCaretHorizontal(@NotNull Editor editor, @NotNull Caret caret, int count, boolean allowPastEnd) { public int moveCaretHorizontal(@NotNull Editor editor, @NotNull Caret caret, int count, boolean allowPastEnd) {
int oldOffset = caret.getOffset(); int oldOffset = caret.getOffset();
int diff = 0; int diff = 0;
String text = editor.getDocument().getText(); CharSequence text = editor.getDocument().getCharsSequence();
int sign = (int)Math.signum(count); int sign = (int)Math.signum(count);
for (Integer pointer : new IntProgression(0, count - sign, sign)) { for (Integer pointer : new IntProgression(0, count - sign, sign)) {
int textPointer = oldOffset + pointer; int textPointer = oldOffset + pointer;
if (textPointer < text.length() && textPointer >= 0) { if (textPointer < text.length() && textPointer >= 0) {
// Actual char size can differ from 1 if unicode characters are used (like 🐔) // 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 { else {
diff += 1; diff += 1;
@@ -1108,7 +1098,7 @@ public class MotionGroup {
return editor.logicalPositionToOffset(newPos); 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 CaretModel caretModel = editor.getCaretModel();
final int currentLogicalLine = caretModel.getLogicalPosition().line; final int currentLogicalLine = caretModel.getLogicalPosition().line;

View File

@@ -33,6 +33,7 @@ import com.intellij.openapi.actionSystem.KeyboardShortcut
import com.intellij.openapi.keymap.Keymap import com.intellij.openapi.keymap.Keymap
import com.intellij.openapi.keymap.KeymapUtil import com.intellij.openapi.keymap.KeymapUtil
import com.intellij.openapi.options.ShowSettingsUtil import com.intellij.openapi.options.ShowSettingsUtil
import com.intellij.openapi.project.DumbAwareAction
import com.intellij.openapi.project.Project import com.intellij.openapi.project.Project
import com.intellij.openapi.ui.Messages import com.intellij.openapi.ui.Messages
import com.intellij.openapi.util.SystemInfo import com.intellij.openapi.util.SystemInfo
@@ -48,6 +49,9 @@ import javax.swing.event.HyperlinkEvent
/** /**
* @author Alex Plate * @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?) { class NotificationService(private val project: Project?) {
// This constructor is used to create an applicationService // This constructor is used to create an applicationService
@@ -143,7 +147,7 @@ class NotificationService(private val project: Project?) {
NotificationType.INFORMATION).notify(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) { override fun actionPerformed(e: AnActionEvent) {
val eventProject = e.project val eventProject = e.project
if (eventProject != null) { if (eventProject != null) {

View File

@@ -62,8 +62,7 @@ public class ProcessGroup {
return ExEntryPanel.getInstance().getLabel().equals("/"); return ExEntryPanel.getInstance().getLabel().equals("/");
} }
@NotNull public @NotNull String endSearchCommand(final @NotNull Editor editor) {
public String endSearchCommand(@NotNull final Editor editor) {
ExEntryPanel panel = ExEntryPanel.getInstance(); ExEntryPanel panel = ExEntryPanel.getInstance();
panel.deactivate(true); 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(); ExEntryPanel panel = ExEntryPanel.getInstance();
panel.deactivate(true); panel.deactivate(true);
boolean res = true; boolean res = true;
@@ -143,7 +142,7 @@ public class ProcessGroup {
return res; return res;
} }
public void cancelExEntry(@NotNull final Editor editor, boolean resetCaret) { public void cancelExEntry(final @NotNull Editor editor, boolean resetCaret) {
CommandState.getInstance(editor).popModes(); CommandState.getInstance(editor).popModes();
KeyHandler.getInstance().reset(editor); KeyHandler.getInstance().reset(editor);
ExEntryPanel panel = ExEntryPanel.getInstance(); ExEntryPanel panel = ExEntryPanel.getInstance();
@@ -163,8 +162,7 @@ public class ProcessGroup {
panel.activate(editor, context, ":", initText, 1); panel.activate(editor, context, ":", initText, 1);
} }
@NotNull private @NotNull String getRange(Editor editor, @NotNull Command cmd) {
private String getRange(Editor editor, @NotNull Command cmd) {
String initText = ""; String initText = "";
if (CommandState.getInstance(editor).getMode() == CommandState.Mode.VISUAL) { if (CommandState.getInstance(editor).getMode() == CommandState.Mode.VISUAL) {
initText = "'<,'>"; initText = "'<,'>";
@@ -191,8 +189,7 @@ public class ProcessGroup {
return true; return true;
} }
@NotNull public @NotNull String executeCommand(@NotNull String command, @Nullable CharSequence input) throws IOException {
public String executeCommand(@NotNull String command, @Nullable CharSequence input) throws IOException {
if (logger.isDebugEnabled()) { if (logger.isDebugEnabled()) {
logger.debug("command=" + command); logger.debug("command=" + command);
} }

View File

@@ -23,6 +23,10 @@ import com.intellij.codeInsight.editorActions.CopyPastePostProcessor;
import com.intellij.codeInsight.editorActions.CopyPastePreProcessor; import com.intellij.codeInsight.editorActions.CopyPastePreProcessor;
import com.intellij.codeInsight.editorActions.TextBlockTransferable; import com.intellij.codeInsight.editorActions.TextBlockTransferable;
import com.intellij.codeInsight.editorActions.TextBlockTransferableData; 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.diagnostic.Logger;
import com.intellij.openapi.editor.CaretStateTransferableData; import com.intellij.openapi.editor.CaretStateTransferableData;
import com.intellij.openapi.editor.Editor; 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 * 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 WRITABLE_REGISTERS = "0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ-*+_/\"";
private static final String READONLY_REGISTERS = ":.%#=/"; private static final String READONLY_REGISTERS = ":.%#=/";
private static final String RECORDABLE_REGISTER = "0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ"; private static final String RECORDABLE_REGISTER = "0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ";
@@ -83,9 +90,9 @@ public class RegisterGroup {
private char defaultRegister = '"'; private char defaultRegister = '"';
private char lastRegister = 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; private char recordRegister = 0;
@Nullable private List<KeyStroke> recordList = null; private @Nullable List<KeyStroke> recordList = null;
public RegisterGroup() { public RegisterGroup() {
final ListOption clipboardOption = OptionsManager.INSTANCE.getClipboard(); final ListOption clipboardOption = OptionsManager.INSTANCE.getClipboard();
@@ -246,10 +253,9 @@ public class RegisterGroup {
return true; return true;
} }
@NotNull public @NotNull List<TextBlockTransferableData> getTransferableData(@NotNull Editor editor,
public List<TextBlockTransferableData> getTransferableData(@NotNull Editor editor, @NotNull TextRange textRange,
@NotNull TextRange textRange, String text) {
String text) {
final List<TextBlockTransferableData> transferableDatas = new ArrayList<>(); final List<TextBlockTransferableData> transferableDatas = new ArrayList<>();
final Project project = editor.getProject(); final Project project = editor.getProject();
if (project == null) return new ArrayList<>(); if (project == null) return new ArrayList<>();
@@ -316,13 +322,11 @@ public class RegisterGroup {
* *
* @return The register, null if no such register * @return The register, null if no such register
*/ */
@Nullable public @Nullable Register getLastRegister() {
public Register getLastRegister() {
return getRegister(lastRegister); return getRegister(lastRegister);
} }
@Nullable public @Nullable Register getPlaybackRegister(char r) {
public Register getPlaybackRegister(char r) {
if (PLAYBACK_REGISTER.indexOf(r) != 0) { if (PLAYBACK_REGISTER.indexOf(r) != 0) {
return getRegister(r); return getRegister(r);
} }
@@ -331,8 +335,7 @@ public class RegisterGroup {
} }
} }
@Nullable public @Nullable Register getRegister(char r) {
public Register getRegister(char r) {
// Uppercase registers actually get the lowercase register // Uppercase registers actually get the lowercase register
if (Character.isUpperCase(r)) { if (Character.isUpperCase(r)) {
r = Character.toLowerCase(r); r = Character.toLowerCase(r);
@@ -356,8 +359,7 @@ public class RegisterGroup {
return defaultRegister; return defaultRegister;
} }
@NotNull public @NotNull List<Register> getRegisters() {
public List<Register> getRegisters() {
final List<Register> res = new ArrayList<>(registers.values()); final List<Register> res = new ArrayList<>(registers.values());
for (Character r : CLIPBOARD_REGISTERS) { for (Character r : CLIPBOARD_REGISTERS) {
final Register register = refreshClipboardRegister(r); final Register register = refreshClipboardRegister(r);
@@ -419,7 +421,7 @@ public class RegisterGroup {
recordRegister = 0; recordRegister = 0;
} }
public void saveData(@NotNull final Element element) { public void saveData(final @NotNull Element element) {
logger.debug("saveData"); logger.debug("saveData");
final Element registersElement = new Element("registers"); final Element registersElement = new Element("registers");
for (Character key : registers.keySet()) { for (Character key : registers.keySet()) {
@@ -451,7 +453,7 @@ public class RegisterGroup {
element.addContent(registersElement); element.addContent(registersElement);
} }
public void readData(@NotNull final Element element) { public void readData(final @NotNull Element element) {
logger.debug("readData"); logger.debug("readData");
final Element registersElement = element.getChild("registers"); final Element registersElement = element.getChild("registers");
if (registersElement != null) { if (registersElement != null) {
@@ -491,8 +493,7 @@ public class RegisterGroup {
} }
} }
@Nullable private @Nullable Register refreshClipboardRegister(char r) {
private Register refreshClipboardRegister(char r) {
final Pair<String, List<TextBlockTransferableData>> clipboardData = ClipboardHandler.getClipboardTextAndTransferableData(); final Pair<String, List<TextBlockTransferableData>> clipboardData = ClipboardHandler.getClipboardTextAndTransferableData();
final Register currentRegister = registers.get(r); final Register currentRegister = registers.get(r);
final String text = clipboardData.getFirst(); final String text = clipboardData.getFirst();
@@ -506,8 +507,7 @@ public class RegisterGroup {
return null; return null;
} }
@NotNull private @NotNull SelectionType guessSelectionType(@NotNull String text) {
private SelectionType guessSelectionType(@NotNull String text) {
if (text.endsWith("\n")) { if (text.endsWith("\n")) {
return SelectionType.LINE_WISE; return SelectionType.LINE_WISE;
} }
@@ -515,4 +515,17 @@ public class RegisterGroup {
return SelectionType.CHARACTER_WISE; 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);
}
} }

View File

@@ -19,6 +19,10 @@ package com.maddyhome.idea.vim.group;
import com.google.common.collect.Lists; import com.google.common.collect.Lists;
import com.intellij.openapi.application.ApplicationManager; 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.diagnostic.Logger;
import com.intellij.openapi.editor.*; import com.intellij.openapi.editor.*;
import com.intellij.openapi.editor.colors.EditorColors; import com.intellij.openapi.editor.colors.EditorColors;
@@ -60,7 +64,10 @@ import java.text.ParsePosition;
import java.util.List; import java.util.List;
import java.util.*; 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() { public SearchGroup() {
final OptionsManager options = OptionsManager.INSTANCE; final OptionsManager options = OptionsManager.INSTANCE;
options.getHlsearch().addOptionChangeListener((oldValue, newValue) -> { options.getHlsearch().addOptionChangeListener((oldValue, newValue) -> {
@@ -90,8 +97,7 @@ public class SearchGroup {
showSearchHighlight = show; showSearchHighlight = show;
} }
@Nullable public @Nullable String getLastSearch() {
public String getLastSearch() {
return lastSearch; return lastSearch;
} }
@@ -101,8 +107,7 @@ public class SearchGroup {
return lastDir; return lastDir;
} }
@Nullable public @Nullable String getLastPattern() {
public String getLastPattern() {
return lastPattern; return lastPattern;
} }
@@ -123,8 +128,7 @@ public class SearchGroup {
// This method should not be private because it's used in external plugins // This method should not be private because it's used in external plugins
@SuppressWarnings("WeakerAccess") @SuppressWarnings("WeakerAccess")
@NotNull public static @NotNull List<TextRange> findAll(@NotNull Editor editor,
public static List<TextRange> findAll(@NotNull Editor editor,
@NotNull String pattern, @NotNull String pattern,
int startLine, int startLine,
int endLine, int endLine,
@@ -172,8 +176,7 @@ public class SearchGroup {
return results; return results;
} }
@NotNull private static @NotNull ReplaceConfirmationChoice confirmChoice(@NotNull Editor editor, @NotNull String match, @NotNull Caret caret, int startoff) {
private static ReplaceConfirmationChoice confirmChoice(@NotNull Editor editor, @NotNull String match, @NotNull Caret caret, int startoff) {
final Ref<ReplaceConfirmationChoice> result = Ref.create(ReplaceConfirmationChoice.QUIT); final Ref<ReplaceConfirmationChoice> result = Ref.create(ReplaceConfirmationChoice.QUIT);
final Function1<KeyStroke, Boolean> keyStrokeProcessor = key -> { final Function1<KeyStroke, Boolean> keyStrokeProcessor = key -> {
final ReplaceConfirmationChoice choice; final ReplaceConfirmationChoice choice;
@@ -520,8 +523,7 @@ public class SearchGroup {
return max.getStartOffset(); return max.getStartOffset();
} }
@Nullable public @Nullable TextRange getNextSearchRange(@NotNull Editor editor, int count, boolean forwards) {
public TextRange getNextSearchRange(@NotNull Editor editor, int count, boolean forwards) {
editor.getCaretModel().removeSecondaryCarets(); editor.getCaretModel().removeSecondaryCarets();
TextRange current = findUnderCaret(editor); TextRange current = findUnderCaret(editor);
@@ -544,8 +546,7 @@ public class SearchGroup {
} }
} }
@Nullable private @Nullable TextRange findNextSearchForGn(@NotNull Editor editor, int count, boolean forwards) {
private TextRange findNextSearchForGn(@NotNull Editor editor, int count, boolean forwards) {
if (forwards) { if (forwards) {
final EnumSet<SearchOptions> searchOptions = EnumSet.of(SearchOptions.WRAP, SearchOptions.WHOLE_FILE); final EnumSet<SearchOptions> searchOptions = EnumSet.of(SearchOptions.WRAP, SearchOptions.WHOLE_FILE);
return findIt(editor, lastSearch, editor.getCaretModel().getOffset(), count, searchOptions); return findIt(editor, lastSearch, editor.getCaretModel().getOffset(), count, searchOptions);
@@ -554,15 +555,13 @@ public class SearchGroup {
} }
} }
@Nullable private @Nullable TextRange findUnderCaret(@NotNull Editor editor) {
private TextRange findUnderCaret(@NotNull Editor editor) {
final TextRange backSearch = searchBackward(editor, editor.getCaretModel().getOffset() + 1, 1); final TextRange backSearch = searchBackward(editor, editor.getCaretModel().getOffset() + 1, 1);
if (backSearch == null) return null; if (backSearch == null) return null;
return backSearch.contains(editor.getCaretModel().getOffset()) ? backSearch : null; return backSearch.contains(editor.getCaretModel().getOffset()) ? backSearch : null;
} }
@Nullable private @Nullable TextRange searchBackward(@NotNull Editor editor, int offset, int count) {
private 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 // 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 EnumSet<SearchOptions> searchOptions = EnumSet.of(SearchOptions.WRAP, SearchOptions.WHOLE_FILE, SearchOptions.BACKWARDS);
final TextRange foundBackward = findIt(editor, lastSearch, offset, count, searchOptions); final TextRange foundBackward = findIt(editor, lastSearch, offset, count, searchOptions);
@@ -1320,8 +1319,7 @@ public class SearchGroup {
return true; return true;
} }
@NotNull private @NotNull RangeHighlighter highlightConfirm(@NotNull Editor editor, int start, int end) {
private RangeHighlighter highlightConfirm(@NotNull Editor editor, int start, int end) {
TextAttributes color = new TextAttributes( TextAttributes color = new TextAttributes(
editor.getColorsScheme().getColor(EditorColors.SELECTION_FOREGROUND_COLOR), editor.getColorsScheme().getColor(EditorColors.SELECTION_FOREGROUND_COLOR),
editor.getColorsScheme().getColor(EditorColors.SELECTION_BACKGROUND_COLOR), editor.getColorsScheme().getColor(EditorColors.SELECTION_BACKGROUND_COLOR),
@@ -1332,8 +1330,7 @@ public class SearchGroup {
color, HighlighterTargetArea.EXACT_RANGE); color, HighlighterTargetArea.EXACT_RANGE);
} }
@NotNull private static @NotNull RangeHighlighter highlightMatch(@NotNull Editor editor, int start, int end, boolean current, String tooltip) {
private static RangeHighlighter highlightMatch(@NotNull Editor editor, int start, int end, boolean current, String tooltip) {
TextAttributes attributes = editor.getColorsScheme().getAttributes(EditorColors.TEXT_SEARCH_RESULT_ATTRIBUTES); TextAttributes attributes = editor.getColorsScheme().getAttributes(EditorColors.TEXT_SEARCH_RESULT_ATTRIBUTES);
if (current) { if (current) {
// This mimics what IntelliJ does with the Find live preview // This mimics what IntelliJ does with the Find live preview
@@ -1411,8 +1408,7 @@ public class SearchGroup {
element.addContent(search); element.addContent(search);
} }
@NotNull private static @NotNull Element createElementWithText(@NotNull String name, @NotNull String text) {
private static Element createElementWithText(@NotNull String name, @NotNull String text) {
return StringHelper.setSafeXmlText(new Element(name), text); return StringHelper.setSafeXmlText(new Element(name), text);
} }
@@ -1442,8 +1438,7 @@ public class SearchGroup {
} }
} }
@Nullable private static @Nullable String getSafeChildText(@NotNull Element element, @NotNull String name) {
private static String getSafeChildText(@NotNull Element element, @NotNull String name) {
final Element child = element.getChild(name); final Element child = element.getChild(name);
return child != null ? StringHelper.getSafeXmlText(child) : null; return child != null ? StringHelper.getSafeXmlText(child) : null;
} }
@@ -1456,6 +1451,19 @@ public class SearchGroup {
VimPlugin.getSearch().updateSearchHighlights(); 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 class DocumentSearchListener implements DocumentListener {
public static DocumentSearchListener INSTANCE = new DocumentSearchListener(); public static DocumentSearchListener INSTANCE = new DocumentSearchListener();
@@ -1524,11 +1532,11 @@ public class SearchGroup {
IGNORE_SMARTCASE, IGNORE_SMARTCASE,
} }
@Nullable private String lastSearch; private @Nullable String lastSearch;
@Nullable private String lastPattern; private @Nullable String lastPattern;
@Nullable private String lastSubstitute; private @Nullable String lastSubstitute;
@Nullable private String lastReplace; private @Nullable String lastReplace;
@Nullable private String lastOffset; private @Nullable String lastOffset;
private boolean lastIgnoreSmartCase; private boolean lastIgnoreSmartCase;
private int lastDir; private int lastDir;
private boolean showSearchHighlight = OptionsManager.INSTANCE.getHlsearch().isSet(); private boolean showSearchHighlight = OptionsManager.INSTANCE.getHlsearch().isSet();

View File

@@ -114,9 +114,8 @@ public class WindowGroup {
windows.get(normalized).setAsCurrentWindow(true); windows.get(normalized).setAsCurrentWindow(true);
} }
@NotNull private static @NotNull List<EditorWindow> findWindowsInRow(@NotNull EditorWindow anchor,
private static List<EditorWindow> findWindowsInRow(@NotNull EditorWindow anchor, @NotNull List<EditorWindow> windows, final boolean vertical) {
@NotNull List<EditorWindow> windows, final boolean vertical) {
final Rectangle anchorRect = getEditorWindowRectangle(anchor); final Rectangle anchorRect = getEditorWindowRectangle(anchor);
if (anchorRect != null) { if (anchorRect != null) {
final List<EditorWindow> result = new ArrayList<>(); final List<EditorWindow> result = new ArrayList<>();
@@ -145,8 +144,7 @@ public class WindowGroup {
return Collections.singletonList(anchor); return Collections.singletonList(anchor);
} }
@NotNull private static @NotNull FileEditorManagerEx getFileEditorManager(@NotNull DataContext context) {
private static FileEditorManagerEx getFileEditorManager(@NotNull DataContext context) {
final Project project = PlatformDataKeys.PROJECT.getData(context); final Project project = PlatformDataKeys.PROJECT.getData(context);
return FileEditorManagerEx.getInstanceEx(Objects.requireNonNull(project)); return FileEditorManagerEx.getInstanceEx(Objects.requireNonNull(project));
} }
@@ -171,8 +169,7 @@ public class WindowGroup {
} }
} }
@Nullable private static @Nullable Rectangle getEditorWindowRectangle(@NotNull EditorWindow window) {
private static Rectangle getEditorWindowRectangle(@NotNull EditorWindow window) {
final EditorWithProviderComposite editor = window.getSelectedEditor(); final EditorWithProviderComposite editor = window.getSelectedEditor();
if (editor != null) { if (editor != null) {
final Point point = editor.getComponent().getLocationOnScreen(); final Point point = editor.getComponent().getLocationOnScreen();

View File

@@ -40,6 +40,12 @@ import javax.swing.KeyStroke
* If you want to use exactly `<` character, replace it with `&lt;`. E.g. `i&lt;` - i< * If you want to use exactly `<` character, replace it with `&lt;`. E.g. `i&lt;` - i<
* If you want to use comma in mapping, use `«COMMA»` * If you want to use comma in mapping, use `«COMMA»`
* Do not place a whitespace around the 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() { class ActionBeanClass : AbstractExtensionPointBean() {
@Attribute("implementation") @Attribute("implementation")

View File

@@ -39,8 +39,7 @@ public class DigraphResult {
return new DigraphResult(RES_HANDLED, promptCharacter); return new DigraphResult(RES_HANDLED, promptCharacter);
} }
@Nullable public @Nullable KeyStroke getStroke() {
public KeyStroke getStroke() {
return stroke; return stroke;
} }
@@ -53,6 +52,6 @@ public class DigraphResult {
} }
private final int result; private final int result;
@Nullable private final KeyStroke stroke; private final @Nullable KeyStroke stroke;
private char promptCharacter; private char promptCharacter;
} }

View File

@@ -55,8 +55,7 @@ public class DigraphSequence {
return DigraphResult.HANDLED_LITERAL; return DigraphResult.HANDLED_LITERAL;
} }
@NotNull public @NotNull DigraphResult processKey(@NotNull KeyStroke key, @NotNull Editor editor) {
public DigraphResult processKey(@NotNull KeyStroke key, @NotNull Editor editor) {
switch (digraphState) { switch (digraphState) {
case DIG_STATE_PENDING: case DIG_STATE_PENDING:
logger.debug("DIG_STATE_PENDING"); logger.debug("DIG_STATE_PENDING");

View File

@@ -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. * This is a set of helper methods for working with editors. All line and column values are zero based.
*/ */
public class EditorHelper { 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(); final Rectangle visibleArea = editor.getScrollingModel().getVisibleArea();
return getFullVisualLine(editor, visibleArea.y, visibleArea.y, visibleArea.y + visibleArea.height); 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 ScrollingModel scrollingModel = editor.getScrollingModel();
final Rectangle visibleArea = scrollingModel.getVisibleArea(); final Rectangle visibleArea = scrollingModel.getVisibleArea();
return editor.yToVisualLine(visibleArea.y + (visibleArea.height / 2)); 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(); final Rectangle visibleArea = editor.getScrollingModel().getVisibleArea();
return getFullVisualLine(editor, visibleArea.y + visibleArea.height, visibleArea.y, visibleArea.y + visibleArea.height); return getFullVisualLine(editor, visibleArea.y + visibleArea.height, visibleArea.y, visibleArea.y + visibleArea.height);
} }
@@ -65,7 +65,7 @@ public class EditorHelper {
* @param editor The editor * @param editor The editor
* @return The number of characters in the current line * @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); return getLineLength(editor, editor.getCaretModel().getLogicalPosition().line);
} }
@@ -77,7 +77,7 @@ public class EditorHelper {
* @param line The logical line within the file * @param line The logical line within the file
* @return The number of characters in the specified line * @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) { if (getLineCount(editor) == 0) {
return 0; return 0;
} }
@@ -94,7 +94,7 @@ public class EditorHelper {
* @param line The visual line within the file * @param line The visual line within the file
* @return The number of characters in the specified line * @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)); return getLineLength(editor, visualLineToLogicalLine(editor, line));
} }
@@ -105,7 +105,7 @@ public class EditorHelper {
* @param editor The editor * @param editor The editor
* @return The number of visible lines in the file * @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); int count = getLineCount(editor);
return count == 0 ? 0 : logicalLineToVisualLine(editor, count - 1) + 1; return count == 0 ? 0 : logicalLineToVisualLine(editor, count - 1) + 1;
} }
@@ -116,7 +116,7 @@ public class EditorHelper {
* @param editor The editor * @param editor The editor
* @return The file line count * @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(); int len = editor.getDocument().getLineCount();
if (editor.getDocument().getTextLength() > 0 && if (editor.getDocument().getTextLength() > 0 &&
editor.getDocument().getCharsSequence().charAt(editor.getDocument().getTextLength() - 1) == '\n') { editor.getDocument().getCharsSequence().charAt(editor.getDocument().getTextLength() - 1) == '\n') {
@@ -132,7 +132,7 @@ public class EditorHelper {
* @param editor The editor * @param editor The editor
* @return The file's character count * @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); return getFileSize(editor, false);
} }
@@ -143,7 +143,7 @@ public class EditorHelper {
* @param includeEndNewLine True include newline * @param includeEndNewLine True include newline
* @return The file's character count * @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(); final int len = editor.getDocument().getTextLength();
return includeEndNewLine || len == 0 || editor.getDocument().getCharsSequence().charAt(len - 1) != '\n' ? len : len - 1; 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 * @param scrollOffset The value of the 'scrolloff' option
* @return The scroll offset value to use * @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); return Math.min(scrollOffset, getApproximateScreenHeight(editor) / 2);
} }
@@ -176,7 +176,7 @@ public class EditorHelper {
* @param editor The editor * @param editor The editor
* @return The number of screen lines * @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 lh = editor.getLineHeight();
int height = editor.getScrollingModel().getVisibleArea().y + int height = editor.getScrollingModel().getVisibleArea().y +
editor.getScrollingModel().getVisibleArea().height - editor.getScrollingModel().getVisibleArea().height -
@@ -190,7 +190,7 @@ public class EditorHelper {
* @param editor The editor * @param editor The editor
* @return The number of screen columns * @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(); Rectangle rect = editor.getScrollingModel().getVisibleArea();
Point pt = new Point(rect.width, 0); Point pt = new Point(rect.width, 0);
VisualPosition vp = editor.xyToVisualPosition(pt); VisualPosition vp = editor.xyToVisualPosition(pt);
@@ -204,7 +204,7 @@ public class EditorHelper {
* @param editor The editor * @param editor The editor
* @return The number of pixels * @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(); Rectangle rect = editor.getScrollingModel().getVisibleArea();
if (rect.width == 0) return 0; if (rect.width == 0) return 0;
Point pt = new Point(rect.width, 0); Point pt = new Point(rect.width, 0);
@@ -220,7 +220,7 @@ public class EditorHelper {
* @param editor The editor * @param editor The editor
* @return The column number * @return The column number
*/ */
public static int getVisualColumnAtLeftOfScreen(@NotNull final Editor editor) { public static int getVisualColumnAtLeftOfScreen(final @NotNull Editor editor) {
int cw = getColumnWidth(editor); int cw = getColumnWidth(editor);
if (cw == 0) return 0; if (cw == 0) return 0;
return (editor.getScrollingModel().getHorizontalScrollOffset() + cw - 1) / cw; return (editor.getScrollingModel().getHorizontalScrollOffset() + cw - 1) / cw;
@@ -233,7 +233,7 @@ public class EditorHelper {
* @param line The visual line number to convert * @param line The visual line number to convert
* @return The logical line number * @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; int logicalLine = editor.visualToLogicalPosition(new VisualPosition(line, 0)).line;
return normalizeLine(editor, logicalLine); return normalizeLine(editor, logicalLine);
} }
@@ -246,7 +246,7 @@ public class EditorHelper {
* @param line The logical line number to convert * @param line The logical line number to convert
* @return The visual line number * @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) { if (editor instanceof EditorImpl) {
// This is faster than simply calling Editor#logicalToVisualPosition // This is faster than simply calling Editor#logicalToVisualPosition
return ((EditorImpl) editor).offsetToVisualLine(editor.getDocument().getLineStartOffset(line)); return ((EditorImpl) editor).offsetToVisualLine(editor.getDocument().getLineStartOffset(line));
@@ -254,7 +254,7 @@ public class EditorHelper {
return editor.logicalToVisualPosition(new LogicalPosition(line, 0)).line; 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)); 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. * @param line The logical line to get the start offset for.
* @return 0 if line is &lt 0, file size of line is bigger than file, else the start offset for the line * @return 0 if line is &lt 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) { if (line < 0) {
return 0; return 0;
} }
@@ -285,7 +285,7 @@ public class EditorHelper {
* @param allowEnd True include newline * @param allowEnd True include newline
* @return 0 if line is &lt 0, file size of line is bigger than file, else the end offset for the line * @return 0 if line is &lt 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) { if (line < 0) {
return 0; return 0;
} }
@@ -307,7 +307,7 @@ public class EditorHelper {
* @param line The visual line number to normalize * @param line The visual line number to normalize
* @return The normalized visual line number * @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)); 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 * @param line The logical line number to normalize
* @return The normalized logical line number * @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)); return Math.max(0, Math.min(line, getLineCount(editor) - 1));
} }
@@ -333,7 +333,7 @@ public class EditorHelper {
* @param allowEnd True if newline allowed * @param allowEnd True if newline allowed
* @return The normalized column number * @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))); 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 * @param allowEnd True if newline allowed
* @return The normalized column number * @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); 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 * @param allowEnd true if the offset can be one past the last character on the line, false if not
* @return The normalized column number * @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) { if (getFileSize(editor, allowEnd) == 0) {
return 0; return 0;
} }
@@ -371,11 +371,11 @@ public class EditorHelper {
return Math.max(Math.min(offset, max), min); 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); 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) { if (offset <= 0) {
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); 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 start = getLineStartOffset(editor, line) + col;
int end = getLineEndOffset(editor, line, true); int end = getLineEndOffset(editor, line, true);
CharSequence chars = editor.getDocument().getCharsSequence(); CharSequence chars = editor.getDocument().getCharsSequence();
@@ -411,8 +411,7 @@ public class EditorHelper {
return pos; return pos;
} }
@NotNull public static @NotNull String getLeadingWhitespace(final @NotNull Editor editor, final int line) {
public static String getLeadingWhitespace(@NotNull final Editor editor, final int line) {
int start = getLineStartOffset(editor, line); int start = getLineStartOffset(editor, line);
int end = getLeadingCharacterOffset(editor, line); int end = getLeadingCharacterOffset(editor, line);
@@ -425,8 +424,7 @@ public class EditorHelper {
* @param file The virtual file get the editor for * @param file The virtual file get the editor for
* @return The matching editor or null if no match was found * @return The matching editor or null if no match was found
*/ */
@Nullable public static @Nullable Editor getEditor(final @Nullable VirtualFile file) {
public static Editor getEditor(@Nullable final VirtualFile file) {
if (file == null) { if (file == null) {
return null; return null;
} }
@@ -450,7 +448,7 @@ public class EditorHelper {
* @param pos The visual position to convert * @param pos The visual position to convert
* @return The file offset of the visual position * @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)); return editor.logicalPositionToOffset(editor.visualToLogicalPosition(pos));
} }
@@ -462,15 +460,13 @@ public class EditorHelper {
* @param end The ending offset (exclusive) * @param end The ending offset (exclusive)
* @return The string, never null but empty if start == end * @return The string, never null but empty if start == end
*/ */
@NotNull public static @NotNull String getText(final @NotNull Editor editor, final int start, final int end) {
public static String getText(@NotNull final Editor editor, final int start, final int end) {
if (start == end) return ""; if (start == end) return "";
final CharSequence documentChars = editor.getDocument().getCharsSequence(); final CharSequence documentChars = editor.getDocument().getCharsSequence();
return documentChars.subSequence(normalizeOffset(editor, start), normalizeOffset(editor, end)).toString(); return documentChars.subSequence(normalizeOffset(editor, start), normalizeOffset(editor, end)).toString();
} }
@NotNull public static @NotNull String getText(final @NotNull Editor editor, final @NotNull TextRange range) {
public static String getText(@NotNull final Editor editor, @NotNull final TextRange range) {
int len = range.size(); int len = range.size();
if (len == 1) { if (len == 1) {
return getText(editor, range.getStartOffset(), range.getEndOffset()); return getText(editor, range.getStartOffset(), range.getEndOffset());
@@ -505,7 +501,7 @@ public class EditorHelper {
* @param offset The offset within the line * @param offset The offset within the line
* @return The offset of the line start * @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)); LogicalPosition pos = editor.offsetToLogicalPosition(normalizeOffset(editor, offset));
return editor.getDocument().getLineStartOffset(pos.line); return editor.getDocument().getLineStartOffset(pos.line);
} }
@@ -517,12 +513,12 @@ public class EditorHelper {
* @param offset The offset within the line * @param offset The offset within the line
* @return The offset of the line end * @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)); LogicalPosition pos = editor.offsetToLogicalPosition(normalizeOffset(editor, offset));
return editor.getDocument().getLineEndOffset(pos.line); 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); 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 * @param line The logical line to get the text for
* @return The requested line * @return The requested line
*/ */
@NotNull public static @NotNull String getLineText(final @NotNull Editor editor, final int line) {
public static String getLineText(@NotNull final Editor editor, final int line) {
return getText(editor, getLineStartOffset(editor, line), getLineEndOffset(editor, line, true)); return getText(editor, getLineStartOffset(editor, line), getLineEndOffset(editor, line, true));
} }
@NotNull public static @NotNull CharBuffer getLineBuffer(final @NotNull Editor editor, final int line) {
public static CharBuffer getLineBuffer(@NotNull final Editor editor, final int line) {
int start = getLineStartOffset(editor, line); int start = getLineStartOffset(editor, line);
return CharBuffer.wrap(editor.getDocument().getCharsSequence(), start, start + getLineCharCount(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(); CharSequence chars = editor.getDocument().getCharsSequence();
if (chars.length() == 0) return true; if (chars.length() == 0) return true;
int offset = getLineStartOffset(editor, line); int offset = getLineStartOffset(editor, line);
@@ -565,8 +559,7 @@ public class EditorHelper {
return false; return false;
} }
@NotNull public static @NotNull String pad(final @NotNull Editor editor, @NotNull DataContext context, int line, final int to) {
public static String pad(@NotNull final Editor editor, @NotNull DataContext context, int line, final int to) {
final int len = getLineLength(editor, line); final int len = getLineLength(editor, line);
if(len >= to) return ""; if(len >= to) return "";
@@ -579,8 +572,7 @@ public class EditorHelper {
* *
* @param editor The editor from which the carets are taken * @param editor The editor from which the carets are taken
*/ */
@NotNull public static @NotNull List<Caret> getOrderedCaretsList(@NotNull Editor editor) {
public static List<Caret> getOrderedCaretsList(@NotNull Editor editor) {
@NotNull List<Caret> carets = editor.getCaretModel().getAllCarets(); @NotNull List<Caret> carets = editor.getCaretModel().getAllCarets();
carets.sort(Comparator.comparingInt(Caret::getOffset)); carets.sort(Comparator.comparingInt(Caret::getOffset));
@@ -603,7 +595,7 @@ public class EditorHelper {
* @param editor The editor to scroll * @param editor The editor to scroll
* @param visualLine The visual line to scroll to the current caret location * @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 ScrollingModel scrollingModel = editor.getScrollingModel();
final Rectangle visibleArea = scrollingModel.getVisibleArea(); final Rectangle visibleArea = scrollingModel.getVisibleArea();
final int caretScreenOffset = editor.visualLineToY(editor.getCaretModel().getVisualPosition().line) - visibleArea.y; 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 * @param visualLine The visual line to place at the top of the current window
* @return Returns true if the window was moved * @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(); final ScrollingModel scrollingModel = editor.getScrollingModel();
int inlayHeight = getHeightOfVisualLineInlays(editor, visualLine, true); int inlayHeight = getHeightOfVisualLineInlays(editor, visualLine, true);
int y = editor.visualLineToY(visualLine) - inlayHeight; 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. * @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. * @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) { if (pages > 0) {
return scrollFullPageDown(editor, pages); return scrollFullPageDown(editor, pages);
} }
@@ -707,7 +699,7 @@ public class EditorHelper {
return -1; // visual lines are 1-based 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; return editor.offsetToVisualPosition(EditorHelper.getLineEndOffset(editor, line, allowEnd)).column;
} }
@@ -741,7 +733,7 @@ public class EditorHelper {
UserDataManager.setVimLastColumn(caret, targetColumn); 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 Rectangle visibleArea = editor.getScrollingModel().getVisibleArea();
final int lineCount = getVisualLineCount(editor); final int lineCount = getVisualLineCount(editor);
@@ -784,7 +776,7 @@ public class EditorHelper {
return caretLine; 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 Rectangle visibleArea = editor.getScrollingModel().getVisibleArea();
final int lineHeight = editor.getLineHeight(); final int lineHeight = editor.getLineHeight();
@@ -813,7 +805,7 @@ public class EditorHelper {
return caretLine; 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 line = editor.yToVisualLine(y);
int yActual = editor.visualLineToY(line); int yActual = editor.visualLineToY(line);
if (yActual < topBound) { if (yActual < topBound) {
@@ -825,7 +817,7 @@ public class EditorHelper {
return line; 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(); InlayModel inlayModel = editor.getInlayModel();
List<Inlay> inlays = inlayModel.getBlockElementsForVisualLine(visualLine, above); List<Inlay> inlays = inlayModel.getBlockElementsForVisualLine(visualLine, above);
int inlayHeight = 0; int inlayHeight = 0;
@@ -841,8 +833,7 @@ public class EditorHelper {
* @param editor The editor * @param editor The editor
* @return The virtual file for the editor * @return The virtual file for the editor
*/ */
@Nullable public static @Nullable VirtualFile getVirtualFile(@NotNull Editor editor) {
public static VirtualFile getVirtualFile(@NotNull Editor editor) {
return FileDocumentManager.getInstance().getFile(editor.getDocument()); return FileDocumentManager.getInstance().getFile(editor.getDocument());
} }

View File

@@ -31,15 +31,13 @@ import java.io.InputStreamReader;
*/ */
public class MacKeyRepeat { public class MacKeyRepeat {
public static final String FMT = "defaults %s -globalDomain ApplePressAndHoldEnabled"; 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 @NotNull MacKeyRepeat getInstance() {
public static MacKeyRepeat getInstance() {
return INSTANCE; return INSTANCE;
} }
@Nullable public @Nullable Boolean isEnabled() {
public Boolean isEnabled() {
final String command = String.format(FMT, "read"); final String command = String.format(FMT, "read");
try { try {
final Process process = Runtime.getRuntime().exec(command); final Process process = Runtime.getRuntime().exec(command);
@@ -77,8 +75,7 @@ public class MacKeyRepeat {
} }
} }
@NotNull private static @NotNull String read(@NotNull InputStream stream) throws IOException {
private static String read(@NotNull InputStream stream) throws IOException {
return CharStreams.toString(new InputStreamReader(stream)); return CharStreams.toString(new InputStreamReader(stream));
} }
} }

View File

@@ -30,7 +30,7 @@ import java.util.ResourceBundle;
public class MessageHelper { public class MessageHelper {
@Nullable private static Reference<ResourceBundle> ourBundle; private static @Nullable Reference<ResourceBundle> ourBundle;
@NonNls @NonNls
private static final String BUNDLE = "messages"; private static final String BUNDLE = "messages";
@@ -38,21 +38,18 @@ public class MessageHelper {
private MessageHelper() { private MessageHelper() {
} }
@NotNull public static @NotNull String message(@NotNull @PropertyKey(resourceBundle = BUNDLE)String key, Object... params) {
public static String message(@NotNull @PropertyKey(resourceBundle = BUNDLE)String key, Object... params) {
return CommonBundle.message(getBundle(), key, params); return CommonBundle.message(getBundle(), key, params);
} }
/* /*
* This method added for jruby access * This method added for jruby access
*/ */
@NotNull public static @NotNull String message(@NotNull @PropertyKey(resourceBundle = BUNDLE)String key) {
public static String message(@NotNull @PropertyKey(resourceBundle = BUNDLE)String key) {
return CommonBundle.message(getBundle(), key); return CommonBundle.message(getBundle(), key);
} }
@NotNull protected static @NotNull ResourceBundle getBundle() {
protected static ResourceBundle getBundle() {
ResourceBundle bundle = null; ResourceBundle bundle = null;
if (ourBundle != null) bundle = ourBundle.get(); if (ourBundle != null) bundle = ourBundle.get();
if (bundle == null) { if (bundle == null) {

View File

@@ -95,6 +95,7 @@ public class PsiHelper {
if (element.getLanguage().getID().equals("JAVA")) { if (element.getLanguage().getID().equals("JAVA")) {
// HACK: for Java classes and methods, we want to jump to the opening brace // HACK: for Java classes and methods, we want to jump to the opening brace
int textOffset = element.getTextOffset(); 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); int braceIndex = element.getText().indexOf('{', textOffset - offset);
if (braceIndex >= 0) { if (braceIndex >= 0) {
offset += braceIndex; offset += braceIndex;
@@ -113,8 +114,7 @@ public class PsiHelper {
} }
} }
@Nullable public static @Nullable PsiFile getFile(@NotNull Editor editor) {
public static PsiFile getFile(@NotNull Editor editor) {
VirtualFile vf = EditorHelper.getVirtualFile(editor); VirtualFile vf = EditorHelper.getVirtualFile(editor);
if (vf != null) { if (vf != null) {
Project proj = editor.getProject(); Project proj = editor.getProject();

View File

@@ -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);
}
}
}

View 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)
}
}

View File

@@ -37,12 +37,13 @@ import org.jetbrains.annotations.Contract;
import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable; import org.jetbrains.annotations.Nullable;
import java.util.ArrayList; import java.util.*;
import java.util.List; import java.util.function.Function;
import java.util.Stack;
import java.util.regex.Matcher; import java.util.regex.Matcher;
import java.util.regex.Pattern; import java.util.regex.Pattern;
import static com.maddyhome.idea.vim.helper.SearchHelperKtKt.checkInString;
/** /**
* Helper methods for searching text * Helper methods for searching text
*/ */
@@ -98,17 +99,16 @@ public class SearchHelper {
int pos = caret.getOffset(); int pos = caret.getOffset();
int loc = blockChars.indexOf(type); int loc = blockChars.indexOf(type);
// What direction should we go now (-1 is backward, 1 is forward) // 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 // Which character did we find and which should we now search for
char match = blockChars.charAt(loc); 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); return findBlockLocation(chars, found, match, dir, pos, count, false);
} }
@Nullable public static @Nullable TextRange findBlockRange(@NotNull Editor editor, @NotNull Caret caret, char type, int count,
public static TextRange findBlockRange(@NotNull Editor editor, @NotNull Caret caret, char type, int count, boolean isOuter) {
boolean isOuter) {
CharSequence chars = editor.getDocument().getCharsSequence(); CharSequence chars = editor.getDocument().getCharsSequence();
int pos = caret.getOffset(); int pos = caret.getOffset();
int start = caret.getSelectionStart(); int start = caret.getSelectionStart();
@@ -153,10 +153,10 @@ public class SearchHelper {
int endOffset = quoteRange.getEndOffset(); int endOffset = quoteRange.getEndOffset();
CharSequence subSequence = chars.subSequence(startOffset, endOffset); CharSequence subSequence = chars.subSequence(startOffset, endOffset);
int inQuotePos = pos - startOffset; 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) { if (inQuoteStart != -1) {
startPosInStringFound = true; 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) { if (inQuoteEnd != -1) {
bstart = inQuoteStart + startOffset; bstart = inQuoteStart + startOffset;
bend = inQuoteEnd + startOffset; bend = inQuoteEnd + startOffset;
@@ -166,9 +166,9 @@ public class SearchHelper {
} }
if (!startPosInStringFound) { if (!startPosInStringFound) {
bstart = findBlockLocation(chars, close, type, -1, pos, count, false); bstart = findBlockLocation(chars, close, type, Direction.BACK, pos, count, false);
if (bstart != -1) { 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, private static int findMatchingBlockCommentPair(@NotNull PsiComment comment, int pos, @Nullable String prefix,
@Nullable String suffix) { @Nullable String suffix) {
if (prefix != null && suffix != null) { 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(); final String commentText = comment.getText();
if (commentText.startsWith(prefix) && commentText.endsWith(suffix)) { if (commentText.startsWith(prefix) && commentText.endsWith(suffix)) {
final int endOffset = comment.getTextOffset() + comment.getTextLength(); final int endOffset = comment.getTextOffset() + comment.getTextLength();
@@ -281,10 +282,10 @@ public class SearchHelper {
// If we found one ... // If we found one ...
if (loc >= 0) { if (loc >= 0) {
// What direction should we go now (-1 is backward, 1 is forward) // 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 // Which character did we find and which should we now search for
char found = getPairChars().charAt(loc); 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); res = findBlockLocation(chars, found, match, dir, pos, 1, true);
} }
@@ -308,21 +309,29 @@ public class SearchHelper {
private static int findBlockLocation(@NotNull CharSequence chars, private static int findBlockLocation(@NotNull CharSequence chars,
char found, char found,
char match, char match,
int dir, @NotNull Direction dir,
int pos, int pos,
int cnt, int cnt,
boolean allowInString) { boolean allowInString) {
int res = -1; 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 inString = checkInString(chars, inCheckPos, true);
boolean initialInString = inString; boolean initialInString = inString;
boolean inChar = checkInString(chars, inCheckPos, false); boolean inChar = checkInString(chars, inCheckPos, false);
boolean initial = true;
int stack = 0; int stack = 0;
// Search to start or end of file, as appropriate // 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) { 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 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 // We found our match
if (stack == 0) { if (stack == 0) {
res = pos; 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 // 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; inString = false;
inChar = false; inChar = false;
} }
else if (!initial) { else if (pos != initialPos) {
// We found another character like our original - belongs to another pair // We found another character like our original - belongs to another pair
if (!inString && !inChar && chars.charAt(pos) == found) { if (!inString && !inChar && c == found) {
stack++; stack++;
} }
// We found the start/end of a string // We found the start/end of a string
else if (!inChar && isQuoteWithoutEscape(chars, pos, '"')) { else if (!inChar) {
inString = !inString; inString = checkInString(chars, inCheckPosF.apply(pos), true);
} }
else if (!inString && isQuoteWithoutEscape(chars, pos, '\'')) { else if (!inString) {
inChar = !inChar; inChar = checkInString(chars, inCheckPosF.apply(pos), false);
} }
} }
pos += dir.toInt();
pos += dir;
initial = false;
} }
return res; return res;
@@ -366,18 +373,13 @@ public class SearchHelper {
if (chars.charAt(pos) != quote) return false; if (chars.charAt(pos) != quote) return false;
int backslashCounter = 0; int backslashCounter = 0;
while (pos-- > 0) { while (pos-- > 0 && chars.charAt(pos) == '\\') {
if (chars.charAt(pos) == '\\') { backslashCounter++;
backslashCounter++;
}
else {
break;
}
} }
return backslashCounter % 2 == 0; return backslashCounter % 2 == 0;
} }
private enum Direction { public enum Direction {
BACK(-1), FORWARD(1); BACK(-1), FORWARD(1);
private final int value; private final int value;
@@ -386,7 +388,7 @@ public class SearchHelper {
value = i; value = i;
} }
private int toInt() { public int toInt() {
return value; return value;
} }
} }
@@ -419,10 +421,28 @@ public class SearchHelper {
return cnt; 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, private static int findCharacterPosition(@NotNull CharSequence chars, int pos, final char c, boolean currentLineOnly,
boolean searchEscaped, @NotNull Direction direction) { boolean searchEscaped, @NotNull Direction direction) {
while (pos >= 0 && pos < chars.length() && (!currentLineOnly || chars.charAt(pos) != '\n')) { 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; return pos;
} }
pos += direction.toInt(); pos += direction.toInt();
@@ -441,8 +461,7 @@ public class SearchHelper {
} }
@Nullable public static @Nullable TextRange findBlockTagRange(@NotNull Editor editor, @NotNull Caret caret, int count, boolean isOuter) {
public static TextRange findBlockTagRange(@NotNull Editor editor, @NotNull Caret caret, int count, boolean isOuter) {
final int position = caret.getOffset(); final int position = caret.getOffset();
final CharSequence sequence = editor.getDocument().getCharsSequence(); 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/>. * 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; int openingBracket = -1;
for (int i = position; i >= 0 && i < sequence.length(); i--) { for (int i = position; i >= 0 && i < sequence.length(); i--) {
if (sequence.charAt(i) == '<') { if (sequence.charAt(i) == '<') {
@@ -551,8 +570,7 @@ public class SearchHelper {
return closingBracket != -1 && sequence.charAt(closingBracket - 1) != '/'; return closingBracket != -1 && sequence.charAt(closingBracket - 1) != '/';
} }
@Nullable private static @Nullable Pair<TextRange,String> findUnmatchedClosingTag(final @NotNull CharSequence sequence, final int position, int count) {
private static Pair<TextRange,String> findUnmatchedClosingTag(@NotNull final CharSequence sequence, final int position, int count) {
// The tag name may contain any characters except slashes, whitespace and '>' // The tag name may contain any characters except slashes, whitespace and '>'
final String tagNamePattern = "([^/\\s>]+)"; final String tagNamePattern = "([^/\\s>]+)";
// An opening tag consists of '<' followed by a tag name, optionally some additional text after whitespace and a '>' // 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; return null;
} }
@Nullable private static @Nullable TextRange findUnmatchedOpeningTag(@NotNull CharSequence sequence, int position, @NotNull String tagName) {
private static TextRange findUnmatchedOpeningTag(@NotNull CharSequence sequence, int position, @NotNull String tagName) {
final String quotedTagName = Pattern.quote(tagName); final String quotedTagName = Pattern.quote(tagName);
final String patternString = "(</%s>)" // match closing tags final String patternString = "(</%s>)" // match closing tags
+ "|(<%s" // or opening tags starting with tagName + "|(<%s" // or opening tags starting with tagName
@@ -619,9 +636,8 @@ public class SearchHelper {
} }
@Nullable public static @Nullable TextRange findBlockQuoteInLineRange(@NotNull Editor editor, @NotNull Caret caret, char quote,
public static TextRange findBlockQuoteInLineRange(@NotNull Editor editor, @NotNull Caret caret, char quote, boolean isOuter) {
boolean isOuter) {
final CharSequence chars = editor.getDocument().getCharsSequence(); final CharSequence chars = editor.getDocument().getCharsSequence();
final int pos = caret.getOffset(); final int pos = caret.getOffset();
if (pos >= chars.length() || chars.charAt(pos) == '\n') { if (pos >= chars.length() || chars.charAt(pos) == '\n') {
@@ -664,27 +680,6 @@ public class SearchHelper {
return new TextRange(start, end + 1); 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) { public static int findNextCamelStart(@NotNull Editor editor, @NotNull Caret caret, int count) {
CharSequence chars = editor.getDocument().getCharsSequence(); CharSequence chars = editor.getDocument().getCharsSequence();
int pos = caret.getOffset(); int pos = caret.getOffset();
@@ -779,8 +774,7 @@ public class SearchHelper {
/** /**
* This counts all the words in the file. * This counts all the words in the file.
*/ */
@NotNull public static @NotNull CountPosition countWords(@NotNull Editor editor) {
public static CountPosition countWords(@NotNull Editor editor) {
int size = EditorHelper.getFileSize(editor); int size = EditorHelper.getFileSize(editor);
return countWords(editor, 0, size); return countWords(editor, 0, size);
@@ -789,16 +783,14 @@ public class SearchHelper {
/** /**
* This counts all the words in the file. * This counts all the words in the file.
*/ */
@NotNull public static @NotNull CountPosition countWords(@NotNull Editor editor, int start, int end) {
public static CountPosition countWords(@NotNull Editor editor, int start, int end) {
CharSequence chars = editor.getDocument().getCharsSequence(); CharSequence chars = editor.getDocument().getCharsSequence();
int offset = editor.getCaretModel().getOffset(); int offset = editor.getCaretModel().getOffset();
return countWords(chars, start, end, offset); return countWords(chars, start, end, offset);
} }
@NotNull public static @NotNull CountPosition countWords(@NotNull CharSequence chars, int start, int end, int offset) {
public static CountPosition countWords(@NotNull CharSequence chars, int start, int end, int offset) {
int count = 1; int count = 1;
int position = 0; int position = 0;
int last = -1; int last = -1;
@@ -924,9 +916,8 @@ public class SearchHelper {
return res; return res;
} }
@NotNull public static @NotNull List<TextRange> findNumbersInRange(final @NotNull Editor editor, @NotNull TextRange textRange,
public static List<TextRange> findNumbersInRange(@NotNull final Editor editor, @NotNull TextRange textRange, final boolean alpha, final boolean hex, final boolean octal) {
final boolean alpha, final boolean hex, final boolean octal) {
List<TextRange> result = new ArrayList<>(); List<TextRange> result = new ArrayList<>();
int firstLine = editor.offsetToLogicalPosition(textRange.getStartOffset()).line; int firstLine = editor.offsetToLogicalPosition(textRange.getStartOffset()).line;
int lastLine = editor.offsetToLogicalPosition(textRange.getEndOffset()).line; int lastLine = editor.offsetToLogicalPosition(textRange.getEndOffset()).line;
@@ -950,9 +941,8 @@ public class SearchHelper {
return result; return result;
} }
@Nullable public static @Nullable TextRange findNumberUnderCursor(final @NotNull Editor editor, @NotNull Caret caret, final boolean alpha,
public static TextRange findNumberUnderCursor(@NotNull final Editor editor, @NotNull Caret caret, final boolean alpha, final boolean hex, final boolean octal) {
final boolean hex, final boolean octal) {
int lline = caret.getLogicalPosition().line; int lline = caret.getLogicalPosition().line;
String text = EditorHelper.getLineText(editor, lline).toLowerCase(); String text = EditorHelper.getLineText(editor, lline).toLowerCase();
int startLineOffset = EditorHelper.getLineStartOffset(editor, lline); int startLineOffset = EditorHelper.getLineStartOffset(editor, lline);
@@ -974,9 +964,8 @@ public class SearchHelper {
* @param startPosOnLine - start offset to search * @param startPosOnLine - start offset to search
* @return - text range with number * @return - text range with number
*/ */
@Nullable public static @Nullable TextRange findNumberInText(final @NotNull String textInRange, int startPosOnLine, final boolean alpha,
public static TextRange findNumberInText(@NotNull final String textInRange, int startPosOnLine, final boolean alpha, final boolean hex, final boolean octal) {
final boolean hex, final boolean octal) {
if (logger.isDebugEnabled()) { if (logger.isDebugEnabled()) {
logger.debug("text=" + textInRange); logger.debug("text=" + textInRange);
@@ -1066,9 +1055,8 @@ public class SearchHelper {
/** /**
* Searches for digits block that matches parameters * Searches for digits block that matches parameters
*/ */
@NotNull private static @NotNull Pair<Integer, Integer> findRange(final @NotNull String text, final int pos, final boolean alpha,
private static Pair<Integer, Integer> findRange(@NotNull final String text, final int pos, final boolean alpha, final boolean hex, final boolean octal, final boolean decimal) {
final boolean hex, final boolean octal, final boolean decimal) {
int end = pos; int end = pos;
while (end < text.length() && isNumberChar(text.charAt(end), alpha, hex, octal, decimal)) { while (end < text.length() && isNumberChar(text.charAt(end), alpha, hex, octal, decimal)) {
end++; end++;
@@ -1108,8 +1096,7 @@ public class SearchHelper {
* @param caret The caret to find word under * @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 * @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 @Nullable TextRange findWordUnderCursor(@NotNull Editor editor, @NotNull Caret caret) {
public static TextRange findWordUnderCursor(@NotNull Editor editor, @NotNull Caret caret) {
CharSequence chars = editor.getDocument().getCharsSequence(); CharSequence chars = editor.getDocument().getCharsSequence();
int stop = EditorHelper.getLineEndOffset(editor, caret.getLogicalPosition().line, true); int stop = EditorHelper.getLineEndOffset(editor, caret.getLogicalPosition().line, true);
@@ -1159,9 +1146,8 @@ public class SearchHelper {
} }
@Contract("_, _, _, _, _, _, _ -> new") @Contract("_, _, _, _, _, _, _ -> new")
@NotNull public static @NotNull TextRange findWordUnderCursor(@NotNull Editor editor, @NotNull Caret caret, int count, int dir,
public static TextRange findWordUnderCursor(@NotNull Editor editor, @NotNull Caret caret, int count, int dir, boolean isOuter, boolean isBig, boolean hasSelection) {
boolean isOuter, boolean isBig, boolean hasSelection) {
if (logger.isDebugEnabled()) { if (logger.isDebugEnabled()) {
logger.debug("count=" + count); logger.debug("count=" + count);
logger.debug("dir=" + dir); logger.debug("dir=" + dir);
@@ -1892,8 +1878,7 @@ public class SearchHelper {
} }
@Contract("_, _, _, _ -> new") @Contract("_, _, _, _ -> new")
@NotNull public static @NotNull TextRange findSentenceRange(@NotNull Editor editor, @NotNull Caret caret, int count, boolean isOuter) {
public static TextRange findSentenceRange(@NotNull Editor editor, @NotNull Caret caret, int count, boolean isOuter) {
CharSequence chars = editor.getDocument().getCharsSequence(); CharSequence chars = editor.getDocument().getCharsSequence();
if (chars.length() == 0) return new TextRange(0, 0); if (chars.length() == 0) return new TextRange(0, 0);
int max = EditorHelper.getFileSize(editor); int max = EditorHelper.getFileSize(editor);
@@ -2030,8 +2015,7 @@ public class SearchHelper {
return line; return line;
} }
@Nullable public static @Nullable TextRange findParagraphRange(@NotNull Editor editor, @NotNull Caret caret, int count, boolean isOuter) {
public static TextRange findParagraphRange(@NotNull Editor editor, @NotNull Caret caret, int count, boolean isOuter) {
int line = caret.getLogicalPosition().line; int line = caret.getLogicalPosition().line;
int maxline = EditorHelper.getLineCount(editor); int maxline = EditorHelper.getLineCount(editor);
if (logger.isDebugEnabled()) logger.debug("starting on line " + line); if (logger.isDebugEnabled()) logger.debug("starting on line " + line);
@@ -2150,8 +2134,7 @@ public class SearchHelper {
return PsiHelper.findMethodEnd(editor, caret.getOffset(), count); return PsiHelper.findMethodEnd(editor, caret.getOffset(), count);
} }
@NotNull private static @NotNull String getPairChars() {
private static String getPairChars() {
if (pairsChars == null) { if (pairsChars == null) {
ListOption lo = OptionsManager.INSTANCE.getMatchpairs(); ListOption lo = OptionsManager.INSTANCE.getMatchpairs();
lo.addOptionChangeListenerAndExecute((oldValue, newValue) -> pairsChars = parseOption(lo)); lo.addOptionChangeListenerAndExecute((oldValue, newValue) -> pairsChars = parseOption(lo));
@@ -2160,8 +2143,7 @@ public class SearchHelper {
return pairsChars; return pairsChars;
} }
@NotNull private static @NotNull String parseOption(@NotNull ListOption option) {
private static String parseOption(@NotNull ListOption option) {
List<String> vals = option.values(); List<String> vals = option.values();
StringBuilder res = new StringBuilder(); StringBuilder res = new StringBuilder();
for (String s : vals) { for (String s : vals) {
@@ -2191,8 +2173,8 @@ public class SearchHelper {
private final int position; private final int position;
} }
@Nullable private static String pairsChars = null; private static @Nullable String pairsChars = null;
@NotNull private static final String blockChars = "{}()[]<>"; private static final @NotNull String blockChars = "{}()[]<>";
private static final Logger logger = Logger.getInstance(SearchHelper.class.getName()); private static final Logger logger = Logger.getInstance(SearchHelper.class.getName());
} }

View 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)
}
}

View File

@@ -46,8 +46,7 @@ public class StringHelper {
private StringHelper() {} private StringHelper() {}
@Nullable private static @Nullable String toEscapeNotation(@NotNull KeyStroke key) {
private static String toEscapeNotation(@NotNull KeyStroke key) {
final char c = key.getKeyChar(); final char c = key.getKeyChar();
if (isControlCharacter(c)) { if (isControlCharacter(c)) {
return "^" + (char)(c + 'A' - 1); return "^" + (char)(c + 'A' - 1);
@@ -58,8 +57,7 @@ public class StringHelper {
return null; return null;
} }
@NotNull public static @NotNull List<KeyStroke> stringToKeys(@NotNull String s) {
public static List<KeyStroke> stringToKeys(@NotNull String s) {
final List<KeyStroke> res = new ArrayList<>(); final List<KeyStroke> res = new ArrayList<>();
for (int i = 0; i < s.length(); i++) { for (int i = 0; i < s.length(); i++) {
res.add(getKeyStroke(s.charAt(i))); res.add(getKeyStroke(s.charAt(i)));
@@ -67,8 +65,7 @@ public class StringHelper {
return res; return res;
} }
@NotNull public static final @NotNull KeyStroke PlugKeyStroke = parseKeys("<Plug>").get(0);
public static final KeyStroke PlugKeyStroke = parseKeys("<Plug>").get(0);
private enum KeyParserState { private enum KeyParserState {
INIT, INIT,
@@ -82,8 +79,7 @@ public class StringHelper {
* @throws java.lang.IllegalArgumentException if the mapping doesn't make sense for Vim emulation * @throws java.lang.IllegalArgumentException if the mapping doesn't make sense for Vim emulation
* @see :help <> * @see :help <>
*/ */
@NotNull public static @NotNull List<KeyStroke> parseKeys(@NotNull String... strings) {
public static List<KeyStroke> parseKeys(@NotNull String... strings) {
final List<KeyStroke> result = new ArrayList<>(); final List<KeyStroke> result = new ArrayList<>();
for (String s : strings) { for (String s : strings) {
KeyParserState state = KeyParserState.INIT; KeyParserState state = KeyParserState.INIT;
@@ -167,8 +163,7 @@ public class StringHelper {
return result; return result;
} }
@Nullable private static @Nullable List<KeyStroke> parseMapLeader(@NotNull String s) {
private static List<KeyStroke> parseMapLeader(@NotNull String s) {
if ("leader".equals(s.toLowerCase())) { if ("leader".equals(s.toLowerCase())) {
final Object mapLeader = VimScriptGlobalEnvironment.getInstance().getVariables().get("mapleader"); final Object mapLeader = VimScriptGlobalEnvironment.getInstance().getVariables().get("mapleader");
if (mapLeader instanceof String) { if (mapLeader instanceof String) {
@@ -189,8 +184,7 @@ public class StringHelper {
return key.getKeyChar() == CHAR_UNDEFINED && key.getKeyCode() < 0x20 && key.getModifiers() == 0; return key.getKeyChar() == CHAR_UNDEFINED && key.getKeyCode() < 0x20 && key.getModifiers() == 0;
} }
@NotNull public static @NotNull String toKeyNotation(@NotNull List<KeyStroke> keys) {
public static String toKeyNotation(@NotNull List<KeyStroke> keys) {
if (keys.isEmpty()) { if (keys.isEmpty()) {
return "<Nop>"; return "<Nop>";
} }
@@ -201,8 +195,7 @@ public class StringHelper {
return builder.toString(); return builder.toString();
} }
@NotNull public static @NotNull String toKeyNotation(@NotNull KeyStroke key) {
public static String toKeyNotation(@NotNull KeyStroke key) {
final char c = key.getKeyChar(); final char c = key.getKeyChar();
final int keyCode = key.getKeyCode(); final int keyCode = key.getKeyCode();
final int modifiers = key.getModifiers(); final int modifiers = key.getModifiers();
@@ -318,8 +311,7 @@ public class StringHelper {
/** /**
* Set the text of an XML element, safely encode it if needed. * Set the text of an XML element, safely encode it if needed.
*/ */
@NotNull public static @NotNull Element setSafeXmlText(@NotNull Element element, @NotNull String text) {
public static Element setSafeXmlText(@NotNull Element element, @NotNull String text) {
final Character first = firstCharacter(text); final Character first = firstCharacter(text);
final Character last = lastCharacter(text); final Character last = lastCharacter(text);
if (!StringHelper.isXmlCharacterData(text) || if (!StringHelper.isXmlCharacterData(text) ||
@@ -338,8 +330,7 @@ public class StringHelper {
/** /**
* Get the (potentially safely encoded) text of an XML element. * Get the (potentially safely encoded) text of an XML element.
*/ */
@Nullable public static @Nullable String getSafeXmlText(@NotNull Element element) {
public static String getSafeXmlText(@NotNull Element element) {
final String text = element.getText(); final String text = element.getText();
final String encoding = element.getAttributeValue("encoding"); final String encoding = element.getAttributeValue("encoding");
if (encoding == null) { if (encoding == null) {
@@ -365,8 +356,7 @@ public class StringHelper {
return true; return true;
} }
@Nullable private static @Nullable KeyStroke parseSpecialKey(@NotNull String s, int modifiers) {
private static KeyStroke parseSpecialKey(@NotNull String s, int modifiers) {
final String lower = s.toLowerCase(); final String lower = s.toLowerCase();
final Integer keyCode = getVimKeyName(lower); final Integer keyCode = getVimKeyName(lower);
final Character typedChar = getVimTypedKeyName(lower); final Character typedChar = getVimTypedKeyName(lower);
@@ -541,8 +531,7 @@ public class StringHelper {
} }
} }
@NotNull private static @NotNull KeyStroke getTypedOrPressedKeyStroke(char c, int modifiers) {
private static KeyStroke getTypedOrPressedKeyStroke(char c, int modifiers) {
if (modifiers == 0) { if (modifiers == 0) {
return getKeyStroke(c); return getKeyStroke(c);
} }
@@ -554,13 +543,11 @@ public class StringHelper {
} }
} }
@Nullable private static @Nullable Character lastCharacter(@NotNull String text) {
private static Character lastCharacter(@NotNull String text) {
return text.length() > 0 ? text.charAt(text.length() - 1) : null; return text.length() > 0 ? text.charAt(text.length() - 1) : null;
} }
@Nullable private static @Nullable Character firstCharacter(@NotNull String text) {
private static Character firstCharacter(@NotNull String text) {
return text.length() > 0 ? text.charAt(0) : null; return text.length() > 0 ? text.charAt(0) : null;
} }

View File

@@ -30,7 +30,7 @@ import java.util.List;
* @author vlan * @author vlan
*/ */
public class TestInputModel { public class TestInputModel {
@NotNull private final List<KeyStroke> myKeyStrokes = Lists.newArrayList(); private final @NotNull List<KeyStroke> myKeyStrokes = Lists.newArrayList();
private TestInputModel() {} private TestInputModel() {}
@@ -48,8 +48,7 @@ public class TestInputModel {
myKeyStrokes.addAll(keyStrokes); myKeyStrokes.addAll(keyStrokes);
} }
@Nullable public @Nullable KeyStroke nextKeyStroke() {
public KeyStroke nextKeyStroke() {
if (!myKeyStrokes.isEmpty()) { if (!myKeyStrokes.isEmpty()) {
return myKeyStrokes.remove(0); return myKeyStrokes.remove(0);
} }

View File

@@ -20,13 +20,14 @@ package com.maddyhome.idea.vim.key;
import com.google.common.collect.HashMultiset; import com.google.common.collect.HashMultiset;
import com.google.common.collect.Multiset; import com.google.common.collect.Multiset;
import com.maddyhome.idea.vim.command.MappingMode;
import com.maddyhome.idea.vim.extension.VimExtensionHandler; import com.maddyhome.idea.vim.extension.VimExtensionHandler;
import kotlin.Pair;
import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable; import org.jetbrains.annotations.Nullable;
import javax.swing.*; import javax.swing.*;
import java.util.*; import java.util.*;
import java.util.stream.Collectors;
/** /**
* Container for key mappings for some mode * Container for key mappings for some mode
@@ -35,36 +36,46 @@ import java.util.*;
* @author vlan * @author vlan
*/ */
public class KeyMapping implements Iterable<List<KeyStroke>> { 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. * Set the contains all possible prefixes for mappings.
* E.g. if there is mapping for "hello", this set will contain "h", "he", "hel", etc. * E.g. if there is mapping for "hello", this set will contain "h", "he", "hel", etc.
* Multiset is used to correctly remove the mappings. * 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 @Override
public Iterator<List<KeyStroke>> iterator() { public @NotNull Iterator<List<KeyStroke>> iterator() {
return new ArrayList<>(myKeys.keySet()).iterator(); return new ArrayList<>(myKeys.keySet()).iterator();
} }
@Nullable public @Nullable MappingInfo get(@NotNull Iterable<KeyStroke> keys) {
public 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. // 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? // TODO: Should we change this to be a trie?
assert (keys instanceof List) : "keys must be of type List<KeyStroke>"; assert (keys instanceof List) : "keys must be of type List<KeyStroke>";
return myKeys.get(keys); return myKeys.get(keys);
} }
public void put(@NotNull Set<MappingMode> mappingModes, public void put(@NotNull List<KeyStroke> fromKeys,
@NotNull List<KeyStroke> fromKeys, @NotNull MappingOwner owner,
@Nullable List<KeyStroke> toKeys, @NotNull VimExtensionHandler extensionHandler,
@Nullable VimExtensionHandler extensionHandler,
boolean recursive) { boolean recursive) {
myKeys.put(new ArrayList<>(fromKeys), myKeys.put(new ArrayList<>(fromKeys), new ToHandlerMappingInfo(extensionHandler, fromKeys, recursive, owner));
new MappingInfo(mappingModes, fromKeys, toKeys, extensionHandler, recursive)); 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<>(); List<KeyStroke> prefix = new ArrayList<>();
final int prefixLength = fromKeys.size() - 1; final int prefixLength = fromKeys.size() - 1;
for (int i = 0; i < prefixLength; i++) { 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) { public void delete(@NotNull MappingOwner owner) {
myKeys.remove(keys); List<Map.Entry<List<KeyStroke>, MappingInfo>> toRemove =
List<KeyStroke> prefix = new ArrayList<>(); myKeys.entrySet().stream().filter(o -> o.getValue().getOwner().equals(owner)).collect(Collectors.toList());
final int prefixLength = keys.size() - 1;
for (int i = 0; i < prefixLength; i++) { toRemove.forEach(o -> myKeys.remove(o.getKey(), o.getValue()));
prefix.add(keys.get(i)); toRemove.stream().map(Map.Entry::getKey).forEach(keys -> {
myPrefixes.remove(prefix); 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) { public boolean isPrefix(@NotNull Iterable<KeyStroke> keys) {

View File

@@ -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;
}
}
}

View 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)

View 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()
}
}
}

View File

@@ -28,27 +28,24 @@ public enum ShortcutOwner {
IDE("ide", "IDE"), IDE("ide", "IDE"),
VIM("vim", "Vim"); VIM("vim", "Vim");
@NotNull private final String name; private final @NotNull String name;
@NotNull private final String title; private final @NotNull String title;
ShortcutOwner(@NotNull String name, @NotNull String title) { ShortcutOwner(@NotNull String name, @NotNull String title) {
this.name = name; this.name = name;
this.title = title; this.title = title;
} }
@NotNull
@Override @Override
public String toString() { public @NotNull String toString() {
return title; return title;
} }
@NotNull public @NotNull String getName() {
public String getName() {
return name; return name;
} }
@NotNull public static @NotNull ShortcutOwner fromString(@NotNull String s) {
public static ShortcutOwner fromString(@NotNull String s) {
if ("ide".equals(s)) { if ("ide".equals(s)) {
return IDE; return IDE;
} }

View File

@@ -72,5 +72,5 @@ public class BoundListOption extends ListOption {
return true; return true;
} }
@NotNull protected final List<String> values; protected final @NotNull List<String> values;
} }

View File

@@ -30,10 +30,10 @@ import java.util.regex.Pattern;
import java.util.stream.Collectors; import java.util.stream.Collectors;
public final class KeywordOption extends ListOption { public final class KeywordOption extends ListOption {
@NotNull private final Pattern validationPattern; private final @NotNull Pattern validationPattern;
// KeywordSpecs are the option values in reverse order // 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) { public KeywordOption(@NotNull String name, @NotNull String abbrev, @NotNull String[] defaultValue) {
super(name, abbrev, defaultValue, super(name, abbrev, defaultValue,
@@ -116,8 +116,7 @@ public final class KeywordOption extends ListOption {
} }
} }
@NotNull private @NotNull List<KeywordSpec> valsToReversedSpecs(@NotNull List<String> vals) {
private List<KeywordSpec> valsToReversedSpecs(@NotNull List<String> vals) {
final ArrayList<KeywordSpec> res = new ArrayList<>(); final ArrayList<KeywordSpec> res = new ArrayList<>();
for (int i = vals.size() - 1; i >= 0; i--) { for (int i = vals.size() - 1; i >= 0; i--) {
res.add(new KeywordSpec(vals.get(i))); res.add(new KeywordSpec(vals.get(i)));
@@ -125,8 +124,7 @@ public final class KeywordOption extends ListOption {
return res; return res;
} }
@Nullable private @Nullable List<KeywordSpec> valsToValidatedAndReversedSpecs(@Nullable List<String> vals) {
private List<KeywordSpec> valsToValidatedAndReversedSpecs(@Nullable List<String> vals) {
final List<KeywordSpec> specs = new ArrayList<>(); final List<KeywordSpec> specs = new ArrayList<>();
if (vals != null) { if (vals != null) {
for (String val : vals) { for (String val : vals) {
@@ -141,9 +139,8 @@ public final class KeywordOption extends ListOption {
return specs; return specs;
} }
@Nullable
@Override @Override
protected List<String> parseVals(@NotNull String content) { protected @Nullable List<String> parseVals(@NotNull String content) {
if (!validationPattern.matcher(content).matches()) { if (!validationPattern.matcher(content).matches()) {
return null; return null;
} }

View File

@@ -30,7 +30,7 @@ import java.util.StringTokenizer;
* This is an option that accepts an arbitrary list of values * This is an option that accepts an arbitrary list of values
*/ */
public class ListOption extends TextOption { 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 * 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 * @return The option's value
*/ */
@Override @Override
@NotNull public @NotNull String getValue() {
public String getValue() {
StringBuilder res = new StringBuilder(); StringBuilder res = new StringBuilder();
int cnt = 0; int cnt = 0;
for (String s : value) { for (String s : value) {
@@ -59,8 +58,7 @@ public class ListOption extends TextOption {
* *
* @return The option's values * @return The option's values
*/ */
@NotNull public @NotNull List<String> values() {
public List<String> values() {
return value; return value;
} }
@@ -197,8 +195,7 @@ public class ListOption extends TextOption {
return dflt.equals(value); return dflt.equals(value);
} }
@Nullable protected @Nullable List<String> parseVals(String val) {
protected List<String> parseVals(String val) {
List<String> res = new ArrayList<>(); List<String> res = new ArrayList<>();
StringTokenizer tokenizer = new StringTokenizer(val, ","); StringTokenizer tokenizer = new StringTokenizer(val, ",");
while (tokenizer.hasMoreTokens()) { while (tokenizer.hasMoreTokens()) {
@@ -219,13 +216,12 @@ public class ListOption extends TextOption {
* *
* @return The option as a string {name}={value list} * @return The option as a string {name}={value list}
*/ */
@NotNull public @NotNull String toString() {
public String toString() {
return " " + getName() + "=" + getValue(); return " " + getName() + "=" + getValue();
} }
@NotNull protected final List<String> dflt; protected final @NotNull List<String> dflt;
@NotNull protected List<String> value; protected @NotNull List<String> value;
protected final String pattern; protected final String pattern;
/** /**

View File

@@ -213,8 +213,7 @@ public class NumberOption extends TextOption {
} }
} }
@Nullable protected @Nullable Integer asNumber(String val) {
protected Integer asNumber(String val) {
try { try {
return Integer.decode(val); return Integer.decode(val);
} }
@@ -232,8 +231,7 @@ public class NumberOption extends TextOption {
* *
* @return The option as a string * @return The option as a string
*/ */
@NotNull public @NotNull String toString() {
public String toString() {
return " " + getName() + "=" + value; return " " + getName() + "=" + value;
} }

View File

@@ -113,5 +113,5 @@ public abstract class Option<T> {
protected final String name; protected final String name;
protected final String abbrev; protected final String abbrev;
@NotNull private final List<OptionChangeListener<T>> listeners = new ArrayList<>(); private final @NotNull List<OptionChangeListener<T>> listeners = new ArrayList<>();
} }

View File

@@ -36,6 +36,7 @@ import com.maddyhome.idea.vim.helper.isBlockCaret
import com.maddyhome.idea.vim.helper.mode import com.maddyhome.idea.vim.helper.mode
import com.maddyhome.idea.vim.helper.subMode import com.maddyhome.idea.vim.helper.subMode
import com.maddyhome.idea.vim.listener.SelectionVimListenerSuppressor import com.maddyhome.idea.vim.listener.SelectionVimListenerSuppressor
import org.jetbrains.annotations.ApiStatus
import org.jetbrains.annotations.Contract import org.jetbrains.annotations.Contract
import java.util.* import java.util.*
import kotlin.math.ceil import kotlin.math.ceil
@@ -82,6 +83,10 @@ object OptionsManager {
val wrapscan = addOption(ToggleOption("wrapscan", "ws", true)) val wrapscan = addOption(ToggleOption("wrapscan", "ws", true))
val visualEnterDelay = addOption(NumberOption("visualdelay", "visualdelay", 100, 0, Int.MAX_VALUE)) val visualEnterDelay = addOption(NumberOption("visualdelay", "visualdelay", 100, 0, Int.MAX_VALUE))
val idearefactormode = addOption(BoundStringOption(IdeaRefactorMode.name, IdeaRefactorMode.name, IdeaRefactorMode.select, IdeaRefactorMode.availableValues)) 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)) val ideastatusbar = addOption(ToggleOption("ideastatusbar", "ideastatusbar", true))
fun isSet(name: String): Boolean { fun isSet(name: String): Boolean {
@@ -353,6 +358,11 @@ object OptionsManager {
abbrevs += option.abbrev to option abbrevs += option.abbrev to option
return option return option
} }
fun removeOption(name: String) {
options.remove(name)
abbrevs.values.find { it.name == name }?.let { option -> abbrevs.remove(option.abbrev) }
}
} }
object KeyModelOptionData { object KeyModelOptionData {
@@ -379,8 +389,11 @@ object SelectModeOptionData {
const val key = "key" const val key = "key"
const val cmd = "cmd" const val cmd = "cmd"
@ApiStatus.ScheduledForRemoval(inVersion = "0.57")
@Deprecated("Please, use `idearefactormode` option") @Deprecated("Please, use `idearefactormode` option")
const val template = "template" const val template = "template"
@ApiStatus.ScheduledForRemoval(inVersion = "0.57")
@Deprecated("Please, use `ideaselection`") @Deprecated("Please, use `ideaselection`")
const val refactoring = "refactoring" const val refactoring = "refactoring"
@@ -520,3 +533,12 @@ object LookupKeysData {
"<C-Q>" "<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)
}

View File

@@ -139,8 +139,7 @@ public class StringOption extends TextOption {
* *
* @return The option as a string for display * @return The option as a string for display
*/ */
@NotNull public @NotNull String toString() {
public String toString() {
return " " + getName() + "=" + value; return " " + getName() + "=" + value;
} }

View File

@@ -86,8 +86,7 @@ public class ToggleOption extends Option<Boolean> {
* *
* @return The option's display value * @return The option's display value
*/ */
@NotNull public @NotNull String toString() {
public String toString() {
StringBuilder res = new StringBuilder(); StringBuilder res = new StringBuilder();
if (!value) { if (!value) {
res.append("no"); res.append("no");

View File

@@ -26,7 +26,7 @@ import java.util.Objects;
public class CharPointer { public class CharPointer {
@NotNull private CharSequence seq; private @NotNull CharSequence seq;
private int pointer; private int pointer;
private boolean readonly; private boolean readonly;
@@ -55,13 +55,11 @@ public class CharPointer {
return pointer; return pointer;
} }
@NotNull public @NotNull CharPointer set(char ch) {
public CharPointer set(char ch) {
return set(ch, 0); return set(ch, 0);
} }
@NotNull public @NotNull CharPointer set(char ch, int offset) {
public CharPointer set(char ch, int offset) {
if (readonly) { if (readonly) {
throw new IllegalStateException("readonly string"); throw new IllegalStateException("readonly string");
} }
@@ -94,32 +92,27 @@ public class CharPointer {
return seq.charAt(pointer + offset); return seq.charAt(pointer + offset);
} }
@NotNull public @NotNull CharPointer inc() {
public CharPointer inc() {
return inc(1); return inc(1);
} }
@NotNull public @NotNull CharPointer inc(int cnt) {
public CharPointer inc(int cnt) {
pointer += cnt; pointer += cnt;
return this; return this;
} }
@NotNull public @NotNull CharPointer dec() {
public CharPointer dec() {
return dec(1); return dec(1);
} }
@NotNull public @NotNull CharPointer dec(int cnt) {
public CharPointer dec(int cnt) {
pointer -= cnt; pointer -= cnt;
return this; return this;
} }
@NotNull public @NotNull CharPointer assign(@NotNull CharPointer ptr) {
public CharPointer assign(@NotNull CharPointer ptr) {
seq = ptr.seq; seq = ptr.seq;
pointer = ptr.pointer; pointer = ptr.pointer;
readonly = ptr.readonly; readonly = ptr.readonly;
@@ -127,13 +120,11 @@ public class CharPointer {
return this; return this;
} }
@NotNull public @NotNull CharPointer ref(int offset) {
public CharPointer ref(int offset) {
return new CharPointer(this, offset); return new CharPointer(this, offset);
} }
@NotNull public @NotNull String substring(int len) {
public String substring(int len) {
if (end()) return ""; if (end()) return "";
int start = pointer; int start = pointer;
@@ -188,8 +179,7 @@ public class CharPointer {
return 0; return 0;
} }
@Nullable public @Nullable CharPointer strchr(char c) {
public CharPointer strchr(char c) {
if (end()) { if (end()) {
return null; return null;
} }
@@ -208,8 +198,7 @@ public class CharPointer {
return null; return null;
} }
@Nullable public @Nullable CharPointer istrchr(char c) {
public CharPointer istrchr(char c) {
if (end()) { if (end()) {
return null; return null;
} }
@@ -247,8 +236,7 @@ public class CharPointer {
return charAt(); return charAt();
} }
@NotNull public @NotNull CharPointer OPERAND() {
public CharPointer OPERAND() {
return ref(3); return ref(3);
} }
@@ -306,8 +294,7 @@ public class CharPointer {
return Math.min(seq.length(), pos); return Math.min(seq.length(), pos);
} }
@NotNull public @NotNull String toString() {
public String toString() {
return substring(strlen()); return substring(strlen());
} }
} }

View File

@@ -29,8 +29,8 @@ import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable; import org.jetbrains.annotations.Nullable;
public class RegExp { public class RegExp {
@Nullable public static reg_extmatch_T re_extmatch_out = null; public static @Nullable 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_in = null;
/* /*
* The opcodes are: * The opcodes are:
@@ -356,8 +356,7 @@ public class RegExp {
* "p" must point to the character after the '['. * "p" must point to the character after the '['.
* The returned pointer is on the matching ']', or the terminating NUL. * The returned pointer is on the matching ']', or the terminating NUL.
*/ */
@NotNull private static @NotNull CharPointer skip_anyof(@NotNull CharPointer p) {
private static CharPointer skip_anyof(@NotNull CharPointer p) {
if (p.charAt() == '^') /* Complement of range. */ { if (p.charAt() == '^') /* Complement of range. */ {
p.inc(); p.inc();
} }
@@ -401,8 +400,7 @@ public class RegExp {
* Take care of characters with a backslash in front of it. * Take care of characters with a backslash in front of it.
* Skip strings inside [ and ]. * Skip strings inside [ and ].
*/ */
@NotNull public static @NotNull CharPointer skip_regexp(@NotNull CharPointer p, char dirc, boolean magic) {
public static CharPointer skip_regexp(@NotNull CharPointer p, char dirc, boolean magic) {
int mymagic; int mymagic;
if (magic) { if (magic) {
@@ -455,8 +453,7 @@ public class RegExp {
* Beware that the optimization-preparation code in here knows about some * Beware that the optimization-preparation code in here knows about some
* of the structure of the compiled regexp. * of the structure of the compiled regexp.
*/ */
@Nullable public @Nullable regprog_T vim_regcomp(@Nullable String expr, int magic) {
public regprog_T vim_regcomp(@Nullable String expr, int magic) {
regprog_T r; regprog_T r;
CharPointer scan; CharPointer scan;
CharPointer longest; 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 * is a trifle forced, but the need to tie the tails of the branches to what
* follows makes it hard to avoid. * follows makes it hard to avoid.
*/ */
@Nullable private @Nullable CharPointer reg(int paren, @NotNull Flags flagp) {
private CharPointer reg(int paren, @NotNull Flags flagp) {
CharPointer ret; CharPointer ret;
CharPointer br; CharPointer br;
CharPointer ender; CharPointer ender;
@@ -708,8 +704,7 @@ public class RegExp {
* *
* Implements the & operator. * Implements the & operator.
*/ */
@Nullable private @Nullable CharPointer regbranch(@NotNull Flags flagp) {
private CharPointer regbranch(@NotNull Flags flagp) {
CharPointer ret; CharPointer ret;
CharPointer chain = null; CharPointer chain = null;
CharPointer latest; CharPointer latest;
@@ -750,8 +745,7 @@ public class RegExp {
* *
* Implements the concatenation operator. * Implements the concatenation operator.
*/ */
@Nullable private @Nullable CharPointer regconcat(@NotNull Flags flagp) {
private CharPointer regconcat(@NotNull Flags flagp) {
CharPointer first = null; CharPointer first = null;
CharPointer chain = null; CharPointer chain = null;
CharPointer latest; CharPointer latest;
@@ -831,8 +825,7 @@ public class RegExp {
* It might seem that this node could be dispensed with entirely, but the * It might seem that this node could be dispensed with entirely, but the
* endmarker role is not redundant. * endmarker role is not redundant.
*/ */
@Nullable private @Nullable CharPointer regpiece(@NotNull Flags flagp) {
private CharPointer regpiece(@NotNull Flags flagp) {
CharPointer ret; CharPointer ret;
int op; int op;
CharPointer next; CharPointer next;
@@ -992,8 +985,7 @@ public class RegExp {
* it can turn them into a single node, which is smaller to store and * 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. * faster to run. Don't do this when one_exactly is set.
*/ */
@Nullable private @Nullable CharPointer regatom(@NotNull Flags flagp) {
private CharPointer regatom(@NotNull Flags flagp) {
CharPointer ret = null; CharPointer ret = null;
Flags flags = new Flags(); Flags flags = new Flags();
boolean cpo_lit = false; /* 'cpoptions' contains 'l' flag */ 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. * Write a long as four bytes at "p" and return pointer to the next char.
*/ */
@NotNull private @NotNull CharPointer re_put_long(@NotNull CharPointer p, int val) {
private CharPointer re_put_long(@NotNull CharPointer p, int val) {
p.set((char)((val >> 24) & 0xff)).inc(); p.set((char)((val >> 24) & 0xff)).inc();
p.set((char)((val >> 16) & 0xff)).inc(); p.set((char)((val >> 16) & 0xff)).inc();
p.set((char)((val >> 8) & 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 * Should end with 'end'. If minval is missing, zero is default, if maxval is
* missing, a very big number is the default. * missing, a very big number is the default.
*/ */
@Nullable private @Nullable MinMax read_limits() {
private MinMax read_limits() {
boolean reverse = false; boolean reverse = false;
CharPointer first_char; CharPointer first_char;
int minval; int minval;
@@ -2022,8 +2012,7 @@ public class RegExp {
/* /*
* Get pointer to the line "lnum", which is relative to "reg_firstlnum". * Get pointer to the line "lnum", which is relative to "reg_firstlnum".
*/ */
@Nullable private @Nullable CharPointer reg_getline(int lnum) {
private CharPointer reg_getline(int lnum) {
/* when looking behind for a match/no-match lnum is negative. But we /* when looking behind for a match/no-match lnum is negative. But we
* can't go before line 1 */ * can't go before line 1 */
if (reg_firstlnum + lnum < 0) { if (reg_firstlnum + lnum < 0) {
@@ -2208,8 +2197,7 @@ public class RegExp {
/* /*
* Create a new extmatch and mark it as referenced once. * Create a new extmatch and mark it as referenced once.
*/ */
@NotNull private @NotNull reg_extmatch_T make_extmatch() {
private reg_extmatch_T make_extmatch() {
return new reg_extmatch_T(); return new reg_extmatch_T();
} }
@@ -3555,8 +3543,7 @@ public class RegExp {
/* /*
* regnext - dig the "next" pointer out of a node * regnext - dig the "next" pointer out of a node
*/ */
@Nullable private @Nullable CharPointer regnext(@NotNull CharPointer p) {
private CharPointer regnext(@NotNull CharPointer p) {
int offset; int offset;
offset = p.NEXT(); offset = p.NEXT();
@@ -3739,8 +3726,7 @@ public class RegExp {
/* /*
* cstrchr: This function is used a lot for simple searches, keep it fast! * cstrchr: This function is used a lot for simple searches, keep it fast!
*/ */
@Nullable private @Nullable CharPointer cstrchr(@NotNull CharPointer s, char c) {
private CharPointer cstrchr(@NotNull CharPointer s, char c) {
if (!ireg_ic) { if (!ireg_ic) {
return s.strchr(c); return s.strchr(c);
} }
@@ -3882,8 +3868,7 @@ public class RegExp {
* <p/> * <p/>
* Returns the size of the replacement, including terminating '\u0000'. * Returns the size of the replacement, including terminating '\u0000'.
*/ */
@Nullable public @Nullable String vim_regsub(regmatch_T rmp, CharPointer source, int magic, boolean backslash) {
public String vim_regsub(regmatch_T rmp, CharPointer source, int magic, boolean backslash) {
reg_match = rmp; reg_match = rmp;
reg_mmatch = null; reg_mmatch = null;
reg_maxline = 0; reg_maxline = 0;
@@ -3891,8 +3876,7 @@ public class RegExp {
return vim_regsub_both(source, magic, backslash); return vim_regsub_both(source, magic, backslash);
} }
@Nullable public @Nullable String vim_regsub_multi(regmmatch_T rmp, int lnum, CharPointer source, int magic, boolean backslash) {
public String vim_regsub_multi(regmmatch_T rmp, int lnum, CharPointer source, int magic, boolean backslash) {
reg_match = null; reg_match = null;
reg_mmatch = rmp; reg_mmatch = rmp;
//reg_buf = curbuf; /* always works on the current buffer! */ //reg_buf = curbuf; /* always works on the current buffer! */
@@ -3924,8 +3908,7 @@ public class RegExp {
return mode; return mode;
} }
@Nullable private @Nullable String vim_regsub_both(@Nullable CharPointer source, int magic, boolean backslash) {
private String vim_regsub_both(@Nullable CharPointer source, int magic, boolean backslash) {
CharPointer src; CharPointer src;
StringBuffer dst = new StringBuffer(); StringBuffer dst = new StringBuffer();
CharPointer s; CharPointer s;
@@ -4211,8 +4194,7 @@ public class RegExp {
/* /*
* regdump - dump a regexp onto stdout in vaguely comprehensible form * regdump - dump a regexp onto stdout in vaguely comprehensible form
*/ */
@NotNull private @NotNull String regdump(String pattern, @NotNull regprog_T r) {
private String regdump(String pattern, @NotNull regprog_T r) {
CharPointer start; CharPointer start;
CharPointer s; CharPointer s;
int op = EXACTLY; /* Arbitrary non-END op. */ int op = EXACTLY; /* Arbitrary non-END op. */
@@ -4290,8 +4272,7 @@ public class RegExp {
/* /*
* regprop - printable representation of opcode * regprop - printable representation of opcode
*/ */
@NotNull private @NotNull String regprop(@NotNull CharPointer op) {
private String regprop(@NotNull CharPointer op) {
String p; String p;
StringBuffer buf = new StringBuffer(); StringBuffer buf = new StringBuffer();
@@ -4727,15 +4708,15 @@ public class RegExp {
} }
} }
@Nullable public regprog_T regprog; public @Nullable regprog_T regprog;
@NotNull public lpos_T[] startpos = new lpos_T[NSUBEXP]; public @NotNull lpos_T[] startpos = new lpos_T[NSUBEXP];
@NotNull public lpos_T[] endpos = new lpos_T[NSUBEXP]; public @NotNull lpos_T[] endpos = new lpos_T[NSUBEXP];
public boolean rmm_ic; public boolean rmm_ic;
} }
private int reg_do_extmatch = 0; 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 CharPointer regparse; /* Input-scan pointer. */
private int prevchr_len; /* byte length of previous char */ private int prevchr_len; /* byte length of previous char */
@@ -4744,11 +4725,11 @@ public class RegExp {
private int regnzpar; /* \z() count. */ private int regnzpar; /* \z() count. */
private char re_has_z; /* \z item detected */ private char re_has_z; /* \z item detected */
private CharPointer regcode; /* Code-emit pointer */ 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 */ private int regflags; /* RF_ flags for prog */
@NotNull private int[] brace_min = new int[10]; /* Minimums for complex brace repeats */ private @NotNull int[] brace_min = new int[10]; /* Minimums for complex brace repeats */
@NotNull private int[] brace_max = new int[10]; /* Maximums for complex brace repeats */ private @NotNull 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_count = new int[10]; /* Current counts for complex brace repeats */
private boolean had_eol; /* true when EOL found by vim_regcomp() */ private boolean had_eol; /* true when EOL found by vim_regcomp() */
private boolean one_exactly = false; /* only do one char for EXACTLY */ 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: */ /* The current match-position is remembered with these variables: */
private int reglnum; /* line number, relative to first line */ 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 CharPointer reginput; /* current input, points into "regline" */
private boolean need_clear_subexpr; /* subexpressions still need to be 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 * 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. * 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; //private int reg_tofreelen;
/* /*
@@ -4820,12 +4801,12 @@ public class RegExp {
* reg_firstlnum <invalid> first line in which to search * reg_firstlnum <invalid> first line in which to search
* reg_maxline 0 last line nr * reg_maxline 0 last line nr
*/ */
@Nullable private regmatch_T reg_match; private @Nullable regmatch_T reg_match;
@Nullable private regmmatch_T reg_mmatch; private @Nullable regmmatch_T reg_mmatch;
@NotNull private CharPointer[] reg_startp = new CharPointer[NSUBEXP]; private @NotNull CharPointer[] reg_startp = new CharPointer[NSUBEXP];
@NotNull private CharPointer[] reg_endp = new CharPointer[NSUBEXP]; private @NotNull CharPointer[] reg_endp = new CharPointer[NSUBEXP];
@NotNull private lpos_T[] reg_startpos = new lpos_T[NSUBEXP]; private @NotNull lpos_T[] reg_startpos = new lpos_T[NSUBEXP];
@NotNull private lpos_T[] reg_endpos = new lpos_T[NSUBEXP]; private @NotNull lpos_T[] reg_endpos = new lpos_T[NSUBEXP];
//static win_T *reg_win; //static win_T *reg_win;
private Editor reg_buf; private Editor reg_buf;
private int reg_firstlnum; private int reg_firstlnum;
@@ -4833,10 +4814,10 @@ public class RegExp {
private regsave_T behind_pos; private regsave_T behind_pos;
@NotNull private CharPointer[] reg_startzp = new CharPointer[NSUBEXP]; /* Workspace to mark beginning */ private @NotNull CharPointer[] reg_startzp = new CharPointer[NSUBEXP]; /* Workspace to mark beginning */
@NotNull private CharPointer[] reg_endzp = new CharPointer[NSUBEXP]; /* and end of \z(...\) matches */ private @NotNull CharPointer[] reg_endzp = new CharPointer[NSUBEXP]; /* and end of \z(...\) matches */
@NotNull private lpos_T[] reg_startzpos = new lpos_T[NSUBEXP]; /* idem, beginning pos */ private @NotNull 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 lpos_T[] reg_endzpos = new lpos_T[NSUBEXP]; /* idem, end pos */
private boolean got_int = false; private boolean got_int = false;

View File

@@ -45,8 +45,7 @@ public class ClipboardHandler {
* *
* @return The clipboard string or null if data isn't plain text * @return The clipboard string or null if data isn't plain text
*/ */
@NotNull public static @NotNull Pair<String, List<TextBlockTransferableData>> getClipboardTextAndTransferableData() {
public static Pair<String, List<TextBlockTransferableData>> getClipboardTextAndTransferableData() {
String res = null; String res = null;
List<TextBlockTransferableData> transferableData = new ArrayList<>(); List<TextBlockTransferableData> transferableData = new ArrayList<>();
try { try {

View File

@@ -50,8 +50,7 @@ public class ExEditorKit extends DefaultEditorKit {
* @return the type * @return the type
*/ */
@Override @Override
@NotNull public @NotNull String getContentType() {
public String getContentType() {
return "text/ideavim"; return "text/ideavim";
} }
@@ -77,13 +76,11 @@ public class ExEditorKit extends DefaultEditorKit {
* @return the model * @return the model
*/ */
@Override @Override
@NotNull public @NotNull Document createDefaultDocument() {
public Document createDefaultDocument() {
return new ExDocument(); return new ExDocument();
} }
@Nullable private static @Nullable KeyStroke convert(@NotNull ActionEvent event) {
private static KeyStroke convert(@NotNull ActionEvent event) {
String cmd = event.getActionCommand(); String cmd = event.getActionCommand();
int mods = event.getModifiers(); int mods = event.getModifiers();
if (cmd != null && cmd.length() > 0) { if (cmd != null && cmd.length() > 0) {
@@ -114,7 +111,7 @@ public class ExEditorKit extends DefaultEditorKit {
static final String StartDigraph = "start-digraph"; static final String StartDigraph = "start-digraph";
static final String StartLiteral = "start-literal"; static final String StartLiteral = "start-literal";
@NotNull private final Action[] exActions = new Action[]{ private final @NotNull Action[] exActions = new Action[]{
new CancelEntryAction(), new CancelEntryAction(),
new CompleteEntryAction(), new CompleteEntryAction(),
new EscapeCharAction(), new EscapeCharAction(),
@@ -217,7 +214,7 @@ public class ExEditorKit extends DefaultEditorKit {
WAIT_REGISTER, WAIT_REGISTER,
} }
@NotNull private State state = State.SKIP_CTRL_R; private @NotNull State state = State.SKIP_CTRL_R;
InsertRegisterAction() { InsertRegisterAction() {
super(InsertRegister); super(InsertRegister);
@@ -308,7 +305,7 @@ public class ExEditorKit extends DefaultEditorKit {
} }
@SuppressWarnings("BooleanMethodIsAlwaysInverted") @SuppressWarnings("BooleanMethodIsAlwaysInverted")
private static abstract class DeleteCharAction extends TextAction { private abstract static class DeleteCharAction extends TextAction {
DeleteCharAction(String name) { DeleteCharAction(String name) {
super(name); super(name);
@@ -488,9 +485,8 @@ public class ExEditorKit extends DefaultEditorKit {
} }
} }
private static abstract class StartDigraphLiteralActionBase extends TextAction implements MultiStepAction { private abstract static class StartDigraphLiteralActionBase extends TextAction implements MultiStepAction {
@Nullable private @Nullable DigraphSequence digraphSequence;
private DigraphSequence digraphSequence;
public StartDigraphLiteralActionBase(String name) { public StartDigraphLiteralActionBase(String name) {
super(name); super(name);

View File

@@ -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 @Override
protected void textChanged(@NotNull DocumentEvent e) { protected void textChanged(@NotNull DocumentEvent e) {
final Editor editor = entry.getEditor(); final Editor editor = entry.getEditor();
@@ -284,8 +284,7 @@ public class ExEntryPanel extends JPanel implements LafManagerListener {
} }
@Contract("null -> null") @Contract("null -> null")
@Nullable private @Nullable ExCommand getIncsearchCommand(@Nullable String commandText) {
private ExCommand getIncsearchCommand(@Nullable String commandText) {
if (commandText == null) return null; if (commandText == null) return null;
try { try {
final ExCommand exCommand = CommandParser.getInstance().parse(commandText); final ExCommand exCommand = CommandParser.getInstance().parse(commandText);
@@ -335,13 +334,11 @@ public class ExEntryPanel extends JPanel implements LafManagerListener {
* *
* @return The user entered text * @return The user entered text
*/ */
@NotNull public @NotNull String getText() {
public String getText() {
return entry.getActualText(); return entry.getActualText();
} }
@NotNull public @NotNull ExTextField getEntry() {
public ExTextField getEntry() {
return entry; return entry;
} }
@@ -421,9 +418,9 @@ public class ExEntryPanel extends JPanel implements LafManagerListener {
private int count; private int count;
// UI stuff // UI stuff
@Nullable private JComponent parent; private @Nullable JComponent parent;
@NotNull private final JLabel label; private final @NotNull JLabel label;
@NotNull private final ExTextField entry; private final @NotNull ExTextField entry;
private JComponent oldGlass; private JComponent oldGlass;
private LayoutManager oldLayout; private LayoutManager oldLayout;
private boolean wasOpaque; private boolean wasOpaque;
@@ -433,7 +430,7 @@ public class ExEntryPanel extends JPanel implements LafManagerListener {
private int horizontalOffset; private int horizontalOffset;
private int caretOffset; private int caretOffset;
@NotNull private final ComponentListener resizePanelListener = new ComponentAdapter() { private final @NotNull ComponentListener resizePanelListener = new ComponentAdapter() {
@Override @Override
public void componentResized(ComponentEvent e) { public void componentResized(ComponentEvent e) {
positionPanel(); positionPanel();

View File

@@ -46,18 +46,18 @@ import java.util.List;
* This panel displays text in a <code>more</code> like window. * This panel displays text in a <code>more</code> like window.
*/ */
public class ExOutputPanel extends JPanel implements LafManagerListener { 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"); private final @NotNull JLabel myLabel = new JLabel("more");
@NotNull private final JTextArea myText = new JTextArea(); private final @NotNull JTextArea myText = new JTextArea();
@NotNull private final JScrollPane myScrollPane = private final @NotNull JScrollPane myScrollPane =
new JBScrollPane(myText, JScrollPane.VERTICAL_SCROLLBAR_AS_NEEDED, JScrollPane.HORIZONTAL_SCROLLBAR_NEVER); 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 boolean myAtEnd = false;
private int myLineHeight = 0; private int myLineHeight = 0;
@Nullable private JComponent myOldGlass = null; private @Nullable JComponent myOldGlass = null;
@Nullable private LayoutManager myOldLayout = null; private @Nullable LayoutManager myOldLayout = null;
private boolean myWasOpaque = false; private boolean myWasOpaque = false;
private boolean myActive = false; private boolean myActive = false;
@@ -95,8 +95,7 @@ public class ExOutputPanel extends JPanel implements LafManagerListener {
updateUI(); updateUI();
} }
@NotNull public static @NotNull ExOutputPanel getInstance(@NotNull Editor editor) {
public static ExOutputPanel getInstance(@NotNull Editor editor) {
ExOutputPanel panel = UserDataManager.getVimMorePanel(editor); ExOutputPanel panel = UserDataManager.getVimMorePanel(editor);
if (panel == null) { if (panel == null) {
panel = new ExOutputPanel(editor); panel = new ExOutputPanel(editor);
@@ -298,7 +297,7 @@ public class ExOutputPanel extends JPanel implements LafManagerListener {
close(null); close(null);
} }
private void close(@Nullable final KeyEvent e) { private void close(final @Nullable KeyEvent e) {
ApplicationManager.getApplication().invokeLater(() -> { ApplicationManager.getApplication().invokeLater(() -> {
deactivate(true); deactivate(true);

View File

@@ -286,8 +286,7 @@ public class ExTextField extends JTextField {
* @return the default model implementation * @return the default model implementation
*/ */
@Override @Override
@NotNull protected @NotNull Document createDefaultModel() {
protected Document createDefaultModel() {
return new ExDocument(); return new ExDocument();
} }
@@ -547,7 +546,7 @@ public class ExTextField extends JTextField {
private String actualText; private String actualText;
private List<HistoryGroup.HistoryEntry> history; private List<HistoryGroup.HistoryEntry> history;
private int histIndex = 0; private int histIndex = 0;
@Nullable private ExEditorKit.MultiStepAction currentAction; private @Nullable ExEditorKit.MultiStepAction currentAction;
private char currentActionPromptCharacter; private char currentActionPromptCharacter;
private int currentActionPromptCharacterOffset = -1; private int currentActionPromptCharacterOffset = -1;

View File

@@ -30,9 +30,9 @@ import java.awt.event.KeyListener;
* @author vlan * @author vlan
*/ */
public class ModalEntryDialog extends JDialog { public class ModalEntryDialog extends JDialog {
@NotNull private final JTextField myEntry; private final @NotNull JTextField myEntry;
@NotNull private final JLabel myLabel; private final @NotNull JLabel myLabel;
@NotNull private final JComponent myParent; private final @NotNull JComponent myParent;
public ModalEntryDialog(@NotNull Editor editor, @NotNull String prompt) { public ModalEntryDialog(@NotNull Editor editor, @NotNull String prompt) {
super((Frame)null, true); super((Frame)null, true);
@@ -89,8 +89,7 @@ public class ModalEntryDialog extends JDialog {
myEntry.addKeyListener(listener); myEntry.addKeyListener(listener);
} }
@NotNull public @NotNull String getText() {
public String getText() {
return myEntry.getText(); return myEntry.getText();
} }
} }

View File

@@ -47,25 +47,22 @@ import java.util.*;
* @author vlan * @author vlan
*/ */
public class VimEmulationConfigurable implements Configurable { public class VimEmulationConfigurable implements Configurable {
@NotNull private final VimShortcutConflictsTable.Model myConflictsTableModel = new VimShortcutConflictsTable.Model(); private final @NotNull VimShortcutConflictsTable.Model myConflictsTableModel = new VimShortcutConflictsTable.Model();
@NotNull private final VimSettingsPanel myPanel = new VimSettingsPanel(myConflictsTableModel); private final @NotNull VimSettingsPanel myPanel = new VimSettingsPanel(myConflictsTableModel);
@NotNull
@Nls @Nls
@Override @Override
public String getDisplayName() { public @NotNull String getDisplayName() {
return "Vim Emulation"; return "Vim Emulation";
} }
@Nullable
@Override @Override
public String getHelpTopic() { public @Nullable String getHelpTopic() {
return null; return null;
} }
@Nullable
@Override @Override
public JComponent createComponent() { public @Nullable JComponent createComponent() {
return myPanel; return myPanel;
} }
@@ -89,7 +86,7 @@ public class VimEmulationConfigurable implements Configurable {
} }
private static final class VimSettingsPanel extends JPanel { private static final class VimSettingsPanel extends JPanel {
@NotNull private final VimShortcutConflictsTable myShortcutConflictsTable; private final @NotNull VimShortcutConflictsTable myShortcutConflictsTable;
public VimSettingsPanel(@NotNull VimShortcutConflictsTable.Model model) { public VimSettingsPanel(@NotNull VimShortcutConflictsTable.Model model) {
myShortcutConflictsTable = new VimShortcutConflictsTable(model); myShortcutConflictsTable = new VimShortcutConflictsTable(model);
@@ -116,20 +113,17 @@ public class VimEmulationConfigurable implements Configurable {
ownerColumn.setCellEditor(renderer); ownerColumn.setCellEditor(renderer);
} }
@NotNull
@Override @Override
public Dimension getMinimumSize() { public @NotNull Dimension getMinimumSize() {
return calcSize(super.getMinimumSize()); return calcSize(super.getMinimumSize());
} }
@NotNull
@Override @Override
public Dimension getPreferredSize() { public @NotNull Dimension getPreferredSize() {
return calcSize(super.getPreferredSize()); return calcSize(super.getPreferredSize());
} }
@NotNull private @NotNull Dimension calcSize(@NotNull Dimension dimension) {
private Dimension calcSize(@NotNull Dimension dimension) {
final Container container = getParent(); final Container container = getParent();
if (container != null) { if (container != null) {
final Dimension size = container.getSize(); final Dimension size = container.getSize();
@@ -138,8 +132,7 @@ public class VimEmulationConfigurable implements Configurable {
return dimension; return dimension;
} }
@NotNull private @NotNull TableColumn getTableColumn(@NotNull Column column) {
private TableColumn getTableColumn(@NotNull Column column) {
return getColumnModel().getColumn(column.getIndex()); return getColumnModel().getColumn(column.getIndex());
} }
@@ -167,7 +160,7 @@ public class VimEmulationConfigurable implements Configurable {
IDE_ACTION(1, "IDE Action"), IDE_ACTION(1, "IDE Action"),
OWNER(2, "Handler"); OWNER(2, "Handler");
@NotNull private static final Map<Integer, Column> ourMembers = new HashMap<>(); private static final @NotNull Map<Integer, Column> ourMembers = new HashMap<>();
static { static {
for (Column column : values()) { for (Column column : values()) {
@@ -176,15 +169,14 @@ public class VimEmulationConfigurable implements Configurable {
} }
private final int myIndex; private final int myIndex;
@NotNull private final String myTitle; private final @NotNull String myTitle;
Column(int index, @NotNull String title) { Column(int index, @NotNull String title) {
myIndex = index; myIndex = index;
myTitle = title; myTitle = title;
} }
@Nullable public static @Nullable Column fromIndex(int index) {
public static Column fromIndex(int index) {
return ourMembers.get(index); return ourMembers.get(index);
} }
@@ -192,16 +184,15 @@ public class VimEmulationConfigurable implements Configurable {
return myIndex; return myIndex;
} }
@NotNull public @NotNull String getTitle() {
public String getTitle() {
return myTitle; return myTitle;
} }
} }
private static final class Row implements Comparable<Row> { private static final class Row implements Comparable<Row> {
@NotNull private final KeyStroke myKeyStroke; private final @NotNull KeyStroke myKeyStroke;
@NotNull private final AnAction myAction; private final @NotNull AnAction myAction;
@NotNull private ShortcutOwner myOwner; private @NotNull ShortcutOwner myOwner;
private Row(@NotNull KeyStroke keyStroke, @NotNull AnAction action, @NotNull ShortcutOwner owner) { private Row(@NotNull KeyStroke keyStroke, @NotNull AnAction action, @NotNull ShortcutOwner owner) {
myKeyStroke = keyStroke; myKeyStroke = keyStroke;
@@ -209,18 +200,15 @@ public class VimEmulationConfigurable implements Configurable {
myOwner = owner; myOwner = owner;
} }
@NotNull public @NotNull KeyStroke getKeyStroke() {
public KeyStroke getKeyStroke() {
return myKeyStroke; return myKeyStroke;
} }
@NotNull public @NotNull AnAction getAction() {
public AnAction getAction() {
return myAction; return myAction;
} }
@NotNull public @NotNull ShortcutOwner getOwner() {
public ShortcutOwner getOwner() {
return myOwner; return myOwner;
} }
@@ -237,7 +225,7 @@ public class VimEmulationConfigurable implements Configurable {
} }
private static final class Model extends AbstractTableModel { 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() { public Model() {
reset(); reset();
@@ -253,9 +241,8 @@ public class VimEmulationConfigurable implements Configurable {
return Column.values().length; return Column.values().length;
} }
@Nullable
@Override @Override
public Object getValueAt(int rowIndex, int columnIndex) { public @Nullable Object getValueAt(int rowIndex, int columnIndex) {
final Column column = Column.fromIndex(columnIndex); final Column column = Column.fromIndex(columnIndex);
if (column != null && rowIndex >= 0 && rowIndex < myRows.size()) { if (column != null && rowIndex >= 0 && rowIndex < myRows.size()) {
final Row row = myRows.get(rowIndex); final Row row = myRows.get(rowIndex);
@@ -285,9 +272,8 @@ public class VimEmulationConfigurable implements Configurable {
return Column.fromIndex(columnIndex) == Column.OWNER; return Column.fromIndex(columnIndex) == Column.OWNER;
} }
@Nullable
@Override @Override
public String getColumnName(int index) { public @Nullable String getColumnName(int index) {
final Column column = Column.fromIndex(index); final Column column = Column.fromIndex(index);
return column != null ? column.getTitle() : null; return column != null ? column.getTitle() : null;
} }
@@ -312,8 +298,7 @@ public class VimEmulationConfigurable implements Configurable {
Collections.sort(myRows); Collections.sort(myRows);
} }
@NotNull private @NotNull Map<KeyStroke, ShortcutOwner> getCurrentData() {
private Map<KeyStroke, ShortcutOwner> getCurrentData() {
final Map<KeyStroke, ShortcutOwner> result = new HashMap<>(); final Map<KeyStroke, ShortcutOwner> result = new HashMap<>();
for (Row row : myRows) { for (Row row : myRows) {
result.put(row.getKeyStroke(), row.getOwner()); result.put(row.getKeyStroke(), row.getOwner());

View File

@@ -21,7 +21,13 @@ package org.jetbrains.plugins.ideavim
import com.maddyhome.idea.vim.RegisterActions.VIM_ACTIONS_EP import com.maddyhome.idea.vim.RegisterActions.VIM_ACTIONS_EP
import com.maddyhome.idea.vim.VimPlugin import com.maddyhome.idea.vim.VimPlugin
import com.maddyhome.idea.vim.command.CommandState import com.maddyhome.idea.vim.command.CommandState
import com.maddyhome.idea.vim.command.MappingMode
import com.maddyhome.idea.vim.handler.ActionBeanClass
import com.maddyhome.idea.vim.helper.StringHelper 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() { class RegisterActionsTest : VimTestCase() {
fun `test simple action`() { fun `test simple action`() {
@@ -65,10 +71,24 @@ class RegisterActionsTest : VimTestCase() {
val keys = StringHelper.parseKeys("l") val keys = StringHelper.parseKeys("l")
val before = "I ${c}found it in a legendary land" val before = "I ${c}found it in a legendary land"
val after = "I f${c}ound 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) { 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") @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?
} }
} }

View File

@@ -136,6 +136,12 @@ public abstract class VimTestCase extends UsefulTestCase {
return myFixture.getEditor(); return myFixture.getEditor();
} }
@NotNull
protected Editor configureByFileName(@NotNull String fileName) {
myFixture.configureByText(fileName, "\n");
return myFixture.getEditor();
}
@NotNull @NotNull
protected Editor configureByJavaText(@NotNull String content) { protected Editor configureByJavaText(@NotNull String content) {
myFixture.configureByText(JavaFileType.INSTANCE, content); myFixture.configureByText(JavaFileType.INSTANCE, content);

View File

@@ -292,6 +292,27 @@ public class MotionActionTest extends VimTestCase {
myFixture.checkResult("a{<caret>}b}"); 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>| // |d| |v_i>|
public void testDeleteInnerAngleBracketBlock() { public void testDeleteInnerAngleBracketBlock() {
typeTextInFile(parseKeys("di>"), typeTextInFile(parseKeys("di>"),
@@ -348,6 +369,31 @@ public class MotionActionTest extends VimTestCase {
myFixture.checkResult("foo = [\"\", \"two\", \"three\"];\n"); 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"| // VIM-132 |v_i"|
public void testInnerDoubleQuotedStringSelection() { public void testInnerDoubleQuotedStringSelection() {
typeTextInFile(parseKeys("vi\""), typeTextInFile(parseKeys("vi\""),

View File

@@ -102,6 +102,22 @@ class MotionInnerBlockParenActionTest : VimTestCase() {
myFixture.checkResult("foo()\n") 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| // VIM-326 |d| |v_ib|
fun testDeleteInnerBlockCaretBeforeString() { fun testDeleteInnerBlockCaretBeforeString() {
typeTextInFile(parseKeys("di)"), typeTextInFile(parseKeys("di)"),

View File

@@ -20,8 +20,12 @@ package org.jetbrains.plugins.ideavim.ex
import com.maddyhome.idea.vim.VimPlugin import com.maddyhome.idea.vim.VimPlugin
import com.maddyhome.idea.vim.command.CommandState import com.maddyhome.idea.vim.command.CommandState
import com.maddyhome.idea.vim.ex.CommandParser
import com.maddyhome.idea.vim.ex.CommandParser.EX_COMMAND_EP 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.commands
import com.maddyhome.idea.vim.ex.ranges.Ranges
import junit.framework.TestCase import junit.framework.TestCase
import org.jetbrains.plugins.ideavim.VimTestCase import org.jetbrains.plugins.ideavim.VimTestCase
@@ -91,10 +95,21 @@ class CommandParserTest : VimTestCase() {
val keys = commandToKeys(">>") val keys = commandToKeys(">>")
val before = "I ${c}found it in a legendary land" val before = "I ${c}found it in a legendary land"
val after = " ${c}I 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) { 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") @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", "")))
} }
} }

View File

@@ -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)
}
}

View File

@@ -20,13 +20,14 @@ package org.jetbrains.plugins.ideavim.extension
import com.intellij.openapi.actionSystem.DataContext import com.intellij.openapi.actionSystem.DataContext
import com.intellij.openapi.editor.Editor import com.intellij.openapi.editor.Editor
import com.intellij.testFramework.UsefulTestCase
import com.maddyhome.idea.vim.VimPlugin import com.maddyhome.idea.vim.VimPlugin
import com.maddyhome.idea.vim.command.CommandState import com.maddyhome.idea.vim.command.CommandState
import com.maddyhome.idea.vim.command.MappingMode 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.putExtensionHandlerMapping
import com.maddyhome.idea.vim.extension.VimExtensionFacade.putKeyMapping import com.maddyhome.idea.vim.extension.VimExtensionFacade.putKeyMapping
import com.maddyhome.idea.vim.extension.VimExtensionHandler 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.MotionGroup
import com.maddyhome.idea.vim.helper.StringHelper.parseKeys import com.maddyhome.idea.vim.helper.StringHelper.parseKeys
import com.maddyhome.idea.vim.helper.isEndAllowed import com.maddyhome.idea.vim.helper.isEndAllowed
@@ -36,11 +37,14 @@ import org.jetbrains.plugins.ideavim.VimTestCase
class OpMappingTest : VimTestCase() { class OpMappingTest : VimTestCase() {
private var initialized = false private var initialized = false
private val extension = TestExtension()
override fun setUp() { override fun setUp() {
super.setUp() super.setUp()
if (!initialized) { if (!initialized) {
initialized = true initialized = true
TestExtension().init() VimExtension.EP_NAME.getPoint(null).registerExtension(extension)
enableExtensions("TestExtension")
} }
} }
@@ -87,21 +91,70 @@ class OpMappingTest : VimTestCase() {
CommandState.Mode.COMMAND, CommandState.Mode.COMMAND,
CommandState.SubMode.NONE) 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")
}
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 : VimNonDisposableExtension() { private class TestExtension : VimExtension {
override fun getName(): String = "TestExtension" override fun getName(): String = "TestExtension"
override fun initOnce() { override fun init() {
putExtensionHandlerMapping(MappingMode.O, parseKeys("<Plug>TestExtensionEmulateInclusive"), MoveEmulateInclusive(), false) putExtensionHandlerMapping(MappingMode.O, parseKeys("<Plug>TestExtensionEmulateInclusive"), owner, MoveEmulateInclusive(), false)
putExtensionHandlerMapping(MappingMode.O, parseKeys("<Plug>TestExtensionBackwardsCharacter"), MoveBackwards(), false) putExtensionHandlerMapping(MappingMode.O, parseKeys("<Plug>TestExtensionBackwardsCharacter"), owner, MoveBackwards(), false)
putExtensionHandlerMapping(MappingMode.O, parseKeys("<Plug>TestExtensionCharacter"), Move(), false) putExtensionHandlerMapping(MappingMode.O, parseKeys("<Plug>TestExtensionCharacter"), owner, Move(), false)
putExtensionHandlerMapping(MappingMode.O, parseKeys("<Plug>TestExtensionLinewise"), MoveLinewise(), 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("U"), owner, parseKeys("<Plug>TestExtensionEmulateInclusive"), true)
putKeyMapping(MappingMode.O, parseKeys("P"), parseKeys("<Plug>TestExtensionBackwardsCharacter"), true) putKeyMapping(MappingMode.O, parseKeys("P"), owner, parseKeys("<Plug>TestExtensionBackwardsCharacter"), true)
putKeyMapping(MappingMode.O, parseKeys("I"), parseKeys("<Plug>TestExtensionCharacter"), true) putKeyMapping(MappingMode.O, parseKeys("I"), owner, parseKeys("<Plug>TestExtensionCharacter"), true)
putKeyMapping(MappingMode.O, parseKeys("O"), parseKeys("<Plug>TestExtensionLinewise"), 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 { private class MoveEmulateInclusive : VimExtensionHandler {
@@ -133,4 +186,12 @@ private class TestExtension : VimNonDisposableExtension() {
MotionGroup.moveCaret(editor, caret, newOffset) 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)
}
}
} }

View File

@@ -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()
}

View File

@@ -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);
}
}

View 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")
}
}

View File

@@ -1205,7 +1205,7 @@ class SearchGroupTest : VimTestCase() {
val project = myFixture.project val project = myFixture.project
val searchGroup = VimPlugin.getSearch() val searchGroup = VimPlugin.getSearch()
val ref = Ref.create(-1) 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) val n = searchGroup.search(editor, pattern, 1, EnumSet.of(CommandFlags.FLAG_SEARCH_FWD), false)
ref.set(n) ref.set(n)
}, null, null) }, null, null)

View File

@@ -19,6 +19,7 @@
package org.jetbrains.plugins.ideavim.helper; package org.jetbrains.plugins.ideavim.helper;
import com.maddyhome.idea.vim.helper.SearchHelper; import com.maddyhome.idea.vim.helper.SearchHelper;
import com.maddyhome.idea.vim.helper.SearchHelperKtKt;
import org.jetbrains.plugins.ideavim.VimTestCase; import org.jetbrains.plugins.ideavim.VimTestCase;
import static com.maddyhome.idea.vim.helper.StringHelper.parseKeys; 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)"); typeTextInFile(parseKeys("v", "a("), "((int) nu<caret>m)");
myFixture.checkResult("<selection>((int) num)</selection>"); 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);
}
} }

View File

@@ -16,19 +16,17 @@
* along with this program. If not, see <https://www.gnu.org/licenses/>. * along with this program. If not, see <https://www.gnu.org/licenses/>.
*/ */
package com.maddyhome.idea.vim package org.jetbrains.plugins.ideavim.key
import com.intellij.openapi.components.ProjectComponent import com.maddyhome.idea.vim.key.MappingOwner
import com.intellij.openapi.project.Project import junit.framework.TestCase
import com.maddyhome.idea.vim.listener.VimListenerManager import org.jetbrains.plugins.ideavim.VimTestCase
/** class MappingOwnerTest : VimTestCase() {
* @author Alex Plate fun `test get two plugin owners`() {
*/ val pluginName = "MyPlugin"
class VimProjectComponent(private val project: Project) : ProjectComponent { val firstOwner = MappingOwner.Plugin.get(pluginName)
override fun projectOpened() { val secondOwner = MappingOwner.Plugin.get(pluginName)
if (!VimPlugin.isEnabled()) return TestCase.assertSame(firstOwner, secondOwner)
// Project listeners are self-disposable, so there is no need to unregister them on project close
VimListenerManager.ProjectListeners.add(project)
} }
} }