1
0
mirror of https://github.com/chylex/IntelliJ-IdeaVim.git synced 2025-08-17 16:31:45 +02:00

Compare commits

...

119 Commits

Author SHA1 Message Date
Alex Plate
0a4683d908 Clean up repository for the release 2020-04-09 11:06:21 +03:00
Alex Plate
4c280b0193 Run manual tests 2020-04-09 10:51:32 +03:00
Alex Plate
e88a3deafd Fix replace with clipboard register 2020-04-09 10:25:42 +03:00
Alex Plate
bd172b3300 Run manual tests 2020-04-07 11:35:58 +03:00
Alex Plate
95c7a13cb5 Turning plugin on should be performed after commands registration 2020-04-07 11:27:24 +03:00
Alex Plate
b1ddf03385 Add notes to changelog about Keep a Changelog and Semantic Versioning. 2020-04-06 10:25:50 +03:00
Alex Plate
a83c326736 Add logging for activating ex panel 2020-04-03 08:23:55 +03:00
Alex Plate
b1acb56247 Fix exception for ciw on last char in file 2020-03-28 15:57:54 +03:00
Alex Plate
caa4731a13 Fix exception when using text objects on empty files 2020-03-28 15:39:06 +03:00
Alex Plate
5b0ece7a91 ReplaceWithRegister for clipboard registers 2020-03-23 10:34:53 +03:00
Alex Plate
c8d64e0a06 Update changelog 2020-03-20 11:44:27 +03:00
Alex Plate
1a3dea0de6 Update slack notification format 2020-03-20 11:42:40 +03:00
Alex Plate
17b642280e Update gradle wrapper properties 2020-03-20 11:42:14 +03:00
Alex Plate
1c1717b78b Add kk to the contributions list 2020-03-20 10:38:30 +03:00
Alex Plate
4bbbdf8108 Fix ReplaceWithRegister to the empty line 2020-03-20 10:35:08 +03:00
Alex Pláte
04a193309d Merge pull request #228 from kevin70/master
fixed #VIM-570
2020-03-20 10:34:58 +03:00
Alex Plate
f106ffa176 Support ReplaceWithRegister plugin emulation 2020-03-19 11:25:30 +03:00
Alex Plate
8d5d099542 Update icon on ideastatusicon option change 2020-03-19 09:13:49 +03:00
kk
4849992ca9 fixed #VIM-570 2020-03-18 18:49:55 +08:00
Alex Plate
623105650e PutTextAction refactor 2020-03-17 09:43:40 +03:00
Alex Plate
5e2c01daa6 Rename PutTextAction.kt 2020-03-17 09:32:46 +03:00
Alex Plate
58bf3a4d30 Merge Put actions into one file 2020-03-17 09:28:49 +03:00
Alex Plate
2d434c38b9 Move test to correct directory 2020-03-15 16:53:57 +03:00
Alex Plate
246f5cd8cf VIM-1911 Lookup keys respect IDE handler 2020-03-14 23:03:42 +03:00
Alex Plate
5a174d21f1 Update CHANGES 2020-03-14 18:06:58 +03:00
Alex Plate
e632c653f6 Add showcmd text to widget label 2020-03-14 18:05:38 +03:00
Alex Plate
174d17b088 VIM-1958 Fix X command for linewise selection 2020-03-14 18:00:45 +03:00
Alex Plate
3a35c931e4 Unignore some tests 2020-03-14 17:42:33 +03:00
Alex Plate
b768b26c85 Remove some warnings 2020-03-14 17:28:14 +03:00
Alex Plate
123ce6ebaf Get rid of deprecated KeyEvents 2020-03-14 15:55:01 +03:00
Alex Plate
276c8db512 Fix some tests 2020-03-12 11:48:24 +03:00
Alex Plate
f898b8d181 Fix mappings to <S-Space> 2020-03-12 11:25:14 +03:00
Alex Plate
e9f9e531e4 Convert vim typed action to kt 2020-03-12 11:04:35 +03:00
Alex Plate
a7d813cb86 Rename .java to .kt 2020-03-12 11:04:35 +03:00
Alex Plate
75b6eedb12 Remove unused class 2020-03-12 10:33:52 +03:00
Alex Plate
ec6860aa90 Change the label of showcmd widget 2020-03-12 10:03:39 +03:00
Alex Plate
5cf661c6ae Fix exception during command typing 2020-03-10 14:58:35 +03:00
Alex Plate
8c62caae7c Fix compilation errors 2020-03-10 11:30:10 +03:00
Alex Plate
cc6fe21af6 Update CHANGES.md 2020-03-10 10:28:17 +03:00
Alex Pláte
1902151efa Merge pull request #220 from citizenmatt/feature/showcmd
Implement showcmd
2020-03-10 10:24:36 +03:00
Alex Pláte
b7af1e6289 Merge branch 'master' into feature/showcmd 2020-03-10 10:24:06 +03:00
Alex Plate
0c77b320db VIM-570 Print non-ascii characters in ex panel 2020-03-06 13:03:28 +03:00
Alex Plate
ee41adc4e9 Update kotlin version 2020-03-06 09:54:57 +03:00
Alex Pláte
93462d7505 Merge pull request #221 from igrekster/master
argtextobj: support bracket pairs configuration via let g:argtextobj_pairs="..."
2020-03-06 09:54:33 +03:00
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
igrekster
6ec39314ee argtextobj: support bracket pairs configuration via let g:argtextobj_pairs="..."
argtextobj by default only handles arguments inside parenthesis. This is
very limiting when editing C++ source files. This change allows the list
of bracket pairs to be configurable via a global IdeaVim variable. The
variable takes effect immediately.
2020-02-26 10:32:21 +11: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
Matt Ellis
14c8b6a248 Fix nullability compile error on 2019.2 2020-02-11 10:29:02 +00:00
Alex Plate
adaa683e58 Use 2020.1 build for README label 2020-02-11 10:11:52 +03:00
Matt Ellis
9b71215cde Merge branch 'master' into feature/showcmd 2020-02-11 00:24:29 +00:00
Matt Ellis
8be572f976 Update set-commands and changes 2020-02-11 00:22:29 +00:00
Matt Ellis
4f43bcffb9 Replace SelectRegisterAction with direct parsing
It's not a command, but part of a command
2020-02-11 00:08:08 +00:00
Matt Ellis
29e4dc5fb5 Show digraph entry in showcmd 2020-02-10 23:38:02 +00:00
Matt Ellis
0dc95cb13c [VIM-434] Display showcmd in status bar
IdeaVim has showcmd enabled by default. Vim has it enabled by default, but disabled for Unix, with concerns about slow terminals. It is enabled by defaults.vim
2020-02-10 11:35:52 +00: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
Matt Ellis
9fad4a74ed Remove keys from Command
Also refactors PutVisualTextAction
2020-02-04 00:36:59 +00: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
168 changed files with 5488 additions and 2174 deletions

View File

@@ -271,6 +271,22 @@ 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
* [![icon][mail]](mailto:kevinz@weghst.com)
[![icon][github]](https://github.com/kevin70)
&nbsp;
kk
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

@@ -3,6 +3,12 @@ The Changelog
History of changes in IdeaVim for the IntelliJ platform. History of changes in IdeaVim for the IntelliJ platform.
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
and this project DOES NOT adhere to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
Stable versions use X.Y format.
EAP versions use X.Y.Z format.
Get an Early Access Get an Early Access
------------------- -------------------
@@ -16,18 +22,34 @@ It is important to distinguish EAP from traditional pre-release software.
Please note that the quality of EAP versions may at times be way below even Please note that the quality of EAP versions may at times be way below even
usual beta standards. usual beta standards.
To Be Released 0.56, 2020-04-09
-------------- --------------
_Available since 0.55.1 EAP:_ **Features:**
* `ReplaceWithRegister` plugin emulation ([ReplaceWithRegister](https://www.vim.org/scripts/script.php?script_id=2703)).
* `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)).
* [VIM-434](https://youtrack.jetbrains.com/issue/VIM-434) Add `'showcmd'` support, on by default.
* Support `ls/buffers/files` commands.
**Changes:**
* Replace `ideastatusbar` option with `ideastatusicon`. Now you can make the icon gray.
**Deprecations:**
* `ideastatusbar` option is deprecated now. See `ideastatusicon`.
**Fixes:** **Fixes:**
* [VIM-1284](https://youtrack.jetbrains.com/issue/VIM-1284) Fix mapping of digits * [VIM-1284](https://youtrack.jetbrains.com/issue/VIM-1284) Fix mapping of digits.
* Fix handling of counts on both operator and motion, e.g. `3d2w` deletes 6 words, instead of 32 * Fix handling of counts on both operator and motion, e.g. `3d2w` deletes 6 words, instead of 32.
* Allow mapping of `<C-K>` and `<C-V>`/`<C-Q>` * Allow mapping of `<C-K>` and `<C-V>`/`<C-Q>`.
* [VIM-1899](https://youtrack.jetbrains.com/issue/VIM-1899) Add argument to `:registers` command * [VIM-1899](https://youtrack.jetbrains.com/issue/VIM-1899) Add argument to `:registers` command.
* [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.
* [VIM-570](https://youtrack.jetbrains.com/issue/VIM-570) Print non-ascii characters in ex panel.
* [VIM-926](https://youtrack.jetbrains.com/issue/VIM-926) Fix `<S-Space>` mapping.
* [VIM-1958](https://youtrack.jetbrains.com/issue/VIM-1958) Fix `X` command for linewise selection.
* [VIM-1911](https://youtrack.jetbrains.com/issue/VIM-1911) Lookup keys respect `IDE` handler.
* [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,9 @@ Emulated Vim plugins:
* vim-surround * vim-surround
* vim-multiple-cursors * vim-multiple-cursors
* vim-commentary * vim-commentary
* argtextobj.vim
* vim-textobj-entire
* ReplaceWithRegister
Not supported (yet): Not supported (yet):
@@ -165,6 +168,28 @@ 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`
* ReplaceWithRegister
* Setup: `set ReplaceWithRegister`
* Emulates [ReplaceWithRegister](https://github.com/vim-scripts/ReplaceWithRegister)
* Commands: `gr`, `grr`
* argtextobj
* Setup:
* `set argtextobj`
* By default, only the arguments inside parenthesis are considered. To extend the functionality
to other types of brackets, set `g:argtextobj_pairs` variable to a comma-separated
list of colon-separated pairs (same as VIM's `matchpairs` option), like
`let g:argtextobj_pairs="(:),{:},<:>"`. The order of pairs matters when
handling symbols that can also be operators: `func(x << 5, 20) >> 17`. To handle
this syntax parenthesis, must come before angle brackets in the list.
* Emulates [argtextobj.vim](https://www.vim.org/scripts/script.php?script_id=2699)
* Additional text objects: `aa`, `ia`
* textobj-entire
* 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 {
@@ -74,15 +74,56 @@ tasks.register("slackEapNotification") {
doLast { doLast {
if (!slackUrl) return if (!slackUrl) return
def post = new URL(slackUrl).openConnection() def post = new URL(slackUrl).openConnection()
def message = "{\"text\":\"New EAP released: $version\"}" def changeLog = extractChangelog()
changeLog = changeLog.replace("* ", "• ") // Replace stars with bullets
changeLog = changeLog.replace("**", "*") // Enable bold text
changeLog = changeLog.replaceAll("\\[(.+)]\\(([^)]+)\\)", '<$2|$1>') // Enable links
def message ="""
{
"text": "Danny Torrence left a 1 star review for your property.",
"blocks": [
{
"type": "section",
"text": {
"type": "mrkdwn",
"text": "New EAP released: $version\\n$changeLog"
}
}
]
}
"""
post.setRequestMethod("POST") post.setRequestMethod("POST")
post.setDoOutput(true) post.setDoOutput(true)
post.setRequestProperty("Content-Type", "application/json") post.setRequestProperty("Content-Type", "application/json")
post.getOutputStream().write(message.getBytes("UTF-8")) post.getOutputStream().write(message.getBytes("UTF-8"))
def postRC = post.getResponseCode() def postRC = post.getResponseCode()
println(postRC) println(postRC)
if(postRC.equals(200)) { if(postRC == 200) {
println(post.getInputStream().getText()) println(post.getInputStream().getText())
} }
} }
} }
// Very primitive changelog extraction code
def extractChangelog() {
def startLine = "_Available since $version EAP:_"
def endLine = "_To Be Released..._"
def startSaving = false
def res = new StringBuilder()
new File("./CHANGES.md").eachLine { line ->
if (startSaving) {
if (line == endLine) {
startSaving = false
}
else {
res.append(line).append('\n')
}
}
else {
if (line == startLine) {
startSaving = true
}
}
}
return res.toString()
}

View File

@@ -65,6 +65,7 @@ The following `:set` commands can appear in `~/.ideavimrc` or be set manually in
same as ideaselection - IdeaVim ONLY same as ideaselection - IdeaVim ONLY
'showmode' 'smd' message on the status line to show current mode 'showmode' 'smd' message on the status line to show current mode
'showcmd' 'sc' show (partial) command in the status bar
'sidescroll' 'ss' minimum number of columns to scroll horizontally 'sidescroll' 'ss' minimum number of columns to scroll horizontally
'sidescrolloff' 'siso' min. number of columns to left and right of cursor 'sidescrolloff' 'siso' min. number of columns to left and right of cursor
'smartcase' 'scs' no ignore case when pattern is uppercase 'smartcase' 'scs' no ignore case when pattern is uppercase
@@ -104,10 +105,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")
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).

View File

@@ -5,7 +5,7 @@ downloadIdeaSources=true
instrumentPluginCode=true instrumentPluginCode=true
version=SNAPSHOT version=SNAPSHOT
javaVersion=1.8 javaVersion=1.8
kotlinVersion=1.3.61 kotlinVersion=1.3.70
publishUsername=username publishUsername=username
publishToken=token publishToken=token
publishChannels=eap publishChannels=eap

Binary file not shown.

View File

@@ -1,5 +1,5 @@
#Fri Nov 22 14:42:01 MSK 2019 #Fri Mar 20 11:41:45 MSK 2020
distributionUrl=https\://services.gradle.org/distributions/gradle-6.0.1-all.zip distributionUrl=https\://services.gradle.org/distributions/gradle-6.2.1-all.zip
distributionBase=GRADLE_USER_HOME distributionBase=GRADLE_USER_HOME
distributionPath=wrapper/dists distributionPath=wrapper/dists
zipStorePath=wrapper/dists 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

@@ -197,8 +197,8 @@
<vimAction implementation="com.maddyhome.idea.vim.action.change.insert.InsertSingleCommandAction" mappingModes="I" keys="«C-O»"/> <vimAction implementation="com.maddyhome.idea.vim.action.change.insert.InsertSingleCommandAction" mappingModes="I" keys="«C-O»"/>
<vimAction implementation="com.maddyhome.idea.vim.action.change.insert.VisualBlockInsertAction" mappingModes="X" keys="I"/> <vimAction implementation="com.maddyhome.idea.vim.action.change.insert.VisualBlockInsertAction" mappingModes="X" keys="I"/>
<vimAction implementation="com.maddyhome.idea.vim.action.change.insert.VisualBlockAppendAction" mappingModes="X" keys="A"/> <vimAction implementation="com.maddyhome.idea.vim.action.change.insert.VisualBlockAppendAction" mappingModes="X" keys="A"/>
<vimAction implementation="com.maddyhome.idea.vim.action.change.insert.StartInsertDigraphAction" mappingModes="IC" keys="«C-K»"/> <vimAction implementation="com.maddyhome.idea.vim.action.change.insert.InsertCompletedDigraphAction" mappingModes="IC" keys="«C-K»"/>
<vimAction implementation="com.maddyhome.idea.vim.action.change.insert.StartInsertLiteralAction" mappingModes="IC" keys="«C-V»,«C-Q»"/> <vimAction implementation="com.maddyhome.idea.vim.action.change.insert.InsertCompletedLiteralAction" mappingModes="IC" keys="«C-V»,«C-Q»"/>
<!-- Delete --> <!-- Delete -->
<vimAction implementation="com.maddyhome.idea.vim.action.change.delete.DeleteCharacterAction" mappingModes="N" keys="«DEL»"/> <vimAction implementation="com.maddyhome.idea.vim.action.change.delete.DeleteCharacterAction" mappingModes="N" keys="«DEL»"/>
@@ -260,14 +260,16 @@
<vimAction implementation="com.maddyhome.idea.vim.action.copy.PutTextBeforeCursorNoIndentAction" mappingModes="N" keys="[P,]P,[p"/> <vimAction implementation="com.maddyhome.idea.vim.action.copy.PutTextBeforeCursorNoIndentAction" mappingModes="N" keys="[P,]P,[p"/>
<vimAction implementation="com.maddyhome.idea.vim.action.copy.PutTextAfterCursorActionMoveCursor" mappingModes="N" keys="gp"/> <vimAction implementation="com.maddyhome.idea.vim.action.copy.PutTextAfterCursorActionMoveCursor" mappingModes="N" keys="gp"/>
<vimAction implementation="com.maddyhome.idea.vim.action.copy.PutTextBeforeCursorActionMoveCursor" mappingModes="N" keys="gP"/> <vimAction implementation="com.maddyhome.idea.vim.action.copy.PutTextBeforeCursorActionMoveCursor" mappingModes="N" keys="gP"/>
<vimAction implementation="com.maddyhome.idea.vim.action.copy.SelectRegisterAction" mappingModes="NXO" keys='"'/>
<vimAction implementation="com.maddyhome.idea.vim.action.copy.YankLineAction" mappingModes="N" keys="Y"/> <vimAction implementation="com.maddyhome.idea.vim.action.copy.YankLineAction" mappingModes="N" keys="Y"/>
<vimAction implementation="com.maddyhome.idea.vim.action.copy.YankMotionAction" mappingModes="N" keys="y"/> <vimAction implementation="com.maddyhome.idea.vim.action.copy.YankMotionAction" mappingModes="N" keys="y"/>
<vimAction implementation="com.maddyhome.idea.vim.action.copy.YankVisualAction" mappingModes="X" keys="y"/> <vimAction implementation="com.maddyhome.idea.vim.action.copy.YankVisualAction" mappingModes="X" keys="y"/>
<vimAction implementation="com.maddyhome.idea.vim.action.copy.YankVisualLinesAction" mappingModes="X" keys="Y"/> <vimAction implementation="com.maddyhome.idea.vim.action.copy.YankVisualLinesAction" mappingModes="X" keys="Y"/>
<vimAction implementation="com.maddyhome.idea.vim.action.copy.PutVisualTextAction" mappingModes="X" keys="p,P"/> <vimAction implementation="com.maddyhome.idea.vim.action.copy.PutVisualTextBeforeCursorAction" mappingModes="X" keys="P"/>
<vimAction implementation="com.maddyhome.idea.vim.action.copy.PutVisualTextNoIndentAction" mappingModes="X" keys="[p,]p,]P,[P"/> <vimAction implementation="com.maddyhome.idea.vim.action.copy.PutVisualTextAfterCursorAction" mappingModes="X" keys="p"/>
<vimAction implementation="com.maddyhome.idea.vim.action.copy.PutVisualTextMoveCursorAction" mappingModes="X" keys="gp,gP"/> <vimAction implementation="com.maddyhome.idea.vim.action.copy.PutVisualTextBeforeCursorNoIndentAction" mappingModes="X" keys="]P,[P"/>
<vimAction implementation="com.maddyhome.idea.vim.action.copy.PutVisualTextAfterCursorNoIndentAction" mappingModes="X" keys="[p,]p"/>
<vimAction implementation="com.maddyhome.idea.vim.action.copy.PutVisualTextBeforeCursorMoveCursorAction" mappingModes="X" keys="gP"/>
<vimAction implementation="com.maddyhome.idea.vim.action.copy.PutVisualTextAfterCursorMoveCursorAction" mappingModes="X" keys="gp"/>
<!-- File --> <!-- File -->
<vimAction implementation="com.maddyhome.idea.vim.action.file.FileSaveCloseAction" mappingModes="N" keys="ZQ,ZZ"/> <vimAction implementation="com.maddyhome.idea.vim.action.file.FileSaveCloseAction" mappingModes="N" keys="ZQ,ZZ"/>

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,8 @@
<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"/>
<vimExtension implementation="com.maddyhome.idea.vim.extension.replacewithregister.ReplaceWithRegister"/>
</extensions> </extensions>
</idea-plugin> </idea-plugin>

View File

@@ -3,9 +3,12 @@
<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 ReplaceWithRegister plugin emulation</li>
<li>Support XDG settings standard</li> <li>Support argtextobj.vim plugin emulation</li>
<li>Add option to remove the status bar icon</li> <li>Support vim-textobj-entire plugin emulation</li>
<li>Support showcmd command</li>
<li>Support ls/buffers/files command</li>
<li>Control the icon in the status bar using an `ideastatusicon` option</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 +37,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 +56,12 @@
<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"/>
<statusBarWidgetProvider implementation="com.maddyhome.idea.vim.ui.ShowCmdStatusBarWidget"/>
<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

@@ -35,6 +35,8 @@ import com.intellij.openapi.project.Project;
import com.intellij.openapi.ui.popup.JBPopupFactory; import com.intellij.openapi.ui.popup.JBPopupFactory;
import com.intellij.openapi.ui.popup.ListPopup; import com.intellij.openapi.ui.popup.ListPopup;
import com.maddyhome.idea.vim.action.change.VimRepeater; import com.maddyhome.idea.vim.action.change.VimRepeater;
import com.maddyhome.idea.vim.action.change.insert.InsertCompletedDigraphAction;
import com.maddyhome.idea.vim.action.change.insert.InsertCompletedLiteralAction;
import com.maddyhome.idea.vim.action.macro.ToggleRecordingAction; import com.maddyhome.idea.vim.action.macro.ToggleRecordingAction;
import com.maddyhome.idea.vim.command.*; import com.maddyhome.idea.vim.command.*;
import com.maddyhome.idea.vim.extension.VimExtensionHandler; import com.maddyhome.idea.vim.extension.VimExtensionHandler;
@@ -48,6 +50,7 @@ import com.maddyhome.idea.vim.key.*;
import com.maddyhome.idea.vim.listener.SelectionVimListenerSuppressor; import com.maddyhome.idea.vim.listener.SelectionVimListenerSuppressor;
import com.maddyhome.idea.vim.listener.VimListenerSuppressor; import com.maddyhome.idea.vim.listener.VimListenerSuppressor;
import com.maddyhome.idea.vim.option.OptionsManager; import com.maddyhome.idea.vim.option.OptionsManager;
import com.maddyhome.idea.vim.ui.ShowCmd;
import org.jetbrains.annotations.Contract; import org.jetbrains.annotations.Contract;
import org.jetbrains.annotations.NonNls; import org.jetbrains.annotations.NonNls;
import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.NotNull;
@@ -56,8 +59,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 +79,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();
} }
@@ -154,16 +158,6 @@ public class KeyHandler {
return false; return false;
} }
public void startDigraphSequence(@NotNull Editor editor) {
final CommandState editorState = CommandState.getInstance(editor);
editorState.startDigraphSequence();
}
public void startLiteralSequence(@NotNull Editor editor) {
final CommandState editorState = CommandState.getInstance(editor);
editorState.startLiteralSequence();
}
/** /**
* This is the main key handler for the Vim plugin. Every keystroke not handled directly by Idea is sent here for * This is the main key handler for the Vim plugin. Every keystroke not handled directly by Idea is sent here for
* processing. * processing.
@@ -220,7 +214,7 @@ public class KeyHandler {
try { try {
if (!allowKeyMappings || !handleKeyMapping(editor, key, context)) { if (!allowKeyMappings || !handleKeyMapping(editor, key, context)) {
if (isCommandCountKey(chKey, editorState)) { if (isCommandCountKey(chKey, editorState)) {
commandBuilder.addCountCharacter(chKey); commandBuilder.addCountCharacter(key);
} else if (isDeleteCommandCountKey(key, editorState)) { } else if (isDeleteCommandCountKey(key, editorState)) {
commandBuilder.deleteCountCharacter(); commandBuilder.deleteCountCharacter();
} else if (isEditorReset(key, editorState)) { } else if (isEditorReset(key, editorState)) {
@@ -231,21 +225,29 @@ public class KeyHandler {
else if (isExpectingCharArgument(commandBuilder)) { else if (isExpectingCharArgument(commandBuilder)) {
handleCharArgument(key, chKey, editorState); handleCharArgument(key, chKey, editorState);
} }
else if (editorState.getSubMode() == CommandState.SubMode.REGISTER_PENDING) {
commandBuilder.addKey(key);
handleSelectRegister(editorState, chKey);
}
// If we are this far, then the user must be entering a command or a non-single-character argument // If we are this far, then the user must be entering a command or a non-single-character argument
// to an entered command. Let's figure out which it is. // to an entered command. Let's figure out which it is.
else if (!handleDigraph(editor, key, context, editorState)) { else if (!handleDigraph(editor, key, context, editorState)) {
commandBuilder.addKey(key);
// Ask the key/action tree if this is an appropriate key at this point in the command and if so, // Ask the key/action tree if this is an appropriate key at this point in the command and if so,
// return the node matching this keystroke // return the node matching this keystroke
final Node node = mapOpCommand(key, commandBuilder.getChildNode(key), editorState); final Node node = mapOpCommand(key, commandBuilder.getChildNode(key), editorState);
if (node instanceof CommandNode) { if (node instanceof CommandNode) {
handleCommandNode(editor, context, key, (CommandNode) node, editorState); handleCommandNode(editor, context, key, (CommandNode) node, editorState);
commandBuilder.addKey(key);
} else if (node instanceof CommandPartNode) { } else if (node instanceof CommandPartNode) {
commandBuilder.setCurrentCommandPartNode((CommandPartNode) node); commandBuilder.setCurrentCommandPartNode((CommandPartNode) node);
} else { commandBuilder.addKey(key);
} else if (isSelectRegister(key, editorState)) {
editorState.pushModes(CommandState.Mode.COMMAND, CommandState.SubMode.REGISTER_PENDING);
commandBuilder.addKey(key);
}
else { // node == null
// If we are in insert/replace mode send this key in for processing // If we are in insert/replace mode send this key in for processing
if (editorState.getMode() == CommandState.Mode.INSERT || editorState.getMode() == CommandState.Mode.REPLACE) { if (editorState.getMode() == CommandState.Mode.INSERT || editorState.getMode() == CommandState.Mode.REPLACE) {
shouldRecord &= VimPlugin.getChange().processKey(editor, context, key); shouldRecord &= VimPlugin.getChange().processKey(editor, context, key);
@@ -270,10 +272,11 @@ public class KeyHandler {
// Do we have a fully entered command at this point? If so, let's execute it. // Do we have a fully entered command at this point? If so, let's execute it.
if (commandBuilder.isReady()) { if (commandBuilder.isReady()) {
executeCommand(editor, key, context, editorState); executeCommand(editor, context, editorState);
} }
else if (commandBuilder.isBad()) { else if (commandBuilder.isBad()) {
editorState.resetOpPending(); editorState.resetOpPending();
editorState.resetRegisterPending();
VimPlugin.indicateError(); VimPlugin.indicateError();
reset(editor); reset(editor);
} }
@@ -282,6 +285,9 @@ public class KeyHandler {
if (shouldRecord && editorState.isRecording()) { if (shouldRecord && editorState.isRecording()) {
VimPlugin.getRegister().recordKeyStroke(key); VimPlugin.getRegister().recordKeyStroke(key);
} }
// This will update immediately, if we're on the EDT (which we are)
ShowCmd.INSTANCE.update();
} }
/** /**
@@ -306,7 +312,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 +327,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 +436,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 +446,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
@@ -570,7 +575,29 @@ public class KeyHandler {
} }
private boolean isEditorReset(@NotNull KeyStroke key, @NotNull CommandState editorState) { private boolean isEditorReset(@NotNull KeyStroke key, @NotNull CommandState editorState) {
return (editorState.getMode() == CommandState.Mode.COMMAND) && StringHelper.isCloseKeyStroke(key); return editorState.getMode() == CommandState.Mode.COMMAND && StringHelper.isCloseKeyStroke(key);
}
private boolean isSelectRegister(@NotNull KeyStroke key, @NotNull CommandState editorState) {
if (editorState.getMode() != CommandState.Mode.COMMAND && editorState.getMode() != CommandState.Mode.VISUAL) {
return false;
}
if (editorState.getSubMode() == CommandState.SubMode.REGISTER_PENDING) {
return true;
}
return key.getKeyChar() == '"' && !editorState.isOperatorPending() && editorState.getCommandBuilder().getExpectedArgumentType() == null;
}
private void handleSelectRegister(@NotNull CommandState commandState, char chKey) {
commandState.resetRegisterPending();
if (VimPlugin.getRegister().isValid(chKey)) {
commandState.getCommandBuilder().pushCommandPart(chKey);
}
else {
commandState.getCommandBuilder().setCommandState(CurrentCommandState.BAD_COMMAND);
}
} }
private boolean isExpectingCharArgument(@NotNull CommandBuilder commandBuilder) { private boolean isExpectingCharArgument(@NotNull CommandBuilder commandBuilder) {
@@ -615,10 +642,12 @@ public class KeyHandler {
if (commandBuilder.getExpectedArgumentType() == Argument.Type.DIGRAPH) { if (commandBuilder.getExpectedArgumentType() == Argument.Type.DIGRAPH) {
if (DigraphSequence.isDigraphStart(key)) { if (DigraphSequence.isDigraphStart(key)) {
editorState.startDigraphSequence(); editorState.startDigraphSequence();
editorState.getCommandBuilder().addKey(key);
return true; return true;
} }
if (DigraphSequence.isLiteralStart(key)) { if (DigraphSequence.isLiteralStart(key)) {
editorState.startLiteralSequence(); editorState.startLiteralSequence();
editorState.getCommandBuilder().addKey(key);
return true; return true;
} }
} }
@@ -626,7 +655,7 @@ public class KeyHandler {
DigraphResult res = editorState.processDigraphKey(key, editor); DigraphResult res = editorState.processDigraphKey(key, editor);
switch (res.getResult()) { switch (res.getResult()) {
case DigraphResult.RES_HANDLED: case DigraphResult.RES_HANDLED:
case DigraphResult.RES_BAD: editorState.getCommandBuilder().addKey(key);
return true; return true;
case DigraphResult.RES_DONE: case DigraphResult.RES_DONE:
@@ -637,10 +666,20 @@ public class KeyHandler {
if (stroke == null) { if (stroke == null) {
return false; return false;
} }
editorState.getCommandBuilder().addKey(key);
handleKey(editor, stroke, context); handleKey(editor, stroke, context);
return true; return true;
case DigraphResult.RES_BAD:
// BAD is an error. We were expecting a valid character, and we didn't get it.
if (commandBuilder.getExpectedArgumentType() != null) {
commandBuilder.setCommandState(CurrentCommandState.BAD_COMMAND);
}
return true;
case DigraphResult.RES_UNHANDLED: case DigraphResult.RES_UNHANDLED:
// UNHANDLED means the key stroke made no sense in the context of a digraph, but isn't an error in the current
// state. E.g. waiting for {char} <BS> {char}. Let the key handler have a go at it.
if (commandBuilder.getExpectedArgumentType() == Argument.Type.DIGRAPH) { if (commandBuilder.getExpectedArgumentType() == Argument.Type.DIGRAPH) {
commandBuilder.fallbackToCharacterArgument(); commandBuilder.fallbackToCharacterArgument();
handleKey(editor, key, context); handleKey(editor, key, context);
@@ -653,7 +692,6 @@ public class KeyHandler {
} }
private void executeCommand(@NotNull Editor editor, private void executeCommand(@NotNull Editor editor,
@NotNull KeyStroke key,
@NotNull DataContext context, @NotNull DataContext context,
@NotNull CommandState editorState) { @NotNull CommandState editorState) {
final Command command = editorState.getCommandBuilder().buildCommand(); final Command command = editorState.getCommandBuilder().buildCommand();
@@ -676,7 +714,7 @@ public class KeyHandler {
} }
if (ApplicationManager.getApplication().isDispatchThread()) { if (ApplicationManager.getApplication().isDispatchThread()) {
Runnable action = new ActionRunner(editor, context, command, key); Runnable action = new ActionRunner(editor, context, command);
EditorActionHandlerBase cmdAction = command.getAction(); EditorActionHandlerBase cmdAction = command.getAction();
String name = cmdAction.getId(); String name = cmdAction.getId();
@@ -714,7 +752,7 @@ public class KeyHandler {
} }
else { else {
final Argument.Type argumentType = action.getArgumentType(); final Argument.Type argumentType = action.getArgumentType();
startWaitingForArgument(editor, context, key.getKeyChar(), argumentType, editorState); startWaitingForArgument(editor, context, key.getKeyChar(), action, argumentType, editorState);
partialReset(editor); partialReset(editor);
} }
@@ -751,6 +789,7 @@ public class KeyHandler {
private void startWaitingForArgument(Editor editor, private void startWaitingForArgument(Editor editor,
DataContext context, DataContext context,
char key, char key,
@NotNull EditorActionHandlerBase action,
@NotNull Argument.Type argument, @NotNull Argument.Type argument,
CommandState editorState) { CommandState editorState) {
final CommandBuilder commandBuilder = editorState.getCommandBuilder(); final CommandBuilder commandBuilder = editorState.getCommandBuilder();
@@ -761,6 +800,17 @@ public class KeyHandler {
} }
editorState.pushModes(editorState.getMode(), CommandState.SubMode.OP_PENDING); editorState.pushModes(editorState.getMode(), CommandState.SubMode.OP_PENDING);
break; break;
case DIGRAPH:
// Command actions represent the completion of a command. Showcmd relies on this - if the action represents a
// part of a command, the showcmd output is reset part way through. This means we need to special case entering
// digraph/literal input mode. We have an action that takes a digraph as an argument, and pushes it back through
// the key handler when it's complete.
if (action instanceof InsertCompletedDigraphAction) {
editorState.startDigraphSequence();
} else if (action instanceof InsertCompletedLiteralAction) {
editorState.startLiteralSequence();
}
break;
case EX_STRING: case EX_STRING:
// The current Command expects an EX_STRING argument. E.g. SearchEntry(Fwd|Rev)Action. This won't execute until // The current Command expects an EX_STRING argument. E.g. SearchEntry(Fwd|Rev)Action. This won't execute until
// state hits READY. Start the ex input field, push CMD_LINE mode and wait for the argument. // state hits READY. Start the ex input field, push CMD_LINE mode and wait for the argument.
@@ -810,8 +860,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 +874,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 +885,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 +904,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 +916,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);
} }
@@ -884,11 +934,10 @@ public class KeyHandler {
*/ */
static class ActionRunner implements Runnable { static class ActionRunner implements Runnable {
@Contract(pure = true) @Contract(pure = true)
ActionRunner(Editor editor, DataContext context, Command cmd, KeyStroke key) { ActionRunner(Editor editor, DataContext context, Command cmd) {
this.editor = editor; this.editor = editor;
this.context = context; this.context = context;
this.cmd = cmd; this.cmd = cmd;
this.key = key;
} }
@Override @Override
@@ -897,6 +946,11 @@ public class KeyHandler {
editorState.getCommandBuilder().setCommandState(CurrentCommandState.NEW_COMMAND); editorState.getCommandBuilder().setCommandState(CurrentCommandState.NEW_COMMAND);
final Character register = cmd.getRegister();
if (register != null) {
VimPlugin.getRegister().selectRegister(register);
}
executeVimAction(editor, cmd.getAction(), context); executeVimAction(editor, cmd.getAction(), context);
if (editorState.getMode() == CommandState.Mode.INSERT || editorState.getMode() == CommandState.Mode.REPLACE) { if (editorState.getMode() == CommandState.Mode.INSERT || editorState.getMode() == CommandState.Mode.REPLACE) {
VimPlugin.getChange().processCommand(editor, cmd); VimPlugin.getChange().processCommand(editor, cmd);
@@ -905,10 +959,8 @@ public class KeyHandler {
// Now the command has been executed let's clean up a few things. // Now the command has been executed let's clean up a few things.
// By default, the "empty" register is used by all commands, so we want to reset whatever the last register // By default, the "empty" register is used by all commands, so we want to reset whatever the last register
// selected by the user was to the empty register - unless we just executed the "select register" command. // selected by the user was to the empty register
if (cmd.getType() != Command.Type.SELECT_REGISTER) { VimPlugin.getRegister().resetRegister();
VimPlugin.getRegister().resetRegister();
}
// If, at this point, we are not in insert, replace, or visual modes, we need to restore the previous // If, at this point, we are not in insert, replace, or visual modes, we need to restore the previous
// mode we were in. This handles commands in those modes that temporarily allow us to execute normal // mode we were in. This handles commands in those modes that temporarily allow us to execute normal
@@ -925,7 +977,6 @@ public class KeyHandler {
private final Editor editor; private final Editor editor;
private final DataContext context; private final DataContext context;
private final Command cmd; private final Command cmd;
private final KeyStroke key;
} }
private TypedActionHandler origHandler; private TypedActionHandler origHandler;

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,11 +60,20 @@ 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? {
@Suppress("DEPRECATION")
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 {
init {
OptionsManager.ideastatusicon.addOptionChangeListener { _, _ -> this.update() }
}
private var statusBar: StatusBar? = null private var statusBar: StatusBar? = null
override fun ID(): String = "IdeaVim-Icon" override fun ID(): String = "IdeaVim-Icon"
@@ -76,7 +86,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,28 @@ 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() {
@Suppress("DEPRECATION")
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,25 @@ 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 +133,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 +159,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 +198,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 +296,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,18 +386,13 @@ 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() {
ApplicationManager.getApplication().invokeLater(this::updateState); ApplicationManager.getApplication().invokeLater(this::updateState);
getEditor().turnOn();
getSearch().turnOn();
VimListenerManager.INSTANCE.turnOn();
// Register vim actions in command mode // Register vim actions in command mode
RegisterActions.registerActions(); RegisterActions.registerActions();
@@ -423,21 +404,32 @@ public class VimPlugin implements BaseComponent, PersistentStateComponent<Elemen
// Execute ~/.ideavimrc // Execute ~/.ideavimrc
registerIdeavimrc(); registerIdeavimrc();
// Turing on should be performed after all commands registration
getEditor().turnOn();
getSearch().turnOn();
VimListenerManager.INSTANCE.turnOn();
} }
private void turnOffPlugin() { private void turnOffPlugin() {
KeyHandler.getInstance().fullReset(null); KeyHandler.getInstance().fullReset(null);
EditorGroup editorGroup = getEditorIfCreated();
if (editorGroup != null) {
editorGroup.turnOff();
}
SearchGroup searchGroup = getSearchIfCreated();
if (searchGroup != null) {
searchGroup.turnOff();
}
VimListenerManager.INSTANCE.turnOff();
ExEntryPanel.fullReset();
// Unregister vim actions in command mode // Unregister vim actions in command mode
RegisterActions.unregisterActions(); RegisterActions.unregisterActions();
// Unregister ex handlers // Unregister ex handlers
CommandParser.getInstance().unregisterHandlers(); CommandParser.getInstance().unregisterHandlers();
getEditor().turnOff();
getSearch().turnOff();
VimListenerManager.INSTANCE.turnOff();
ExEntryPanel.fullReset();
} }
private boolean stateUpdated = false; private boolean stateUpdated = false;
@@ -485,7 +477,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 +492,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 +520,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

@@ -1,61 +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;
import com.intellij.openapi.actionSystem.DataContext;
import com.intellij.openapi.diagnostic.Logger;
import com.intellij.openapi.editor.Editor;
import com.intellij.openapi.editor.actionSystem.ActionPlan;
import com.intellij.openapi.editor.actionSystem.TypedActionHandler;
import com.intellij.openapi.editor.actionSystem.TypedActionHandlerEx;
import com.maddyhome.idea.vim.helper.EditorDataContext;
import org.jetbrains.annotations.NotNull;
import javax.swing.*;
/**
* Accepts all regular keystrokes and passes them on to the Vim key handler.
*
* IDE shortcut keys used by Vim commands are handled by {@link com.maddyhome.idea.vim.action.VimShortcutKeyAction}.
*/
public class VimTypedActionHandler implements TypedActionHandlerEx {
private static final Logger logger = Logger.getInstance(VimTypedActionHandler.class.getName());
@NotNull private final KeyHandler handler;
public VimTypedActionHandler(TypedActionHandler origHandler) {
handler = KeyHandler.getInstance();
handler.setOriginalHandler(origHandler);
}
@Override
public void beforeExecute(@NotNull Editor editor, char charTyped, @NotNull DataContext context, @NotNull ActionPlan plan) {
handler.beforeHandleKey(editor, KeyStroke.getKeyStroke(charTyped), context, plan);
}
@Override
public void execute(@NotNull final Editor editor, final char charTyped, @NotNull final DataContext context) {
try {
handler.handleKey(editor, KeyStroke.getKeyStroke(charTyped), new EditorDataContext(editor));
}
catch (Throwable e) {
logger.error(e);
}
}
}

View File

@@ -0,0 +1,74 @@
/*
* 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.actionSystem.DataContext
import com.intellij.openapi.diagnostic.logger
import com.intellij.openapi.editor.Editor
import com.intellij.openapi.editor.actionSystem.ActionPlan
import com.intellij.openapi.editor.actionSystem.TypedActionHandler
import com.intellij.openapi.editor.actionSystem.TypedActionHandlerEx
import com.maddyhome.idea.vim.helper.EditorDataContext
import java.awt.event.KeyAdapter
import java.awt.event.KeyEvent
import javax.swing.KeyStroke
/**
* Accepts all regular keystrokes and passes them on to the Vim key handler.
*
* IDE shortcut keys used by Vim commands are handled by [com.maddyhome.idea.vim.action.VimShortcutKeyAction].
*/
class VimTypedActionHandler(origHandler: TypedActionHandler?) : TypedActionHandlerEx {
private val handler = KeyHandler.getInstance()
init {
handler.originalHandler = origHandler
}
override fun beforeExecute(editor: Editor, charTyped: Char, context: DataContext, plan: ActionPlan) {
val modifiers = if (charTyped == ' ' && VimKeyListener.isSpaceShift) KeyEvent.SHIFT_DOWN_MASK else 0
val keyStroke = KeyStroke.getKeyStroke(charTyped, modifiers)
handler.beforeHandleKey(editor, keyStroke, context, plan)
}
override fun execute(editor: Editor, charTyped: Char, context: DataContext) {
try {
val modifiers = if (charTyped == ' ' && VimKeyListener.isSpaceShift) KeyEvent.SHIFT_DOWN_MASK else 0
val keyStroke = KeyStroke.getKeyStroke(charTyped, modifiers)
handler.handleKey(editor, keyStroke, EditorDataContext(editor))
} catch (e: Throwable) {
logger.error(e)
}
}
companion object {
private val logger = logger<VimTypedActionHandler>()
}
}
/**
* A nasty workaround to handle `<S-Space>` events. Probably all the key events should go trough this listener.
*/
object VimKeyListener : KeyAdapter() {
var isSpaceShift = false
override fun keyPressed(e: KeyEvent) {
isSpaceShift = e.modifiersEx and KeyEvent.SHIFT_DOWN_MASK != 0 && e.keyChar == ' '
}
}

View File

@@ -86,27 +86,26 @@ class VimShortcutKeyAction : AnAction(), DumbAware {
@Suppress("DEPRECATION") @Suppress("DEPRECATION")
val SMART_STEP_INPLACE_DATA = Key.findKeyByName("SMART_STEP_INPLACE_DATA") val SMART_STEP_INPLACE_DATA = Key.findKeyByName("SMART_STEP_INPLACE_DATA")
if (SMART_STEP_INPLACE_DATA != null && editor.getUserData(SMART_STEP_INPLACE_DATA) != null) return false if (SMART_STEP_INPLACE_DATA != null && editor.getUserData(SMART_STEP_INPLACE_DATA) != null) return false
if (aceJumpActive()) return false if (aceJumpActive()) return false
val keyCode = keyStroke.keyCode val keyCode = keyStroke.keyCode
if (LookupManager.getActiveLookup(editor) != null) {
return LookupKeys.isEnabledForLookup(keyStroke) if (LookupManager.getActiveLookup(editor) != null && !LookupKeys.isEnabledForLookup(keyStroke)) return false
}
if (keyCode == KeyEvent.VK_ESCAPE) { if (keyCode == KeyEvent.VK_ESCAPE) return isEnabledForEscape(editor)
return isEnabledForEscape(editor)
}
if (editor.inInsertMode) { // XXX: <Tab> won't be recorded in macros if (editor.inInsertMode) { // XXX: <Tab> won't be recorded in macros
if (keyCode == KeyEvent.VK_TAB) { if (keyCode == KeyEvent.VK_TAB) {
VimPlugin.getChange().tabAction = true VimPlugin.getChange().tabAction = true
return false return false
} }
// Debug watch, Python console, etc. // Debug watch, Python console, etc.
if (NON_FILE_EDITOR_KEYS.contains(keyStroke) && !EditorHelper.isFileEditor(editor)) { if (keyStroke in NON_FILE_EDITOR_KEYS && !EditorHelper.isFileEditor(editor)) return false
return false
}
}
if (VIM_ONLY_EDITOR_KEYS.contains(keyStroke)) {
return true
} }
if (keyStroke in VIM_ONLY_EDITOR_KEYS) return true
val savedShortcutConflicts = VimPlugin.getKey().savedShortcutConflicts val savedShortcutConflicts = VimPlugin.getKey().savedShortcutConflicts
return when (savedShortcutConflicts[keyStroke]) { return when (savedShortcutConflicts[keyStroke]) {
ShortcutOwner.VIM -> true ShortcutOwner.VIM -> true
@@ -191,15 +190,15 @@ class VimShortcutKeyAction : AnAction(), DumbAware {
companion object { companion object {
@JvmField @JvmField
val VIM_ONLY_EDITOR_KEYS: Set<KeyStroke> = ImmutableSet.builder<KeyStroke>().addAll(getKeyStrokes(KeyEvent.VK_ENTER, 0)).addAll(getKeyStrokes(KeyEvent.VK_ESCAPE, 0)) val VIM_ONLY_EDITOR_KEYS: Set<KeyStroke> = ImmutableSet.builder<KeyStroke>().addAll(getKeyStrokes(KeyEvent.VK_ENTER, 0)).addAll(getKeyStrokes(KeyEvent.VK_ESCAPE, 0))
.addAll(getKeyStrokes(KeyEvent.VK_TAB, 0)).addAll(getKeyStrokes(KeyEvent.VK_BACK_SPACE, 0, InputEvent.CTRL_MASK)) .addAll(getKeyStrokes(KeyEvent.VK_TAB, 0)).addAll(getKeyStrokes(KeyEvent.VK_BACK_SPACE, 0, InputEvent.CTRL_DOWN_MASK))
.addAll(getKeyStrokes(KeyEvent.VK_INSERT, 0)).addAll(getKeyStrokes(KeyEvent.VK_DELETE, 0, InputEvent.CTRL_MASK)) .addAll(getKeyStrokes(KeyEvent.VK_INSERT, 0)).addAll(getKeyStrokes(KeyEvent.VK_DELETE, 0, InputEvent.CTRL_DOWN_MASK))
.addAll(getKeyStrokes(KeyEvent.VK_UP, 0, InputEvent.CTRL_MASK, InputEvent.SHIFT_MASK)).addAll(getKeyStrokes(KeyEvent.VK_DOWN, 0, InputEvent.CTRL_MASK, InputEvent.SHIFT_MASK)) .addAll(getKeyStrokes(KeyEvent.VK_UP, 0, InputEvent.CTRL_DOWN_MASK, InputEvent.SHIFT_DOWN_MASK)).addAll(getKeyStrokes(KeyEvent.VK_DOWN, 0, InputEvent.CTRL_DOWN_MASK, InputEvent.SHIFT_DOWN_MASK))
.addAll(getKeyStrokes(KeyEvent.VK_LEFT, 0, InputEvent.CTRL_MASK, InputEvent.SHIFT_MASK, InputEvent.CTRL_MASK or InputEvent.SHIFT_MASK)) .addAll(getKeyStrokes(KeyEvent.VK_LEFT, 0, InputEvent.CTRL_DOWN_MASK, InputEvent.SHIFT_DOWN_MASK, InputEvent.CTRL_DOWN_MASK or InputEvent.SHIFT_DOWN_MASK))
.addAll(getKeyStrokes(KeyEvent.VK_RIGHT, 0, InputEvent.CTRL_MASK, InputEvent.SHIFT_MASK, InputEvent.CTRL_MASK or InputEvent.SHIFT_MASK)) .addAll(getKeyStrokes(KeyEvent.VK_RIGHT, 0, InputEvent.CTRL_DOWN_MASK, InputEvent.SHIFT_DOWN_MASK, InputEvent.CTRL_DOWN_MASK or InputEvent.SHIFT_DOWN_MASK))
.addAll(getKeyStrokes(KeyEvent.VK_HOME, 0, InputEvent.CTRL_MASK, InputEvent.SHIFT_MASK, InputEvent.CTRL_MASK or InputEvent.SHIFT_MASK)) .addAll(getKeyStrokes(KeyEvent.VK_HOME, 0, InputEvent.CTRL_DOWN_MASK, InputEvent.SHIFT_DOWN_MASK, InputEvent.CTRL_DOWN_MASK or InputEvent.SHIFT_DOWN_MASK))
.addAll(getKeyStrokes(KeyEvent.VK_END, 0, InputEvent.CTRL_MASK, InputEvent.SHIFT_MASK, InputEvent.CTRL_MASK or InputEvent.SHIFT_MASK)) .addAll(getKeyStrokes(KeyEvent.VK_END, 0, InputEvent.CTRL_DOWN_MASK, InputEvent.SHIFT_DOWN_MASK, InputEvent.CTRL_DOWN_MASK or InputEvent.SHIFT_DOWN_MASK))
.addAll(getKeyStrokes(KeyEvent.VK_PAGE_UP, 0, InputEvent.SHIFT_MASK, InputEvent.CTRL_MASK or InputEvent.SHIFT_MASK)) .addAll(getKeyStrokes(KeyEvent.VK_PAGE_UP, 0, InputEvent.SHIFT_DOWN_MASK, InputEvent.CTRL_DOWN_MASK or InputEvent.SHIFT_DOWN_MASK))
.addAll(getKeyStrokes(KeyEvent.VK_PAGE_DOWN, 0, InputEvent.SHIFT_MASK, InputEvent.CTRL_MASK or InputEvent.SHIFT_MASK)).build() .addAll(getKeyStrokes(KeyEvent.VK_PAGE_DOWN, 0, InputEvent.SHIFT_DOWN_MASK, InputEvent.CTRL_DOWN_MASK or InputEvent.SHIFT_DOWN_MASK)).build()
private const val ACTION_ID = "VimShortcutKeyAction" private const val ACTION_ID = "VimShortcutKeyAction"

View File

@@ -64,7 +64,7 @@ class RepeatChangeAction : VimActionHandler.SingleExecution() {
} }
state.setExecutingCommand(lastCommand) state.setExecutingCommand(lastCommand)
KeyHandler.executeVimAction(editor, lastCommand.action, context) KeyHandler.executeVimAction(editor, lastCommand.action!!, context)
VimRepeater.saveLastChange(lastCommand) VimRepeater.saveLastChange(lastCommand)
} }

View File

@@ -23,14 +23,12 @@ import com.intellij.openapi.editor.Editor
import com.maddyhome.idea.vim.VimPlugin import com.maddyhome.idea.vim.VimPlugin
import com.maddyhome.idea.vim.command.Command import com.maddyhome.idea.vim.command.Command
import com.maddyhome.idea.vim.command.CommandFlags import com.maddyhome.idea.vim.command.CommandFlags
import com.maddyhome.idea.vim.command.CommandState.SubMode
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.group.visual.VimSelection import com.maddyhome.idea.vim.group.visual.VimSelection
import com.maddyhome.idea.vim.handler.VisualOperatorActionHandler import com.maddyhome.idea.vim.handler.VisualOperatorActionHandler
import com.maddyhome.idea.vim.helper.EditorHelper import com.maddyhome.idea.vim.helper.EditorHelper
import com.maddyhome.idea.vim.helper.enumSetOf import com.maddyhome.idea.vim.helper.enumSetOf
import com.maddyhome.idea.vim.helper.subMode
import java.util.* import java.util.*
/** /**
@@ -46,15 +44,18 @@ class DeleteVisualLinesAction : VisualOperatorActionHandler.ForEachCaret() {
context: DataContext, context: DataContext,
cmd: Command, cmd: Command,
range: VimSelection): Boolean { range: VimSelection): Boolean {
val mode = editor.subMode
val textRange = range.toVimTextRange(false) val textRange = range.toVimTextRange(false)
return if (mode == SubMode.VISUAL_BLOCK) { val (usedCaret, usedRange, usedType) = when (range.type) {
VimPlugin.getChange() SelectionType.BLOCK_WISE -> Triple(editor.caretModel.primaryCaret, textRange, range.type)
.deleteRange(editor, editor.caretModel.primaryCaret, textRange, SelectionType.fromSubMode(mode), false) SelectionType.LINE_WISE -> Triple(caret, textRange, SelectionType.LINE_WISE)
} else { SelectionType.CHARACTER_WISE -> {
val lineRange = TextRange(EditorHelper.getLineStartForOffset(editor, textRange.startOffset), val lineRange = TextRange(
EditorHelper.getLineEndForOffset(editor, textRange.endOffset) + 1) EditorHelper.getLineStartForOffset(editor, textRange.startOffset),
VimPlugin.getChange().deleteRange(editor, caret, lineRange, SelectionType.LINE_WISE, false) EditorHelper.getLineEndForOffset(editor, textRange.endOffset) + 1
)
Triple(caret, lineRange, SelectionType.LINE_WISE)
}
} }
return VimPlugin.getChange().deleteRange(editor, usedCaret, usedRange, usedType, false)
} }
} }

View File

@@ -0,0 +1,21 @@
package com.maddyhome.idea.vim.action.change.insert
import com.intellij.openapi.actionSystem.DataContext
import com.intellij.openapi.editor.Editor
import com.maddyhome.idea.vim.KeyHandler
import com.maddyhome.idea.vim.command.Argument
import com.maddyhome.idea.vim.command.Command
import com.maddyhome.idea.vim.handler.VimActionHandler
import javax.swing.KeyStroke
class InsertCompletedDigraphAction : VimActionHandler.SingleExecution() {
override val type: Command.Type = Command.Type.INSERT
override val argumentType: Argument.Type? = Argument.Type.DIGRAPH
override fun execute(editor: Editor, context: DataContext, cmd: Command): Boolean {
// The converted digraph character has been captured as an argument, push it back through key handler
val keyStroke = KeyStroke.getKeyStroke(cmd.argument!!.character)
KeyHandler.getInstance().handleKey(editor, keyStroke, context)
return true
}
}

View File

@@ -0,0 +1,21 @@
package com.maddyhome.idea.vim.action.change.insert
import com.intellij.openapi.actionSystem.DataContext
import com.intellij.openapi.editor.Editor
import com.maddyhome.idea.vim.KeyHandler
import com.maddyhome.idea.vim.command.Argument
import com.maddyhome.idea.vim.command.Command
import com.maddyhome.idea.vim.handler.VimActionHandler
import javax.swing.KeyStroke
class InsertCompletedLiteralAction : VimActionHandler.SingleExecution() {
override val type: Command.Type = Command.Type.INSERT
override val argumentType: Argument.Type? = Argument.Type.DIGRAPH
override fun execute(editor: Editor, context: DataContext, cmd: Command): Boolean {
// The converted literal character has been captured as an argument, push it back through key handler
val keyStroke = KeyStroke.getKeyStroke(cmd.argument!!.character)
KeyHandler.getInstance().handleKey(editor, keyStroke, context)
return true
}
}

View File

@@ -29,9 +29,9 @@ import javax.swing.KeyStroke
class InsertPreviousInsertExitAction : ChangeEditorActionHandler.SingleExecution(), ComplicatedKeysAction { class InsertPreviousInsertExitAction : ChangeEditorActionHandler.SingleExecution(), ComplicatedKeysAction {
override val keyStrokesSet: Set<List<KeyStroke>> = setOf( override val keyStrokesSet: Set<List<KeyStroke>> = setOf(
listOf(KeyStroke.getKeyStroke(KeyEvent.VK_2, KeyEvent.CTRL_MASK or KeyEvent.SHIFT_MASK)), listOf(KeyStroke.getKeyStroke(KeyEvent.VK_2, KeyEvent.CTRL_DOWN_MASK or KeyEvent.SHIFT_DOWN_MASK)),
listOf(KeyStroke.getKeyStroke(KeyEvent.VK_2, KeyEvent.CTRL_MASK)), listOf(KeyStroke.getKeyStroke(KeyEvent.VK_2, KeyEvent.CTRL_DOWN_MASK)),
listOf(KeyStroke.getKeyStroke(KeyEvent.VK_AT, KeyEvent.CTRL_MASK)) listOf(KeyStroke.getKeyStroke(KeyEvent.VK_AT, KeyEvent.CTRL_DOWN_MASK))
) )
override val type: Command.Type = Command.Type.INSERT override val type: Command.Type = Command.Type.INSERT

View File

@@ -1,16 +0,0 @@
package com.maddyhome.idea.vim.action.change.insert
import com.intellij.openapi.actionSystem.DataContext
import com.intellij.openapi.editor.Editor
import com.maddyhome.idea.vim.KeyHandler
import com.maddyhome.idea.vim.command.Command
import com.maddyhome.idea.vim.handler.VimActionHandler
class StartInsertDigraphAction : VimActionHandler.SingleExecution() {
override val type: Command.Type = Command.Type.INSERT
override fun execute(editor: Editor, context: DataContext, cmd: Command): Boolean {
KeyHandler.getInstance().startDigraphSequence(editor)
return true
}
}

View File

@@ -1,16 +0,0 @@
package com.maddyhome.idea.vim.action.change.insert
import com.intellij.openapi.actionSystem.DataContext
import com.intellij.openapi.editor.Editor
import com.maddyhome.idea.vim.KeyHandler
import com.maddyhome.idea.vim.command.Command
import com.maddyhome.idea.vim.handler.VimActionHandler
class StartInsertLiteralAction : VimActionHandler.SingleExecution() {
override val type: Command.Type = Command.Type.INSERT
override fun execute(editor: Editor, context: DataContext, cmd: Command): Boolean {
KeyHandler.getInstance().startLiteralSequence(editor)
return true
}
}

View File

@@ -26,17 +26,27 @@ import com.maddyhome.idea.vim.group.copy.PutData
import com.maddyhome.idea.vim.group.copy.PutData.TextData import com.maddyhome.idea.vim.group.copy.PutData.TextData
import com.maddyhome.idea.vim.handler.ChangeEditorActionHandler import com.maddyhome.idea.vim.handler.ChangeEditorActionHandler
class PutTextAfterCursorAction : ChangeEditorActionHandler.SingleExecution() { sealed class PutTextBaseAction(
private val insertTextBeforeCaret: Boolean,
private val indent: Boolean,
private val caretAfterInsertedText: Boolean
) : ChangeEditorActionHandler.SingleExecution() {
override val type: Command.Type = Command.Type.OTHER_SELF_SYNCHRONIZED override val type: Command.Type = Command.Type.OTHER_SELF_SYNCHRONIZED
override fun execute(editor: Editor, override fun execute(editor: Editor, context: DataContext, count: Int, rawCount: Int, argument: Argument?): Boolean {
context: DataContext,
count: Int,
rawCount: Int,
argument: Argument?): Boolean {
val lastRegister = VimPlugin.getRegister().lastRegister val lastRegister = VimPlugin.getRegister().lastRegister
val textData = if (lastRegister != null) TextData(lastRegister.text, lastRegister.type, lastRegister.transferableData) else null val textData = if (lastRegister != null) TextData(lastRegister.text, lastRegister.type, lastRegister.transferableData) else null
val putData = PutData(textData, null, count, insertTextBeforeCaret = false, _indent = true, caretAfterInsertedText = false, putToLine = -1) val putData = PutData(textData, null, count, insertTextBeforeCaret, indent, caretAfterInsertedText, -1)
return VimPlugin.getPut().putText(editor, context, putData) return VimPlugin.getPut().putText(editor, context, putData)
} }
} }
class PutTextAfterCursorAction : PutTextBaseAction(insertTextBeforeCaret = false, indent = true, caretAfterInsertedText = false)
class PutTextAfterCursorActionMoveCursor : PutTextBaseAction(insertTextBeforeCaret = false, indent = true, caretAfterInsertedText = true)
class PutTextAfterCursorNoIndentAction : PutTextBaseAction(insertTextBeforeCaret = false, indent = false, caretAfterInsertedText = false)
class PutTextBeforeCursorNoIndentAction : PutTextBaseAction(insertTextBeforeCaret = true, indent = false, caretAfterInsertedText = false)
class PutTextBeforeCursorAction : PutTextBaseAction(insertTextBeforeCaret = true, indent = true, caretAfterInsertedText = false)
class PutTextBeforeCursorActionMoveCursor : PutTextBaseAction(insertTextBeforeCaret = true, indent = true, caretAfterInsertedText = true)

View File

@@ -1,42 +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.action.copy
import com.intellij.openapi.actionSystem.DataContext
import com.intellij.openapi.editor.Editor
import com.maddyhome.idea.vim.VimPlugin
import com.maddyhome.idea.vim.command.Argument
import com.maddyhome.idea.vim.command.Command
import com.maddyhome.idea.vim.group.copy.PutData
import com.maddyhome.idea.vim.group.copy.PutData.TextData
import com.maddyhome.idea.vim.handler.ChangeEditorActionHandler
class PutTextAfterCursorActionMoveCursor : ChangeEditorActionHandler.SingleExecution() {
override val type: Command.Type = Command.Type.OTHER_SELF_SYNCHRONIZED
override fun execute(editor: Editor,
context: DataContext,
count: Int,
rawCount: Int,
argument: Argument?): Boolean {
val lastRegister = VimPlugin.getRegister().lastRegister
val textData = if (lastRegister != null) TextData(lastRegister.text, lastRegister.type, lastRegister.transferableData) else null
val putData = PutData(textData, null, count, false, _indent = true, caretAfterInsertedText = true, putToLine = -1)
return VimPlugin.getPut().putText(editor, context, putData)
}
}

View File

@@ -1,42 +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.action.copy
import com.intellij.openapi.actionSystem.DataContext
import com.intellij.openapi.editor.Editor
import com.maddyhome.idea.vim.VimPlugin
import com.maddyhome.idea.vim.command.Argument
import com.maddyhome.idea.vim.command.Command
import com.maddyhome.idea.vim.group.copy.PutData
import com.maddyhome.idea.vim.group.copy.PutData.TextData
import com.maddyhome.idea.vim.handler.ChangeEditorActionHandler
class PutTextAfterCursorNoIndentAction : ChangeEditorActionHandler.SingleExecution() {
override val type: Command.Type = Command.Type.OTHER_SELF_SYNCHRONIZED
override fun execute(editor: Editor,
context: DataContext,
count: Int,
rawCount: Int,
argument: Argument?): Boolean {
val lastRegister = VimPlugin.getRegister().lastRegister
val textData = if (lastRegister != null) TextData(lastRegister.text, lastRegister.type, lastRegister.transferableData) else null
val putData = PutData(textData, null, count, insertTextBeforeCaret = false, _indent = false, caretAfterInsertedText = false, putToLine = -1)
return VimPlugin.getPut().putText(editor, context, putData)
}
}

View File

@@ -1,42 +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.action.copy
import com.intellij.openapi.actionSystem.DataContext
import com.intellij.openapi.editor.Editor
import com.maddyhome.idea.vim.VimPlugin
import com.maddyhome.idea.vim.command.Argument
import com.maddyhome.idea.vim.command.Command
import com.maddyhome.idea.vim.group.copy.PutData
import com.maddyhome.idea.vim.group.copy.PutData.TextData
import com.maddyhome.idea.vim.handler.ChangeEditorActionHandler
class PutTextBeforeCursorAction : ChangeEditorActionHandler.SingleExecution() {
override val type: Command.Type = Command.Type.OTHER_SELF_SYNCHRONIZED
override fun execute(editor: Editor,
context: DataContext,
count: Int,
rawCount: Int,
argument: Argument?): Boolean {
val lastRegister = VimPlugin.getRegister().lastRegister
val textData = if (lastRegister != null) TextData(lastRegister.text, lastRegister.type, lastRegister.transferableData) else null
val putData = PutData(textData, null, count, insertTextBeforeCaret = true, _indent = true, caretAfterInsertedText = false, putToLine = -1)
return VimPlugin.getPut().putText(editor, context, putData)
}
}

View File

@@ -1,42 +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.action.copy
import com.intellij.openapi.actionSystem.DataContext
import com.intellij.openapi.editor.Editor
import com.maddyhome.idea.vim.VimPlugin
import com.maddyhome.idea.vim.command.Argument
import com.maddyhome.idea.vim.command.Command
import com.maddyhome.idea.vim.group.copy.PutData
import com.maddyhome.idea.vim.group.copy.PutData.TextData
import com.maddyhome.idea.vim.handler.ChangeEditorActionHandler
class PutTextBeforeCursorActionMoveCursor : ChangeEditorActionHandler.SingleExecution() {
override val type: Command.Type = Command.Type.OTHER_SELF_SYNCHRONIZED
override fun execute(editor: Editor,
context: DataContext,
count: Int,
rawCount: Int,
argument: Argument?): Boolean {
val lastRegister = VimPlugin.getRegister().lastRegister
val textData = if (lastRegister != null) TextData(lastRegister.text, lastRegister.type, lastRegister.transferableData) else null
val putData = PutData(textData, null, count, insertTextBeforeCaret = true, _indent = true, caretAfterInsertedText = true, putToLine = -1)
return VimPlugin.getPut().putText(editor, context, putData)
}
}

View File

@@ -1,42 +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.action.copy
import com.intellij.openapi.actionSystem.DataContext
import com.intellij.openapi.editor.Editor
import com.maddyhome.idea.vim.VimPlugin
import com.maddyhome.idea.vim.command.Argument
import com.maddyhome.idea.vim.command.Command
import com.maddyhome.idea.vim.group.copy.PutData
import com.maddyhome.idea.vim.group.copy.PutData.TextData
import com.maddyhome.idea.vim.handler.ChangeEditorActionHandler
class PutTextBeforeCursorNoIndentAction : ChangeEditorActionHandler.SingleExecution() {
override val type: Command.Type = Command.Type.OTHER_SELF_SYNCHRONIZED
override fun execute(editor: Editor,
context: DataContext,
count: Int,
rawCount: Int,
argument: Argument?): Boolean {
val lastRegister = VimPlugin.getRegister().lastRegister
val textData = if (lastRegister != null) TextData(lastRegister.text, lastRegister.type, lastRegister.transferableData) else null
val putData = PutData(textData, null, count, insertTextBeforeCaret = true, _indent = false, caretAfterInsertedText = false, putToLine = -1)
return VimPlugin.getPut().putText(editor, context, putData)
}
}

View File

@@ -33,23 +33,33 @@ import java.util.*
/** /**
* @author vlan * @author vlan
*/ */
class PutVisualTextAction : VisualOperatorActionHandler.SingleExecution() { sealed class PutVisualTextBaseAction(
private val insertTextBeforeCaret: Boolean,
private val indent: Boolean,
private val caretAfterInsertedText: Boolean
) : VisualOperatorActionHandler.SingleExecution() {
override val type: Command.Type = Command.Type.OTHER_SELF_SYNCHRONIZED override val type: Command.Type = Command.Type.OTHER_SELF_SYNCHRONIZED
override val flags: EnumSet<CommandFlags> = enumSetOf(CommandFlags.FLAG_EXIT_VISUAL) override val flags: EnumSet<CommandFlags> = enumSetOf(CommandFlags.FLAG_EXIT_VISUAL)
override fun executeForAllCarets(editor: Editor,
context: DataContext, override fun executeForAllCarets(editor: Editor, context: DataContext, cmd: Command, caretsAndSelections: Map<Caret, VimSelection>): Boolean {
cmd: Command,
caretsAndSelections: Map<Caret, VimSelection>): Boolean {
if (caretsAndSelections.isEmpty()) return false if (caretsAndSelections.isEmpty()) return false
val textData = VimPlugin.getRegister().lastRegister?.let { PutData.TextData(it.text, it.type, it.transferableData) } val textData = VimPlugin.getRegister().lastRegister?.let { PutData.TextData(it.text, it.type, it.transferableData) }
VimPlugin.getRegister().resetRegister() VimPlugin.getRegister().resetRegister()
val insertTextBeforeCaret = cmd.keys[0].keyChar == 'P'
val selection = PutData.VisualSelection(caretsAndSelections, caretsAndSelections.values.first().type) val selection = PutData.VisualSelection(caretsAndSelections, caretsAndSelections.values.first().type)
val putData = PutData(textData, selection, cmd.count, insertTextBeforeCaret, _indent = true, caretAfterInsertedText = false) val putData = PutData(textData, selection, cmd.count, insertTextBeforeCaret, indent, caretAfterInsertedText)
return VimPlugin.getPut().putText(editor, context, putData) return VimPlugin.getPut().putText(editor, context, putData)
} }
} }
class PutVisualTextBeforeCursorAction: PutVisualTextBaseAction(insertTextBeforeCaret = true, indent = true, caretAfterInsertedText = false)
class PutVisualTextAfterCursorAction: PutVisualTextBaseAction(insertTextBeforeCaret = false, indent = true, caretAfterInsertedText = false)
class PutVisualTextBeforeCursorNoIndentAction: PutVisualTextBaseAction(insertTextBeforeCaret = true, indent = false, caretAfterInsertedText = false)
class PutVisualTextAfterCursorNoIndentAction: PutVisualTextBaseAction(insertTextBeforeCaret = false, indent = false, caretAfterInsertedText = false)
class PutVisualTextBeforeCursorMoveCursorAction: PutVisualTextBaseAction(insertTextBeforeCaret = true, indent = true, caretAfterInsertedText = true)
class PutVisualTextAfterCursorMoveCursorAction: PutVisualTextBaseAction(insertTextBeforeCaret = false, indent = true, caretAfterInsertedText = true)

View File

@@ -1,53 +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.action.copy
import com.intellij.openapi.actionSystem.DataContext
import com.intellij.openapi.editor.Caret
import com.intellij.openapi.editor.Editor
import com.maddyhome.idea.vim.VimPlugin
import com.maddyhome.idea.vim.command.Command
import com.maddyhome.idea.vim.command.CommandFlags
import com.maddyhome.idea.vim.group.copy.PutData
import com.maddyhome.idea.vim.group.visual.VimSelection
import com.maddyhome.idea.vim.handler.VisualOperatorActionHandler
import com.maddyhome.idea.vim.helper.enumSetOf
import java.util.*
/**
* @author vlan
*/
class PutVisualTextMoveCursorAction : VisualOperatorActionHandler.SingleExecution() {
override val type: Command.Type = Command.Type.OTHER_SELF_SYNCHRONIZED
override val flags: EnumSet<CommandFlags> = enumSetOf(CommandFlags.FLAG_EXIT_VISUAL)
override fun executeForAllCarets(editor: Editor, context: DataContext, cmd: Command, caretsAndSelections: Map<Caret, VimSelection>): Boolean {
if (caretsAndSelections.isEmpty()) return false
val textData = VimPlugin.getRegister().lastRegister?.let { PutData.TextData(it.text, it.type, it.transferableData) }
VimPlugin.getRegister().resetRegister()
val insertTextBeforeCaret = cmd.keys[1].keyChar == 'P'
val selection = PutData.VisualSelection(caretsAndSelections, caretsAndSelections.values.first().type)
val putData = PutData(textData, selection, cmd.count, insertTextBeforeCaret, _indent = true, caretAfterInsertedText = true)
return VimPlugin.getPut().putText(editor, context, putData)
}
}

View File

@@ -1,53 +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.action.copy
import com.intellij.openapi.actionSystem.DataContext
import com.intellij.openapi.editor.Caret
import com.intellij.openapi.editor.Editor
import com.maddyhome.idea.vim.VimPlugin
import com.maddyhome.idea.vim.command.Command
import com.maddyhome.idea.vim.command.CommandFlags
import com.maddyhome.idea.vim.group.copy.PutData
import com.maddyhome.idea.vim.group.visual.VimSelection
import com.maddyhome.idea.vim.handler.VisualOperatorActionHandler
import com.maddyhome.idea.vim.helper.enumSetOf
import java.util.*
/**
* @author vlan
*/
class PutVisualTextNoIndentAction : VisualOperatorActionHandler.SingleExecution() {
override val type: Command.Type = Command.Type.OTHER_SELF_SYNCHRONIZED
override val flags: EnumSet<CommandFlags> = enumSetOf(CommandFlags.FLAG_EXIT_VISUAL)
override fun executeForAllCarets(editor: Editor, context: DataContext, cmd: Command, caretsAndSelections: Map<Caret, VimSelection>): Boolean {
if (caretsAndSelections.isEmpty()) return false
val textData = VimPlugin.getRegister().lastRegister?.let { PutData.TextData(it.text, it.type, it.transferableData) }
VimPlugin.getRegister().resetRegister()
val insertBeforeCaret = cmd.keys[1].keyChar == 'P'
val selection = PutData.VisualSelection(caretsAndSelections, caretsAndSelections.values.first().type)
val putData = PutData(textData, selection, cmd.count, insertBeforeCaret, _indent = false, caretAfterInsertedText = false)
return VimPlugin.getPut().putText(editor, context, putData)
}
}

View File

@@ -1,41 +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.action.copy
import com.intellij.openapi.actionSystem.DataContext
import com.intellij.openapi.editor.Editor
import com.maddyhome.idea.vim.VimPlugin
import com.maddyhome.idea.vim.command.Argument
import com.maddyhome.idea.vim.command.Command
import com.maddyhome.idea.vim.command.CommandFlags
import com.maddyhome.idea.vim.handler.VimActionHandler
import com.maddyhome.idea.vim.helper.enumSetOf
import java.util.*
class SelectRegisterAction : VimActionHandler.SingleExecution() {
override val type: Command.Type = Command.Type.SELECT_REGISTER
override val flags: EnumSet<CommandFlags> = enumSetOf(CommandFlags.FLAG_EXPECT_MORE)
override val argumentType: Argument.Type = Argument.Type.CHARACTER
override fun execute(editor: Editor, context: DataContext, cmd: Command): Boolean {
val argument = cmd.argument
return argument != null && VimPlugin.getRegister().selectRegister(argument.character)
}
}

View File

@@ -34,7 +34,7 @@ class VimEditorBackSpace : VimActionHandler.SingleExecution(), ComplicatedKeysAc
private val actionName: String = "EditorBackSpace" private val actionName: String = "EditorBackSpace"
override val keyStrokesSet: Set<List<KeyStroke>> = setOf( override val keyStrokesSet: Set<List<KeyStroke>> = setOf(
listOf(KeyStroke.getKeyStroke(KeyEvent.VK_H, KeyEvent.CTRL_MASK)), listOf(KeyStroke.getKeyStroke(KeyEvent.VK_H, KeyEvent.CTRL_DOWN_MASK)),
listOf(KeyStroke.getKeyStroke(KeyEvent.VK_BACK_SPACE, 0)) listOf(KeyStroke.getKeyStroke(KeyEvent.VK_BACK_SPACE, 0))
) )
@@ -85,7 +85,7 @@ class VimEditorTab : VimActionHandler.SingleExecution(), ComplicatedKeysAction {
private val actionName: String = "EditorTab" private val actionName: String = "EditorTab"
override val keyStrokesSet: Set<List<KeyStroke>> = setOf( override val keyStrokesSet: Set<List<KeyStroke>> = setOf(
listOf(KeyStroke.getKeyStroke(KeyEvent.VK_I, KeyEvent.CTRL_MASK)), listOf(KeyStroke.getKeyStroke(KeyEvent.VK_I, KeyEvent.CTRL_DOWN_MASK)),
listOf(KeyStroke.getKeyStroke(KeyEvent.VK_TAB, 0)) listOf(KeyStroke.getKeyStroke(KeyEvent.VK_TAB, 0))
) )

View File

@@ -28,8 +28,8 @@ import javax.swing.KeyStroke
class FilePreviousAction : VimActionHandler.SingleExecution(), ComplicatedKeysAction { class FilePreviousAction : VimActionHandler.SingleExecution(), ComplicatedKeysAction {
override val keyStrokesSet: Set<List<KeyStroke>> = setOf( override val keyStrokesSet: Set<List<KeyStroke>> = setOf(
listOf(KeyStroke.getKeyStroke(KeyEvent.VK_6, KeyEvent.CTRL_MASK or KeyEvent.SHIFT_MASK)), listOf(KeyStroke.getKeyStroke(KeyEvent.VK_6, KeyEvent.CTRL_DOWN_MASK or KeyEvent.SHIFT_DOWN_MASK)),
listOf(KeyStroke.getKeyStroke(KeyEvent.VK_CIRCUMFLEX, KeyEvent.CTRL_MASK)) listOf(KeyStroke.getKeyStroke(KeyEvent.VK_CIRCUMFLEX, KeyEvent.CTRL_DOWN_MASK))
) )
override val type: Command.Type = Command.Type.OTHER_READONLY override val type: Command.Type = Command.Type.OTHER_READONLY

View File

@@ -44,10 +44,10 @@ class MotionScrollPageDownInsertModeAction : VimActionHandler.SingleExecution(),
override val keyStrokesSet: Set<List<KeyStroke>> = setOf( override val keyStrokesSet: Set<List<KeyStroke>> = setOf(
listOf(KeyStroke.getKeyStroke(KeyEvent.VK_PAGE_DOWN, 0)), listOf(KeyStroke.getKeyStroke(KeyEvent.VK_PAGE_DOWN, 0)),
listOf(KeyStroke.getKeyStroke(KeyEvent.VK_DOWN, KeyEvent.CTRL_MASK)), listOf(KeyStroke.getKeyStroke(KeyEvent.VK_DOWN, KeyEvent.CTRL_DOWN_MASK)),
listOf(KeyStroke.getKeyStroke(KeyEvent.VK_KP_DOWN, KeyEvent.CTRL_MASK)), listOf(KeyStroke.getKeyStroke(KeyEvent.VK_KP_DOWN, KeyEvent.CTRL_DOWN_MASK)),
listOf(KeyStroke.getKeyStroke(KeyEvent.VK_DOWN, KeyEvent.SHIFT_MASK)), listOf(KeyStroke.getKeyStroke(KeyEvent.VK_DOWN, KeyEvent.SHIFT_DOWN_MASK)),
listOf(KeyStroke.getKeyStroke(KeyEvent.VK_KP_DOWN, KeyEvent.SHIFT_MASK)) listOf(KeyStroke.getKeyStroke(KeyEvent.VK_KP_DOWN, KeyEvent.SHIFT_DOWN_MASK))
) )
override val type: Command.Type = Command.Type.OTHER_READONLY override val type: Command.Type = Command.Type.OTHER_READONLY

View File

@@ -44,10 +44,10 @@ class MotionScrollPageUpInsertModeAction : VimActionHandler.SingleExecution(), C
override val keyStrokesSet: Set<List<KeyStroke>> = setOf( override val keyStrokesSet: Set<List<KeyStroke>> = setOf(
listOf(KeyStroke.getKeyStroke(KeyEvent.VK_PAGE_UP, 0)), listOf(KeyStroke.getKeyStroke(KeyEvent.VK_PAGE_UP, 0)),
listOf(KeyStroke.getKeyStroke(KeyEvent.VK_UP, KeyEvent.CTRL_MASK)), listOf(KeyStroke.getKeyStroke(KeyEvent.VK_UP, KeyEvent.CTRL_DOWN_MASK)),
listOf(KeyStroke.getKeyStroke(KeyEvent.VK_KP_UP, KeyEvent.CTRL_MASK)), listOf(KeyStroke.getKeyStroke(KeyEvent.VK_KP_UP, KeyEvent.CTRL_DOWN_MASK)),
listOf(KeyStroke.getKeyStroke(KeyEvent.VK_UP, KeyEvent.SHIFT_MASK)), listOf(KeyStroke.getKeyStroke(KeyEvent.VK_UP, KeyEvent.SHIFT_DOWN_MASK)),
listOf(KeyStroke.getKeyStroke(KeyEvent.VK_KP_UP, KeyEvent.SHIFT_MASK)) listOf(KeyStroke.getKeyStroke(KeyEvent.VK_KP_UP, KeyEvent.SHIFT_DOWN_MASK))
) )
override val type: Command.Type = Command.Type.OTHER_READONLY override val type: Command.Type = Command.Type.OTHER_READONLY

View File

@@ -50,10 +50,10 @@ class MotionWordLeftInsertAction : MotionActionHandler.ForEachCaret(), Complicat
override val motionType: MotionType = MotionType.EXCLUSIVE override val motionType: MotionType = MotionType.EXCLUSIVE
override val keyStrokesSet: Set<List<KeyStroke>> = setOf( override val keyStrokesSet: Set<List<KeyStroke>> = setOf(
listOf(KeyStroke.getKeyStroke(KeyEvent.VK_LEFT, KeyEvent.CTRL_MASK)), listOf(KeyStroke.getKeyStroke(KeyEvent.VK_LEFT, KeyEvent.CTRL_DOWN_MASK)),
listOf(KeyStroke.getKeyStroke(KeyEvent.VK_KP_LEFT, KeyEvent.CTRL_MASK)), listOf(KeyStroke.getKeyStroke(KeyEvent.VK_KP_LEFT, KeyEvent.CTRL_DOWN_MASK)),
listOf(KeyStroke.getKeyStroke(KeyEvent.VK_LEFT, KeyEvent.SHIFT_MASK)), listOf(KeyStroke.getKeyStroke(KeyEvent.VK_LEFT, KeyEvent.SHIFT_DOWN_MASK)),
listOf(KeyStroke.getKeyStroke(KeyEvent.VK_KP_LEFT, KeyEvent.SHIFT_MASK)) listOf(KeyStroke.getKeyStroke(KeyEvent.VK_KP_LEFT, KeyEvent.SHIFT_DOWN_MASK))
) )
override val flags: EnumSet<CommandFlags> = enumSetOf(CommandFlags.FLAG_SAVE_STROKE) override val flags: EnumSet<CommandFlags> = enumSetOf(CommandFlags.FLAG_SAVE_STROKE)

View File

@@ -50,10 +50,10 @@ class MotionWordRightInsertAction : MotionActionHandler.ForEachCaret(), Complica
override val motionType: MotionType = MotionType.EXCLUSIVE override val motionType: MotionType = MotionType.EXCLUSIVE
override val keyStrokesSet: Set<List<KeyStroke>> = setOf( override val keyStrokesSet: Set<List<KeyStroke>> = setOf(
listOf(KeyStroke.getKeyStroke(KeyEvent.VK_RIGHT, KeyEvent.CTRL_MASK)), listOf(KeyStroke.getKeyStroke(KeyEvent.VK_RIGHT, KeyEvent.CTRL_DOWN_MASK)),
listOf(KeyStroke.getKeyStroke(KeyEvent.VK_KP_RIGHT, KeyEvent.CTRL_MASK)), listOf(KeyStroke.getKeyStroke(KeyEvent.VK_KP_RIGHT, KeyEvent.CTRL_DOWN_MASK)),
listOf(KeyStroke.getKeyStroke(KeyEvent.VK_RIGHT, KeyEvent.SHIFT_MASK)), listOf(KeyStroke.getKeyStroke(KeyEvent.VK_RIGHT, KeyEvent.SHIFT_DOWN_MASK)),
listOf(KeyStroke.getKeyStroke(KeyEvent.VK_KP_RIGHT, KeyEvent.SHIFT_MASK)) listOf(KeyStroke.getKeyStroke(KeyEvent.VK_KP_RIGHT, KeyEvent.SHIFT_DOWN_MASK))
) )
override val flags: EnumSet<CommandFlags> = enumSetOf(CommandFlags.FLAG_SAVE_STROKE) override val flags: EnumSet<CommandFlags> = enumSetOf(CommandFlags.FLAG_SAVE_STROKE)

View File

@@ -50,6 +50,6 @@ class Argument private constructor(
override fun execute(editor: Editor, context: DataContext, cmd: Command): Boolean = true override fun execute(editor: Editor, context: DataContext, cmd: Command): Boolean = true
override val type: Command.Type = Command.Type.UNDEFINED override val type: Command.Type = Command.Type.UNDEFINED
}, Command.Type.UNDEFINED, EnumSet.noneOf(CommandFlags::class.java), emptyList()) }, Command.Type.UNDEFINED, EnumSet.noneOf(CommandFlags::class.java))
} }
} }

View File

@@ -20,7 +20,6 @@ package com.maddyhome.idea.vim.command
import com.maddyhome.idea.vim.handler.EditorActionHandlerBase import com.maddyhome.idea.vim.handler.EditorActionHandlerBase
import java.util.* import java.util.*
import javax.swing.KeyStroke
/** /**
* This represents a single Vim command to be executed (operator, motion, text object, etc.). It may optionally include * This represents a single Vim command to be executed (operator, motion, text object, etc.). It may optionally include
@@ -28,14 +27,17 @@ import javax.swing.KeyStroke
*/ */
data class Command( data class Command(
var rawCount: Int, var rawCount: Int,
var action: EditorActionHandlerBase, var action: EditorActionHandlerBase?,
val type: Type, val type: Type,
var flags: EnumSet<CommandFlags>, var flags: EnumSet<CommandFlags>
var keys: List<KeyStroke>
) { ) {
constructor(rawCount: Int, register: Char): this(rawCount, null, Type.SELECT_REGISTER, EnumSet.of(CommandFlags.FLAG_EXPECT_MORE)) {
this.register = register
}
init { init {
action.process(this) action?.process(this)
} }
var count: Int var count: Int
@@ -45,6 +47,7 @@ data class Command(
} }
var argument: Argument? = null var argument: Argument? = null
var register: Char? = null
enum class Type { enum class Type {
/** /**
@@ -72,12 +75,12 @@ data class Command(
*/ */
COPY, COPY,
PASTE, PASTE,
// TODO REMOVE?
RESET,
/** /**
* Represents commands that select the register. * Represents commands that select the register.
*/ */
SELECT_REGISTER, SELECT_REGISTER,
// TODO REMOVE?
RESET,
OTHER_READONLY, OTHER_READONLY,
OTHER_WRITABLE, OTHER_WRITABLE,
/** /**
@@ -88,7 +91,7 @@ data class Command(
val isRead: Boolean val isRead: Boolean
get() = when (this) { get() = when (this) {
MOTION, COPY, SELECT_REGISTER, OTHER_READONLY, COMPLETION -> true MOTION, COPY, OTHER_READONLY, COMPLETION -> true
else -> false else -> false
} }

View File

@@ -9,12 +9,13 @@ import java.util.*
import javax.swing.KeyStroke import javax.swing.KeyStroke
class CommandBuilder(private var currentCommandPartNode: CommandPartNode) { class CommandBuilder(private var currentCommandPartNode: CommandPartNode) {
private val commandParts = Stack<Command>() private val commandParts = ArrayDeque<Command>()
private var keys = mutableListOf<KeyStroke>() private var keyList = mutableListOf<KeyStroke>()
var commandState = CurrentCommandState.NEW_COMMAND var commandState = CurrentCommandState.NEW_COMMAND
var count = 0 var count = 0
private set private set
val keys: Iterable<KeyStroke> get() = keyList
// The argument type for the current command part's action. Kept separate to handle digraphs and characters. We first // The argument type for the current command part's action. Kept separate to handle digraphs and characters. We first
// try to accept a digraph. If we get it, set expected argument type to character and handle the converted key. If we // try to accept a digraph. If we get it, set expected argument type to character and handle the converted key. If we
@@ -24,7 +25,7 @@ class CommandBuilder(private var currentCommandPartNode: CommandPartNode) {
val isReady get() = commandState == CurrentCommandState.READY val isReady get() = commandState == CurrentCommandState.READY
val isBad get() = commandState == CurrentCommandState.BAD_COMMAND val isBad get() = commandState == CurrentCommandState.BAD_COMMAND
val isEmpty get() = commandParts.empty() val isEmpty get() = commandParts.isEmpty()
val isAtDefaultState get() = isEmpty && count == 0 && expectedArgumentType == null val isAtDefaultState get() = isEmpty && count == 0 && expectedArgumentType == null
val isExpectingCount: Boolean val isExpectingCount: Boolean
@@ -35,14 +36,23 @@ class CommandBuilder(private var currentCommandPartNode: CommandPartNode) {
} }
fun pushCommandPart(action: EditorActionHandlerBase) { fun pushCommandPart(action: EditorActionHandlerBase) {
commandParts.push(Command(count, action, action.type, action.flags, keys)) commandParts.add(Command(count, action, action.type, action.flags))
expectedArgumentType = action.argumentType expectedArgumentType = action.argumentType
keys = mutableListOf() count = 0
} }
fun popCommandPart() { fun pushCommandPart(register: Char) {
commandParts.pop() // We will never execute this command, but we need to push something to correctly handle counts on either side of a
expectedArgumentType = if (commandParts.size > 0) commandParts.peek().action.argumentType else null // select register command part. e.g. 2"a2d2w or even crazier 2"a2"a2"a2"a2"a2d2w
commandParts.add(Command(count, register))
expectedArgumentType = null
count = 0
}
fun popCommandPart(): Command {
val command = commandParts.removeLast()
expectedArgumentType = if (commandParts.size > 0) commandParts.peekLast().action?.argumentType else null
return command
} }
fun fallbackToCharacterArgument() { fun fallbackToCharacterArgument() {
@@ -52,16 +62,24 @@ class CommandBuilder(private var currentCommandPartNode: CommandPartNode) {
expectedArgumentType = Argument.Type.CHARACTER expectedArgumentType = Argument.Type.CHARACTER
} }
fun addKey(keyStroke: KeyStroke) { fun addKey(key: KeyStroke) {
keys.add(keyStroke) keyList.add(key)
} }
fun addCountCharacter(chKey: Char) { fun addCountCharacter(key: KeyStroke) {
count = (count * 10) + (chKey - '0') count = (count * 10) + (key.keyChar - '0')
// If count overflows and flips negative, reset to 999999999L. In Vim, count is a long, which is *usually* 32 bits,
// so will flip at 2147483648. We store count as an Int, which is also 32 bit.
// See https://github.com/vim/vim/blob/b376ace1aeaa7614debc725487d75c8f756dd773/src/normal.c#L631
if (count < 0) {
count = 999999999
}
addKey(key)
} }
fun deleteCountCharacter() { fun deleteCountCharacter() {
count /= 10 count /= 10
keyList.removeAt(keyList.size - 1)
} }
fun setCurrentCommandPartNode(newNode: CommandPartNode) { fun setCurrentCommandPartNode(newNode: CommandPartNode) {
@@ -74,7 +92,7 @@ class CommandBuilder(private var currentCommandPartNode: CommandPartNode) {
fun isAwaitingCharOrDigraphArgument(): Boolean { fun isAwaitingCharOrDigraphArgument(): Boolean {
if (commandParts.size == 0) return false if (commandParts.size == 0) return false
val argumentType = commandParts.peek().action.argumentType val argumentType = commandParts.peekLast().action?.argumentType
return argumentType == Argument.Type.CHARACTER || argumentType == Argument.Type.DIGRAPH return argumentType == Argument.Type.CHARACTER || argumentType == Argument.Type.DIGRAPH
} }
@@ -88,12 +106,12 @@ class CommandBuilder(private var currentCommandPartNode: CommandPartNode) {
} }
fun completeCommandPart(argument: Argument) { fun completeCommandPart(argument: Argument) {
commandParts.peek().argument = argument commandParts.peekLast().argument = argument
commandState = CurrentCommandState.READY commandState = CurrentCommandState.READY
} }
fun isDuplicateOperatorKeyStroke(key: KeyStroke): Boolean { fun isDuplicateOperatorKeyStroke(key: KeyStroke): Boolean {
val action = commandParts.peek()?.action as? DuplicableOperatorAction val action = commandParts.peekLast()?.action as? DuplicableOperatorAction
return action?.duplicateWith == key.keyChar return action?.duplicateWith == key.keyChar
} }
@@ -102,27 +120,23 @@ class CommandBuilder(private var currentCommandPartNode: CommandPartNode) {
} }
fun buildCommand(): Command { fun buildCommand(): Command {
/* Let's go through the command stack and merge it all into one command. At this time there should never
be more than two commands on the stack - one is the actual command, and the other would be a motion
command argument needed by the first command */
var command: Command = commandParts.pop()
while (commandParts.size > 0) {
val top: Command = commandParts.pop()
top.argument = Argument(command)
command = top
}
return fixCommandCounts(command)
}
private fun fixCommandCounts(command: Command): Command { var command: Command = commandParts.removeFirst()
// If we have a command with a motion command argument, both could have their own counts. We need to adjust the while (commandParts.size > 0) {
// counts, so the motion gets the product of both counts, and the count associated with the command gets reset. val next = commandParts.removeFirst()
// E.g. 3c2w (change 2 words, three times) becomes c6w (change 6 words) next.count = if (command.rawCount == 0 && next.rawCount == 0) 0 else command.count * next.count
if (command.argument?.type === Argument.Type.MOTION) {
val motion = command.argument!!.motion
motion.count = if (command.rawCount == 0 && motion.rawCount == 0) 0 else command.count * motion.count
command.count = 0 command.count = 0
if (command.type == Command.Type.SELECT_REGISTER) {
next.register = command.register
command.register = null
command = next
}
else {
command.argument = Argument(next)
assert(commandParts.size == 0)
}
} }
expectedArgumentType = null
return command return command
} }
@@ -130,11 +144,11 @@ class CommandBuilder(private var currentCommandPartNode: CommandPartNode) {
resetInProgressCommandPart(commandPartNode) resetInProgressCommandPart(commandPartNode)
commandState = CurrentCommandState.NEW_COMMAND commandState = CurrentCommandState.NEW_COMMAND
commandParts.clear() commandParts.clear()
keyList.clear()
expectedArgumentType = null expectedArgumentType = null
} }
fun resetInProgressCommandPart(commandPartNode: CommandPartNode) { fun resetInProgressCommandPart(commandPartNode: CommandPartNode) {
keys.clear()
count = 0 count = 0
setCurrentCommandPartNode(commandPartNode) setCurrentCommandPartNode(commandPartNode)
} }

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;
} }
@@ -168,6 +164,12 @@ public class CommandState {
} }
} }
public void resetRegisterPending() {
if (getSubMode() == SubMode.REGISTER_PENDING) {
popModes();
}
}
private void resetModes() { private void resetModes() {
modeStates.clear(); modeStates.clear();
setMappingMode(); setMappingMode();
@@ -202,13 +204,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 +294,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);
@@ -350,12 +349,12 @@ public class CommandState {
} }
public enum SubMode { public enum SubMode {
NONE, SINGLE_COMMAND, OP_PENDING, VISUAL_CHARACTER, VISUAL_LINE, VISUAL_BLOCK NONE, SINGLE_COMMAND, OP_PENDING, REGISTER_PENDING, VISUAL_CHARACTER, VISUAL_LINE, VISUAL_BLOCK
} }
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 +362,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

@@ -60,4 +60,8 @@ enum class SelectionType(val value: Int) {
else -> CHARACTER_WISE else -> CHARACTER_WISE
} }
} }
} }
val SelectionType.isLine get() = this == SelectionType.LINE_WISE
val SelectionType.isChar get() = this == SelectionType.CHARACTER_WISE
val SelectionType.isBlock get() = this == SelectionType.BLOCK_WISE

View File

@@ -29,12 +29,14 @@ class Register {
val type: SelectionType val type: SelectionType
val keys: MutableList<KeyStroke> val keys: MutableList<KeyStroke>
val transferableData: MutableList<out TextBlockTransferableData> val transferableData: MutableList<out TextBlockTransferableData>
val rawText: String?
constructor(name: Char, type: SelectionType, keys: MutableList<KeyStroke>) { constructor(name: Char, type: SelectionType, keys: MutableList<KeyStroke>) {
this.name = name this.name = name
this.type = type this.type = type
this.keys = keys this.keys = keys
this.transferableData = mutableListOf() this.transferableData = mutableListOf()
this.rawText = text
} }
constructor(name: Char, type: SelectionType, text: String, transferableData: MutableList<out TextBlockTransferableData>) { constructor(name: Char, type: SelectionType, text: String, transferableData: MutableList<out TextBlockTransferableData>) {
@@ -42,6 +44,15 @@ class Register {
this.type = type this.type = type
this.keys = StringHelper.stringToKeys(text) this.keys = StringHelper.stringToKeys(text)
this.transferableData = transferableData this.transferableData = transferableData
this.rawText = text
}
constructor(name: Char, type: SelectionType, text: String, transferableData: MutableList<out TextBlockTransferableData>, rawText: String) {
this.name = name
this.type = type
this.keys = StringHelper.stringToKeys(text)
this.transferableData = transferableData
this.rawText = rawText
} }
val text: String? val text: String?

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

@@ -17,7 +17,6 @@
*/ */
package com.maddyhome.idea.vim.ex package com.maddyhome.idea.vim.ex
import com.intellij.openapi.actionSystem.DataContext
import com.intellij.openapi.editor.Caret import com.intellij.openapi.editor.Caret
import com.intellij.openapi.editor.Editor import com.intellij.openapi.editor.Editor
import com.maddyhome.idea.vim.common.TextRange import com.maddyhome.idea.vim.common.TextRange
@@ -28,33 +27,33 @@ class ExCommand(val ranges: Ranges, val command: String, var argument: String) {
fun getLine(editor: Editor): Int = ranges.getLine(editor) fun getLine(editor: Editor): Int = ranges.getLine(editor)
fun getLine(editor: Editor, caret: Caret, context: DataContext): Int = ranges.getLine(editor, caret, context) fun getLine(editor: Editor, caret: Caret): Int = ranges.getLine(editor, caret)
fun getCount(editor: Editor, context: DataContext?, defaultCount: Int, checkCount: Boolean): Int { fun getCount(editor: Editor, defaultCount: Int, checkCount: Boolean): Int {
val count = if (checkCount) countArgument else -1 val count = if (checkCount) countArgument else -1
val res = ranges.getCount(editor, count) val res = ranges.getCount(editor, count)
return if (res == -1) defaultCount else res return if (res == -1) defaultCount else res
} }
fun getCount(editor: Editor, caret: Caret, context: DataContext, defaultCount: Int, checkCount: Boolean): Int { fun getCount(editor: Editor, caret: Caret, defaultCount: Int, checkCount: Boolean): Int {
val count = ranges.getCount(editor, caret, context, if (checkCount) countArgument else -1) val count = ranges.getCount(editor, caret, if (checkCount) countArgument else -1)
return if (count == -1) defaultCount else count return if (count == -1) defaultCount else count
} }
fun getLineRange(editor: Editor): LineRange = ranges.getLineRange(editor, -1) fun getLineRange(editor: Editor): LineRange = ranges.getLineRange(editor, -1)
fun getLineRange(editor: Editor, caret: Caret, context: DataContext): LineRange { fun getLineRange(editor: Editor, caret: Caret): LineRange {
return ranges.getLineRange(editor, caret, context, -1) return ranges.getLineRange(editor, caret, -1)
} }
fun getTextRange(editor: Editor, context: DataContext?, checkCount: Boolean): TextRange { fun getTextRange(editor: Editor, checkCount: Boolean): TextRange {
val count = if (checkCount) countArgument else -1 val count = if (checkCount) countArgument else -1
return ranges.getTextRange(editor, context, count) return ranges.getTextRange(editor, count)
} }
fun getTextRange(editor: Editor, caret: Caret, context: DataContext, checkCount: Boolean): TextRange { fun getTextRange(editor: Editor, caret: Caret, checkCount: Boolean): TextRange {
return ranges.getTextRange(editor, caret, context, if (checkCount) countArgument else -1) return ranges.getTextRange(editor, caret, if (checkCount) countArgument else -1)
} }
private val countArgument: Int private val countArgument: Int

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

@@ -22,7 +22,11 @@ import com.intellij.openapi.actionSystem.DataContext
import com.intellij.openapi.diagnostic.Logger import com.intellij.openapi.diagnostic.Logger
import com.intellij.openapi.editor.Editor import com.intellij.openapi.editor.Editor
import com.maddyhome.idea.vim.VimPlugin import com.maddyhome.idea.vim.VimPlugin
import com.maddyhome.idea.vim.ex.* import com.maddyhome.idea.vim.ex.CommandHandler
import com.maddyhome.idea.vim.ex.ExCommand
import com.maddyhome.idea.vim.ex.ExException
import com.maddyhome.idea.vim.ex.ExOutputModel
import com.maddyhome.idea.vim.ex.flags
import com.maddyhome.idea.vim.helper.MessageHelper import com.maddyhome.idea.vim.helper.MessageHelper
import com.maddyhome.idea.vim.helper.Msg import com.maddyhome.idea.vim.helper.Msg
import java.io.IOException import java.io.IOException
@@ -54,7 +58,7 @@ class CmdFilterHandler : CommandHandler.SingleExecution() {
true true
} else { } else {
// Filter // Filter
val range = cmd.getTextRange(editor, context, false) val range = cmd.getTextRange(editor, false)
VimPlugin.getProcess().executeFilter(editor, range, command) VimPlugin.getProcess().executeFilter(editor, range, command)
} }
} catch (e: IOException) { } catch (e: IOException) {

View File

@@ -22,7 +22,10 @@ import com.intellij.openapi.actionSystem.DataContext
import com.intellij.openapi.editor.Editor import com.intellij.openapi.editor.Editor
import com.maddyhome.idea.vim.VimPlugin import com.maddyhome.idea.vim.VimPlugin
import com.maddyhome.idea.vim.command.SelectionType import com.maddyhome.idea.vim.command.SelectionType
import com.maddyhome.idea.vim.ex.* import com.maddyhome.idea.vim.ex.CommandHandler
import com.maddyhome.idea.vim.ex.CommandParser
import com.maddyhome.idea.vim.ex.ExCommand
import com.maddyhome.idea.vim.ex.flags
import com.maddyhome.idea.vim.group.copy.PutData import com.maddyhome.idea.vim.group.copy.PutData
import com.maddyhome.idea.vim.helper.EditorHelper import com.maddyhome.idea.vim.helper.EditorHelper
@@ -31,11 +34,11 @@ class CopyTextHandler : CommandHandler.SingleExecution() {
override fun execute(editor: Editor, context: DataContext, cmd: ExCommand): Boolean { override fun execute(editor: Editor, context: DataContext, cmd: ExCommand): Boolean {
val carets = EditorHelper.getOrderedCaretsList(editor) val carets = EditorHelper.getOrderedCaretsList(editor)
for (caret in carets) { for (caret in carets) {
val range = cmd.getTextRange(editor, caret, context, false) val range = cmd.getTextRange(editor, caret, false)
val text = EditorHelper.getText(editor, range.startOffset, range.endOffset) val text = EditorHelper.getText(editor, range.startOffset, range.endOffset)
val arg = CommandParser.getInstance().parse(cmd.argument) val arg = CommandParser.getInstance().parse(cmd.argument)
val line = arg.ranges.getFirstLine(editor, caret, context) val line = arg.ranges.getFirstLine(editor, caret)
val transferableData = VimPlugin.getRegister().getTransferableData(editor, range, text) val transferableData = VimPlugin.getRegister().getTransferableData(editor, range, text)
val textData = PutData.TextData(text, SelectionType.LINE_WISE, transferableData) val textData = PutData.TextData(text, SelectionType.LINE_WISE, transferableData)

View File

@@ -40,7 +40,7 @@ class DeleteLinesHandler : CommandHandler.ForEachCaret() {
if (!VimPlugin.getRegister().selectRegister(register)) return false if (!VimPlugin.getRegister().selectRegister(register)) return false
val textRange = cmd.getTextRange(editor, caret, context, true) val textRange = cmd.getTextRange(editor, caret, true)
return VimPlugin.getChange().deleteRange(editor, caret, textRange, SelectionType.LINE_WISE, false) return VimPlugin.getChange().deleteRange(editor, caret, textRange, SelectionType.LINE_WISE, false)
} }
} }

View File

@@ -29,7 +29,7 @@ import com.maddyhome.idea.vim.ex.flags
class FileHandler : CommandHandler.SingleExecution() { class FileHandler : CommandHandler.SingleExecution() {
override val argFlags = flags(RangeFlag.RANGE_IS_COUNT, ARGUMENT_FORBIDDEN, Access.READ_ONLY) override val argFlags = flags(RangeFlag.RANGE_IS_COUNT, ARGUMENT_FORBIDDEN, Access.READ_ONLY)
override fun execute(editor: Editor, context: DataContext, cmd: ExCommand): Boolean { override fun execute(editor: Editor, context: DataContext, cmd: ExCommand): Boolean {
val count = cmd.getCount(editor, context, 0, false) val count = cmd.getCount(editor, 0, false)
VimPlugin.getFile().displayFileInfo(editor, count > 0) VimPlugin.getFile().displayFileInfo(editor, count > 0)
return true return true
} }

View File

@@ -32,7 +32,7 @@ class GotoCharacterHandler : CommandHandler.ForEachCaret() {
override val argFlags: CommandHandlerFlags = flags(RangeFlag.RANGE_IS_COUNT, ArgumentFlag.ARGUMENT_OPTIONAL, Access.READ_ONLY) override val argFlags: CommandHandlerFlags = flags(RangeFlag.RANGE_IS_COUNT, ArgumentFlag.ARGUMENT_OPTIONAL, Access.READ_ONLY)
override fun execute(editor: Editor, caret: Caret, context: DataContext, cmd: ExCommand): Boolean { override fun execute(editor: Editor, caret: Caret, context: DataContext, cmd: ExCommand): Boolean {
val count = cmd.getCount(editor, caret, context, 1, true) val count = cmd.getCount(editor, caret, 1, true)
if (count <= 0) return false if (count <= 0) return false
val offset = VimPlugin.getMotion().moveCaretToNthCharacter(editor, count - 1) val offset = VimPlugin.getMotion().moveCaretToNthCharacter(editor, count - 1)

View File

@@ -47,7 +47,7 @@ class GotoLineHandler : CommandHandler.ForEachCaret() {
* @return True if able to perform the command, false if not * @return True if able to perform the command, false if not
*/ */
override fun execute(editor: Editor, caret: Caret, context: DataContext, cmd: ExCommand): Boolean { override fun execute(editor: Editor, caret: Caret, context: DataContext, cmd: ExCommand): Boolean {
val line = min(cmd.getLine(editor, caret, context), EditorHelper.getLineCount(editor) - 1) val line = min(cmd.getLine(editor, caret), EditorHelper.getLineCount(editor) - 1)
if (line >= 0) { if (line >= 0) {
MotionGroup.moveCaret(editor, caret, VimPlugin.getMotion().moveCaretToLineStartSkipLeading(editor, line)) MotionGroup.moveCaret(editor, caret, VimPlugin.getMotion().moveCaretToLineStartSkipLeading(editor, line))

View File

@@ -23,7 +23,10 @@ import com.intellij.openapi.editor.Caret
import com.intellij.openapi.editor.Editor import com.intellij.openapi.editor.Editor
import com.maddyhome.idea.vim.VimPlugin import com.maddyhome.idea.vim.VimPlugin
import com.maddyhome.idea.vim.common.TextRange import com.maddyhome.idea.vim.common.TextRange
import com.maddyhome.idea.vim.ex.* import com.maddyhome.idea.vim.ex.CommandHandler
import com.maddyhome.idea.vim.ex.CommandHandlerFlags
import com.maddyhome.idea.vim.ex.ExCommand
import com.maddyhome.idea.vim.ex.flags
class JoinLinesHandler : CommandHandler.ForEachCaret() { class JoinLinesHandler : CommandHandler.ForEachCaret() {
override val argFlags: CommandHandlerFlags = flags(RangeFlag.RANGE_OPTIONAL, ArgumentFlag.ARGUMENT_OPTIONAL, Access.WRITABLE) override val argFlags: CommandHandlerFlags = flags(RangeFlag.RANGE_OPTIONAL, ArgumentFlag.ARGUMENT_OPTIONAL, Access.WRITABLE)
@@ -32,7 +35,7 @@ class JoinLinesHandler : CommandHandler.ForEachCaret() {
val arg = cmd.argument val arg = cmd.argument
val spaces = arg.isEmpty() || arg[0] != '!' val spaces = arg.isEmpty() || arg[0] != '!'
val textRange = cmd.getTextRange(editor, caret, context, true) ?: return false val textRange = cmd.getTextRange(editor, caret, true)
return VimPlugin.getChange().deleteJoinRange(editor, caret, TextRange(textRange.startOffset, return VimPlugin.getChange().deleteJoinRange(editor, caret, TextRange(textRange.startOffset,
textRange.endOffset - 1), spaces) textRange.endOffset - 1), spaces)

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

@@ -24,7 +24,12 @@ import com.intellij.openapi.editor.Editor
import com.maddyhome.idea.vim.VimPlugin import com.maddyhome.idea.vim.VimPlugin
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.ex.* import com.maddyhome.idea.vim.ex.CommandHandler
import com.maddyhome.idea.vim.ex.CommandParser
import com.maddyhome.idea.vim.ex.ExCommand
import com.maddyhome.idea.vim.ex.ExException
import com.maddyhome.idea.vim.ex.InvalidRangeException
import com.maddyhome.idea.vim.ex.flags
import com.maddyhome.idea.vim.ex.ranges.LineRange import com.maddyhome.idea.vim.ex.ranges.LineRange
import com.maddyhome.idea.vim.group.copy.PutData import com.maddyhome.idea.vim.group.copy.PutData
import com.maddyhome.idea.vim.helper.EditorHelper import com.maddyhome.idea.vim.helper.EditorHelper
@@ -48,10 +53,10 @@ class MoveTextHandler : CommandHandler.SingleExecution() {
var lastRange: TextRange? = null var lastRange: TextRange? = null
for (caret in carets) { for (caret in carets) {
val range = cmd.getTextRange(editor, caret, context, false) val range = cmd.getTextRange(editor, caret, false)
val lineRange = cmd.getLineRange(editor, caret, context) val lineRange = cmd.getLineRange(editor, caret)
line = min(line, normalizeLine(editor, caret, context, command, lineRange)) line = min(line, normalizeLine(editor, caret, command, lineRange))
texts.add(EditorHelper.getText(editor, range.startOffset, range.endOffset)) texts.add(EditorHelper.getText(editor, range.startOffset, range.endOffset))
if (lastRange == null || lastRange.startOffset != range.startOffset && lastRange.endOffset != range.endOffset) { if (lastRange == null || lastRange.startOffset != range.startOffset && lastRange.endOffset != range.endOffset) {
@@ -75,9 +80,9 @@ class MoveTextHandler : CommandHandler.SingleExecution() {
} }
@Throws @Throws
private fun normalizeLine(editor: Editor, caret: Caret, context: DataContext, private fun normalizeLine(editor: Editor, caret: Caret, command: ExCommand,
command: ExCommand, lineRange: LineRange): Int { lineRange: LineRange): Int {
var line = command.ranges.getFirstLine(editor, caret, context) var line = command.ranges.getFirstLine(editor, caret)
val adj = lineRange.endLine - lineRange.startLine + 1 val adj = lineRange.endLine - lineRange.startLine + 1
if (line >= lineRange.endLine) if (line >= lineRange.endLine)
line -= adj line -= adj

View File

@@ -29,7 +29,7 @@ class NextFileHandler : CommandHandler.SingleExecution() {
override val argFlags = flags(RangeFlag.RANGE_IS_COUNT, ArgumentFlag.ARGUMENT_OPTIONAL, Access.READ_ONLY) override val argFlags = flags(RangeFlag.RANGE_IS_COUNT, ArgumentFlag.ARGUMENT_OPTIONAL, Access.READ_ONLY)
override fun execute(editor: Editor, context: DataContext, cmd: ExCommand): Boolean { override fun execute(editor: Editor, context: DataContext, cmd: ExCommand): Boolean {
val count = cmd.getCount(editor, context, 1, true) val count = cmd.getCount(editor, 1, true)
VimPlugin.getMark().saveJumpLocation(editor) VimPlugin.getMark().saveJumpLocation(editor)
VimPlugin.getFile().selectNextFile(count, context) VimPlugin.getFile().selectNextFile(count, context)
return true return true

View File

@@ -28,7 +28,7 @@ import com.maddyhome.idea.vim.ex.flags
class PreviousFileHandler : CommandHandler.SingleExecution() { class PreviousFileHandler : CommandHandler.SingleExecution() {
override val argFlags = flags(RangeFlag.RANGE_IS_COUNT, ArgumentFlag.ARGUMENT_OPTIONAL, Access.READ_ONLY) override val argFlags = flags(RangeFlag.RANGE_IS_COUNT, ArgumentFlag.ARGUMENT_OPTIONAL, Access.READ_ONLY)
override fun execute(editor: Editor, context: DataContext, cmd: ExCommand): Boolean { override fun execute(editor: Editor, context: DataContext, cmd: ExCommand): Boolean {
val count = cmd.getCount(editor, context, 1, true) val count = cmd.getCount(editor, 1, true)
VimPlugin.getMark().saveJumpLocation(editor) VimPlugin.getMark().saveJumpLocation(editor)
VimPlugin.getFile().selectNextFile(-count, context) VimPlugin.getFile().selectNextFile(-count, context)

View File

@@ -37,7 +37,7 @@ class RepeatHandler : CommandHandler.ForEachCaret() {
if (arg == '@') arg = lastArg if (arg == '@') arg = lastArg
lastArg = arg lastArg = arg
val line = cmd.getLine(editor, caret, context) val line = cmd.getLine(editor, caret)
MotionGroup.moveCaret(editor, caret, VimPlugin.getMotion().moveCaretToLine(editor, line, editor.caretModel.primaryCaret)) MotionGroup.moveCaret(editor, caret, VimPlugin.getMotion().moveCaretToLine(editor, line, editor.caretModel.primaryCaret))
if (arg == ':') { if (arg == ':') {

View File

@@ -29,7 +29,7 @@ class SelectFileHandler : CommandHandler.SingleExecution() {
override val argFlags = flags(RangeFlag.RANGE_IS_COUNT, ArgumentFlag.ARGUMENT_OPTIONAL, Access.READ_ONLY) override val argFlags = flags(RangeFlag.RANGE_IS_COUNT, ArgumentFlag.ARGUMENT_OPTIONAL, Access.READ_ONLY)
override fun execute(editor: Editor, context: DataContext, cmd: ExCommand): Boolean { override fun execute(editor: Editor, context: DataContext, cmd: ExCommand): Boolean {
val count = cmd.getCount(editor, context, 0, true) val count = cmd.getCount(editor, 0, true)
if (count > 0) { if (count > 0) {
val res = VimPlugin.getFile().selectFile(count - 1, context) val res = VimPlugin.getFile().selectFile(count - 1, context)

View File

@@ -36,7 +36,7 @@ class ShiftLeftHandler : CommandHandler.ForEachCaret(), ComplicatedNameExCommand
override val argFlags: CommandHandlerFlags = flags(RangeFlag.RANGE_OPTIONAL, ArgumentFlag.ARGUMENT_OPTIONAL, Access.WRITABLE) override val argFlags: CommandHandlerFlags = flags(RangeFlag.RANGE_OPTIONAL, ArgumentFlag.ARGUMENT_OPTIONAL, Access.WRITABLE)
override fun execute(editor: Editor, caret: Caret, context: DataContext, cmd: ExCommand): Boolean { override fun execute(editor: Editor, caret: Caret, context: DataContext, cmd: ExCommand): Boolean {
val range = cmd.getTextRange(editor, caret, context, true) val range = cmd.getTextRange(editor, caret, true)
val endOffsets = range.endOffsets.map { it - 1 }.toIntArray() val endOffsets = range.endOffsets.map { it - 1 }.toIntArray()
VimPlugin.getChange().indentRange(editor, caret, context, VimPlugin.getChange().indentRange(editor, caret, context,
TextRange(range.startOffsets, endOffsets), TextRange(range.startOffsets, endOffsets),

View File

@@ -36,7 +36,7 @@ class ShiftRightHandler : CommandHandler.ForEachCaret(), ComplicatedNameExComman
override val argFlags: CommandHandlerFlags = flags(RangeFlag.RANGE_OPTIONAL, ArgumentFlag.ARGUMENT_OPTIONAL, Access.WRITABLE) override val argFlags: CommandHandlerFlags = flags(RangeFlag.RANGE_OPTIONAL, ArgumentFlag.ARGUMENT_OPTIONAL, Access.WRITABLE)
override fun execute(editor: Editor, caret: Caret, context: DataContext, cmd: ExCommand): Boolean { override fun execute(editor: Editor, caret: Caret, context: DataContext, cmd: ExCommand): Boolean {
val range = cmd.getTextRange(editor, caret, context, true) val range = cmd.getTextRange(editor, caret, true)
val endOffsets = range.endOffsets.map { it - 1 }.toIntArray() val endOffsets = range.endOffsets.map { it - 1 }.toIntArray()
VimPlugin.getChange().indentRange(editor, caret, context, VimPlugin.getChange().indentRange(editor, caret, context,
TextRange(range.startOffsets, endOffsets), TextRange(range.startOffsets, endOffsets),

View File

@@ -23,7 +23,10 @@ import com.intellij.openapi.editor.Caret
import com.intellij.openapi.editor.Editor import com.intellij.openapi.editor.Editor
import com.intellij.openapi.util.text.StringUtil import com.intellij.openapi.util.text.StringUtil
import com.maddyhome.idea.vim.VimPlugin import com.maddyhome.idea.vim.VimPlugin
import com.maddyhome.idea.vim.ex.* import com.maddyhome.idea.vim.ex.CommandHandler
import com.maddyhome.idea.vim.ex.ExCommand
import com.maddyhome.idea.vim.ex.ExException
import com.maddyhome.idea.vim.ex.flags
import com.maddyhome.idea.vim.ex.ranges.LineRange import com.maddyhome.idea.vim.ex.ranges.LineRange
import com.maddyhome.idea.vim.helper.inBlockSubMode import com.maddyhome.idea.vim.helper.inBlockSubMode
import java.util.* import java.util.*
@@ -46,7 +49,7 @@ class SortHandler : CommandHandler.SingleExecution() {
val lineComparator = LineComparator(ignoreCase, number, reverse) val lineComparator = LineComparator(ignoreCase, number, reverse)
if (editor.inBlockSubMode) { if (editor.inBlockSubMode) {
val primaryCaret = editor.caretModel.primaryCaret val primaryCaret = editor.caretModel.primaryCaret
val range = getLineRange(editor, primaryCaret, context, cmd) val range = getLineRange(editor, primaryCaret, cmd)
val worked = VimPlugin.getChange().sortRange(editor, range, lineComparator) val worked = VimPlugin.getChange().sortRange(editor, range, lineComparator)
primaryCaret.moveToOffset(VimPlugin.getMotion().moveCaretToLineStartSkipLeading(editor, range.startLine)) primaryCaret.moveToOffset(VimPlugin.getMotion().moveCaretToLineStartSkipLeading(editor, range.startLine))
return worked return worked
@@ -54,7 +57,7 @@ class SortHandler : CommandHandler.SingleExecution() {
var worked = true var worked = true
for (caret in editor.caretModel.allCarets) { for (caret in editor.caretModel.allCarets) {
val range = getLineRange(editor, caret, context, cmd) val range = getLineRange(editor, caret, cmd)
if (!VimPlugin.getChange().sortRange(editor, range, lineComparator)) { if (!VimPlugin.getChange().sortRange(editor, range, lineComparator)) {
worked = false worked = false
} }
@@ -64,8 +67,8 @@ class SortHandler : CommandHandler.SingleExecution() {
return worked return worked
} }
private fun getLineRange(editor: Editor, caret: Caret, context: DataContext, cmd: ExCommand): LineRange { private fun getLineRange(editor: Editor, caret: Caret, cmd: ExCommand): LineRange {
val range = cmd.getLineRange(editor, caret, context) val range = cmd.getLineRange(editor, caret)
// Something like "30,20sort" gets converted to "20,30sort" // Something like "30,20sort" gets converted to "20,30sort"
val normalizedRange = if (range.endLine < range.startLine) LineRange(range.endLine, range.startLine) else range val normalizedRange = if (range.endLine < range.startLine) LineRange(range.endLine, range.startLine) else range

View File

@@ -30,7 +30,7 @@ class SubstituteHandler : CommandHandler.SingleExecution() {
override fun execute(editor: Editor, context: DataContext, cmd: ExCommand): Boolean { override fun execute(editor: Editor, context: DataContext, cmd: ExCommand): Boolean {
var result = true var result = true
for (caret in editor.caretModel.allCarets) { for (caret in editor.caretModel.allCarets) {
val lineRange = cmd.getLineRange(editor, caret, context) val lineRange = cmd.getLineRange(editor, caret)
if (!VimPlugin.getSearch().searchAndReplace(editor, caret, lineRange, cmd.command, cmd.argument)) { if (!VimPlugin.getSearch().searchAndReplace(editor, caret, lineRange, cmd.command, cmd.argument)) {
result = false result = false
} }

View File

@@ -28,7 +28,7 @@ import com.maddyhome.idea.vim.ex.flags
class WriteNextFileHandler : CommandHandler.SingleExecution() { class WriteNextFileHandler : CommandHandler.SingleExecution() {
override val argFlags = flags(RangeFlag.RANGE_IS_COUNT, ArgumentFlag.ARGUMENT_OPTIONAL, Access.READ_ONLY) override val argFlags = flags(RangeFlag.RANGE_IS_COUNT, ArgumentFlag.ARGUMENT_OPTIONAL, Access.READ_ONLY)
override fun execute(editor: Editor, context: DataContext, cmd: ExCommand): Boolean { override fun execute(editor: Editor, context: DataContext, cmd: ExCommand): Boolean {
val count = cmd.getCount(editor, context, 1, true) val count = cmd.getCount(editor, 1, true)
VimPlugin.getFile().saveFile(context) VimPlugin.getFile().saveFile(context)
VimPlugin.getMark().saveJumpLocation(editor) VimPlugin.getMark().saveJumpLocation(editor)

View File

@@ -28,7 +28,7 @@ import com.maddyhome.idea.vim.ex.flags
class WritePreviousFileHandler : CommandHandler.SingleExecution() { class WritePreviousFileHandler : CommandHandler.SingleExecution() {
override val argFlags = flags(RangeFlag.RANGE_OPTIONAL, ArgumentFlag.ARGUMENT_OPTIONAL, Access.READ_ONLY) override val argFlags = flags(RangeFlag.RANGE_OPTIONAL, ArgumentFlag.ARGUMENT_OPTIONAL, Access.READ_ONLY)
override fun execute(editor: Editor, context: DataContext, cmd: ExCommand): Boolean { override fun execute(editor: Editor, context: DataContext, cmd: ExCommand): Boolean {
val count = cmd.getCount(editor, context, 1, true) val count = cmd.getCount(editor, 1, true)
VimPlugin.getFile().saveFile(context) VimPlugin.getFile().saveFile(context)
VimPlugin.getMark().saveJumpLocation(editor) VimPlugin.getMark().saveJumpLocation(editor)

View File

@@ -45,7 +45,7 @@ class YankLinesHandler : CommandHandler.SingleExecution() {
val starts = ArrayList<Int>(caretModel.caretCount) val starts = ArrayList<Int>(caretModel.caretCount)
val ends = ArrayList<Int>(caretModel.caretCount) val ends = ArrayList<Int>(caretModel.caretCount)
for (caret in caretModel.allCarets) { for (caret in caretModel.allCarets) {
val range = cmd.getTextRange(editor, caret, context, true) val range = cmd.getTextRange(editor, caret, true)
starts.add(range.startOffset) starts.add(range.startOffset)
ends.add(range.endOffset) ends.add(range.endOffset)
} }

View File

@@ -17,7 +17,6 @@
*/ */
package com.maddyhome.idea.vim.ex.ranges package com.maddyhome.idea.vim.ex.ranges
import com.intellij.openapi.actionSystem.DataContext
import com.intellij.openapi.editor.Caret import com.intellij.openapi.editor.Caret
import com.intellij.openapi.editor.Editor import com.intellij.openapi.editor.Editor
import com.maddyhome.idea.vim.VimPlugin import com.maddyhome.idea.vim.VimPlugin
@@ -59,13 +58,13 @@ class Ranges {
return endLine return endLine
} }
fun getLine(editor: Editor, caret: Caret, context: DataContext): Int { fun getLine(editor: Editor, caret: Caret): Int {
processRange(editor, caret, context) processRange(editor, caret)
return endLine return endLine
} }
fun getFirstLine(editor: Editor, caret: Caret, context: DataContext): Int { fun getFirstLine(editor: Editor, caret: Caret): Int {
processRange(editor, caret, context) processRange(editor, caret)
return startLine return startLine
} }
@@ -79,8 +78,8 @@ class Ranges {
*/ */
fun getCount(editor: Editor, count: Int): Int = if (count == -1) getLine(editor) else count fun getCount(editor: Editor, count: Int): Int = if (count == -1) getLine(editor) else count
fun getCount(editor: Editor, caret: Caret, context: DataContext, count: Int): Int { fun getCount(editor: Editor, caret: Caret, count: Int): Int {
return if (count == -1) getLine(editor, caret, context) else count return if (count == -1) getLine(editor, caret) else count
} }
/** /**
@@ -105,8 +104,8 @@ class Ranges {
return LineRange(start, end) return LineRange(start, end)
} }
fun getLineRange(editor: Editor, caret: Caret, context: DataContext, count: Int): LineRange { fun getLineRange(editor: Editor, caret: Caret, count: Int): LineRange {
processRange(editor, caret, context) processRange(editor, caret)
return if (count == -1) LineRange(startLine, endLine) else LineRange(endLine, endLine + count - 1) return if (count == -1) LineRange(startLine, endLine) else LineRange(endLine, endLine + count - 1)
} }
@@ -120,15 +119,15 @@ class Ranges {
* @param count The count given at the end of the command or -1 if no such count * @param count The count given at the end of the command or -1 if no such count
* @return The text range * @return The text range
*/ */
fun getTextRange(editor: Editor, context: DataContext?, count: Int): TextRange { fun getTextRange(editor: Editor, count: Int): TextRange {
val lr = getLineRange(editor, count) val lr = getLineRange(editor, count)
val start = EditorHelper.getLineStartOffset(editor, lr.startLine) val start = EditorHelper.getLineStartOffset(editor, lr.startLine)
val end = EditorHelper.getLineEndOffset(editor, lr.endLine, true) + 1 val end = EditorHelper.getLineEndOffset(editor, lr.endLine, true) + 1
return TextRange(start, min(end, EditorHelper.getFileSize(editor))) return TextRange(start, min(end, EditorHelper.getFileSize(editor)))
} }
fun getTextRange(editor: Editor, caret: Caret, context: DataContext, count: Int): TextRange { fun getTextRange(editor: Editor, caret: Caret, count: Int): TextRange {
val lineRange = getLineRange(editor, caret, context, count) val lineRange = getLineRange(editor, caret, count)
val start = EditorHelper.getLineStartOffset(editor, lineRange.startLine) val start = EditorHelper.getLineStartOffset(editor, lineRange.startLine)
val end = EditorHelper.getLineEndOffset(editor, lineRange.endLine, true) + 1 val end = EditorHelper.getLineEndOffset(editor, lineRange.endLine, true) + 1
return TextRange(start, min(end, EditorHelper.getFileSize(editor))) return TextRange(start, min(end, EditorHelper.getFileSize(editor)))
@@ -165,7 +164,7 @@ class Ranges {
done = true done = true
} }
private fun processRange(editor: Editor, caret: Caret, context: DataContext) { private fun processRange(editor: Editor, caret: Caret) {
startLine = if (defaultLine == -1) caret.logicalPosition.line else defaultLine startLine = if (defaultLine == -1) caret.logicalPosition.line else defaultLine
endLine = startLine endLine = startLine
var lastZero = false var lastZero = false

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,711 @@
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.ex.vimscript.VimScriptGlobalEnvironment;
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);
}
/**
* The pairs of brackets that delimit different types of argument lists.
*/
static private class BracketPairs {
// NOTE: brackets must match by the position, and ordered by rank (highest to lowest).
@NotNull private final String openBrackets;
@NotNull private final String closeBrackets;
static class ParseError extends Exception {
public ParseError(@NotNull String message) {
super(message);
}
}
private enum ParseState {
OPEN,
COLON,
CLOSE,
COMMA,
}
/**
* Constructs @ref BracketPair from a string of bracket pairs with the same syntax
* as VIM's @c matchpairs option: "(:),{:},[:]"
*
* @param bracketPairs comma-separated list of colon-separated bracket pairs.
* @throws ParseError if a syntax error is detected.
*/
@NotNull
static BracketPairs fromBracketPairList(@NotNull final String bracketPairs) throws ParseError {
StringBuilder openBrackets = new StringBuilder();
StringBuilder closeBrackets = new StringBuilder();
ParseState state = ParseState.OPEN;
for (char ch : bracketPairs.toCharArray()) {
switch (state) {
case OPEN:
openBrackets.append(ch);
state = ParseState.COLON;
break;
case COLON:
if (ch == ':') {
state = ParseState.CLOSE;
} else {
throw new ParseError("expecting ':', but got '" + ch + "' instead");
}
break;
case CLOSE:
final char lastOpenBracket = openBrackets.charAt(openBrackets.length() - 1);
if (lastOpenBracket == ch) {
throw new ParseError("open and close brackets must be different");
}
closeBrackets.append(ch);
state = ParseState.COMMA;
break;
case COMMA:
if (ch == ',') {
state = ParseState.OPEN;
} else {
throw new ParseError("expecting ',', but got '" + ch + "' instead");
}
break;
}
}
if (state != ParseState.COMMA) {
throw new ParseError("list of pairs is incomplete");
}
return new BracketPairs(openBrackets.toString(), closeBrackets.toString());
}
BracketPairs(@NotNull final String openBrackets, @NotNull final String closeBrackets) {
assert openBrackets.length() == closeBrackets.length();
this.openBrackets = openBrackets;
this.closeBrackets = closeBrackets;
}
int getBracketPrio(char ch) {
return Math.max(openBrackets.toString().indexOf(ch), closeBrackets.indexOf(ch));
}
char matchingBracket(char ch) {
int idx = closeBrackets.indexOf(ch);
if (idx != -1) {
return openBrackets.charAt(idx);
} else {
assert isOpenBracket(ch);
idx = openBrackets.toString().indexOf(ch);
return closeBrackets.charAt(idx);
}
}
boolean isCloseBracket(final int ch) {
return closeBrackets.indexOf(ch) != -1;
}
boolean isOpenBracket(final int ch) {
return openBrackets.toString().indexOf(ch) != -1;
}
}
public static final BracketPairs DEFAULT_BRACKET_PAIRS = new BracketPairs("(", ")");
@Nullable
private static String bracketPairsVariable() {
final VimScriptGlobalEnvironment env = VimScriptGlobalEnvironment.getInstance();
final Object value = env.getVariables().get("g:argtextobj_pairs");
if (value instanceof String) {
return (String) value;
}
return null;
}
/**
* 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) {
BracketPairs bracketPairs = DEFAULT_BRACKET_PAIRS;
final String bracketPairsVar = bracketPairsVariable();
if (bracketPairsVar != null) {
try {
bracketPairs = BracketPairs.fromBracketPairList(bracketPairsVar);
} catch (BracketPairs.ParseError parseError) {
VimPlugin.showMessage("argtextobj: Invalid value of g:argtextobj_pairs -- " + parseError.getMessage());
VimPlugin.indicateError();
return null;
}
}
final ArgBoundsFinder finder = new ArgBoundsFinder(editor.getDocument(), bracketPairs);
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))));
}
}
}
/**
* Helper class to find argument boundaries starting at the specified
* position
*/
private static class ArgBoundsFinder {
@NotNull private final CharSequence text;
@NotNull private final Document document;
@NotNull private final BracketPairs brackets;
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 = "\"'";
private static final int MAX_SEARCH_LINES = 10;
private static final int MAX_SEARCH_OFFSET = MAX_SEARCH_LINES * 80;
ArgBoundsFinder(@NotNull Document document, @NotNull BracketPairs bracketPairs) {
this.text = document.getImmutableCharSequence();
this.document = document;
this.brackets = bracketPairs;
}
/**
* 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 (brackets.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
//
if (nextLeft < leftLimit || nextRight > rightLimit) {
error = "not an argument";
return false;
}
bothBrackets = getCharAt(leftBound) != ',' && getCharAt(rightBound) != ',';
final boolean nonEmptyArg = (rightBound - leftBound) > 1;
if (bothBrackets && nonEmptyArg && 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 (brackets.isOpenBracket(ch)) {
rightBound = skipSexp(rightBound, rightBracket, SexpDirection.forward(brackets));
} else {
if (isQuoteChar(ch)) {
rightBound = skipQuotedTextForward(rightBound, rightBracket);
}
++rightBound;
}
}
}
private void findLeftBound() {
while (leftBound > leftBracket) {
final char ch = getCharAt(leftBound);
if (ch == ',') {
break;
}
if (brackets.isCloseBracket(ch)) {
leftBound = skipSexp(leftBound, leftBracket, SexpDirection.backward(brackets));
} 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 a 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 a 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 SexpDirection forward(BracketPairs brackets) {
return new SexpDirection() {
@Override
int delta() {
return 1;
}
@Override
boolean isOpenBracket(char ch) {
return brackets.isOpenBracket(ch);
}
@Override
boolean isCloseBracket(char ch) {
return brackets.isCloseBracket(ch);
}
@Override
int skipQuotedText(int pos, int end, ArgBoundsFinder self) {
return self.skipQuotedTextForward(pos, end);
}
};
}
static SexpDirection backward(BracketPairs brackets) {
return new SexpDirection() {
@Override
int delta() {
return -1;
}
@Override
boolean isOpenBracket(char ch) {
return brackets.isCloseBracket(ch);
}
@Override
boolean isCloseBracket(char ch) {
return brackets.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() == brackets.matchingBracket(ch)) {
bracketStack.pop();
} else {
//noinspection StatementWithEmptyBody
if (brackets.getBracketPrio(ch) < brackets.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();
}
}
/**
* 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 = brackets.getBracketPrio(getCharAt(leftBracket));
final int rightPrio = brackets.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 (!brackets.isOpenBracket(ch = getCharAt(leftBracket))) {
if (brackets.isCloseBracket(ch)) {
leftBracket = skipSexp(leftBracket, start, SexpDirection.backward(brackets));
} 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 (!brackets.isCloseBracket(ch = getCharAt(rightBracket))) {
if (brackets.isOpenBracket(ch)) {
rightBracket = skipSexp(rightBracket, end, SexpDirection.forward(brackets));
} 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

@@ -0,0 +1,150 @@
/*
* 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.replacewithregister
import com.intellij.openapi.actionSystem.DataContext
import com.intellij.openapi.editor.Caret
import com.intellij.openapi.editor.Editor
import com.maddyhome.idea.vim.VimPlugin
import com.maddyhome.idea.vim.command.CommandState
import com.maddyhome.idea.vim.command.MappingMode
import com.maddyhome.idea.vim.command.SelectionType
import com.maddyhome.idea.vim.command.isLine
import com.maddyhome.idea.vim.common.TextRange
import com.maddyhome.idea.vim.extension.VimExtension
import com.maddyhome.idea.vim.extension.VimExtensionFacade
import com.maddyhome.idea.vim.extension.VimExtensionFacade.executeNormal
import com.maddyhome.idea.vim.extension.VimExtensionFacade.putKeyMapping
import com.maddyhome.idea.vim.extension.VimExtensionFacade.setOperatorFunction
import com.maddyhome.idea.vim.extension.VimExtensionHandler
import com.maddyhome.idea.vim.group.MotionGroup
import com.maddyhome.idea.vim.group.RegisterGroup
import com.maddyhome.idea.vim.group.copy.PutData
import com.maddyhome.idea.vim.group.visual.VimSelection
import com.maddyhome.idea.vim.helper.EditorDataContext
import com.maddyhome.idea.vim.helper.StringHelper.parseKeys
import com.maddyhome.idea.vim.helper.exitVisualMode
import com.maddyhome.idea.vim.helper.subMode
import com.maddyhome.idea.vim.helper.vimForEachCaret
import com.maddyhome.idea.vim.key.OperatorFunction
class ReplaceWithRegister : VimExtension {
override fun getName(): String = "ReplaceWithRegister"
override fun init() {
VimExtensionFacade.putExtensionHandlerMapping(MappingMode.N, parseKeys(RWR_OPERATOR), owner, RwrMotion(), false)
VimExtensionFacade.putExtensionHandlerMapping(MappingMode.N, parseKeys(RWR_LINE), owner, RwrLine(), false)
VimExtensionFacade.putExtensionHandlerMapping(MappingMode.X, parseKeys(RWR_VISUAL), owner, RwrVisual(), false)
putKeyMapping(MappingMode.N, parseKeys("gr"), owner, parseKeys(RWR_OPERATOR), true)
putKeyMapping(MappingMode.N, parseKeys("grr"), owner, parseKeys(RWR_LINE), true)
putKeyMapping(MappingMode.X, parseKeys("gr"), owner, parseKeys(RWR_VISUAL), true)
}
private class RwrVisual : VimExtensionHandler {
override fun execute(editor: Editor, context: DataContext) {
val caretsAndSelections = mutableMapOf<Caret, VimSelection>()
val typeInEditor = SelectionType.fromSubMode(editor.subMode)
editor.vimForEachCaret { caret ->
val selectionStart = caret.selectionStart
val selectionEnd = caret.selectionEnd
caretsAndSelections += caret to VimSelection.create(selectionStart, selectionEnd - 1, typeInEditor, editor)
}
doReplace(editor, PutData.VisualSelection(caretsAndSelections, typeInEditor))
editor.exitVisualMode()
}
}
private class RwrMotion : VimExtensionHandler {
override fun isRepeatable(): Boolean = true
override fun execute(editor: Editor, context: DataContext) {
setOperatorFunction(Operator())
executeNormal(parseKeys("g@"), editor)
}
}
private class RwrLine : VimExtensionHandler {
override fun isRepeatable(): Boolean = true
override fun execute(editor: Editor, context: DataContext) {
val caretsAndSelections = mutableMapOf<Caret, VimSelection>()
editor.vimForEachCaret { caret ->
val logicalLine = caret.logicalPosition.line
val lineStart = editor.document.getLineStartOffset(logicalLine)
val lineEnd = editor.document.getLineEndOffset(logicalLine)
caretsAndSelections += caret to VimSelection.create(lineStart, lineEnd, SelectionType.LINE_WISE, editor)
}
val visualSelection = PutData.VisualSelection(caretsAndSelections, SelectionType.LINE_WISE)
doReplace(editor, visualSelection)
editor.vimForEachCaret { caret ->
val vimStart = caretsAndSelections[caret]?.vimStart
if (vimStart != null) {
MotionGroup.moveCaret(editor, caret, vimStart)
}
}
}
}
private class Operator : OperatorFunction {
override fun apply(editor: Editor, context: DataContext, selectionType: SelectionType): Boolean {
val range = getRange(editor) ?: return false
val visualSelection = PutData.VisualSelection(mapOf(editor.caretModel.primaryCaret to VimSelection.create(range.startOffset, range.endOffset - 1, selectionType, editor)), selectionType)
doReplace(editor, visualSelection)
return true
}
private fun getRange(editor: Editor): TextRange? = when (CommandState.getInstance(editor).mode) {
CommandState.Mode.COMMAND -> VimPlugin.getMark().getChangeMarks(editor)
CommandState.Mode.VISUAL -> editor.caretModel.primaryCaret.run { TextRange(selectionStart, selectionEnd) }
else -> null
}
}
companion object {
private const val RWR_OPERATOR = "<Plug>ReplaceWithRegisterOperator"
private const val RWR_LINE = "<Plug>ReplaceWithRegisterLine"
private const val RWR_VISUAL = "<Plug>ReplaceWithRegisterVisual"
private fun doReplace(editor: Editor, visualSelection: PutData.VisualSelection) {
val savedRegister = VimPlugin.getRegister().lastRegister ?: return
var usedType = savedRegister.type
var usedText = savedRegister.text
if (usedType.isLine && usedText?.endsWith('\n') == true) {
// Code from original plugin implementation. Correct text for linewise selected text
usedText = usedText.dropLast(1)
usedType = SelectionType.CHARACTER_WISE
}
val textData = PutData.TextData(usedText, usedType, savedRegister.transferableData)
val putData = PutData(textData, visualSelection, 1, insertTextBeforeCaret = true, _indent = true, caretAfterInsertedText = false, putToLine = -1)
VimPlugin.getPut().putText(editor, EditorDataContext(editor), putData)
VimPlugin.getRegister().saveRegister(savedRegister.name, savedRegister)
VimPlugin.getRegister().saveRegister(RegisterGroup.DEFAULT_REGISTER, savedRegister)
}
}
}

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,151 @@
/*
* 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))));
}
}
}
}

View File

@@ -84,8 +84,10 @@ public class ChangeGroup {
private static final String VIM_MOTION_WORD_END_RIGHT = "VimMotionWordEndRightAction"; private static final String VIM_MOTION_WORD_END_RIGHT = "VimMotionWordEndRightAction";
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";
private static final ImmutableSet<String> wordMotions =
ImmutableSet.of(VIM_MOTION_WORD_RIGHT, VIM_MOTION_BIG_WORD_RIGHT, VIM_MOTION_CAMEL_RIGHT);
@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 +156,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 +216,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 +310,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 +423,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 +739,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 +807,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 +826,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 +1063,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 +1164,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;
} }
@@ -1223,13 +1225,12 @@ public class ChangeGroup {
boolean bigWord = id.equals(VIM_MOTION_BIG_WORD_RIGHT); boolean bigWord = id.equals(VIM_MOTION_BIG_WORD_RIGHT);
final CharSequence chars = editor.getDocument().getCharsSequence(); final CharSequence chars = editor.getDocument().getCharsSequence();
final int offset = caret.getOffset(); final int offset = caret.getOffset();
if (EditorHelper.getFileSize(editor) > 0) { int fileSize = EditorHelper.getFileSize(editor);
if (fileSize > 0) {
final CharacterHelper.CharacterType charType = CharacterHelper.charType(chars.charAt(offset), bigWord); final CharacterHelper.CharacterType charType = CharacterHelper.charType(chars.charAt(offset), bigWord);
if (charType != CharacterHelper.CharacterType.WHITESPACE) { if (charType != CharacterHelper.CharacterType.WHITESPACE) {
final boolean lastWordChar = offset > EditorHelper.getFileSize(editor) || final boolean lastWordChar = offset >= fileSize - 1 ||
CharacterHelper.charType(chars.charAt(offset + 1), bigWord) != charType; CharacterHelper.charType(chars.charAt(offset + 1), bigWord) != charType;
final ImmutableSet<String> wordMotions =
ImmutableSet.of(VIM_MOTION_WORD_RIGHT, VIM_MOTION_BIG_WORD_RIGHT, VIM_MOTION_CAMEL_RIGHT);
if (wordMotions.contains(id) && lastWordChar && motion.getCount() == 1) { if (wordMotions.contains(id) && lastWordChar && motion.getCount() == 1) {
final boolean res = deleteCharacter(editor, caret, 1, true); final boolean res = deleteCharacter(editor, caret, 1, true);
if (res) { if (res) {
@@ -1258,7 +1259,7 @@ public class ChangeGroup {
} }
if (kludge) { if (kludge) {
int size = EditorHelper.getFileSize(editor); int size = fileSize;
int cnt = count * motion.getCount(); int cnt = count * motion.getCount();
int pos1 = SearchHelper.findNextWordEnd(chars, offset, size, cnt, bigWord, false); int pos1 = SearchHelper.findNextWordEnd(chars, offset, size, cnt, bigWord, false);
int pos2 = SearchHelper.findNextWordEnd(chars, pos1, size, -cnt, bigWord, false); int pos2 = SearchHelper.findNextWordEnd(chars, pos1, size, -cnt, bigWord, false);
@@ -1456,7 +1457,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 +1731,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 +1840,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 +1884,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 +1913,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 +2036,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

@@ -60,7 +60,6 @@ public class DigraphGroup {
final String digraph = keys.get(ch); final String digraph = keys.get(ch);
final String digraphText = digraph == null ? "" : ", Digr " + digraph; final String digraphText = digraph == null ? "" : ", Digr " + digraph;
final String hexText = (ch > 0xff) ? String.format("Hex %04x", (int) ch) : String.format("Hex %02x", (int) ch);
if (ch < 0x100) { if (ch < 0x100) {
VimPlugin.showMessage(String.format("<%s> %d, Hex %02x, Oct %03o%s", VimPlugin.showMessage(String.format("<%s> %d, Hex %02x, Oct %03o%s",
@@ -1749,8 +1748,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) {
@@ -191,10 +195,11 @@ class NotificationService(private val project: Project?) {
const val IDEAVIM_NOTIFICATION_ID = "ideavim" const val IDEAVIM_NOTIFICATION_ID = "ideavim"
const val IDEAVIM_NOTIFICATION_TITLE = "IdeaVim" const val IDEAVIM_NOTIFICATION_TITLE = "IdeaVim"
const val ideajoinExamplesUrl = "https://github.com/JetBrains/ideavim/wiki/%60ideajoin%60-examples" const val ideajoinExamplesUrl = "https://github.com/JetBrains/ideavim/wiki/%60ideajoin%60-examples"
const val selectModeUrl = "https://vimhelp.org/visual.txt.html#Select-mode"
private fun createIdeaVimRcManually(message: String, project: Project?) { private fun createIdeaVimRcManually(message: String, project: Project?) {
val notification = Notification(IDEAVIM_NOTIFICATION_ID, IDEAVIM_NOTIFICATION_TITLE, message, NotificationType.WARNING) val notification = Notification(IDEAVIM_NOTIFICATION_ID, IDEAVIM_NOTIFICATION_TITLE, message, NotificationType.WARNING)
@Suppress("UnstableApiUsage", "DEPRECATION")
// [VERSION UPDATE] 193+ com.intellij.ide.actions.RevealFileAction.openDirectory
var actionName = if (SystemInfo.isMac) "Reveal Home in Finder" else "Show Home in " + ShowFilePathAction.getFileManagerName() var actionName = if (SystemInfo.isMac) "Reveal Home in Finder" else "Show Home in " + ShowFilePathAction.getFileManagerName()
if (!File(System.getProperty("user.home")).exists()) { if (!File(System.getProperty("user.home")).exists()) {
actionName = "" actionName = ""
@@ -202,6 +207,8 @@ class NotificationService(private val project: Project?) {
notification.addAction(object : AnAction(actionName) { notification.addAction(object : AnAction(actionName) {
override fun actionPerformed(e: AnActionEvent) { override fun actionPerformed(e: AnActionEvent) {
val homeDir = File(System.getProperty("user.home")) val homeDir = File(System.getProperty("user.home"))
@Suppress("DEPRECATION", "UnstableApiUsage")
// [VERSION UPDATE] 193+ com.intellij.ide.actions.RevealFileAction.openDirectory
ShowFilePathAction.openDirectory(homeDir) ShowFilePathAction.openDirectory(homeDir)
notification.expire() notification.expire()
} }

Some files were not shown because too many files have changed in this diff Show More