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

Compare commits

..

157 Commits

Author SHA1 Message Date
Alex Plate
0adde753f4 Preparation for 0.57.1 EAP release 2020-05-28 10:39:43 +03:00
Alex Plate
5f28a22666 Notes about unsupported features of exchange plugin 2020-05-22 15:06:20 +03:00
Alex Plate
a975b53894 Fix bug in test 2020-05-22 10:27:53 +03:00
Alex Plate
98aee5d0ab Fixes and more tests for vim exchange 2020-05-22 09:59:53 +03:00
Alex Plate
f57af8bf9e new badges 2020-05-20 23:53:06 +03:00
Alex Plate
c6c3b6643e Add linewise visual test for vim exchange plugin 2020-05-20 23:44:45 +03:00
Alex Pláte
af94079b92 Merge pull request #238 from citizenmatt/bug/prioritise-startup-activity
Prioritise startup activity to initialise IdeaVim early
2020-05-15 10:27:33 +03:00
Alex Plate
7203cc5cb3 Revert "Remove dynamic loader stopper"
This reverts commit 03493e23
2020-05-15 09:48:41 +03:00
Alex Plate
028423cf58 Make exchange extension repeatable 2020-05-14 10:37:37 +03:00
Alex Plate
2ead6af96a Fix visual operator with dot command 2020-05-14 10:37:16 +03:00
Matt Ellis
bf853e3c0c Initialise as soon as possible during startup 2020-05-13 17:38:09 +01:00
Alex Plate
c85f41e65b Fix tests 2020-05-12 10:43:04 +03:00
Alex Plate
2759bed1b2 Update changelog 2020-05-12 10:15:55 +03:00
Alex Pláte
89c2a8ec9b Merge pull request #229 from fan-tom/VIM-921_exchange
VIM-921 vim-exchange plugin emulation
2020-05-12 10:08:53 +03:00
Alexey Gerasimov
aa2c1257ac Use ${c} instead of <caret> 2020-05-10 17:55:55 +05:00
Alexey Gerasimov
f9fa15b7ac Parenthesize command names 2020-05-10 14:51:16 +05:00
Alex Plate
93a9be41bc Update changes image 2020-05-09 23:16:02 +03:00
Alex Plate
ecd2f2032c Update changes 2020-05-09 23:08:15 +03:00
Alex Plate
de5ce5f635 Update ideavimrc reload implementation 2020-05-09 22:56:10 +03:00
Alex Plate
2eb6fd6819 Convert VimParser to kt 2020-05-09 22:56:10 +03:00
Alex Plate
22ea4e7ffa Rename .java to .kt 2020-05-09 22:56:10 +03:00
Alex Plate
3d98f3035f Reload vimrc 2020-05-09 22:56:10 +03:00
Alex Plate
ec1d6ac477 Update changelog 2020-05-08 11:51:29 +03:00
Alex Pláte
0dc236cb5b Merge pull request #231 from citizenmatt/feature/smooth-scrolling
Support smooth scrolling
2020-05-08 11:44:18 +03:00
Alex Plate
98349a49fd Temporally remove changelog 2020-05-08 11:40:27 +03:00
Alex Plate
ab8be2cada Update changes 2020-05-08 10:35:28 +03:00
Alex Pláte
b8c22d0928 Merge pull request #230 from fan-tom/VIM-1924_select_next_occurrence
VIM-1924
2020-05-08 10:30:34 +03:00
Alex Plate
c6cf77e4b8 Remove some deprecations 2020-05-06 09:53:04 +03:00
Alex Plate
6c0511a898 Update IdeaVim icons class 2020-05-06 09:38:47 +03:00
Alex Plate
366c862bcf Add loading/unloading to manual tests 2020-05-06 09:38:11 +03:00
Alex Plate
03493e2390 Remove dynamic loader stopper 2020-05-06 09:36:41 +03:00
Alex Plate
8f9c71dd55 Correct ex command and update changes 2020-05-06 08:49:07 +03:00
Alex Plate
11beb1e331 Add Piotr Mikulski to contributors list 2020-05-06 08:47:10 +03:00
Alex Pláte
01b4dc233a Merge pull request #227 from angelbot/buffer_command
Add support for buffer command
2020-05-06 08:46:41 +03:00
Alex Pláte
9f1e80e969 Merge pull request #224 from pmnoxx/master
Populate intelij navigation history together with ideavim jumplist
2020-05-06 08:30:06 +03:00
Alex Plate
7e319e11c6 Add valis to contributors list 2020-05-06 08:20:48 +03:00
Alex Plate
d11bf1c4d2 Update api 2020-05-06 08:14:34 +03:00
Alex Plate
3e2f18b757 Take back dynamic loader stopper 2020-05-06 08:14:33 +03:00
Alex Pláte
61677aa811 Merge pull request #233
Fix #VIM-1994
2020-05-06 08:13:40 +03:00
Alex Plate
fb04e835ef Update vimBehaviourDiffers annotation description 2020-05-02 13:13:51 +03:00
Alex Plate
bb133922d6 Update scheduledForRemoval because of quickfix release 2020-05-01 11:30:26 +03:00
Alex Plate
44dd5ef872 Convert VimExtensionRegistrar to kt 2020-05-01 11:16:28 +03:00
Alex Plate
bcc8e1c055 Rename .java to .kt 2020-05-01 11:13:54 +03:00
Alex Plate
71117ed335 Update registration of extension pointers 2020-05-01 11:06:29 +03:00
Alex Plate
de07fb3b74 Well, the status bar icon should be configurable 2020-05-01 10:38:23 +03:00
Alex Plate
e31d5a4dcf Initial cleanup after IJ requirements update 2020-04-30 11:43:24 +03:00
Alex Plate
e14aae761d Java plugin is always required 2020-04-30 11:33:11 +03:00
Alex Plate
47db2a247c Remove unused labels 2020-04-30 11:20:24 +03:00
Alex Plate
e449bb9692 Refactor ChangeGroup listeners 2020-04-30 10:41:29 +03:00
Alex Plate
b8fc72b6a7 Do not create project manager if it doesn't exist yet 2020-04-30 10:28:03 +03:00
Alex Plate
64c01c1bd1 Cleanup timer for java tests 2020-04-30 10:23:14 +03:00
Alex Plate
0a0e3df42b Extract statistic reporter into the separate file 2020-04-28 11:41:33 +03:00
Alex Plate
949c69a7e9 Refactor EditorGroup listeners 2020-04-28 11:41:33 +03:00
Alex Plate
69caf7a604 Refactor MotionGroup listeners 2020-04-28 11:41:32 +03:00
Alex Plate
23860ad5f9 Use project-level service as parent disposable 2020-04-28 11:41:32 +03:00
Alex Plate
ace5234d8d Update showcmd widget 2020-04-28 11:41:32 +03:00
Alex Plate
4654f821a9 Fix issue with listener removing 2020-04-28 11:41:32 +03:00
Alex Plate
927e0e7865 Update status bar widget to the new API 2020-04-28 11:41:32 +03:00
Alex Plate
d47c9735b5 Use concurrent list to store listeners 2020-04-28 11:41:31 +03:00
Alex Plate
6100433636 Move StatusBar.kt to a different package 2020-04-28 11:41:31 +03:00
Alex Plate
43f79e8183 Update minimal required version of IJ 2020-04-28 11:41:31 +03:00
Alex Plate
f58fda0c87 Add .DS_Store to gitignore 2020-04-28 11:41:31 +03:00
Alex Plate
64b49e37d7 Add link to gitter chat 2020-04-28 11:41:31 +03:00
Alex Plate
e44418d410 Add icon in .idea 2020-04-28 11:41:30 +03:00
Alex Plate
ca8d05ff13 Clear keymap on reset 2020-04-28 11:41:30 +03:00
Alex Plate
626871e34d Register topics via xml file 2020-04-28 11:41:29 +03:00
Alex Plate
4b659fe643 Prepare for 0.57 release 2020-04-28 11:18:56 +03:00
Alex Plate
d5055506b0 Fix regex for slack notification 2020-04-22 09:56:39 +03:00
Alex Plate
55f54b2e82 Prepare for 0.56.1 release 2020-04-22 09:31:38 +03:00
Alex Plate
1b18065e68 Small refactoring of handlers 2020-04-18 17:43:50 +03:00
Alex Plate
053dc02152 EditorActionHandlerBase doesn't take null as caret 2020-04-18 16:40:07 +03:00
Alex Plate
b8cb4a1295 Move IdeaVim icon in the README 2020-04-17 11:34:28 +03:00
Alex Plate
cd2cbf68a1 Update README 2020-04-17 11:31:17 +03:00
Alex Plate
73f3be8af0 Include copyright into repository 2020-04-17 11:17:34 +03:00
Alex Plate
8cce059fb4 Write tests for yanking and pasting with number register 2020-04-17 10:48:09 +03:00
Alex Plate
db641ec6f6 Add runforprogram to contributors list 2020-04-17 10:27:11 +03:00
Alex Plate
9d8239b68d Update changelog 2020-04-17 10:19:56 +03:00
Alex Pláte
4ec0bac275 Merge pull request #234 from runforprogram/master
[VIM-1991] fix >0 number register not work
2020-04-17 10:19:49 +03:00
Alex Plate
613c234cfb Fix related tests 2020-04-16 11:31:22 +03:00
Alex Plate
83dca71f69 [VIM-1992] Fix shift-letter mappings 2020-04-16 11:20:26 +03:00
Alex Plate
f7ea9cdb6e Convert mapping tests to kotlin 2020-04-16 11:03:40 +03:00
Alex Plate
762cb1804f Rename .java to .kt 2020-04-16 11:02:38 +03:00
run
962cfb7ae2 [VIM-1991] fix >0 number register not work 2020-04-16 15:45:57 +08:00
Valery Isaev
9bc2ec7d8a Fix #VIM-1994 2020-04-13 15:02:49 +03:00
Alex Plate
8415d104e9 Clear registers before test 2020-04-10 11:03:14 +03:00
Alex Plate
abd0f9b961 Update dependencies 2020-04-10 11:00:47 +03:00
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
Matt Ellis
7de08e08d0 Support smooth scrolling 2020-04-07 17:30:18 +01: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
Alexey Gerasimov
a4cd94847e Return VISUAL_BLOCK submode from autodetect only if 'Add Selection for NextOccurrence' was not performed previously 2020-03-22 16:45:45 +05:00
Alexey Gerasimov
a0a7386b51 Remove highlight after command is executed or canceled 2020-03-21 20:08:57 +05:00
Alexey Gerasimov
535a72000f Cleanup 2020-03-21 20:08:37 +05:00
Alexey Gerasimov
60531b9cd2 Add methods in RegisterGroup and VimExtensionFacade to setRegister with specified selection type 2020-03-21 20:04:30 +05:00
Alexey Gerasimov
8f86ad696d Register VimExchange extension 2020-03-21 20:04:30 +05:00
Alexey Gerasimov
c9bda98a6a Add VimExchange extension tests 2020-03-21 20:04:30 +05:00
Alexey Gerasimov
9ea08da133 Add VimExchange extension 2020-03-21 17:09:02 +05:00
Alexey Gerasimov
5762ec0518 Add marks last changed end position test 2020-03-21 17:09:02 +05:00
Alexey Gerasimov
7db74460fa Fix marks range end position handling, as excluded 2020-03-21 17:09:02 +05: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
John Weigel
2f148255f7 Cleanup 2020-03-03 23:14:11 -06:00
John Weigel
cb00b8b335 Merge remote-tracking branch 'upstream/master' into buffer_command 2020-03-03 23:10:00 -06:00
John Weigel
559b56c8a2 Minor updates 2020-03-03 23:05:32 -06: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
158cea51db Add override test 2020-02-23 22:02:06 -06:00
John Weigel
33d34f35e9 Merge branch 'master' into buffer_command 2020-02-23 21:49:22 -06:00
John Weigel
1f4f40fd7c Merge remote-tracking branch 'upstream/master' 2020-02-23 21:48:30 -06:00
John Weigel
7c908b247e Merge branch 'master' into buffer_command 2020-02-23 21:14:21 -06:00
John Weigel
41c822fde1 Add support for buffer command. 2020-02-23 20:45:06 -06:00
Piotr Mikulski
2a6569742d populate intelij navigation history together with ideavim jumplist 2020-02-17 17:55:45 -08:00
Matt Ellis
14c8b6a248 Fix nullability compile error on 2019.2 2020-02-11 10:29:02 +00: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
Matt Ellis
9fad4a74ed Remove keys from Command
Also refactors PutVisualTextAction
2020-02-04 00:36:59 +00:00
162 changed files with 5025 additions and 2409 deletions

9
.gitignore vendored
View File

@@ -1,6 +1,13 @@
*.swp
/.gradle/
/.idea/
!/.idea/scopes
!/.idea/copyright
!/.idea/icon.png
/build/
/out/
/tmp/
/tmp/
*.DS_Store

6
.idea/copyright/IdeaVim.xml generated Normal file
View File

@@ -0,0 +1,6 @@
<component name="CopyrightManager">
<copyright>
<option name="notice" value="IdeaVim - Vim emulator for IDEs based on the IntelliJ platform&#10;Copyright (C) 2003-&amp;#36;today.year The IdeaVim authors&#10;&#10;This program is free software: you can redistribute it and/or modify&#10;it under the terms of the GNU General Public License as published by&#10;the Free Software Foundation, either version 2 of the License, or&#10;(at your option) any later version.&#10;&#10;This program is distributed in the hope that it will be useful,&#10;but WITHOUT ANY WARRANTY; without even the implied warranty of&#10;MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the&#10;GNU General Public License for more details.&#10;&#10;You should have received a copy of the GNU General Public License&#10;along with this program. If not, see &lt;https://www.gnu.org/licenses/&gt;." />
<option name="myName" value="IdeaVim" />
</copyright>
</component>

7
.idea/copyright/profiles_settings.xml generated Normal file
View File

@@ -0,0 +1,7 @@
<component name="CopyrightManager">
<settings>
<module2copyright>
<element module="Copyright" copyright="IdeaVim" />
</module2copyright>
</settings>
</component>

BIN
.idea/icon.png generated Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 113 KiB

3
.idea/scopes/Copyright.xml generated Normal file
View File

@@ -0,0 +1,3 @@
<component name="DependencyValidationManager">
<scope name="Copyright" pattern="file[IdeaVIM.main]:com//*||file[IdeaVIM.test]:*/" />
</component>

View File

@@ -283,6 +283,22 @@ Contributors:
[![icon][github]](https://github.com/angelbot)
&nbsp;
John Weigel
* [![icon][mail]](mailto:kevinz@weghst.com)
[![icon][github]](https://github.com/kevin70)
&nbsp;
kk
* [![icon][mail]](mailto:runforprogram@163.com)
[![icon][github]](https://github.com/runforprogram)
&nbsp;
runforprogram
* [![icon][mail]](mailto:valery.isaev@jetbrains.com)
[![icon][github]](https://github.com/valis)
&nbsp;
valis
* [![icon][mail]](mailto:pmikulski@voleon.com)
[![icon][github]](https://github.com/pmnoxx)
&nbsp;
Piotr Mikulski
If you are a contributor and your name is not listed here, feel free to
contact the maintainers.

View File

@@ -3,6 +3,12 @@ The Changelog
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
-------------------
@@ -17,30 +23,78 @@ Please note that the quality of EAP versions may at times be way below even
usual beta standards.
To Be Released
--------------
-------------
_Available since 0.55.1 EAP:_
**Fixes:**
* [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
* 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-1835](https://youtrack.jetbrains.com/issue/VIM-1835) Macros record input keystrokes instead of mapped keystrokes
* [VIM-1900](https://youtrack.jetbrains.com/issue/VIM-1900) Ensure non-printable output for `:registers`, `:marks` and `:jumps` is encoded correctly
_Available since 0.55.2 EAP:_
_Available since 0.57.1 EAP:_
**Features:**
* `argtextobj.vim` plugin emulation ([argtextobj.vim](https://vim.sourceforge.io/scripts/script.php?script_id=2699))
* `vim-textobj-entire` plugin emulation ([vim-textobj-entire](https://github.com/kana/vim-textobj-entire))
* Support `ls/buffers/files` commands
* `exchange` plugin emulation ([vim-exchange](https://github.com/tommcdo/vim-exchange)).
* `~/.ideavimrc` file can be reloaded using the new floating action.
* <details>
<summary><strong>Click to see details</strong></summary>
<img src="resources/changes/0.58/reload_ideavimrc.png" alt="IdeaVimRc reload"/>
</details>
* Add `:buffer` command.
**Changes:**
* Support IntelliJ's smooth scrolling. Use "Enable smooth scrolling" checkbox in _Preferences | Editor | General_ to disable.
**Fixes:**
* [VIM-1994](https://youtrack.jetbrains.com/issue/VIM-1994) Correct paste after `y}P` command.
* [VIM-1924](https://youtrack.jetbrains.com/issue/VIM-1924) Select next occurrence doesn't become block selection.
**Merged PRs:**
* [233](https://github.com/JetBrains/ideavim/pull/233) by [valis](https://github.com/valis): [VIM-1994] Correct paste after `y}P` command.
* [224](https://github.com/JetBrains/ideavim/pull/224) by [pmnoxx](https://github.com/pmnoxx): Populate intelij navigation history together with ideavim jumplist.
* [227](https://github.com/JetBrains/ideavim/pull/227) by [angelbot](https://github.com/angelbot): Add support for buffer command.
* [230](https://github.com/JetBrains/ideavim/pull/230) by [fan-tom](https://github.com/fan-tom): VIM-1924.
* [231](https://github.com/JetBrains/ideavim/pull/231) by [citizenmatt](https://github.com/citizenmatt): Support smooth scrolling.
_To Be Released..._
...
0.57, 2020-04-28
-------------
**Fixes:**
* [VIM-1992](https://youtrack.jetbrains.com/issue/VIM-1992) Fix mappings to `<S-Letter>`
* [VIM-1991](https://youtrack.jetbrains.com/issue/VIM-1991) Fix working with number registers
**Merged PRs:**
* [234](https://github.com/JetBrains/ideavim/pull/234) by [runforprogram](https://github.com/runforprogram): [VIM-1991] fix >0 number register not work
0.56, 2020-04-09
--------------
**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:**
* [VIM-1008](https://youtrack.jetbrains.com/issue/VIM-1008) Correct `ci{` behavior
* [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.
* 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-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-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
--------------

View File

@@ -1,33 +1,4 @@
<div>
<a href="https://teamcity.jetbrains.com/viewType.html?buildTypeId=IdeaVim_TestsForIntelliJ20183&guest=1">
<img src="https://teamcity.jetbrains.com/app/rest/builds/buildType:(id:IdeaVim_TestsForIntelliJ20183)/statusIcon.svg?guest=1"/>
</a>
<span>2018.3 Tests</span>
</div>
<div>
<a href="https://teamcity.jetbrains.com/viewType.html?buildTypeId=IdeaVim_TestsForIntelliJ20191&guest=1">
<img src="https://teamcity.jetbrains.com/app/rest/builds/buildType:(id:IdeaVim_TestsForIntelliJ20191)/statusIcon.svg?guest=1"/>
</a>
<span>2019.1 Tests</span>
</div>
<div>
<a href="https://teamcity.jetbrains.com/viewType.html?buildTypeId=IdeaVim_TestsForIntelliJ20192&guest=1">
<img src="https://teamcity.jetbrains.com/app/rest/builds/buildType:(id:IdeaVim_TestsForIntelliJ20192)/statusIcon.svg?guest=1"/>
</a>
<span>2019.2 Tests</span>
</div>
<div>
<a href="https://teamcity.jetbrains.com/viewType.html?buildTypeId=IdeaVim_TestsForIntelliJ20193&guest=1">
<img src="https://teamcity.jetbrains.com/app/rest/builds/buildType:(id:IdeaVim_TestsForIntelliJ20193)/statusIcon.svg?guest=1"/>
</a>
<span>2019.3 Tests</span>
</div>
<div>
<a href="https://teamcity.jetbrains.com/viewType.html?buildTypeId=IdeaVim_TestsForIntelliJ20201&guest=1">
<img src="https://teamcity.jetbrains.com/app/rest/builds/buildType:(id:IdeaVim_TestsForIntelliJ20201)/statusIcon.svg?guest=1"/>
</a>
<span>2020.1 Tests</span>
</div>
[![TeamCity Build][teamcity-build-status-svg]][teamcity-build-status]
### Where to Start
@@ -46,6 +17,8 @@ You can start by:
in the issue tracker.
- Read about the `@VimBehaviorDiffers` annotation and fix the corresponding functionality.
Also join the brand new [![Join the chat at https://gitter.im/JetBrains/ideavim](https://badges.gitter.im/JetBrains/ideavim.svg)](https://gitter.im/JetBrains/ideavim?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge&utm_content=badge) for IdeaVim developers!
### Development Environment
@@ -54,11 +27,10 @@ in the issue tracker.
2. Import the project from the existing sources in IntelliJ IDEA 2018.1 or newer (Community or
Ultimate), by selecting "File | New | Project from Existing Sources..." or selecting "Import
Project" from the Welcome screen.
* In the project wizard, select "Import project from external model | Gradle".
* Select your Java 8+ JDK as the Gradle JVM; leave other parameters unchanged.
* In the project wizard, select "Import project from external model | Gradle".
* Select your Java 8+ JDK as the Gradle JVM; leave other parameters unchanged.
3. Run your IdeaVim plugin within IntelliJ via a Gradle task:
* Select the "View | Tool Windows | Gradle" tool window.
@@ -78,37 +50,7 @@ in the issue tracker.
* You can install this file by selecting "Settings | Plugins | Install plugin
from disk...".
### Copyright
1. Go to `Preferences | Appearance & Behavior | Scopes`, press "+" button, `Shared`.
Name: Copyright scope
Pattern: `file[IdeaVIM.main]:com//*||file[IdeaVIM.test]:*/`
2. Go to `Preferences | Editor | Copyright | Copyright Profiles` and click the "+" button.
Name: IdeaVim
Text:
IdeaVim - Vim emulator for IDEs based on the IntelliJ platform
Copyright (C) 2003-$today.year 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/>.
3. Go to `Preferences | Editor | Copyright`, click the "+" button.
Scope: Copyright scope
Copyright: IdeaVim
### Testing
1. Read about the `@VimBehaviorDiffers` annotation.
@@ -122,3 +64,8 @@ For example, take a few lines from your favorite poem, or use
3. Test your functionality properly.
Especially check whether your command works with:
line start, line end, file start, file end, empty line, multiple carets, dollar motion, etc.
<!-- Badges -->
[teamcity-build-status]: https://teamcity.jetbrains.com/viewType.html?buildTypeId=IdeaVim_TestsForIntelliJ20201&guest=1
[teamcity-build-status-svg]: https://teamcity.jetbrains.com/app/rest/builds/buildType:(id:IdeaVim_TestsForIntelliJ20201)/statusIcon.svg?guest=1

144
README.md
View File

@@ -3,14 +3,12 @@
IdeaVim
===
<div>
<a href="https://confluence.jetbrains.com/display/ALL/JetBrains+on+GitHub">
<img src="https://jb.gg/badges/official.svg" alt="official JetBrains project"/>
</a>
<a href="https://teamcity.jetbrains.com/viewType.html?buildTypeId=IdeaVim_TestsForIntelliJ20201&guest=1">
<img src="https://teamcity.jetbrains.com/app/rest/builds/buildType:(id:IdeaVim_TestsForIntelliJ20201)/statusIcon.svg?guest=1" alt="TeamCity Build"/>
</a>
</div>
[![Official JetBrains Project][jb-official-svg]][jb-official]
[![Downloads][plugin-downloads-svg]][plugin-repo]
[![Rating][plugin-rating-svg]][plugin-repo]
[![Version][plugin-version-svg]][plugin-repo]
[![Gitter][gitter-svg]][gitter]
[![Twitter][twitter-svg]][twitter]
IdeaVim is a Vim emulation plugin for IDEs based on the IntelliJ Platform.
IdeaVim can be used with IntelliJ IDEA, PyCharm, CLion, PhpStorm, WebStorm,
@@ -24,24 +22,20 @@ Resources:
* [Continuous integration builds](https://teamcity.jetbrains.com/project.html?projectId=IdeaVim&guest=1)
* [@IdeaVim](https://twitter.com/ideavim) in Twitter
Installation
Setup
------------
Use the IDE's plugin manager to install the latest version of the plugin.
Start the IDE normally and enable the Vim emulation using "Tools | Vim
Emulator" menu item. At this point you must use Vim keystrokes in all editors.
- IdeaVim can be installed via `Settings | Plugins`.
See [detailed instructions](https://www.jetbrains.com/help/idea/managing-plugins.html#).
If you wish to disable the plugin, select the "Tools | Vim Emulator" menu so
it is unchecked. At this point your IDE will work with its regular keyboard
shortcuts.
- Use `Tools | Vim Emulator` to enable or disable emulation.
Keyboard shortcut conflicts between the Vim emulation and the IDE can be
resolved via "File | Settings | Editor | Vim Emulation", "File | Settings |
Keymap" on Linux & Windows, and via "Preferences | Editor | Vim Emulation",
"Preferences | Keymap" on macOS. They can also be resolved by key-mapping
commands in your ~/.ideavimrc file.
- Use `~/.ideavimrc` file as an analog of `~/.vimrc` ([details](#Files)). XGD standard is supported as well.
- Shortcut conflicts can be resolved using:
- Linux & Windows: `File | Settings | Editor | Vim Emulation` & `File | Settings | Keymap`,
- macOS: `Preferences | Editor | Vim Emulation` & `Preferences | Keymap`,
- regular vim mappings in the `~/.ideavimrc` file.
Get Early Access
-------------------
@@ -49,7 +43,9 @@ Get Early Access
Would you like to try new features and fixes? Join the Early Access Program and
receive EAP builds as updates!
1. Click the IdeaVim icon in the status bar | `EAP` | `Get Early Access...`
1. Click the IdeaVim icon <img src="resources/META-INF/pluginIcon_noBorders.svg" width="16" height="16" alt="icon"/>
in the status bar | `EAP` | `Get Early Access...`
Or subscribe to EAP updates manually:
@@ -57,7 +53,7 @@ Or subscribe to EAP updates manually:
2. Click the gear icon :gear:, select `Manage Plugin Repositories`, and add the following url:
`https://plugins.jetbrains.com/plugins/eap/ideavim`
See [the changelog](CHANGES.md) for the list of hot unreleased features.
See [the changelog](CHANGES.md) for the list of unreleased features.
It is important to distinguish EAP builds from traditional pre-release software.
Please note that the quality of EAP versions may at times be way below even
@@ -91,14 +87,16 @@ Supported:
* Vim web help
* Select mode
Emulated Vim plugins:
[Emulated Vim plugins](doc/emulated-plugins.md):
* vim-easymotion
* vim-surround
* vim-multiple-cursors
* vim-commentary
* argtextobj.vim [To Be Released]
* vim-textobj-entire [To Be Released]
* argtextobj.vim
* vim-textobj-entire
* ReplaceWithRegister
* vim-exchange [To Be Released]
Not supported (yet):
@@ -114,19 +112,19 @@ See also:
Files
-----
* ~/.ideavimrc
* `~/.ideavimrc`
* Your IdeaVim-specific Vim initialization commands
You can read your ~/.vimrc file from ~/.ideavimrc with this command:
You can read your `~/.vimrc` file from `~/.ideavimrc` with this command:
source ~/.vimrc
Note, that IdeaVim currently parses ~/.ideavimrc file via simple pattern matching.
Note, that IdeaVim currently parses `~/.ideavimrc` file via simple pattern matching.
See [VIM-669](https://youtrack.jetbrains.com/issue/VIM-669) for proper parsing
of VimL files.
Also note that if you have overridden the `user.home` JVM option, this
will affect where IdeaVim looks for your .ideavimrc file. For example, if you
will affect where IdeaVim looks for your `.ideavimrc` file. For example, if you
have `-Duser.home=/my/alternate/home` then IdeaVim will source
`/my/alternate/home/.ideavimrc` instead of `~/.ideavimrc`.
@@ -137,50 +135,25 @@ Put your settings to `$XDG_CONFIG_HOME$/ideavim/ideavimrc` file.
Emulated Vim Plugins
--------------------
IdeaVim extensions emulate some plugins of the original Vim. In order to use
IdeaVim extensions, you have to enable them via this command in your `~/.ideavimrc`:
set <extension-name>
Available extensions:
* easymotion
* Setup:
* Install [IdeaVim-EasyMotion](https://plugins.jetbrains.com/plugin/13360-ideavim-easymotion/)
and [AceJump](https://plugins.jetbrains.com/plugin/7086-acejump/) plugins.
* `set easymotion`
* Emulates [vim-easymotion](https://github.com/easymotion/vim-easymotion)
* Commands: All commands with the mappings are supported. See the [full list of supported commands](https://github.com/AlexPl292/IdeaVim-EasyMotion#supported-commands).
* surround
* Setup: `set surround`
* Emulates [vim-surround](https://github.com/tpope/vim-surround)
* Commands: `ys`, `cs`, `ds`, `S`
* multiple-cursors
* Setup: `set multiple-cursors`
* Emulates [vim-multiple-cursors](https://github.com/terryma/vim-multiple-cursors)
* Commands: `<A-n>`, `<A-x>`, `<A-p>`, `g<A-n>`
* commentary
* Setup: `set commentary`
* Emulates [commentary.vim](https://github.com/tpope/vim-commentary)
* Commands: `gcc`, `gc + motion`, `v_gc`
* argtextobj [To Be Released]
* Setup: `set argtextobj`
* Emulates [argtextobj.vim](https://www.vim.org/scripts/script.php?script_id=2699)
* Additional text objects: `aa`, `ia`
* textobj-entire [To Be Released]
* Setup: `set textobj-entire`
* Emulates [vim-textobj-entire](https://github.com/kana/vim-textobj-entire)
* Additional text objects: `ae`, `ie`
See [doc/emulated-plugins.md](doc/emulated-plugins.md)
Changes to the IDE
------------------
### Executing IDE Actions
IdeaVim adds two commands for listing and executing arbitrary IDE actions as
Ex commands or via `:map` command mappings:
* `:actionlist [pattern]`
* Find IDE actions by name or keymap pattern (E.g. `:actionlist extract`, `:actionlist <C-D`)
* `:action {name}`
* Execute an action named `NAME`
For example, here `\r` is mapped to the Reformat Code action:
:map \r :action ReformatCode<CR>
### Undo/Redo
The IdeaVim plugin uses the undo/redo functionality of the IntelliJ Platform,
@@ -199,21 +172,6 @@ improvement.
See also [unresolved escape issues](https://youtrack.jetbrains.com/issues/VIM?q=%23Unresolved+Help+topic%3A+i_Esc).
### Executing IDE Actions
IdeaVim adds two commands for listing and executing arbitrary IDE actions as
Ex commands or via `:map` command mappings:
* `:actionlist [pattern]`
* Find IDE actions by name or keymap pattern (E.g. `:actionlist extract`, `:actionlist <C-D`)
* `:action {name}`
* Execute an action named `NAME`
For example, here `\r` is mapped to the Reformat Code action:
:map \r :action ReformatCode<CR>
Contributing
------------
@@ -231,3 +189,19 @@ License
IdeaVim is licensed under the terms of the GNU Public License version 2
or any later version.
<!-- Badges -->
[jb-official]: https://confluence.jetbrains.com/display/ALL/JetBrains+on+GitHub
[jb-official-svg]: https://jb.gg/badges/official.svg
[plugin-repo]: https://plugins.jetbrains.com/plugin/164-ideavim
[plugin-downloads-svg]: http://img.shields.io/jetbrains/plugin/d/IdeaVIM
[plugin-rating-svg]: http://img.shields.io/jetbrains/plugin/r/rating/IdeaVIM
[plugin-version-svg]: https://img.shields.io/jetbrains/plugin/v/ideavim?label=version
[gitter-svg]: https://badges.gitter.im/JetBrains/ideavim.svg
[gitter]: https://gitter.im/JetBrains/ideavim?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge&utm_content=badge
[twitter]: https://twitter.com/ideavim
[twitter-svg]: https://img.shields.io/twitter/follow/ideavim?label=twitter%20%40ideavim

View File

@@ -9,7 +9,7 @@ buildscript {
}
plugins {
id 'org.jetbrains.intellij' version '0.4.16'
id 'org.jetbrains.intellij' version '0.4.18'
}
apply plugin: 'java'
@@ -37,11 +37,7 @@ intellij {
downloadSources Boolean.valueOf(downloadIdeaSources)
instrumentCode Boolean.valueOf(instrumentPluginCode)
intellijRepo = "https://www.jetbrains.com/intellij-repository"
if (!Boolean.valueOf(legacyNoJavaPlugin)) {
// Since 192 version of IJ java plugin should be defined separately
// Set `legacyNoJavaPlugin` to true if you are going to run tests under idea version < 192
plugins = ['java']
}
plugins = ['java']
publishPlugin {
channels publishChannels.split(',')
@@ -74,15 +70,56 @@ tasks.register("slackEapNotification") {
doLast {
if (!slackUrl) return
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": "New version of IdeaVim",
"blocks": [
{
"type": "section",
"text": {
"type": "mrkdwn",
"text": "IdeaVim EAP $version has been relesed\\n$changeLog"
}
}
]
}
"""
post.setRequestMethod("POST")
post.setDoOutput(true)
post.setRequestProperty("Content-Type", "application/json")
post.getOutputStream().write(message.getBytes("UTF-8"))
def postRC = post.getResponseCode()
println(postRC)
if(postRC.equals(200)) {
if(postRC == 200) {
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()
}

68
doc/emulated-plugins.md Normal file
View File

@@ -0,0 +1,68 @@
Emulated Vim Plugins
--------------------
IdeaVim extensions emulate plugins of the original Vim. In order to use
IdeaVim extensions, you have to enable them via this command in your `~/.ideavimrc`:
set <extension-name>
Available extensions:
## easymotion
* Setup:
* Install [IdeaVim-EasyMotion](https://plugins.jetbrains.com/plugin/13360-ideavim-easymotion/)
and [AceJump](https://plugins.jetbrains.com/plugin/7086-acejump/) plugins.
* `set easymotion`
* Emulates [vim-easymotion](https://github.com/easymotion/vim-easymotion)
* Commands: All commands with the mappings are supported. See the [full list of supported commands](https://github.com/AlexPl292/IdeaVim-EasyMotion#supported-commands).
## surround
* Setup: `set surround`
* Emulates [vim-surround](https://github.com/tpope/vim-surround)
* Commands: `ys`, `cs`, `ds`, `S`
## multiple-cursors
* Setup: `set multiple-cursors`
* Emulates [vim-multiple-cursors](https://github.com/terryma/vim-multiple-cursors)
* Commands: `<A-n>`, `<A-x>`, `<A-p>`, `g<A-n>`
## commentary
* Setup: `set commentary`
* Emulates [commentary.vim](https://github.com/tpope/vim-commentary)
* 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`
## exchange [To Be Released]
* Setup: `set exchange`
* Emulates [vim-exchange](https://github.com/tommcdo/vim-exchange)
* Commands: `cx`, `cxx`, `X`, `cxc`
## textobj-entire
* Setup: `set textobj-entire`
* Emulates [vim-textobj-entire](https://github.com/kana/vim-textobj-entire)
* Additional text objects: `ae`, `ie`

View File

@@ -65,6 +65,7 @@ The following `:set` commands can appear in `~/.ideavimrc` or be set manually in
same as ideaselection - IdeaVim ONLY
'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
'sidescrolloff' 'siso' min. number of columns to left and right of cursor
'smartcase' 'scs' no ignore case when pattern is uppercase
@@ -109,7 +110,7 @@ The following `:set` commands can appear in `~/.ideavimrc` or be set manually in
If false, IdeaVim icon won't be shown in the status bar.
Works only from `~/.ideavimrc` after the IDE restart.
`ideastatusicon` `ideastatusicon` String(default "enabled") [To Be Released]
`ideastatusicon` `ideastatusicon` String(default "enabled")
Define the behavior of IdeaVim icon in the status bar.
@@ -117,8 +118,6 @@ The following `:set` commands can appear in `~/.ideavimrc` or be set manually in
- 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

View File

@@ -1,16 +1,13 @@
# suppress inspection "UnusedProperty" for whole file
ideaVersion=201-EAP-SNAPSHOT
ideaVersion=2020.1
downloadIdeaSources=true
instrumentPluginCode=true
version=SNAPSHOT
javaVersion=1.8
kotlinVersion=1.3.61
kotlinVersion=1.3.71
publishUsername=username
publishToken=token
publishChannels=eap
# Since 192 version of IJ java plugin should be defined separately
# Set this value to true if you are going to run tests under idea version < 192
legacyNoJavaPlugin=false
slackUrl=

View File

@@ -1,5 +1,6 @@
#Fri Apr 10 10:57:10 MSK 2020
distributionUrl=https\://services.gradle.org/distributions/gradle-6.3-all.zip
distributionBase=GRADLE_USER_HOME
distributionPath=wrapper/dists
distributionUrl=https\://services.gradle.org/distributions/gradle-6.2.1-bin.zip
zipStoreBase=GRADLE_USER_HOME
zipStorePath=wrapper/dists
zipStoreBase=GRADLE_USER_HOME

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.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.StartInsertDigraphAction" 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.InsertCompletedDigraphAction" mappingModes="IC" keys="«C-K»"/>
<vimAction implementation="com.maddyhome.idea.vim.action.change.insert.InsertCompletedLiteralAction" mappingModes="IC" keys="«C-V»,«C-Q»"/>
<!-- Delete -->
<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.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.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.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.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.PutVisualTextNoIndentAction" mappingModes="X" keys="[p,]p,]P,[P"/>
<vimAction implementation="com.maddyhome.idea.vim.action.copy.PutVisualTextMoveCursorAction" mappingModes="X" keys="gp,gP"/>
<vimAction implementation="com.maddyhome.idea.vim.action.copy.PutVisualTextBeforeCursorAction" mappingModes="X" keys="P"/>
<vimAction implementation="com.maddyhome.idea.vim.action.copy.PutVisualTextAfterCursorAction" mappingModes="X" keys="p"/>
<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 -->
<vimAction implementation="com.maddyhome.idea.vim.action.file.FileSaveCloseAction" mappingModes="N" keys="ZQ,ZZ"/>

View File

@@ -61,5 +61,6 @@
<vimExCommand implementation="com.maddyhome.idea.vim.ex.handler.PreviousTabHandler" names="tabp[revious],tabN[ext]"/>
<vimExCommand implementation="com.maddyhome.idea.vim.ex.handler.TabOnlyHandler" names="tabo[nly]"/>
<vimExCommand implementation="com.maddyhome.idea.vim.ex.handler.BufferListHandler" names="buffers,ls,files"/>
<vimExCommand implementation="com.maddyhome.idea.vim.ex.handler.BufferHandler" names="b[uffer]"/>
</extensions>
</idea-plugin>

View File

@@ -5,5 +5,7 @@
<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"/>
<vimExtension implementation="com.maddyhome.idea.vim.extension.exchange.VimExchangeExtension"/>
</extensions>
</idea-plugin>

View File

@@ -0,0 +1,25 @@
<idea-plugin>
<applicationListeners>
<listener class="com.maddyhome.idea.vim.ui.ExEntryPanel$LafListener"
topic="com.intellij.ide.ui.LafManagerListener"/>
</applicationListeners>
<projectListeners>
<listener class="com.maddyhome.idea.vim.ui.ExOutputPanel$LafListener"
topic="com.intellij.ide.ui.LafManagerListener"/>
<listener class="com.maddyhome.idea.vim.listener.VimListenerManager$VimFileEditorManagerListener"
topic="com.intellij.openapi.fileEditor.FileEditorManagerListener"/>
<listener class="com.maddyhome.idea.vim.listener.IdeaSpecifics$VimActionListener"
topic="com.intellij.openapi.actionSystem.ex.AnActionListener"/>
<listener class="com.maddyhome.idea.vim.listener.IdeaSpecifics$VimTemplateManagerListener"
topic="com.intellij.codeInsight.template.TemplateManagerListener"/>
<listener class="com.maddyhome.idea.vim.group.MarkGroup$MarkListener"
topic="com.intellij.ide.bookmarks.BookmarksListener"/>
<listener class="com.maddyhome.idea.vim.listener.IdeaSpecifics$VimFindModelListener"
topic="com.intellij.find.FindModelListener"/>
</projectListeners>
</idea-plugin>

View File

@@ -3,9 +3,8 @@
<id>IdeaVIM</id>
<change-notes><![CDATA[
<ul>
<li>Support argtextobj.vim plugin emulation</li>
<li>Support vim-textobj-entire plugin emulation</li>
<li>Support ls/buffers/files command</li>
<li>vim-exchange plugin emulation</li>
<li>A new floating action for reloading .ideavimrc</li>
<li>Various bug fixes</li>
</ul>
<p>See also the complete <a href="https://github.com/JetBrains/ideavim/blob/master/CHANGES.md">changelog</a>.</p>
@@ -24,7 +23,7 @@
<!-- Please search for "[VERSION UPDATE]" in project in case you update the since-build version -->
<!-- Check for [Version Update] tag in YouTrack as well -->
<idea-version since-build="183.4284.148"/>
<idea-version since-build="201.5985"/>
<!-- Mark the plugin as compatible with RubyMine and other products based on the IntelliJ platform -->
<depends>com.intellij.modules.lang</depends>
@@ -52,19 +51,25 @@
<extensions defaultExtensionNs="com.intellij">
<applicationConfigurable groupId="editor" instance="com.maddyhome.idea.vim.ui.VimEmulationConfigurable"/>
<projectService serviceImplementation="com.maddyhome.idea.vim.group.NotificationService"/>
<statusBarWidgetProvider implementation="com.maddyhome.idea.vim.StatusBarIconProvider"/>
<statusBarWidgetFactory implementation="com.maddyhome.idea.vim.ui.StatusBarIconFactory"/>
<statusBarWidgetFactory implementation="com.maddyhome.idea.vim.ui.ShowCmdStatusBarWidgetFactory" order="first"/>
<applicationService serviceImplementation="com.maddyhome.idea.vim.VimLocalConfig"/>
<applicationService serviceImplementation="com.maddyhome.idea.vim.VimPlugin"/>
<postStartupActivity implementation="com.maddyhome.idea.vim.PluginStartup"/>
<!-- Initialise as early as possible so that we're ready to edit quickly. This is especially important for Rider,
which (at least for 2020.1) has some long running activities that block other startup extensions. None of the
core platform activities have IDs, so we can't use "before ID". We have to use "first" -->
<postStartupActivity implementation="com.maddyhome.idea.vim.PluginStartup" order="first"/>
<editorFloatingToolbarProvider implementation="com.maddyhome.idea.vim.ui.ReloadFloatingToolbar"/>
</extensions>
<xi:include href="/META-INF/includes/ApplicationServices.xml" xpointer="xpointer(/idea-plugin/*)"/>
<xi:include href="/META-INF/includes/VimActions.xml" xpointer="xpointer(/idea-plugin/*)"/>
<xi:include href="/META-INF/includes/VimExCommands.xml" xpointer="xpointer(/idea-plugin/*)"/>
<xi:include href="/META-INF/includes/VimExtensions.xml" xpointer="xpointer(/idea-plugin/*)"/>
<xi:include href="/META-INF/includes/VimListeners.xml" xpointer="xpointer(/idea-plugin/*)"/>
<actions>
<action id="VimPluginToggle" class="com.maddyhome.idea.vim.action.VimPluginToggleAction" text="Vim Emulator" description="Toggle the vim plugin On/off">
@@ -75,6 +80,20 @@
<action id="VimInternalAddInlays" class="com.maddyhome.idea.vim.action.internal.AddInlaysAction" text="Add Test Inlays | IdeaVim Internal" internal="true"/>
<action id="VimShortcutKeyAction" class="com.maddyhome.idea.vim.action.VimShortcutKeyAction" text="Shortcuts"/>
<action id="VimActions" class="com.maddyhome.idea.vim.VimActions" text="Vim Actions"/>
<action id="VimActions" class="com.maddyhome.idea.vim.ui.VimActions" text="Vim Actions"/>
<!-- [Version Update] 202+ use-shortcut-of="ExternalSystem.ProjectRefreshAction" -->
<group id="IdeaVim.ReloadVimRc.group" class="com.maddyhome.idea.vim.ui.ReloadFloatingToolbarActionGroup">
<action id="IdeaVim.ReloadVimRc.reload" class="com.maddyhome.idea.vim.ui.ReloadVimRc">
<keyboard-shortcut first-keystroke="control shift O" keymap="$default"/>
<keyboard-shortcut first-keystroke="control shift O" keymap="Eclipse" remove="true"/>
<keyboard-shortcut first-keystroke="control shift O" keymap="NetBeans 6.5" remove="true"/>
<keyboard-shortcut first-keystroke="control shift O" keymap="Visual Studio" remove="true"/>
<keyboard-shortcut first-keystroke="meta shift O" keymap="Mac OS X" replace-all="true"/>
<keyboard-shortcut first-keystroke="meta shift O" keymap="Eclipse (Mac OS X)" replace-all="true" remove="true"/>
<keyboard-shortcut first-keystroke="meta shift O" keymap="Xcode" replace-all="true" remove="true"/>
<keyboard-shortcut first-keystroke="meta shift I" keymap="Mac OS X 10.5+" replace-all="true"/>
</action>
</group>
</actions>
</idea-plugin>

View File

@@ -0,0 +1,9 @@
<svg xmlns="http://www.w3.org/2000/svg" width="40" height="40" viewBox="0 0 40 40">
<defs>
<linearGradient id="ideavim_plugin-a" x1="-6.748%" x2="47.286%" y1="33.61%" y2="85.907%">
<stop offset="0%" stop-color="#3BEA62"/>
<stop offset="100%" stop-color="#087CFA"/>
</linearGradient>
</defs>
<polygon fill="url(#ideavim_plugin-a)" fill-rule="evenodd" points="29.019 0 13.988 26.119 13.988 0 0 0 0 40 16.953 40 40 0"/>
</svg>

After

Width:  |  Height:  |  Size: 450 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 194 KiB

View File

@@ -56,7 +56,6 @@ public class EventFacade {
private static final @NotNull EventFacade ourInstance = new EventFacade();
private @Nullable TypedActionHandler myOriginalTypedActionHandler;
private Map<Project, MessageBusConnection> connections = new HashMap<>();
private EventFacade() {
}
@@ -92,31 +91,6 @@ public class EventFacade {
action.unregisterCustomShortcutSet(component);
}
public void connectFileEditorManagerListener(@NotNull Project project, @NotNull FileEditorManagerListener listener) {
final MessageBusConnection connection = getConnection(project);
connection.subscribe(FileEditorManagerListener.FILE_EDITOR_MANAGER, listener);
}
public void connectAnActionListener(@NotNull Project project, @NotNull AnActionListener listener) {
final MessageBusConnection connection = getConnection(project);
connection.subscribe(AnActionListener.TOPIC, listener);
}
public void connectTemplateStartedListener(@NotNull Project project, @NotNull TemplateManagerListener listener) {
final MessageBusConnection connection = getConnection(project);
connection.subscribe(TemplateManager.TEMPLATE_STARTED_TOPIC, listener);
}
public void connectBookmarkListener(@NotNull Project project, @NotNull BookmarksListener bookmarksListener) {
final MessageBusConnection connection = getConnection(project);
connection.subscribe(BookmarksListener.TOPIC, bookmarksListener);
}
public void connectFindModelListener(@NotNull Project project, @NotNull FindModelListener findModelListener) {
final MessageBusConnection connection = getConnection(project);
connection.subscribe(FindManager.FIND_MODEL_TOPIC, findModelListener);
}
public void addDocumentListener(@NotNull Document document, @NotNull DocumentListener listener) {
document.addDocumentListener(listener);
}
@@ -168,30 +142,14 @@ public class EventFacade {
}
public void registerLookupListener(@NotNull Project project, @NotNull PropertyChangeListener propertyChangeListener) {
LookupManager.getInstance(project).addPropertyChangeListener(propertyChangeListener, project);
VimProjectService parentDisposable = VimProjectService.getInstance(project);
LookupManager.getInstance(project).addPropertyChangeListener(propertyChangeListener, parentDisposable);
}
public void removeLookupListener(@NotNull Project project, @NotNull PropertyChangeListener propertyChangeListener) {
LookupManager.getInstance(project).removePropertyChangeListener(propertyChangeListener);
}
public void disableBusConnection() {
connections.values().forEach(MessageBusConnection::disconnect);
connections.clear();
}
private MessageBusConnection getConnection(Project project) {
if (!connections.containsKey(project)) {
final MessageBusConnection connection = project.getMessageBus().connect();
connections.put(project, connection);
Disposer.register(project, () -> {
connection.disconnect();
connections.remove(project);
});
}
return connections.get(project);
}
private @NotNull TypedAction 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.ListPopup;
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.command.*;
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.VimListenerSuppressor;
import com.maddyhome.idea.vim.option.OptionsManager;
import com.maddyhome.idea.vim.ui.ShowCmd;
import org.jetbrains.annotations.Contract;
import org.jetbrains.annotations.NonNls;
import org.jetbrains.annotations.NotNull;
@@ -155,16 +158,6 @@ public class KeyHandler {
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
* processing.
@@ -221,7 +214,7 @@ public class KeyHandler {
try {
if (!allowKeyMappings || !handleKeyMapping(editor, key, context)) {
if (isCommandCountKey(chKey, editorState)) {
commandBuilder.addCountCharacter(chKey);
commandBuilder.addCountCharacter(key);
} else if (isDeleteCommandCountKey(key, editorState)) {
commandBuilder.deleteCountCharacter();
} else if (isEditorReset(key, editorState)) {
@@ -232,21 +225,29 @@ public class KeyHandler {
else if (isExpectingCharArgument(commandBuilder)) {
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
// to an entered command. Let's figure out which it is.
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,
// return the node matching this keystroke
final Node node = mapOpCommand(key, commandBuilder.getChildNode(key), editorState);
if (node instanceof CommandNode) {
handleCommandNode(editor, context, key, (CommandNode) node, editorState);
commandBuilder.addKey(key);
} else if (node instanceof CommandPartNode) {
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 (editorState.getMode() == CommandState.Mode.INSERT || editorState.getMode() == CommandState.Mode.REPLACE) {
shouldRecord &= VimPlugin.getChange().processKey(editor, context, key);
@@ -271,10 +272,11 @@ public class KeyHandler {
// Do we have a fully entered command at this point? If so, let's execute it.
if (commandBuilder.isReady()) {
executeCommand(editor, key, context, editorState);
executeCommand(editor, context, editorState);
}
else if (commandBuilder.isBad()) {
editorState.resetOpPending();
editorState.resetRegisterPending();
VimPlugin.indicateError();
reset(editor);
}
@@ -283,6 +285,9 @@ public class KeyHandler {
if (shouldRecord && editorState.isRecording()) {
VimPlugin.getRegister().recordKeyStroke(key);
}
// This will update immediately, if we're on the EDT (which we are)
ShowCmd.INSTANCE.update();
}
/**
@@ -558,7 +563,9 @@ public class KeyHandler {
private boolean isCommandCountKey(char chKey, @NotNull CommandState editorState) {
// Make sure to avoid handling '0' as the start of a count.
final CommandBuilder commandBuilder = editorState.getCommandBuilder();
return (editorState.getMode() == CommandState.Mode.COMMAND || editorState.getMode() == CommandState.Mode.VISUAL)
return ((editorState.getMode() == CommandState.Mode.COMMAND
&&editorState.getSubMode()!=CommandState.SubMode.REGISTER_PENDING)
|| editorState.getMode() == CommandState.Mode.VISUAL)
&& commandBuilder.isExpectingCount() && Character.isDigit(chKey) && (commandBuilder.getCount() > 0 || chKey != '0');
}
@@ -570,7 +577,29 @@ public class KeyHandler {
}
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) {
@@ -615,10 +644,12 @@ public class KeyHandler {
if (commandBuilder.getExpectedArgumentType() == Argument.Type.DIGRAPH) {
if (DigraphSequence.isDigraphStart(key)) {
editorState.startDigraphSequence();
editorState.getCommandBuilder().addKey(key);
return true;
}
if (DigraphSequence.isLiteralStart(key)) {
editorState.startLiteralSequence();
editorState.getCommandBuilder().addKey(key);
return true;
}
}
@@ -626,7 +657,7 @@ public class KeyHandler {
DigraphResult res = editorState.processDigraphKey(key, editor);
switch (res.getResult()) {
case DigraphResult.RES_HANDLED:
case DigraphResult.RES_BAD:
editorState.getCommandBuilder().addKey(key);
return true;
case DigraphResult.RES_DONE:
@@ -637,10 +668,20 @@ public class KeyHandler {
if (stroke == null) {
return false;
}
editorState.getCommandBuilder().addKey(key);
handleKey(editor, stroke, context);
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:
// 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) {
commandBuilder.fallbackToCharacterArgument();
handleKey(editor, key, context);
@@ -653,7 +694,6 @@ public class KeyHandler {
}
private void executeCommand(@NotNull Editor editor,
@NotNull KeyStroke key,
@NotNull DataContext context,
@NotNull CommandState editorState) {
final Command command = editorState.getCommandBuilder().buildCommand();
@@ -676,7 +716,7 @@ public class KeyHandler {
}
if (ApplicationManager.getApplication().isDispatchThread()) {
Runnable action = new ActionRunner(editor, context, command, key);
Runnable action = new ActionRunner(editor, context, command);
EditorActionHandlerBase cmdAction = command.getAction();
String name = cmdAction.getId();
@@ -714,7 +754,7 @@ public class KeyHandler {
}
else {
final Argument.Type argumentType = action.getArgumentType();
startWaitingForArgument(editor, context, key.getKeyChar(), argumentType, editorState);
startWaitingForArgument(editor, context, key.getKeyChar(), action, argumentType, editorState);
partialReset(editor);
}
@@ -751,6 +791,7 @@ public class KeyHandler {
private void startWaitingForArgument(Editor editor,
DataContext context,
char key,
@NotNull EditorActionHandlerBase action,
@NotNull Argument.Type argument,
CommandState editorState) {
final CommandBuilder commandBuilder = editorState.getCommandBuilder();
@@ -761,6 +802,17 @@ public class KeyHandler {
}
editorState.pushModes(editorState.getMode(), CommandState.SubMode.OP_PENDING);
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:
// 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.
@@ -884,11 +936,10 @@ public class KeyHandler {
*/
static class ActionRunner implements Runnable {
@Contract(pure = true)
ActionRunner(Editor editor, DataContext context, Command cmd, KeyStroke key) {
ActionRunner(Editor editor, DataContext context, Command cmd) {
this.editor = editor;
this.context = context;
this.cmd = cmd;
this.key = key;
}
@Override
@@ -897,6 +948,11 @@ public class KeyHandler {
editorState.getCommandBuilder().setCommandState(CurrentCommandState.NEW_COMMAND);
final Character register = cmd.getRegister();
if (register != null) {
VimPlugin.getRegister().selectRegister(register);
}
executeVimAction(editor, cmd.getAction(), context);
if (editorState.getMode() == CommandState.Mode.INSERT || editorState.getMode() == CommandState.Mode.REPLACE) {
VimPlugin.getChange().processCommand(editor, cmd);
@@ -905,10 +961,8 @@ public class KeyHandler {
// 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
// selected by the user was to the empty register - unless we just executed the "select register" command.
if (cmd.getType() != Command.Type.SELECT_REGISTER) {
VimPlugin.getRegister().resetRegister();
}
// selected by the user was to the empty register
VimPlugin.getRegister().resetRegister();
// 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
@@ -925,7 +979,6 @@ public class KeyHandler {
private final Editor editor;
private final DataContext context;
private final Command cmd;
private final KeyStroke key;
}
private TypedActionHandler origHandler;

View File

@@ -18,17 +18,14 @@
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 {
class PluginStartup : StartupActivity.DumbAware {
private var firstInitializationOccurred = false

View File

@@ -34,33 +34,6 @@ public class RegisterActions {
public static final ExtensionPointName<ActionBeanClass> VIM_ACTIONS_EP =
ExtensionPointName.create("IdeaVIM.vimAction");
private static boolean initialRegistration = false;
static {
// IdeaVim doesn't support contribution to VIM_ACTIONS_EP extension point, so technically we can skip this update,
// but let's support dynamic plugins in a more classic way and reload actions on every EP change.
// TODO: [VERSION UPDATE] since 191 use
// ExtensionPoint.addExtensionPointListener(ExtensionPointListener<T>, boolean, Disposable)
// TODO: [VERSION UPDATE] since 201 use
// ExtensionPoint.addExtensionPointListener(ExtensionPointChangeListener, boolean, Disposable)
VIM_ACTIONS_EP.getPoint(null).addExtensionPointListener(new ExtensionPointListener<ActionBeanClass>() {
@Override
public void extensionAdded(@NotNull ActionBeanClass extension, PluginDescriptor pluginDescriptor) {
// Suppress listener before the `VimPlugin.turnOn()` function execution. This logic should be rewritten after
// version update (or earlier).
if (!initialRegistration) return;
unregisterActions();
registerActions();
}
@Override
public void extensionRemoved(@NotNull ActionBeanClass extension, PluginDescriptor pluginDescriptor) {
if (!initialRegistration) return;
unregisterActions();
registerActions();
}
});
}
/**
* Register all the key/action mappings for the plugin.
@@ -68,7 +41,16 @@ public class RegisterActions {
public static void registerActions() {
registerVimCommandActions();
registerEmptyShortcuts();
initialRegistration = true;
registerEpListener();
}
private static void registerEpListener() {
// IdeaVim doesn't support contribution to VIM_ACTIONS_EP extension point, so technically we can skip this update,
// but let's support dynamic plugins in a more classic way and reload actions on every EP change.
VIM_ACTIONS_EP.getPoint(null).addExtensionPointListener(() -> {
unregisterActions();
registerActions();
}, false, VimPlugin.getInstance());
}
public static @Nullable EditorActionHandlerBase findAction(@NotNull String id) {

View File

@@ -33,7 +33,6 @@ import org.jdom.Element
Storage("\$APP_CONFIG$$/vim_local_settings.xml", roamingType = RoamingType.DISABLED, deprecated = true),
Storage("\$APP_CONFIG$/vim_local_settings.xml", roamingType = RoamingType.DISABLED, deprecated = true)
])
// TODO: 27.01.2020 [VERSION UPDATE] 2019.3 https://www.jetbrains.org/intellij/sdk/docs/basics/plugin_structure/plugin_services.html#light-services
@Deprecated("The data from this class will be stored in vim_settings")
class VimLocalConfig : PersistentStateComponent<Element> {
override fun getState(): Element? = null
@@ -50,6 +49,7 @@ class VimLocalConfig : PersistentStateComponent<Element> {
companion object {
fun initialize() {
@Suppress("DEPRECATION")
ServiceManager.getService(VimLocalConfig::class.java)
}
}

View File

@@ -19,14 +19,11 @@ package com.maddyhome.idea.vim;
import com.intellij.ide.plugins.IdeaPluginDescriptor;
import com.intellij.ide.plugins.PluginManager;
import com.intellij.ide.util.PropertiesComponent;
import com.intellij.notification.Notification;
import com.intellij.notification.NotificationListener;
import com.intellij.openapi.Disposable;
import com.intellij.openapi.application.Application;
import com.intellij.openapi.application.ApplicationInfo;
import com.intellij.openapi.application.ApplicationManager;
import com.intellij.openapi.application.PermanentInstallationID;
import com.intellij.openapi.components.PersistentStateComponent;
import com.intellij.openapi.components.ServiceManager;
import com.intellij.openapi.components.State;
@@ -40,12 +37,9 @@ import com.intellij.openapi.options.ShowSettingsUtil;
import com.intellij.openapi.project.Project;
import com.intellij.openapi.project.ProjectManager;
import com.intellij.openapi.ui.Messages;
import com.intellij.openapi.util.JDOMUtil;
import com.intellij.openapi.util.SystemInfo;
import com.intellij.openapi.vfs.CharsetToolkit;
import com.intellij.openapi.wm.StatusBar;
import com.intellij.openapi.wm.WindowManager;
import com.intellij.util.io.HttpRequests;
import com.maddyhome.idea.vim.ex.CommandParser;
import com.maddyhome.idea.vim.ex.vimscript.VimScriptParser;
import com.maddyhome.idea.vim.extension.VimExtensionRegistrar;
@@ -57,18 +51,17 @@ import com.maddyhome.idea.vim.helper.MacKeyRepeat;
import com.maddyhome.idea.vim.listener.VimListenerManager;
import com.maddyhome.idea.vim.option.OptionsManager;
import com.maddyhome.idea.vim.ui.ExEntryPanel;
import com.maddyhome.idea.vim.ui.StatusBarIconFactory;
import com.maddyhome.idea.vim.ui.VimEmulationConfigurable;
import com.maddyhome.idea.vim.ui.VimRcFileState;
import org.jdom.Element;
import org.jdom.JDOMException;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import javax.swing.event.HyperlinkEvent;
import java.awt.*;
import java.io.File;
import java.io.IOException;
import java.net.URLEncoder;
import java.util.concurrent.TimeUnit;
import java.util.List;
import static com.maddyhome.idea.vim.group.EditorGroup.EDITOR_STORE_ELEMENT;
import static com.maddyhome.idea.vim.group.KeyGroup.SHORTCUT_CONFLICTS_ELEMENT;
@@ -84,7 +77,6 @@ import static com.maddyhome.idea.vim.group.KeyGroup.SHORTCUT_CONFLICTS_ELEMENT;
@State(name = "VimSettings", storages = {@Storage("$APP_CONFIG$/vim_settings.xml")})
public class VimPlugin implements PersistentStateComponent<Element>, Disposable {
private static final String IDEAVIM_PLUGIN_ID = "IdeaVIM";
private static final String IDEAVIM_STATISTICS_TIMESTAMP_KEY = "ideavim.statistics.timestamp";
private static final int STATE_VERSION = 6;
private static long lastBeepTimeMillis;
@@ -114,7 +106,8 @@ public class VimPlugin implements PersistentStateComponent<Element>, Disposable
Application application = ApplicationManager.getApplication();
if (application.isUnitTestMode()) {
application.invokeAndWait(this::turnOnPlugin);
} else {
}
else {
application.invokeLater(this::turnOnPlugin);
}
}
@@ -150,53 +143,6 @@ public class VimPlugin implements PersistentStateComponent<Element>, Disposable
return ServiceManager.getService(MotionGroup.class);
}
/**
* Reports statistics about installed IdeaVim and enabled Vim emulation.
* <p>
* See https://github.com/go-lang-plugin-org/go-lang-idea-plugin/commit/5182ab4a1d01ad37f6786268a2fe5e908575a217
*/
public static void statisticReport() {
final PropertiesComponent propertiesComponent = PropertiesComponent.getInstance();
final long lastUpdate = propertiesComponent.getOrInitLong(IDEAVIM_STATISTICS_TIMESTAMP_KEY, 0);
final boolean outOfDate = lastUpdate == 0 || System.currentTimeMillis() - lastUpdate > TimeUnit.DAYS.toMillis(1);
if (outOfDate && isEnabled()) {
ApplicationManager.getApplication().executeOnPooledThread(() -> {
try {
final String buildNumber = ApplicationInfo.getInstance().getBuild().asString();
final String version = URLEncoder.encode(getVersion(), CharsetToolkit.UTF8);
final String os = URLEncoder.encode(SystemInfo.OS_NAME + " " + SystemInfo.OS_VERSION, CharsetToolkit.UTF8);
final String uid = PermanentInstallationID.get();
final String url = "https://plugins.jetbrains.com/plugins/list" +
"?pluginId=" +
IDEAVIM_PLUGIN_ID +
"&build=" +
buildNumber +
"&pluginVersion=" +
version +
"&os=" +
os +
"&uuid=" +
uid;
PropertiesComponent.getInstance()
.setValue(IDEAVIM_STATISTICS_TIMESTAMP_KEY, String.valueOf(System.currentTimeMillis()));
HttpRequests.request(url).connect(request -> {
LOG.info("Sending statistics: " + url);
try {
JDOMUtil.load(request.getInputStream());
}
catch (JDOMException e) {
LOG.warn(e);
}
return null;
});
}
catch (IOException e) {
LOG.warn(e);
}
});
}
}
public static @NotNull ChangeGroup getChange() {
return ServiceManager.getService(ChangeGroup.class);
}
@@ -288,10 +234,15 @@ public class VimPlugin implements PersistentStateComponent<Element>, Disposable
ideavimrcRegistered = true;
if (!ApplicationManager.getApplication().isUnitTestMode()) {
final File ideaVimRc = VimScriptParser.findIdeaVimRc();
if (ideaVimRc != null) {
VimScriptParser.executeFile(ideaVimRc);
}
executeIdeaVimRc();
}
}
public void executeIdeaVimRc() {
final File ideaVimRc = VimScriptParser.findIdeaVimRc();
if (ideaVimRc != null) {
List<String> parsedLines = VimScriptParser.executeFile(ideaVimRc);
VimRcFileState.INSTANCE.saveFileState(ideaVimRc.getAbsolutePath(), parsedLines);
}
}
@@ -299,8 +250,6 @@ public class VimPlugin implements PersistentStateComponent<Element>, Disposable
return PluginId.getId(IDEAVIM_PLUGIN_ID);
}
// [VERSION UPDATE] 193+ remove suppress
@SuppressWarnings({"MissingRecentApi", "UnstableApiUsage"})
public static @NotNull String getVersion() {
final IdeaPluginDescriptor plugin = PluginManager.getPlugin(getPluginId());
if (!ApplicationManager.getApplication().isInternal()) {
@@ -328,7 +277,7 @@ public class VimPlugin implements PersistentStateComponent<Element>, Disposable
getInstance().turnOnPlugin();
}
VimStatusBar.INSTANCE.update();
StatusBarIconFactory.Companion.updateIcon();
}
public static boolean isError() {
@@ -392,10 +341,6 @@ public class VimPlugin implements PersistentStateComponent<Element>, Disposable
private void turnOnPlugin() {
ApplicationManager.getApplication().invokeLater(this::updateState);
getEditor().turnOn();
getSearch().turnOn();
VimListenerManager.INSTANCE.turnOn();
// Register vim actions in command mode
RegisterActions.registerActions();
@@ -407,27 +352,27 @@ public class VimPlugin implements PersistentStateComponent<Element>, Disposable
// Execute ~/.ideavimrc
registerIdeavimrc();
// Turing on should be performed after all commands registration
getSearch().turnOn();
VimListenerManager.INSTANCE.turnOn();
}
private void turnOffPlugin() {
KeyHandler.getInstance().fullReset(null);
// Unregister vim actions in command mode
RegisterActions.unregisterActions();
// Unregister ex handlers
CommandParser.getInstance().unregisterHandlers();
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
RegisterActions.unregisterActions();
// Unregister ex handlers
CommandParser.getInstance().unregisterHandlers();
}
private boolean stateUpdated = false;

View File

@@ -0,0 +1,39 @@
/*
* 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.Disposable
import com.intellij.openapi.components.Service
import com.intellij.openapi.components.ServiceManager
import com.intellij.openapi.project.Project
@Service
class VimProjectService(val project: Project) : Disposable {
override fun dispose() {}
companion object {
@JvmStatic
fun getInstance(project: Project): VimProjectService {
return ServiceManager.getService(project, VimProjectService::class.java)
}
}
}
val Project.vimDisposable
get() = VimProjectService.getInstance(this)

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());
private final @NotNull 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(final @NotNull Editor editor, final char charTyped, final @NotNull 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

@@ -20,7 +20,7 @@ package com.maddyhome.idea.vim.action
import com.intellij.openapi.actionSystem.AnActionEvent
import com.intellij.openapi.project.DumbAwareToggleAction
import com.maddyhome.idea.vim.VimActions
import com.maddyhome.idea.vim.ui.VimActions
import com.maddyhome.idea.vim.VimPlugin
/**

View File

@@ -86,27 +86,26 @@ class VimShortcutKeyAction : AnAction(), DumbAware {
@Suppress("DEPRECATION")
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 (aceJumpActive()) return false
val keyCode = keyStroke.keyCode
if (LookupManager.getActiveLookup(editor) != null) {
return LookupKeys.isEnabledForLookup(keyStroke)
}
if (keyCode == KeyEvent.VK_ESCAPE) {
return isEnabledForEscape(editor)
}
if (LookupManager.getActiveLookup(editor) != null && !LookupKeys.isEnabledForLookup(keyStroke)) return false
if (keyCode == KeyEvent.VK_ESCAPE) return isEnabledForEscape(editor)
if (editor.inInsertMode) { // XXX: <Tab> won't be recorded in macros
if (keyCode == KeyEvent.VK_TAB) {
VimPlugin.getChange().tabAction = true
return false
}
// Debug watch, Python console, etc.
if (NON_FILE_EDITOR_KEYS.contains(keyStroke) && !EditorHelper.isFileEditor(editor)) {
return false
}
}
if (VIM_ONLY_EDITOR_KEYS.contains(keyStroke)) {
return true
if (keyStroke in NON_FILE_EDITOR_KEYS && !EditorHelper.isFileEditor(editor)) return false
}
if (keyStroke in VIM_ONLY_EDITOR_KEYS) return true
val savedShortcutConflicts = VimPlugin.getKey().savedShortcutConflicts
return when (savedShortcutConflicts[keyStroke]) {
ShortcutOwner.VIM -> true
@@ -191,15 +190,15 @@ class VimShortcutKeyAction : AnAction(), DumbAware {
companion object {
@JvmField
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_INSERT, 0)).addAll(getKeyStrokes(KeyEvent.VK_DELETE, 0, InputEvent.CTRL_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_LEFT, 0, InputEvent.CTRL_MASK, InputEvent.SHIFT_MASK, InputEvent.CTRL_MASK or InputEvent.SHIFT_MASK))
.addAll(getKeyStrokes(KeyEvent.VK_RIGHT, 0, InputEvent.CTRL_MASK, InputEvent.SHIFT_MASK, InputEvent.CTRL_MASK or InputEvent.SHIFT_MASK))
.addAll(getKeyStrokes(KeyEvent.VK_HOME, 0, InputEvent.CTRL_MASK, InputEvent.SHIFT_MASK, InputEvent.CTRL_MASK or InputEvent.SHIFT_MASK))
.addAll(getKeyStrokes(KeyEvent.VK_END, 0, InputEvent.CTRL_MASK, InputEvent.SHIFT_MASK, InputEvent.CTRL_MASK or InputEvent.SHIFT_MASK))
.addAll(getKeyStrokes(KeyEvent.VK_PAGE_UP, 0, InputEvent.SHIFT_MASK, InputEvent.CTRL_MASK or InputEvent.SHIFT_MASK))
.addAll(getKeyStrokes(KeyEvent.VK_PAGE_DOWN, 0, InputEvent.SHIFT_MASK, InputEvent.CTRL_MASK or InputEvent.SHIFT_MASK)).build()
.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_DOWN_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_DOWN_MASK, InputEvent.SHIFT_DOWN_MASK, InputEvent.CTRL_DOWN_MASK or InputEvent.SHIFT_DOWN_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_DOWN_MASK, InputEvent.SHIFT_DOWN_MASK, InputEvent.CTRL_DOWN_MASK or InputEvent.SHIFT_DOWN_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_DOWN_MASK, InputEvent.CTRL_DOWN_MASK or InputEvent.SHIFT_DOWN_MASK))
.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"

View File

@@ -45,6 +45,7 @@ class OperatorAction : VimActionHandler.SingleExecution() {
if (!editor.commandState.isDotRepeatInProgress) {
VimRepeater.Extension.argumentCaptured = argument
}
val saveRepeatHandler = VimRepeater.repeatHandler
val motion = argument.motion
val range = MotionGroup
.getMotionRange(editor, editor.caretModel.primaryCaret, context, cmd.count, cmd.rawCount, argument)
@@ -52,7 +53,9 @@ class OperatorAction : VimActionHandler.SingleExecution() {
VimPlugin.getMark().setChangeMarks(editor, range)
val selectionType = SelectionType.fromCommandFlags(motion.flags)
KeyHandler.getInstance().reset(editor)
return operatorFunction.apply(editor, context, selectionType)
val result = operatorFunction.apply(editor, context, selectionType)
VimRepeater.repeatHandler = saveRepeatHandler
return result
}
}
return false

View File

@@ -64,7 +64,7 @@ class RepeatChangeAction : VimActionHandler.SingleExecution() {
}
state.setExecutingCommand(lastCommand)
KeyHandler.executeVimAction(editor, lastCommand.action, context)
KeyHandler.executeVimAction(editor, lastCommand.action!!, context)
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.command.Command
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.common.TextRange
import com.maddyhome.idea.vim.group.visual.VimSelection
import com.maddyhome.idea.vim.handler.VisualOperatorActionHandler
import com.maddyhome.idea.vim.helper.EditorHelper
import com.maddyhome.idea.vim.helper.enumSetOf
import com.maddyhome.idea.vim.helper.subMode
import java.util.*
/**
@@ -46,15 +44,18 @@ class DeleteVisualLinesAction : VisualOperatorActionHandler.ForEachCaret() {
context: DataContext,
cmd: Command,
range: VimSelection): Boolean {
val mode = editor.subMode
val textRange = range.toVimTextRange(false)
return if (mode == SubMode.VISUAL_BLOCK) {
VimPlugin.getChange()
.deleteRange(editor, editor.caretModel.primaryCaret, textRange, SelectionType.fromSubMode(mode), false)
} else {
val lineRange = TextRange(EditorHelper.getLineStartForOffset(editor, textRange.startOffset),
EditorHelper.getLineEndForOffset(editor, textRange.endOffset) + 1)
VimPlugin.getChange().deleteRange(editor, caret, lineRange, SelectionType.LINE_WISE, false)
val (usedCaret, usedRange, usedType) = when (range.type) {
SelectionType.BLOCK_WISE -> Triple(editor.caretModel.primaryCaret, textRange, range.type)
SelectionType.LINE_WISE -> Triple(caret, textRange, SelectionType.LINE_WISE)
SelectionType.CHARACTER_WISE -> {
val lineRange = TextRange(
EditorHelper.getLineStartForOffset(editor, textRange.startOffset),
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 {
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_MASK)),
listOf(KeyStroke.getKeyStroke(KeyEvent.VK_AT, KeyEvent.CTRL_MASK))
listOf(KeyStroke.getKeyStroke(KeyEvent.VK_2, KeyEvent.CTRL_DOWN_MASK or KeyEvent.SHIFT_DOWN_MASK)),
listOf(KeyStroke.getKeyStroke(KeyEvent.VK_2, KeyEvent.CTRL_DOWN_MASK)),
listOf(KeyStroke.getKeyStroke(KeyEvent.VK_AT, KeyEvent.CTRL_DOWN_MASK))
)
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.handler.ChangeEditorActionHandler
class PutTextBeforeCursorAction : 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 fun execute(editor: Editor,
context: DataContext,
count: Int,
rawCount: Int,
argument: Argument?): Boolean {
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)
val putData = PutData(textData, null, count, insertTextBeforeCaret, indent, caretAfterInsertedText, -1)
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 PutTextAfterCursorAction : 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 = 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 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 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
*/
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 flags: EnumSet<CommandFlags> = enumSetOf(CommandFlags.FLAG_EXIT_VISUAL)
override fun executeForAllCarets(editor: Editor,
context: DataContext,
cmd: Command,
caretsAndSelections: Map<Caret, VimSelection>): Boolean {
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[0].keyChar == 'P'
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)
}
}
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"
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))
)
@@ -85,7 +85,7 @@ class VimEditorTab : VimActionHandler.SingleExecution(), ComplicatedKeysAction {
private val actionName: String = "EditorTab"
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))
)

View File

@@ -28,8 +28,8 @@ import javax.swing.KeyStroke
class FilePreviousAction : VimActionHandler.SingleExecution(), ComplicatedKeysAction {
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_CIRCUMFLEX, KeyEvent.CTRL_MASK))
listOf(KeyStroke.getKeyStroke(KeyEvent.VK_6, KeyEvent.CTRL_DOWN_MASK or KeyEvent.SHIFT_DOWN_MASK)),
listOf(KeyStroke.getKeyStroke(KeyEvent.VK_CIRCUMFLEX, KeyEvent.CTRL_DOWN_MASK))
)
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(
listOf(KeyStroke.getKeyStroke(KeyEvent.VK_PAGE_DOWN, 0)),
listOf(KeyStroke.getKeyStroke(KeyEvent.VK_DOWN, KeyEvent.CTRL_MASK)),
listOf(KeyStroke.getKeyStroke(KeyEvent.VK_KP_DOWN, KeyEvent.CTRL_MASK)),
listOf(KeyStroke.getKeyStroke(KeyEvent.VK_DOWN, KeyEvent.SHIFT_MASK)),
listOf(KeyStroke.getKeyStroke(KeyEvent.VK_KP_DOWN, KeyEvent.SHIFT_MASK))
listOf(KeyStroke.getKeyStroke(KeyEvent.VK_DOWN, KeyEvent.CTRL_DOWN_MASK)),
listOf(KeyStroke.getKeyStroke(KeyEvent.VK_KP_DOWN, KeyEvent.CTRL_DOWN_MASK)),
listOf(KeyStroke.getKeyStroke(KeyEvent.VK_DOWN, KeyEvent.SHIFT_DOWN_MASK)),
listOf(KeyStroke.getKeyStroke(KeyEvent.VK_KP_DOWN, KeyEvent.SHIFT_DOWN_MASK))
)
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(
listOf(KeyStroke.getKeyStroke(KeyEvent.VK_PAGE_UP, 0)),
listOf(KeyStroke.getKeyStroke(KeyEvent.VK_UP, KeyEvent.CTRL_MASK)),
listOf(KeyStroke.getKeyStroke(KeyEvent.VK_KP_UP, KeyEvent.CTRL_MASK)),
listOf(KeyStroke.getKeyStroke(KeyEvent.VK_UP, KeyEvent.SHIFT_MASK)),
listOf(KeyStroke.getKeyStroke(KeyEvent.VK_KP_UP, KeyEvent.SHIFT_MASK))
listOf(KeyStroke.getKeyStroke(KeyEvent.VK_UP, KeyEvent.CTRL_DOWN_MASK)),
listOf(KeyStroke.getKeyStroke(KeyEvent.VK_KP_UP, KeyEvent.CTRL_DOWN_MASK)),
listOf(KeyStroke.getKeyStroke(KeyEvent.VK_UP, KeyEvent.SHIFT_DOWN_MASK)),
listOf(KeyStroke.getKeyStroke(KeyEvent.VK_KP_UP, KeyEvent.SHIFT_DOWN_MASK))
)
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 keyStrokesSet: Set<List<KeyStroke>> = setOf(
listOf(KeyStroke.getKeyStroke(KeyEvent.VK_LEFT, KeyEvent.CTRL_MASK)),
listOf(KeyStroke.getKeyStroke(KeyEvent.VK_KP_LEFT, KeyEvent.CTRL_MASK)),
listOf(KeyStroke.getKeyStroke(KeyEvent.VK_LEFT, KeyEvent.SHIFT_MASK)),
listOf(KeyStroke.getKeyStroke(KeyEvent.VK_KP_LEFT, KeyEvent.SHIFT_MASK))
listOf(KeyStroke.getKeyStroke(KeyEvent.VK_LEFT, KeyEvent.CTRL_DOWN_MASK)),
listOf(KeyStroke.getKeyStroke(KeyEvent.VK_KP_LEFT, KeyEvent.CTRL_DOWN_MASK)),
listOf(KeyStroke.getKeyStroke(KeyEvent.VK_LEFT, KeyEvent.SHIFT_DOWN_MASK)),
listOf(KeyStroke.getKeyStroke(KeyEvent.VK_KP_LEFT, KeyEvent.SHIFT_DOWN_MASK))
)
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 keyStrokesSet: Set<List<KeyStroke>> = setOf(
listOf(KeyStroke.getKeyStroke(KeyEvent.VK_RIGHT, KeyEvent.CTRL_MASK)),
listOf(KeyStroke.getKeyStroke(KeyEvent.VK_KP_RIGHT, KeyEvent.CTRL_MASK)),
listOf(KeyStroke.getKeyStroke(KeyEvent.VK_RIGHT, KeyEvent.SHIFT_MASK)),
listOf(KeyStroke.getKeyStroke(KeyEvent.VK_KP_RIGHT, KeyEvent.SHIFT_MASK))
listOf(KeyStroke.getKeyStroke(KeyEvent.VK_RIGHT, KeyEvent.CTRL_DOWN_MASK)),
listOf(KeyStroke.getKeyStroke(KeyEvent.VK_KP_RIGHT, KeyEvent.CTRL_DOWN_MASK)),
listOf(KeyStroke.getKeyStroke(KeyEvent.VK_RIGHT, KeyEvent.SHIFT_DOWN_MASK)),
listOf(KeyStroke.getKeyStroke(KeyEvent.VK_KP_RIGHT, KeyEvent.SHIFT_DOWN_MASK))
)
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 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 java.util.*
import javax.swing.KeyStroke
/**
* 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(
var rawCount: Int,
var action: EditorActionHandlerBase,
var action: EditorActionHandlerBase?,
val type: Type,
var flags: EnumSet<CommandFlags>,
var keys: List<KeyStroke>
var flags: EnumSet<CommandFlags>
) {
constructor(rawCount: Int, register: Char): this(rawCount, null, Type.SELECT_REGISTER, EnumSet.of(CommandFlags.FLAG_EXPECT_MORE)) {
this.register = register
}
init {
action.process(this)
action?.process(this)
}
var count: Int
@@ -45,6 +47,7 @@ data class Command(
}
var argument: Argument? = null
var register: Char? = null
enum class Type {
/**
@@ -72,12 +75,12 @@ data class Command(
*/
COPY,
PASTE,
// TODO REMOVE?
RESET,
/**
* Represents commands that select the register.
*/
SELECT_REGISTER,
// TODO REMOVE?
RESET,
OTHER_READONLY,
OTHER_WRITABLE,
/**
@@ -88,7 +91,7 @@ data class Command(
val isRead: Boolean
get() = when (this) {
MOTION, COPY, SELECT_REGISTER, OTHER_READONLY, COMPLETION -> true
MOTION, COPY, OTHER_READONLY, COMPLETION -> true
else -> false
}

View File

@@ -9,12 +9,13 @@ import java.util.*
import javax.swing.KeyStroke
class CommandBuilder(private var currentCommandPartNode: CommandPartNode) {
private val commandParts = Stack<Command>()
private var keys = mutableListOf<KeyStroke>()
private val commandParts = ArrayDeque<Command>()
private var keyList = mutableListOf<KeyStroke>()
var commandState = CurrentCommandState.NEW_COMMAND
var count = 0
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
// 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 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 isExpectingCount: Boolean
@@ -35,14 +36,23 @@ class CommandBuilder(private var currentCommandPartNode: CommandPartNode) {
}
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
keys = mutableListOf()
count = 0
}
fun popCommandPart() {
commandParts.pop()
expectedArgumentType = if (commandParts.size > 0) commandParts.peek().action.argumentType else null
fun pushCommandPart(register: Char) {
// We will never execute this command, but we need to push something to correctly handle counts on either side of a
// 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() {
@@ -52,16 +62,24 @@ class CommandBuilder(private var currentCommandPartNode: CommandPartNode) {
expectedArgumentType = Argument.Type.CHARACTER
}
fun addKey(keyStroke: KeyStroke) {
keys.add(keyStroke)
fun addKey(key: KeyStroke) {
keyList.add(key)
}
fun addCountCharacter(chKey: Char) {
count = (count * 10) + (chKey - '0')
fun addCountCharacter(key: KeyStroke) {
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() {
count /= 10
keyList.removeAt(keyList.size - 1)
}
fun setCurrentCommandPartNode(newNode: CommandPartNode) {
@@ -74,7 +92,7 @@ class CommandBuilder(private var currentCommandPartNode: CommandPartNode) {
fun isAwaitingCharOrDigraphArgument(): Boolean {
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
}
@@ -88,12 +106,12 @@ class CommandBuilder(private var currentCommandPartNode: CommandPartNode) {
}
fun completeCommandPart(argument: Argument) {
commandParts.peek().argument = argument
commandParts.peekLast().argument = argument
commandState = CurrentCommandState.READY
}
fun isDuplicateOperatorKeyStroke(key: KeyStroke): Boolean {
val action = commandParts.peek()?.action as? DuplicableOperatorAction
val action = commandParts.peekLast()?.action as? DuplicableOperatorAction
return action?.duplicateWith == key.keyChar
}
@@ -102,27 +120,23 @@ class CommandBuilder(private var currentCommandPartNode: CommandPartNode) {
}
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 {
// If we have a command with a motion command argument, both could have their own counts. We need to adjust the
// counts, so the motion gets the product of both counts, and the count associated with the command gets reset.
// E.g. 3c2w (change 2 words, three times) becomes c6w (change 6 words)
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
var command: Command = commandParts.removeFirst()
while (commandParts.size > 0) {
val next = commandParts.removeFirst()
next.count = if (command.rawCount == 0 && next.rawCount == 0) 0 else command.count * next.count
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
}
@@ -130,11 +144,11 @@ class CommandBuilder(private var currentCommandPartNode: CommandPartNode) {
resetInProgressCommandPart(commandPartNode)
commandState = CurrentCommandState.NEW_COMMAND
commandParts.clear()
keyList.clear()
expectedArgumentType = null
}
fun resetInProgressCommandPart(commandPartNode: CommandPartNode) {
keys.clear()
count = 0
setCurrentCommandPartNode(commandPartNode)
}

View File

@@ -86,7 +86,7 @@ public class CommandState {
}
// Keep the compatibility with the IdeaVim-EasyMotion plugin before the stable release
@ApiStatus.ScheduledForRemoval(inVersion = "0.56")
@ApiStatus.ScheduledForRemoval(inVersion = "0.58")
@Deprecated
public MappingMode getMappingMode() {
return mappingState.getMappingMode();
@@ -164,6 +164,12 @@ public class CommandState {
}
}
public void resetRegisterPending() {
if (getSubMode() == SubMode.REGISTER_PENDING) {
popModes();
}
}
private void resetModes() {
modeStates.clear();
setMappingMode();
@@ -343,7 +349,7 @@ public class CommandState {
}
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 {

View File

@@ -60,4 +60,8 @@ enum class SelectionType(val value: Int) {
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

@@ -59,9 +59,7 @@ class IndentConfig private constructor(indentOptions: IndentOptions) {
val indentOptions = if (project != null) {
CodeStyle.getIndentOptions(project, editor.document)
} else {
// [VERSION UPDATE] 191+
@Suppress("UNNECESSARY_NOT_NULL_ASSERTION")
CodeStyle.getDefaultSettings().indentOptions!!
CodeStyle.getDefaultSettings().indentOptions
}
return IndentConfig(indentOptions)
}

View File

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

View File

@@ -67,34 +67,8 @@ public class CommandParser {
* Don't let anyone create one of these.
*/
private CommandParser() {
// IdeaVim doesn't support contribution to ex_command_ep extension point, so technically we can skip this update,
// but let's support dynamic plugins in a more classic way and reload handlers on every EP change.
// TODO: [VERSION UPDATE] since 191 use
// ExtensionPoint.addExtensionPointListener(ExtensionPointListener<T>, boolean, Disposable)
// TODO: [VERSION UPDATE] since 201 use
// ExtensionPoint.addExtensionPointListener(ExtensionPointChangeListener, boolean, Disposable)
//noinspection deprecation
EX_COMMAND_EP.getPoint(null).addExtensionPointListener(new ExtensionPointListener<ExBeanClass>() {
@Override
public void extensionAdded(@NotNull ExBeanClass extension, PluginDescriptor pluginDescriptor) {
// Suppress listener before the `VimPlugin.turnOn()` function execution. This logic should be rewritten after
// version update (or earlier).
if (!initialRegistration) return;
unregisterHandlers();
registerHandlers();
}
@Override
public void extensionRemoved(@NotNull ExBeanClass extension, PluginDescriptor pluginDescriptor) {
if (!initialRegistration) return;
unregisterHandlers();
registerHandlers();
}
});
}
private static boolean initialRegistration = false;
public void unregisterHandlers() {
root.clear();
}
@@ -104,7 +78,16 @@ public class CommandParser {
*/
public void registerHandlers() {
EX_COMMAND_EP.extensions().forEach(ExBeanClass::register);
initialRegistration = true;
registerEpListener();
}
private void registerEpListener() {
// IdeaVim doesn't support contribution to ex_command_ep extension point, so technically we can skip this update,
// but let's support dynamic plugins in a more classic way and reload handlers on every EP change.
EX_COMMAND_EP.getPoint(null).addExtensionPointListener(() -> {
unregisterHandlers();
registerHandlers();
}, false, VimPlugin.getInstance());
}
/**

View File

@@ -32,9 +32,7 @@ class ExBeanClass : AbstractExtensionPointBean() {
var names: String? = null
val handler: CommandHandler by lazy {
// FIXME. [VERSION UPDATE] change to instantiateClass for 193+
@Suppress("DEPRECATION")
this.instantiate<CommandHandler>(
this.instantiateClass<CommandHandler>(
implementation ?: "", ApplicationManager.getApplication().picoContainer)
}

View File

@@ -17,7 +17,6 @@
*/
package com.maddyhome.idea.vim.ex
import com.intellij.openapi.actionSystem.DataContext
import com.intellij.openapi.editor.Caret
import com.intellij.openapi.editor.Editor
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, 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 res = ranges.getCount(editor, count)
return if (res == -1) defaultCount else res
}
fun getCount(editor: Editor, caret: Caret, context: DataContext, defaultCount: Int, checkCount: Boolean): Int {
val count = ranges.getCount(editor, caret, context, if (checkCount) countArgument else -1)
fun getCount(editor: Editor, caret: Caret, defaultCount: Int, checkCount: Boolean): Int {
val count = ranges.getCount(editor, caret, if (checkCount) countArgument else -1)
return if (count == -1) defaultCount else count
}
fun getLineRange(editor: Editor): LineRange = ranges.getLineRange(editor, -1)
fun getLineRange(editor: Editor, caret: Caret, context: DataContext): LineRange {
return ranges.getLineRange(editor, caret, context, -1)
fun getLineRange(editor: Editor, caret: Caret): LineRange {
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
return ranges.getTextRange(editor, context, count)
return ranges.getTextRange(editor, count)
}
fun getTextRange(editor: Editor, caret: Caret, context: DataContext, checkCount: Boolean): TextRange {
return ranges.getTextRange(editor, caret, context, if (checkCount) countArgument else -1)
fun getTextRange(editor: Editor, caret: Caret, checkCount: Boolean): TextRange {
return ranges.getTextRange(editor, caret, if (checkCount) countArgument else -1)
}
private val countArgument: Int

View File

@@ -0,0 +1,95 @@
/*
* 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.Editor
import com.intellij.openapi.fileEditor.FileEditorManager
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.flags
import com.maddyhome.idea.vim.helper.EditorHelper
/**
* Handles buffer, buf, bu, b.
*
* @author John Weigel
*/
class BufferHandler : CommandHandler.SingleExecution() {
override val argFlags = flags(RangeFlag.RANGE_FORBIDDEN, ArgumentFlag.ARGUMENT_OPTIONAL, Access.READ_ONLY)
override fun execute(editor: Editor, context: DataContext, cmd: ExCommand): Boolean {
val arg = cmd.argument.trim()
val overrideModified = arg.startsWith('!')
val buffer = if (overrideModified) arg.replace(Regex("^!\\s*"), "") else arg
var result = true
if (buffer.isNotEmpty()) {
if (buffer.matches(Regex("^\\d+$"))) {
val bufNum = buffer.toInt() - 1
if (!VimPlugin.getFile().selectFile(bufNum, context)) {
VimPlugin.showMessage("Buffer $bufNum does not exist")
result = false
}
} else {
val editors = findPartialMatch(context, buffer)
when(editors.size) {
0 -> {
VimPlugin.showMessage("No matching buffer for $buffer")
result = false
}
1 -> {
if (EditorHelper.hasUnsavedChanges(editor) && !overrideModified) {
VimPlugin.showMessage("No write since last change (add ! to override)")
result = false
}
else {
VimPlugin.getFile().openFile(EditorHelper.getVirtualFile(editors[0])!!.name, context)
}
}
else -> {
VimPlugin.showMessage("More than one match for $buffer")
result = false
}
}
}
}
return result
}
private fun findPartialMatch(context: DataContext, fileName: String): List<Editor> {
val matchedFiles = mutableListOf<Editor>()
val project = PlatformDataKeys.PROJECT.getData(context) ?: return matchedFiles
for (file in FileEditorManager.getInstance(project).openFiles) {
if (file.name.contains(fileName)) {
val editor = EditorHelper.getEditor(file) ?: continue
matchedFiles.add(editor)
}
}
return matchedFiles
}
}

View File

@@ -22,7 +22,11 @@ import com.intellij.openapi.actionSystem.DataContext
import com.intellij.openapi.diagnostic.Logger
import com.intellij.openapi.editor.Editor
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.Msg
import java.io.IOException
@@ -54,7 +58,7 @@ class CmdFilterHandler : CommandHandler.SingleExecution() {
true
} else {
// Filter
val range = cmd.getTextRange(editor, context, false)
val range = cmd.getTextRange(editor, false)
VimPlugin.getProcess().executeFilter(editor, range, command)
}
} catch (e: IOException) {

View File

@@ -22,7 +22,10 @@ import com.intellij.openapi.actionSystem.DataContext
import com.intellij.openapi.editor.Editor
import com.maddyhome.idea.vim.VimPlugin
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.helper.EditorHelper
@@ -31,11 +34,11 @@ class CopyTextHandler : CommandHandler.SingleExecution() {
override fun execute(editor: Editor, context: DataContext, cmd: ExCommand): Boolean {
val carets = EditorHelper.getOrderedCaretsList(editor)
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 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 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
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)
}
}

View File

@@ -29,7 +29,7 @@ import com.maddyhome.idea.vim.ex.flags
class FileHandler : CommandHandler.SingleExecution() {
override val argFlags = flags(RangeFlag.RANGE_IS_COUNT, ARGUMENT_FORBIDDEN, Access.READ_ONLY)
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)
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 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
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
*/
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) {
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.maddyhome.idea.vim.VimPlugin
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() {
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 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,
textRange.endOffset - 1), spaces)

View File

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

View File

@@ -28,7 +28,7 @@ import com.maddyhome.idea.vim.ex.flags
class PreviousFileHandler : CommandHandler.SingleExecution() {
override val argFlags = flags(RangeFlag.RANGE_IS_COUNT, ArgumentFlag.ARGUMENT_OPTIONAL, Access.READ_ONLY)
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.getFile().selectNextFile(-count, context)

View File

@@ -37,7 +37,7 @@ class RepeatHandler : CommandHandler.ForEachCaret() {
if (arg == '@') arg = lastArg
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))
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 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) {
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 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()
VimPlugin.getChange().indentRange(editor, caret, context,
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 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()
VimPlugin.getChange().indentRange(editor, caret, context,
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.util.text.StringUtil
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.helper.inBlockSubMode
import java.util.*
@@ -46,7 +49,7 @@ class SortHandler : CommandHandler.SingleExecution() {
val lineComparator = LineComparator(ignoreCase, number, reverse)
if (editor.inBlockSubMode) {
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)
primaryCaret.moveToOffset(VimPlugin.getMotion().moveCaretToLineStartSkipLeading(editor, range.startLine))
return worked
@@ -54,7 +57,7 @@ class SortHandler : CommandHandler.SingleExecution() {
var worked = true
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)) {
worked = false
}
@@ -64,8 +67,8 @@ class SortHandler : CommandHandler.SingleExecution() {
return worked
}
private fun getLineRange(editor: Editor, caret: Caret, context: DataContext, cmd: ExCommand): LineRange {
val range = cmd.getLineRange(editor, caret, context)
private fun getLineRange(editor: Editor, caret: Caret, cmd: ExCommand): LineRange {
val range = cmd.getLineRange(editor, caret)
// Something like "30,20sort" gets converted to "20,30sort"
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 {
var result = true
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)) {
result = false
}

View File

@@ -28,7 +28,7 @@ import com.maddyhome.idea.vim.ex.flags
class WriteNextFileHandler : CommandHandler.SingleExecution() {
override val argFlags = flags(RangeFlag.RANGE_IS_COUNT, ArgumentFlag.ARGUMENT_OPTIONAL, Access.READ_ONLY)
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.getMark().saveJumpLocation(editor)

View File

@@ -28,7 +28,7 @@ import com.maddyhome.idea.vim.ex.flags
class WritePreviousFileHandler : CommandHandler.SingleExecution() {
override val argFlags = flags(RangeFlag.RANGE_OPTIONAL, ArgumentFlag.ARGUMENT_OPTIONAL, Access.READ_ONLY)
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.getMark().saveJumpLocation(editor)

View File

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

View File

@@ -17,7 +17,6 @@
*/
package com.maddyhome.idea.vim.ex.ranges
import com.intellij.openapi.actionSystem.DataContext
import com.intellij.openapi.editor.Caret
import com.intellij.openapi.editor.Editor
import com.maddyhome.idea.vim.VimPlugin
@@ -59,13 +58,13 @@ class Ranges {
return endLine
}
fun getLine(editor: Editor, caret: Caret, context: DataContext): Int {
processRange(editor, caret, context)
fun getLine(editor: Editor, caret: Caret): Int {
processRange(editor, caret)
return endLine
}
fun getFirstLine(editor: Editor, caret: Caret, context: DataContext): Int {
processRange(editor, caret, context)
fun getFirstLine(editor: Editor, caret: Caret): Int {
processRange(editor, caret)
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, caret: Caret, context: DataContext, count: Int): Int {
return if (count == -1) getLine(editor, caret, context) else count
fun getCount(editor: Editor, caret: Caret, count: Int): Int {
return if (count == -1) getLine(editor, caret) else count
}
/**
@@ -105,8 +104,8 @@ class Ranges {
return LineRange(start, end)
}
fun getLineRange(editor: Editor, caret: Caret, context: DataContext, count: Int): LineRange {
processRange(editor, caret, context)
fun getLineRange(editor: Editor, caret: Caret, count: Int): LineRange {
processRange(editor, caret)
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
* @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 start = EditorHelper.getLineStartOffset(editor, lr.startLine)
val end = EditorHelper.getLineEndOffset(editor, lr.endLine, true) + 1
return TextRange(start, min(end, EditorHelper.getFileSize(editor)))
}
fun getTextRange(editor: Editor, caret: Caret, context: DataContext, count: Int): TextRange {
val lineRange = getLineRange(editor, caret, context, count)
fun getTextRange(editor: Editor, caret: Caret, count: Int): TextRange {
val lineRange = getLineRange(editor, caret, count)
val start = EditorHelper.getLineStartOffset(editor, lineRange.startLine)
val end = EditorHelper.getLineEndOffset(editor, lineRange.endLine, true) + 1
return TextRange(start, min(end, EditorHelper.getFileSize(editor)))
@@ -165,7 +164,7 @@ class Ranges {
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
endLine = startLine
var lastZero = false

View File

@@ -1,188 +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.ex.vimscript;
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 org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import java.io.BufferedReader;
import java.io.File;
import java.io.FileReader;
import java.io.IOException;
import java.nio.file.Paths;
import java.util.Map;
import java.util.Objects;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
/**
* @author vlan
*/
public class VimScriptParser {
public static final String VIMRC_FILE_NAME = "ideavimrc";
public static final String[] HOME_VIMRC_PATHS = {"." + VIMRC_FILE_NAME, "_" + VIMRC_FILE_NAME};
public static final String XDG_VIMRC_PATH = "ideavim" + File.separator + VIMRC_FILE_NAME;
public static final int BUFSIZE = 4096;
private static final Pattern EOL_SPLIT_PATTERN = Pattern.compile(" *(\r\n|\n)+ *");
private static final Pattern DOUBLE_QUOTED_STRING = Pattern.compile("\"([^\"]*)\"");
private static final Pattern SINGLE_QUOTED_STRING = Pattern.compile("'([^']*)'");
private static final Pattern REFERENCE_EXPR = Pattern.compile("([A-Za-z_][A-Za-z_0-9]*)");
private static final Pattern DEC_NUMBER = Pattern.compile("(\\d+)");
private VimScriptParser() {
}
public static @Nullable File findIdeaVimRc() {
final String homeDirName = System.getProperty("user.home");
// Check whether file exists in home dir
if (homeDirName != null) {
for (String fileName : HOME_VIMRC_PATHS) {
final File file = new File(homeDirName, fileName);
if (file.exists()) {
return file;
}
}
}
// Check in XDG config directory
final String xdgConfigHomeProperty = System.getenv("XDG_CONFIG_HOME");
File xdgConfig = null;
if (xdgConfigHomeProperty == null || Objects.equals(xdgConfigHomeProperty, "")) {
if (homeDirName != null) {
xdgConfig = Paths.get(homeDirName, ".config", XDG_VIMRC_PATH).toFile();
}
} else {
xdgConfig = new File(xdgConfigHomeProperty, XDG_VIMRC_PATH);
}
if (xdgConfig != null && xdgConfig.exists()) {
return xdgConfig;
}
return null;
}
public static @Nullable File findOrCreateIdeaVimRc() {
final File found = findIdeaVimRc();
if (found != null) return found;
final String homeDirName = System.getProperty("user.home");
if (homeDirName != null) {
for (String fileName : HOME_VIMRC_PATHS) {
try {
final File file = new File(homeDirName, fileName);
//noinspection ResultOfMethodCallIgnored
file.createNewFile();
return file;
} catch (IOException ignored) {
// Try to create one of two files
}
}
}
return null;
}
public static void executeFile(@NotNull File file) {
final String data;
try {
data = readFile(file);
}
catch (IOException ignored) {
return;
}
executeText(data);
}
public static void executeText(@NotNull String text) {
for (String line : EOL_SPLIT_PATTERN.split(text)) {
// TODO: Build a proper parse tree for a VimL file instead of ignoring potentially nested lines (VIM-669)
if (line.startsWith(" ") || line.startsWith("\t")) {
continue;
}
if (line.startsWith(":")) {
line = line.substring(1);
}
final CommandParser commandParser = CommandParser.getInstance();
try {
final ExCommand command = commandParser.parse(line);
final CommandHandler commandHandler = commandParser.getCommandHandler(command);
if (commandHandler instanceof VimScriptCommandHandler) {
final VimScriptCommandHandler handler = (VimScriptCommandHandler)commandHandler;
handler.execute(command);
}
}
catch (ExException ignored) {
}
}
}
public static @NotNull Object evaluate(@NotNull String expression, @NotNull Map<String, Object> globals) throws ExException {
// This evaluator is very basic, no proper parsing whatsoever. It is here as the very first step necessary to
// support mapleader, VIM-650. See also VIM-669.
Matcher m;
m = DOUBLE_QUOTED_STRING.matcher(expression);
if (m.matches()) {
return m.group(1);
}
m = SINGLE_QUOTED_STRING.matcher(expression);
if (m.matches()) {
return m.group(1);
}
m = REFERENCE_EXPR.matcher(expression);
if (m.matches()) {
final String name = m.group(1);
final Object value = globals.get(name);
if (value != null) {
return value;
}
else {
throw new ExException(String.format("Undefined variable: %s", name));
}
}
m = DEC_NUMBER.matcher(expression);
if (m.matches()) {
return Integer.parseInt(m.group(1));
}
throw new ExException(String.format("Invalid expression: %s", expression));
}
public static @NotNull String expressionToString(@NotNull Object value) throws ExException {
// TODO: Return meaningful value representations
if (value instanceof String) {
return (String)value;
} else if (value instanceof Integer) {
return value.toString();
}
throw new ExException(String.format("Cannot convert '%s' to string", value));
}
private static @NotNull String readFile(@NotNull File file) throws IOException {
final BufferedReader reader = new BufferedReader(new FileReader(file));
final StringBuilder builder = new StringBuilder();
final char[] buffer = new char[BUFSIZE];
int n;
while ((n = reader.read(buffer)) > 0) {
builder.append(buffer, 0, n);
}
return builder.toString();
}
}

View File

@@ -0,0 +1,167 @@
/*
* 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.vimscript
import com.maddyhome.idea.vim.ex.CommandParser
import com.maddyhome.idea.vim.ex.ExException
import com.maddyhome.idea.vim.ui.VimRcFileState
import java.io.File
import java.io.IOException
import java.nio.file.Paths
import java.util.regex.Matcher
import java.util.regex.Pattern
/**
* @author vlan
*/
object VimScriptParser {
const val VIMRC_FILE_NAME = "ideavimrc"
val HOME_VIMRC_PATHS = arrayOf(".$VIMRC_FILE_NAME", "_$VIMRC_FILE_NAME")
val XDG_VIMRC_PATH = "ideavim" + File.separator + VIMRC_FILE_NAME
private val DOUBLE_QUOTED_STRING = Pattern.compile("\"([^\"]*)\"")
private val SINGLE_QUOTED_STRING = Pattern.compile("'([^']*)'")
private val REFERENCE_EXPR = Pattern.compile("([A-Za-z_][A-Za-z_0-9]*)")
private val DEC_NUMBER = Pattern.compile("(\\d+)")
// This is a pattern used in ideavimrc parsing for a long time. It removes all trailing/leading spaced and blank lines
private val EOL_SPLIT_PATTERN = Pattern.compile(" *(\r\n|\n)+ *")
@JvmStatic
fun findIdeaVimRc(): File? {
val homeDirName = System.getProperty("user.home")
// Check whether file exists in home dir
if (homeDirName != null) {
for (fileName in HOME_VIMRC_PATHS) {
val file = File(homeDirName, fileName)
if (file.exists()) {
return file
}
}
}
// Check in XDG config directory
val xdgConfigHomeProperty = System.getenv("XDG_CONFIG_HOME")
val xdgConfig = if (xdgConfigHomeProperty == null || xdgConfigHomeProperty == "") {
if (homeDirName != null) Paths.get(homeDirName, ".config", XDG_VIMRC_PATH).toFile() else null
} else {
File(xdgConfigHomeProperty, XDG_VIMRC_PATH)
}
return if (xdgConfig != null && xdgConfig.exists()) xdgConfig else null
}
fun findOrCreateIdeaVimRc(): File? {
val found = findIdeaVimRc()
if (found != null) return found
val homeDirName = System.getProperty("user.home")
if (homeDirName != null) {
for (fileName in HOME_VIMRC_PATHS) {
try {
val file = File(homeDirName, fileName)
file.createNewFile()
VimRcFileState.filePath = file.absolutePath
return file
} catch (ignored: IOException) {
// Try to create one of two files
}
}
}
return null
}
@JvmStatic
fun executeFile(file: File): List<String> {
val data = try {
readFile(file)
} catch (ignored: IOException) {
return emptyList()
}
executeText(data)
return data
}
fun executeText(text: List<String>) {
for (line in text) {
// TODO: Build a proper parse tree for a VimL file instead of ignoring potentially nested lines (VIM-669)
if (line.startsWith(" ") || line.startsWith("\t")) continue
val lineToExecute = if (line.startsWith(":")) line.substring(1) else line
val commandParser = CommandParser.getInstance()
try {
val command = commandParser.parse(lineToExecute)
val commandHandler = commandParser.getCommandHandler(command)
if (commandHandler is VimScriptCommandHandler) {
commandHandler.execute(command)
}
} catch (ignored: ExException) {
}
}
}
@Throws(ExException::class)
fun evaluate(expression: String, globals: Map<String?, Any?>): Any {
// 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.
var m: Matcher = DOUBLE_QUOTED_STRING.matcher(expression)
if (m.matches()) return m.group(1)
m = SINGLE_QUOTED_STRING.matcher(expression)
if (m.matches()) return m.group(1)
m = REFERENCE_EXPR.matcher(expression)
if (m.matches()) {
val name = m.group(1)
val value = globals[name]
return value ?: throw ExException("Undefined variable: $name")
}
m = DEC_NUMBER.matcher(expression)
if (m.matches()) return m.group(1).toInt()
throw ExException("Invalid expression: $expression")
}
@Throws(ExException::class)
fun expressionToString(value: Any): String {
// TODO: Return meaningful value representations
return when (value) {
is String -> value
is Int -> value.toString()
else -> throw ExException("Cannot convert '$value' to string")
}
}
@Throws(IOException::class)
fun readFile(file: File): List<String> {
val lines = ArrayList<String>()
file.forEachLine { line -> lineProcessor(line, lines) }
return lines
}
fun readText(data: CharSequence): List<String> {
val lines = ArrayList<String>()
EOL_SPLIT_PATTERN.split(data).forEach { line -> lineProcessor(line, lines) }
return lines
}
fun lineProcessor(line: String, lines: ArrayList<String>) {
val trimmedLine = line.trim()
if (trimmedLine.isBlank()) return
lines += trimmedLine
}
}

View File

@@ -23,6 +23,7 @@ import com.maddyhome.idea.vim.KeyHandler
import com.maddyhome.idea.vim.VimPlugin
import com.maddyhome.idea.vim.action.change.VimRepeater
import com.maddyhome.idea.vim.command.MappingMode
import com.maddyhome.idea.vim.command.SelectionType
import com.maddyhome.idea.vim.helper.EditorDataContext
import com.maddyhome.idea.vim.helper.StringHelper
import com.maddyhome.idea.vim.helper.TestInputModel
@@ -45,13 +46,13 @@ import javax.swing.KeyStroke
object VimExtensionFacade {
/** The 'map' command for mapping keys to handlers defined in extensions. */
@JvmStatic
@ScheduledForRemoval(inVersion = "0.57")
@ScheduledForRemoval(inVersion = "0.58")
@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")
@ScheduledForRemoval(inVersion = "0.58")
@Deprecated("Only for EasyMotion support")
@JvmStatic
fun putKeyMapping(modes: Set<MappingMode>, fromKeys: List<KeyStroke>,
@@ -186,4 +187,10 @@ object VimExtensionFacade {
fun setRegister(register: Char, keys: List<KeyStroke?>?) {
VimPlugin.getRegister().setKeys(register, keys ?: emptyList())
}
/** Set the current contents of the given register */
@JvmStatic
fun setRegister(register: Char, keys: List<KeyStroke?>?, type: SelectionType) {
VimPlugin.getRegister().setKeys(register, keys ?: emptyList(), type)
}
}

View File

@@ -1,97 +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.extension;
import com.intellij.openapi.diagnostic.Logger;
import com.intellij.openapi.extensions.ExtensionPointListener;
import com.intellij.openapi.extensions.PluginDescriptor;
import com.maddyhome.idea.vim.key.MappingOwner;
import com.maddyhome.idea.vim.option.OptionsManager;
import com.maddyhome.idea.vim.option.ToggleOption;
import org.jetbrains.annotations.NotNull;
import java.util.HashSet;
import java.util.Set;
/**
* TODO [VERSION UPDATE] this file cannot be converted to kt before 192 because of nullabilities problems in
* [ExtensionPointListener]. (In previous versions of IJ pluginDescriptor was nullable)
*/
public class VimExtensionRegistrar {
private static final Set<String> registeredExtensions = new HashSet<>();
private static boolean extensionRegistered = false;
private static final Logger logger = Logger.getInstance(VimExtensionRegistrar.class);
public static void registerExtensions() {
if (extensionRegistered) return;
extensionRegistered = true;
// TODO: [VERSION UPDATE] since 191 use
// ExtensionPoint.addExtensionPointListener(ExtensionPointListener<T>, boolean, Disposable)
//noinspection deprecation
VimExtension.EP_NAME.getPoint(null).addExtensionPointListener(new ExtensionPointListener<VimExtension>() {
@Override
public void extensionAdded(@NotNull VimExtension extension, PluginDescriptor pluginDescriptor) {
registerExtension(extension);
}
@Override
public void extensionRemoved(@NotNull VimExtension extension, PluginDescriptor pluginDescriptor) {
unregisterExtension(extension);
}
});
}
private static synchronized void registerExtension(@NotNull VimExtension extension) {
String name = extension.getName();
if (registeredExtensions.contains(name)) return;
registeredExtensions.add(name);
ToggleOption option = new ToggleOption(name, name, false);
option.addOptionChangeListener((oldValue, newValue) -> {
for (VimExtension extensionInListener : VimExtension.EP_NAME.getExtensionList()) {
if (name.equals(extensionInListener.getName())) {
if (OptionsManager.INSTANCE.isSet(name)) {
extensionInListener.init();
logger.info("IdeaVim extension '" + name + "' initialized");
}
else {
extensionInListener.dispose();
}
}
}
});
OptionsManager.INSTANCE.addOption(option);
}
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

@@ -0,0 +1,82 @@
/*
* 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
import com.intellij.openapi.diagnostic.logger
import com.intellij.openapi.extensions.ExtensionPointListener
import com.intellij.openapi.extensions.PluginDescriptor
import com.maddyhome.idea.vim.VimPlugin
import com.maddyhome.idea.vim.key.MappingOwner.Plugin.Companion.remove
import com.maddyhome.idea.vim.option.OptionsManager.addOption
import com.maddyhome.idea.vim.option.OptionsManager.isSet
import com.maddyhome.idea.vim.option.OptionsManager.removeOption
import com.maddyhome.idea.vim.option.ToggleOption
import java.util.*
object VimExtensionRegistrar {
private val registeredExtensions: MutableSet<String> = HashSet()
private var extensionRegistered = false
private val logger = logger<VimExtensionRegistrar>()
@JvmStatic
fun registerExtensions() {
if (extensionRegistered) return
extensionRegistered = true
VimExtension.EP_NAME.getPoint(null).addExtensionPointListener(object : ExtensionPointListener<VimExtension> {
override fun extensionAdded(extension: VimExtension, pluginDescriptor: PluginDescriptor) {
registerExtension(extension)
}
override fun extensionRemoved(extension: VimExtension, pluginDescriptor: PluginDescriptor) {
unregisterExtension(extension)
}
}, true, VimPlugin.getInstance())
}
@Synchronized
private fun registerExtension(extension: VimExtension) {
val name = extension.name
if (name in registeredExtensions) return
registeredExtensions.add(name)
val option = ToggleOption(name, name, false)
option.addOptionChangeListener { _, _ ->
for (extensionInListener in VimExtension.EP_NAME.extensionList) {
if (name != extensionInListener.name) continue
if (isSet(name)) {
extensionInListener.init()
logger.info("IdeaVim extension '$name' initialized")
} else {
extensionInListener.dispose()
}
}
}
addOption(option)
}
@Synchronized
private fun unregisterExtension(extension: VimExtension) {
val name = extension.name
if (name !in registeredExtensions) return
registeredExtensions.remove(name)
extension.dispose()
removeOption(name)
remove(name)
logger.info("IdeaVim extension '$name' disposed")
}
}

View File

@@ -24,7 +24,7 @@ import org.jetbrains.annotations.ApiStatus;
* @author vlan
*/
@ApiStatus.ScheduledForRemoval(inVersion = "0.57")
@ApiStatus.ScheduledForRemoval(inVersion = "0.58")
@Deprecated
public abstract class VimNonDisposableExtension implements VimExtension {
@Override

View File

@@ -7,6 +7,7 @@ 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;
@@ -46,6 +47,117 @@ public class VimArgTextObjExtension implements VimExtension {
}
/**
* 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.
*/
@@ -66,7 +178,18 @@ public class VimArgTextObjExtension implements VimExtension {
@Override
public @Nullable TextRange getRange(@NotNull Editor editor, @NotNull Caret caret, @NotNull DataContext context, int count, int rawCount, @Nullable Argument argument) {
final ArgBoundsFinder finder = new ArgBoundsFinder(editor.getDocument());
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) {
@@ -114,9 +237,7 @@ public class VimArgTextObjExtension implements VimExtension {
});
} else {
commandState.getCommandBuilder().completeCommandPart(new Argument(new Command(count,
textObjectHandler, Command.Type.MOTION, EnumSet.of(CommandFlags.FLAG_MOT_CHARACTERWISE),
emptyList()
)));
textObjectHandler, Command.Type.MOTION, EnumSet.of(CommandFlags.FLAG_MOT_CHARACTERWISE))));
}
}
}
@@ -126,8 +247,9 @@ public class VimArgTextObjExtension implements VimExtension {
* position
*/
private static class ArgBoundsFinder {
private final CharSequence text;
private final Document document;
@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;
@@ -135,18 +257,13 @@ public class VimArgTextObjExtension implements VimExtension {
private String error = null;
private static final String QUOTES = "\"'";
// NOTE: brackets must match by the position, and ordered by rank.
// NOTE2: The original implementation worked for ], } and > as well, but because of some broken cases this feature
// was removed.
private static final String OPEN_BRACKETS = "("; // "[{(<"
private static final String CLOSE_BRACKETS = ")"; // "]})>"
private static final int MAX_SEARCH_LINES = 10;
private static final int MAX_SEARCH_OFFSET = MAX_SEARCH_LINES * 80;
ArgBoundsFinder(@NotNull Document document) {
ArgBoundsFinder(@NotNull Document document, @NotNull BracketPairs bracketPairs) {
this.text = document.getImmutableCharSequence();
this.document = document;
this.brackets = bracketPairs;
}
/**
@@ -167,7 +284,7 @@ public class VimArgTextObjExtension implements VimExtension {
rightBound = Math.max(position, rightBound);
getOutOfQuotedText();
if (rightBound == leftBound) {
if (isCloseBracket(getCharAt(rightBound))) {
if (brackets.isCloseBracket(getCharAt(rightBound))) {
--leftBound;
} else {
++rightBound;
@@ -196,14 +313,15 @@ public class VimArgTextObjExtension implements VimExtension {
findRightBound();
nextRight = rightBound + 1;
//
// If reached text boundaries or there is nothing between delimiters.
// If reached text boundaries
//
if (nextLeft < leftLimit || nextRight > rightLimit || (rightBound - leftBound) == 1) {
if (nextLeft < leftLimit || nextRight > rightLimit) {
error = "not an argument";
return false;
}
bothBrackets = getCharAt(leftBound) != ',' && getCharAt(rightBound) != ',';
if (bothBrackets && isIdentPreceding()) {
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;
@@ -257,7 +375,7 @@ public class VimArgTextObjExtension implements VimExtension {
private boolean isIdentPreceding() {
int i = leftBound - 1;
final int idEnd = i;
while (i > 0 && Character.isJavaIdentifierPart(getCharAt(i))) {
while (i >= 0 && Character.isJavaIdentifierPart(getCharAt(i))) {
--i;
}
return (idEnd - i) > 0 && Character.isJavaIdentifierStart(getCharAt(i + 1));
@@ -296,8 +414,8 @@ public class VimArgTextObjExtension implements VimExtension {
if (ch == ',') {
break;
}
if (isOpenBracket(ch)) {
rightBound = skipSexp(rightBound, rightBracket, SexpDirection.FORWARD);
if (brackets.isOpenBracket(ch)) {
rightBound = skipSexp(rightBound, rightBracket, SexpDirection.forward(brackets));
} else {
if (isQuoteChar(ch)) {
rightBound = skipQuotedTextForward(rightBound, rightBracket);
@@ -307,33 +425,14 @@ public class VimArgTextObjExtension implements VimExtension {
}
}
private static char matchingBracket(char ch) {
int idx = CLOSE_BRACKETS.indexOf(ch);
if (idx != -1) {
return OPEN_BRACKETS.charAt(idx);
} else {
assert isOpenBracket(ch);
idx = OPEN_BRACKETS.indexOf(ch);
return CLOSE_BRACKETS.charAt(idx);
}
}
private static boolean isCloseBracket(final int ch) {
return CLOSE_BRACKETS.indexOf(ch) != -1;
}
private static boolean isOpenBracket(final int ch) {
return OPEN_BRACKETS.indexOf(ch) != -1;
}
private void findLeftBound() {
while (leftBound > leftBracket) {
final char ch = getCharAt(leftBound);
if (ch == ',') {
break;
}
if (isCloseBracket(ch)) {
leftBound = skipSexp(leftBound, leftBracket, SexpDirection.BACKWARD);
if (brackets.isCloseBracket(ch)) {
leftBound = skipSexp(leftBound, leftBracket, SexpDirection.backward(brackets));
} else {
if (isQuoteChar(ch)) {
leftBound = skipQuotedTextBackward(leftBound, leftBracket);
@@ -365,7 +464,7 @@ public class VimArgTextObjExtension implements VimExtension {
while (i <= end) {
final char ch = getCharAt(i);
if (ch == quoteChar && !backSlash) {
// Found matching quote and it's not escaped.
// Found a matching quote, and it's not escaped.
break;
} else {
backSlash = ch == '\\' && !backSlash;
@@ -386,7 +485,7 @@ public class VimArgTextObjExtension implements VimExtension {
// NOTE: doesn't handle cases like \\"str", but they make no
// sense anyway.
if (ch == quoteChar && prevChar != '\\') {
// Found matching quote and it's not escaped.
// Found a matching quote, and it's not escaped.
break;
}
--i;
@@ -424,48 +523,53 @@ public class VimArgTextObjExtension implements VimExtension {
abstract int skipQuotedText(int pos, int end, ArgBoundsFinder self);
static final SexpDirection FORWARD = new SexpDirection() {
@Override
int delta() {
return 1;
}
static SexpDirection forward(BracketPairs brackets) {
return new SexpDirection() {
@Override
int delta() {
return 1;
}
@Override
boolean isOpenBracket(char ch) {
return ArgBoundsFinder.isOpenBracket(ch);
}
@Override
boolean isOpenBracket(char ch) {
return brackets.isOpenBracket(ch);
}
@Override
boolean isCloseBracket(char ch) {
return ArgBoundsFinder.isCloseBracket(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 final SexpDirection BACKWARD = new SexpDirection() {
@Override
int delta() {
return -1;
}
@Override
int skipQuotedText(int pos, int end, ArgBoundsFinder self) {
return self.skipQuotedTextForward(pos, end);
}
};
}
@Override
boolean isOpenBracket(char ch) {
return ArgBoundsFinder.isCloseBracket(ch);
}
static SexpDirection backward(BracketPairs brackets) {
return new SexpDirection() {
@Override
int delta() {
return -1;
}
@Override
boolean isCloseBracket(char ch) {
return ArgBoundsFinder.isOpenBracket(ch);
}
@Override
boolean isOpenBracket(char ch) {
return brackets.isCloseBracket(ch);
}
@Override
int skipQuotedText(int pos, int end, ArgBoundsFinder self) {
return self.skipQuotedTextBackward(pos, end);
}
};
@Override
boolean isCloseBracket(char ch) {
return brackets.isOpenBracket(ch);
}
@Override
int skipQuotedText(int pos, int end, ArgBoundsFinder self) {
return self.skipQuotedTextBackward(pos, end);
}
};
}
}
/**
@@ -489,11 +593,11 @@ public class VimArgTextObjExtension implements VimExtension {
bracketStack.push(ch);
} else {
if (dir.isCloseBracket(ch)) {
if (bracketStack.lastElement() == matchingBracket(ch)) {
if (bracketStack.lastElement() == brackets.matchingBracket(ch)) {
bracketStack.pop();
} else {
//noinspection StatementWithEmptyBody
if (getBracketPrio(ch) < getBracketPrio(bracketStack.lastElement())) {
if (brackets.getBracketPrio(ch) < brackets.getBracketPrio(bracketStack.lastElement())) {
// (<...) -> (...)
bracketStack.pop();
// Retry the same character again for cases like (...<<...).
@@ -518,13 +622,6 @@ public class VimArgTextObjExtension implements VimExtension {
}
}
/**
* @return rank of a bracket.
*/
static int getBracketPrio(char ch) {
return Math.max(OPEN_BRACKETS.indexOf(ch), CLOSE_BRACKETS.indexOf(ch));
}
/**
* Find a pair of brackets surrounding (leftBracket..rightBracket) block.
*
@@ -535,8 +632,8 @@ public class VimArgTextObjExtension implements VimExtension {
boolean findOuterBrackets(final int start, final int end) {
boolean hasNewBracket = findPrevOpenBracket(start) && findNextCloseBracket(end);
while (hasNewBracket) {
final int leftPrio = getBracketPrio(getCharAt(leftBracket));
final int rightPrio = getBracketPrio(getCharAt(rightBracket));
final int leftPrio = brackets.getBracketPrio(getCharAt(leftBracket));
final int rightPrio = brackets.getBracketPrio(getCharAt(rightBracket));
if (leftPrio == rightPrio) {
// matching brackets
return true;
@@ -569,9 +666,9 @@ public class VimArgTextObjExtension implements VimExtension {
*/
private boolean findPrevOpenBracket(final int start) {
char ch;
while (!isOpenBracket(ch = getCharAt(leftBracket))) {
if (isCloseBracket(ch)) {
leftBracket = skipSexp(leftBracket, start, SexpDirection.BACKWARD);
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);
@@ -594,9 +691,9 @@ public class VimArgTextObjExtension implements VimExtension {
*/
private boolean findNextCloseBracket(final int end) {
char ch;
while (!isCloseBracket(ch = getCharAt(rightBracket))) {
if (isOpenBracket(ch)) {
rightBracket = skipSexp(rightBracket, end, SexpDirection.FORWARD);
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);

View File

@@ -0,0 +1,349 @@
/*
* 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.exchange
import com.intellij.openapi.actionSystem.DataContext
import com.intellij.openapi.application.runWriteAction
import com.intellij.openapi.editor.Editor
import com.intellij.openapi.editor.LogicalPosition
import com.intellij.openapi.editor.colors.EditorColors
import com.intellij.openapi.editor.markup.HighlighterLayer
import com.intellij.openapi.editor.markup.HighlighterTargetArea
import com.intellij.openapi.editor.markup.RangeHighlighter
import com.intellij.openapi.util.Key
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.common.Mark
import com.maddyhome.idea.vim.common.TextRange
import com.maddyhome.idea.vim.common.VimMark
import com.maddyhome.idea.vim.extension.VimExtension
import com.maddyhome.idea.vim.extension.VimExtensionFacade.executeNormal
import com.maddyhome.idea.vim.extension.VimExtensionFacade.getRegister
import com.maddyhome.idea.vim.extension.VimExtensionFacade.putExtensionHandlerMapping
import com.maddyhome.idea.vim.extension.VimExtensionFacade.putKeyMapping
import com.maddyhome.idea.vim.extension.VimExtensionFacade.setOperatorFunction
import com.maddyhome.idea.vim.extension.VimExtensionFacade.setRegister
import com.maddyhome.idea.vim.extension.VimExtensionHandler
import com.maddyhome.idea.vim.group.MarkGroup
import com.maddyhome.idea.vim.helper.EditorHelper
import com.maddyhome.idea.vim.helper.StringHelper.parseKeys
import com.maddyhome.idea.vim.helper.StringHelper.stringToKeys
import com.maddyhome.idea.vim.helper.subMode
import com.maddyhome.idea.vim.key.OperatorFunction
/**
* This emulation misses:
* - `:ExchangeClear` command
* - `g:exchange_no_mappings` variable
* - `g:exchange_indent` variable (?)
* - Default mappings should not be applied if there is a mapping defined in `~/.ideavimrc`.
* This functionality requires rewriting of IdeaVim initialization, so that plugins would be
* loaded after `~/.ideavimrc` is executed (as vim works). But the `if no bindings` can be added even now.
* It just won't work if the binding is defined after `set exchange`.
*/
class VimExchangeExtension: VimExtension {
override fun getName() = "exchange"
override fun init() {
putExtensionHandlerMapping(MappingMode.N, parseKeys(EXCHANGE_CMD), owner, ExchangeHandler(false), false)
putExtensionHandlerMapping(MappingMode.X, parseKeys(EXCHANGE_CMD), owner, VExchangeHandler(), false)
putExtensionHandlerMapping(MappingMode.N, parseKeys(EXCHANGE_CLEAR_CMD), owner, ExchangeClearHandler(), false)
putExtensionHandlerMapping(MappingMode.N, parseKeys(EXCHANGE_LINE_CMD), owner, ExchangeHandler(true), false)
putKeyMapping(MappingMode.N, parseKeys("cx"), owner, parseKeys(EXCHANGE_CMD), true)
putKeyMapping(MappingMode.X, parseKeys("X"), owner, parseKeys(EXCHANGE_CMD), true)
putKeyMapping(MappingMode.N, parseKeys("cxc"), owner, parseKeys(EXCHANGE_CLEAR_CMD), true)
putKeyMapping(MappingMode.N, parseKeys("cxx"), owner, parseKeys(EXCHANGE_LINE_CMD), true)
}
companion object {
const val EXCHANGE_CMD = "<Plug>(Exchange)"
const val EXCHANGE_CLEAR_CMD = "<Plug>(ExchangeClear)"
const val EXCHANGE_LINE_CMD = "<Plug>(ExchangeLine)"
val EXCHANGE_KEY = Key<Exchange>("exchange");
class Exchange(val type: CommandState.SubMode, val start: Mark, val end: Mark, val text: String) {
private var myHighlighter: RangeHighlighter? = null
fun setHighlighter(highlighter: RangeHighlighter) {
myHighlighter = highlighter
}
fun getHighlighter(): RangeHighlighter? = myHighlighter
}
fun clearExchange(editor: Editor) {
editor.getUserData(EXCHANGE_KEY)?.getHighlighter()?.let {
editor.markupModel.removeHighlighter(it)
}
editor.putUserData(EXCHANGE_KEY, null)
}
}
private class ExchangeHandler(private val isLine: Boolean) : VimExtensionHandler {
override fun isRepeatable() = true
override fun execute(editor: Editor, context: DataContext) {
setOperatorFunction(Operator(false))
executeNormal(parseKeys(if(isLine) "g@_" else "g@"), editor)
}
}
private class ExchangeClearHandler: VimExtensionHandler {
override fun execute(editor: Editor, context: DataContext) {
clearExchange(editor)
}
}
private class VExchangeHandler: VimExtensionHandler {
override fun execute(editor: Editor, context: DataContext) {
runWriteAction {
val subMode = editor.subMode
// Leave visual mode to create selection marks
executeNormal(parseKeys("<Esc>"), editor)
Operator(true).apply(editor, context, SelectionType.fromSubMode(subMode))
}
}
}
private class Operator(private val isVisual: Boolean): OperatorFunction {
fun Editor.getMarkOffset(mark: Mark) = EditorHelper.getOffset(this, mark.logicalLine, mark.col)
fun CommandState.SubMode.getString() = when(this) {
CommandState.SubMode.VISUAL_CHARACTER -> "v"
CommandState.SubMode.VISUAL_LINE -> "V"
CommandState.SubMode.VISUAL_BLOCK -> "\\<C-V>"
else -> throw Error("Invalid SubMode: $this")
}
override fun apply(editor: Editor, context: DataContext, selectionType: SelectionType): Boolean {
fun highlightExchange(ex: Exchange): RangeHighlighter {
val attributes = editor.colorsScheme.getAttributes(EditorColors.TEXT_SEARCH_RESULT_ATTRIBUTES)
val hlArea = when(ex.type) {
CommandState.SubMode.VISUAL_LINE -> HighlighterTargetArea.LINES_IN_RANGE
// TODO: handle other modes
else -> HighlighterTargetArea.EXACT_RANGE
}
return editor.markupModel.addRangeHighlighter(
editor.getMarkOffset(ex.start),
(editor.getMarkOffset(ex.end) + 1).coerceAtMost(EditorHelper.getFileSize(editor, true)),
HighlighterLayer.SELECTION - 1,
attributes,
hlArea
)
}
val currentExchange = getExchange(editor, isVisual, selectionType)
val exchange1 = editor.getUserData(EXCHANGE_KEY)
if (exchange1 == null) {
val highlighter = highlightExchange(currentExchange)
currentExchange.setHighlighter(highlighter)
editor.putUserData(EXCHANGE_KEY, currentExchange)
return true
} else {
val cmp = compareExchanges(exchange1, currentExchange)
var reverse = false
var expand = false
val (ex1, ex2) = when(cmp) {
ExchangeCompareResult.OVERLAP -> return false
ExchangeCompareResult.OUTER -> {
reverse = true
expand = true
Pair(currentExchange, exchange1)
}
ExchangeCompareResult.INNER -> {
expand = true
Pair(exchange1, currentExchange)
}
ExchangeCompareResult.GT -> {
reverse = true
Pair(currentExchange, exchange1)
}
ExchangeCompareResult.LT -> {
Pair(exchange1, currentExchange)
}
}
exchange(editor, ex1, ex2, reverse, expand)
clearExchange(editor)
return true
}
}
private fun exchange(editor: Editor, ex1: Exchange, ex2: Exchange, reverse: Boolean, expand: Boolean) {
fun pasteExchange(sourceExchange: Exchange, targetExchange: Exchange) {
VimPlugin.getMark().setChangeMarks(editor, TextRange(editor.getMarkOffset(targetExchange.start), editor.getMarkOffset(targetExchange.end)+1))
// do this instead of direct text manipulation to set change marks
setRegister('z', stringToKeys(sourceExchange.text), SelectionType.fromSubMode(sourceExchange.type))
executeNormal(stringToKeys("""`[${targetExchange.type.getString()}`]"zp"""), editor)
}
fun fixCursor(ex1: Exchange, ex2: Exchange, reverse: Boolean) {
val primaryCaret = editor.caretModel.primaryCaret
if(reverse) {
primaryCaret.moveToOffset(editor.getMarkOffset(ex1.start))
} else {
if (ex1.start.logicalLine == ex2.start.logicalLine) {
val horizontalOffset = ex1.end.col - ex2.end.col
primaryCaret.moveToLogicalPosition(LogicalPosition(ex1.start.logicalLine, ex1.start.col - horizontalOffset))
} else if(ex1.end.logicalLine - ex1.start.logicalLine != ex2.end.logicalLine - ex2.start.logicalLine) {
val verticalOffset = ex1.end.logicalLine - ex2.end.logicalLine
primaryCaret.moveToLogicalPosition(LogicalPosition(ex1.start.logicalLine - verticalOffset, ex1.start.col))
}
}
}
val zRegText = getRegister('z')
val unnRegText = getRegister('"')
val startRegText = getRegister('*')
val plusRegText = getRegister('+')
runWriteAction {
// TODO handle:
// " Compare using =~ because "'==' != 0" returns 0
// let indent = s:get_setting('exchange_indent', 1) !~ 0 && a:x.type ==# 'V' && a:y.type ==# 'V'
pasteExchange(ex1, ex2)
if (!expand) {
pasteExchange(ex2, ex1)
}
// TODO: handle: if ident
if (!expand) {
fixCursor(ex1, ex2, reverse)
}
setRegister('z', zRegText)
setRegister('"', unnRegText)
setRegister('*', startRegText)
setRegister('+', plusRegText)
}
}
private fun compareExchanges(x: Exchange, y: Exchange): ExchangeCompareResult {
fun intersects(x: Exchange, y: Exchange) =
x.end.logicalLine < y.start.logicalLine ||
x.start.logicalLine > y.end.logicalLine ||
x.end.col < y.start.col ||
x.start.col > y.end.col
fun comparePos(x: Mark, y: Mark): Int =
if (x.logicalLine == y.logicalLine) {
x.col - y.col
} else {
x.logicalLine - y.logicalLine
}
return if (x.type == CommandState.SubMode.VISUAL_BLOCK && y.type == CommandState.SubMode.VISUAL_BLOCK) {
when {
intersects(x, y) -> {
ExchangeCompareResult.OVERLAP
}
x.start.col <= y.start.col -> {
ExchangeCompareResult.LT
}
else -> {
ExchangeCompareResult.GT
}
}
} else if (comparePos(x.start, y.start) <=0 && comparePos(x.end, y.end) >=0) {
ExchangeCompareResult.OUTER
} else if (comparePos(y.start, x.start) <=0 && comparePos(y.end, x.end) >=0) {
ExchangeCompareResult.INNER
} else if (comparePos(x.start, y.end) <=0 && comparePos(y.start, x.end) <=0 ||
comparePos(y.start, x.end) <=0 && comparePos(x.start, y.end) <=0
) {
ExchangeCompareResult.OVERLAP
} else {
val cmp = comparePos(x.start, y.start)
when {
cmp == 0 -> ExchangeCompareResult.OVERLAP
cmp < 0 -> ExchangeCompareResult.LT
else -> ExchangeCompareResult.GT
}
}
}
enum class ExchangeCompareResult {
OVERLAP,
OUTER,
INNER,
LT,
GT,
}
private fun getExchange(editor: Editor, isVisual: Boolean, selectionType: SelectionType): Exchange {
fun getEndCol(selectionEnd: Mark, type: CommandState.SubMode): Int {
return if (type == CommandState.SubMode.VISUAL_LINE) {
EditorHelper.getLineLength(editor, selectionEnd.logicalLine)
} else {
selectionEnd.col
}
}
// TODO: improve KeyStroke list to sting conversion
fun getRegisterText(reg: Char): String = getRegister(reg)?.map { it.keyChar }?.joinToString("") ?: ""
fun getMarks(isVisual: Boolean): Pair<Mark, Mark> {
val (startMark, endMark) =
if (isVisual) {
Pair(MarkGroup.MARK_VISUAL_START, MarkGroup.MARK_VISUAL_END)
} else {
Pair(MarkGroup.MARK_CHANGE_START, MarkGroup.MARK_CHANGE_END)
}
val marks = VimPlugin.getMark()
return Pair(marks.getMark(editor, startMark)!!, marks.getMark(editor, endMark)!!)
}
val unnRegText = getRegister('"')
val starRegText = getRegister('*')
val plusRegText = getRegister('+')
var (selectionStart, selectionEnd) = getMarks(isVisual)
if (isVisual) {
executeNormal(parseKeys("gvy"), editor)
// TODO: handle
//if &selection ==# 'exclusive' && start != end
// let end.column -= len(matchstr(@@, '\_.$'))
} else {
selectionEnd = selectionEnd.let {
VimMark.create(
it.key,
it.logicalLine,
getEndCol(it, selectionType.toSubMode()),
it.filename,
it.protocol
)!!
}
when (selectionType) {
SelectionType.LINE_WISE -> executeNormal(stringToKeys("`[V`]y"), editor)
SelectionType.BLOCK_WISE -> executeNormal(stringToKeys("""`[<C-V>`]y"""), editor)
SelectionType.CHARACTER_WISE -> executeNormal(stringToKeys("`[v`]y"), editor)
}
}
val text = getRegisterText('"')
setRegister('"', unnRegText)
setRegister('*', starRegText)
setRegister('+', plusRegText)
return Exchange(
selectionType.toSubMode(),
selectionStart,
selectionEnd,
text
)
}
}
}

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

@@ -144,8 +144,7 @@ public class VimTextObjEntireExtension implements VimExtension {
} else {
commandState.getCommandBuilder().completeCommandPart(new Argument(new Command(count,
textObjectHandler, Command.Type.MOTION,
EnumSet.of(CommandFlags.FLAG_MOT_CHARACTERWISE),
emptyList())));
EnumSet.of(CommandFlags.FLAG_MOT_CHARACTERWISE))));
}
}
}

View File

@@ -32,7 +32,10 @@ import com.intellij.openapi.editor.*;
import com.intellij.openapi.editor.actionSystem.ActionPlan;
import com.intellij.openapi.editor.actionSystem.TypedActionHandler;
import com.intellij.openapi.editor.actionSystem.TypedActionHandlerEx;
import com.intellij.openapi.editor.event.*;
import com.intellij.openapi.editor.event.DocumentEvent;
import com.intellij.openapi.editor.event.DocumentListener;
import com.intellij.openapi.editor.event.EditorMouseEvent;
import com.intellij.openapi.editor.event.EditorMouseListener;
import com.intellij.openapi.editor.ex.EditorEx;
import com.intellij.openapi.editor.impl.TextRangeInterval;
import com.intellij.openapi.fileEditor.FileDocumentManager;
@@ -84,6 +87,8 @@ public class ChangeGroup {
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_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);
private @Nullable Command lastInsert;
@@ -431,18 +436,12 @@ public class ChangeGroup {
}
};
public void editorCreated(@NotNull EditorFactoryEvent event) {
final Editor editor = event.getEditor();
public void editorCreated(Editor editor) {
EventFacade.getInstance().addEditorMouseListener(editor, listener);
UserDataManager.setVimChangeGroup(editor, true);
}
public void editorReleased(@NotNull EditorFactoryEvent event) {
final Editor editor = event.getEditor();
if (UserDataManager.getVimChangeGroup(editor)) {
EventFacade.getInstance().removeEditorMouseListener(editor, listener);
UserDataManager.setVimChangeGroup(editor, false);
}
public void editorReleased(Editor editor) {
EventFacade.getInstance().removeEditorMouseListener(editor, listener);
}
/**
@@ -1223,13 +1222,12 @@ public class ChangeGroup {
boolean bigWord = id.equals(VIM_MOTION_BIG_WORD_RIGHT);
final CharSequence chars = editor.getDocument().getCharsSequence();
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);
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;
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) {
final boolean res = deleteCharacter(editor, caret, 1, true);
if (res) {
@@ -1258,7 +1256,7 @@ public class ChangeGroup {
}
if (kludge) {
int size = EditorHelper.getFileSize(editor);
int size = fileSize;
int cnt = count * motion.getCount();
int pos1 = SearchHelper.findNextWordEnd(chars, offset, size, cnt, bigWord, false);
int pos2 = SearchHelper.findNextWordEnd(chars, pos1, size, -cnt, bigWord, false);
@@ -1751,7 +1749,7 @@ public class ChangeGroup {
if (type != null) {
final int start = range.getStartOffset();
VimPlugin.getMark().setMark(editor, MarkGroup.MARK_CHANGE_POS, start);
VimPlugin.getMark().setChangeMarks(editor, new TextRange(start, start));
VimPlugin.getMark().setChangeMarks(editor, new TextRange(start, start+1));
}
return true;

View File

@@ -60,7 +60,6 @@ public class DigraphGroup {
final String digraph = keys.get(ch);
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) {
VimPlugin.showMessage(String.format("<%s> %d, Hex %02x, Oct %03o%s",

View File

@@ -52,12 +52,10 @@ import java.util.List;
*/
@State(name = "VimEditorSettings", storages = {@Storage(value = "$APP_CONFIG$/vim_settings.xml")})
public class EditorGroup implements PersistentStateComponent<Element> {
private static final boolean ANIMATED_SCROLLING_VIM_VALUE = false;
private static final boolean REFRAIN_FROM_SCROLLING_VIM_VALUE = true;
public static final String EDITOR_STORE_ELEMENT = "editor";
private boolean isBlockCursor = false;
private boolean isAnimatedScrolling = false;
private boolean isRefrainFromScrolling = false;
private Boolean isKeyRepeat = null;
@@ -69,18 +67,6 @@ public class EditorGroup implements PersistentStateComponent<Element> {
}
};
public void turnOn() {
for (Editor editor : EditorFactory.getInstance().getAllEditors()) {
VimPlugin.getEditor().editorCreated(editor);
}
}
public void turnOff() {
for (Editor editor : EditorFactory.getInstance().getAllEditors()) {
VimPlugin.getEditor().editorDeinit(editor, false);
}
}
private void initLineNumbers(final @NotNull Editor editor) {
if (!supportsVimLineNumbers(editor) || UserDataManager.getVimEditorGroup(editor)) {
return;
@@ -244,7 +230,6 @@ public class EditorGroup implements PersistentStateComponent<Element> {
public void editorCreated(@NotNull Editor editor) {
isBlockCursor = editor.getSettings().isBlockCursor();
isAnimatedScrolling = editor.getSettings().isAnimatedScrolling();
isRefrainFromScrolling = editor.getSettings().isRefrainFromScrolling();
DocumentManager.INSTANCE.addListeners(editor.getDocument());
VimPlugin.getKey().registerRequiredShortcutKeys(editor);
@@ -258,7 +243,6 @@ public class EditorGroup implements PersistentStateComponent<Element> {
KeyHandler.getInstance().reset(editor);
}
editor.getSettings().setBlockCursor(!CommandStateHelper.inInsertMode(editor));
editor.getSettings().setAnimatedScrolling(ANIMATED_SCROLLING_VIM_VALUE);
editor.getSettings().setRefrainFromScrolling(REFRAIN_FROM_SCROLLING_VIM_VALUE);
}
@@ -267,7 +251,6 @@ public class EditorGroup implements PersistentStateComponent<Element> {
UserDataManager.unInitializeEditor(editor);
VimPlugin.getKey().unregisterShortcutKeys(editor);
editor.getSettings().setBlockCursor(isBlockCursor);
editor.getSettings().setAnimatedScrolling(isAnimatedScrolling);
editor.getSettings().setRefrainFromScrolling(isRefrainFromScrolling);
DocumentManager.INSTANCE.removeListeners(editor.getDocument());
}

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