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

Compare commits

..

71 Commits

Author SHA1 Message Date
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
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
Alex Plate
bd172b3300 Run manual tests 2020-04-07 11:35:58 +03:00
Alex Plate
95c7a13cb5 Turning plugin on should be performed after commands registration 2020-04-07 11:27:24 +03:00
Alex Plate
b1ddf03385 Add notes to changelog about Keep a Changelog and Semantic Versioning. 2020-04-06 10:25:50 +03:00
Alex Plate
a83c326736 Add logging for activating ex panel 2020-04-03 08:23:55 +03:00
Alex Plate
b1acb56247 Fix exception for ciw on last char in file 2020-03-28 15:57:54 +03:00
Alex Plate
caa4731a13 Fix exception when using text objects on empty files 2020-03-28 15:39:06 +03:00
Alex Plate
5b0ece7a91 ReplaceWithRegister for clipboard registers 2020-03-23 10:34:53 +03:00
Alex Plate
c8d64e0a06 Update changelog 2020-03-20 11:44:27 +03:00
Alex Plate
1a3dea0de6 Update slack notification format 2020-03-20 11:42:40 +03:00
Alex Plate
17b642280e Update gradle wrapper properties 2020-03-20 11:42:14 +03:00
Alex Plate
1c1717b78b Add kk to the contributions list 2020-03-20 10:38:30 +03:00
Alex Plate
4bbbdf8108 Fix ReplaceWithRegister to the empty line 2020-03-20 10:35:08 +03:00
Alex Pláte
04a193309d Merge pull request #228 from kevin70/master
fixed #VIM-570
2020-03-20 10:34:58 +03:00
Alex Plate
f106ffa176 Support ReplaceWithRegister plugin emulation 2020-03-19 11:25:30 +03:00
Alex Plate
8d5d099542 Update icon on ideastatusicon option change 2020-03-19 09:13:49 +03:00
kk
4849992ca9 fixed #VIM-570 2020-03-18 18:49:55 +08:00
Alex Plate
623105650e PutTextAction refactor 2020-03-17 09:43:40 +03:00
Alex Plate
5e2c01daa6 Rename PutTextAction.kt 2020-03-17 09:32:46 +03:00
Alex Plate
58bf3a4d30 Merge Put actions into one file 2020-03-17 09:28:49 +03:00
Alex Plate
2d434c38b9 Move test to correct directory 2020-03-15 16:53:57 +03:00
Alex Plate
246f5cd8cf VIM-1911 Lookup keys respect IDE handler 2020-03-14 23:03:42 +03:00
Alex Plate
5a174d21f1 Update CHANGES 2020-03-14 18:06:58 +03:00
Alex Plate
e632c653f6 Add showcmd text to widget label 2020-03-14 18:05:38 +03:00
Alex Plate
174d17b088 VIM-1958 Fix X command for linewise selection 2020-03-14 18:00:45 +03:00
Alex Plate
3a35c931e4 Unignore some tests 2020-03-14 17:42:33 +03:00
Alex Plate
b768b26c85 Remove some warnings 2020-03-14 17:28:14 +03:00
Alex Plate
123ce6ebaf Get rid of deprecated KeyEvents 2020-03-14 15:55:01 +03:00
Alex Plate
276c8db512 Fix some tests 2020-03-12 11:48:24 +03:00
Alex Plate
f898b8d181 Fix mappings to <S-Space> 2020-03-12 11:25:14 +03:00
Alex Plate
e9f9e531e4 Convert vim typed action to kt 2020-03-12 11:04:35 +03:00
Alex Plate
a7d813cb86 Rename .java to .kt 2020-03-12 11:04:35 +03:00
Alex Plate
75b6eedb12 Remove unused class 2020-03-12 10:33:52 +03:00
Alex Plate
ec6860aa90 Change the label of showcmd widget 2020-03-12 10:03:39 +03:00
Alex Plate
5cf661c6ae Fix exception during command typing 2020-03-10 14:58:35 +03:00
Alex Plate
8c62caae7c Fix compilation errors 2020-03-10 11:30:10 +03:00
Alex Plate
cc6fe21af6 Update CHANGES.md 2020-03-10 10:28:17 +03:00
Alex Pláte
1902151efa Merge pull request #220 from citizenmatt/feature/showcmd
Implement showcmd
2020-03-10 10:24:36 +03:00
Alex Pláte
b7af1e6289 Merge branch 'master' into feature/showcmd 2020-03-10 10:24:06 +03:00
Alex Plate
0c77b320db VIM-570 Print non-ascii characters in ex panel 2020-03-06 13:03:28 +03:00
Alex Plate
ee41adc4e9 Update kotlin version 2020-03-06 09:54:57 +03:00
Alex Pláte
93462d7505 Merge pull request #221 from igrekster/master
argtextobj: support bracket pairs configuration via let g:argtextobj_pairs="..."
2020-03-06 09:54:33 +03:00
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
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
118 changed files with 3013 additions and 1648 deletions

4
.gitignore vendored
View File

@@ -1,6 +1,10 @@
*.swp *.swp
/.gradle/ /.gradle/
/.idea/ /.idea/
!/.idea/scopes
!/.idea/copyright
/build/ /build/
/out/ /out/
/tmp/ /tmp/

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>

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,14 @@ Contributors:
[![icon][github]](https://github.com/angelbot) [![icon][github]](https://github.com/angelbot)
&nbsp; &nbsp;
John Weigel 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
If you are a contributor and your name is not listed here, feel free to If you are a contributor and your name is not listed here, feel free to
contact the maintainers. contact the maintainers.

View File

@@ -3,6 +3,12 @@ The Changelog
History of changes in IdeaVim for the IntelliJ platform. History of changes in IdeaVim for the IntelliJ platform.
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
and this project DOES NOT adhere to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
Stable versions use X.Y format.
EAP versions use X.Y.Z format.
Get an Early Access Get an Early Access
------------------- -------------------
@@ -16,31 +22,44 @@ It is important to distinguish EAP from traditional pre-release software.
Please note that the quality of EAP versions may at times be way below even Please note that the quality of EAP versions may at times be way below even
usual beta standards. usual beta standards.
To Be Released 0.57, 2020-04-28
-------------- -------------
_Available since 0.55.1 EAP:_
**Fixes:** **Fixes:**
* [VIM-1284](https://youtrack.jetbrains.com/issue/VIM-1284) Fix mapping of digits * [VIM-1992](https://youtrack.jetbrains.com/issue/VIM-1992) Fix mappings to `<S-Letter>`
* Fix handling of counts on both operator and motion, e.g. `3d2w` deletes 6 words, instead of 32 * [VIM-1991](https://youtrack.jetbrains.com/issue/VIM-1991) Fix working with number registers
* 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:_ **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:** **Features:**
* `argtextobj.vim` plugin emulation ([argtextobj.vim](https://vim.sourceforge.io/scripts/script.php?script_id=2699)) * `ReplaceWithRegister` plugin emulation ([ReplaceWithRegister](https://www.vim.org/scripts/script.php?script_id=2703)).
* `vim-textobj-entire` plugin emulation ([vim-textobj-entire](https://github.com/kana/vim-textobj-entire)) * `argtextobj.vim` plugin emulation ([argtextobj.vim](https://vim.sourceforge.io/scripts/script.php?script_id=2699)).
* Support `ls/buffers/files` commands * `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:** **Changes:**
* Replace `ideastatusbar` option with `ideastatusicon`. Now you can make the icon gray. * Replace `ideastatusbar` option with `ideastatusicon`. Now you can make the icon gray.
**Deprecations:**
* `ideastatusbar` option is deprecated now. See `ideastatusicon`.
**Fixes:** **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 0.55, 2020-01-20
-------------- --------------

View File

@@ -78,36 +78,6 @@ in the issue tracker.
* You can install this file by selecting "Settings | Plugins | Install plugin * You can install this file by selecting "Settings | Plugins | Install plugin
from disk...". 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 ### Testing

112
README.md
View File

@@ -25,23 +25,20 @@ Resources:
* [@IdeaVim](https://twitter.com/ideavim) in Twitter * [@IdeaVim](https://twitter.com/ideavim) in Twitter
Installation Setup
------------ ------------
Use the IDE's plugin manager to install the latest version of the plugin. - IdeaVim can be installed via `Settings | Plugins`.
Start the IDE normally and enable the Vim emulation using "Tools | Vim See [detailed instructions](https://www.jetbrains.com/help/idea/managing-plugins.html#).
Emulator" menu item. At this point you must use Vim keystrokes in all editors.
If you wish to disable the plugin, select the "Tools | Vim Emulator" menu so - Use `Tools | Vim Emulator` to enable or disable emulation.
it is unchecked. At this point your IDE will work with its regular keyboard
shortcuts.
Keyboard shortcut conflicts between the Vim emulation and the IDE can be - Use `~/.ideavimrc` file as an analog of `~/.vimrc` ([details](#Files)). XGD standard is supported as well.
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.
- 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 Get Early Access
------------------- -------------------
@@ -49,7 +46,9 @@ Get Early Access
Would you like to try new features and fixes? Join the Early Access Program and Would you like to try new features and fixes? Join the Early Access Program and
receive EAP builds as updates! 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: Or subscribe to EAP updates manually:
@@ -57,7 +56,7 @@ Or subscribe to EAP updates manually:
2. Click the gear icon :gear:, select `Manage Plugin Repositories`, and add the following url: 2. Click the gear icon :gear:, select `Manage Plugin Repositories`, and add the following url:
`https://plugins.jetbrains.com/plugins/eap/ideavim` `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. 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 Please note that the quality of EAP versions may at times be way below even
@@ -91,14 +90,15 @@ Supported:
* Vim web help * Vim web help
* Select mode * Select mode
Emulated Vim plugins: [Emulated Vim plugins](doc/emulated-plugins.md):
* vim-easymotion * vim-easymotion
* vim-surround * vim-surround
* vim-multiple-cursors * vim-multiple-cursors
* vim-commentary * vim-commentary
* argtextobj.vim [To Be Released] * argtextobj.vim
* vim-textobj-entire [To Be Released] * vim-textobj-entire
* ReplaceWithRegister
Not supported (yet): Not supported (yet):
@@ -114,19 +114,19 @@ See also:
Files Files
----- -----
* ~/.ideavimrc * `~/.ideavimrc`
* Your IdeaVim-specific Vim initialization commands * 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 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 See [VIM-669](https://youtrack.jetbrains.com/issue/VIM-669) for proper parsing
of VimL files. of VimL files.
Also note that if you have overridden the `user.home` JVM option, this 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 have `-Duser.home=/my/alternate/home` then IdeaVim will source
`/my/alternate/home/.ideavimrc` instead of `~/.ideavimrc`. `/my/alternate/home/.ideavimrc` instead of `~/.ideavimrc`.
@@ -137,50 +137,25 @@ Put your settings to `$XDG_CONFIG_HOME$/ideavim/ideavimrc` file.
Emulated Vim Plugins Emulated Vim Plugins
-------------------- --------------------
IdeaVim extensions emulate some plugins of the original Vim. In order to use See [doc/emulated-plugins.md](doc/emulated-plugins.md)
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`
Changes to the IDE 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 ### Undo/Redo
The IdeaVim plugin uses the undo/redo functionality of the IntelliJ Platform, The IdeaVim plugin uses the undo/redo functionality of the IntelliJ Platform,
@@ -199,21 +174,6 @@ improvement.
See also [unresolved escape issues](https://youtrack.jetbrains.com/issues/VIM?q=%23Unresolved+Help+topic%3A+i_Esc). 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 Contributing
------------ ------------

View File

@@ -9,7 +9,7 @@ buildscript {
} }
plugins { plugins {
id 'org.jetbrains.intellij' version '0.4.16' id 'org.jetbrains.intellij' version '0.4.18'
} }
apply plugin: 'java' apply plugin: 'java'
@@ -74,15 +74,56 @@ tasks.register("slackEapNotification") {
doLast { doLast {
if (!slackUrl) return if (!slackUrl) return
def post = new URL(slackUrl).openConnection() def post = new URL(slackUrl).openConnection()
def message = "{\"text\":\"New EAP released: $version\"}" def changeLog = extractChangelog()
changeLog = changeLog.replace("* ", "• ") // Replace stars with bullets
changeLog = changeLog.replace("**", "*") // Enable bold text
changeLog = changeLog.replaceAll("\\[([^]]+)]\\(([^)]+)\\)", '<$2|$1>') // Enable links
def message ="""
{
"text": "New version of IdeaVim",
"blocks": [
{
"type": "section",
"text": {
"type": "mrkdwn",
"text": "IdeaVim EAP $version has been relesed\\n$changeLog"
}
}
]
}
"""
post.setRequestMethod("POST") post.setRequestMethod("POST")
post.setDoOutput(true) post.setDoOutput(true)
post.setRequestProperty("Content-Type", "application/json") post.setRequestProperty("Content-Type", "application/json")
post.getOutputStream().write(message.getBytes("UTF-8")) post.getOutputStream().write(message.getBytes("UTF-8"))
def postRC = post.getResponseCode() def postRC = post.getResponseCode()
println(postRC) println(postRC)
if(postRC.equals(200)) { if(postRC == 200) {
println(post.getInputStream().getText()) println(post.getInputStream().getText())
} }
} }
} }
// Very primitive changelog extraction code
def extractChangelog() {
def startLine = "_Available since $version EAP:_"
def endLine = "_To Be Released..._"
def startSaving = false
def res = new StringBuilder()
new File("./CHANGES.md").eachLine { line ->
if (startSaving) {
if (line == endLine) {
startSaving = false
}
else {
res.append(line).append('\n')
}
}
else {
if (line == startLine) {
startSaving = true
}
}
}
return res.toString()
}

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

@@ -0,0 +1,62 @@
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`
## 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 same as ideaselection - IdeaVim ONLY
'showmode' 'smd' message on the status line to show current mode 'showmode' 'smd' message on the status line to show current mode
'showcmd' 'sc' show (partial) command in the status bar
'sidescroll' 'ss' minimum number of columns to scroll horizontally 'sidescroll' 'ss' minimum number of columns to scroll horizontally
'sidescrolloff' 'siso' min. number of columns to left and right of cursor 'sidescrolloff' 'siso' min. number of columns to left and right of cursor
'smartcase' 'scs' no ignore case when pattern is uppercase 'smartcase' 'scs' no ignore case when pattern is uppercase
@@ -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. If false, IdeaVim icon won't be shown in the status bar.
Works only from `~/.ideavimrc` after the IDE restart. Works only from `~/.ideavimrc` after the IDE restart.
`ideastatusicon` `ideastatusicon` String(default "enabled") [To Be Released] `ideastatusicon` `ideastatusicon` String(default "enabled")
Define the behavior of IdeaVim icon in the status bar. Define the behavior of IdeaVim icon in the status bar.

View File

@@ -1,11 +1,11 @@
# suppress inspection "UnusedProperty" for whole file # suppress inspection "UnusedProperty" for whole file
ideaVersion=201-EAP-SNAPSHOT ideaVersion=2020.1
downloadIdeaSources=true downloadIdeaSources=true
instrumentPluginCode=true instrumentPluginCode=true
version=SNAPSHOT version=SNAPSHOT
javaVersion=1.8 javaVersion=1.8
kotlinVersion=1.3.61 kotlinVersion=1.3.71
publishUsername=username publishUsername=username
publishToken=token publishToken=token
publishChannels=eap publishChannels=eap

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 distributionBase=GRADLE_USER_HOME
distributionPath=wrapper/dists distributionPath=wrapper/dists
distributionUrl=https\://services.gradle.org/distributions/gradle-6.2.1-bin.zip
zipStoreBase=GRADLE_USER_HOME
zipStorePath=wrapper/dists 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.InsertSingleCommandAction" mappingModes="I" keys="«C-O»"/>
<vimAction implementation="com.maddyhome.idea.vim.action.change.insert.VisualBlockInsertAction" mappingModes="X" keys="I"/> <vimAction implementation="com.maddyhome.idea.vim.action.change.insert.VisualBlockInsertAction" mappingModes="X" keys="I"/>
<vimAction implementation="com.maddyhome.idea.vim.action.change.insert.VisualBlockAppendAction" mappingModes="X" keys="A"/> <vimAction implementation="com.maddyhome.idea.vim.action.change.insert.VisualBlockAppendAction" mappingModes="X" keys="A"/>
<vimAction implementation="com.maddyhome.idea.vim.action.change.insert.StartInsertDigraphAction" mappingModes="IC" keys="«C-K»"/> <vimAction implementation="com.maddyhome.idea.vim.action.change.insert.InsertCompletedDigraphAction" mappingModes="IC" keys="«C-K»"/>
<vimAction implementation="com.maddyhome.idea.vim.action.change.insert.StartInsertLiteralAction" mappingModes="IC" keys="«C-V»,«C-Q»"/> <vimAction implementation="com.maddyhome.idea.vim.action.change.insert.InsertCompletedLiteralAction" mappingModes="IC" keys="«C-V»,«C-Q»"/>
<!-- Delete --> <!-- Delete -->
<vimAction implementation="com.maddyhome.idea.vim.action.change.delete.DeleteCharacterAction" mappingModes="N" keys="«DEL»"/> <vimAction implementation="com.maddyhome.idea.vim.action.change.delete.DeleteCharacterAction" mappingModes="N" keys="«DEL»"/>
@@ -260,14 +260,16 @@
<vimAction implementation="com.maddyhome.idea.vim.action.copy.PutTextBeforeCursorNoIndentAction" mappingModes="N" keys="[P,]P,[p"/> <vimAction implementation="com.maddyhome.idea.vim.action.copy.PutTextBeforeCursorNoIndentAction" mappingModes="N" keys="[P,]P,[p"/>
<vimAction implementation="com.maddyhome.idea.vim.action.copy.PutTextAfterCursorActionMoveCursor" mappingModes="N" keys="gp"/> <vimAction implementation="com.maddyhome.idea.vim.action.copy.PutTextAfterCursorActionMoveCursor" mappingModes="N" keys="gp"/>
<vimAction implementation="com.maddyhome.idea.vim.action.copy.PutTextBeforeCursorActionMoveCursor" mappingModes="N" keys="gP"/> <vimAction implementation="com.maddyhome.idea.vim.action.copy.PutTextBeforeCursorActionMoveCursor" mappingModes="N" keys="gP"/>
<vimAction implementation="com.maddyhome.idea.vim.action.copy.SelectRegisterAction" mappingModes="NXO" keys='"'/>
<vimAction implementation="com.maddyhome.idea.vim.action.copy.YankLineAction" mappingModes="N" keys="Y"/> <vimAction implementation="com.maddyhome.idea.vim.action.copy.YankLineAction" mappingModes="N" keys="Y"/>
<vimAction implementation="com.maddyhome.idea.vim.action.copy.YankMotionAction" mappingModes="N" keys="y"/> <vimAction implementation="com.maddyhome.idea.vim.action.copy.YankMotionAction" mappingModes="N" keys="y"/>
<vimAction implementation="com.maddyhome.idea.vim.action.copy.YankVisualAction" mappingModes="X" keys="y"/> <vimAction implementation="com.maddyhome.idea.vim.action.copy.YankVisualAction" mappingModes="X" keys="y"/>
<vimAction implementation="com.maddyhome.idea.vim.action.copy.YankVisualLinesAction" mappingModes="X" keys="Y"/> <vimAction implementation="com.maddyhome.idea.vim.action.copy.YankVisualLinesAction" mappingModes="X" keys="Y"/>
<vimAction implementation="com.maddyhome.idea.vim.action.copy.PutVisualTextAction" mappingModes="X" keys="p,P"/> <vimAction implementation="com.maddyhome.idea.vim.action.copy.PutVisualTextBeforeCursorAction" mappingModes="X" keys="P"/>
<vimAction implementation="com.maddyhome.idea.vim.action.copy.PutVisualTextNoIndentAction" mappingModes="X" keys="[p,]p,]P,[P"/> <vimAction implementation="com.maddyhome.idea.vim.action.copy.PutVisualTextAfterCursorAction" mappingModes="X" keys="p"/>
<vimAction implementation="com.maddyhome.idea.vim.action.copy.PutVisualTextMoveCursorAction" mappingModes="X" keys="gp,gP"/> <vimAction implementation="com.maddyhome.idea.vim.action.copy.PutVisualTextBeforeCursorNoIndentAction" mappingModes="X" keys="]P,[P"/>
<vimAction implementation="com.maddyhome.idea.vim.action.copy.PutVisualTextAfterCursorNoIndentAction" mappingModes="X" keys="[p,]p"/>
<vimAction implementation="com.maddyhome.idea.vim.action.copy.PutVisualTextBeforeCursorMoveCursorAction" mappingModes="X" keys="gP"/>
<vimAction implementation="com.maddyhome.idea.vim.action.copy.PutVisualTextAfterCursorMoveCursorAction" mappingModes="X" keys="gp"/>
<!-- File --> <!-- File -->
<vimAction implementation="com.maddyhome.idea.vim.action.file.FileSaveCloseAction" mappingModes="N" keys="ZQ,ZZ"/> <vimAction implementation="com.maddyhome.idea.vim.action.file.FileSaveCloseAction" mappingModes="N" keys="ZQ,ZZ"/>

View File

@@ -5,5 +5,6 @@
<vimExtension implementation="com.maddyhome.idea.vim.extension.commentary.CommentaryExtension"/> <vimExtension implementation="com.maddyhome.idea.vim.extension.commentary.CommentaryExtension"/>
<vimExtension implementation="com.maddyhome.idea.vim.extension.textobjentire.VimTextObjEntireExtension"/> <vimExtension implementation="com.maddyhome.idea.vim.extension.textobjentire.VimTextObjEntireExtension"/>
<vimExtension implementation="com.maddyhome.idea.vim.extension.argtextobj.VimArgTextObjExtension"/> <vimExtension implementation="com.maddyhome.idea.vim.extension.argtextobj.VimArgTextObjExtension"/>
<vimExtension implementation="com.maddyhome.idea.vim.extension.replacewithregister.ReplaceWithRegister"/>
</extensions> </extensions>
</idea-plugin> </idea-plugin>

View File

@@ -3,9 +3,8 @@
<id>IdeaVIM</id> <id>IdeaVIM</id>
<change-notes><![CDATA[ <change-notes><![CDATA[
<ul> <ul>
<li>Support argtextobj.vim plugin emulation</li> <li>Fix mappings for uppercase letters</li>
<li>Support vim-textobj-entire plugin emulation</li> <li>Fix yank/paste with number registers</li>
<li>Support ls/buffers/files command</li>
<li>Various bug fixes</li> <li>Various bug fixes</li>
</ul> </ul>
<p>See also the complete <a href="https://github.com/JetBrains/ideavim/blob/master/CHANGES.md">changelog</a>.</p> <p>See also the complete <a href="https://github.com/JetBrains/ideavim/blob/master/CHANGES.md">changelog</a>.</p>
@@ -53,6 +52,7 @@
<applicationConfigurable groupId="editor" instance="com.maddyhome.idea.vim.ui.VimEmulationConfigurable"/> <applicationConfigurable groupId="editor" instance="com.maddyhome.idea.vim.ui.VimEmulationConfigurable"/>
<projectService serviceImplementation="com.maddyhome.idea.vim.group.NotificationService"/> <projectService serviceImplementation="com.maddyhome.idea.vim.group.NotificationService"/>
<statusBarWidgetProvider implementation="com.maddyhome.idea.vim.StatusBarIconProvider"/> <statusBarWidgetProvider implementation="com.maddyhome.idea.vim.StatusBarIconProvider"/>
<statusBarWidgetProvider implementation="com.maddyhome.idea.vim.ui.ShowCmdStatusBarWidget"/>
<applicationService serviceImplementation="com.maddyhome.idea.vim.VimLocalConfig"/> <applicationService serviceImplementation="com.maddyhome.idea.vim.VimLocalConfig"/>
<applicationService serviceImplementation="com.maddyhome.idea.vim.VimPlugin"/> <applicationService serviceImplementation="com.maddyhome.idea.vim.VimPlugin"/>

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

View File

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

View File

@@ -61,6 +61,7 @@ import javax.swing.SwingConstants
private class StatusBarIconProvider : StatusBarWidgetProvider { private class StatusBarIconProvider : StatusBarWidgetProvider {
override fun getWidget(project: Project): VimStatusBar? { override fun getWidget(project: Project): VimStatusBar? {
@Suppress("DEPRECATION")
if (!OptionsManager.ideastatusbar.isSet) return null if (!OptionsManager.ideastatusbar.isSet) return null
if (OptionsManager.ideastatusicon.value == IdeaStatusIcon.disabled) return null if (OptionsManager.ideastatusicon.value == IdeaStatusIcon.disabled) return null
return VimStatusBar return VimStatusBar
@@ -69,6 +70,10 @@ private class StatusBarIconProvider : StatusBarWidgetProvider {
object VimStatusBar : StatusBarWidget, StatusBarWidget.IconPresentation { object VimStatusBar : StatusBarWidget, StatusBarWidget.IconPresentation {
init {
OptionsManager.ideastatusicon.addOptionChangeListener { _, _ -> this.update() }
}
private var statusBar: StatusBar? = null private var statusBar: StatusBar? = null
override fun ID(): String = "IdeaVim-Icon" override fun ID(): String = "IdeaVim-Icon"

View File

@@ -50,6 +50,7 @@ class VimLocalConfig : PersistentStateComponent<Element> {
companion object { companion object {
fun initialize() { fun initialize() {
@Suppress("DEPRECATION")
ServiceManager.getService(VimLocalConfig::class.java) ServiceManager.getService(VimLocalConfig::class.java)
} }
} }

View File

@@ -114,7 +114,8 @@ public class VimPlugin implements PersistentStateComponent<Element>, Disposable
Application application = ApplicationManager.getApplication(); Application application = ApplicationManager.getApplication();
if (application.isUnitTestMode()) { if (application.isUnitTestMode()) {
application.invokeAndWait(this::turnOnPlugin); application.invokeAndWait(this::turnOnPlugin);
} else { }
else {
application.invokeLater(this::turnOnPlugin); application.invokeLater(this::turnOnPlugin);
} }
} }
@@ -392,10 +393,6 @@ public class VimPlugin implements PersistentStateComponent<Element>, Disposable
private void turnOnPlugin() { private void turnOnPlugin() {
ApplicationManager.getApplication().invokeLater(this::updateState); ApplicationManager.getApplication().invokeLater(this::updateState);
getEditor().turnOn();
getSearch().turnOn();
VimListenerManager.INSTANCE.turnOn();
// Register vim actions in command mode // Register vim actions in command mode
RegisterActions.registerActions(); RegisterActions.registerActions();
@@ -407,17 +404,16 @@ public class VimPlugin implements PersistentStateComponent<Element>, Disposable
// Execute ~/.ideavimrc // Execute ~/.ideavimrc
registerIdeavimrc(); registerIdeavimrc();
// Turing on should be performed after all commands registration
getEditor().turnOn();
getSearch().turnOn();
VimListenerManager.INSTANCE.turnOn();
} }
private void turnOffPlugin() { private void turnOffPlugin() {
KeyHandler.getInstance().fullReset(null); KeyHandler.getInstance().fullReset(null);
// Unregister vim actions in command mode
RegisterActions.unregisterActions();
// Unregister ex handlers
CommandParser.getInstance().unregisterHandlers();
EditorGroup editorGroup = getEditorIfCreated(); EditorGroup editorGroup = getEditorIfCreated();
if (editorGroup != null) { if (editorGroup != null) {
editorGroup.turnOff(); editorGroup.turnOff();
@@ -428,6 +424,12 @@ public class VimPlugin implements PersistentStateComponent<Element>, Disposable
} }
VimListenerManager.INSTANCE.turnOff(); VimListenerManager.INSTANCE.turnOff();
ExEntryPanel.fullReset(); ExEntryPanel.fullReset();
// Unregister vim actions in command mode
RegisterActions.unregisterActions();
// Unregister ex handlers
CommandParser.getInstance().unregisterHandlers();
} }
private boolean stateUpdated = false; private boolean stateUpdated = false;

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

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -1,42 +0,0 @@
/*
* IdeaVim - Vim emulator for IDEs based on the IntelliJ platform
* Copyright (C) 2003-2020 The IdeaVim authors
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package com.maddyhome.idea.vim.action.copy
import com.intellij.openapi.actionSystem.DataContext
import com.intellij.openapi.editor.Editor
import com.maddyhome.idea.vim.VimPlugin
import com.maddyhome.idea.vim.command.Argument
import com.maddyhome.idea.vim.command.Command
import com.maddyhome.idea.vim.group.copy.PutData
import com.maddyhome.idea.vim.group.copy.PutData.TextData
import com.maddyhome.idea.vim.handler.ChangeEditorActionHandler
class 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 * @author vlan
*/ */
class PutVisualTextAction : VisualOperatorActionHandler.SingleExecution() { sealed class PutVisualTextBaseAction(
private val insertTextBeforeCaret: Boolean,
private val indent: Boolean,
private val caretAfterInsertedText: Boolean
) : VisualOperatorActionHandler.SingleExecution() {
override val type: Command.Type = Command.Type.OTHER_SELF_SYNCHRONIZED override val type: Command.Type = Command.Type.OTHER_SELF_SYNCHRONIZED
override val flags: EnumSet<CommandFlags> = enumSetOf(CommandFlags.FLAG_EXIT_VISUAL) override val flags: EnumSet<CommandFlags> = enumSetOf(CommandFlags.FLAG_EXIT_VISUAL)
override fun executeForAllCarets(editor: Editor,
context: DataContext, override fun executeForAllCarets(editor: Editor, context: DataContext, cmd: Command, caretsAndSelections: Map<Caret, VimSelection>): Boolean {
cmd: Command,
caretsAndSelections: Map<Caret, VimSelection>): Boolean {
if (caretsAndSelections.isEmpty()) return false if (caretsAndSelections.isEmpty()) return false
val textData = VimPlugin.getRegister().lastRegister?.let { PutData.TextData(it.text, it.type, it.transferableData) } val textData = VimPlugin.getRegister().lastRegister?.let { PutData.TextData(it.text, it.type, it.transferableData) }
VimPlugin.getRegister().resetRegister() VimPlugin.getRegister().resetRegister()
val insertTextBeforeCaret = cmd.keys[0].keyChar == 'P'
val selection = PutData.VisualSelection(caretsAndSelections, caretsAndSelections.values.first().type) val selection = PutData.VisualSelection(caretsAndSelections, caretsAndSelections.values.first().type)
val putData = PutData(textData, selection, cmd.count, insertTextBeforeCaret, _indent = true, caretAfterInsertedText = false) val putData = PutData(textData, selection, cmd.count, insertTextBeforeCaret, indent, caretAfterInsertedText)
return VimPlugin.getPut().putText(editor, context, putData) return VimPlugin.getPut().putText(editor, context, putData)
} }
} }
class PutVisualTextBeforeCursorAction: PutVisualTextBaseAction(insertTextBeforeCaret = true, indent = true, caretAfterInsertedText = false)
class PutVisualTextAfterCursorAction: PutVisualTextBaseAction(insertTextBeforeCaret = false, indent = true, caretAfterInsertedText = false)
class PutVisualTextBeforeCursorNoIndentAction: PutVisualTextBaseAction(insertTextBeforeCaret = true, indent = false, caretAfterInsertedText = false)
class PutVisualTextAfterCursorNoIndentAction: PutVisualTextBaseAction(insertTextBeforeCaret = false, indent = false, caretAfterInsertedText = false)
class PutVisualTextBeforeCursorMoveCursorAction: PutVisualTextBaseAction(insertTextBeforeCaret = true, indent = true, caretAfterInsertedText = true)
class PutVisualTextAfterCursorMoveCursorAction: PutVisualTextBaseAction(insertTextBeforeCaret = false, indent = true, caretAfterInsertedText = true)

View File

@@ -1,53 +0,0 @@
/*
* IdeaVim - Vim emulator for IDEs based on the IntelliJ platform
* Copyright (C) 2003-2020 The IdeaVim authors
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package com.maddyhome.idea.vim.action.copy
import com.intellij.openapi.actionSystem.DataContext
import com.intellij.openapi.editor.Caret
import com.intellij.openapi.editor.Editor
import com.maddyhome.idea.vim.VimPlugin
import com.maddyhome.idea.vim.command.Command
import com.maddyhome.idea.vim.command.CommandFlags
import com.maddyhome.idea.vim.group.copy.PutData
import com.maddyhome.idea.vim.group.visual.VimSelection
import com.maddyhome.idea.vim.handler.VisualOperatorActionHandler
import com.maddyhome.idea.vim.helper.enumSetOf
import java.util.*
/**
* @author vlan
*/
class PutVisualTextMoveCursorAction : VisualOperatorActionHandler.SingleExecution() {
override val type: Command.Type = Command.Type.OTHER_SELF_SYNCHRONIZED
override val flags: EnumSet<CommandFlags> = enumSetOf(CommandFlags.FLAG_EXIT_VISUAL)
override fun executeForAllCarets(editor: Editor, context: DataContext, cmd: Command, caretsAndSelections: Map<Caret, VimSelection>): Boolean {
if (caretsAndSelections.isEmpty()) return false
val textData = VimPlugin.getRegister().lastRegister?.let { PutData.TextData(it.text, it.type, it.transferableData) }
VimPlugin.getRegister().resetRegister()
val insertTextBeforeCaret = cmd.keys[1].keyChar == 'P'
val selection = PutData.VisualSelection(caretsAndSelections, caretsAndSelections.values.first().type)
val putData = PutData(textData, selection, cmd.count, insertTextBeforeCaret, _indent = true, caretAfterInsertedText = true)
return VimPlugin.getPut().putText(editor, context, putData)
}
}

View File

@@ -1,53 +0,0 @@
/*
* IdeaVim - Vim emulator for IDEs based on the IntelliJ platform
* Copyright (C) 2003-2020 The IdeaVim authors
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package com.maddyhome.idea.vim.action.copy
import com.intellij.openapi.actionSystem.DataContext
import com.intellij.openapi.editor.Caret
import com.intellij.openapi.editor.Editor
import com.maddyhome.idea.vim.VimPlugin
import com.maddyhome.idea.vim.command.Command
import com.maddyhome.idea.vim.command.CommandFlags
import com.maddyhome.idea.vim.group.copy.PutData
import com.maddyhome.idea.vim.group.visual.VimSelection
import com.maddyhome.idea.vim.handler.VisualOperatorActionHandler
import com.maddyhome.idea.vim.helper.enumSetOf
import java.util.*
/**
* @author vlan
*/
class PutVisualTextNoIndentAction : VisualOperatorActionHandler.SingleExecution() {
override val type: Command.Type = Command.Type.OTHER_SELF_SYNCHRONIZED
override val flags: EnumSet<CommandFlags> = enumSetOf(CommandFlags.FLAG_EXIT_VISUAL)
override fun executeForAllCarets(editor: Editor, context: DataContext, cmd: Command, caretsAndSelections: Map<Caret, VimSelection>): Boolean {
if (caretsAndSelections.isEmpty()) return false
val textData = VimPlugin.getRegister().lastRegister?.let { PutData.TextData(it.text, it.type, it.transferableData) }
VimPlugin.getRegister().resetRegister()
val insertBeforeCaret = cmd.keys[1].keyChar == 'P'
val selection = PutData.VisualSelection(caretsAndSelections, caretsAndSelections.values.first().type)
val putData = PutData(textData, selection, cmd.count, insertBeforeCaret, _indent = false, caretAfterInsertedText = false)
return VimPlugin.getPut().putText(editor, context, putData)
}
}

View File

@@ -1,41 +0,0 @@
/*
* IdeaVim - Vim emulator for IDEs based on the IntelliJ platform
* Copyright (C) 2003-2020 The IdeaVim authors
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package com.maddyhome.idea.vim.action.copy
import com.intellij.openapi.actionSystem.DataContext
import com.intellij.openapi.editor.Editor
import com.maddyhome.idea.vim.VimPlugin
import com.maddyhome.idea.vim.command.Argument
import com.maddyhome.idea.vim.command.Command
import com.maddyhome.idea.vim.command.CommandFlags
import com.maddyhome.idea.vim.handler.VimActionHandler
import com.maddyhome.idea.vim.helper.enumSetOf
import java.util.*
class SelectRegisterAction : VimActionHandler.SingleExecution() {
override val type: Command.Type = Command.Type.SELECT_REGISTER
override val flags: EnumSet<CommandFlags> = enumSetOf(CommandFlags.FLAG_EXPECT_MORE)
override val argumentType: Argument.Type = Argument.Type.CHARACTER
override fun execute(editor: Editor, context: DataContext, cmd: Command): Boolean {
val argument = cmd.argument
return argument != null && VimPlugin.getRegister().selectRegister(argument.character)
}
}

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -164,6 +164,12 @@ public class CommandState {
} }
} }
public void resetRegisterPending() {
if (getSubMode() == SubMode.REGISTER_PENDING) {
popModes();
}
}
private void resetModes() { private void resetModes() {
modeStates.clear(); modeStates.clear();
setMappingMode(); setMappingMode();
@@ -343,7 +349,7 @@ public class CommandState {
} }
public enum SubMode { public enum SubMode {
NONE, SINGLE_COMMAND, OP_PENDING, VISUAL_CHARACTER, VISUAL_LINE, VISUAL_BLOCK NONE, SINGLE_COMMAND, OP_PENDING, REGISTER_PENDING, VISUAL_CHARACTER, VISUAL_LINE, VISUAL_BLOCK
} }
private static class ModeState { private static class ModeState {

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -7,6 +7,7 @@ import com.intellij.openapi.editor.Editor;
import com.maddyhome.idea.vim.VimPlugin; import com.maddyhome.idea.vim.VimPlugin;
import com.maddyhome.idea.vim.command.*; import com.maddyhome.idea.vim.command.*;
import com.maddyhome.idea.vim.common.TextRange; 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.VimExtension;
import com.maddyhome.idea.vim.extension.VimExtensionHandler; import com.maddyhome.idea.vim.extension.VimExtensionHandler;
import com.maddyhome.idea.vim.handler.TextObjectActionHandler; 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. * A text object for an argument to a function definition or a call.
*/ */
@@ -66,7 +178,18 @@ public class VimArgTextObjExtension implements VimExtension {
@Override @Override
public @Nullable TextRange getRange(@NotNull Editor editor, @NotNull Caret caret, @NotNull DataContext context, int count, int rawCount, @Nullable Argument argument) { 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(); int pos = caret.getOffset();
for (int i = 0; i < count; ++i) { for (int i = 0; i < count; ++i) {
@@ -114,9 +237,7 @@ public class VimArgTextObjExtension implements VimExtension {
}); });
} else { } else {
commandState.getCommandBuilder().completeCommandPart(new Argument(new Command(count, commandState.getCommandBuilder().completeCommandPart(new Argument(new Command(count,
textObjectHandler, Command.Type.MOTION, EnumSet.of(CommandFlags.FLAG_MOT_CHARACTERWISE), textObjectHandler, Command.Type.MOTION, EnumSet.of(CommandFlags.FLAG_MOT_CHARACTERWISE))));
emptyList()
)));
} }
} }
} }
@@ -126,8 +247,9 @@ public class VimArgTextObjExtension implements VimExtension {
* position * position
*/ */
private static class ArgBoundsFinder { private static class ArgBoundsFinder {
private final CharSequence text; @NotNull private final CharSequence text;
private final Document document; @NotNull private final Document document;
@NotNull private final BracketPairs brackets;
private int leftBound = Integer.MAX_VALUE; private int leftBound = Integer.MAX_VALUE;
private int rightBound = Integer.MIN_VALUE; private int rightBound = Integer.MIN_VALUE;
private int leftBracket; private int leftBracket;
@@ -135,18 +257,13 @@ public class VimArgTextObjExtension implements VimExtension {
private String error = null; private String error = null;
private static final String QUOTES = "\"'"; 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_LINES = 10;
private static final int MAX_SEARCH_OFFSET = MAX_SEARCH_LINES * 80; 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.text = document.getImmutableCharSequence();
this.document = document; this.document = document;
this.brackets = bracketPairs;
} }
/** /**
@@ -167,7 +284,7 @@ public class VimArgTextObjExtension implements VimExtension {
rightBound = Math.max(position, rightBound); rightBound = Math.max(position, rightBound);
getOutOfQuotedText(); getOutOfQuotedText();
if (rightBound == leftBound) { if (rightBound == leftBound) {
if (isCloseBracket(getCharAt(rightBound))) { if (brackets.isCloseBracket(getCharAt(rightBound))) {
--leftBound; --leftBound;
} else { } else {
++rightBound; ++rightBound;
@@ -196,14 +313,15 @@ public class VimArgTextObjExtension implements VimExtension {
findRightBound(); findRightBound();
nextRight = rightBound + 1; 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"; error = "not an argument";
return false; return false;
} }
bothBrackets = getCharAt(leftBound) != ',' && getCharAt(rightBound) != ','; 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 // Looking at a pair of brackets preceded by an
// identifier -- single argument function call. // identifier -- single argument function call.
break; break;
@@ -257,7 +375,7 @@ public class VimArgTextObjExtension implements VimExtension {
private boolean isIdentPreceding() { private boolean isIdentPreceding() {
int i = leftBound - 1; int i = leftBound - 1;
final int idEnd = i; final int idEnd = i;
while (i > 0 && Character.isJavaIdentifierPart(getCharAt(i))) { while (i >= 0 && Character.isJavaIdentifierPart(getCharAt(i))) {
--i; --i;
} }
return (idEnd - i) > 0 && Character.isJavaIdentifierStart(getCharAt(i + 1)); return (idEnd - i) > 0 && Character.isJavaIdentifierStart(getCharAt(i + 1));
@@ -296,8 +414,8 @@ public class VimArgTextObjExtension implements VimExtension {
if (ch == ',') { if (ch == ',') {
break; break;
} }
if (isOpenBracket(ch)) { if (brackets.isOpenBracket(ch)) {
rightBound = skipSexp(rightBound, rightBracket, SexpDirection.FORWARD); rightBound = skipSexp(rightBound, rightBracket, SexpDirection.forward(brackets));
} else { } else {
if (isQuoteChar(ch)) { if (isQuoteChar(ch)) {
rightBound = skipQuotedTextForward(rightBound, rightBracket); 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() { private void findLeftBound() {
while (leftBound > leftBracket) { while (leftBound > leftBracket) {
final char ch = getCharAt(leftBound); final char ch = getCharAt(leftBound);
if (ch == ',') { if (ch == ',') {
break; break;
} }
if (isCloseBracket(ch)) { if (brackets.isCloseBracket(ch)) {
leftBound = skipSexp(leftBound, leftBracket, SexpDirection.BACKWARD); leftBound = skipSexp(leftBound, leftBracket, SexpDirection.backward(brackets));
} else { } else {
if (isQuoteChar(ch)) { if (isQuoteChar(ch)) {
leftBound = skipQuotedTextBackward(leftBound, leftBracket); leftBound = skipQuotedTextBackward(leftBound, leftBracket);
@@ -365,7 +464,7 @@ public class VimArgTextObjExtension implements VimExtension {
while (i <= end) { while (i <= end) {
final char ch = getCharAt(i); final char ch = getCharAt(i);
if (ch == quoteChar && !backSlash) { if (ch == quoteChar && !backSlash) {
// Found matching quote and it's not escaped. // Found a matching quote, and it's not escaped.
break; break;
} else { } else {
backSlash = ch == '\\' && !backSlash; backSlash = ch == '\\' && !backSlash;
@@ -386,7 +485,7 @@ public class VimArgTextObjExtension implements VimExtension {
// NOTE: doesn't handle cases like \\"str", but they make no // NOTE: doesn't handle cases like \\"str", but they make no
// sense anyway. // sense anyway.
if (ch == quoteChar && prevChar != '\\') { if (ch == quoteChar && prevChar != '\\') {
// Found matching quote and it's not escaped. // Found a matching quote, and it's not escaped.
break; break;
} }
--i; --i;
@@ -424,48 +523,53 @@ public class VimArgTextObjExtension implements VimExtension {
abstract int skipQuotedText(int pos, int end, ArgBoundsFinder self); abstract int skipQuotedText(int pos, int end, ArgBoundsFinder self);
static final SexpDirection FORWARD = new SexpDirection() { static SexpDirection forward(BracketPairs brackets) {
@Override return new SexpDirection() {
int delta() { @Override
return 1; int delta() {
} return 1;
}
@Override @Override
boolean isOpenBracket(char ch) { boolean isOpenBracket(char ch) {
return ArgBoundsFinder.isOpenBracket(ch); return brackets.isOpenBracket(ch);
} }
@Override @Override
boolean isCloseBracket(char ch) { boolean isCloseBracket(char ch) {
return ArgBoundsFinder.isCloseBracket(ch); return brackets.isCloseBracket(ch);
} }
@Override @Override
int skipQuotedText(int pos, int end, ArgBoundsFinder self) { int skipQuotedText(int pos, int end, ArgBoundsFinder self) {
return self.skipQuotedTextForward(pos, end); return self.skipQuotedTextForward(pos, end);
} }
}; };
static final SexpDirection BACKWARD = new SexpDirection() { }
@Override
int delta() {
return -1;
}
@Override static SexpDirection backward(BracketPairs brackets) {
boolean isOpenBracket(char ch) { return new SexpDirection() {
return ArgBoundsFinder.isCloseBracket(ch); @Override
} int delta() {
return -1;
}
@Override @Override
boolean isCloseBracket(char ch) { boolean isOpenBracket(char ch) {
return ArgBoundsFinder.isOpenBracket(ch); return brackets.isCloseBracket(ch);
} }
@Override @Override
int skipQuotedText(int pos, int end, ArgBoundsFinder self) { boolean isCloseBracket(char ch) {
return self.skipQuotedTextBackward(pos, end); 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); bracketStack.push(ch);
} else { } else {
if (dir.isCloseBracket(ch)) { if (dir.isCloseBracket(ch)) {
if (bracketStack.lastElement() == matchingBracket(ch)) { if (bracketStack.lastElement() == brackets.matchingBracket(ch)) {
bracketStack.pop(); bracketStack.pop();
} else { } else {
//noinspection StatementWithEmptyBody //noinspection StatementWithEmptyBody
if (getBracketPrio(ch) < getBracketPrio(bracketStack.lastElement())) { if (brackets.getBracketPrio(ch) < brackets.getBracketPrio(bracketStack.lastElement())) {
// (<...) -> (...) // (<...) -> (...)
bracketStack.pop(); bracketStack.pop();
// Retry the same character again for cases like (...<<...). // 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. * 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 findOuterBrackets(final int start, final int end) {
boolean hasNewBracket = findPrevOpenBracket(start) && findNextCloseBracket(end); boolean hasNewBracket = findPrevOpenBracket(start) && findNextCloseBracket(end);
while (hasNewBracket) { while (hasNewBracket) {
final int leftPrio = getBracketPrio(getCharAt(leftBracket)); final int leftPrio = brackets.getBracketPrio(getCharAt(leftBracket));
final int rightPrio = getBracketPrio(getCharAt(rightBracket)); final int rightPrio = brackets.getBracketPrio(getCharAt(rightBracket));
if (leftPrio == rightPrio) { if (leftPrio == rightPrio) {
// matching brackets // matching brackets
return true; return true;
@@ -569,9 +666,9 @@ public class VimArgTextObjExtension implements VimExtension {
*/ */
private boolean findPrevOpenBracket(final int start) { private boolean findPrevOpenBracket(final int start) {
char ch; char ch;
while (!isOpenBracket(ch = getCharAt(leftBracket))) { while (!brackets.isOpenBracket(ch = getCharAt(leftBracket))) {
if (isCloseBracket(ch)) { if (brackets.isCloseBracket(ch)) {
leftBracket = skipSexp(leftBracket, start, SexpDirection.BACKWARD); leftBracket = skipSexp(leftBracket, start, SexpDirection.backward(brackets));
} else { } else {
if (isQuoteChar(ch)) { if (isQuoteChar(ch)) {
leftBracket = skipQuotedTextBackward(leftBracket, start); leftBracket = skipQuotedTextBackward(leftBracket, start);
@@ -594,9 +691,9 @@ public class VimArgTextObjExtension implements VimExtension {
*/ */
private boolean findNextCloseBracket(final int end) { private boolean findNextCloseBracket(final int end) {
char ch; char ch;
while (!isCloseBracket(ch = getCharAt(rightBracket))) { while (!brackets.isCloseBracket(ch = getCharAt(rightBracket))) {
if (isOpenBracket(ch)) { if (brackets.isOpenBracket(ch)) {
rightBracket = skipSexp(rightBracket, end, SexpDirection.FORWARD); rightBracket = skipSexp(rightBracket, end, SexpDirection.forward(brackets));
} else { } else {
if (isQuoteChar(ch)) { if (isQuoteChar(ch)) {
rightBracket = skipQuotedTextForward(rightBracket, end); rightBracket = skipQuotedTextForward(rightBracket, end);

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 { } else {
commandState.getCommandBuilder().completeCommandPart(new Argument(new Command(count, commandState.getCommandBuilder().completeCommandPart(new Argument(new Command(count,
textObjectHandler, Command.Type.MOTION, textObjectHandler, Command.Type.MOTION,
EnumSet.of(CommandFlags.FLAG_MOT_CHARACTERWISE), EnumSet.of(CommandFlags.FLAG_MOT_CHARACTERWISE))));
emptyList())));
} }
} }
} }

View File

@@ -84,6 +84,8 @@ public class ChangeGroup {
private static final String VIM_MOTION_WORD_END_RIGHT = "VimMotionWordEndRightAction"; private static final String VIM_MOTION_WORD_END_RIGHT = "VimMotionWordEndRightAction";
private static final String VIM_MOTION_BIG_WORD_END_RIGHT = "VimMotionBigWordEndRightAction"; private static final String VIM_MOTION_BIG_WORD_END_RIGHT = "VimMotionBigWordEndRightAction";
private static final String VIM_MOTION_CAMEL_END_RIGHT = "VimMotionCamelEndRightAction"; private static final String VIM_MOTION_CAMEL_END_RIGHT = "VimMotionCamelEndRightAction";
private static final ImmutableSet<String> wordMotions =
ImmutableSet.of(VIM_MOTION_WORD_RIGHT, VIM_MOTION_BIG_WORD_RIGHT, VIM_MOTION_CAMEL_RIGHT);
private @Nullable Command lastInsert; private @Nullable Command lastInsert;
@@ -1223,13 +1225,12 @@ public class ChangeGroup {
boolean bigWord = id.equals(VIM_MOTION_BIG_WORD_RIGHT); boolean bigWord = id.equals(VIM_MOTION_BIG_WORD_RIGHT);
final CharSequence chars = editor.getDocument().getCharsSequence(); final CharSequence chars = editor.getDocument().getCharsSequence();
final int offset = caret.getOffset(); final int offset = caret.getOffset();
if (EditorHelper.getFileSize(editor) > 0) { int fileSize = EditorHelper.getFileSize(editor);
if (fileSize > 0) {
final CharacterHelper.CharacterType charType = CharacterHelper.charType(chars.charAt(offset), bigWord); final CharacterHelper.CharacterType charType = CharacterHelper.charType(chars.charAt(offset), bigWord);
if (charType != CharacterHelper.CharacterType.WHITESPACE) { if (charType != CharacterHelper.CharacterType.WHITESPACE) {
final boolean lastWordChar = offset > EditorHelper.getFileSize(editor) || final boolean lastWordChar = offset >= fileSize - 1 ||
CharacterHelper.charType(chars.charAt(offset + 1), bigWord) != charType; CharacterHelper.charType(chars.charAt(offset + 1), bigWord) != charType;
final ImmutableSet<String> wordMotions =
ImmutableSet.of(VIM_MOTION_WORD_RIGHT, VIM_MOTION_BIG_WORD_RIGHT, VIM_MOTION_CAMEL_RIGHT);
if (wordMotions.contains(id) && lastWordChar && motion.getCount() == 1) { if (wordMotions.contains(id) && lastWordChar && motion.getCount() == 1) {
final boolean res = deleteCharacter(editor, caret, 1, true); final boolean res = deleteCharacter(editor, caret, 1, true);
if (res) { if (res) {
@@ -1258,7 +1259,7 @@ public class ChangeGroup {
} }
if (kludge) { if (kludge) {
int size = EditorHelper.getFileSize(editor); int size = fileSize;
int cnt = count * motion.getCount(); int cnt = count * motion.getCount();
int pos1 = SearchHelper.findNextWordEnd(chars, offset, size, cnt, bigWord, false); int pos1 = SearchHelper.findNextWordEnd(chars, offset, size, cnt, bigWord, false);
int pos2 = SearchHelper.findNextWordEnd(chars, pos1, size, -cnt, bigWord, false); int pos2 = SearchHelper.findNextWordEnd(chars, pos1, size, -cnt, bigWord, false);

View File

@@ -60,7 +60,6 @@ public class DigraphGroup {
final String digraph = keys.get(ch); final String digraph = keys.get(ch);
final String digraphText = digraph == null ? "" : ", Digr " + digraph; final String digraphText = digraph == null ? "" : ", Digr " + digraph;
final String hexText = (ch > 0xff) ? String.format("Hex %04x", (int) ch) : String.format("Hex %02x", (int) ch);
if (ch < 0x100) { if (ch < 0x100) {
VimPlugin.showMessage(String.format("<%s> %d, Hex %02x, Oct %03o%s", VimPlugin.showMessage(String.format("<%s> %d, Hex %02x, Oct %03o%s",

View File

@@ -195,10 +195,11 @@ class NotificationService(private val project: Project?) {
const val IDEAVIM_NOTIFICATION_ID = "ideavim" const val IDEAVIM_NOTIFICATION_ID = "ideavim"
const val IDEAVIM_NOTIFICATION_TITLE = "IdeaVim" const val IDEAVIM_NOTIFICATION_TITLE = "IdeaVim"
const val ideajoinExamplesUrl = "https://github.com/JetBrains/ideavim/wiki/%60ideajoin%60-examples" const val ideajoinExamplesUrl = "https://github.com/JetBrains/ideavim/wiki/%60ideajoin%60-examples"
const val selectModeUrl = "https://vimhelp.org/visual.txt.html#Select-mode"
private fun createIdeaVimRcManually(message: String, project: Project?) { private fun createIdeaVimRcManually(message: String, project: Project?) {
val notification = Notification(IDEAVIM_NOTIFICATION_ID, IDEAVIM_NOTIFICATION_TITLE, message, NotificationType.WARNING) val notification = Notification(IDEAVIM_NOTIFICATION_ID, IDEAVIM_NOTIFICATION_TITLE, message, NotificationType.WARNING)
@Suppress("UnstableApiUsage", "DEPRECATION")
// [VERSION UPDATE] 193+ com.intellij.ide.actions.RevealFileAction.openDirectory
var actionName = if (SystemInfo.isMac) "Reveal Home in Finder" else "Show Home in " + ShowFilePathAction.getFileManagerName() var actionName = if (SystemInfo.isMac) "Reveal Home in Finder" else "Show Home in " + ShowFilePathAction.getFileManagerName()
if (!File(System.getProperty("user.home")).exists()) { if (!File(System.getProperty("user.home")).exists()) {
actionName = "" actionName = ""
@@ -206,6 +207,8 @@ class NotificationService(private val project: Project?) {
notification.addAction(object : AnAction(actionName) { notification.addAction(object : AnAction(actionName) {
override fun actionPerformed(e: AnActionEvent) { override fun actionPerformed(e: AnActionEvent) {
val homeDir = File(System.getProperty("user.home")) val homeDir = File(System.getProperty("user.home"))
@Suppress("DEPRECATION", "UnstableApiUsage")
// [VERSION UPDATE] 193+ com.intellij.ide.actions.RevealFileAction.openDirectory
ShowFilePathAction.openDirectory(homeDir) ShowFilePathAction.openDirectory(homeDir)
notification.expire() notification.expire()
} }

View File

@@ -88,8 +88,8 @@ public class RegisterGroup implements PersistentStateComponent<Element> {
private static final List<Character> CLIPBOARD_REGISTERS = ImmutableList.of('*', '+'); private static final List<Character> CLIPBOARD_REGISTERS = ImmutableList.of('*', '+');
private static final Logger logger = Logger.getInstance(RegisterGroup.class.getName()); private static final Logger logger = Logger.getInstance(RegisterGroup.class.getName());
private char defaultRegister = '"'; public static char DEFAULT_REGISTER = '"';
private char lastRegister = defaultRegister; private char lastRegister = DEFAULT_REGISTER;
private final @NotNull HashMap<Character, Register> registers = new HashMap<>(); private final @NotNull HashMap<Character, Register> registers = new HashMap<>();
private char recordRegister = 0; private char recordRegister = 0;
private @Nullable List<KeyStroke> recordList = null; private @Nullable List<KeyStroke> recordList = null;
@@ -98,15 +98,15 @@ public class RegisterGroup implements PersistentStateComponent<Element> {
final ListOption clipboardOption = OptionsManager.INSTANCE.getClipboard(); final ListOption clipboardOption = OptionsManager.INSTANCE.getClipboard();
clipboardOption.addOptionChangeListenerAndExecute((oldValue, newValue) -> { clipboardOption.addOptionChangeListenerAndExecute((oldValue, newValue) -> {
if (clipboardOption.contains("unnamed")) { if (clipboardOption.contains("unnamed")) {
defaultRegister = '*'; DEFAULT_REGISTER = '*';
} }
else if (clipboardOption.contains("unnamedplus")) { else if (clipboardOption.contains("unnamedplus")) {
defaultRegister = '+'; DEFAULT_REGISTER = '+';
} }
else { else {
defaultRegister = '"'; DEFAULT_REGISTER = '"';
} }
lastRegister = defaultRegister; lastRegister = DEFAULT_REGISTER;
}); });
} }
@@ -117,6 +117,10 @@ public class RegisterGroup implements PersistentStateComponent<Element> {
return READONLY_REGISTERS.indexOf(lastRegister) < 0; return READONLY_REGISTERS.indexOf(lastRegister) < 0;
} }
public boolean isValid(char reg) {
return VALID_REGISTERS.indexOf(reg) != -1;
}
/** /**
* Store which register the user wishes to work with. * Store which register the user wishes to work with.
* *
@@ -124,7 +128,7 @@ public class RegisterGroup implements PersistentStateComponent<Element> {
* @return true if a valid register name, false if not * @return true if a valid register name, false if not
*/ */
public boolean selectRegister(char reg) { public boolean selectRegister(char reg) {
if (VALID_REGISTERS.indexOf(reg) != -1) { if (isValid(reg)) {
lastRegister = reg; lastRegister = reg;
if (logger.isDebugEnabled()) logger.debug("register selected: " + lastRegister); if (logger.isDebugEnabled()) logger.debug("register selected: " + lastRegister);
@@ -139,7 +143,7 @@ public class RegisterGroup implements PersistentStateComponent<Element> {
* Reset the selected register back to the default register. * Reset the selected register back to the default register.
*/ */
public void resetRegister() { public void resetRegister() {
lastRegister = defaultRegister; lastRegister = DEFAULT_REGISTER;
logger.debug("Last register reset to default register"); logger.debug("Last register reset to default register");
} }
@@ -213,8 +217,8 @@ public class RegisterGroup implements PersistentStateComponent<Element> {
} }
// Also add it to the default register if the default wasn't specified // Also add it to the default register if the default wasn't specified
if (register != defaultRegister && ".:/".indexOf(register) == -1) { if (register != DEFAULT_REGISTER && ".:/".indexOf(register) == -1) {
registers.put(defaultRegister, new Register(defaultRegister, type, processedText, new ArrayList<>(transferableData))); registers.put(DEFAULT_REGISTER, new Register(DEFAULT_REGISTER, type, processedText, new ArrayList<>(transferableData)));
if (logger.isDebugEnabled()) logger.debug("register '" + register + "' contains: \"" + processedText + "\""); if (logger.isDebugEnabled()) logger.debug("register '" + register + "' contains: \"" + processedText + "\"");
} }
@@ -223,7 +227,7 @@ public class RegisterGroup implements PersistentStateComponent<Element> {
editor.offsetToLogicalPosition(start).line == editor.offsetToLogicalPosition(end).line; editor.offsetToLogicalPosition(start).line == editor.offsetToLogicalPosition(end).line;
// Deletes go into numbered registers only if text is smaller than a line, register is used or it's a special case // Deletes go into numbered registers only if text is smaller than a line, register is used or it's a special case
if (!smallInlineDeletion || register != defaultRegister || isSmallDeletionSpecialCase(editor)) { if (!smallInlineDeletion || register != DEFAULT_REGISTER || isSmallDeletionSpecialCase(editor)) {
// Old 1 goes to 2, etc. Old 8 to 9, old 9 is lost // Old 1 goes to 2, etc. Old 8 to 9, old 9 is lost
for (char d = '8'; d >= '1'; d--) { for (char d = '8'; d >= '1'; d--) {
Register t = registers.get(d); Register t = registers.get(d);
@@ -236,12 +240,12 @@ public class RegisterGroup implements PersistentStateComponent<Element> {
} }
// Deletes smaller than one line and without specified register go the the "-" register // Deletes smaller than one line and without specified register go the the "-" register
if (smallInlineDeletion && register == defaultRegister) { if (smallInlineDeletion && register == DEFAULT_REGISTER) {
registers.put('-', new Register('-', type, processedText, new ArrayList<>(transferableData))); registers.put('-', new Register('-', type, processedText, new ArrayList<>(transferableData)));
} }
} }
// Yanks also go to register 0 if the default register was used // Yanks also go to register 0 if the default register was used
else if (register == defaultRegister) { else if (register == DEFAULT_REGISTER) {
registers.put('0', new Register('0', type, processedText, new ArrayList<>(transferableData))); registers.put('0', new Register('0', type, processedText, new ArrayList<>(transferableData)));
if (logger.isDebugEnabled()) logger.debug("register '" + '0' + "' contains: \"" + processedText + "\""); if (logger.isDebugEnabled()) logger.debug("register '" + '0' + "' contains: \"" + processedText + "\"");
} }
@@ -343,6 +347,17 @@ public class RegisterGroup implements PersistentStateComponent<Element> {
return CLIPBOARD_REGISTERS.contains(r) ? refreshClipboardRegister(r) : registers.get(r); return CLIPBOARD_REGISTERS.contains(r) ? refreshClipboardRegister(r) : registers.get(r);
} }
public void saveRegister(char r, Register register) {
// Uppercase registers actually get the lowercase register
if (Character.isUpperCase(r)) {
r = Character.toLowerCase(r);
}
if (CLIPBOARD_REGISTERS.contains(r)) {
ClipboardHandler.setClipboardText(register.getText(), new ArrayList<>(register.getTransferableData()), register.getRawText());
}
registers.put(r, register);
}
/** /**
* Gets the last register name selected by the user * Gets the last register name selected by the user
* *
@@ -356,7 +371,7 @@ public class RegisterGroup implements PersistentStateComponent<Element> {
* The register key for the default register. * The register key for the default register.
*/ */
public char getDefaultRegister() { public char getDefaultRegister() {
return defaultRegister; return DEFAULT_REGISTER;
} }
public @NotNull List<Register> getRegisters() { public @NotNull List<Register> getRegisters() {

View File

@@ -27,13 +27,21 @@ import com.intellij.openapi.actionSystem.PlatformDataKeys
import com.intellij.openapi.application.ApplicationManager import com.intellij.openapi.application.ApplicationManager
import com.intellij.openapi.diagnostic.Logger import com.intellij.openapi.diagnostic.Logger
import com.intellij.openapi.diagnostic.debug import com.intellij.openapi.diagnostic.debug
import com.intellij.openapi.editor.* import com.intellij.openapi.editor.Caret
import com.intellij.openapi.editor.CaretStateTransferableData
import com.intellij.openapi.editor.Editor
import com.intellij.openapi.editor.LogicalPosition
import com.intellij.openapi.editor.RangeMarker
import com.intellij.openapi.editor.RawText
import com.intellij.openapi.ide.CopyPasteManager import com.intellij.openapi.ide.CopyPasteManager
import com.intellij.openapi.project.Project import com.intellij.openapi.project.Project
import com.intellij.openapi.util.text.StringUtil import com.intellij.openapi.util.text.StringUtil
import com.maddyhome.idea.vim.VimPlugin import com.maddyhome.idea.vim.VimPlugin
import com.maddyhome.idea.vim.command.CommandState import com.maddyhome.idea.vim.command.CommandState
import com.maddyhome.idea.vim.command.SelectionType import com.maddyhome.idea.vim.command.SelectionType
import com.maddyhome.idea.vim.command.isBlock
import com.maddyhome.idea.vim.command.isChar
import com.maddyhome.idea.vim.command.isLine
import com.maddyhome.idea.vim.common.TextRange import com.maddyhome.idea.vim.common.TextRange
import com.maddyhome.idea.vim.group.MarkGroup import com.maddyhome.idea.vim.group.MarkGroup
import com.maddyhome.idea.vim.group.MotionGroup import com.maddyhome.idea.vim.group.MotionGroup
@@ -96,7 +104,7 @@ class PutGroup {
} }
private fun collectPreModificationData(editor: Editor, data: PutData): Map<String, Any> { private fun collectPreModificationData(editor: Editor, data: PutData): Map<String, Any> {
return if (data.visualSelection != null && data.visualSelection.typeInEditor == SelectionType.BLOCK_WISE) { return if (data.visualSelection != null && data.visualSelection.typeInEditor.isBlock) {
val vimSelection = data.visualSelection.caretsAndSelections.getValue(editor.caretModel.primaryCaret) val vimSelection = data.visualSelection.caretsAndSelections.getValue(editor.caretModel.primaryCaret)
val selStart = editor.offsetToLogicalPosition(vimSelection.vimStart) val selStart = editor.offsetToLogicalPosition(vimSelection.vimStart)
val selEnd = editor.offsetToLogicalPosition(vimSelection.vimEnd) val selEnd = editor.offsetToLogicalPosition(vimSelection.vimEnd)
@@ -132,9 +140,11 @@ class PutGroup {
return null return null
} }
if (data.visualSelection?.typeInEditor == SelectionType.LINE_WISE && data.textData.typeInRegister == SelectionType.CHARACTER_WISE) text += "\n" if (data.visualSelection?.typeInEditor?.isLine == true && data.textData.typeInRegister.isChar) text += "\n"
if (data.textData.typeInRegister == SelectionType.LINE_WISE && text.isNotEmpty() && text.last() != '\n') text += '\n' if (data.textData.typeInRegister.isLine && text.isNotEmpty() && text.last() != '\n') text += '\n'
if (data.textData.typeInRegister.isChar && text.lastOrNull() == '\n' && data.visualSelection?.typeInEditor?.isLine != true) text = text.dropLast(1)
return ProcessedTextData(text, data.textData.typeInRegister, data.textData.transferableData) return ProcessedTextData(text, data.textData.typeInRegister, data.textData.transferableData)
} }
@@ -163,7 +173,7 @@ class PutGroup {
} }
private fun putForCaret(editor: Editor, caret: Caret, data: PutData, additionalData: Map<String, Any>, context: DataContext, text: ProcessedTextData) { private fun putForCaret(editor: Editor, caret: Caret, data: PutData, additionalData: Map<String, Any>, context: DataContext, text: ProcessedTextData) {
if (data.visualSelection?.typeInEditor == SelectionType.LINE_WISE && editor.isOneLineMode) return if (data.visualSelection?.typeInEditor?.isLine == true && editor.isOneLineMode) return
val startOffsets = prepareDocumentAndGetStartOffsets(editor, caret, text.typeInRegister, data, additionalData) val startOffsets = prepareDocumentAndGetStartOffsets(editor, caret, text.typeInRegister, data, additionalData)
startOffsets.forEach { startOffset -> startOffsets.forEach { startOffset ->
@@ -179,11 +189,11 @@ class PutGroup {
val application = ApplicationManager.getApplication() val application = ApplicationManager.getApplication()
if (data.visualSelection != null) { if (data.visualSelection != null) {
return when { return when {
data.visualSelection.typeInEditor == SelectionType.CHARACTER_WISE && typeInRegister == SelectionType.LINE_WISE -> { data.visualSelection.typeInEditor.isChar && typeInRegister.isLine -> {
application.runWriteAction { editor.document.insertString(caret.offset, "\n") } application.runWriteAction { editor.document.insertString(caret.offset, "\n") }
listOf(caret.offset + 1) listOf(caret.offset + 1)
} }
data.visualSelection.typeInEditor == SelectionType.BLOCK_WISE -> { data.visualSelection.typeInEditor.isBlock -> {
val firstSelectedLine = additionalData["firstSelectedLine"] as Int val firstSelectedLine = additionalData["firstSelectedLine"] as Int
val selectedLines = additionalData["selectedLines"] as Int val selectedLines = additionalData["selectedLines"] as Int
val startColumnOfSelection = additionalData["startColumnOfSelection"] as Int val startColumnOfSelection = additionalData["startColumnOfSelection"] as Int
@@ -235,8 +245,8 @@ class PutGroup {
} }
private fun getProviderForPasteViaIde(context: DataContext, typeInRegister: SelectionType, data: PutData): PasteProvider? { private fun getProviderForPasteViaIde(context: DataContext, typeInRegister: SelectionType, data: PutData): PasteProvider? {
if (data.visualSelection != null && data.visualSelection.typeInEditor == SelectionType.BLOCK_WISE) return null if (data.visualSelection != null && data.visualSelection.typeInEditor.isBlock) return null
if ((typeInRegister == SelectionType.LINE_WISE || typeInRegister == SelectionType.CHARACTER_WISE) && data.count == 1) { if ((typeInRegister.isLine || typeInRegister.isChar) && data.count == 1) {
val provider = PlatformDataKeys.PASTE_PROVIDER.getData(context) val provider = PlatformDataKeys.PASTE_PROVIDER.getData(context)
if (provider != null && provider.isPasteEnabled(context)) return provider if (provider != null && provider.isPasteEnabled(context)) return provider
} }

View File

@@ -29,17 +29,35 @@ import com.maddyhome.idea.vim.command.Command
import com.maddyhome.idea.vim.helper.vimChangeActionSwitchMode import com.maddyhome.idea.vim.helper.vimChangeActionSwitchMode
import com.maddyhome.idea.vim.helper.vimLastColumn import com.maddyhome.idea.vim.helper.vimLastColumn
sealed class ChangeEditorActionHandler : VimActionHandler.SingleExecution() { /**
* Base handler for commands that performs change actions.
* This handler stores the commands and they can be repeated later with dot command.
*
* Use subclasses of this handler:
* - [ChangeEditorActionHandler.SingleExecution]
* - [ChangeEditorActionHandler.ForEachCaret]
*/
sealed class ChangeEditorActionHandler : EditorActionHandlerBase(false) {
/**
* This handler executes an action for each caret. That means that if you have 5 carets, [execute] will be
* called 5 times.
* @see [ChangeEditorActionHandler.SingleExecution] for only one execution.
*/
abstract class ForEachCaret : ChangeEditorActionHandler() { abstract class ForEachCaret : ChangeEditorActionHandler() {
abstract fun execute(editor: Editor, caret: Caret, context: DataContext, count: Int, rawCount: Int, argument: Argument?): Boolean abstract fun execute(editor: Editor, caret: Caret, context: DataContext, count: Int, rawCount: Int, argument: Argument?): Boolean
} }
/**
* This handler executes an action only once for all carets. That means that if you have 5 carets,
* [execute] will be called 1 time.
* @see [ChangeEditorActionHandler.ForEachCaret] for per-caret execution
*/
abstract class SingleExecution : ChangeEditorActionHandler() { abstract class SingleExecution : ChangeEditorActionHandler() {
abstract fun execute(editor: Editor, context: DataContext, count: Int, rawCount: Int, argument: Argument?): Boolean abstract fun execute(editor: Editor, context: DataContext, count: Int, rawCount: Int, argument: Argument?): Boolean
} }
final override fun execute(editor: Editor, context: DataContext, cmd: Command): Boolean { final override fun baseExecute(editor: Editor, caret: Caret, context: DataContext, cmd: Command): Boolean {
// Here we have to save the last changed command. This should be done separately for each // Here we have to save the last changed command. This should be done separately for each
// call of the task, not for each caret. Currently there is no way to schedule any action // call of the task, not for each caret. Currently there is no way to schedule any action
// to be worked after each task. So here we override the deprecated execute function which // to be worked after each task. So here we override the deprecated execute function which
@@ -50,9 +68,9 @@ sealed class ChangeEditorActionHandler : VimActionHandler.SingleExecution() {
val worked = Ref.create(true) val worked = Ref.create(true)
when (this) { when (this) {
is ForEachCaret -> { is ForEachCaret -> {
editor.caretModel.runForEachCaret({ caret -> editor.caretModel.runForEachCaret({ current ->
if (!caret.isValid) return@runForEachCaret if (!current.isValid) return@runForEachCaret
if (!execute(editor, caret, context, cmd.count, cmd.rawCount, cmd.argument)) { if (!execute(editor, current, context, cmd.count, cmd.rawCount, cmd.argument)) {
worked.set(false) worked.set(false)
} }
}, true) }, true)

View File

@@ -36,52 +36,22 @@ import java.util.*
import javax.swing.KeyStroke import javax.swing.KeyStroke
/** /**
* Structure of handlers * All the commands in IdeaVim should implement one of the following handlers and be registered in VimActions.xml
* `~` - this symbol means that this handler cannot be used directly (only its children) * Check the KtDocs of handlers for the details.
* Almost each handler isn't usable by itself and has two children - "SingleExecution" and "ForEachCaret"
* which should be used
* *
* ~ EditorActionHandlerBase ~ * Structure of handlers:
* |
* ----------------------------------------------------------------------------
* | | |
* ~ ForEachCaret ~ ~ SingleExecution ~ ~ VimActionHandler ~
* | | / \
* TextObjectActionHandler MotionActionHandler / \
* SingleExecution ForEachCaret
* |
* -------------------------------------------------------------
* | |
* ~ ChangeEditorActionHandler ~ ~ VisualOperatorActionHandler ~
* / \ / \
* SingleExecution ForEachCaret SingleExecution ForEachCaret
* *
* - [EditorActionHandlerBase]: Base handler for all handlers. Please don't use it directly.
* - [VimActionHandler]: .............. Common vim commands.. E.g.: u, <C-W>s, <C-D>.
* - [TextObjectActionHandler]: ....... Text objects. ....... E.g.: iw, a(, i>
* - [MotionActionHandler]: ........... Motion commands. .... E.g.: k, w, <Up>
* - [ChangeEditorActionHandler]: ..... Change commands. .... E.g.: s, r, gU
* - [VisualOperatorActionHandler]: ... Visual commands.
* *
* SpecialKeyHandlers are not presented here because these handlers are created to a limited set of commands and they * SpecialKeyHandlers are not presented here because these handlers are created to a limited set of commands and they
* are already implemented * are already implemented.
*/ */
abstract class EditorActionHandlerBase(private val myRunForEachCaret: Boolean) {
/**
* Handler for common usage
*/
sealed class VimActionHandler(myRunForEachCaret: Boolean) : EditorActionHandlerBase(myRunForEachCaret) {
abstract class ForEachCaret : VimActionHandler(true) {
abstract fun execute(editor: Editor, caret: Caret, context: DataContext, cmd: Command): Boolean
}
abstract class SingleExecution : VimActionHandler(false) {
abstract fun execute(editor: Editor, context: DataContext, cmd: Command): Boolean
}
final override fun baseExecute(editor: Editor, caret: Caret?, context: DataContext, cmd: Command): Boolean {
return when (this) {
is ForEachCaret -> caret == null || execute(editor, caret, context, cmd)
is SingleExecution -> execute(editor, context, cmd)
}
}
}
sealed class EditorActionHandlerBase(private val myRunForEachCaret: Boolean) {
val id: String = getActionId(this::class.java.name) val id: String = getActionId(this::class.java.name)
abstract val type: Command.Type abstract val type: Command.Type
@@ -97,25 +67,7 @@ sealed class EditorActionHandlerBase(private val myRunForEachCaret: Boolean) {
*/ */
open val flags: EnumSet<CommandFlags> = noneOfEnum() open val flags: EnumSet<CommandFlags> = noneOfEnum()
abstract fun baseExecute(editor: Editor, caret: Caret, context: DataContext, cmd: Command): Boolean
abstract class ForEachCaret : EditorActionHandlerBase(true) {
abstract fun execute(editor: Editor, caret: Caret, context: DataContext, cmd: Command): Boolean
final override fun baseExecute(editor: Editor, caret: Caret?, context: DataContext, cmd: Command): Boolean {
if (caret == null) return false
return execute(editor, caret, context, cmd)
}
}
abstract class SingleExecution : EditorActionHandlerBase(false) {
abstract fun execute(editor: Editor, context: DataContext, cmd: Command): Boolean
final override fun baseExecute(editor: Editor, caret: Caret?, context: DataContext, cmd: Command): Boolean {
return execute(editor, context, cmd)
}
}
abstract fun baseExecute(editor: Editor, caret: Caret?, context: DataContext, cmd: Command): Boolean
fun execute(editor: Editor, context: DataContext) { fun execute(editor: Editor, context: DataContext) {
val hostEditor: Editor = CommonDataKeys.HOST_EDITOR.getData(context) ?: editor val hostEditor: Editor = CommonDataKeys.HOST_EDITOR.getData(context) ?: editor

View File

@@ -42,7 +42,7 @@ import com.maddyhome.idea.vim.helper.vimSelectionStart
* Base class for motion handlers. * Base class for motion handlers.
* @see [MotionActionHandler.SingleExecution] and [MotionActionHandler.ForEachCaret] * @see [MotionActionHandler.SingleExecution] and [MotionActionHandler.ForEachCaret]
*/ */
sealed class MotionActionHandler : EditorActionHandlerBase.SingleExecution() { sealed class MotionActionHandler : EditorActionHandlerBase(false) {
/** /**
* Base class for motion handlers. * Base class for motion handlers.
@@ -127,7 +127,7 @@ sealed class MotionActionHandler : EditorActionHandlerBase.SingleExecution() {
} }
} }
final override fun execute(editor: Editor, context: DataContext, cmd: Command): Boolean { final override fun baseExecute(editor: Editor, caret: Caret, context: DataContext, cmd: Command): Boolean {
val blockSubmodeActive = editor.inBlockSubMode val blockSubmodeActive = editor.inBlockSubMode
when (this) { when (this) {

View File

@@ -41,10 +41,16 @@ import com.maddyhome.idea.vim.helper.vimSelectionStart
* *
* This handler gets executed for each caret. * This handler gets executed for each caret.
*/ */
abstract class TextObjectActionHandler : EditorActionHandlerBase.ForEachCaret() { abstract class TextObjectActionHandler : EditorActionHandlerBase(true) {
final override val type: Command.Type = Command.Type.MOTION final override val type: Command.Type = Command.Type.MOTION
abstract fun getRange(editor: Editor, caret: Caret, context: DataContext, count: Int, rawCount: Int, argument: Argument?): TextRange? abstract fun getRange(editor: Editor, caret: Caret, context: DataContext, count: Int, rawCount: Int, argument: Argument?): TextRange?
override fun execute(editor: Editor, caret: Caret, context: DataContext, cmd: Command): Boolean {
/**
* This code is called when user executes text object in visual mode. E.g. `va(a(a(`
*/
final override fun baseExecute(editor: Editor, caret: Caret, context: DataContext, cmd: Command): Boolean {
if (!editor.inVisualMode) return true if (!editor.inVisualMode) return true
val range = getRange(editor, caret, context, cmd.count, cmd.rawCount, cmd.argument) ?: return false val range = getRange(editor, caret, context, cmd.count, cmd.rawCount, cmd.argument) ?: return false

View File

@@ -0,0 +1,58 @@
/*
* 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.handler
import com.intellij.openapi.actionSystem.DataContext
import com.intellij.openapi.editor.Caret
import com.intellij.openapi.editor.Editor
import com.maddyhome.idea.vim.command.Command
/**
* Handler for common usage.
*
* Use subclasses of this handler:
* - [VimActionHandler.SingleExecution]
* - [VimActionHandler.ForEachCaret]
*/
sealed class VimActionHandler(myRunForEachCaret: Boolean) : EditorActionHandlerBase(myRunForEachCaret) {
/**
* This handler executes an action for each caret. That means that if you have 5 carets,
* [execute] will be called 5 times.
* @see [VimActionHandler.SingleExecution] for only one execution.
*/
abstract class ForEachCaret : VimActionHandler(true) {
abstract fun execute(editor: Editor, caret: Caret, context: DataContext, cmd: Command): Boolean
}
/**
* This handler executes an action only once for all carets. That means that if you have 5 carets,
* [execute] will be called 1 time.
* @see [VimActionHandler.ForEachCaret] for per-caret execution.
*/
abstract class SingleExecution : VimActionHandler(false) {
abstract fun execute(editor: Editor, context: DataContext, cmd: Command): Boolean
}
final override fun baseExecute(editor: Editor, caret: Caret, context: DataContext, cmd: Command): Boolean {
return when (this) {
is ForEachCaret -> execute(editor, caret, context, cmd)
is SingleExecution -> execute(editor, context, cmd)
}
}
}

View File

@@ -52,14 +52,17 @@ import com.maddyhome.idea.vim.helper.vimSelectionStart
* @author Alex Plate * @author Alex Plate
* *
* Base class for visual operation handlers. * Base class for visual operation handlers.
* @see [VisualOperatorActionHandler.SingleExecution] and [VisualOperatorActionHandler.ForEachCaret] *
* Use subclasses of this handler:
* - [VisualOperatorActionHandler.SingleExecution]
* - [VisualOperatorActionHandler.ForEachCaret]
*/ */
sealed class VisualOperatorActionHandler : VimActionHandler.SingleExecution() { sealed class VisualOperatorActionHandler : EditorActionHandlerBase(false) {
/** /**
* Base class for visual operation handlers. * Base class for visual operation handlers.
* This handler executes an action for each caret. That means that if you have 5 carets, * This handler executes an action for each caret. That means that if you have 5 carets,
* [executeAction] will be called 5 times. * [executeAction] will be called 5 times.
* @see [VisualOperatorActionHandler.SingleExecution] for only one execution * @see [VisualOperatorActionHandler.SingleExecution] for only one execution.
*/ */
abstract class ForEachCaret : VisualOperatorActionHandler() { abstract class ForEachCaret : VisualOperatorActionHandler() {
@@ -90,7 +93,7 @@ sealed class VisualOperatorActionHandler : VimActionHandler.SingleExecution() {
/** /**
* Base class for visual operation handlers. * Base class for visual operation handlers.
* This handler executes an action only once for all carets. That means that if you have 5 carets, * This handler executes an action only once for all carets. That means that if you have 5 carets,
* [executeAction] will be called 1 time. * [executeForAllCarets] will be called 1 time.
* @see [VisualOperatorActionHandler.ForEachCaret] for per-caret execution * @see [VisualOperatorActionHandler.ForEachCaret] for per-caret execution
*/ */
abstract class SingleExecution : VisualOperatorActionHandler() { abstract class SingleExecution : VisualOperatorActionHandler() {
@@ -104,7 +107,7 @@ sealed class VisualOperatorActionHandler : VimActionHandler.SingleExecution() {
abstract fun executeForAllCarets(editor: Editor, context: DataContext, cmd: Command, caretsAndSelections: Map<Caret, VimSelection>): Boolean abstract fun executeForAllCarets(editor: Editor, context: DataContext, cmd: Command, caretsAndSelections: Map<Caret, VimSelection>): Boolean
} }
final override fun execute(editor: Editor, context: DataContext, cmd: Command): Boolean { final override fun baseExecute(editor: Editor, caret: Caret, context: DataContext, cmd: Command): Boolean {
logger.info("Execute visual command $cmd") logger.info("Execute visual command $cmd")
editor.vimChangeActionSwitchMode = null editor.vimChangeActionSwitchMode = null
@@ -131,9 +134,9 @@ sealed class VisualOperatorActionHandler : VimActionHandler.SingleExecution() {
when { when {
selections.keys.isEmpty() -> return false selections.keys.isEmpty() -> return false
selections.keys.size == 1 -> res.set(executeAction(editor, selections.keys.first(), context, cmd, selections.values.first())) selections.keys.size == 1 -> res.set(executeAction(editor, selections.keys.first(), context, cmd, selections.values.first()))
else -> editor.caretModel.runForEachCaret({ caret -> else -> editor.caretModel.runForEachCaret({ currentCaret ->
val range = selections.getValue(caret) val range = selections.getValue(currentCaret)
val loopRes = executeAction(editor, caret, context, cmd, range) val loopRes = executeAction(editor, currentCaret, context, cmd, range)
res.set(loopRes and res.get()) res.set(loopRes and res.get())
}, true) }, true)
} }

View File

@@ -18,6 +18,7 @@
package com.maddyhome.idea.vim.helper; package com.maddyhome.idea.vim.helper;
import com.intellij.openapi.application.ApplicationManager;
import com.intellij.openapi.diagnostic.Logger; import com.intellij.openapi.diagnostic.Logger;
import com.intellij.openapi.editor.Editor; import com.intellij.openapi.editor.Editor;
import com.maddyhome.idea.vim.VimPlugin; import com.maddyhome.idea.vim.VimPlugin;
@@ -32,11 +33,11 @@ public class DigraphSequence {
} }
public static boolean isDigraphStart(@NotNull KeyStroke key) { public static boolean isDigraphStart(@NotNull KeyStroke key) {
return key.getKeyCode() == KeyEvent.VK_K && (key.getModifiers() & KeyEvent.CTRL_MASK) != 0; return key.getKeyCode() == KeyEvent.VK_K && (key.getModifiers() & KeyEvent.CTRL_DOWN_MASK) != 0;
} }
public static boolean isLiteralStart(@NotNull KeyStroke key) { public static boolean isLiteralStart(@NotNull KeyStroke key) {
return (key.getKeyCode() == KeyEvent.VK_V || key.getKeyCode() == KeyEvent.VK_Q) && (key.getModifiers() & KeyEvent.CTRL_MASK) != 0; return (key.getKeyCode() == KeyEvent.VK_V || key.getKeyCode() == KeyEvent.VK_Q) && (key.getModifiers() & KeyEvent.CTRL_DOWN_MASK) != 0;
} }
public DigraphResult startDigraphSequence() { public DigraphResult startDigraphSequence() {
@@ -201,7 +202,11 @@ public class DigraphSequence {
digraphState = DIG_STATE_PENDING; digraphState = DIG_STATE_PENDING;
KeyStroke code = KeyStroke.getKeyStroke((char)val); KeyStroke code = KeyStroke.getKeyStroke((char)val);
VimPlugin.getMacro().postKey(key, editor); if (!ApplicationManager.getApplication().isUnitTestMode()) {
// The key we received isn't part of the literal, so post it to be handled after we've handled the literal.
// This requires swing, so we can't run it in tests.
VimPlugin.getMacro().postKey(key, editor);
}
return DigraphResult.done(code); return DigraphResult.done(code);
} }

View File

@@ -107,7 +107,10 @@ public class SearchHelper {
return findBlockLocation(chars, found, match, dir, pos, count, false); return findBlockLocation(chars, found, match, dir, pos, count, false);
} }
public static @Nullable TextRange findBlockRange(@NotNull Editor editor, @NotNull Caret caret, char type, int count, public static @Nullable TextRange findBlockRange(@NotNull Editor editor,
@NotNull Caret caret,
char type,
int count,
boolean isOuter) { boolean isOuter) {
CharSequence chars = editor.getDocument().getCharsSequence(); CharSequence chars = editor.getDocument().getCharsSequence();
int pos = caret.getOffset(); int pos = caret.getOffset();
@@ -119,15 +122,19 @@ public class SearchHelper {
boolean rangeSelection = end - start > 1; boolean rangeSelection = end - start > 1;
if (rangeSelection && start == 0) // early return not only for optimization if (rangeSelection && start == 0) // early return not only for optimization
{
return null; // but also not to break the interval semantic on this edge case (see below) return null; // but also not to break the interval semantic on this edge case (see below)
}
/* In case of successive inner selection. We want to break out of /* In case of successive inner selection. We want to break out of
* the block delimiter of the current inner selection. * the block delimiter of the current inner selection.
* In other terms, for the rest of the algorithm, a previous inner selection of a block * In other terms, for the rest of the algorithm, a previous inner selection of a block
* if equivalent to an outer one. */ * if equivalent to an outer one. */
if (!isOuter if (!isOuter &&
&& (start - 1) >= 0 && type == chars.charAt(start - 1) (start - 1) >= 0 &&
&& end < chars.length() && close == chars.charAt(end)) { type == chars.charAt(start - 1) &&
end < chars.length() &&
close == chars.charAt(end)) {
start = start - 1; start = start - 1;
pos = start; pos = start;
rangeSelection = true; rangeSelection = true;
@@ -136,8 +143,7 @@ public class SearchHelper {
/* when one char is selected, we want to find the enclosing block of (start,end] /* when one char is selected, we want to find the enclosing block of (start,end]
* although when a range of characters is selected, we want the enclosing block of [start, end] * although when a range of characters is selected, we want the enclosing block of [start, end]
* shifting the position allow to express which kind of interval we work on */ * shifting the position allow to express which kind of interval we work on */
if (rangeSelection) if (rangeSelection) pos = Math.max(0, start - 1);
pos = Math.max(0, start - 1);
boolean initialPosIsInString = checkInString(chars, pos, true); boolean initialPosIsInString = checkInString(chars, pos, true);
@@ -203,7 +209,9 @@ public class SearchHelper {
return new TextRange(bstart, bend + 1); return new TextRange(bstart, bend + 1);
} }
private static int findMatchingBlockCommentPair(@NotNull PsiComment comment, int pos, @Nullable String prefix, private static int findMatchingBlockCommentPair(@NotNull PsiComment comment,
int pos,
@Nullable String prefix,
@Nullable String suffix) { @Nullable String suffix) {
if (prefix != null && suffix != null) { if (prefix != null && suffix != null) {
// TODO: Try to get rid of `getText()` because it takes a lot of time to calculate the string // TODO: Try to get rid of `getText()` because it takes a lot of time to calculate the string
@@ -262,7 +270,7 @@ public class SearchHelper {
// To handle the case where visual mode allows the user to go past the end of the line, // To handle the case where visual mode allows the user to go past the end of the line,
// which will prevent loc from finding a pairable character below // which will prevent loc from finding a pairable character below
if(pos > 0 && pos == end) { if (pos > 0 && pos == end) {
pos = end - 1; pos = end - 1;
} }
@@ -324,7 +332,7 @@ public class SearchHelper {
// Search to start or end of file, as appropriate // Search to start or end of file, as appropriate
Set<Character> charsToSearch = new HashSet<>(Arrays.asList('\'', '"', '\n', match, found)); Set<Character> charsToSearch = new HashSet<>(Arrays.asList('\'', '"', '\n', match, found));
while (pos >= 0 && pos < chars.length() && cnt > 0) { while (pos >= 0 && pos < chars.length() && cnt > 0) {
Pair<Character, Integer> ci = findPositionOfFirstCharacter(chars, pos, charsToSearch, false, dir); Pair<Character, Integer> ci = findPositionOfFirstCharacter(chars, pos, charsToSearch, false, dir);
if (ci == null) { if (ci == null) {
return -1; return -1;
} }
@@ -421,13 +429,11 @@ public class SearchHelper {
return cnt; return cnt;
} }
public static @Nullable Pair<Character, Integer> findPositionOfFirstCharacter( public static @Nullable Pair<Character, Integer> findPositionOfFirstCharacter(@NotNull CharSequence chars,
@NotNull CharSequence chars, int pos,
int pos, final Set<Character> needles,
final Set<Character> needles, boolean searchEscaped,
boolean searchEscaped, @NotNull Direction direction) {
@NotNull Direction direction
) {
int dir = direction.toInt(); int dir = direction.toInt();
while (pos >= 0 && pos < chars.length()) { while (pos >= 0 && pos < chars.length()) {
final char c = chars.charAt(pos); final char c = chars.charAt(pos);
@@ -439,8 +445,12 @@ public class SearchHelper {
return null; return null;
} }
private static int findCharacterPosition(@NotNull CharSequence chars, int pos, final char c, boolean currentLineOnly, private static int findCharacterPosition(@NotNull CharSequence chars,
boolean searchEscaped, @NotNull Direction direction) { int pos,
final char c,
boolean currentLineOnly,
boolean searchEscaped,
@NotNull Direction direction) {
while (pos >= 0 && pos < chars.length() && (!currentLineOnly || chars.charAt(pos) != '\n')) { while (pos >= 0 && pos < chars.length() && (!currentLineOnly || chars.charAt(pos) != '\n')) {
if (chars.charAt(pos) == c && (pos == 0 || searchEscaped || isQuoteWithoutEscape(chars, pos, c))) { if (chars.charAt(pos) == c && (pos == 0 || searchEscaped || isQuoteWithoutEscape(chars, pos, c))) {
return pos; return pos;
@@ -450,7 +460,9 @@ public class SearchHelper {
return -1; return -1;
} }
/** returns new position which ignore whitespaces at beginning of the line*/ /**
* returns new position which ignore whitespaces at beginning of the line
*/
private static int ignoreWhitespaceAtLineStart(CharSequence seq, int lineStart, int pos) { private static int ignoreWhitespaceAtLineStart(CharSequence seq, int lineStart, int pos) {
if (seq.subSequence(lineStart, pos).chars().allMatch(Character::isWhitespace)) { if (seq.subSequence(lineStart, pos).chars().allMatch(Character::isWhitespace)) {
while (pos < seq.length() && seq.charAt(pos) != '\n' && Character.isWhitespace(seq.charAt(pos))) { while (pos < seq.length() && seq.charAt(pos) != '\n' && Character.isWhitespace(seq.charAt(pos))) {
@@ -461,7 +473,10 @@ public class SearchHelper {
} }
public static @Nullable TextRange findBlockTagRange(@NotNull Editor editor, @NotNull Caret caret, int count, boolean isOuter) { public static @Nullable TextRange findBlockTagRange(@NotNull Editor editor,
@NotNull Caret caret,
int count,
boolean isOuter) {
final int position = caret.getOffset(); final int position = caret.getOffset();
final CharSequence sequence = editor.getDocument().getCharsSequence(); final CharSequence sequence = editor.getDocument().getCharsSequence();
@@ -475,20 +490,21 @@ public class SearchHelper {
final int line = caret.getLogicalPosition().line; final int line = caret.getLogicalPosition().line;
final int lineBegin = editor.getDocument().getLineStartOffset(line); final int lineBegin = editor.getDocument().getLineStartOffset(line);
searchStartPosition = ignoreWhitespaceAtLineStart(sequence, lineBegin, position); searchStartPosition = ignoreWhitespaceAtLineStart(sequence, lineBegin, position);
} else { }
else {
searchStartPosition = selectionEnd; searchStartPosition = selectionEnd;
} }
if (isInHTMLTag(sequence, searchStartPosition, false)) { if (isInHTMLTag(sequence, searchStartPosition, false)) {
// caret is inside opening tag. Move to closing '>'. // caret is inside opening tag. Move to closing '>'.
while (searchStartPosition < sequence.length() && sequence.charAt(searchStartPosition) != '>') { while (searchStartPosition < sequence.length() && sequence.charAt(searchStartPosition) != '>') {
searchStartPosition ++; searchStartPosition++;
} }
} }
else if (isInHTMLTag(sequence, searchStartPosition, true)) { else if (isInHTMLTag(sequence, searchStartPosition, true)) {
// caret is inside closing tag. Move to starting '<'. // caret is inside closing tag. Move to starting '<'.
while (searchStartPosition > 0 && sequence.charAt(searchStartPosition) != '<') { while (searchStartPosition > 0 && sequence.charAt(searchStartPosition) != '<') {
searchStartPosition --; searchStartPosition--;
} }
} }
@@ -514,22 +530,25 @@ public class SearchHelper {
int selectionEndWithoutNewline = selectionEnd; int selectionEndWithoutNewline = selectionEnd;
while (selectionEndWithoutNewline < sequence.length() && sequence.charAt(selectionEndWithoutNewline) == '\n') { while (selectionEndWithoutNewline < sequence.length() && sequence.charAt(selectionEndWithoutNewline) == '\n') {
selectionEndWithoutNewline ++; selectionEndWithoutNewline++;
} }
if (closingTagTextRange.getStartOffset() == selectionEndWithoutNewline && openingTag.getEndOffset() == selectionStart) { if (closingTagTextRange.getStartOffset() == selectionEndWithoutNewline &&
openingTag.getEndOffset() == selectionStart) {
// Special case: if the inner tag is already selected we should like isOuter is active // Special case: if the inner tag is already selected we should like isOuter is active
// Note that we need to ignore newlines, because their selection is lost between multiple "it" invocations // Note that we need to ignore newlines, because their selection is lost between multiple "it" invocations
isOuter = true; isOuter = true;
} else }
if (openingTag.getEndOffset() == closingTagTextRange.getStartOffset() && selectionStart == openingTag.getEndOffset()) { else if (openingTag.getEndOffset() == closingTagTextRange.getStartOffset() &&
selectionStart == openingTag.getEndOffset()) {
// Special case: for an empty tag pair (e.g. <a></a>) the whole tag is selected if the caret is in the middle. // Special case: for an empty tag pair (e.g. <a></a>) the whole tag is selected if the caret is in the middle.
isOuter = true; isOuter = true;
} }
if (isOuter) { if (isOuter) {
return new TextRange(openingTag.getStartOffset(), closingTagTextRange.getEndOffset()); return new TextRange(openingTag.getStartOffset(), closingTagTextRange.getEndOffset());
} else { }
else {
return new TextRange(openingTag.getEndOffset(), closingTagTextRange.getStartOffset()); return new TextRange(openingTag.getEndOffset(), closingTagTextRange.getStartOffset());
} }
} }
@@ -570,7 +589,9 @@ public class SearchHelper {
return closingBracket != -1 && sequence.charAt(closingBracket - 1) != '/'; return closingBracket != -1 && sequence.charAt(closingBracket - 1) != '/';
} }
private static @Nullable Pair<TextRange,String> findUnmatchedClosingTag(final @NotNull CharSequence sequence, final int position, int count) { private static @Nullable Pair<TextRange, String> findUnmatchedClosingTag(final @NotNull CharSequence sequence,
final int position,
int count) {
// The tag name may contain any characters except slashes, whitespace and '>' // The tag name may contain any characters except slashes, whitespace and '>'
final String tagNamePattern = "([^/\\s>]+)"; final String tagNamePattern = "([^/\\s>]+)";
// An opening tag consists of '<' followed by a tag name, optionally some additional text after whitespace and a '>' // An opening tag consists of '<' followed by a tag name, optionally some additional text after whitespace and a '>'
@@ -592,13 +613,16 @@ public class SearchHelper {
if (openTags.isEmpty()) { if (openTags.isEmpty()) {
if (count <= 1) { if (count <= 1) {
return Pair.create(new TextRange(position + matcher.start(), position + matcher.end()), tagName); return Pair.create(new TextRange(position + matcher.start(), position + matcher.end()), tagName);
} else { }
else {
count--; count--;
} }
} else { }
else {
openTags.pop(); openTags.pop();
} }
} else { }
else {
final String tagName = matcher.group(1); final String tagName = matcher.group(1);
openTags.push(tagName); openTags.push(tagName);
} }
@@ -606,14 +630,23 @@ public class SearchHelper {
return null; return null;
} }
private static @Nullable TextRange findUnmatchedOpeningTag(@NotNull CharSequence sequence, int position, @NotNull String tagName) { private static @Nullable TextRange findUnmatchedOpeningTag(@NotNull CharSequence sequence,
int position,
@NotNull String tagName) {
final String quotedTagName = Pattern.quote(tagName); final String quotedTagName = Pattern.quote(tagName);
final String patternString = "(</%s>)" // match closing tags final String patternString = "(</%s>)"
+ "|(<%s" // or opening tags starting with tagName // match closing tags
+ "(\\s([^>]*" // After at least one whitespace there might be additional text in the tag. E.g. <html lang="en"> +
+ "[^/])?)?>)"; // Slash is not allowed as last character (this would be a self closing tag). "|(<%s"
final Pattern tagPattern = Pattern.compile(String.format(patternString, quotedTagName, quotedTagName), Pattern.CASE_INSENSITIVE); // or opening tags starting with tagName
final Matcher matcher = tagPattern.matcher(sequence.subSequence(0, position+1)); +
"(\\s([^>]*"
// After at least one whitespace there might be additional text in the tag. E.g. <html lang="en">
+
"[^/])?)?>)"; // Slash is not allowed as last character (this would be a self closing tag).
final Pattern tagPattern =
Pattern.compile(String.format(patternString, quotedTagName, quotedTagName), Pattern.CASE_INSENSITIVE);
final Matcher matcher = tagPattern.matcher(sequence.subSequence(0, position + 1));
final Stack<TextRange> openTags = new Stack<>(); final Stack<TextRange> openTags = new Stack<>();
while (matcher.find()) { while (matcher.find()) {
@@ -630,13 +663,16 @@ public class SearchHelper {
if (openTags.isEmpty()) { if (openTags.isEmpty()) {
return null; return null;
} else { }
else {
return openTags.pop(); return openTags.pop();
} }
} }
public static @Nullable TextRange findBlockQuoteInLineRange(@NotNull Editor editor, @NotNull Caret caret, char quote, public static @Nullable TextRange findBlockQuoteInLineRange(@NotNull Editor editor,
@NotNull Caret caret,
char quote,
boolean isOuter) { boolean isOuter) {
final CharSequence chars = editor.getDocument().getCharsSequence(); final CharSequence chars = editor.getDocument().getCharsSequence();
final int pos = caret.getOffset(); final int pos = caret.getOffset();
@@ -832,7 +868,11 @@ public class SearchHelper {
return findNextWord(chars, searchFrom, size, count, bigWord, false); return findNextWord(chars, searchFrom, size, count, bigWord, false);
} }
public static int findNextWord(@NotNull CharSequence chars, int pos, int size, int count, boolean bigWord, public static int findNextWord(@NotNull CharSequence chars,
int pos,
int size,
int count,
boolean bigWord,
boolean spaceWords) { boolean spaceWords) {
int step = count >= 0 ? 1 : -1; int step = count >= 0 ? 1 : -1;
count = Math.abs(count); count = Math.abs(count);
@@ -848,7 +888,11 @@ public class SearchHelper {
return res; return res;
} }
private static int findNextWordOne(@NotNull CharSequence chars, int pos, int size, int step, boolean bigWord, private static int findNextWordOne(@NotNull CharSequence chars,
int pos,
int size,
int step,
boolean bigWord,
boolean spaceWords) { boolean spaceWords) {
boolean found = false; boolean found = false;
pos = pos < size ? pos : Math.min(size, chars.length() - 1); pos = pos < size ? pos : Math.min(size, chars.length() - 1);
@@ -916,8 +960,11 @@ public class SearchHelper {
return res; return res;
} }
public static @NotNull List<TextRange> findNumbersInRange(final @NotNull Editor editor, @NotNull TextRange textRange, public static @NotNull List<TextRange> findNumbersInRange(final @NotNull Editor editor,
final boolean alpha, final boolean hex, final boolean octal) { @NotNull TextRange textRange,
final boolean alpha,
final boolean hex,
final boolean octal) {
List<TextRange> result = new ArrayList<>(); List<TextRange> result = new ArrayList<>();
int firstLine = editor.offsetToLogicalPosition(textRange.getStartOffset()).line; int firstLine = editor.offsetToLogicalPosition(textRange.getStartOffset()).line;
int lastLine = editor.offsetToLogicalPosition(textRange.getEndOffset()).line; int lastLine = editor.offsetToLogicalPosition(textRange.getEndOffset()).line;
@@ -941,8 +988,11 @@ public class SearchHelper {
return result; return result;
} }
public static @Nullable TextRange findNumberUnderCursor(final @NotNull Editor editor, @NotNull Caret caret, final boolean alpha, public static @Nullable TextRange findNumberUnderCursor(final @NotNull Editor editor,
final boolean hex, final boolean octal) { @NotNull Caret caret,
final boolean alpha,
final boolean hex,
final boolean octal) {
int lline = caret.getLogicalPosition().line; int lline = caret.getLogicalPosition().line;
String text = EditorHelper.getLineText(editor, lline).toLowerCase(); String text = EditorHelper.getLineText(editor, lline).toLowerCase();
int startLineOffset = EditorHelper.getLineStartOffset(editor, lline); int startLineOffset = EditorHelper.getLineStartOffset(editor, lline);
@@ -954,18 +1004,21 @@ public class SearchHelper {
return null; return null;
} }
return new TextRange(numberTextRange.getStartOffset() + startLineOffset, return new TextRange(numberTextRange.getStartOffset() + startLineOffset,
numberTextRange.getEndOffset() + startLineOffset); numberTextRange.getEndOffset() + startLineOffset);
} }
/** /**
* Search for number in given text from start position * Search for number in given text from start position
* *
* @param textInRange - text to search in * @param textInRange - text to search in
* @param startPosOnLine - start offset to search * @param startPosOnLine - start offset to search
* @return - text range with number * @return - text range with number
*/ */
public static @Nullable TextRange findNumberInText(final @NotNull String textInRange, int startPosOnLine, final boolean alpha, public static @Nullable TextRange findNumberInText(final @NotNull String textInRange,
final boolean hex, final boolean octal) { int startPosOnLine,
final boolean alpha,
final boolean hex,
final boolean octal) {
if (logger.isDebugEnabled()) { if (logger.isDebugEnabled()) {
logger.debug("text=" + textInRange); logger.debug("text=" + textInRange);
@@ -990,7 +1043,9 @@ public class SearchHelper {
if (hex) { if (hex) {
// Ox and OX handling // Ox and OX handling
if (textInRange.charAt(pos) == '0' && pos < lineEndOffset - 1 && "xX".indexOf(textInRange.charAt(pos + 1)) >= 0) { if (textInRange.charAt(pos) == '0' &&
pos < lineEndOffset - 1 &&
"xX".indexOf(textInRange.charAt(pos + 1)) >= 0) {
pos += 2; pos += 2;
} }
else if ("xX".indexOf(textInRange.charAt(pos)) >= 0 && pos > 0 && textInRange.charAt(pos - 1) == '0') { else if ("xX".indexOf(textInRange.charAt(pos)) >= 0 && pos > 0 && textInRange.charAt(pos - 1) == '0') {
@@ -1055,8 +1110,12 @@ public class SearchHelper {
/** /**
* Searches for digits block that matches parameters * Searches for digits block that matches parameters
*/ */
private static @NotNull Pair<Integer, Integer> findRange(final @NotNull String text, final int pos, final boolean alpha, private static @NotNull Pair<Integer, Integer> findRange(final @NotNull String text,
final boolean hex, final boolean octal, final boolean decimal) { final int pos,
final boolean alpha,
final boolean hex,
final boolean octal,
final boolean decimal) {
int end = pos; int end = pos;
while (end < text.length() && isNumberChar(text.charAt(end), alpha, hex, octal, decimal)) { while (end < text.length() && isNumberChar(text.charAt(end), alpha, hex, octal, decimal)) {
end++; end++;
@@ -1093,7 +1152,7 @@ public class SearchHelper {
* Find the word under the cursor or the next word to the right of the cursor on the current line. * Find the word under the cursor or the next word to the right of the cursor on the current line.
* *
* @param editor The editor to find the word in * @param editor The editor to find the word in
* @param caret The caret to find word under * @param caret The caret to find word under
* @return The text range of the found word or null if there is no word under/after the cursor on the line * @return The text range of the found word or null if there is no word under/after the cursor on the line
*/ */
public static @Nullable TextRange findWordUnderCursor(@NotNull Editor editor, @NotNull Caret caret) { public static @Nullable TextRange findWordUnderCursor(@NotNull Editor editor, @NotNull Caret caret) {
@@ -1146,8 +1205,13 @@ public class SearchHelper {
} }
@Contract("_, _, _, _, _, _, _ -> new") @Contract("_, _, _, _, _, _, _ -> new")
public static @NotNull TextRange findWordUnderCursor(@NotNull Editor editor, @NotNull Caret caret, int count, int dir, public static @NotNull TextRange findWordUnderCursor(@NotNull Editor editor,
boolean isOuter, boolean isBig, boolean hasSelection) { @NotNull Caret caret,
int count,
int dir,
boolean isOuter,
boolean isBig,
boolean hasSelection) {
if (logger.isDebugEnabled()) { if (logger.isDebugEnabled()) {
logger.debug("count=" + count); logger.debug("count=" + count);
logger.debug("dir=" + dir); logger.debug("dir=" + dir);
@@ -1161,6 +1225,7 @@ public class SearchHelper {
//int max = EditorHelper.getLineEndOffset(editor, EditorHelper.getCurrentLogicalLine(editor), true); //int max = EditorHelper.getLineEndOffset(editor, EditorHelper.getCurrentLogicalLine(editor), true);
int min = 0; int min = 0;
int max = EditorHelper.getFileSize(editor); int max = EditorHelper.getFileSize(editor);
if (max == 0) return new TextRange(0, 0);
if (logger.isDebugEnabled()) { if (logger.isDebugEnabled()) {
logger.debug("min=" + min); logger.debug("min=" + min);
@@ -1304,7 +1369,11 @@ public class SearchHelper {
return findNextWordEnd(chars, pos, size, count, bigWord, false); return findNextWordEnd(chars, pos, size, count, bigWord, false);
} }
public static int findNextWordEnd(@NotNull CharSequence chars, int pos, int size, int count, boolean bigWord, public static int findNextWordEnd(@NotNull CharSequence chars,
int pos,
int size,
int count,
boolean bigWord,
boolean spaceWords) { boolean spaceWords) {
int step = count >= 0 ? 1 : -1; int step = count >= 0 ? 1 : -1;
count = Math.abs(count); count = Math.abs(count);
@@ -1320,7 +1389,11 @@ public class SearchHelper {
return res; return res;
} }
private static int findNextWordEndOne(@NotNull CharSequence chars, int pos, int size, int step, boolean bigWord, private static int findNextWordEndOne(@NotNull CharSequence chars,
int pos,
int size,
int step,
boolean bigWord,
boolean spaceWords) { boolean spaceWords) {
boolean found = false; boolean found = false;
// For forward searches, skip any current whitespace so we start at the start of a word // For forward searches, skip any current whitespace so we start at the start of a word
@@ -1366,12 +1439,10 @@ public class SearchHelper {
} }
if (found) { if (found) {
if (res < 0) if (res < 0) {
{
res = 0; res = 0;
} }
else if (res >= size) else if (res >= size) {
{
res = size - 1; res = size - 1;
} }
} }
@@ -1445,7 +1516,10 @@ public class SearchHelper {
} }
} }
public static int findNextSentenceStart(@NotNull Editor editor, @NotNull Caret caret, int count, boolean countCurrent, public static int findNextSentenceStart(@NotNull Editor editor,
@NotNull Caret caret,
int count,
boolean countCurrent,
boolean requireAll) { boolean requireAll) {
int dir = count > 0 ? 1 : -1; int dir = count > 0 ? 1 : -1;
count = Math.abs(count); count = Math.abs(count);
@@ -1483,7 +1557,10 @@ public class SearchHelper {
return findNextSentenceStart(editor, editor.getCaretModel().getPrimaryCaret(), count, countCurrent, requireAll); return findNextSentenceStart(editor, editor.getCaretModel().getPrimaryCaret(), count, countCurrent, requireAll);
} }
public static int findNextSentenceEnd(@NotNull Editor editor, @NotNull Caret caret, int count, boolean countCurrent, public static int findNextSentenceEnd(@NotNull Editor editor,
@NotNull Caret caret,
int count,
boolean countCurrent,
boolean requireAll) { boolean requireAll) {
int dir = count > 0 ? 1 : -1; int dir = count > 0 ? 1 : -1;
count = Math.abs(count); count = Math.abs(count);
@@ -1514,8 +1591,13 @@ public class SearchHelper {
return res; return res;
} }
private static int findSentenceStart(@NotNull Editor editor, @NotNull CharSequence chars, int start, int max, int dir, private static int findSentenceStart(@NotNull Editor editor,
boolean countCurrent, boolean multiple) { @NotNull CharSequence chars,
int start,
int max,
int dir,
boolean countCurrent,
boolean multiple) {
// Save off the next paragraph since a paragraph is a valid sentence. // Save off the next paragraph since a paragraph is a valid sentence.
int lline = editor.offsetToLogicalPosition(start).line; int lline = editor.offsetToLogicalPosition(start).line;
int np = findNextParagraph(editor, lline, dir, false, multiple); int np = findNextParagraph(editor, lline, dir, false, multiple);
@@ -1600,8 +1682,13 @@ public class SearchHelper {
return res; return res;
} }
private static int findSentenceEnd(@NotNull Editor editor, @NotNull CharSequence chars, int start, int max, int dir, private static int findSentenceEnd(@NotNull Editor editor,
boolean countCurrent, boolean multiple) { @NotNull CharSequence chars,
int start,
int max,
int dir,
boolean countCurrent,
boolean multiple) {
if (dir > 0 && start >= EditorHelper.getFileSize(editor) - 1) { if (dir > 0 && start >= EditorHelper.getFileSize(editor) - 1) {
return -1; return -1;
} }
@@ -1752,8 +1839,13 @@ public class SearchHelper {
return res; return res;
} }
private static int findSentenceRangeEnd(@NotNull Editor editor, @NotNull CharSequence chars, int start, int max, private static int findSentenceRangeEnd(@NotNull Editor editor,
int count, boolean isOuter, boolean oneway) { @NotNull CharSequence chars,
int start,
int max,
int count,
boolean isOuter,
boolean oneway) {
int dir = count > 0 ? 1 : -1; int dir = count > 0 ? 1 : -1;
count = Math.abs(count); count = Math.abs(count);
int total = count; int total = count;
@@ -1878,7 +1970,10 @@ public class SearchHelper {
} }
@Contract("_, _, _, _ -> new") @Contract("_, _, _, _ -> new")
public static @NotNull TextRange findSentenceRange(@NotNull Editor editor, @NotNull Caret caret, int count, boolean isOuter) { public static @NotNull TextRange findSentenceRange(@NotNull Editor editor,
@NotNull Caret caret,
int count,
boolean isOuter) {
CharSequence chars = editor.getDocument().getCharsSequence(); CharSequence chars = editor.getDocument().getCharsSequence();
if (chars.length() == 0) return new TextRange(0, 0); if (chars.length() == 0) return new TextRange(0, 0);
int max = EditorHelper.getFileSize(editor); int max = EditorHelper.getFileSize(editor);
@@ -1939,7 +2034,10 @@ public class SearchHelper {
return findNextParagraph(editor, editor.getCaretModel().getPrimaryCaret(), count, allowBlanks); return findNextParagraph(editor, editor.getCaretModel().getPrimaryCaret(), count, allowBlanks);
} }
private static int findNextParagraph(@NotNull Editor editor, int lline, int dir, boolean allowBlanks, private static int findNextParagraph(@NotNull Editor editor,
int lline,
int dir,
boolean allowBlanks,
boolean skipLines) { boolean skipLines) {
int line = findNextParagraphLine(editor, lline, dir, allowBlanks, skipLines); int line = findNextParagraphLine(editor, lline, dir, allowBlanks, skipLines);
@@ -1951,7 +2049,9 @@ public class SearchHelper {
} }
} }
private static int findNextParagraphLine(@NotNull Editor editor, @NotNull Caret caret, int count, private static int findNextParagraphLine(@NotNull Editor editor,
@NotNull Caret caret,
int count,
boolean allowBlanks) { boolean allowBlanks) {
int line = caret.getLogicalPosition().line; int line = caret.getLogicalPosition().line;
@@ -1982,7 +2082,10 @@ public class SearchHelper {
return findNextParagraphLine(editor, editor.getCaretModel().getPrimaryCaret(), count, allowBlanks); return findNextParagraphLine(editor, editor.getCaretModel().getPrimaryCaret(), count, allowBlanks);
} }
private static int findNextParagraphLine(@NotNull Editor editor, int line, int dir, boolean allowBlanks, private static int findNextParagraphLine(@NotNull Editor editor,
int line,
int dir,
boolean allowBlanks,
boolean skipLines) { boolean skipLines) {
int maxline = EditorHelper.getLineCount(editor); int maxline = EditorHelper.getLineCount(editor);
int res = -1; int res = -1;
@@ -2015,7 +2118,10 @@ public class SearchHelper {
return line; return line;
} }
public static @Nullable TextRange findParagraphRange(@NotNull Editor editor, @NotNull Caret caret, int count, boolean isOuter) { public static @Nullable TextRange findParagraphRange(@NotNull Editor editor,
@NotNull Caret caret,
int count,
boolean isOuter) {
int line = caret.getLogicalPosition().line; int line = caret.getLogicalPosition().line;
int maxline = EditorHelper.getLineCount(editor); int maxline = EditorHelper.getLineCount(editor);
if (logger.isDebugEnabled()) logger.debug("starting on line " + line); if (logger.isDebugEnabled()) logger.debug("starting on line " + line);

View File

@@ -101,7 +101,7 @@ public class StringHelper {
stroke = getKeyStroke(c, 0); stroke = getKeyStroke(c, 0);
} }
else if (isControlCharacter(c)) { else if (isControlCharacter(c)) {
stroke = getKeyStroke(c + 'A' - 1, CTRL_MASK); stroke = getKeyStroke(c + 'A' - 1, CTRL_DOWN_MASK);
} }
else { else {
stroke = getKeyStroke(c); stroke = getKeyStroke(c);
@@ -205,16 +205,16 @@ public class StringHelper {
} }
String prefix = ""; String prefix = "";
if ((modifiers & META_MASK) != 0) { if ((modifiers & META_DOWN_MASK) != 0) {
prefix += "M-"; prefix += "M-";
} }
if ((modifiers & ALT_MASK) != 0) { if ((modifiers & ALT_DOWN_MASK) != 0) {
prefix += "A-"; prefix += "A-";
} }
if ((modifiers & CTRL_MASK) != 0) { if ((modifiers & CTRL_DOWN_MASK) != 0) {
prefix += "C-"; prefix += "C-";
} }
if ((modifiers & SHIFT_MASK) != 0) { if ((modifiers & SHIFT_DOWN_MASK) != 0) {
prefix += "S-"; prefix += "S-";
} }
@@ -268,7 +268,7 @@ public class StringHelper {
if (c == CHAR_UNDEFINED && key.getModifiers() == 0) { if (c == CHAR_UNDEFINED && key.getModifiers() == 0) {
c = (char)key.getKeyCode(); c = (char)key.getKeyCode();
} }
else if (c == CHAR_UNDEFINED && (key.getModifiers() & CTRL_MASK) != 0) { else if (c == CHAR_UNDEFINED && (key.getModifiers() & CTRL_DOWN_MASK) != 0) {
c = (char)(key.getKeyCode() - 'A' + 1); c = (char)(key.getKeyCode() - 'A' + 1);
} }
@@ -304,8 +304,8 @@ public class StringHelper {
public static boolean isCloseKeyStroke(@NotNull KeyStroke key) { public static boolean isCloseKeyStroke(@NotNull KeyStroke key) {
return key.getKeyCode() == VK_ESCAPE || return key.getKeyCode() == VK_ESCAPE ||
key.getKeyChar() == VK_ESCAPE || key.getKeyChar() == VK_ESCAPE ||
key.getKeyCode() == VK_C && (key.getModifiers() & CTRL_MASK) != 0 || key.getKeyCode() == VK_C && (key.getModifiers() & CTRL_DOWN_MASK) != 0 ||
key.getKeyCode() == '[' && (key.getModifiers() & CTRL_MASK) != 0; key.getKeyCode() == '[' && (key.getModifiers() & CTRL_DOWN_MASK) != 0;
} }
/** /**
@@ -367,16 +367,16 @@ public class StringHelper {
return getTypedOrPressedKeyStroke(typedChar, modifiers); return getTypedOrPressedKeyStroke(typedChar, modifiers);
} }
else if (lower.startsWith(META_PREFIX)) { else if (lower.startsWith(META_PREFIX)) {
return parseSpecialKey(s.substring(META_PREFIX.length()), modifiers | META_MASK); return parseSpecialKey(s.substring(META_PREFIX.length()), modifiers | META_DOWN_MASK);
} }
else if (lower.startsWith(ALT_PREFIX)) { else if (lower.startsWith(ALT_PREFIX)) {
return parseSpecialKey(s.substring(ALT_PREFIX.length()), modifiers | ALT_MASK); return parseSpecialKey(s.substring(ALT_PREFIX.length()), modifiers | ALT_DOWN_MASK);
} }
else if (lower.startsWith(CTRL_PREFIX)) { else if (lower.startsWith(CTRL_PREFIX)) {
return parseSpecialKey(s.substring(CTRL_PREFIX.length()), modifiers | CTRL_MASK); return parseSpecialKey(s.substring(CTRL_PREFIX.length()), modifiers | CTRL_DOWN_MASK);
} }
else if (lower.startsWith(SHIFT_PREFIX)) { else if (lower.startsWith(SHIFT_PREFIX)) {
return parseSpecialKey(s.substring(SHIFT_PREFIX.length()), modifiers | SHIFT_MASK); return parseSpecialKey(s.substring(SHIFT_PREFIX.length()), modifiers | SHIFT_DOWN_MASK);
} }
else if (s.length() == 1) { else if (s.length() == 1) {
return getTypedOrPressedKeyStroke(s.charAt(0), modifiers); return getTypedOrPressedKeyStroke(s.charAt(0), modifiers);
@@ -535,7 +535,7 @@ public class StringHelper {
if (modifiers == 0) { if (modifiers == 0) {
return getKeyStroke(c); return getKeyStroke(c);
} }
else if (modifiers == SHIFT_MASK) { else if (modifiers == SHIFT_DOWN_MASK && Character.isLetter(c)) {
return getKeyStroke(Character.toUpperCase(c)); return getKeyStroke(Character.toUpperCase(c));
} }
else { else {

View File

@@ -42,8 +42,11 @@ fun runAfterGotFocus(runnable: Runnable) {
IdeFocusManager.findInstance().doWhenFocusSettlesDown(runnable, ModalityState.defaultModalityState()) IdeFocusManager.findInstance().doWhenFocusSettlesDown(runnable, ModalityState.defaultModalityState())
} }
val editorFont: Font fun selectFont(forStr: String): Font {
get() { val scheme = EditorColorsManager.getInstance().globalScheme
val scheme = EditorColorsManager.getInstance().globalScheme
return Font(scheme.editorFontName, Font.PLAIN, scheme.editorFontSize) val fontName = scheme.fontPreferences.realFontFamilies.firstOrNull {
} Font(it, Font.PLAIN, scheme.editorFontSize).canDisplayUpTo(forStr) == -1
} ?: return Font(scheme.editorFontName, Font.PLAIN, scheme.editorFontSize)
return Font(fontName, Font.PLAIN, scheme.editorFontSize)
}

View File

@@ -40,6 +40,7 @@ import com.intellij.openapi.project.Project
import com.intellij.openapi.project.ProjectManager import com.intellij.openapi.project.ProjectManager
import com.maddyhome.idea.vim.EventFacade import com.maddyhome.idea.vim.EventFacade
import com.maddyhome.idea.vim.KeyHandler import com.maddyhome.idea.vim.KeyHandler
import com.maddyhome.idea.vim.VimKeyListener
import com.maddyhome.idea.vim.VimPlugin import com.maddyhome.idea.vim.VimPlugin
import com.maddyhome.idea.vim.VimTypedActionHandler import com.maddyhome.idea.vim.VimTypedActionHandler
import com.maddyhome.idea.vim.command.CommandState import com.maddyhome.idea.vim.command.CommandState
@@ -65,6 +66,7 @@ import com.maddyhome.idea.vim.helper.vimLastColumn
import com.maddyhome.idea.vim.helper.vimMotionGroup import com.maddyhome.idea.vim.helper.vimMotionGroup
import com.maddyhome.idea.vim.option.OptionsManager import com.maddyhome.idea.vim.option.OptionsManager
import com.maddyhome.idea.vim.ui.ExEntryPanel import com.maddyhome.idea.vim.ui.ExEntryPanel
import com.maddyhome.idea.vim.ui.ShowCmdOptionChangeListener
import java.awt.event.MouseAdapter import java.awt.event.MouseAdapter
import java.awt.event.MouseEvent import java.awt.event.MouseEvent
import java.io.Closeable import java.io.Closeable
@@ -159,6 +161,8 @@ object VimListenerManager {
object GlobalListeners { object GlobalListeners {
fun enable() { fun enable() {
@Suppress("DEPRECATION")
// [VERSION UPDATE] 193+ com.intellij.openapi.editor.actionSystem.TypedAction.getInstance
val typedAction = EditorActionManager.getInstance().typedAction val typedAction = EditorActionManager.getInstance().typedAction
if (typedAction.rawHandler !is VimTypedActionHandler) { if (typedAction.rawHandler !is VimTypedActionHandler) {
// Actually this if should always be true, but just as protection // Actually this if should always be true, but just as protection
@@ -167,6 +171,7 @@ object VimListenerManager {
OptionsManager.number.addOptionChangeListener(EditorGroup.NumberChangeListener.INSTANCE) OptionsManager.number.addOptionChangeListener(EditorGroup.NumberChangeListener.INSTANCE)
OptionsManager.relativenumber.addOptionChangeListener(EditorGroup.NumberChangeListener.INSTANCE) OptionsManager.relativenumber.addOptionChangeListener(EditorGroup.NumberChangeListener.INSTANCE)
OptionsManager.showcmd.addOptionChangeListener(ShowCmdOptionChangeListener)
EventFacade.getInstance().addEditorFactoryListener(VimEditorFactoryListener, ApplicationManager.getApplication()) EventFacade.getInstance().addEditorFactoryListener(VimEditorFactoryListener, ApplicationManager.getApplication())
} }
@@ -176,6 +181,7 @@ object VimListenerManager {
OptionsManager.number.removeOptionChangeListener(EditorGroup.NumberChangeListener.INSTANCE) OptionsManager.number.removeOptionChangeListener(EditorGroup.NumberChangeListener.INSTANCE)
OptionsManager.relativenumber.removeOptionChangeListener(EditorGroup.NumberChangeListener.INSTANCE) OptionsManager.relativenumber.removeOptionChangeListener(EditorGroup.NumberChangeListener.INSTANCE)
OptionsManager.showcmd.addOptionChangeListener(ShowCmdOptionChangeListener)
EventFacade.getInstance().removeEditorFactoryListener(VimEditorFactoryListener) EventFacade.getInstance().removeEditorFactoryListener(VimEditorFactoryListener)
} }
@@ -223,6 +229,7 @@ object VimListenerManager {
@JvmStatic @JvmStatic
fun add(editor: Editor) { fun add(editor: Editor) {
editor.contentComponent.addKeyListener(VimKeyListener)
val eventFacade = EventFacade.getInstance() val eventFacade = EventFacade.getInstance()
eventFacade.addEditorMouseListener(editor, EditorMouseHandler) eventFacade.addEditorMouseListener(editor, EditorMouseHandler)
eventFacade.addEditorMouseMotionListener(editor, EditorMouseHandler) eventFacade.addEditorMouseMotionListener(editor, EditorMouseHandler)
@@ -232,6 +239,7 @@ object VimListenerManager {
@JvmStatic @JvmStatic
fun remove(editor: Editor) { fun remove(editor: Editor) {
editor.contentComponent.removeKeyListener(VimKeyListener)
val eventFacade = EventFacade.getInstance() val eventFacade = EventFacade.getInstance()
eventFacade.removeEditorMouseListener(editor, EditorMouseHandler) eventFacade.removeEditorMouseListener(editor, EditorMouseHandler)
eventFacade.removeEditorMouseMotionListener(editor, EditorMouseHandler) eventFacade.removeEditorMouseMotionListener(editor, EditorMouseHandler)

View File

@@ -70,6 +70,7 @@ object OptionsManager {
val scrolloff = addOption(NumberOption("scrolloff", "so", 0)) val scrolloff = addOption(NumberOption("scrolloff", "so", 0))
val selection = addOption(BoundStringOption("selection", "sel", "inclusive", arrayOf("old", "inclusive", "exclusive"))) val selection = addOption(BoundStringOption("selection", "sel", "inclusive", arrayOf("old", "inclusive", "exclusive")))
val selectmode = addOption(SelectModeOptionData.option) val selectmode = addOption(SelectModeOptionData.option)
val showcmd = addOption(ToggleOption("showcmd", "sc", true)) // Vim: Off by default on platforms with possibly slow tty. On by default elsewhere.
val showmode = addOption(ToggleOption("showmode", "smd", false)) val showmode = addOption(ToggleOption("showmode", "smd", false))
val sidescroll = addOption(NumberOption("sidescroll", "ss", 0)) val sidescroll = addOption(NumberOption("sidescroll", "ss", 0))
val sidescrolloff = addOption(NumberOption("sidescrolloff", "siso", 0)) val sidescrolloff = addOption(NumberOption("sidescrolloff", "siso", 0))

View File

@@ -40,7 +40,7 @@
* |i_CTRL-I| IntelliJ editor tab * |i_CTRL-I| IntelliJ editor tab
* |i_<NL>| {@link com.maddyhome.idea.vim.action.change.insert.InsertEnterAction} * |i_<NL>| {@link com.maddyhome.idea.vim.action.change.insert.InsertEnterAction}
* |i_CTRL-J| TO BE IMPLEMENTED * |i_CTRL-J| TO BE IMPLEMENTED
* |i_CTRL-K| {@link com.maddyhome.idea.vim.action.change.insert.StartInsertDigraphAction} * |i_CTRL-K| {@link com.maddyhome.idea.vim.action.change.insert.InsertCompletedDigraphAction}
* |i_CTRL-L| TO BE IMPLEMENTED * |i_CTRL-L| TO BE IMPLEMENTED
* |i_<CR>| {@link com.maddyhome.idea.vim.action.change.insert.InsertEnterAction} * |i_<CR>| {@link com.maddyhome.idea.vim.action.change.insert.InsertEnterAction}
* |i_CTRL-M| {@link com.maddyhome.idea.vim.action.change.insert.InsertEnterAction} * |i_CTRL-M| {@link com.maddyhome.idea.vim.action.change.insert.InsertEnterAction}
@@ -54,8 +54,8 @@
* |i_CTRL-R_CTRL-P| TO BE IMPLEMENTED * |i_CTRL-R_CTRL-P| TO BE IMPLEMENTED
* |i_CTRL-T| {@link com.maddyhome.idea.vim.action.change.shift.ShiftRightLinesAction} * |i_CTRL-T| {@link com.maddyhome.idea.vim.action.change.shift.ShiftRightLinesAction}
* |i_CTRL-U| {@link com.maddyhome.idea.vim.action.change.insert.InsertDeleteInsertedTextAction} * |i_CTRL-U| {@link com.maddyhome.idea.vim.action.change.insert.InsertDeleteInsertedTextAction}
* |i_CTRL-V| {@link com.maddyhome.idea.vim.action.change.insert.StartInsertLiteralAction} * |i_CTRL-V| {@link com.maddyhome.idea.vim.action.change.insert.InsertCompletedLiteralAction}
* |i_CTRL-V_digit| {@link com.maddyhome.idea.vim.action.change.insert.StartInsertLiteralAction} * |i_CTRL-V_digit| {@link com.maddyhome.idea.vim.action.change.insert.InsertCompletedLiteralAction}
* |i_CTRL-W| {@link com.maddyhome.idea.vim.action.change.insert.InsertDeletePreviousWordAction} * |i_CTRL-W| {@link com.maddyhome.idea.vim.action.change.insert.InsertDeletePreviousWordAction}
* |i_CTRL-X| TO BE IMPLEMENTED * |i_CTRL-X| TO BE IMPLEMENTED
* |i_CTRL-Y| {@link com.maddyhome.idea.vim.action.change.insert.InsertCharacterAboveCursorAction} * |i_CTRL-Y| {@link com.maddyhome.idea.vim.action.change.insert.InsertCharacterAboveCursorAction}
@@ -130,7 +130,7 @@
* |<Space>| {@link com.maddyhome.idea.vim.action.motion.leftright.MotionRightWrapAction} * |<Space>| {@link com.maddyhome.idea.vim.action.motion.leftright.MotionRightWrapAction}
* |!| {@link com.maddyhome.idea.vim.action.change.change.FilterMotionAction} * |!| {@link com.maddyhome.idea.vim.action.change.change.FilterMotionAction}
* |!!| translated to !_ * |!!| translated to !_
* |quote| {@link com.maddyhome.idea.vim.action.copy.SelectRegisterAction} * |quote| handled by command key parser
* |#| {@link com.maddyhome.idea.vim.action.motion.search.SearchWholeWordBackwardAction} * |#| {@link com.maddyhome.idea.vim.action.motion.search.SearchWholeWordBackwardAction}
* |$| {@link com.maddyhome.idea.vim.action.motion.leftright.MotionLastColumnAction} * |$| {@link com.maddyhome.idea.vim.action.motion.leftright.MotionLastColumnAction}
* |%| {@link com.maddyhome.idea.vim.action.motion.updown.MotionPercentOrMatchAction} * |%| {@link com.maddyhome.idea.vim.action.motion.updown.MotionPercentOrMatchAction}
@@ -330,7 +330,7 @@
* |[D| TO BE IMPLEMENTED * |[D| TO BE IMPLEMENTED
* |[I| TO BE IMPLEMENTED * |[I| TO BE IMPLEMENTED
* |[M| {@link com.maddyhome.idea.vim.action.motion.text.MotionMethodPreviousEndAction} * |[M| {@link com.maddyhome.idea.vim.action.motion.text.MotionMethodPreviousEndAction}
* |[P| {@link com.maddyhome.idea.vim.action.copy.PutVisualTextNoIndentAction} * |[P| {@link com.maddyhome.idea.vim.action.copy.PutVisualTextBeforeCursorNoIndentAction}
* |[P| {@link com.maddyhome.idea.vim.action.copy.PutTextBeforeCursorNoIndentAction} * |[P| {@link com.maddyhome.idea.vim.action.copy.PutTextBeforeCursorNoIndentAction}
* |[[| {@link com.maddyhome.idea.vim.action.motion.text.MotionSectionBackwardStartAction} * |[[| {@link com.maddyhome.idea.vim.action.motion.text.MotionSectionBackwardStartAction}
* |[]| {@link com.maddyhome.idea.vim.action.motion.text.MotionSectionBackwardEndAction} * |[]| {@link com.maddyhome.idea.vim.action.motion.text.MotionSectionBackwardEndAction}
@@ -339,7 +339,7 @@
* |[f| TO BE IMPLEMENTED * |[f| TO BE IMPLEMENTED
* |[i| TO BE IMPLEMENTED * |[i| TO BE IMPLEMENTED
* |[m| {@link com.maddyhome.idea.vim.action.motion.text.MotionMethodPreviousStartAction} * |[m| {@link com.maddyhome.idea.vim.action.motion.text.MotionMethodPreviousStartAction}
* |[p| {@link com.maddyhome.idea.vim.action.copy.PutVisualTextNoIndentAction} * |[p| {@link com.maddyhome.idea.vim.action.copy.PutVisualTextAfterCursorNoIndentAction}
* |[p| {@link com.maddyhome.idea.vim.action.copy.PutTextAfterCursorNoIndentAction} * |[p| {@link com.maddyhome.idea.vim.action.copy.PutTextAfterCursorNoIndentAction}
* |[s| TO BE IMPLEMENTED * |[s| TO BE IMPLEMENTED
* |[z| TO BE IMPLEMENTED * |[z| TO BE IMPLEMENTED
@@ -355,7 +355,7 @@
* |]D| TO BE IMPLEMENTED * |]D| TO BE IMPLEMENTED
* |]I| TO BE IMPLEMENTED * |]I| TO BE IMPLEMENTED
* |]M| {@link com.maddyhome.idea.vim.action.motion.text.MotionMethodNextEndAction} * |]M| {@link com.maddyhome.idea.vim.action.motion.text.MotionMethodNextEndAction}
* |]P| {@link com.maddyhome.idea.vim.action.copy.PutVisualTextNoIndentAction} * |]P| {@link com.maddyhome.idea.vim.action.copy.PutVisualTextBeforeCursorNoIndentAction}
* |]P| {@link com.maddyhome.idea.vim.action.copy.PutTextBeforeCursorNoIndentAction} * |]P| {@link com.maddyhome.idea.vim.action.copy.PutTextBeforeCursorNoIndentAction}
* |][| {@link com.maddyhome.idea.vim.action.motion.text.MotionSectionForwardStartAction} * |][| {@link com.maddyhome.idea.vim.action.motion.text.MotionSectionForwardStartAction}
* |]]| {@link com.maddyhome.idea.vim.action.motion.text.MotionSectionForwardEndAction} * |]]| {@link com.maddyhome.idea.vim.action.motion.text.MotionSectionForwardEndAction}
@@ -364,7 +364,7 @@
* |]f| TO BE IMPLEMENTED * |]f| TO BE IMPLEMENTED
* |]i| TO BE IMPLEMENTED * |]i| TO BE IMPLEMENTED
* |]m| {@link com.maddyhome.idea.vim.action.motion.text.MotionMethodNextStartAction} * |]m| {@link com.maddyhome.idea.vim.action.motion.text.MotionMethodNextStartAction}
* |]p| {@link com.maddyhome.idea.vim.action.copy.PutVisualTextNoIndentAction} * |]p| {@link com.maddyhome.idea.vim.action.copy.PutVisualTextAfterCursorNoIndentAction}
* |]p| {@link com.maddyhome.idea.vim.action.copy.PutTextAfterCursorNoIndentAction} * |]p| {@link com.maddyhome.idea.vim.action.copy.PutTextAfterCursorNoIndentAction}
* |]s| TO BE IMPLEMENTED * |]s| TO BE IMPLEMENTED
* |]z| TO BE IMPLEMENTED * |]z| TO BE IMPLEMENTED
@@ -404,6 +404,7 @@
* |gJ| {@link com.maddyhome.idea.vim.action.change.delete.DeleteJoinLinesAction} * |gJ| {@link com.maddyhome.idea.vim.action.change.delete.DeleteJoinLinesAction}
* |gN| {@link com.maddyhome.idea.vim.action.motion.gn.VisualSelectPreviousSearch} * |gN| {@link com.maddyhome.idea.vim.action.motion.gn.VisualSelectPreviousSearch}
* |gN| {@link com.maddyhome.idea.vim.action.motion.gn.GnPreviousTextObject} * |gN| {@link com.maddyhome.idea.vim.action.motion.gn.GnPreviousTextObject}
* |gP| {@link com.maddyhome.idea.vim.action.copy.PutVisualTextBeforeCursorMoveCursorAction}
* |gP| {@link com.maddyhome.idea.vim.action.copy.PutTextBeforeCursorActionMoveCursor} * |gP| {@link com.maddyhome.idea.vim.action.copy.PutTextBeforeCursorActionMoveCursor}
* |gQ| TO BE IMPLEMENTED * |gQ| TO BE IMPLEMENTED
* |gR| TO BE IMPLEMENTED * |gR| TO BE IMPLEMENTED
@@ -426,7 +427,7 @@
* |gn| {@link com.maddyhome.idea.vim.action.motion.gn.GnNextTextObject} * |gn| {@link com.maddyhome.idea.vim.action.motion.gn.GnNextTextObject}
* |gm| {@link com.maddyhome.idea.vim.action.motion.leftright.MotionMiddleColumnAction} * |gm| {@link com.maddyhome.idea.vim.action.motion.leftright.MotionMiddleColumnAction}
* |go| {@link com.maddyhome.idea.vim.action.motion.text.MotionNthCharacterAction} * |go| {@link com.maddyhome.idea.vim.action.motion.text.MotionNthCharacterAction}
* |gp| {@link com.maddyhome.idea.vim.action.copy.PutVisualTextMoveCursorAction} * |gp| {@link com.maddyhome.idea.vim.action.copy.PutVisualTextAfterCursorMoveCursorAction}
* |gp| {@link com.maddyhome.idea.vim.action.copy.PutTextAfterCursorActionMoveCursor} * |gp| {@link com.maddyhome.idea.vim.action.copy.PutTextAfterCursorActionMoveCursor}
* |gq| {@link com.maddyhome.idea.vim.action.change.change.ReformatCodeMotionAction} * |gq| {@link com.maddyhome.idea.vim.action.change.change.ReformatCodeMotionAction}
* |gr| TO BE IMPLEMENTED * |gr| TO BE IMPLEMENTED
@@ -522,7 +523,7 @@
* |v_J| {@link com.maddyhome.idea.vim.action.change.delete.DeleteJoinVisualLinesSpacesAction} * |v_J| {@link com.maddyhome.idea.vim.action.change.delete.DeleteJoinVisualLinesSpacesAction}
* |v_K| TO BE IMPLEMENTED * |v_K| TO BE IMPLEMENTED
* |v_O| {@link com.maddyhome.idea.vim.action.motion.visual.VisualSwapEndsBlockAction} * |v_O| {@link com.maddyhome.idea.vim.action.motion.visual.VisualSwapEndsBlockAction}
* |v_P| {@link com.maddyhome.idea.vim.action.copy.PutVisualTextAction} * |v_P| {@link com.maddyhome.idea.vim.action.copy.PutVisualTextBeforeCursorAction}
* |v_R| {@link com.maddyhome.idea.vim.action.change.change.ChangeVisualLinesAction} * |v_R| {@link com.maddyhome.idea.vim.action.change.change.ChangeVisualLinesAction}
* |v_S| {@link com.maddyhome.idea.vim.action.change.change.ChangeVisualLinesAction} * |v_S| {@link com.maddyhome.idea.vim.action.change.change.ChangeVisualLinesAction}
* |v_U| {@link com.maddyhome.idea.vim.action.change.change.ChangeCaseUpperVisualAction} * |v_U| {@link com.maddyhome.idea.vim.action.change.change.ChangeCaseUpperVisualAction}
@@ -574,7 +575,7 @@
* |v_i{| {@link com.maddyhome.idea.vim.action.motion.object.MotionInnerBlockBraceAction} * |v_i{| {@link com.maddyhome.idea.vim.action.motion.object.MotionInnerBlockBraceAction}
* |v_i}| {@link com.maddyhome.idea.vim.action.motion.object.MotionInnerBlockBraceAction} * |v_i}| {@link com.maddyhome.idea.vim.action.motion.object.MotionInnerBlockBraceAction}
* |v_o| {@link com.maddyhome.idea.vim.action.motion.visual.VisualSwapEndsAction} * |v_o| {@link com.maddyhome.idea.vim.action.motion.visual.VisualSwapEndsAction}
* |v_p| {@link com.maddyhome.idea.vim.action.copy.PutVisualTextAction} * |v_p| {@link com.maddyhome.idea.vim.action.copy.PutVisualTextAfterCursorAction}
* |v_r| {@link com.maddyhome.idea.vim.action.change.change.ChangeVisualCharacterAction} * |v_r| {@link com.maddyhome.idea.vim.action.change.change.ChangeVisualCharacterAction}
* |v_s| {@link com.maddyhome.idea.vim.action.change.change.ChangeVisualAction} * |v_s| {@link com.maddyhome.idea.vim.action.change.change.ChangeVisualAction}
* |v_u| {@link com.maddyhome.idea.vim.action.change.change.ChangeCaseLowerVisualAction} * |v_u| {@link com.maddyhome.idea.vim.action.change.change.ChangeCaseLowerVisualAction}
@@ -621,8 +622,8 @@
* |c_CTRL-K| {@link com.maddyhome.idea.vim.ui.ExEditorKit.StartDigraphAction} * |c_CTRL-K| {@link com.maddyhome.idea.vim.ui.ExEditorKit.StartDigraphAction}
* |c_CTRL-L| TO BE IMPLEMENTED * |c_CTRL-L| TO BE IMPLEMENTED
* |c_CTRL-M| {@link com.maddyhome.idea.vim.action.ex.ProcessExEntryAction} * |c_CTRL-M| {@link com.maddyhome.idea.vim.action.ex.ProcessExEntryAction}
* |c_CTRL-N| {@link com.maddyhome.idea.vim.ui.ExEditorKit.HistoryDownAction} [To Be Released] * |c_CTRL-N| {@link com.maddyhome.idea.vim.ui.ExEditorKit.HistoryDownAction}
* |c_CTRL-P| {@link com.maddyhome.idea.vim.ui.ExEditorKit.HistoryUpAction} [To Be Released] * |c_CTRL-P| {@link com.maddyhome.idea.vim.ui.ExEditorKit.HistoryUpAction}
* |c_CTRL-Q| {@link com.maddyhome.idea.vim.ui.ExEditorKit.StartDigraphAction} * |c_CTRL-Q| {@link com.maddyhome.idea.vim.ui.ExEditorKit.StartDigraphAction}
* |c_CTRL-R| {@link com.maddyhome.idea.vim.ui.ExEditorKit.InsertRegisterAction} * |c_CTRL-R| {@link com.maddyhome.idea.vim.ui.ExEditorKit.InsertRegisterAction}
* |c_CTRL-R_CTRL-A| TO BE IMPLEMENTED * |c_CTRL-R_CTRL-A| TO BE IMPLEMENTED

View File

@@ -86,7 +86,7 @@ public class ExEditorKit extends DefaultEditorKit {
if (cmd != null && cmd.length() > 0) { if (cmd != null && cmd.length() > 0) {
char ch = cmd.charAt(0); char ch = cmd.charAt(0);
if (ch < ' ') { if (ch < ' ') {
if ((mods & KeyEvent.CTRL_MASK) != 0) { if ((mods & ActionEvent.CTRL_MASK) != 0) {
return KeyStroke.getKeyStroke(KeyEvent.VK_A + ch - 1, mods); return KeyStroke.getKeyStroke(KeyEvent.VK_A + ch - 1, mods);
} }
} }
@@ -246,7 +246,7 @@ public class ExEditorKit extends DefaultEditorKit {
target.setCaretPosition(offset + text.length()); target.setCaretPosition(offset + text.length());
} }
} }
} else if ((key.getModifiers() & KeyEvent.CTRL_MASK) != 0 && key.getKeyCode() == KeyEvent.VK_C) { } else if ((key.getModifiers() & KeyEvent.CTRL_DOWN_MASK) != 0 && key.getKeyCode() == KeyEvent.VK_C) {
// Eat any unused keys, unless it's <C-C>, in which case forward on and cancel entry // Eat any unused keys, unless it's <C-C>, in which case forward on and cancel entry
target.handleKey(key); target.handleKey(key);
} }
@@ -507,7 +507,7 @@ public class ExEditorKit extends DefaultEditorKit {
target.clearCurrentAction(); target.clearCurrentAction();
// Eat the character, unless it's <C-C>, in which case, forward on and cancel entry. Note that at some point // Eat the character, unless it's <C-C>, in which case, forward on and cancel entry. Note that at some point
// we should support input of control characters // we should support input of control characters
if ((key.getModifiers() & KeyEvent.CTRL_MASK) != 0 && key.getKeyCode() == KeyEvent.VK_C) { if ((key.getModifiers() & KeyEvent.CTRL_DOWN_MASK) != 0 && key.getKeyCode() == KeyEvent.VK_C) {
target.handleKey(key); target.handleKey(key);
} }
break; break;

View File

@@ -122,15 +122,18 @@ public class ExEntryPanel extends JPanel implements LafManagerListener {
* @param count A holder for the ex entry count * @param count A holder for the ex entry count
*/ */
public void activate(@NotNull Editor editor, DataContext context, @NotNull String label, String initText, int count) { public void activate(@NotNull Editor editor, DataContext context, @NotNull String label, String initText, int count) {
logger.info("Activate ex entry panel");
this.label.setText(label); this.label.setText(label);
this.label.setFont(UiHelper.selectFont(label));
this.count = count; this.count = count;
setFontForElements();
entry.reset(); entry.reset();
entry.setEditor(editor, context); entry.setEditor(editor, context);
entry.setText(initText); entry.setText(initText);
entry.setFont(UiHelper.selectFont(initText));
entry.setType(label); entry.setType(label);
parent = editor.getContentComponent(); parent = editor.getContentComponent();
entry.getDocument().addDocumentListener(fontListener);
if (isIncSearchEnabled()) { if (isIncSearchEnabled()) {
entry.getDocument().addDocumentListener(incSearchDocumentListener); entry.getDocument().addDocumentListener(incSearchDocumentListener);
caretOffset = editor.getCaretModel().getOffset(); caretOffset = editor.getCaretModel().getOffset();
@@ -177,6 +180,7 @@ public class ExEntryPanel extends JPanel implements LafManagerListener {
active = false; active = false;
try { try {
entry.getDocument().removeDocumentListener(fontListener);
// incsearch won't change in the lifetime of this activation // incsearch won't change in the lifetime of this activation
if (isIncSearchEnabled()) { if (isIncSearchEnabled()) {
entry.getDocument().removeDocumentListener(incSearchDocumentListener); entry.getDocument().removeDocumentListener(incSearchDocumentListener);
@@ -236,6 +240,17 @@ public class ExEntryPanel extends JPanel implements LafManagerListener {
} }
} }
private final @NotNull DocumentListener fontListener = new DocumentAdapter() {
@Override
protected void textChanged(@NotNull DocumentEvent e) {
String text = entry.getActualText();
Font newFont = UiHelper.selectFont(text);
if (newFont != entry.getFont()) {
entry.setFont(newFont);
}
}
};
private final @NotNull DocumentListener incSearchDocumentListener = new DocumentAdapter() { private final @NotNull DocumentListener incSearchDocumentListener = new DocumentAdapter() {
@Override @Override
protected void textChanged(@NotNull DocumentEvent e) { protected void textChanged(@NotNull DocumentEvent e) {
@@ -246,6 +261,7 @@ public class ExEntryPanel extends JPanel implements LafManagerListener {
char separator = label.getText().charAt(0); char separator = label.getText().charAt(0);
String searchText = entry.getActualText(); String searchText = entry.getActualText();
if (label.getText().equals(":")) { if (label.getText().equals(":")) {
if (searchText.isEmpty()) return;
final ExCommand command = getIncsearchCommand(searchText); final ExCommand command = getIncsearchCommand(searchText);
if (command == null) { if (command == null) {
return; return;
@@ -389,9 +405,8 @@ public class ExEntryPanel extends JPanel implements LafManagerListener {
} }
private void setFontForElements() { private void setFontForElements() {
final Font font = UiHelper.getEditorFont(); label.setFont(UiHelper.selectFont(label.getText()));
label.setFont(font); entry.setFont(UiHelper.selectFont(entry.getActualText()));
entry.setFont(font);
} }
private void positionPanel() { private void positionPanel() {

View File

@@ -31,60 +31,60 @@ object ExKeyBindings {
arrayOf( arrayOf(
// Escape will cancel a pending insert digraph/register before cancelling // Escape will cancel a pending insert digraph/register before cancelling
KeyBinding(KeyStroke.getKeyStroke(KeyEvent.VK_ESCAPE, 0), ExEditorKit.EscapeChar), KeyBinding(KeyStroke.getKeyStroke(KeyEvent.VK_ESCAPE, 0), ExEditorKit.EscapeChar),
KeyBinding(KeyStroke.getKeyStroke(KeyEvent.VK_OPEN_BRACKET, KeyEvent.CTRL_MASK), ExEditorKit.EscapeChar), KeyBinding(KeyStroke.getKeyStroke(KeyEvent.VK_OPEN_BRACKET, KeyEvent.CTRL_DOWN_MASK), ExEditorKit.EscapeChar),
// Cancel entry, ignoring any pending actions such as digraph/registry entry // Cancel entry, ignoring any pending actions such as digraph/registry entry
KeyBinding(KeyStroke.getKeyStroke(KeyEvent.VK_C, KeyEvent.CTRL_MASK), ExEditorKit.CancelEntry), KeyBinding(KeyStroke.getKeyStroke(KeyEvent.VK_C, KeyEvent.CTRL_DOWN_MASK), ExEditorKit.CancelEntry),
KeyBinding(KeyStroke.getKeyStroke(KeyEvent.VK_ENTER, 0), ExEditorKit.CompleteEntry), KeyBinding(KeyStroke.getKeyStroke(KeyEvent.VK_ENTER, 0), ExEditorKit.CompleteEntry),
KeyBinding(KeyStroke.getKeyStroke(KeyEvent.VK_J, KeyEvent.CTRL_MASK), ExEditorKit.CompleteEntry), KeyBinding(KeyStroke.getKeyStroke(KeyEvent.VK_J, KeyEvent.CTRL_DOWN_MASK), ExEditorKit.CompleteEntry),
KeyBinding(KeyStroke.getKeyStroke(KeyEvent.VK_M, KeyEvent.CTRL_MASK), ExEditorKit.CompleteEntry), KeyBinding(KeyStroke.getKeyStroke(KeyEvent.VK_M, KeyEvent.CTRL_DOWN_MASK), ExEditorKit.CompleteEntry),
KeyBinding(KeyStroke.getKeyStroke(KeyEvent.VK_B, KeyEvent.CTRL_MASK), ExEditorKit.beginLineAction), KeyBinding(KeyStroke.getKeyStroke(KeyEvent.VK_B, KeyEvent.CTRL_DOWN_MASK), ExEditorKit.beginLineAction),
KeyBinding(KeyStroke.getKeyStroke(0x02.toChar().toInt(), KeyEvent.CTRL_MASK), ExEditorKit.beginLineAction), KeyBinding(KeyStroke.getKeyStroke(0x02.toChar().toInt(), KeyEvent.CTRL_DOWN_MASK), ExEditorKit.beginLineAction),
KeyBinding(KeyStroke.getKeyStroke(KeyEvent.VK_HOME, 0), ExEditorKit.beginLineAction), KeyBinding(KeyStroke.getKeyStroke(KeyEvent.VK_HOME, 0), ExEditorKit.beginLineAction),
KeyBinding(KeyStroke.getKeyStroke(KeyEvent.VK_E, KeyEvent.CTRL_MASK), ExEditorKit.endLineAction), KeyBinding(KeyStroke.getKeyStroke(KeyEvent.VK_E, KeyEvent.CTRL_DOWN_MASK), ExEditorKit.endLineAction),
KeyBinding(KeyStroke.getKeyStroke(0x05.toChar().toInt(), KeyEvent.CTRL_MASK), ExEditorKit.endLineAction), KeyBinding(KeyStroke.getKeyStroke(0x05.toChar().toInt(), KeyEvent.CTRL_DOWN_MASK), ExEditorKit.endLineAction),
KeyBinding(KeyStroke.getKeyStroke(KeyEvent.VK_END, 0), ExEditorKit.endLineAction), KeyBinding(KeyStroke.getKeyStroke(KeyEvent.VK_END, 0), ExEditorKit.endLineAction),
KeyBinding(KeyStroke.getKeyStroke(KeyEvent.VK_BACK_SPACE, 0), ExEditorKit.deletePrevCharAction), KeyBinding(KeyStroke.getKeyStroke(KeyEvent.VK_BACK_SPACE, 0), ExEditorKit.deletePrevCharAction),
KeyBinding(KeyStroke.getKeyStroke(KeyEvent.VK_H, KeyEvent.CTRL_MASK), ExEditorKit.deletePrevCharAction), KeyBinding(KeyStroke.getKeyStroke(KeyEvent.VK_H, KeyEvent.CTRL_DOWN_MASK), ExEditorKit.deletePrevCharAction),
KeyBinding(KeyStroke.getKeyStroke(0x08.toChar().toInt(), KeyEvent.CTRL_MASK), ExEditorKit.deletePrevCharAction), KeyBinding(KeyStroke.getKeyStroke(0x08.toChar().toInt(), KeyEvent.CTRL_DOWN_MASK), ExEditorKit.deletePrevCharAction),
KeyBinding(KeyStroke.getKeyStroke(KeyEvent.VK_DELETE, 0), ExEditorKit.deleteNextCharAction), KeyBinding(KeyStroke.getKeyStroke(KeyEvent.VK_DELETE, 0), ExEditorKit.deleteNextCharAction),
KeyBinding(KeyStroke.getKeyStroke(KeyEvent.VK_W, KeyEvent.CTRL_MASK), ExEditorKit.deletePrevWordAction), KeyBinding(KeyStroke.getKeyStroke(KeyEvent.VK_W, KeyEvent.CTRL_DOWN_MASK), ExEditorKit.deletePrevWordAction),
KeyBinding(KeyStroke.getKeyStroke(KeyEvent.VK_U, KeyEvent.CTRL_MASK), ExEditorKit.DeleteToCursor), KeyBinding(KeyStroke.getKeyStroke(KeyEvent.VK_U, KeyEvent.CTRL_DOWN_MASK), ExEditorKit.DeleteToCursor),
KeyBinding(KeyStroke.getKeyStroke(KeyEvent.VK_UP, 0), ExEditorKit.HistoryUpFilter), KeyBinding(KeyStroke.getKeyStroke(KeyEvent.VK_UP, 0), ExEditorKit.HistoryUpFilter),
KeyBinding(KeyStroke.getKeyStroke(KeyEvent.VK_UP, KeyEvent.SHIFT_MASK), ExEditorKit.HistoryUp), KeyBinding(KeyStroke.getKeyStroke(KeyEvent.VK_UP, KeyEvent.SHIFT_DOWN_MASK), ExEditorKit.HistoryUp),
KeyBinding(KeyStroke.getKeyStroke(KeyEvent.VK_PAGE_UP, 0), ExEditorKit.HistoryUp), KeyBinding(KeyStroke.getKeyStroke(KeyEvent.VK_PAGE_UP, 0), ExEditorKit.HistoryUp),
KeyBinding(KeyStroke.getKeyStroke(KeyEvent.VK_P, KeyEvent.CTRL_MASK), ExEditorKit.HistoryUp), KeyBinding(KeyStroke.getKeyStroke(KeyEvent.VK_P, KeyEvent.CTRL_DOWN_MASK), ExEditorKit.HistoryUp),
KeyBinding(KeyStroke.getKeyStroke(KeyEvent.VK_DOWN, 0), ExEditorKit.HistoryDownFilter), KeyBinding(KeyStroke.getKeyStroke(KeyEvent.VK_DOWN, 0), ExEditorKit.HistoryDownFilter),
KeyBinding(KeyStroke.getKeyStroke(KeyEvent.VK_DOWN, KeyEvent.SHIFT_MASK), ExEditorKit.HistoryDown), KeyBinding(KeyStroke.getKeyStroke(KeyEvent.VK_DOWN, KeyEvent.SHIFT_DOWN_MASK), ExEditorKit.HistoryDown),
KeyBinding(KeyStroke.getKeyStroke(KeyEvent.VK_PAGE_DOWN, 0), ExEditorKit.HistoryDown), KeyBinding(KeyStroke.getKeyStroke(KeyEvent.VK_PAGE_DOWN, 0), ExEditorKit.HistoryDown),
KeyBinding(KeyStroke.getKeyStroke(KeyEvent.VK_N, KeyEvent.CTRL_MASK), ExEditorKit.HistoryDown), KeyBinding(KeyStroke.getKeyStroke(KeyEvent.VK_N, KeyEvent.CTRL_DOWN_MASK), ExEditorKit.HistoryDown),
KeyBinding(KeyStroke.getKeyStroke(KeyEvent.VK_INSERT, 0), ExEditorKit.ToggleInsertReplace), KeyBinding(KeyStroke.getKeyStroke(KeyEvent.VK_INSERT, 0), ExEditorKit.ToggleInsertReplace),
KeyBinding(KeyStroke.getKeyStroke(KeyEvent.VK_LEFT, 0), ExEditorKit.backwardAction), KeyBinding(KeyStroke.getKeyStroke(KeyEvent.VK_LEFT, 0), ExEditorKit.backwardAction),
KeyBinding(KeyStroke.getKeyStroke(KeyEvent.VK_LEFT, KeyEvent.SHIFT_MASK), ExEditorKit.previousWordAction), KeyBinding(KeyStroke.getKeyStroke(KeyEvent.VK_LEFT, KeyEvent.SHIFT_DOWN_MASK), ExEditorKit.previousWordAction),
KeyBinding(KeyStroke.getKeyStroke(KeyEvent.VK_LEFT, KeyEvent.CTRL_MASK), ExEditorKit.previousWordAction), KeyBinding(KeyStroke.getKeyStroke(KeyEvent.VK_LEFT, KeyEvent.CTRL_DOWN_MASK), ExEditorKit.previousWordAction),
KeyBinding(KeyStroke.getKeyStroke(KeyEvent.VK_RIGHT, 0), ExEditorKit.forwardAction), KeyBinding(KeyStroke.getKeyStroke(KeyEvent.VK_RIGHT, 0), ExEditorKit.forwardAction),
KeyBinding(KeyStroke.getKeyStroke(KeyEvent.VK_RIGHT, KeyEvent.SHIFT_MASK), ExEditorKit.nextWordAction), KeyBinding(KeyStroke.getKeyStroke(KeyEvent.VK_RIGHT, KeyEvent.SHIFT_DOWN_MASK), ExEditorKit.nextWordAction),
KeyBinding(KeyStroke.getKeyStroke(KeyEvent.VK_RIGHT, KeyEvent.CTRL_MASK), ExEditorKit.nextWordAction), KeyBinding(KeyStroke.getKeyStroke(KeyEvent.VK_RIGHT, KeyEvent.CTRL_DOWN_MASK), ExEditorKit.nextWordAction),
KeyBinding(KeyStroke.getKeyStroke(KeyEvent.VK_K, KeyEvent.CTRL_MASK), ExEditorKit.StartDigraph), KeyBinding(KeyStroke.getKeyStroke(KeyEvent.VK_K, KeyEvent.CTRL_DOWN_MASK), ExEditorKit.StartDigraph),
KeyBinding(KeyStroke.getKeyStroke(KeyEvent.VK_V, KeyEvent.CTRL_MASK), ExEditorKit.StartLiteral), KeyBinding(KeyStroke.getKeyStroke(KeyEvent.VK_V, KeyEvent.CTRL_DOWN_MASK), ExEditorKit.StartLiteral),
KeyBinding(KeyStroke.getKeyStroke(KeyEvent.VK_Q, KeyEvent.CTRL_MASK), ExEditorKit.StartLiteral), KeyBinding(KeyStroke.getKeyStroke(KeyEvent.VK_Q, KeyEvent.CTRL_DOWN_MASK), ExEditorKit.StartLiteral),
KeyBinding(KeyStroke.getKeyStroke(KeyEvent.VK_R, KeyEvent.CTRL_MASK), ExEditorKit.InsertRegister), KeyBinding(KeyStroke.getKeyStroke(KeyEvent.VK_R, KeyEvent.CTRL_DOWN_MASK), ExEditorKit.InsertRegister),
// These appear to be non-Vim shortcuts // These appear to be non-Vim shortcuts
KeyBinding(KeyStroke.getKeyStroke(KeyEvent.VK_V, KeyEvent.META_MASK), ExEditorKit.pasteAction), KeyBinding(KeyStroke.getKeyStroke(KeyEvent.VK_V, KeyEvent.META_DOWN_MASK), ExEditorKit.pasteAction),
KeyBinding(KeyStroke.getKeyStroke(KeyEvent.VK_INSERT, KeyEvent.SHIFT_MASK), ExEditorKit.pasteAction) KeyBinding(KeyStroke.getKeyStroke(KeyEvent.VK_INSERT, KeyEvent.SHIFT_DOWN_MASK), ExEditorKit.pasteAction)
) )
} }
} }

View File

@@ -18,6 +18,7 @@
package com.maddyhome.idea.vim.ui; package com.maddyhome.idea.vim.ui;
import com.intellij.ide.IdeTooltip;
import com.intellij.ide.ui.LafManager; import com.intellij.ide.ui.LafManager;
import com.intellij.ide.ui.LafManagerListener; import com.intellij.ide.ui.LafManagerListener;
import com.intellij.openapi.application.ApplicationManager; import com.intellij.openapi.application.ApplicationManager;
@@ -133,6 +134,7 @@ public class ExOutputPanel extends JPanel implements LafManagerListener {
} }
myText.setText(data); myText.setText(data);
myText.setFont(UiHelper.selectFont(data));
myText.setCaretPosition(0); myText.setCaretPosition(0);
if (data.length() > 0) { if (data.length() > 0) {
activate(); activate();
@@ -197,9 +199,8 @@ public class ExOutputPanel extends JPanel implements LafManagerListener {
} }
private void setFontForElements() { private void setFontForElements() {
final Font font = UiHelper.getEditorFont(); myText.setFont(UiHelper.selectFont(myText.getText()));
myText.setFont(font); myLabel.setFont(UiHelper.selectFont(myLabel.getText()));
myLabel.setFont(font);
} }
private static int countLines(@NotNull String text) { private static int countLines(@NotNull String text) {
@@ -245,6 +246,7 @@ public class ExOutputPanel extends JPanel implements LafManagerListener {
private void badKey() { private void badKey() {
myLabel.setText("-- MORE -- (RET: line, SPACE: page, d: half page, q: quit)"); myLabel.setText("-- MORE -- (RET: line, SPACE: page, d: half page, q: quit)");
myLabel.setFont(UiHelper.selectFont(myLabel.getText()));
} }
private void scrollOffset(int more) { private void scrollOffset(int more) {
@@ -256,9 +258,11 @@ public class ExOutputPanel extends JPanel implements LafManagerListener {
myScrollPane.getVerticalScrollBar().getMaximum() - myScrollPane.getVerticalScrollBar().getVisibleAmount()) { myScrollPane.getVerticalScrollBar().getMaximum() - myScrollPane.getVerticalScrollBar().getVisibleAmount()) {
myAtEnd = true; myAtEnd = true;
myLabel.setText("Hit ENTER or type command to continue"); myLabel.setText("Hit ENTER or type command to continue");
myLabel.setFont(UiHelper.selectFont(myLabel.getText()));
} }
else { else {
myLabel.setText("-- MORE --"); myLabel.setText("-- MORE --");
myLabel.setFont(UiHelper.selectFont(myLabel.getText()));
} }
} }

View File

@@ -26,6 +26,7 @@ import com.intellij.openapi.util.Disposer;
import com.intellij.util.ui.JBUI; import com.intellij.util.ui.JBUI;
import com.maddyhome.idea.vim.VimPlugin; import com.maddyhome.idea.vim.VimPlugin;
import com.maddyhome.idea.vim.group.HistoryGroup; import com.maddyhome.idea.vim.group.HistoryGroup;
import com.maddyhome.idea.vim.helper.UiHelper;
import kotlin.text.StringsKt; import kotlin.text.StringsKt;
import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable; import org.jetbrains.annotations.Nullable;
@@ -188,8 +189,15 @@ public class ExTextField extends JTextField {
} }
} }
// fix https://youtrack.jetbrains.com/issue/VIM-570
private void resetFont(String string) {
super.setFont(UiHelper.selectFont(string));
}
private void updateText(String string) { private void updateText(String string) {
super.setText(string); super.setText(string);
resetFont(string);
} }
@Override @Override
@@ -197,6 +205,7 @@ public class ExTextField extends JTextField {
super.setText(string); super.setText(string);
saveLastEntry(); saveLastEntry();
resetFont(string);
} }
/** /**
@@ -245,7 +254,7 @@ public class ExTextField extends JTextField {
char c = keyChar; char c = keyChar;
final int modifiers = stroke.getModifiers(); final int modifiers = stroke.getModifiers();
final int keyCode = stroke.getKeyCode(); final int keyCode = stroke.getKeyCode();
if ((modifiers & KeyEvent.CTRL_MASK) != 0) { if ((modifiers & KeyEvent.CTRL_DOWN_MASK) != 0) {
final int codePoint = keyCode - KeyEvent.VK_A + 1; final int codePoint = keyCode - KeyEvent.VK_A + 1;
if (codePoint > 0) { if (codePoint > 0) {
c = Character.toChars(codePoint)[0]; c = Character.toChars(codePoint)[0];

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