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

Compare commits

..

88 Commits

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

5
.gitignore vendored
View File

@@ -4,7 +4,10 @@
/.idea/
!/.idea/scopes
!/.idea/copyright
!/.idea/icon.png
/build/
/out/
/tmp/
/tmp/
*.DS_Store

BIN
.idea/icon.png generated Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 113 KiB

View File

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

View File

@@ -22,10 +22,43 @@ 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
usual beta standards.
[To Be Released]
To Be Released
-------------
_Available since 0.56.1 EAP:_
_Available since 0.57.1 EAP:_
**Features:**
* `exchange` plugin emulation ([vim-exchange](https://github.com/tommcdo/vim-exchange)).
* `~/.ideavimrc` file can be reloaded using the new floating action.
* <details>
<summary><strong>Click to see details</strong></summary>
<img src="resources/changes/0.58/reload_ideavimrc.png" alt="IdeaVimRc reload"/>
</details>
* Add `:buffer` command.
**Changes:**
* Support IntelliJ's smooth scrolling. Use "Enable smooth scrolling" checkbox in _Preferences | Editor | General_ to disable.
**Fixes:**
* [VIM-1994](https://youtrack.jetbrains.com/issue/VIM-1994) Correct paste after `y}P` command.
* [VIM-1924](https://youtrack.jetbrains.com/issue/VIM-1924) Select next occurrence doesn't become block selection.
**Merged PRs:**
* [233](https://github.com/JetBrains/ideavim/pull/233) by [valis](https://github.com/valis): [VIM-1994] Correct paste after `y}P` command.
* [224](https://github.com/JetBrains/ideavim/pull/224) by [pmnoxx](https://github.com/pmnoxx): Populate intelij navigation history together with ideavim jumplist.
* [227](https://github.com/JetBrains/ideavim/pull/227) by [angelbot](https://github.com/angelbot): Add support for buffer command.
* [230](https://github.com/JetBrains/ideavim/pull/230) by [fan-tom](https://github.com/fan-tom): VIM-1924.
* [231](https://github.com/JetBrains/ideavim/pull/231) by [citizenmatt](https://github.com/citizenmatt): Support smooth scrolling.
_To Be Released..._
...
0.57, 2020-04-28
-------------
**Fixes:**
* [VIM-1992](https://youtrack.jetbrains.com/issue/VIM-1992) Fix mappings to `<S-Letter>`
@@ -34,8 +67,6 @@ _Available since 0.56.1 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
_To Be Released..._
0.56, 2020-04-09
--------------

View File

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

View File

@@ -3,14 +3,12 @@
IdeaVim
===
<div>
<a href="https://confluence.jetbrains.com/display/ALL/JetBrains+on+GitHub">
<img src="https://jb.gg/badges/official.svg" alt="official JetBrains project"/>
</a>
<a href="https://teamcity.jetbrains.com/viewType.html?buildTypeId=IdeaVim_TestsForIntelliJ20201&guest=1">
<img src="https://teamcity.jetbrains.com/app/rest/builds/buildType:(id:IdeaVim_TestsForIntelliJ20201)/statusIcon.svg?guest=1" alt="TeamCity Build"/>
</a>
</div>
[![Official JetBrains Project][jb-official-svg]][jb-official]
[![Downloads][plugin-downloads-svg]][plugin-repo]
[![Rating][plugin-rating-svg]][plugin-repo]
[![Version][plugin-version-svg]][plugin-repo]
[![Gitter][gitter-svg]][gitter]
[![Twitter][twitter-svg]][twitter]
IdeaVim is a Vim emulation plugin for IDEs based on the IntelliJ Platform.
IdeaVim can be used with IntelliJ IDEA, PyCharm, CLion, PhpStorm, WebStorm,
@@ -24,7 +22,6 @@ Resources:
* [Continuous integration builds](https://teamcity.jetbrains.com/project.html?projectId=IdeaVim&guest=1)
* [@IdeaVim](https://twitter.com/ideavim) in Twitter
Setup
------------
@@ -99,6 +96,7 @@ Supported:
* argtextobj.vim
* vim-textobj-entire
* ReplaceWithRegister
* vim-exchange [To Be Released]
Not supported (yet):
@@ -191,3 +189,19 @@ License
IdeaVim is licensed under the terms of the GNU Public License version 2
or any later version.
<!-- Badges -->
[jb-official]: https://confluence.jetbrains.com/display/ALL/JetBrains+on+GitHub
[jb-official-svg]: https://jb.gg/badges/official.svg
[plugin-repo]: https://plugins.jetbrains.com/plugin/164-ideavim
[plugin-downloads-svg]: http://img.shields.io/jetbrains/plugin/d/IdeaVIM
[plugin-rating-svg]: http://img.shields.io/jetbrains/plugin/r/rating/IdeaVIM
[plugin-version-svg]: https://img.shields.io/jetbrains/plugin/v/ideavim?label=version
[gitter-svg]: https://badges.gitter.im/JetBrains/ideavim.svg
[gitter]: https://gitter.im/JetBrains/ideavim?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge&utm_content=badge
[twitter]: https://twitter.com/ideavim
[twitter-svg]: https://img.shields.io/twitter/follow/ideavim?label=twitter%20%40ideavim

View File

@@ -37,11 +37,7 @@ intellij {
downloadSources Boolean.valueOf(downloadIdeaSources)
instrumentCode Boolean.valueOf(instrumentPluginCode)
intellijRepo = "https://www.jetbrains.com/intellij-repository"
if (!Boolean.valueOf(legacyNoJavaPlugin)) {
// Since 192 version of IJ java plugin should be defined separately
// Set `legacyNoJavaPlugin` to true if you are going to run tests under idea version < 192
plugins = ['java']
}
plugins = ['java']
publishPlugin {
channels publishChannels.split(',')
@@ -77,7 +73,7 @@ tasks.register("slackEapNotification") {
def changeLog = extractChangelog()
changeLog = changeLog.replace("* ", "• ") // Replace stars with bullets
changeLog = changeLog.replace("**", "*") // Enable bold text
changeLog = changeLog.replaceAll("\\[(.+)]\\(([^)]+)\\)", '<$2|$1>') // Enable links
changeLog = changeLog.replaceAll("\\[([^]]+)]\\(([^)]+)\\)", '<$2|$1>') // Enable links
def message ="""
{
"text": "New version of IdeaVim",

View File

@@ -53,6 +53,12 @@ Available extensions:
this syntax parenthesis, must come before angle brackets in the list.
* Emulates [argtextobj.vim](https://www.vim.org/scripts/script.php?script_id=2699)
* Additional text objects: `aa`, `ia`
## exchange [To Be Released]
* Setup: `set exchange`
* Emulates [vim-exchange](https://github.com/tommcdo/vim-exchange)
* Commands: `cx`, `cxx`, `X`, `cxc`
## textobj-entire

View File

@@ -118,8 +118,6 @@ The following `:set` commands can appear in `~/.ideavimrc` or be set manually in
- enabled - icon is shown in the status bar
- gray - use the gray version of the icon
- disabled - hide the icon
Works only from `~/.ideavimrc` after the IDE restart.
`lookupkeys` `lookupkeys` List of strings

View File

@@ -9,8 +9,5 @@ kotlinVersion=1.3.71
publishUsername=username
publishToken=token
publishChannels=eap
# Since 192 version of IJ java plugin should be defined separately
# Set this value to true if you are going to run tests under idea version < 192
legacyNoJavaPlugin=false
slackUrl=

View File

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

View File

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

View File

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

View File

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

Binary file not shown.

After

Width:  |  Height:  |  Size: 194 KiB

View File

@@ -56,7 +56,6 @@ public class EventFacade {
private static final @NotNull EventFacade ourInstance = new EventFacade();
private @Nullable TypedActionHandler myOriginalTypedActionHandler;
private Map<Project, MessageBusConnection> connections = new HashMap<>();
private EventFacade() {
}
@@ -92,31 +91,6 @@ public class EventFacade {
action.unregisterCustomShortcutSet(component);
}
public void connectFileEditorManagerListener(@NotNull Project project, @NotNull FileEditorManagerListener listener) {
final MessageBusConnection connection = getConnection(project);
connection.subscribe(FileEditorManagerListener.FILE_EDITOR_MANAGER, listener);
}
public void connectAnActionListener(@NotNull Project project, @NotNull AnActionListener listener) {
final MessageBusConnection connection = getConnection(project);
connection.subscribe(AnActionListener.TOPIC, listener);
}
public void connectTemplateStartedListener(@NotNull Project project, @NotNull TemplateManagerListener listener) {
final MessageBusConnection connection = getConnection(project);
connection.subscribe(TemplateManager.TEMPLATE_STARTED_TOPIC, listener);
}
public void connectBookmarkListener(@NotNull Project project, @NotNull BookmarksListener bookmarksListener) {
final MessageBusConnection connection = getConnection(project);
connection.subscribe(BookmarksListener.TOPIC, bookmarksListener);
}
public void connectFindModelListener(@NotNull Project project, @NotNull FindModelListener findModelListener) {
final MessageBusConnection connection = getConnection(project);
connection.subscribe(FindManager.FIND_MODEL_TOPIC, findModelListener);
}
public void addDocumentListener(@NotNull Document document, @NotNull DocumentListener listener) {
document.addDocumentListener(listener);
}
@@ -168,30 +142,14 @@ public class EventFacade {
}
public void registerLookupListener(@NotNull Project project, @NotNull PropertyChangeListener propertyChangeListener) {
LookupManager.getInstance(project).addPropertyChangeListener(propertyChangeListener, project);
VimProjectService parentDisposable = VimProjectService.getInstance(project);
LookupManager.getInstance(project).addPropertyChangeListener(propertyChangeListener, parentDisposable);
}
public void removeLookupListener(@NotNull Project project, @NotNull PropertyChangeListener propertyChangeListener) {
LookupManager.getInstance(project).removePropertyChangeListener(propertyChangeListener);
}
public void disableBusConnection() {
connections.values().forEach(MessageBusConnection::disconnect);
connections.clear();
}
private MessageBusConnection getConnection(Project project) {
if (!connections.containsKey(project)) {
final MessageBusConnection connection = project.getMessageBus().connect();
connections.put(project, connection);
Disposer.register(project, () -> {
connection.disconnect();
connections.remove(project);
});
}
return connections.get(project);
}
private @NotNull TypedAction getTypedAction() {
return EditorActionManager.getInstance().getTypedAction();
}

View File

@@ -18,17 +18,14 @@
package com.maddyhome.idea.vim
import com.intellij.openapi.project.DumbAware
import com.intellij.openapi.project.Project
import com.intellij.openapi.startup.StartupActivity
import com.maddyhome.idea.vim.listener.VimListenerManager
/**
* @author Alex Plate
*
* [VERSION UPDATE] 193+ Use StartupActivity.DumbAware
*/
class PluginStartup : StartupActivity, DumbAware {
class PluginStartup : StartupActivity.DumbAware {
private var firstInitializationOccurred = false

View File

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

View File

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

View File

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

View File

@@ -0,0 +1,39 @@
/*
* IdeaVim - Vim emulator for IDEs based on the IntelliJ platform
* Copyright (C) 2003-2020 The IdeaVim authors
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package com.maddyhome.idea.vim
import com.intellij.openapi.Disposable
import com.intellij.openapi.components.Service
import com.intellij.openapi.components.ServiceManager
import com.intellij.openapi.project.Project
@Service
class VimProjectService(val project: Project) : Disposable {
override fun dispose() {}
companion object {
@JvmStatic
fun getInstance(project: Project): VimProjectService {
return ServiceManager.getService(project, VimProjectService::class.java)
}
}
}
val Project.vimDisposable
get() = VimProjectService.getInstance(this)

View File

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

View File

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

View File

@@ -86,7 +86,7 @@ public class CommandState {
}
// Keep the compatibility with the IdeaVim-EasyMotion plugin before the stable release
@ApiStatus.ScheduledForRemoval(inVersion = "0.56")
@ApiStatus.ScheduledForRemoval(inVersion = "0.58")
@Deprecated
public MappingMode getMappingMode() {
return mappingState.getMappingMode();

View File

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

View File

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

View File

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

View File

@@ -0,0 +1,95 @@
/*
* IdeaVim - Vim emulator for IDEs based on the IntelliJ platform
* Copyright (C) 2003-2020 The IdeaVim authors
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package com.maddyhome.idea.vim.ex.handler
import com.intellij.openapi.actionSystem.DataContext
import com.intellij.openapi.actionSystem.PlatformDataKeys
import com.intellij.openapi.editor.Editor
import com.intellij.openapi.fileEditor.FileEditorManager
import com.maddyhome.idea.vim.VimPlugin
import com.maddyhome.idea.vim.ex.CommandHandler
import com.maddyhome.idea.vim.ex.ExCommand
import com.maddyhome.idea.vim.ex.flags
import com.maddyhome.idea.vim.helper.EditorHelper
/**
* Handles buffer, buf, bu, b.
*
* @author John Weigel
*/
class BufferHandler : CommandHandler.SingleExecution() {
override val argFlags = flags(RangeFlag.RANGE_FORBIDDEN, ArgumentFlag.ARGUMENT_OPTIONAL, Access.READ_ONLY)
override fun execute(editor: Editor, context: DataContext, cmd: ExCommand): Boolean {
val arg = cmd.argument.trim()
val overrideModified = arg.startsWith('!')
val buffer = if (overrideModified) arg.replace(Regex("^!\\s*"), "") else arg
var result = true
if (buffer.isNotEmpty()) {
if (buffer.matches(Regex("^\\d+$"))) {
val bufNum = buffer.toInt() - 1
if (!VimPlugin.getFile().selectFile(bufNum, context)) {
VimPlugin.showMessage("Buffer $bufNum does not exist")
result = false
}
} else {
val editors = findPartialMatch(context, buffer)
when(editors.size) {
0 -> {
VimPlugin.showMessage("No matching buffer for $buffer")
result = false
}
1 -> {
if (EditorHelper.hasUnsavedChanges(editor) && !overrideModified) {
VimPlugin.showMessage("No write since last change (add ! to override)")
result = false
}
else {
VimPlugin.getFile().openFile(EditorHelper.getVirtualFile(editors[0])!!.name, context)
}
}
else -> {
VimPlugin.showMessage("More than one match for $buffer")
result = false
}
}
}
}
return result
}
private fun findPartialMatch(context: DataContext, fileName: String): List<Editor> {
val matchedFiles = mutableListOf<Editor>()
val project = PlatformDataKeys.PROJECT.getData(context) ?: return matchedFiles
for (file in FileEditorManager.getInstance(project).openFiles) {
if (file.name.contains(fileName)) {
val editor = EditorHelper.getEditor(file) ?: continue
matchedFiles.add(editor)
}
}
return matchedFiles
}
}

View File

@@ -1,188 +0,0 @@
/*
* IdeaVim - Vim emulator for IDEs based on the IntelliJ platform
* Copyright (C) 2003-2020 The IdeaVim authors
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package com.maddyhome.idea.vim.ex.vimscript;
import com.maddyhome.idea.vim.ex.CommandHandler;
import com.maddyhome.idea.vim.ex.CommandParser;
import com.maddyhome.idea.vim.ex.ExCommand;
import com.maddyhome.idea.vim.ex.ExException;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import java.io.BufferedReader;
import java.io.File;
import java.io.FileReader;
import java.io.IOException;
import java.nio.file.Paths;
import java.util.Map;
import java.util.Objects;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
/**
* @author vlan
*/
public class VimScriptParser {
public static final String VIMRC_FILE_NAME = "ideavimrc";
public static final String[] HOME_VIMRC_PATHS = {"." + VIMRC_FILE_NAME, "_" + VIMRC_FILE_NAME};
public static final String XDG_VIMRC_PATH = "ideavim" + File.separator + VIMRC_FILE_NAME;
public static final int BUFSIZE = 4096;
private static final Pattern EOL_SPLIT_PATTERN = Pattern.compile(" *(\r\n|\n)+ *");
private static final Pattern DOUBLE_QUOTED_STRING = Pattern.compile("\"([^\"]*)\"");
private static final Pattern SINGLE_QUOTED_STRING = Pattern.compile("'([^']*)'");
private static final Pattern REFERENCE_EXPR = Pattern.compile("([A-Za-z_][A-Za-z_0-9]*)");
private static final Pattern DEC_NUMBER = Pattern.compile("(\\d+)");
private VimScriptParser() {
}
public static @Nullable File findIdeaVimRc() {
final String homeDirName = System.getProperty("user.home");
// Check whether file exists in home dir
if (homeDirName != null) {
for (String fileName : HOME_VIMRC_PATHS) {
final File file = new File(homeDirName, fileName);
if (file.exists()) {
return file;
}
}
}
// Check in XDG config directory
final String xdgConfigHomeProperty = System.getenv("XDG_CONFIG_HOME");
File xdgConfig = null;
if (xdgConfigHomeProperty == null || Objects.equals(xdgConfigHomeProperty, "")) {
if (homeDirName != null) {
xdgConfig = Paths.get(homeDirName, ".config", XDG_VIMRC_PATH).toFile();
}
} else {
xdgConfig = new File(xdgConfigHomeProperty, XDG_VIMRC_PATH);
}
if (xdgConfig != null && xdgConfig.exists()) {
return xdgConfig;
}
return null;
}
public static @Nullable File findOrCreateIdeaVimRc() {
final File found = findIdeaVimRc();
if (found != null) return found;
final String homeDirName = System.getProperty("user.home");
if (homeDirName != null) {
for (String fileName : HOME_VIMRC_PATHS) {
try {
final File file = new File(homeDirName, fileName);
//noinspection ResultOfMethodCallIgnored
file.createNewFile();
return file;
} catch (IOException ignored) {
// Try to create one of two files
}
}
}
return null;
}
public static void executeFile(@NotNull File file) {
final String data;
try {
data = readFile(file);
}
catch (IOException ignored) {
return;
}
executeText(data);
}
public static void executeText(@NotNull String text) {
for (String line : EOL_SPLIT_PATTERN.split(text)) {
// TODO: Build a proper parse tree for a VimL file instead of ignoring potentially nested lines (VIM-669)
if (line.startsWith(" ") || line.startsWith("\t")) {
continue;
}
if (line.startsWith(":")) {
line = line.substring(1);
}
final CommandParser commandParser = CommandParser.getInstance();
try {
final ExCommand command = commandParser.parse(line);
final CommandHandler commandHandler = commandParser.getCommandHandler(command);
if (commandHandler instanceof VimScriptCommandHandler) {
final VimScriptCommandHandler handler = (VimScriptCommandHandler)commandHandler;
handler.execute(command);
}
}
catch (ExException ignored) {
}
}
}
public static @NotNull Object evaluate(@NotNull String expression, @NotNull Map<String, Object> globals) throws ExException {
// This evaluator is very basic, no proper parsing whatsoever. It is here as the very first step necessary to
// support mapleader, VIM-650. See also VIM-669.
Matcher m;
m = DOUBLE_QUOTED_STRING.matcher(expression);
if (m.matches()) {
return m.group(1);
}
m = SINGLE_QUOTED_STRING.matcher(expression);
if (m.matches()) {
return m.group(1);
}
m = REFERENCE_EXPR.matcher(expression);
if (m.matches()) {
final String name = m.group(1);
final Object value = globals.get(name);
if (value != null) {
return value;
}
else {
throw new ExException(String.format("Undefined variable: %s", name));
}
}
m = DEC_NUMBER.matcher(expression);
if (m.matches()) {
return Integer.parseInt(m.group(1));
}
throw new ExException(String.format("Invalid expression: %s", expression));
}
public static @NotNull String expressionToString(@NotNull Object value) throws ExException {
// TODO: Return meaningful value representations
if (value instanceof String) {
return (String)value;
} else if (value instanceof Integer) {
return value.toString();
}
throw new ExException(String.format("Cannot convert '%s' to string", value));
}
private static @NotNull String readFile(@NotNull File file) throws IOException {
final BufferedReader reader = new BufferedReader(new FileReader(file));
final StringBuilder builder = new StringBuilder();
final char[] buffer = new char[BUFSIZE];
int n;
while ((n = reader.read(buffer)) > 0) {
builder.append(buffer, 0, n);
}
return builder.toString();
}
}

View File

@@ -0,0 +1,167 @@
/*
* IdeaVim - Vim emulator for IDEs based on the IntelliJ platform
* Copyright (C) 2003-2020 The IdeaVim authors
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package com.maddyhome.idea.vim.ex.vimscript
import com.maddyhome.idea.vim.ex.CommandParser
import com.maddyhome.idea.vim.ex.ExException
import com.maddyhome.idea.vim.ui.VimRcFileState
import java.io.File
import java.io.IOException
import java.nio.file.Paths
import java.util.regex.Matcher
import java.util.regex.Pattern
/**
* @author vlan
*/
object VimScriptParser {
const val VIMRC_FILE_NAME = "ideavimrc"
val HOME_VIMRC_PATHS = arrayOf(".$VIMRC_FILE_NAME", "_$VIMRC_FILE_NAME")
val XDG_VIMRC_PATH = "ideavim" + File.separator + VIMRC_FILE_NAME
private val DOUBLE_QUOTED_STRING = Pattern.compile("\"([^\"]*)\"")
private val SINGLE_QUOTED_STRING = Pattern.compile("'([^']*)'")
private val REFERENCE_EXPR = Pattern.compile("([A-Za-z_][A-Za-z_0-9]*)")
private val DEC_NUMBER = Pattern.compile("(\\d+)")
// This is a pattern used in ideavimrc parsing for a long time. It removes all trailing/leading spaced and blank lines
private val EOL_SPLIT_PATTERN = Pattern.compile(" *(\r\n|\n)+ *")
@JvmStatic
fun findIdeaVimRc(): File? {
val homeDirName = System.getProperty("user.home")
// Check whether file exists in home dir
if (homeDirName != null) {
for (fileName in HOME_VIMRC_PATHS) {
val file = File(homeDirName, fileName)
if (file.exists()) {
return file
}
}
}
// Check in XDG config directory
val xdgConfigHomeProperty = System.getenv("XDG_CONFIG_HOME")
val xdgConfig = if (xdgConfigHomeProperty == null || xdgConfigHomeProperty == "") {
if (homeDirName != null) Paths.get(homeDirName, ".config", XDG_VIMRC_PATH).toFile() else null
} else {
File(xdgConfigHomeProperty, XDG_VIMRC_PATH)
}
return if (xdgConfig != null && xdgConfig.exists()) xdgConfig else null
}
fun findOrCreateIdeaVimRc(): File? {
val found = findIdeaVimRc()
if (found != null) return found
val homeDirName = System.getProperty("user.home")
if (homeDirName != null) {
for (fileName in HOME_VIMRC_PATHS) {
try {
val file = File(homeDirName, fileName)
file.createNewFile()
VimRcFileState.filePath = file.absolutePath
return file
} catch (ignored: IOException) {
// Try to create one of two files
}
}
}
return null
}
@JvmStatic
fun executeFile(file: File): List<String> {
val data = try {
readFile(file)
} catch (ignored: IOException) {
return emptyList()
}
executeText(data)
return data
}
fun executeText(text: List<String>) {
for (line in text) {
// TODO: Build a proper parse tree for a VimL file instead of ignoring potentially nested lines (VIM-669)
if (line.startsWith(" ") || line.startsWith("\t")) continue
val lineToExecute = if (line.startsWith(":")) line.substring(1) else line
val commandParser = CommandParser.getInstance()
try {
val command = commandParser.parse(lineToExecute)
val commandHandler = commandParser.getCommandHandler(command)
if (commandHandler is VimScriptCommandHandler) {
commandHandler.execute(command)
}
} catch (ignored: ExException) {
}
}
}
@Throws(ExException::class)
fun evaluate(expression: String, globals: Map<String?, Any?>): Any {
// This evaluator is very basic, no proper parsing whatsoever. It is here as the very first step necessary to
// support mapleader, VIM-650. See also VIM-669.
var m: Matcher = DOUBLE_QUOTED_STRING.matcher(expression)
if (m.matches()) return m.group(1)
m = SINGLE_QUOTED_STRING.matcher(expression)
if (m.matches()) return m.group(1)
m = REFERENCE_EXPR.matcher(expression)
if (m.matches()) {
val name = m.group(1)
val value = globals[name]
return value ?: throw ExException("Undefined variable: $name")
}
m = DEC_NUMBER.matcher(expression)
if (m.matches()) return m.group(1).toInt()
throw ExException("Invalid expression: $expression")
}
@Throws(ExException::class)
fun expressionToString(value: Any): String {
// TODO: Return meaningful value representations
return when (value) {
is String -> value
is Int -> value.toString()
else -> throw ExException("Cannot convert '$value' to string")
}
}
@Throws(IOException::class)
fun readFile(file: File): List<String> {
val lines = ArrayList<String>()
file.forEachLine { line -> lineProcessor(line, lines) }
return lines
}
fun readText(data: CharSequence): List<String> {
val lines = ArrayList<String>()
EOL_SPLIT_PATTERN.split(data).forEach { line -> lineProcessor(line, lines) }
return lines
}
fun lineProcessor(line: String, lines: ArrayList<String>) {
val trimmedLine = line.trim()
if (trimmedLine.isBlank()) return
lines += trimmedLine
}
}

View File

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

View File

@@ -1,97 +0,0 @@
/*
* IdeaVim - Vim emulator for IDEs based on the IntelliJ platform
* Copyright (C) 2003-2020 The IdeaVim authors
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package com.maddyhome.idea.vim.extension;
import com.intellij.openapi.diagnostic.Logger;
import com.intellij.openapi.extensions.ExtensionPointListener;
import com.intellij.openapi.extensions.PluginDescriptor;
import com.maddyhome.idea.vim.key.MappingOwner;
import com.maddyhome.idea.vim.option.OptionsManager;
import com.maddyhome.idea.vim.option.ToggleOption;
import org.jetbrains.annotations.NotNull;
import java.util.HashSet;
import java.util.Set;
/**
* TODO [VERSION UPDATE] this file cannot be converted to kt before 192 because of nullabilities problems in
* [ExtensionPointListener]. (In previous versions of IJ pluginDescriptor was nullable)
*/
public class VimExtensionRegistrar {
private static final Set<String> registeredExtensions = new HashSet<>();
private static boolean extensionRegistered = false;
private static final Logger logger = Logger.getInstance(VimExtensionRegistrar.class);
public static void registerExtensions() {
if (extensionRegistered) return;
extensionRegistered = true;
// TODO: [VERSION UPDATE] since 191 use
// ExtensionPoint.addExtensionPointListener(ExtensionPointListener<T>, boolean, Disposable)
//noinspection deprecation
VimExtension.EP_NAME.getPoint(null).addExtensionPointListener(new ExtensionPointListener<VimExtension>() {
@Override
public void extensionAdded(@NotNull VimExtension extension, PluginDescriptor pluginDescriptor) {
registerExtension(extension);
}
@Override
public void extensionRemoved(@NotNull VimExtension extension, PluginDescriptor pluginDescriptor) {
unregisterExtension(extension);
}
});
}
private static synchronized void registerExtension(@NotNull VimExtension extension) {
String name = extension.getName();
if (registeredExtensions.contains(name)) return;
registeredExtensions.add(name);
ToggleOption option = new ToggleOption(name, name, false);
option.addOptionChangeListener((oldValue, newValue) -> {
for (VimExtension extensionInListener : VimExtension.EP_NAME.getExtensionList()) {
if (name.equals(extensionInListener.getName())) {
if (OptionsManager.INSTANCE.isSet(name)) {
extensionInListener.init();
logger.info("IdeaVim extension '" + name + "' initialized");
}
else {
extensionInListener.dispose();
}
}
}
});
OptionsManager.INSTANCE.addOption(option);
}
private static synchronized void unregisterExtension(@NotNull VimExtension extension) {
String name = extension.getName();
if (!registeredExtensions.contains(name)) return;
registeredExtensions.remove(name);
extension.dispose();
OptionsManager.INSTANCE.removeOption(name);
MappingOwner.Plugin.Companion.remove(name);
logger.info("IdeaVim extension '" + name + "' disposed");
}
}

View File

@@ -0,0 +1,82 @@
/*
* IdeaVim - Vim emulator for IDEs based on the IntelliJ platform
* Copyright (C) 2003-2020 The IdeaVim authors
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package com.maddyhome.idea.vim.extension
import com.intellij.openapi.diagnostic.logger
import com.intellij.openapi.extensions.ExtensionPointListener
import com.intellij.openapi.extensions.PluginDescriptor
import com.maddyhome.idea.vim.VimPlugin
import com.maddyhome.idea.vim.key.MappingOwner.Plugin.Companion.remove
import com.maddyhome.idea.vim.option.OptionsManager.addOption
import com.maddyhome.idea.vim.option.OptionsManager.isSet
import com.maddyhome.idea.vim.option.OptionsManager.removeOption
import com.maddyhome.idea.vim.option.ToggleOption
import java.util.*
object VimExtensionRegistrar {
private val registeredExtensions: MutableSet<String> = HashSet()
private var extensionRegistered = false
private val logger = logger<VimExtensionRegistrar>()
@JvmStatic
fun registerExtensions() {
if (extensionRegistered) return
extensionRegistered = true
VimExtension.EP_NAME.getPoint(null).addExtensionPointListener(object : ExtensionPointListener<VimExtension> {
override fun extensionAdded(extension: VimExtension, pluginDescriptor: PluginDescriptor) {
registerExtension(extension)
}
override fun extensionRemoved(extension: VimExtension, pluginDescriptor: PluginDescriptor) {
unregisterExtension(extension)
}
}, true, VimPlugin.getInstance())
}
@Synchronized
private fun registerExtension(extension: VimExtension) {
val name = extension.name
if (name in registeredExtensions) return
registeredExtensions.add(name)
val option = ToggleOption(name, name, false)
option.addOptionChangeListener { _, _ ->
for (extensionInListener in VimExtension.EP_NAME.extensionList) {
if (name != extensionInListener.name) continue
if (isSet(name)) {
extensionInListener.init()
logger.info("IdeaVim extension '$name' initialized")
} else {
extensionInListener.dispose()
}
}
}
addOption(option)
}
@Synchronized
private fun unregisterExtension(extension: VimExtension) {
val name = extension.name
if (name !in registeredExtensions) return
registeredExtensions.remove(name)
extension.dispose()
removeOption(name)
remove(name)
logger.info("IdeaVim extension '$name' disposed")
}
}

View File

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

View File

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

View File

@@ -32,7 +32,10 @@ import com.intellij.openapi.editor.*;
import com.intellij.openapi.editor.actionSystem.ActionPlan;
import com.intellij.openapi.editor.actionSystem.TypedActionHandler;
import com.intellij.openapi.editor.actionSystem.TypedActionHandlerEx;
import com.intellij.openapi.editor.event.*;
import com.intellij.openapi.editor.event.DocumentEvent;
import com.intellij.openapi.editor.event.DocumentListener;
import com.intellij.openapi.editor.event.EditorMouseEvent;
import com.intellij.openapi.editor.event.EditorMouseListener;
import com.intellij.openapi.editor.ex.EditorEx;
import com.intellij.openapi.editor.impl.TextRangeInterval;
import com.intellij.openapi.fileEditor.FileDocumentManager;
@@ -433,18 +436,12 @@ public class ChangeGroup {
}
};
public void editorCreated(@NotNull EditorFactoryEvent event) {
final Editor editor = event.getEditor();
public void editorCreated(Editor editor) {
EventFacade.getInstance().addEditorMouseListener(editor, listener);
UserDataManager.setVimChangeGroup(editor, true);
}
public void editorReleased(@NotNull EditorFactoryEvent event) {
final Editor editor = event.getEditor();
if (UserDataManager.getVimChangeGroup(editor)) {
EventFacade.getInstance().removeEditorMouseListener(editor, listener);
UserDataManager.setVimChangeGroup(editor, false);
}
public void editorReleased(Editor editor) {
EventFacade.getInstance().removeEditorMouseListener(editor, listener);
}
/**
@@ -1752,7 +1749,7 @@ public class ChangeGroup {
if (type != null) {
final int start = range.getStartOffset();
VimPlugin.getMark().setMark(editor, MarkGroup.MARK_CHANGE_POS, start);
VimPlugin.getMark().setChangeMarks(editor, new TextRange(start, start));
VimPlugin.getMark().setChangeMarks(editor, new TextRange(start, start+1));
}
return true;

View File

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

View File

@@ -39,6 +39,7 @@ import com.intellij.openapi.vfs.VirtualFile;
import com.intellij.openapi.vfs.VirtualFileVisitor;
import com.maddyhome.idea.vim.KeyHandler;
import com.maddyhome.idea.vim.VimPlugin;
import com.maddyhome.idea.vim.VimProjectService;
import com.maddyhome.idea.vim.command.CommandState;
import com.maddyhome.idea.vim.common.TextRange;
import com.maddyhome.idea.vim.helper.EditorHelper;
@@ -410,9 +411,8 @@ public class FileGroup {
lastSelections.put(event.getManager(), event.getOldFile());
String disposableKey = FileGroup.disposableKey + event.getManager().hashCode();
if (Disposer.get(disposableKey) == null) {
Disposer.register(event.getManager().getProject(), () -> {
lastSelections.remove(event.getManager());
}, disposableKey);
VimProjectService parentDisposable = VimProjectService.getInstance(event.getManager().getProject());
Disposer.register(parentDisposable, () -> lastSelections.remove(event.getManager()), disposableKey);
}
}
}

View File

@@ -34,6 +34,7 @@ import com.intellij.openapi.editor.event.DocumentEvent;
import com.intellij.openapi.editor.event.DocumentListener;
import com.intellij.openapi.editor.event.EditorFactoryEvent;
import com.intellij.openapi.fileEditor.FileDocumentManager;
import com.intellij.openapi.fileEditor.ex.IdeDocumentHistory;
import com.intellij.openapi.project.Project;
import com.intellij.openapi.util.text.StringUtil;
import com.intellij.openapi.vfs.VirtualFile;
@@ -81,6 +82,11 @@ public class MarkGroup implements PersistentStateComponent<Element> {
public void saveJumpLocation(@NotNull Editor editor) {
addJump(editor, true);
setMark(editor, '\'');
Project project = editor.getProject();
if (project != null) {
IdeDocumentHistory.getInstance(project).includeCurrentCommandAsNavigation();
}
}
/**
@@ -259,7 +265,7 @@ public class MarkGroup implements PersistentStateComponent<Element> {
public void setChangeMarks(@NotNull Editor editor, @NotNull TextRange range) {
setMark(editor, MARK_CHANGE_START, range.getStartOffset());
setMark(editor, MARK_CHANGE_END, range.getEndOffset());
setMark(editor, MARK_CHANGE_END, range.getEndOffset()-1);
}
public @Nullable TextRange getChangeMarks(@NotNull Editor editor) {
@@ -276,7 +282,7 @@ public class MarkGroup implements PersistentStateComponent<Element> {
if (start != null && end != null) {
final int startOffset = EditorHelper.getOffset(editor, start.getLogicalLine(), start.getCol());
final int endOffset = EditorHelper.getOffset(editor, end.getLogicalLine(), end.getCol());
return new TextRange(startOffset, endOffset);
return new TextRange(startOffset, endOffset+1);
}
return null;
}
@@ -756,12 +762,14 @@ public class MarkGroup implements PersistentStateComponent<Element> {
@Override
public void bookmarkAdded(@NotNull Bookmark b) {
if (!VimPlugin.isEnabled()) return;
if (!OptionsManager.INSTANCE.getIdeamarks().isSet()) return;
bookmarkTemplate = b;
}
@Override
public void bookmarkRemoved(@NotNull Bookmark b) {
if (!VimPlugin.isEnabled()) return;
if (!OptionsManager.INSTANCE.getIdeamarks().isSet()) return;
char ch = b.getMnemonic();
@@ -775,6 +783,7 @@ public class MarkGroup implements PersistentStateComponent<Element> {
@Override
public void bookmarkChanged(@NotNull Bookmark b) {
if (!VimPlugin.isEnabled()) return;
/* IJ sets named marks in two steps. Firstly it creates an unnamed mark, then adds a mnemonic */
if (!OptionsManager.INSTANCE.getIdeamarks().isSet()) return;
if (b != bookmarkTemplate) return;

View File

@@ -68,28 +68,6 @@ public class MotionGroup {
public static final int LAST_t = 4;
public static final int LAST_COLUMN = 9999;
public void editorCreated(@NotNull EditorFactoryEvent event) {
final Editor editor = event.getEditor();
// This ridiculous code ensures that a lot of events are processed BEFORE we finally start listening
// to visible area changes. The primary reason for this change is to fix the cursor position bug
// using the gd and gD commands (Goto Declaration). This bug has been around since Idea 6.0.4?
// Prior to this change the visible area code was moving the cursor around during file load and messing
// with the cursor position of the Goto Declaration processing.
ApplicationManager.getApplication().invokeLater(() -> ApplicationManager.getApplication()
.invokeLater(() -> ApplicationManager.getApplication().invokeLater(() -> {
VimListenerManager.EditorListeners.add(editor);
UserDataManager.setVimMotionGroup(editor, true);
})));
}
public void editorReleased(@NotNull EditorFactoryEvent event) {
Editor editor = event.getEditor();
if (UserDataManager.getVimMotionGroup(editor)) {
VimListenerManager.EditorListeners.remove(editor);
UserDataManager.setVimMotionGroup(editor, false);
}
}
/**
* This helper method calculates the complete range a motion will move over taking into account whether
* the motion is FLAG_MOT_LINEWISE or FLAG_MOT_CHARACTERWISE (FLAG_MOT_INCLUSIVE or FLAG_MOT_EXCLUSIVE).
@@ -259,7 +237,7 @@ public class MotionGroup {
}
private static int getScrollScreenTargetCaretVisualLine(final @NotNull Editor editor, int rawCount, boolean down) {
final Rectangle visibleArea = editor.getScrollingModel().getVisibleArea();
final Rectangle visibleArea = EditorHelper.getVisibleArea(editor);
final int caretVisualLine = editor.getCaretModel().getVisualPosition().line;
final int scrollOption = getScrollOption(rawCount);
@@ -890,7 +868,7 @@ public class MotionGroup {
}
private static void scrollColumnToLeftOfScreen(@NotNull Editor editor, int column) {
editor.getScrollingModel().scrollHorizontally(column * EditorHelper.getColumnWidth(editor));
EditorHelper.scrollHorizontally(editor, column * EditorHelper.getColumnWidth(editor));
}
public int moveCaretToMiddleColumn(@NotNull Editor editor, @NotNull Caret caret) {
@@ -1106,8 +1084,7 @@ public class MotionGroup {
return false;
}
final ScrollingModel scrollingModel = editor.getScrollingModel();
final Rectangle visibleArea = scrollingModel.getVisibleArea();
final Rectangle visibleArea = EditorHelper.getVisibleArea(editor);
int targetCaretVisualLine = getScrollScreenTargetCaretVisualLine(editor, rawCount, down);
@@ -1128,7 +1105,7 @@ public class MotionGroup {
}
if (moved) {
// We'll keep the caret at the same position, although that might not be the same line offset as previously
targetCaretVisualLine = editor.yToVisualLine(yInitialCaret + scrollingModel.getVisibleArea().y - yPrevious);
targetCaretVisualLine = editor.yToVisualLine(yInitialCaret + EditorHelper.getVisibleArea(editor).y - yPrevious);
}
}
else {

View File

@@ -20,7 +20,7 @@ package com.maddyhome.idea.vim.group
import com.intellij.icons.AllIcons
import com.intellij.ide.actions.OpenFileAction
import com.intellij.ide.actions.ShowFilePathAction
import com.intellij.ide.actions.RevealFileAction
import com.intellij.ide.browsers.BrowserLauncher
import com.intellij.notification.Notification
import com.intellij.notification.NotificationDisplayType
@@ -198,18 +198,14 @@ class NotificationService(private val project: Project?) {
private fun createIdeaVimRcManually(message: String, project: Project?) {
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 " + RevealFileAction.getFileManagerName()
if (!File(System.getProperty("user.home")).exists()) {
actionName = ""
}
notification.addAction(object : AnAction(actionName) {
override fun actionPerformed(e: AnActionEvent) {
val homeDir = File(System.getProperty("user.home"))
@Suppress("DEPRECATION", "UnstableApiUsage")
// [VERSION UPDATE] 193+ com.intellij.ide.actions.RevealFileAction.openDirectory
ShowFilePathAction.openDirectory(homeDir)
RevealFileAction.openDirectory(homeDir)
notification.expire()
}
})

View File

@@ -251,7 +251,7 @@ public class RegisterGroup implements PersistentStateComponent<Element> {
}
if (start != -1) {
VimPlugin.getMark().setChangeMarks(editor, new TextRange(start, Math.max(end - 1, 0)));
VimPlugin.getMark().setChangeMarks(editor, new TextRange(start, end));
}
return true;
@@ -414,6 +414,10 @@ public class RegisterGroup implements PersistentStateComponent<Element> {
registers.put(register, new Register(register, SelectionType.CHARACTER_WISE, keys));
}
public void setKeys(char register, @NotNull List<KeyStroke> keys, SelectionType type) {
registers.put(register, new Register(register, type, keys));
}
public void finishRecording(Editor editor) {
if (recordRegister != 0) {
Register reg = null;

View File

@@ -396,7 +396,9 @@ public class SearchGroup implements PersistentStateComponent<Element> {
int initialOffset, @Nullable LineRange searchRange, boolean forwards, boolean forceUpdate) {
int currentMatchOffset = -1;
Project[] projects = ProjectManager.getInstance().getOpenProjects();
ProjectManager projectManager = ProjectManager.getInstanceIfCreated();
if (projectManager == null) return currentMatchOffset;
Project[] projects = projectManager.getOpenProjects();
for (Project project : projects) {
Editor current = FileEditorManager.getInstance(project).getSelectedTextEditor();
Editor[] editors = current == null ? null : EditorFactory.getInstance().getEditors(current.getDocument(), project);

View File

@@ -135,7 +135,7 @@ class PutGroup {
if (data.visualSelection != null) {
val offset = editor.caretModel.primaryCaret.offset
VimPlugin.getMark().setMark(editor, MarkGroup.MARK_CHANGE_POS, offset)
VimPlugin.getMark().setChangeMarks(editor, TextRange(offset, offset))
VimPlugin.getMark().setChangeMarks(editor, TextRange(offset, offset+1))
}
return null
}
@@ -144,7 +144,7 @@ class PutGroup {
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)
if (data.textData.typeInRegister.isChar && text.lastOrNull() == '\n' && data.visualSelection?.typeInEditor?.isLine == false) text = text.dropLast(1)
return ProcessedTextData(text, data.textData.typeInRegister, data.textData.transferableData)
}

View File

@@ -18,6 +18,7 @@
package com.maddyhome.idea.vim.group.visual
import com.intellij.find.FindManager
import com.intellij.openapi.editor.Caret
import com.intellij.openapi.editor.Editor
import com.intellij.openapi.editor.LogicalPosition
@@ -53,7 +54,7 @@ class VisualMotionGroup {
editor.commandState.pushModes(CommandState.Mode.VISUAL, lastSelectionType.toSubMode())
val primaryCaret = editor.caretModel.primaryCaret
primaryCaret.vimSetSelection(visualMarks.startOffset, visualMarks.endOffset, true)
primaryCaret.vimSetSelection(visualMarks.startOffset, visualMarks.endOffset-1, true)
editor.scrollingModel.scrollToCaret(ScrollType.CENTER)
@@ -216,6 +217,9 @@ class VisualMotionGroup {
}
fun autodetectVisualSubmode(editor: Editor): CommandState.SubMode {
// IJ specific. See https://youtrack.jetbrains.com/issue/VIM-1924.
if (FindManager.getInstance(editor.project).selectNextOccurrenceWasPerformed()) return CommandState.SubMode.VISUAL_CHARACTER
if (editor.caretModel.caretCount > 1 && seemsLikeBlockMode(editor)) {
return CommandState.SubMode.VISUAL_BLOCK
}

View File

@@ -60,9 +60,7 @@ class ActionBeanClass : AbstractExtensionPointBean() {
val actionId: String get() = implementation?.let { EditorActionHandlerBase.getActionId(it) } ?: ""
val action: EditorActionHandlerBase by lazy {
// FIXME. [VERSION UPDATE] change to instantiateClass for 193+
@Suppress("DEPRECATION")
this.instantiate<EditorActionHandlerBase>(
this.instantiateClass<EditorActionHandlerBase>(
implementation ?: "", ApplicationManager.getApplication().picoContainer)
}

View File

@@ -156,9 +156,8 @@ sealed class VisualOperatorActionHandler : EditorActionHandlerBase(false) {
}
private fun Editor.collectSelections(): Map<Caret, VimSelection>? {
return when {
this.inRepeatMode -> {
!this.inVisualMode && this.inRepeatMode -> {
if (this.vimLastSelectionType == SelectionType.BLOCK_WISE) {
val primaryCaret = caretModel.primaryCaret
val range = primaryCaret.vimLastVisualOperatorRange ?: return null
@@ -232,7 +231,12 @@ sealed class VisualOperatorActionHandler : EditorActionHandlerBase(false) {
if (res) {
VimRepeater.saveLastChange(cmd)
VimRepeater.repeatHandler = false
editor.vimForEachCaret { caret -> visualChanges[caret]?.let { caret.vimLastVisualOperatorRange = it } }
editor.vimForEachCaret { caret ->
val visualChange = visualChanges[caret]
if (visualChange != null) {
caret.vimLastVisualOperatorRange = visualChange
}
}
editor.caretModel.allCarets.forEach { it.vimLastColumn = it.visualPosition.column }
}

View File

@@ -42,19 +42,35 @@ import static java.lang.Integer.max;
* This is a set of helper methods for working with editors. All line and column values are zero based.
*/
public class EditorHelper {
public static @NotNull Rectangle getVisibleArea(final @NotNull Editor editor) {
return editor.getScrollingModel().getVisibleAreaOnScrollingFinished();
}
public static boolean scrollVertically(@NotNull Editor editor, int verticalOffset) {
final ScrollingModel scrollingModel = editor.getScrollingModel();
final Rectangle area = scrollingModel.getVisibleAreaOnScrollingFinished();
scrollingModel.scroll(area.x, verticalOffset);
return scrollingModel.getVisibleAreaOnScrollingFinished().y != area.y;
}
public static void scrollHorizontally(@NotNull Editor editor, int horizontalOffset) {
final ScrollingModel scrollingModel = editor.getScrollingModel();
final Rectangle area = scrollingModel.getVisibleAreaOnScrollingFinished();
scrollingModel.scroll(horizontalOffset, area.y);
}
public static int getVisualLineAtTopOfScreen(final @NotNull Editor editor) {
final Rectangle visibleArea = editor.getScrollingModel().getVisibleArea();
final Rectangle visibleArea = getVisibleArea(editor);
return getFullVisualLine(editor, visibleArea.y, visibleArea.y, visibleArea.y + visibleArea.height);
}
public static int getVisualLineAtMiddleOfScreen(final @NotNull Editor editor) {
final ScrollingModel scrollingModel = editor.getScrollingModel();
final Rectangle visibleArea = scrollingModel.getVisibleArea();
final Rectangle visibleArea = getVisibleArea(editor);
return editor.yToVisualLine(visibleArea.y + (visibleArea.height / 2));
}
public static int getVisualLineAtBottomOfScreen(final @NotNull Editor editor) {
final Rectangle visibleArea = editor.getScrollingModel().getVisibleArea();
final Rectangle visibleArea = getVisibleArea(editor);
return getFullVisualLine(editor, visibleArea.y + visibleArea.height, visibleArea.y, visibleArea.y + visibleArea.height);
}
@@ -178,9 +194,8 @@ public class EditorHelper {
*/
private static int getApproximateScreenHeight(final @NotNull Editor editor) {
int lh = editor.getLineHeight();
int height = editor.getScrollingModel().getVisibleArea().y +
editor.getScrollingModel().getVisibleArea().height -
getVisualLineAtTopOfScreen(editor) * lh;
Rectangle area = getVisibleArea(editor);
int height = area.y + area.height - getVisualLineAtTopOfScreen(editor) * lh;
return height / lh;
}
@@ -191,7 +206,7 @@ public class EditorHelper {
* @return The number of screen columns
*/
public static int getScreenWidth(final @NotNull Editor editor) {
Rectangle rect = editor.getScrollingModel().getVisibleArea();
Rectangle rect = getVisibleArea(editor);
Point pt = new Point(rect.width, 0);
VisualPosition vp = editor.xyToVisualPosition(pt);
@@ -205,7 +220,7 @@ public class EditorHelper {
* @return The number of pixels
*/
public static int getColumnWidth(final @NotNull Editor editor) {
Rectangle rect = editor.getScrollingModel().getVisibleArea();
Rectangle rect = getVisibleArea(editor);
if (rect.width == 0) return 0;
Point pt = new Point(rect.width, 0);
VisualPosition vp = editor.xyToVisualPosition(pt);
@@ -223,7 +238,7 @@ public class EditorHelper {
public static int getVisualColumnAtLeftOfScreen(final @NotNull Editor editor) {
int cw = getColumnWidth(editor);
if (cw == 0) return 0;
return (editor.getScrollingModel().getHorizontalScrollOffset() + cw - 1) / cw;
return (getVisibleArea(editor).x + cw - 1) / cw;
}
/**
@@ -596,8 +611,7 @@ public class EditorHelper {
* @param visualLine The visual line to scroll to the current caret location
*/
public static void scrollVisualLineToCaretLocation(final @NotNull Editor editor, int visualLine) {
final ScrollingModel scrollingModel = editor.getScrollingModel();
final Rectangle visibleArea = scrollingModel.getVisibleArea();
final Rectangle visibleArea = getVisibleArea(editor);
final int caretScreenOffset = editor.visualLineToY(editor.getCaretModel().getVisualPosition().line) - visibleArea.y;
final int yVisualLine = editor.visualLineToY(visualLine);
@@ -615,7 +629,7 @@ public class EditorHelper {
inlayOffset = -bottomInlayHeight;
}
scrollingModel.scrollVertically(yVisualLine - caretScreenOffset - inlayOffset);
scrollVertically(editor, yVisualLine - caretScreenOffset - inlayOffset);
}
/**
@@ -627,13 +641,9 @@ public class EditorHelper {
* @return Returns true if the window was moved
*/
public static boolean scrollVisualLineToTopOfScreen(final @NotNull Editor editor, int visualLine) {
final ScrollingModel scrollingModel = editor.getScrollingModel();
int inlayHeight = getHeightOfVisualLineInlays(editor, visualLine, true);
int y = editor.visualLineToY(visualLine) - inlayHeight;
int verticalPos = scrollingModel.getVerticalScrollOffset();
scrollingModel.scrollVertically(y);
return verticalPos != scrollingModel.getVerticalScrollOffset();
return scrollVertically(editor, y);
}
/**
@@ -643,11 +653,10 @@ public class EditorHelper {
* @param visualLine The visual line to place in the middle of the current window
*/
public static void scrollVisualLineToMiddleOfScreen(@NotNull Editor editor, int visualLine) {
final ScrollingModel scrollingModel = editor.getScrollingModel();
int y = editor.visualLineToY(visualLine);
int lineHeight = editor.getLineHeight();
int height = scrollingModel.getVisibleArea().height;
scrollingModel.scrollVertically(y - ((height - lineHeight) / 2));
int height = getVisibleArea(editor).height;
scrollVertically(editor, y - ((height - lineHeight) / 2));
}
/**
@@ -661,7 +670,6 @@ public class EditorHelper {
* @return True if the editor was scrolled
*/
public static boolean scrollVisualLineToBottomOfScreen(@NotNull Editor editor, int visualLine) {
final ScrollingModel scrollingModel = editor.getScrollingModel();
int inlayHeight = getHeightOfVisualLineInlays(editor, visualLine, false);
int exPanelHeight = 0;
int exPanelWithoutShortcutsHeight = 0;
@@ -672,14 +680,9 @@ public class EditorHelper {
exPanelWithoutShortcutsHeight = ExEntryPanel.getInstanceWithoutShortcuts().getHeight();
}
int y = editor.visualLineToY(visualLine);
int verticalPos = scrollingModel.getVerticalScrollOffset();
int height = inlayHeight + editor.getLineHeight() + exPanelHeight + exPanelWithoutShortcutsHeight;
Rectangle visibleArea = scrollingModel.getVisibleArea();
scrollingModel.scrollVertically(y - visibleArea.height + height);
return verticalPos != scrollingModel.getVerticalScrollOffset();
Rectangle visibleArea = getVisibleArea(editor);
return scrollVertically(editor, y - visibleArea.height + height);
}
/**
@@ -734,7 +737,7 @@ public class EditorHelper {
}
private static int scrollFullPageDown(final @NotNull Editor editor, int pages) {
final Rectangle visibleArea = editor.getScrollingModel().getVisibleArea();
final Rectangle visibleArea = getVisibleArea(editor);
final int lineCount = getVisualLineCount(editor);
if (editor.getCaretModel().getVisualPosition().line == lineCount - 1)
@@ -777,7 +780,7 @@ public class EditorHelper {
}
private static int scrollFullPageUp(final @NotNull Editor editor, int pages) {
final Rectangle visibleArea = editor.getScrollingModel().getVisibleArea();
final Rectangle visibleArea = getVisibleArea(editor);
final int lineHeight = editor.getLineHeight();
int y = visibleArea.y;
@@ -851,4 +854,21 @@ public class EditorHelper {
public static boolean isDiffEditor(@NotNull Editor editor) {
return editor.getEditorKind() == EditorKind.DIFF;
}
/**
* Checks if the document in the editor is modified.
*/
public static boolean hasUnsavedChanges(@NotNull Editor editor) {
int line = 0;
Document document = editor.getDocument();
while (line < document.getLineCount()) {
if (document.isLineModified(line)) {
return true;
}
line++;
}
return false;
}
}

View File

@@ -27,11 +27,11 @@ import java.util.*
/**
* This annotation is created for test functions (methods).
* It means that original vim behavior has small differences from behavior of IdeaVim.
* It means that the original vim behavior has small differences from behavior of IdeaVim.
* [shouldBeFixed] flag indicates whether the given functionality should be fixed
* or the given behavior is normal for IdeaVim and should be leaved as is.
*
* E.g. after execution some commands original vim has next text:
* E.g. after execution of some commands original vim has the following text:
* Hello1
* Hello2
* Hello3
@@ -42,9 +42,11 @@ import java.util.*
* Hello2
* Hello3
*
* Why this annotation exists?
* In this case you should still create the test function and mark this function with [VimBehaviorDiffers] annotation.
*
* Why does this annotation exist?
* After creating some functionality you can understand that IdeaVim has a bit different behavior, but you
* cannot fix it right now because of any reasons (bugs in IDE,
* cannot fix it right now because of any reason (bugs in IDE,
* the impossibility of this functionality in IDEA (*[shouldBeFixed] == false*), leak of time for fixing).
* In that case, you should NOT remove the corresponding test or leave it without any marks that this test
* not fully convenient with vim, but leave the test with IdeaVim's behavior and put this annotation

View File

@@ -0,0 +1,76 @@
/*
* IdeaVim - Vim emulator for IDEs based on the IntelliJ platform
* Copyright (C) 2003-2020 The IdeaVim authors
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package com.maddyhome.idea.vim.helper
import com.intellij.ide.util.PropertiesComponent
import com.intellij.openapi.application.ApplicationInfo
import com.intellij.openapi.application.ApplicationManager
import com.intellij.openapi.application.PermanentInstallationID
import com.intellij.openapi.diagnostic.logger
import com.intellij.openapi.util.JDOMUtil
import com.intellij.openapi.util.SystemInfo
import com.intellij.openapi.vfs.CharsetToolkit
import com.intellij.util.io.HttpRequests
import com.maddyhome.idea.vim.VimPlugin
import org.jdom.JDOMException
import java.io.IOException
import java.net.URLEncoder
import java.util.concurrent.TimeUnit
object StatisticReporter {
private val logger = logger<StatisticReporter>()
private const val IDEAVIM_STATISTICS_TIMESTAMP_KEY = "ideavim.statistics.timestamp"
private val DAY_IN_MILLIS = TimeUnit.DAYS.toMillis(1)
/**
* Reports statistics about installed IdeaVim and enabled Vim emulation.
*
* See https://github.com/go-lang-plugin-org/go-lang-idea-plugin/commit/5182ab4a1d01ad37f6786268a2fe5e908575a217
*/
fun report() {
val lastUpdate = PropertiesComponent.getInstance().getLong(IDEAVIM_STATISTICS_TIMESTAMP_KEY, 0)
val outOfDate = lastUpdate == 0L || System.currentTimeMillis() - lastUpdate > DAY_IN_MILLIS
if (!outOfDate || !VimPlugin.isEnabled()) return
ApplicationManager.getApplication().executeOnPooledThread {
try {
val buildNumber = ApplicationInfo.getInstance().build.asString()
val version = URLEncoder.encode(VimPlugin.getVersion(), CharsetToolkit.UTF8)
val os = URLEncoder.encode("${SystemInfo.OS_NAME} ${SystemInfo.OS_VERSION}", CharsetToolkit.UTF8)
val uid = PermanentInstallationID.get()
val url = "https://plugins.jetbrains.com/plugins/list?pluginId=${VimPlugin.getPluginId().idString}&build=$buildNumber&pluginVersion=$version&os=$os&uuid=$uid"
PropertiesComponent.getInstance().setValue(IDEAVIM_STATISTICS_TIMESTAMP_KEY, System.currentTimeMillis().toString())
HttpRequests.request(url).connect { request: HttpRequests.Request ->
logger.info("Sending statistics: $url")
try {
JDOMUtil.load(request.inputStream)
} catch (e: JDOMException) {
logger.warn(e)
}
}
} catch (e: IOException) {
logger.warn(e)
}
}
}
}

View File

@@ -86,8 +86,6 @@ var Editor.vimIncsearchCurrentMatchOffset: Int? by userData()
*/
var Editor.vimLastSelectionType: SelectionType? by userData()
var Editor.vimCommandState: CommandState? by userData()
var Editor.vimChangeGroup: Boolean by userDataOr { false }
var Editor.vimMotionGroup: Boolean by userDataOr { false }
var Editor.vimEditorGroup: Boolean by userDataOr { false }
var Editor.vimLineNumbersInitialState: Boolean by userDataOr { false }
var Editor.vimHasRelativeLineNumbersInstalled: Boolean by userDataOr { false }

View File

@@ -55,9 +55,6 @@ import java.beans.PropertyChangeListener
*/
object IdeaSpecifics {
fun addIdeaSpecificsListeners(project: Project) {
EventFacade.getInstance().connectAnActionListener(project, VimActionListener)
EventFacade.getInstance().connectTemplateStartedListener(project, VimTemplateManagerListener)
EventFacade.getInstance().connectFindModelListener(project, VimFindModelListener)
EventFacade.getInstance().registerLookupListener(project, LookupListener)
}
@@ -65,15 +62,17 @@ object IdeaSpecifics {
EventFacade.getInstance().removeLookupListener(project, LookupListener)
}
private object VimActionListener : AnActionListener {
class VimActionListener : AnActionListener {
private val surrounderItems = listOf("if", "if / else", "for")
private val surrounderAction = "com.intellij.codeInsight.generation.surroundWith.SurroundWithHandler\$InvokeSurrounderAction"
private var editor: Editor? = null
override fun beforeActionPerformed(action: AnAction, dataContext: DataContext, event: AnActionEvent) {
if (!VimPlugin.isEnabled()) return
editor = dataContext.getData(CommonDataKeys.EDITOR) ?: return
}
override fun afterActionPerformed(action: AnAction, dataContext: DataContext, event: AnActionEvent) {
if (!VimPlugin.isEnabled()) return
//region Extend Selection for Rider
when (ActionManager.getInstance().getId(action)) {
IdeActions.ACTION_EDITOR_SELECT_WORD_AT_CARET, IdeActions.ACTION_EDITOR_UNSELECT_WORD_AT_CARET -> {
@@ -107,8 +106,9 @@ object IdeaSpecifics {
}
//region Enter insert mode for surround templates without selection
private object VimTemplateManagerListener : TemplateManagerListener {
class VimTemplateManagerListener : TemplateManagerListener {
override fun templateStarted(state: TemplateState) {
if (!VimPlugin.isEnabled()) return
val editor = state.editor ?: return
state.addTemplateStateListener(object : TemplateEditingAdapter() {
@@ -157,8 +157,9 @@ object IdeaSpecifics {
//endregion
//region Hide Vim search highlights when showing IntelliJ search results
private object VimFindModelListener : FindModelListener {
class VimFindModelListener : FindModelListener {
override fun findNextModelChanged() {
if (!VimPlugin.isEnabled()) return
VimPlugin.getSearch().clearSearchHighlight()
}
}

View File

@@ -18,12 +18,11 @@
package com.maddyhome.idea.vim.listener
import com.intellij.openapi.application.ApplicationManager
import com.intellij.openapi.diagnostic.Logger
import com.intellij.openapi.editor.Caret
import com.intellij.openapi.editor.Editor
import com.intellij.openapi.editor.EditorFactory
import com.intellij.openapi.editor.actionSystem.EditorActionManager
import com.intellij.openapi.editor.actionSystem.TypedAction
import com.intellij.openapi.editor.event.EditorFactoryEvent
import com.intellij.openapi.editor.event.EditorFactoryListener
import com.intellij.openapi.editor.event.EditorMouseEvent
@@ -48,7 +47,6 @@ import com.maddyhome.idea.vim.ex.ExOutputModel
import com.maddyhome.idea.vim.group.ChangeGroup
import com.maddyhome.idea.vim.group.EditorGroup
import com.maddyhome.idea.vim.group.FileGroup
import com.maddyhome.idea.vim.group.MarkGroup
import com.maddyhome.idea.vim.group.MotionGroup
import com.maddyhome.idea.vim.group.SearchGroup
import com.maddyhome.idea.vim.group.visual.IdeaSelectionControl
@@ -56,6 +54,7 @@ import com.maddyhome.idea.vim.group.visual.VimVisualTimer
import com.maddyhome.idea.vim.group.visual.moveCaretOneCharLeftFromSelectionEnd
import com.maddyhome.idea.vim.group.visual.vimSetSystemSelectionSilently
import com.maddyhome.idea.vim.helper.EditorHelper
import com.maddyhome.idea.vim.helper.StatisticReporter
import com.maddyhome.idea.vim.helper.exitSelectMode
import com.maddyhome.idea.vim.helper.exitVisualMode
import com.maddyhome.idea.vim.helper.inSelectMode
@@ -63,7 +62,8 @@ import com.maddyhome.idea.vim.helper.inVisualMode
import com.maddyhome.idea.vim.helper.isEndAllowed
import com.maddyhome.idea.vim.helper.subMode
import com.maddyhome.idea.vim.helper.vimLastColumn
import com.maddyhome.idea.vim.helper.vimMotionGroup
import com.maddyhome.idea.vim.listener.VimListenerManager.EditorListeners.add
import com.maddyhome.idea.vim.listener.VimListenerManager.EditorListeners.remove
import com.maddyhome.idea.vim.option.OptionsManager
import com.maddyhome.idea.vim.ui.ExEntryPanel
import com.maddyhome.idea.vim.ui.ShowCmdOptionChangeListener
@@ -161,9 +161,7 @@ object VimListenerManager {
object GlobalListeners {
fun enable() {
@Suppress("DEPRECATION")
// [VERSION UPDATE] 193+ com.intellij.openapi.editor.actionSystem.TypedAction.getInstance
val typedAction = EditorActionManager.getInstance().typedAction
val typedAction = TypedAction.getInstance()
if (typedAction.rawHandler !is VimTypedActionHandler) {
// Actually this if should always be true, but just as protection
EventFacade.getInstance().setupTypedActionHandler(VimTypedActionHandler(typedAction.rawHandler))
@@ -173,7 +171,7 @@ object VimListenerManager {
OptionsManager.relativenumber.addOptionChangeListener(EditorGroup.NumberChangeListener.INSTANCE)
OptionsManager.showcmd.addOptionChangeListener(ShowCmdOptionChangeListener)
EventFacade.getInstance().addEditorFactoryListener(VimEditorFactoryListener, ApplicationManager.getApplication())
EventFacade.getInstance().addEditorFactoryListener(VimEditorFactoryListener, VimPlugin.getInstance())
}
fun disable() {
@@ -181,7 +179,7 @@ object VimListenerManager {
OptionsManager.number.removeOptionChangeListener(EditorGroup.NumberChangeListener.INSTANCE)
OptionsManager.relativenumber.removeOptionChangeListener(EditorGroup.NumberChangeListener.INSTANCE)
OptionsManager.showcmd.addOptionChangeListener(ShowCmdOptionChangeListener)
OptionsManager.showcmd.removeOptionChangeListener(ShowCmdOptionChangeListener)
EventFacade.getInstance().removeEditorFactoryListener(VimEditorFactoryListener)
}
@@ -189,15 +187,11 @@ object VimListenerManager {
object ProjectListeners {
fun add(project: Project) {
val eventFacade = EventFacade.getInstance()
eventFacade.connectBookmarkListener(project, MarkGroup.MarkListener(project))
eventFacade.connectFileEditorManagerListener(project, VimFileEditorManagerListener)
IdeaSpecifics.addIdeaSpecificsListeners(project)
}
fun removeAll() {
// Project listeners are self-disposable, so there is no need to unregister them on project close
EventFacade.getInstance().disableBusConnection()
ProjectManager.getInstance().openProjects.filterNot { it.isDisposed }.forEach { IdeaSpecifics.removeIdeaSpecificsListeners(it) }
}
@@ -208,26 +202,17 @@ object VimListenerManager {
object EditorListeners {
fun addAll() {
val editors = EditorFactory.getInstance().allEditors
for (editor in editors) {
if (!editor.vimMotionGroup) {
add(editor)
editor.vimMotionGroup = true
}
EditorFactory.getInstance().allEditors.forEach { editor ->
this.add(editor)
}
}
fun removeAll() {
val editors = EditorFactory.getInstance().allEditors
for (editor in editors) {
if (editor.vimMotionGroup) {
remove(editor)
editor.vimMotionGroup = false
}
EditorFactory.getInstance().allEditors.forEach { editor ->
this.remove(editor, false)
}
}
@JvmStatic
fun add(editor: Editor) {
editor.contentComponent.addKeyListener(VimKeyListener)
val eventFacade = EventFacade.getInstance()
@@ -235,21 +220,29 @@ object VimListenerManager {
eventFacade.addEditorMouseMotionListener(editor, EditorMouseHandler)
eventFacade.addEditorSelectionListener(editor, EditorSelectionHandler)
eventFacade.addComponentMouseListener(editor.contentComponent, ComponentMouseListener)
VimPlugin.getEditor().editorCreated(editor)
VimPlugin.getChange().editorCreated(editor)
}
@JvmStatic
fun remove(editor: Editor) {
fun remove(editor: Editor, isReleased: Boolean) {
editor.contentComponent.removeKeyListener(VimKeyListener)
val eventFacade = EventFacade.getInstance()
eventFacade.removeEditorMouseListener(editor, EditorMouseHandler)
eventFacade.removeEditorMouseMotionListener(editor, EditorMouseHandler)
eventFacade.removeEditorSelectionListener(editor, EditorSelectionHandler)
eventFacade.removeComponentMouseListener(editor.contentComponent, ComponentMouseListener)
VimPlugin.getEditorIfCreated()?.editorDeinit(editor, isReleased)
VimPlugin.getChange().editorReleased(editor)
}
}
object VimFileEditorManagerListener : FileEditorManagerListener {
class VimFileEditorManagerListener : FileEditorManagerListener {
override fun selectionChanged(event: FileEditorManagerEvent) {
if (!VimPlugin.isEnabled()) return
MotionGroup.fileEditorManagerSelectionChangedCallback(event)
FileGroup.fileEditorManagerSelectionChangedCallback(event)
SearchGroup.fileEditorManagerSelectionChangedCallback(event)
@@ -258,16 +251,12 @@ object VimListenerManager {
private object VimEditorFactoryListener : EditorFactoryListener {
override fun editorCreated(event: EditorFactoryEvent) {
VimPlugin.getEditor().editorCreated(event.editor)
VimPlugin.getMotion().editorCreated(event)
VimPlugin.getChange().editorCreated(event)
VimPlugin.statisticReport()
add(event.editor)
StatisticReporter.report()
}
override fun editorReleased(event: EditorFactoryEvent) {
VimPlugin.getEditor().editorDeinit(event.editor, true)
VimPlugin.getMotion().editorReleased(event)
VimPlugin.getChange().editorReleased(event)
remove(event.editor, true)
VimPlugin.getMark().editorReleased(event)
}
}

View File

@@ -18,6 +18,7 @@
package com.maddyhome.idea.vim.option;
import com.intellij.util.containers.ContainerUtil;
import org.jetbrains.annotations.NotNull;
import java.util.ArrayList;
@@ -113,5 +114,5 @@ public abstract class Option<T> {
protected final String name;
protected final String abbrev;
private final @NotNull List<OptionChangeListener<T>> listeners = new ArrayList<>();
private final @NotNull List<OptionChangeListener<T>> listeners = ContainerUtil.createLockFreeCopyOnWriteList();
}

View File

@@ -86,7 +86,7 @@ object OptionsManager {
val idearefactormode = addOption(BoundStringOption(IdeaRefactorMode.name, IdeaRefactorMode.name, IdeaRefactorMode.select, IdeaRefactorMode.availableValues))
val ideastatusicon = addOption(BoundStringOption(IdeaStatusIcon.name, IdeaStatusIcon.name, IdeaStatusIcon.enabled, IdeaStatusIcon.allValues))
@ApiStatus.ScheduledForRemoval(inVersion = "0.58")
@ApiStatus.ScheduledForRemoval(inVersion = "0.59")
@Deprecated("please use ideastatusicon")
val ideastatusbar = addOption(ToggleOption("ideastatusbar", "ideastatusbar", true))
@@ -390,11 +390,11 @@ object SelectModeOptionData {
const val key = "key"
const val cmd = "cmd"
@ApiStatus.ScheduledForRemoval(inVersion = "0.57")
@ApiStatus.ScheduledForRemoval(inVersion = "0.58")
@Deprecated("Please, use `idearefactormode` option")
const val template = "template"
@ApiStatus.ScheduledForRemoval(inVersion = "0.57")
@ApiStatus.ScheduledForRemoval(inVersion = "0.58")
@Deprecated("Please, use `ideaselection`")
const val refactoring = "refactoring"

View File

@@ -52,7 +52,7 @@ import java.awt.event.ComponentListener;
/**
* This is used to enter ex commands such as searches and "colon" commands
*/
public class ExEntryPanel extends JPanel implements LafManagerListener {
public class ExEntryPanel extends JPanel {
private static ExEntryPanel instance;
private static ExEntryPanel instanceWithoutShortcuts;
@@ -78,10 +78,6 @@ public class ExEntryPanel extends JPanel implements LafManagerListener {
new ExShortcutKeyAction(this).registerCustomShortcutSet();
}
// [VERSION UPDATE] 193+
//noinspection deprecation
LafManager.getInstance().addLafManagerListener(this);
updateUI();
}
@@ -101,12 +97,20 @@ public class ExEntryPanel extends JPanel implements LafManagerListener {
return instanceWithoutShortcuts;
}
public static boolean isInstanceWithShortcutsActive() {
return instance != null;
}
public static boolean isInstanceWithoutShortcutsActive() {
return instanceWithoutShortcuts != null;
}
public static void fullReset() {
if (instance != null) {
if (isInstanceWithShortcutsActive()) {
instance.reset();
instance = null;
}
if (instanceWithoutShortcuts != null) {
if (isInstanceWithoutShortcutsActive()) {
instanceWithoutShortcuts.reset();
instanceWithoutShortcuts = null;
}
@@ -224,7 +228,7 @@ public class ExEntryPanel extends JPanel implements LafManagerListener {
private void reset() {
deactivate(false);
LafManager.getInstance().removeLafManagerListener(this);
JTextField.removeKeymap(ExTextField.KEYMAP_NAME);
}
private void resetCaretOffset(@NotNull Editor editor) {
@@ -367,12 +371,6 @@ public class ExEntryPanel extends JPanel implements LafManagerListener {
entry.handleKey(stroke);
}
@Override
public void lookAndFeelChanged(@NotNull LafManager source) {
// Calls updateUI on this and child components
IJSwingUtilities.updateComponentTreeUI(this);
}
// Called automatically when the LAF is changed and the component is visible, and manually by the LAF listener handler
@Override
public void updateUI() {
@@ -453,4 +451,18 @@ public class ExEntryPanel extends JPanel implements LafManagerListener {
};
private static final Logger logger = Logger.getInstance(ExEntryPanel.class.getName());
public static class LafListener implements LafManagerListener {
@Override
public void lookAndFeelChanged(@NotNull LafManager source) {
if (!VimPlugin.isEnabled()) return;
// Calls updateUI on this and child components
if (ExEntryPanel.isInstanceWithShortcutsActive()) {
IJSwingUtilities.updateComponentTreeUI(ExEntryPanel.getInstance());
}
if (ExEntryPanel.isInstanceWithoutShortcutsActive()) {
IJSwingUtilities.updateComponentTreeUI(ExEntryPanel.getInstanceWithoutShortcuts());
}
}
}
}

View File

@@ -18,11 +18,11 @@
package com.maddyhome.idea.vim.ui;
import com.intellij.ide.IdeTooltip;
import com.intellij.ide.ui.LafManager;
import com.intellij.ide.ui.LafManagerListener;
import com.intellij.openapi.application.ApplicationManager;
import com.intellij.openapi.editor.Editor;
import com.intellij.openapi.editor.EditorFactory;
import com.intellij.openapi.project.Project;
import com.intellij.ui.components.JBScrollPane;
import com.intellij.util.IJSwingUtilities;
@@ -46,7 +46,7 @@ import java.util.List;
/**
* This panel displays text in a <code>more</code> like window.
*/
public class ExOutputPanel extends JPanel implements LafManagerListener {
public class ExOutputPanel extends JPanel {
private final @NotNull Editor myEditor;
private final @NotNull JLabel myLabel = new JLabel("more");
@@ -86,16 +86,13 @@ public class ExOutputPanel extends JPanel implements LafManagerListener {
addKeyListener(moreKeyListener);
myText.addKeyListener(moreKeyListener);
final Project project = editor.getProject();
if (project != null) {
// [VERSION UPDATE] 193+
//noinspection deprecation
LafManager.getInstance().addLafManagerListener(this, project);
}
updateUI();
}
public static boolean isPanelActive(@NotNull Editor editor) {
return UserDataManager.getVimMorePanel(editor) != null;
}
public static @NotNull ExOutputPanel getInstance(@NotNull Editor editor) {
ExOutputPanel panel = UserDataManager.getVimMorePanel(editor);
if (panel == null) {
@@ -105,12 +102,6 @@ public class ExOutputPanel extends JPanel implements LafManagerListener {
return panel;
}
@Override
public void lookAndFeelChanged(@NotNull LafManager source) {
// Calls updateUI on this and child components
IJSwingUtilities.updateComponentTreeUI(this);
}
// Called automatically when the LAF is changed and the component is visible, and manually by the LAF listener handler
@Override
public void updateUI() {
@@ -366,4 +357,16 @@ public class ExOutputPanel extends JPanel implements LafManagerListener {
}
}
}
public static class LafListener implements LafManagerListener {
@Override
public void lookAndFeelChanged(@NotNull LafManager source) {
if (!VimPlugin.isEnabled()) return;
// Calls updateUI on this and child components
for (Editor editor : EditorFactory.getInstance().getAllEditors()) {
if (!ExOutputPanel.isPanelActive(editor)) continue;
IJSwingUtilities.updateComponentTreeUI(ExOutputPanel.getInstance(editor));
}
}
}
}

View File

@@ -25,6 +25,7 @@ import com.intellij.openapi.project.Project;
import com.intellij.openapi.util.Disposer;
import com.intellij.util.ui.JBUI;
import com.maddyhome.idea.vim.VimPlugin;
import com.maddyhome.idea.vim.VimProjectService;
import com.maddyhome.idea.vim.group.HistoryGroup;
import com.maddyhome.idea.vim.helper.UiHelper;
import kotlin.text.StringsKt;
@@ -48,6 +49,8 @@ import static java.lang.Math.min;
*/
public class ExTextField extends JTextField {
public final static String KEYMAP_NAME = "ex";
ExTextField() {
// We need to store this in a field, because we can't trust getCaret(), as it will return an instance of
// ComposedTextCaret when working with dead keys or input methods
@@ -112,7 +115,7 @@ public class ExTextField extends JTextField {
}
setInputMap(WHEN_FOCUSED, new InputMap());
Keymap map = addKeymap("ex", getKeymap());
Keymap map = addKeymap(KEYMAP_NAME, getKeymap());
loadKeymap(map, ExKeyBindings.INSTANCE.getBindings(), actions);
map.setDefaultAction(new ExEditorKit.DefaultExKeyHandler());
setKeymap(map);
@@ -233,7 +236,8 @@ public class ExTextField extends JTextField {
String disposeKey = vimExTextFieldDisposeKey + editor.hashCode();
Project project = editor.getProject();
if (Disposer.get(disposeKey) == null && project != null) {
Disposer.register(project, () -> {
VimProjectService parentDisposable = VimProjectService.getInstance(project);
Disposer.register(parentDisposable, () -> {
this.editor = null;
this.context = null;
}, disposeKey);

View File

@@ -0,0 +1,134 @@
/*
* 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.ui
import com.intellij.icons.AllIcons
import com.intellij.openapi.actionSystem.AnActionEvent
import com.intellij.openapi.actionSystem.DefaultActionGroup
import com.intellij.openapi.actionSystem.PlatformDataKeys
import com.intellij.openapi.editor.Document
import com.intellij.openapi.editor.toolbar.floating.AbstractFloatingToolbarProvider
import com.intellij.openapi.editor.toolbar.floating.FloatingToolbarComponent
import com.intellij.openapi.fileEditor.FileDocumentManager
import com.intellij.openapi.project.DumbAwareAction
import com.intellij.openapi.util.io.FileUtil
import com.intellij.util.containers.IntArrayList
import com.maddyhome.idea.vim.VimPlugin
import com.maddyhome.idea.vim.ex.vimscript.VimScriptParser
import com.maddyhome.idea.vim.ui.ReloadFloatingToolbarActionGroup.Companion.ACTION_GROUP
import icons.VimIcons
import org.jetbrains.annotations.TestOnly
import java.util.regex.Pattern
/**
* This file contains a "reload ~/.ideavimrc file" action functionality.
* This is small floating action in the top right corner of the editor that appears if user edits configuration file.
*
* Here you can find:
* - Simplified snapshot of config file
* - Floating bar
* - Action / action group
*/
object VimRcFileState {
// List of hashes of non-empty trimmed lines
private val state = IntArrayList()
// ModificationStamp. Can be taken only from document. Doesn't play a big role, but can help speed up [equalTo]
private var modificationStamp = 0L
// This is a pattern used in ideavimrc parsing for a long time. It removes all trailing/leading spaced and blank lines
private val EOL_SPLIT_PATTERN = Pattern.compile(" *(\r\n|\n)+ *")
var filePath: String? = null
fun saveFileState(filePath: String, data: List<String>) {
this.filePath = FileUtil.toSystemDependentName(filePath)
state.clear()
for (line in data) {
state.add(line.hashCode())
}
}
fun equalTo(document: Document): Boolean {
val fileModificationStamp = document.modificationStamp
if (fileModificationStamp == modificationStamp) return true
val stateSize = state.size()
var i = 0
VimScriptParser.readText(document.charsSequence).forEach { line ->
if (i >= stateSize) return false
if (state.get(i) != line.hashCode()) return false
i++
}
if (i < stateSize) return false
modificationStamp = fileModificationStamp
return true
}
@TestOnly
fun clear() {
state.clear()
modificationStamp = 0
filePath = null
}
}
class ReloadVimRc : DumbAwareAction() {
override fun update(e: AnActionEvent) {
val editor = e.getData(PlatformDataKeys.EDITOR) ?: return
// XXX: Actually, it worth to add e.presentation.description, but it doesn't work because of some reason
val sameDoc = VimRcFileState.equalTo(editor.document)
e.presentation.icon = if (sameDoc) VimIcons.IDEAVIM else AllIcons.Actions.BuildLoadChanges
e.presentation.text = if (sameDoc) "No Changes" else "Reload"
e.presentation.isEnabledAndVisible = true
}
override fun actionPerformed(e: AnActionEvent) {
val editor = e.getData(PlatformDataKeys.EDITOR) ?: return
FileDocumentManager.getInstance().saveDocumentAsIs(editor.document)
VimPlugin.getInstance().executeIdeaVimRc()
}
}
class ReloadFloatingToolbar : AbstractFloatingToolbarProvider(ACTION_GROUP) {
override val autoHideable: Boolean = false
override val priority: Int = 0
}
class ReloadFloatingToolbarActionGroup : DefaultActionGroup() {
override fun update(e: AnActionEvent) {
val virtualFile = e.getData(PlatformDataKeys.VIRTUAL_FILE) ?: return
if (virtualFile.path == VimRcFileState.filePath) {
e.getData(FloatingToolbarComponent.KEY)?.scheduleShow()
}
}
companion object {
const val ACTION_GROUP = "IdeaVim.ReloadVimRc.group"
}
}

View File

@@ -1,14 +1,16 @@
package com.maddyhome.idea.vim.ui
import com.intellij.ide.lightEdit.LightEditCompatible
import com.intellij.openapi.editor.Editor
import com.intellij.openapi.fileEditor.FileEditorManagerEvent
import com.intellij.openapi.project.Project
import com.intellij.openapi.project.ProjectManager
import com.intellij.openapi.wm.StatusBar
import com.intellij.openapi.wm.StatusBarWidget
import com.intellij.openapi.wm.StatusBarWidgetProvider
import com.intellij.openapi.wm.StatusBarWidgetFactory
import com.intellij.openapi.wm.WindowManager
import com.intellij.openapi.wm.impl.status.EditorBasedWidget
import com.intellij.openapi.wm.impl.status.widget.StatusBarWidgetsManager
import com.intellij.util.Consumer
import com.maddyhome.idea.vim.helper.StringHelper
import com.maddyhome.idea.vim.helper.vimCommandState
@@ -20,12 +22,14 @@ import java.awt.event.MouseEvent
object ShowCmd {
// https://github.com/vim/vim/blob/b376ace1aeaa7614debc725487d75c8f756dd773/src/vim.h#L1721
private const val SHOWCMD_COLS = 10
internal const val ID = "IdeaVim::ShowCmd"
internal const val displayName = "IdeaVim showcmd"
fun update() {
val windowManager = WindowManager.getInstance()
ProjectManager.getInstance().openProjects.forEach {
val statusBar = windowManager.getStatusBar(it)
statusBar.updateWidget(ShowCmdStatusBarWidget.ID)
statusBar.updateWidget(ID)
}
}
@@ -46,54 +50,67 @@ object ShowCmd {
object ShowCmdOptionChangeListener: OptionChangeListener<Boolean> {
override fun valueChange(oldValue: Boolean?, newValue: Boolean?) {
ShowCmd.update()
}
}
class ShowCmdStatusBarWidget: StatusBarWidgetProvider {
companion object {
const val ID = "IdeaVim::ShowCmd"
}
override fun getWidget(project: Project): StatusBarWidget = Widget(project)
override fun getAnchor(): String = StatusBar.Anchors.before(StatusBar.StandardWidgets.POSITION_PANEL)
// `:help 'showcmd'`
// Widget shows:
// * Partial command, as it's being typed
// * When selecting characters within a line, the number of characters
// * Tabs are shown as one char
// * If the number of bytes is different, this is also shown: "2-6"
// * When selecting more than one line, the number of lines
// * When selecting a block, the size in screen characters: {lines}x{columns}
//
// We only need to show partial commands, since the standard PositionPanel shows the other information already, with
// the exception of "{lines}x{columns}" (it shows "x carets" instead)
class Widget(project: Project)
: EditorBasedWidget(project), StatusBarWidget.Multiframe, StatusBarWidget.TextPresentation {
override fun ID() = ID
// [VERSION UPDATE] After 193 use `getPresentation()`
@Suppress("UnstableApiUsage", "DEPRECATION")
override fun getPresentation(type: StatusBarWidget.PlatformType): StatusBarWidget.WidgetPresentation? = this
override fun getClickConsumer(): Consumer<MouseEvent>? = null
override fun getTooltipText(): String {
var count = ShowCmd.getFullText(this.editor)
if (!count.isBlank()) count = ": $count"
return "IdeaVim showcmd$count"
}
override fun getText(): String = ShowCmd.getWidgetText(editor)
override fun getAlignment() = Component.CENTER_ALIGNMENT
// Multiframe#copy to show the widget on popped out editors
override fun copy(): StatusBarWidget = Widget(myProject)
override fun selectionChanged(event: FileEditorManagerEvent) {
// Update when changing selected editor
myStatusBar?.updateWidget(ID)
val extension = StatusBarWidgetFactory.EP_NAME.findExtension(ShowCmdStatusBarWidgetFactory::class.java) ?: return
val projectManager = ProjectManager.getInstanceIfCreated() ?: return
for (project in projectManager.openProjects) {
val statusBarWidgetsManager = project.getService(StatusBarWidgetsManager::class.java) ?: continue
statusBarWidgetsManager.updateWidget(extension)
}
}
}
class ShowCmdStatusBarWidgetFactory : StatusBarWidgetFactory, LightEditCompatible {
override fun getId() = ShowCmd.ID
override fun getDisplayName(): String = ShowCmd.displayName
override fun disposeWidget(widget: StatusBarWidget) {}
override fun isAvailable(project: Project): Boolean = OptionsManager.showcmd.isSet
override fun createWidget(project: Project): StatusBarWidget = Widget(project)
override fun canBeEnabledOn(statusBar: StatusBar): Boolean = true
// Should be configured via `set showcmd`
override fun isConfigurable(): Boolean = false
}
// `:help 'showcmd'`
// Widget shows:
// * Partial command, as it's being typed
// * When selecting characters within a line, the number of characters
// * Tabs are shown as one char
// * If the number of bytes is different, this is also shown: "2-6"
// * When selecting more than one line, the number of lines
// * When selecting a block, the size in screen characters: {lines}x{columns}
//
// We only need to show partial commands, since the standard PositionPanel shows the other information already, with
// the exception of "{lines}x{columns}" (it shows "x carets" instead)
class Widget(project: Project) : EditorBasedWidget(project), StatusBarWidget.Multiframe, StatusBarWidget.TextPresentation {
override fun ID() = ShowCmd.ID
override fun getPresentation(): StatusBarWidget.WidgetPresentation? = this
override fun getClickConsumer(): Consumer<MouseEvent>? = null
override fun getTooltipText(): String {
var count = ShowCmd.getFullText(this.editor)
if (!count.isBlank()) count = ": $count"
return "${ShowCmd.displayName}$count"
}
override fun getText(): String = ShowCmd.getWidgetText(editor)
override fun getAlignment() = Component.CENTER_ALIGNMENT
// Multiframe#copy to show the widget on popped out editors
override fun copy(): StatusBarWidget = Widget(myProject)
override fun selectionChanged(event: FileEditorManagerEvent) {
// Update when changing selected editor
myStatusBar?.updateWidget(ShowCmd.ID)
}
}

View File

@@ -16,12 +16,13 @@
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package com.maddyhome.idea.vim
package com.maddyhome.idea.vim.ui
import com.intellij.ide.BrowserUtil
import com.intellij.ide.DataManager
import com.intellij.ide.lightEdit.LightEditCompatible
import com.intellij.ide.plugins.InstalledPluginsState
import com.intellij.ide.plugins.PluginManager
import com.intellij.ide.plugins.PluginManagerCore
import com.intellij.ide.plugins.PluginManagerMain
import com.intellij.ide.plugins.RepositoryHelper
import com.intellij.openapi.actionSystem.ActionManager
@@ -35,6 +36,7 @@ import com.intellij.openapi.progress.ProgressIndicator
import com.intellij.openapi.progress.Task
import com.intellij.openapi.project.DumbAwareAction
import com.intellij.openapi.project.Project
import com.intellij.openapi.project.ProjectManager
import com.intellij.openapi.ui.Messages
import com.intellij.openapi.ui.popup.JBPopupFactory
import com.intellij.openapi.ui.popup.ListPopup
@@ -45,46 +47,81 @@ import com.intellij.openapi.updateSettings.impl.UpdateSettings
import com.intellij.openapi.util.Ref
import com.intellij.openapi.wm.StatusBar
import com.intellij.openapi.wm.StatusBarWidget
import com.intellij.openapi.wm.StatusBarWidgetProvider
import com.intellij.openapi.wm.StatusBarWidgetFactory
import com.intellij.openapi.wm.WindowManager
import com.intellij.openapi.wm.impl.status.widget.StatusBarWidgetsManager
import com.intellij.ui.awt.RelativePoint
import com.intellij.util.Consumer
import com.intellij.util.text.VersionComparatorUtil
import com.maddyhome.idea.vim.VimPlugin
import com.maddyhome.idea.vim.group.NotificationService
import com.maddyhome.idea.vim.option.IdeaStatusIcon
import com.maddyhome.idea.vim.option.OptionsManager
import com.maddyhome.idea.vim.ui.VimEmulationConfigurable
import icons.VimIcons
import java.awt.Point
import java.awt.event.MouseEvent
import javax.swing.Icon
import javax.swing.SwingConstants
private class StatusBarIconProvider : StatusBarWidgetProvider {
override fun getWidget(project: Project): VimStatusBar? {
const val STATUS_BAR_ICON_ID = "IdeaVim-Icon"
const val STATUS_BAR_DISPLAY_NAME = "IdeaVim"
class StatusBarIconFactory : StatusBarWidgetFactory, LightEditCompatible {
override fun getId(): String = STATUS_BAR_ICON_ID
override fun getDisplayName(): String = STATUS_BAR_DISPLAY_NAME
override fun disposeWidget(widget: StatusBarWidget) {}
override fun isAvailable(project: Project): Boolean {
@Suppress("DEPRECATION")
if (!OptionsManager.ideastatusbar.isSet) return null
if (OptionsManager.ideastatusicon.value == IdeaStatusIcon.disabled) return null
return VimStatusBar
if (!OptionsManager.ideastatusbar.isSet) return false
if (OptionsManager.ideastatusicon.value == IdeaStatusIcon.disabled) return false
return true
}
override fun createWidget(project: Project): StatusBarWidget {
OptionsManager.ideastatusicon.addOptionChangeListener { _, _ -> updateAll() }
return VimStatusBar()
}
override fun canBeEnabledOn(statusBar: StatusBar): Boolean = true
/* Use can configure this icon using ideastatusicon option, but we should still keep the option to remove
* the icon via IJ because this option is hard to discover */
override fun isConfigurable(): Boolean = true
private fun updateAll() {
val projectManager = ProjectManager.getInstanceIfCreated() ?: return
for (project in projectManager.openProjects) {
val statusBarWidgetsManager = project.getService(StatusBarWidgetsManager::class.java) ?: continue
statusBarWidgetsManager.updateWidget(this)
}
updateIcon()
}
companion object {
fun updateIcon() {
val projectManager = ProjectManager.getInstanceIfCreated() ?: return
for (project in projectManager.openProjects) {
val statusBar = WindowManager.getInstance().getStatusBar(project) ?: continue
statusBar.updateWidget(STATUS_BAR_ICON_ID)
}
}
}
}
object VimStatusBar : StatusBarWidget, StatusBarWidget.IconPresentation {
class VimStatusBar : StatusBarWidget, StatusBarWidget.IconPresentation {
init {
OptionsManager.ideastatusicon.addOptionChangeListener { _, _ -> this.update() }
}
override fun ID(): String = STATUS_BAR_ICON_ID
private var statusBar: StatusBar? = null
override fun ID(): String = "IdeaVim-Icon"
override fun install(statusBar: StatusBar) {
this.statusBar = statusBar
}
override fun install(statusBar: StatusBar) {}
override fun dispose() {}
override fun getTooltipText() = "IdeaVim"
override fun getTooltipText() = STATUS_BAR_DISPLAY_NAME
override fun getIcon(): Icon {
if (OptionsManager.ideastatusicon.value == IdeaStatusIcon.gray) return VimIcons.IDEAVIM_DISABLED
@@ -100,13 +137,7 @@ object VimStatusBar : StatusBarWidget, StatusBarWidget.IconPresentation {
popup.show(RelativePoint(component, at))
}
// TODO [VERSION UPDATE] After 193 use `getPresentation()`
@Suppress("DEPRECATION", "UnstableApiUsage")
override fun getPresentation(type: StatusBarWidget.PlatformType): StatusBarWidget.WidgetPresentation? = this
fun update() {
statusBar?.updateWidget(this.ID())
}
override fun getPresentation(): StatusBarWidget.WidgetPresentation? = this
}
class VimActions : DumbAwareAction() {
@@ -130,7 +161,7 @@ private object VimActionsPopup {
fun getPopup(dataContext: DataContext): ListPopup {
val actions = getActions()
val popup = JBPopupFactory.getInstance()
.createActionGroupPopup("IdeaVim", actions,
.createActionGroupPopup(STATUS_BAR_DISPLAY_NAME, actions,
dataContext, JBPopupFactory.ActionSelectionAid.SPEEDSEARCH, false,
VimActions.actionPlace)
popup.setAdText("Version ${VimPlugin.getVersion()}", SwingConstants.CENTER)
@@ -207,8 +238,6 @@ private object JoinEap : DumbAwareAction() {
val pluginRef = Ref.create<PluginDownloader>()
// [VERSION UPDATE] 193+ remove suppressing
@Suppress("UnstableApiUsage")
object : Task.Backgroundable(null, "Checking for IdeaVim EAP version", true) {
override fun run(indicator: ProgressIndicator) {
val downloaders = mutableListOf<PluginDownloader>()
@@ -226,14 +255,12 @@ private object JoinEap : DumbAwareAction() {
pluginRef.set(plugin)
}
// [VERSION UPDATE] 193+ remove suppressing
@Suppress("MissingRecentApi", "UnstableApiUsage")
override fun onSuccess() {
val downloader: PluginDownloader = pluginRef.get() ?: run {
notificator.notifySubscribedToEap()
return
}
val currentVersion = PluginManager.getPlugin(VimPlugin.getPluginId())?.version ?: ""
val currentVersion = PluginManagerCore.getPlugin(VimPlugin.getPluginId())?.version ?: ""
if (VersionComparatorUtil.compare(downloader.pluginVersion, currentVersion) <= 0) {
notificator.notifySubscribedToEap()
return
@@ -241,6 +268,7 @@ private object JoinEap : DumbAwareAction() {
val version = downloader.pluginVersion
val message = "Do you want to install the EAP version of IdeaVim?"
@Suppress("MoveVariableDeclarationIntoWhen")
val res = Messages.showYesNoCancelDialog(project, message, "IdeaVim $version", null)
when (res) {

View File

@@ -1,13 +1,18 @@
package icons;
import com.intellij.openapi.util.IconLoader;
import com.intellij.ui.IconManager;
import org.jetbrains.annotations.NotNull;
import javax.swing.*;
public interface VimIcons {
Icon IDEAVIM = IconLoader.getIcon("/icons/ideavim.svg");
Icon IDEAVIM_DISABLED = IconLoader.getIcon("/icons/ideavim_disabled.svg");
Icon GITHUB = IconLoader.getIcon("/icons/github.svg");
Icon TWITTER = IconLoader.getIcon("/icons/twitter.svg");
Icon YOUTRACK = IconLoader.getIcon("/icons/youtrack.svg");
public final class VimIcons {
private static @NotNull Icon load(@NotNull String path) {
return IconManager.getInstance().getIcon(path, VimIcons.class);
}
public static final @NotNull Icon IDEAVIM = load("/icons/ideavim.svg");
public static final @NotNull Icon IDEAVIM_DISABLED = load("/icons/ideavim_disabled.svg");
public static final @NotNull Icon GITHUB = load("/icons/github.svg");
public static final @NotNull Icon TWITTER = load("/icons/twitter.svg");
public static final @NotNull Icon YOUTRACK = load("/icons/youtrack.svg");
}

View File

@@ -1,6 +1,6 @@
# Manual Tests
## #1 [Last run: 2020-04-09]
## #1 [Last run: 2020-04-28]
_Initial mode:_ NORMAL
@@ -14,7 +14,7 @@ Word is selected, block-caret is placed on the word end (offset = `word end - 1`
![](resources/manualTests/1.png)
## #2 [Last run: 2020-04-09]
## #2 [Last run: 2020-04-28]
_Initial mode:_ NORMAL
@@ -26,7 +26,7 @@ Last word is selected, block caret is placed on the word end without bouncing
![](resources/manualTests/2.png)
## #3 [Last run: 2020-04-09]
## #3 [Last run: 2020-04-28]
_Initial mode:_ NORMAL
@@ -38,7 +38,7 @@ Line is selected. Caret is placed on the line end
![](resources/manualTests/3.png)
## #4 [Last run: 2020-04-09]
## #4 [Last run: 2020-04-28]
_Initial mode:_ NORMAL
@@ -51,7 +51,7 @@ After mouse release, caret moves one character back and becomes block shape
![](resources/manualTests/4.gif)
## #5 [Last run: 2020-04-09]
## #5 [Last run: 2020-04-28]
_Initial mode:_ NORMAL
@@ -65,7 +65,7 @@ After mouse release, caret moves one character back and becomes block shape
![](resources/manualTests/5.gif)
## #6 [Last run: 2020-04-09]
## #6 [Last run: 2020-04-28]
_Initial mode:_ NORMAL
@@ -77,7 +77,7 @@ Line is selected, caret is on the first position
![](resources/manualTests/6.gif)
## #6 [Last run: 2020-04-09]
## #6 [Last run: 2020-04-28]
_Initial mode:_ NORMAL
@@ -94,7 +94,7 @@ Caret stays in _block_ shape with a normal mode
![](resources/manualTests/7.2.gif)
## #7 [Last run: 2020-04-09]
## #7 [Last run: 2020-04-28]
_Action:_
Turn emulation off and on
@@ -102,7 +102,7 @@ Turn emulation off and on
_Result:_
Vim emulator works as expected
## #8 [Last run: 2020-04-09
## #8 [Last run: 2020-04-28
_Action:_
Start up IJ with disabled emulator, turn it on
@@ -110,7 +110,7 @@ Start up IJ with disabled emulator, turn it on
_Result:_
Vim emulator works as expected
## #9 [Last run: 2020-04-09]
## #9 [Last run: 2020-04-28]
_Action:_
Wrap with if
@@ -120,3 +120,7 @@ Expression is wrapped, vim in insert mode.
![](resources/manualTests/9.gif)
## #10 [Last run: 2020-05-06]
_Action:_
Test dynamic plugin loading/unloading.

View File

@@ -26,6 +26,7 @@ import com.maddyhome.idea.vim.KeyHandler;
import com.maddyhome.idea.vim.VimPlugin;
import com.maddyhome.idea.vim.command.CommandState;
import com.maddyhome.idea.vim.ex.vimscript.VimScriptGlobalEnvironment;
import com.maddyhome.idea.vim.group.visual.VimVisualTimer;
import com.maddyhome.idea.vim.helper.EditorDataContext;
import com.maddyhome.idea.vim.helper.RunnableHelper;
import com.maddyhome.idea.vim.helper.TestInputModel;
@@ -57,6 +58,10 @@ public abstract class JavaVimTestCase extends JavaCodeInsightFixtureTestCase {
protected void tearDown() throws Exception {
ExEntryPanel.getInstance().deactivate(false);
VimScriptGlobalEnvironment.getInstance().getVariables().clear();
Timer swingTimer = VimVisualTimer.INSTANCE.getSwingTimer();
if (swingTimer != null) {
swingTimer.stop();
}
super.tearDown();
}

View File

@@ -19,6 +19,7 @@
package org.jetbrains.plugins.ideavim.action;
import com.maddyhome.idea.vim.VimPlugin;
import com.maddyhome.idea.vim.command.CommandState;
import com.maddyhome.idea.vim.common.Mark;
import org.jetbrains.plugins.ideavim.VimTestCase;
@@ -173,4 +174,17 @@ public class MarkTest extends VimTestCase {
"four five\n");
assertOffset(14);
}
// |i| |`]|
public void testGotoLastChangePositionEnd() {
doTest(parseKeys("yiw", "P", "gg", "`]"), "one two\n" +
"<caret>three\n" +
"four five\n",
"one two\n" +
"thre<caret>ethree\n" +
"four five\n",
CommandState.Mode.COMMAND,
CommandState.SubMode.NONE
);
}
}

View File

@@ -83,4 +83,29 @@ class PutViaIdeaTest : VimTestCase() {
typeText(StringHelper.parseKeys("ve", "p"))
assertEquals(sizeBefore, CopyPasteManager.getInstance().allContents.size)
}
fun `test insert block with newline`() {
val before = """
A Discovery
$c
I found it in a legendary land
hard by the torrent of a mountain pass.
""".trimIndent()
configureByText(before)
VimPlugin.getRegister().storeText(myFixture.editor, before rangeOf "\nI found it in a legendary land\n", SelectionType.CHARACTER_WISE, false)
typeText(StringHelper.parseKeys("p"))
val after = """
A Discovery
I found it in a legendary land
I found it in a legendary land
hard by the torrent of a mountain pass.
""".trimIndent()
myFixture.checkResult(after)
}
}

View File

@@ -20,7 +20,6 @@ package org.jetbrains.plugins.ideavim.action.copy
import com.maddyhome.idea.vim.VimPlugin
import com.maddyhome.idea.vim.helper.StringHelper.parseKeys
import junit.framework.Assert
import org.jetbrains.plugins.ideavim.VimTestCase
class YankLineActionTest : VimTestCase() {
@@ -32,6 +31,6 @@ class YankLineActionTest : VimTestCase() {
configureByText(before)
typeText(parseKeys("\"4yy"))
val register = VimPlugin.getRegister().getRegister('4')!!
Assert.assertEquals("I found it in a legendary land\n", register.text)
assertEquals("I found it in a legendary land\n", register.text)
}
}

View File

@@ -0,0 +1,108 @@
/*
* IdeaVim - Vim emulator for IDEs based on the IntelliJ platform
* Copyright (C) 2003-2020 The IdeaVim authors
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package org.jetbrains.plugins.ideavim.ex.handler
import com.maddyhome.idea.vim.helper.StringHelper
import org.jetbrains.plugins.ideavim.VimTestCase
/**
* @author John Weigel
*/
class BufferHandlerTest : VimTestCase() {
fun testBufferActionByNumber() {
configureByFileName("aaa.txt")
configureByFileName("bbb.txt")
typeText(commandToKeys("buffer 2"))
assertPluginError(false)
}
fun testBufferActionByName() {
configureByFileName("aaa.txt")
configureByFileName("bbb.txt")
typeText(commandToKeys("buffer aaa"))
assertPluginError(false)
}
fun testBufferActionWithNoArg() {
configureByText("\n")
typeText(commandToKeys("buffer"))
assertPluginError(false)
}
fun testBufferActionWithInvalidBufferNumber() {
configureByText("\n")
typeText(commandToKeys("buffer 999"))
assertPluginError(true)
}
fun testBufferActionWithInvalidBufferName() {
configureByText("\n")
typeText(commandToKeys("buffer invalidbuffer"))
assertPluginError(true)
}
fun testBufferActionWithModifications() {
configureByFileName("aaa.txt")
configureByFileName("bbb.txt")
typeText(StringHelper.parseKeys("aa<esc>:buffer aaa<enter>"))
assertPluginError(true)
}
fun testBufferActionWithModificationsOverride() {
configureByFileName("aaa.txt")
configureByFileName("bbb.txt")
typeText(StringHelper.parseKeys("aa<esc>:buffer! aaa<enter>"))
assertPluginError(false)
}
fun testBufferActionWithMultipleMatches() {
configureByFileName("aaa.txt")
configureByFileName("aaa2.txt")
typeText(commandToKeys("buffer aaa"))
assertPluginError(true)
}
fun testBufAction() {
configureByText("\n")
typeText(commandToKeys("buf 1"))
assertPluginError(false)
}
fun testBuAction() {
configureByText("\n")
typeText(commandToKeys("bu 1"))
assertPluginError(false)
}
fun testBAction() {
configureByText("\n")
typeText(commandToKeys("b 1"))
assertPluginError(false)
}
}

View File

@@ -232,7 +232,7 @@ n ,f <Plug>Foo
// VIM-676 |:map|
fun testBackspaceCharacterInVimRc() {
configureByText("\n")
VimScriptParser.executeText("inoremap # X\u0008#\n")
VimScriptParser.executeText(VimScriptParser.readText("inoremap # X\u0008#\n"))
typeText(StringHelper.parseKeys("i", "#", "<Esc>"))
myFixture.checkResult("#\n")
assertMode(CommandState.Mode.COMMAND)
@@ -247,7 +247,7 @@ n ,f <Plug>Foo
bar
""".trimIndent())
VimScriptParser.executeText("map \u0018i dd\n")
VimScriptParser.executeText(VimScriptParser.readText("map \u0018i dd\n"))
typeText(StringHelper.parseKeys("i", "#", "<Esc>"))
myFixture.checkResult("""
#foo
@@ -264,7 +264,7 @@ n ,f <Plug>Foo
// VIM-679 |:map|
fun testBarCtrlVEscaped() {
configureByText("${c}foo\n")
VimScriptParser.executeText("imap a b \u0016|\u0016| c |\n")
VimScriptParser.executeText(listOf("imap a b \u0016|\u0016| c |\n"))
typeText(StringHelper.parseKeys("ia"))
myFixture.checkResult("b || c foo\n")
}
@@ -272,7 +272,7 @@ n ,f <Plug>Foo
// VIM-679 |:map|
fun testCtrlMCtrlLAsNewLine() {
configureByText("${c}foo\n")
VimScriptParser.executeText("map A :%s/foo/bar/g\r\u000C\n")
VimScriptParser.executeText(listOf("map A :%s/foo/bar/g\r\u000C\n"))
typeText(StringHelper.parseKeys("A"))
myFixture.checkResult("bar\n")
}
@@ -280,7 +280,7 @@ n ,f <Plug>Foo
// VIM-700 |:map|
fun testRemappingZero() {
configureByText("x${c}yz\n")
VimScriptParser.executeText("map 0 ~")
VimScriptParser.executeText(listOf("map 0 ~"))
typeText(StringHelper.parseKeys("0"))
myFixture.checkResult("xYz\n")
}
@@ -288,7 +288,7 @@ n ,f <Plug>Foo
// VIM-700 |:map|
fun testRemappingZeroStillAllowsZeroToBeUsedInCount() {
configureByText("a${c}bcdefghijklmnop\n")
VimScriptParser.executeText("map 0 ^")
VimScriptParser.executeText(listOf("map 0 ^"))
typeText(StringHelper.parseKeys("10~"))
myFixture.checkResult("aBCDEFGHIJKlmnop\n")
}
@@ -296,7 +296,7 @@ n ,f <Plug>Foo
// VIM-700 |:map|
fun testRemappingDeleteOverridesRemovingLastDigitFromCount() {
configureByText("a${c}bcdefghijklmnop\n")
VimScriptParser.executeText("map <Del> ~")
VimScriptParser.executeText(listOf("map <Del> ~"))
typeText(StringHelper.parseKeys("10<Del>"))
myFixture.checkResult("aBCDEFGHIJKlmnop\n")
}

View File

@@ -0,0 +1,330 @@
/*
* IdeaVim - Vim emulator for IDEs based on the IntelliJ platform
* Copyright (C) 2003-2020 The IdeaVim authors
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package org.jetbrains.plugins.ideavim.extension.exchange
import com.intellij.openapi.editor.markup.HighlighterTargetArea
import com.maddyhome.idea.vim.command.CommandState
import com.maddyhome.idea.vim.extension.exchange.VimExchangeExtension
import com.maddyhome.idea.vim.helper.StringHelper
import com.maddyhome.idea.vim.helper.VimBehaviorDiffers
import org.jetbrains.plugins.ideavim.VimTestCase
class VimExchangeExtensionTest : VimTestCase() {
@Throws(Exception::class)
override fun setUp() {
super.setUp()
enableExtensions("exchange")
}
// |cx|
fun `test exchange words left to right`() {
doTest(StringHelper.parseKeys("cxe", "w", "cxe"),
"The quick ${c}brown fox catch over the lazy dog",
"The quick fox ${c}brown catch over the lazy dog",
CommandState.Mode.COMMAND,
CommandState.SubMode.NONE
)
}
// |cx|
fun `test exchange words dot repeat`() {
doTest(StringHelper.parseKeys("cxiw", "w", "."),
"The quick ${c}brown fox catch over the lazy dog",
"The quick fox ${c}brown catch over the lazy dog",
CommandState.Mode.COMMAND,
CommandState.SubMode.NONE
)
}
// |cx|
fun `test exchange words right to left`() {
doTest(StringHelper.parseKeys("cxe", "b", "cxe"),
"The quick brown ${c}fox catch over the lazy dog",
"The quick ${c}fox brown catch over the lazy dog",
CommandState.Mode.COMMAND,
CommandState.SubMode.NONE
)
}
// |cx|
fun `test exchange words right to left with dot`() {
doTest(StringHelper.parseKeys("cxe", "b", "."),
"The quick brown ${c}fox catch over the lazy dog",
"The quick ${c}fox brown catch over the lazy dog",
CommandState.Mode.COMMAND,
CommandState.SubMode.NONE
)
}
// |X|
fun `test visual exchange words left to right`() {
doTest(StringHelper.parseKeys("veX", "w", "veX"),
"The quick ${c}brown fox catch over the lazy dog",
"The quick fox ${c}brown catch over the lazy dog",
CommandState.Mode.COMMAND,
CommandState.SubMode.NONE
)
}
// |X|
@VimBehaviorDiffers(
originalVimAfter = "The ${c}brown catch over the lazy dog",
shouldBeFixed = true
)
fun `test visual exchange words from inside`() {
doTest(StringHelper.parseKeys("veX", "b", "v3e", "X"),
"The quick ${c}brown fox catch over the lazy dog",
"The brow${c}n catch over the lazy dog",
CommandState.Mode.COMMAND,
CommandState.SubMode.NONE
)
}
// |X|
@VimBehaviorDiffers(
originalVimAfter = "The brown ${c}catch over the lazy dog",
shouldBeFixed = true
)
fun `test visual exchange words from outside`() {
doTest(StringHelper.parseKeys("v3e", "X", "w", "veX"),
"The ${c}quick brown fox catch over the lazy dog",
"The brow${c}n catch over the lazy dog",
CommandState.Mode.COMMAND,
CommandState.SubMode.NONE
)
}
// |cxx|
@VimBehaviorDiffers(
originalVimAfter =
"""The quick
catch over
${c}brown fox
the lazy dog
""",
shouldBeFixed = true
)
fun `test exchange lines top down`() {
doTest(StringHelper.parseKeys("cxx", "j", "cxx"),
"""The quick
brown ${c}fox
catch over
the lazy dog""".trimIndent(),
"""The quick
${c}catch over
brown fox
the lazy dog""".trimIndent(),
CommandState.Mode.COMMAND,
CommandState.SubMode.NONE
)
}
// |cxx|
@VimBehaviorDiffers(
originalVimAfter =
"""The quick
catch over
${c}brown fox
the lazy dog
""",
shouldBeFixed = true
)
fun `test exchange lines top down with dot`() {
doTest(StringHelper.parseKeys("cxx", "j", "."),
"""The quick
brown ${c}fox
catch over
the lazy dog""".trimIndent(),
"""The quick
${c}catch over
brown fox
the lazy dog""".trimIndent(),
CommandState.Mode.COMMAND,
CommandState.SubMode.NONE
)
}
@VimBehaviorDiffers(
originalVimAfter = """
The quick
brown thecatch over
fox
lazy dog
"""
)
fun `test exchange to the line end`() {
doTest(StringHelper.parseKeys("v$", "X", "jj^ve", "X"),
"""The quick
brown ${c}fox
catch over
the lazy dog""".trimIndent(),
"""The quick
brown the
catch over
fox lazy dog""".trimIndent(),
CommandState.Mode.COMMAND,
CommandState.SubMode.NONE
)
}
@VimBehaviorDiffers(
originalVimAfter =
"""
catch over
the lazy dog
${c}The quick
brown fox
""",
shouldBeFixed = true
)
fun `test exchange visual lines`() {
doTest(StringHelper.parseKeys("Vj", "X", "jj", "Vj", "X"),
"""
The ${c}quick
brown fox
catch over
the lazy dog
""".trimIndent(),
"""
${c}catch over
the lazy dog
The quick
brown fox
""".trimIndent(),
CommandState.Mode.COMMAND,
CommandState.SubMode.NONE
)
}
fun `test visual char highlighter`() {
val before = """
The ${c}quick
brown fox
catch over
the lazy dog
""".trimIndent()
configureByText(before)
typeText(StringHelper.parseKeys("vlll", "X"))
assertHighlighter(4, 8, HighlighterTargetArea.EXACT_RANGE)
// Exit vim-exchange
exitExchange()
}
fun `test visual line highdhitligthhter`() {
val before = """
The ${c}quick
brown fox
catch over
the lazy dog
""".trimIndent()
configureByText(before)
typeText(StringHelper.parseKeys("Vj", "X"))
assertHighlighter(4, 15, HighlighterTargetArea.LINES_IN_RANGE)
// Exit vim-exchange
exitExchange()
}
fun `test till the line end highlighter`() {
val before = """
The ${c}quick
brown fox
""".trimIndent()
configureByText(before)
typeText(StringHelper.parseKeys("v$", "X"))
assertHighlighter(4, 10, HighlighterTargetArea.EXACT_RANGE)
// Exit vim-exchange
exitExchange()
}
fun `test pre line end highlighter`() {
val before = """
The ${c}quick
brown fox
""".trimIndent()
configureByText(before)
typeText(StringHelper.parseKeys("v\$h", "X"))
assertHighlighter(4, 9, HighlighterTargetArea.EXACT_RANGE)
// Exit vim-exchange
exitExchange()
}
fun `test pre pre line end highlighter`() {
val before = """
The ${c}quick
brown fox
""".trimIndent()
configureByText(before)
typeText(StringHelper.parseKeys("v\$hh", "X"))
assertHighlighter(4, 8, HighlighterTargetArea.EXACT_RANGE)
// Exit vim-exchange
exitExchange()
}
fun `test to file end highlighter`() {
val before = """
The quick
brown ${c}fox
""".trimIndent()
configureByText(before)
typeText(StringHelper.parseKeys("v\$", "X"))
assertHighlighter(16, 19, HighlighterTargetArea.EXACT_RANGE)
// Exit vim-exchange
exitExchange()
}
fun `test to file end with new line highlighter`() {
val before = """
The quick
brown ${c}fox
""".trimIndent()
configureByText(before)
typeText(StringHelper.parseKeys("v\$", "X"))
assertHighlighter(16, 20, HighlighterTargetArea.EXACT_RANGE)
// Exit vim-exchange
exitExchange()
}
private fun exitExchange() {
typeText(StringHelper.parseKeys("cxc"))
}
private fun assertHighlighter(start: Int, end: Int, area: HighlighterTargetArea) {
val currentExchange = myFixture.editor.getUserData(VimExchangeExtension.EXCHANGE_KEY)!!
val highlighter = currentExchange.getHighlighter()!!
assertEquals(start, highlighter.startOffset)
assertEquals(end, highlighter.endOffset)
assertEquals(area, highlighter.targetArea)
}
}

View File

@@ -24,7 +24,6 @@ import com.maddyhome.idea.vim.command.SelectionType
import com.maddyhome.idea.vim.common.TextRange
import com.maddyhome.idea.vim.helper.StringHelper.parseKeys
import com.maddyhome.idea.vim.helper.VimBehaviorDiffers
import junit.framework.Assert
import org.jetbrains.plugins.ideavim.VimTestCase
import org.jetbrains.plugins.ideavim.rangeOf
@@ -51,7 +50,7 @@ class ReplaceWithRegisterTest : VimTestCase() {
VimPlugin.getRegister().storeText(myFixture.editor, text rangeOf "one", SelectionType.CHARACTER_WISE, false)
typeText(parseKeys("griw"))
myFixture.checkResult("one on${c}e three")
Assert.assertEquals("one", VimPlugin.getRegister().lastRegister?.text)
assertEquals("one", VimPlugin.getRegister().lastRegister?.text)
}
fun `test empty text`() {
@@ -78,10 +77,10 @@ class ReplaceWithRegisterTest : VimTestCase() {
configureByText(text)
typeText(parseKeys("\"ayiw", "w", "\"agriw"))
myFixture.checkResult("one two tw${c}o four")
Assert.assertEquals("two", VimPlugin.getRegister().lastRegister?.text)
assertEquals("two", VimPlugin.getRegister().lastRegister?.text)
typeText(parseKeys("w", "griw"))
myFixture.checkResult("one two two tw${c}o")
Assert.assertEquals("two", VimPlugin.getRegister().lastRegister?.text)
assertEquals("two", VimPlugin.getRegister().lastRegister?.text)
}
fun `test replace use clipboard register`() {
@@ -90,7 +89,7 @@ class ReplaceWithRegisterTest : VimTestCase() {
configureByText(text)
typeText(parseKeys("\"+yiw", "w", "\"+griw", "w", "\"+griw"))
myFixture.checkResult("one two two tw${c}o")
Assert.assertEquals("two", VimPlugin.getRegister().lastRegister?.text)
assertEquals("two", VimPlugin.getRegister().lastRegister?.text)
}
fun `test replace use wrong register`() {
@@ -154,7 +153,7 @@ class ReplaceWithRegisterTest : VimTestCase() {
VimPlugin.getRegister().storeText(myFixture.editor, text rangeOf "one", SelectionType.CHARACTER_WISE, false)
typeText(parseKeys("3griw"))
myFixture.checkResult("one on${c}e four")
Assert.assertEquals("one", VimPlugin.getRegister().lastRegister?.text)
assertEquals("one", VimPlugin.getRegister().lastRegister?.text)
}
@VimBehaviorDiffers("one on${c}e on${c}e four")
@@ -165,7 +164,7 @@ class ReplaceWithRegisterTest : VimTestCase() {
VimPlugin.getRegister().storeText(myFixture.editor, text rangeOf "one", SelectionType.CHARACTER_WISE, false)
typeText(parseKeys("griw"))
myFixture.checkResult("one two one four")
Assert.assertEquals("one", VimPlugin.getRegister().lastRegister?.text)
assertEquals("one", VimPlugin.getRegister().lastRegister?.text)
}
fun `test dot repeat`() {
@@ -175,7 +174,7 @@ class ReplaceWithRegisterTest : VimTestCase() {
VimPlugin.getRegister().storeText(myFixture.editor, text rangeOf "one", SelectionType.CHARACTER_WISE, false)
typeText(parseKeys("griw", "w", "."))
myFixture.checkResult("one one on${c}e four")
Assert.assertEquals("one", VimPlugin.getRegister().lastRegister?.text)
assertEquals("one", VimPlugin.getRegister().lastRegister?.text)
}
// --------------------------------------- grr --------------------------
@@ -197,7 +196,7 @@ class ReplaceWithRegisterTest : VimTestCase() {
where it was settled on some sodden sand
hard by the torrent of a mountain pass.
""".trimIndent())
Assert.assertEquals("legendary", VimPlugin.getRegister().lastRegister?.text)
assertEquals("legendary", VimPlugin.getRegister().lastRegister?.text)
}
fun `test line replace with line`() {
@@ -347,7 +346,7 @@ class ReplaceWithRegisterTest : VimTestCase() {
where it was settled on some sodden sand
hard by the torrent of a mountain pass.
""".trimIndent())
Assert.assertEquals("legendary", VimPlugin.getRegister().lastRegister?.text)
assertEquals("legendary", VimPlugin.getRegister().lastRegister?.text)
assertMode(CommandState.Mode.COMMAND)
}

View File

@@ -20,6 +20,9 @@
package org.jetbrains.plugins.ideavim.group.visual
import com.intellij.codeInsight.template.TemplateManager
import com.intellij.codeInsight.template.impl.ConstantNode
import com.intellij.codeInsight.template.impl.TemplateManagerImpl
import com.maddyhome.idea.vim.command.CommandState
import com.maddyhome.idea.vim.group.visual.IdeaSelectionControl
import com.maddyhome.idea.vim.helper.StringHelper.parseKeys
@@ -674,8 +677,6 @@ class IdeaVisualControlTest : VimOptionTestCase(SelectModeOptionData.name) {
}
/*
// [VERSION UPDATE] 191+ Open this test
@VimOptionTestConfiguration(VimTestOption(SelectModeOptionData.name, VimTestOptionType.LIST, [""]))
fun `test control selection from line to char visual modes in keep mode`() {
configureByText("""
@@ -707,5 +708,4 @@ class IdeaVisualControlTest : VimOptionTestCase(SelectModeOptionData.name) {
createdTemplate.addVariable(ConstantNode("1"), true)
templateManager.startTemplate(myFixture.editor, createdTemplate)
}
*/
}

View File

@@ -26,10 +26,8 @@ import com.intellij.codeInsight.template.impl.TemplateManagerImpl
import com.intellij.ide.DataManager
import com.intellij.injected.editor.EditorWindow
import com.intellij.openapi.editor.Editor
import com.intellij.openapi.util.Key
import com.intellij.refactoring.rename.inplace.VariableInplaceRenameHandler
import com.intellij.testFramework.PlatformTestUtil
import com.intellij.testFramework.TestModeFlags
import com.intellij.testFramework.fixtures.CodeInsightTestUtil.doInlineRename
import com.maddyhome.idea.vim.command.CommandState
import com.maddyhome.idea.vim.helper.StringHelper.parseKeys
@@ -55,10 +53,7 @@ class TemplateTest : VimOptionTestCase(IdeaRefactorMode.name) {
override fun setUp() {
super.setUp()
// TODO: 24.10.2019 [VERSION UPDATE] 2019.1
// TemplateManagerImpl.setTemplateTesting(myFixture.testRootDisposable)
@Suppress("DEPRECATION", "UNCHECKED_CAST")
TestModeFlags.set(Key.findKeyByName("TemplateTesting") as Key<Boolean>, true, myFixture.testRootDisposable)
TemplateManagerImpl.setTemplateTesting(myFixture.testRootDisposable)
}
@VimOptionDefaultAll

View File

@@ -0,0 +1,113 @@
/*
* IdeaVim - Vim emulator for IDEs based on the IntelliJ platform
* Copyright (C) 2003-2020 The IdeaVim authors
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package org.jetbrains.plugins.ideavim.ui
import com.intellij.mock.MockEditorFactory
import com.maddyhome.idea.vim.ex.vimscript.VimScriptParser
import com.maddyhome.idea.vim.ui.VimRcFileState
import org.jetbrains.plugins.ideavim.VimTestCase
class ReloadVimRcTest : VimTestCase() {
val editorFactory = MockEditorFactory()
override fun setUp() {
super.setUp()
VimRcFileState.clear()
}
fun `test equalTo`() {
val file = """
map x y
""".trimIndent()
val lines = convertFileToLines(file)
VimRcFileState.saveFileState("", lines)
val document = editorFactory.createDocument(file)
assertTrue(VimRcFileState.equalTo(document))
}
fun `test equalTo with whitespaces`() {
val s = " " // Just to see whitespaces in the following code
val origFile = """
map x y
set myPlugin
map z t
""".trimIndent()
val changedFile = """
map x y
set myPlugin$s$s$s$s$s$s
map z t
""".trimIndent()
val lines = convertFileToLines(origFile)
VimRcFileState.saveFileState("", lines)
val document = editorFactory.createDocument(changedFile)
assertTrue(VimRcFileState.equalTo(document))
}
fun `test equalTo add line`() {
val s = " " // Just to see whitespaces in the following code
val origFile = """
map x y
set myPlugin
""".trimIndent()
val changedFile = """
map x y
set myPlugin
map z t
""".trimIndent()
val lines = convertFileToLines(origFile)
VimRcFileState.saveFileState("", lines)
val document = editorFactory.createDocument(changedFile)
assertFalse(VimRcFileState.equalTo(document))
}
fun `test equalTo remove line`() {
val s = " " // Just to see whitespaces in the following code
val origFile = """
map x y
set myPlugin
""".trimIndent()
val changedFile = """
map x y
""".trimIndent()
val lines = convertFileToLines(origFile)
VimRcFileState.saveFileState("", lines)
val document = editorFactory.createDocument(changedFile)
assertFalse(VimRcFileState.equalTo(document))
}
private fun convertFileToLines(file: String): List<String> {
val res = ArrayList<String>()
file.split("\n").forEach { VimScriptParser.lineProcessor(it, res) }
return res
}
}