1
0
mirror of https://github.com/chylex/IntelliJ-IdeaVim.git synced 2024-11-25 07:42:59 +01:00

Compare commits

...

189 Commits

Author SHA1 Message Date
87b45da4ee
Set plugin version to chylex-29 2024-03-22 00:08:11 +01:00
56ee5f3bd4
Disable speed search in Project tool window when NERDTree is enabled 2024-03-21 23:29:31 +01:00
aef6016b33
Apply scrolloff after executing native IDEA actions 2024-03-21 23:18:58 +01:00
c2c23d8059
Stay on same line after reindenting 2024-03-21 23:18:58 +01:00
854563130a
Update search register when using f/t 2024-03-21 23:18:58 +01:00
7c61822106
Automatically add unambiguous imports after running a macro 2024-03-21 23:18:58 +01:00
4fce924424
Fix(VIM-3179): Respect virtual space below editor (imperfectly) 2024-03-21 23:18:58 +01:00
56e8da8808
Fix(VIM-3178): Workaround to support "Jump to Source" action mapping 2024-03-21 23:18:58 +01:00
c5d108c3d6
Fix(VIM-3166): Workaround to fix broken filtering of visual lines 2024-03-21 23:18:58 +01:00
c76e4eabd4
Add support for count for visual and line motion surround 2024-03-21 23:18:58 +01:00
98c9579d8d
Fix vim-surround not working with multiple cursors
Fixes multiple cursors with vim-surround commands `cs, ds, S` (but not `ys`).
2024-03-21 23:18:58 +01:00
f060750768
Fix(VIM-696) Restore visual mode after undo/redo, and disable incompatible actions 2024-03-21 23:18:58 +01:00
83c2e1c362
Revert(VIM-2884): Fix moving lines to cursor 2024-03-21 23:18:57 +01:00
6555e8dddd
Respect count with <Action> mappings 2024-03-21 23:18:57 +01:00
528fd1b1d3
Change matchit plugin to use HTML patterns in unrecognized files 2024-03-21 23:18:57 +01:00
932fb509ff
Reset insert mode when switching active editor 2024-03-21 23:18:57 +01:00
f353f47987
Remove update checker 2024-03-21 23:00:08 +01:00
5410187bd5
Set custom plugin version 2024-03-21 23:00:06 +01:00
Alex Plate
58a8b96c3c
Revert "Stop IdeaVim actions flowing into JB Client"
This reverts commit bd192561ae.

This commit reverts the fix for VIM-3283 because it causes VIM-3346 and VIM-3347
2024-03-20 13:08:04 +02:00
dependabot[bot]
0e057ca9ae Bump org.eclipse.jgit:org.eclipse.jgit.ssh.apache
Bumps org.eclipse.jgit:org.eclipse.jgit.ssh.apache from 6.8.0.202311291450-r to 6.9.0.202403050737-r.

---
updated-dependencies:
- dependency-name: org.eclipse.jgit:org.eclipse.jgit.ssh.apache
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
2024-03-13 16:06:19 +00:00
dependabot[bot]
36bf2639bb Bump io.ktor:ktor-client-cio from 2.3.8 to 2.3.9
Bumps [io.ktor:ktor-client-cio](https://github.com/ktorio/ktor) from 2.3.8 to 2.3.9.
- [Release notes](https://github.com/ktorio/ktor/releases)
- [Changelog](https://github.com/ktorio/ktor/blob/2.3.9/CHANGELOG.md)
- [Commits](https://github.com/ktorio/ktor/compare/2.3.8...2.3.9)

---
updated-dependencies:
- dependency-name: io.ktor:ktor-client-cio
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2024-03-13 18:04:55 +02:00
dependabot[bot]
0c1326e689 Bump com.google.devtools.ksp:symbol-processing-api
Bumps [com.google.devtools.ksp:symbol-processing-api](https://github.com/google/ksp) from 1.9.22-1.0.18 to 1.9.23-1.0.19.
- [Release notes](https://github.com/google/ksp/releases)
- [Commits](https://github.com/google/ksp/commits/1.9.23-1.0.19)

---
updated-dependencies:
- dependency-name: com.google.devtools.ksp:symbol-processing-api
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2024-03-13 18:04:38 +02:00
dependabot[bot]
dd74438f68 Bump org.jetbrains.kotlin:kotlin-stdlib from 1.9.22 to 1.9.23
Bumps [org.jetbrains.kotlin:kotlin-stdlib](https://github.com/JetBrains/kotlin) from 1.9.22 to 1.9.23.
- [Release notes](https://github.com/JetBrains/kotlin/releases)
- [Changelog](https://github.com/JetBrains/kotlin/blob/v1.9.23/ChangeLog.md)
- [Commits](https://github.com/JetBrains/kotlin/compare/v1.9.22...v1.9.23)

---
updated-dependencies:
- dependency-name: org.jetbrains.kotlin:kotlin-stdlib
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2024-03-13 18:04:26 +02:00
Alex Plate
a9ddfac782
Add test that shows an issue when deleting a line at the end of file 2024-03-12 21:21:15 +02:00
Alex Plate
79437df894
Fix(VIM-3330): Use Z backward visual search in sneak plugin 2024-03-12 20:01:35 +02:00
Alex Plate
b5a04af089
Switch to a stable version of the plugin verifier because the latest version is broken.
Broken version: https://github.com/JetBrains/intellij-plugin-verifier/releases/tag/1.364

Internal discussion: https://jetbrains.slack.com/archives/C03RHGR7J/p1710229884548179
2024-03-12 12:02:21 +02:00
Alex Plate
52372ae3d3
Disable plugin verifier for tests 2024-03-12 09:34:55 +02:00
Alex Plate
65d755d9b2
Bring back the getMappingMode function for binary compatibility with the which-key plugin 2024-03-12 09:31:59 +02:00
Alex Plate
1f1a8f3395
Avoid generation of the huge amount of sets during regex search
The tests shows that the depth of `epsilonVisited` is usually around 0-3, so there is no need to use the set. However, when the set is used, we have to make a new copy everytime we create a new `SimulationStackFrame`.
Now, the previous stack is reused.
2024-03-11 18:51:46 +02:00
Alex Plate
629e4e7053
Fix(VIM-3336): Improve the performance of n in large files
The git history shows that the force update of the search highlights was accidentally enabled during the refactorings
2024-03-11 18:49:38 +02:00
Alex Plate
c50a299cfd
Remove the unused import 2024-03-11 18:48:27 +02:00
Alex Plate
4bad129caf
Do not register clipboard option change listener for caret registers
Register groups for the caret do not use some fields from the base class, however the listener for these fiels is still registered. Now we don't register this listener.
Generally it looks like a bigger refactoring can be performed in order to separate the common registers logic from caret registers logic.

This change should improve the performance of the IjVimCaret initialization because now we won't register a new disposable on each instance of IjVimCaret

This is a part of VIM-3336
2024-03-11 17:41:54 +02:00
Alex Plate
1ffb28e21b
Wait for some checks in UI tests instead of immediate verification 2024-03-11 14:29:08 +02:00
dependabot[bot]
c126243367 Bump io.ktor:ktor-client-auth from 2.3.8 to 2.3.9
Bumps [io.ktor:ktor-client-auth](https://github.com/ktorio/ktor) from 2.3.8 to 2.3.9.
- [Release notes](https://github.com/ktorio/ktor/releases)
- [Changelog](https://github.com/ktorio/ktor/blob/2.3.9/CHANGELOG.md)
- [Commits](https://github.com/ktorio/ktor/compare/2.3.8...2.3.9)

---
updated-dependencies:
- dependency-name: io.ktor:ktor-client-auth
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2024-03-06 18:30:12 +02:00
dependabot[bot]
6da6e461a8 Bump io.ktor:ktor-serialization-kotlinx-json from 2.3.8 to 2.3.9
Bumps [io.ktor:ktor-serialization-kotlinx-json](https://github.com/ktorio/ktor) from 2.3.8 to 2.3.9.
- [Release notes](https://github.com/ktorio/ktor/releases)
- [Changelog](https://github.com/ktorio/ktor/blob/2.3.9/CHANGELOG.md)
- [Commits](https://github.com/ktorio/ktor/compare/2.3.8...2.3.9)

---
updated-dependencies:
- dependency-name: io.ktor:ktor-serialization-kotlinx-json
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2024-03-06 18:15:59 +02:00
dependabot[bot]
103101bbcb Bump com.google.devtools.ksp:symbol-processing-api
Bumps [com.google.devtools.ksp:symbol-processing-api](https://github.com/google/ksp) from 1.9.22-1.0.17 to 1.9.22-1.0.18.
- [Release notes](https://github.com/google/ksp/releases)
- [Commits](https://github.com/google/ksp/commits)

---
updated-dependencies:
- dependency-name: com.google.devtools.ksp:symbol-processing-api
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2024-03-06 18:04:15 +02:00
dependabot[bot]
f737fcba1a Bump io.ktor:ktor-client-core from 2.3.8 to 2.3.9
Bumps [io.ktor:ktor-client-core](https://github.com/ktorio/ktor) from 2.3.8 to 2.3.9.
- [Release notes](https://github.com/ktorio/ktor/releases)
- [Changelog](https://github.com/ktorio/ktor/blob/2.3.9/CHANGELOG.md)
- [Commits](https://github.com/ktorio/ktor/compare/2.3.8...2.3.9)

---
updated-dependencies:
- dependency-name: io.ktor:ktor-client-core
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2024-03-06 18:03:05 +02:00
dependabot[bot]
c5fa0678b8 Bump io.ktor:ktor-client-content-negotiation from 2.3.8 to 2.3.9
Bumps [io.ktor:ktor-client-content-negotiation](https://github.com/ktorio/ktor) from 2.3.8 to 2.3.9.
- [Release notes](https://github.com/ktorio/ktor/releases)
- [Changelog](https://github.com/ktorio/ktor/blob/2.3.9/CHANGELOG.md)
- [Commits](https://github.com/ktorio/ktor/compare/2.3.8...2.3.9)

---
updated-dependencies:
- dependency-name: io.ktor:ktor-client-content-negotiation
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2024-03-06 18:02:42 +02:00
filipp
00ccddf8cf Revert changes to SearchGroup
Wrong branch. The changes should be merged to master only after review in the Fleet branch
2024-03-03 22:16:19 +02:00
filipp
00cbf188fb Replace findUnmatchedBlock method with a new implementation 2024-03-03 22:05:28 +02:00
filipp
988ea74461 Fix(VIM-3294): %-movement mismatches braces 2024-03-03 22:05:28 +02:00
filipp
0914cda7e5 Better matching for a sequence of single-line comments 2024-03-03 22:05:28 +02:00
filipp
5959e9aaa1 Fix(VIM-1399): Uncommented brackets are matched to commented ones in VIM mode 2024-03-03 22:05:28 +02:00
filipp
434df565ae Migrate % command to work with newer method in SearchGroup.kt 2024-03-03 22:05:28 +02:00
filipp
c8f36504d8 Fix tests for % 2024-03-03 22:05:28 +02:00
filipp
06e1af371e Add SearchGroup.kt
In the future, it should become a container for all the search methods that we have in IdeaVim
At the moment we have a bunch of SearchGroups and SearchHelpers, and it may be confusing.
We also want to avoid using unnecessary OOP.
2024-03-03 22:05:28 +02:00
filipp
d744987ac8 Add VimPsiService
We want to avoid unnecessary OOP and use interfaces only for cases where we will have different implementations for different IDEs
This service will help us in our future refactorings of SearchGroup and SearchHelper
2024-03-03 22:03:58 +02:00
filipp
b4eef17aaa Add StringUtil.kt class
Methods in this file will be helpful in future search refactorings
2024-03-03 22:03:58 +02:00
filipp
5c50e8607c Fix search 2024-03-01 08:50:18 +02:00
Filipp Vakhitov
9a324ab448 Reset KeyHandlerState when switching Editors
Now we have a single state for all the editors, so we should not mix their states
2024-02-29 20:27:46 +02:00
dependabot[bot]
c3978335f5 Bump org.mockito.kotlin:mockito-kotlin from 5.0.0 to 5.2.1
Bumps [org.mockito.kotlin:mockito-kotlin](https://github.com/mockito/mockito-kotlin) from 5.0.0 to 5.2.1.
- [Release notes](https://github.com/mockito/mockito-kotlin/releases)
- [Commits](https://github.com/mockito/mockito-kotlin/compare/5.0.0...5.2.1)

---
updated-dependencies:
- dependency-name: org.mockito.kotlin:mockito-kotlin
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
2024-02-28 16:01:23 +00:00
Alex Plate
051296c2aa
UI tests: make sure the text appear in the editor before running tests 2024-02-28 09:37:05 +02:00
Alex Plate
90f2d2ff29
Small update of the minimal version of IJ 2024-02-27 14:43:23 +02:00
Alex Plate
4c2edab406
Run optimize imports 2024-02-27 13:17:02 +02:00
Alex Plate
76e8fd69bf
Increase timeout in UI tests 2024-02-27 11:30:51 +02:00
IdeaVim Bot
5dd458bcf7 Add lippfi, FilipParker to contributors list 2024-02-24 09:02:05 +00:00
Filipp Vakhitov
a94a8b8539 Fix tests 2024-02-24 01:03:18 +02:00
Filipp Vakhitov
261230b23a Remove experimental showmodewidget option 2024-02-24 00:36:36 +02:00
Filipp Vakhitov
b90317e00e More visible text color for mode widget
Visibility may be unexpected for custom themes with non-obvious colors
2024-02-24 00:27:48 +02:00
Filipp Vakhitov
21c9dc8785 Add statistic collector for mode widget 2024-02-24 00:15:01 +02:00
Alex Plate
31bbc60325
Fix all reports of the inspection that prohibits the use of companion objects
This is a requirement from the platform, as a huge amount of companion objects leads to a higher level of classloading
2024-02-23 18:55:01 +02:00
Alex Plate
fec6e5c189
Remove the last argument in EventLogGroup as the compatibility was fixed on the platform side 2024-02-23 18:35:21 +02:00
Alex Plate
23c1493f17
Fix(VIM-3306): Vim paragraph motion won't make mappings if there is already such mapping defined by user 2024-02-23 18:26:50 +02:00
lippfi
00808af569
Merge pull request #824 from JetBrains/fleet
Asynchronous key processing for Fleet
2024-02-23 17:25:21 +02:00
filipp
3c94091d30 Merge branch 'refs/heads/master' into fleet 2024-02-23 17:24:08 +02:00
filipp
b737362aba Update CaretVisualAttributesListener to use new Editor API 2024-02-23 17:21:18 +02:00
Parker7123
db722fc4e5 VIM-1472 Add support for sorting with pattern 2024-02-23 17:15:21 +02:00
filipp
7d679e68dc Merge branch 'refs/heads/master' into fleet
# Conflicts:
#	vim-engine/src/main/kotlin/com/maddyhome/idea/vim/api/VimEditorGroup.kt
#	vim-engine/src/main/kotlin/com/maddyhome/idea/vim/impl/state/VimStateMachineImpl.kt
2024-02-23 17:08:01 +02:00
Matt Ellis
bc808403fb Rename localEditors to getEditors
The fact that these methods only return local editors (i.e., editors for the local user while hosting a Code With Me session) is an implementation detail
2024-02-23 17:01:32 +02:00
Matt Ellis
9d6dc317a4 Only notify editors for the current buffer 2024-02-23 17:01:32 +02:00
Matt Ellis
cf29c50f31 Ensure editors are initialised before use
Fixes VIM-3256
2024-02-23 17:01:32 +02:00
Alex Plate
2a3c4cc441
Use a link to changelog 2024-02-23 16:02:38 +02:00
Matt Ellis
bd192561ae Stop IdeaVim actions flowing into JB Client
IdeaVim actions are local only - they control local behaviour and should not be visible in the Client

Fixes VIM-3283
2024-02-23 15:54:20 +02:00
Matt Ellis
66ff56a05e Move document listeners to global listeners
This means we listen to changes in all documents, rather than just the changes in the documents for open local editors. And this means that we correctly update e.g. marks when a non-local editor changes a file that isn't open in a local editor.
2024-02-23 15:54:20 +02:00
Matt Ellis
def86d179e Review disabled editor checks 2024-02-23 15:54:20 +02:00
Matt Ellis
3c9a343f8b Review listeners to only work with local editors
Reviews all IntelliJ listeners to ensure that they only work with supported local editors. Editor creation was initialising IdeaVim for all editors, which meant that behaviour could leak into Code With Me guest editors. E.g. guest editors incorrectly drawing relative line numbers, or the host using the guest's last selected tab when switching to an alternate file.

This leads to a change in behaviour with some local editors. The editor creation listener will now check to see if the editor is local *and supported*. This means it can exclude single line editors, editors in database cells or dialogs, depending on the state of 'ideavimsupport' at creation time. The behaviour at creation time is now more correct, but if 'ideavimsupport' is modified, existing matching editors will not be initialised.

Fixes VIM-3274, fixes VIM-3275
2024-02-23 15:54:20 +02:00
Matt Ellis
10b6b05fab Clear disposable after disposing 2024-02-23 15:54:20 +02:00
Matt Ellis
caa4ef736a Rename method for clarity 2024-02-23 15:54:20 +02:00
Matt Ellis
23702345a9 Fix comments 2024-02-23 15:54:20 +02:00
Matt Ellis
ba89babd10 Move listener to app level
Fixes VIM-2167
2024-02-23 15:54:20 +02:00
Matt Ellis
2ce3fbd677 Use common APIs to get local editors 2024-02-23 15:54:20 +02:00
Matt Ellis
d8de73a06d Use correct APIs to get local only editors
Always ignores non-local, hidden editors opened by remote guests in Code With Me sessions.

Fixes VIM-3268
2024-02-23 15:54:20 +02:00
Alex Plate
8094e6711a
Update qodana baseline 2024-02-23 15:38:33 +02:00
Alex Plate
10edccc1d6
Add matchit test for jump from try to catch and to finally
From PR https://github.com/JetBrains/ideavim/pull/802
2024-02-23 15:36:38 +02:00
Alex Plate
247aaed188
Use the property to change the state of the octopus handler 2024-02-23 15:32:35 +02:00
Filipp Vakhitov
1a4333fa1b Move implementations to upper level
It will simplify support of immutable editors in Fleet
2024-02-23 15:09:45 +02:00
Filipp Vakhitov
8eaa6df318 Throw error instead of warning on state conflict
It may indicate some serious issues, and we would like to know if anything goes wrong
2024-02-23 15:09:45 +02:00
Filipp Vakhitov
7523db186f Empty status bar message after each test 2024-02-23 15:09:45 +02:00
filipp
4aac113522 Remove duplicate method 2024-02-23 15:09:45 +02:00
filipp
795abd77a7 Add documentation 2024-02-23 15:09:45 +02:00
Filipp Vakhitov
38bc914504 Avoid using annotation-processors in vim-engine 2024-02-23 15:09:45 +02:00
Filipp Vakhitov
c8113eea83 Commit state after receiving unknown key 2024-02-23 15:09:45 +02:00
Filipp Vakhitov
924b7418e8 Fix DigraphSequence cloning 2024-02-23 15:09:45 +02:00
Filipp Vakhitov
a7dfef61e9 Make LazyVimCommand open 2024-02-23 15:09:45 +02:00
Filipp Vakhitov
db35c979b4 Move some editor methods to the base class 2024-02-23 15:09:45 +02:00
Filipp Vakhitov
2de933c723 Make processKey public 2024-02-23 15:09:45 +02:00
filipp
d3704d602f Cleanup after moving logic to other classes 2024-02-23 15:09:45 +02:00
filipp
ea62f227bf Remove piece of code for handling bad commands
Bad commands are handled in consumers
2024-02-23 15:09:45 +02:00
filipp
23fdadc32e Fix test
Sometimes it's not a plugin error and may indicate that key is propagated for later handling by IDE
But what we know for sure - that for both cases we should reset command builder
2024-02-23 15:09:45 +02:00
filipp
e9bf06686f Add synchronize blocks to minimize risk of concurrent key processing and changing of the KeyHandlerState 2024-02-23 15:09:45 +02:00
filipp
7842b155c1 Move some logic to ModeInputConsumer 2024-02-23 15:09:45 +02:00
filipp
74a8277e10 Move some logic to SelectRegisterConsumer 2024-02-23 15:09:45 +02:00
filipp
ddb1b80463 Move some logic to CommandConsumer 2024-02-23 15:09:45 +02:00
filipp
eea3336934 Move some logic to CommandConsumer 2024-02-23 15:09:45 +02:00
filipp
f801145712 Update MappingInfo to match newer signature 2024-02-23 15:09:45 +02:00
filipp
e033b08535 Move some logic to DigraphConsumer 2024-02-23 15:09:45 +02:00
filipp
1d9514a205 Move some logic to RegisterConsumer 2024-02-23 15:09:45 +02:00
filipp
6741120f19 Move some logic to CharArgumentConsumer 2024-02-23 15:09:45 +02:00
filipp
c501457322 Move some logic to EditorResetConsumer 2024-02-23 15:09:45 +02:00
filipp
46425a24c3 Move some logic to DeleteCommandConsumer 2024-02-23 15:09:45 +02:00
filipp
9826f0a7f0 Move some logic to CommandCountConsumer 2024-02-23 15:09:45 +02:00
filipp
43175061e0 Fix broken digraphSequence
It shouldn't be retested on partial reset
2024-02-23 15:09:45 +02:00
filipp
0ab32cac34 Make MappingProcessor a KeyConsumer 2024-02-23 15:09:45 +02:00
filipp
e3ec9c614b Add KeyConsumer
It will help us to have a more modular KeyHandler in future (chain of different consumers)
2024-02-23 15:09:45 +02:00
filipp
f454d60234 Add MutableBoolean to be able to pass and modify shouldRecord in methods 2024-02-23 15:09:45 +02:00
filipp
19fa00837c Use KeyProcessResultBuilder
It will help us to build the KeyProcessResult that we need for asynchronous key processing
2024-02-23 15:09:45 +02:00
filipp
275c5d28e1 Add KeyProcessResultBuilder 2024-02-23 15:09:45 +02:00
filipp
15ae069f6f Make keyHandlerState argument not null
Applying default values may lead to unexpected results, especially if we sometimes want to use the global state (IJ), and at other times, its clone for asynchronous processing (Fleet).
2024-02-23 15:09:45 +02:00
Filipp Vakhitov
00f5541dc6 Add KeyProcessResult interface 2024-02-23 15:09:45 +02:00
Filipp Vakhitov
02540eb303 Pass KeyHandlerState as a method argument 2024-02-23 15:09:45 +02:00
Filipp Vakhitov
282e581bdb Make state cloneable 2024-02-23 15:09:45 +02:00
Filipp Vakhitov
31e7c49608 Add equals & hashCode 2024-02-23 15:09:45 +02:00
filipp
7966a6dc91 Create KeyHandlerState
We do not need multiple commandBuilder, digraphSequence or mappingState and this class will be a singleton containing them
2024-02-23 15:09:45 +02:00
filipp
5fc2f04224 Remove mappingMode from MappingState
It unnecessarily binds mappingState to mode and thus to editor. And we want to simplify things and have a single MappingState instead of multiple of them
2024-02-23 15:09:45 +02:00
filipp
6edfd8ed22 Remove deprecated showmode status bar text update that does not work with the new UI and will be replaced with widget 2024-02-23 15:09:45 +02:00
filipp
363db05db7 Macro recording state is no longer per editor
It will not only simplify VimStateMachine, but also help us to support multi-editor macros in future
2024-02-23 15:09:45 +02:00
filipp
3738012dd6 Listeners refactoring
1. Listeners now disposed after turning plugin off
2. Change widget listeners to be recreated on plugin toggle
3. Add CaretVisualAttributesListener
2024-02-23 15:09:45 +02:00
filipp
355cfe035d Remove Editor from VimStateMachine
Rationale:
1. A much more experienced developer, whom I highly respect, suggested to empty VimStateMachineImpl constructor in his TODO comment.
2. I aim for VimStateMachine to be a data class rather than being a container for both data and complex logic.
3. From an architectural perspective, it is more correct. Editors do have state (or they may possess a single global state if the corresponding option is set), but a state does not own an editor.
2024-02-23 15:09:44 +02:00
Alex Plate
6d01b5be77
Stop maintaining the changelog file
We have quite a fucntionality to maintain the changelof in actual state
However, since we switched to release from the latest EAP, we can't just update the changelog on master because it will contains also unreleased changes since the latest EAP.
The proper support for such change will require a lot of coding that will take a lot of time to implement and will eventually break.
So, it was decided to keep the changelog on YouTrack only and not to maintain the changes file.

This change still may be reverted, so the code around the changelog is note removed, but only commented out
2024-02-23 14:05:12 +02:00
Alex Plate
4938957483
Add a comment line with LATEST-EAP-SNAPSHOT 2024-02-23 13:37:36 +02:00
Alex Plate
46f4fa7cdd
Make tests about join notification more stable
Now we track only new notifications instead of just taking the last one
2024-02-23 11:27:15 +02:00
Alex Plate
f696135f31
Now we execute beforeActionPerformedUpdate instead of lastUpdateAndCheckDumb right before action execution
This is done because of platform changes. Now the `lastUpdateAndCheckDumb` doesn't update an action that supposed to be updated on background.
The problem was detected with commentary tests. The test supposed to use the line comment in case the block comment is not available. However, the since the action was not updated, the presentation was not reset to false and the fallback to line action was not performed.
2024-02-23 11:26:45 +02:00
Alex Plate
52e0fcdc7d
Use the custom version of IntIterator.skip because it was removed from the library 2024-02-23 10:29:33 +02:00
Alex Plate
ac17518a23
Update the changelog 2024-02-23 10:19:39 +02:00
6dd924b2b2 Implement motions to go to next/previous misspelled word 2024-02-23 10:17:25 +02:00
Matt Ellis
f439474b73 Fix set command tests
Also hides more feature flags and diagnostic settings from users and unit tests. Shows them when in internal mode.
2024-02-23 10:04:23 +02:00
Matt Ellis
d6cd92e256 Migrate extensions to use operatorfunc option 2024-02-23 10:04:23 +02:00
Matt Ellis
3a294268d9 Introduce operatorfunc option
Allows creating custom operators in script, as shown in JetBrains/ideavim#702
2024-02-23 10:04:23 +02:00
Alex Plate
9b81c7e650
Update junit version 2024-02-23 10:03:30 +02:00
Alex Plate
e229fb3ad7
Add new plugin that depends on IdeaVim 2024-02-23 09:26:46 +02:00
Alex Plate
720eae63fa
Fix the incorrect condition in UI tests 2024-02-23 09:23:42 +02:00
Alex Plate
0df96a24bd
Add a missing @BeforeEach in tests 2024-02-22 09:19:24 +02:00
Alex Plate
21a1588ede
Increase wait timeout for UI tests 2024-02-22 09:02:03 +02:00
Alex Plate
7970006e8c
Log the base commit during dev version calculation 2024-02-22 09:02:02 +02:00
dependabot[bot]
418d0cff7f Bump org.junit.jupiter:junit-jupiter from 5.10.1 to 5.10.2
Bumps [org.junit.jupiter:junit-jupiter](https://github.com/junit-team/junit5) from 5.10.1 to 5.10.2.
- [Release notes](https://github.com/junit-team/junit5/releases)
- [Commits](https://github.com/junit-team/junit5/compare/r5.10.1...r5.10.2)

---
updated-dependencies:
- dependency-name: org.junit.jupiter:junit-jupiter
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2024-02-21 17:42:43 +02:00
dependabot[bot]
7284360774 Bump org.jetbrains.intellij from 1.17.0 to 1.17.2
Bumps org.jetbrains.intellij from 1.17.0 to 1.17.2.

---
updated-dependencies:
- dependency-name: org.jetbrains.intellij
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2024-02-21 17:42:15 +02:00
dependabot[bot]
9fc3fadee8 Bump org.antlr:antlr4 from 4.10.1 to 4.13.1
Bumps [org.antlr:antlr4](https://github.com/antlr/antlr4) from 4.10.1 to 4.13.1.
- [Release notes](https://github.com/antlr/antlr4/releases)
- [Changelog](https://github.com/antlr/antlr4/blob/dev/CHANGES.txt)
- [Commits](https://github.com/antlr/antlr4/compare/4.10.1...4.13.1)

---
updated-dependencies:
- dependency-name: org.antlr:antlr4
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
2024-02-21 17:42:05 +02:00
Alex Plate
3d2db56f63
Use sets during plugin detection to avoid sorting problems 2024-02-21 10:43:44 +02:00
Alex Plate
e9c7cb8670
Update logic of calculation of dev version 2024-02-21 10:40:20 +02:00
dependabot[bot]
87d19274c5 Bump io.ktor:ktor-client-content-negotiation from 2.3.7 to 2.3.8
Bumps [io.ktor:ktor-client-content-negotiation](https://github.com/ktorio/ktor) from 2.3.7 to 2.3.8.
- [Release notes](https://github.com/ktorio/ktor/releases)
- [Changelog](https://github.com/ktorio/ktor/blob/main/CHANGELOG.md)
- [Commits](https://github.com/ktorio/ktor/commits)

---
updated-dependencies:
- dependency-name: io.ktor:ktor-client-content-negotiation
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2024-02-20 17:10:25 +02:00
dependabot[bot]
3161bf8ffd Bump io.ktor:ktor-client-core from 2.3.7 to 2.3.8
Bumps [io.ktor:ktor-client-core](https://github.com/ktorio/ktor) from 2.3.7 to 2.3.8.
- [Release notes](https://github.com/ktorio/ktor/releases)
- [Changelog](https://github.com/ktorio/ktor/blob/main/CHANGELOG.md)
- [Commits](https://github.com/ktorio/ktor/commits)

---
updated-dependencies:
- dependency-name: io.ktor:ktor-client-core
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2024-02-20 17:10:21 +02:00
Alex Plate
b68865587e
Wait up to 5 mins for initialization of PyCharm 2024-02-20 17:06:32 +02:00
dependabot[bot]
7dc0dbe944 Bump io.ktor:ktor-serialization-kotlinx-json from 2.3.7 to 2.3.8
Bumps [io.ktor:ktor-serialization-kotlinx-json](https://github.com/ktorio/ktor) from 2.3.7 to 2.3.8.
- [Release notes](https://github.com/ktorio/ktor/releases)
- [Changelog](https://github.com/ktorio/ktor/blob/main/CHANGELOG.md)
- [Commits](https://github.com/ktorio/ktor/commits)

---
updated-dependencies:
- dependency-name: io.ktor:ktor-serialization-kotlinx-json
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2024-02-20 16:56:35 +02:00
dependabot[bot]
f50a363525 Bump io.ktor:ktor-client-cio from 2.3.7 to 2.3.8
Bumps [io.ktor:ktor-client-cio](https://github.com/ktorio/ktor) from 2.3.7 to 2.3.8.
- [Release notes](https://github.com/ktorio/ktor/releases)
- [Changelog](https://github.com/ktorio/ktor/blob/main/CHANGELOG.md)
- [Commits](https://github.com/ktorio/ktor/commits)

---
updated-dependencies:
- dependency-name: io.ktor:ktor-client-cio
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2024-02-20 16:56:16 +02:00
Alex Plate
57ad4c70d1
Do not analyze test fixtures by qodana 2024-02-20 16:46:43 +02:00
Alex Plate
d3d93b898f
Unregister NotificationService project service
It's not registered as a light service and doesn't need to be registered in xml files
2024-02-20 16:46:07 +02:00
Alex Plate
7d8973edb2
Add tests for new java matchit functionality
From PR https://github.com/JetBrains/ideavim/pull/802
2024-02-20 16:42:28 +02:00
dependabot[bot]
2302b576b0 Bump io.ktor:ktor-client-auth from 2.3.7 to 2.3.8
Bumps [io.ktor:ktor-client-auth](https://github.com/ktorio/ktor) from 2.3.7 to 2.3.8.
- [Release notes](https://github.com/ktorio/ktor/releases)
- [Changelog](https://github.com/ktorio/ktor/blob/main/CHANGELOG.md)
- [Commits](https://github.com/ktorio/ktor/commits)

---
updated-dependencies:
- dependency-name: io.ktor:ktor-client-auth
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2024-02-20 16:42:07 +02:00
f4782630d4 Add Matchit support for Java statements 2024-02-20 16:41:34 +02:00
IdeaVim Bot
8c1a2a686f Update changelog after merging PR 2024-02-20 14:33:04 +00:00
32d5e1e6fa Enforce LF line separator in project code style 2024-02-20 16:31:36 +02:00
Alex Plate
a381a1cacc
Wait till all toolwindows initialziation 2024-02-20 16:19:29 +02:00
Alex Plate
73c3c9f7fe
Replace Enum.values() with Enum.entries, as suggested since 1.9 2024-02-20 16:12:34 +02:00
Alex Plate
67ef0a75d5
Update capitalization 2024-02-20 16:12:11 +02:00
Alex Plate
328bc5e95a
Convert some services to light services 2024-02-20 16:10:07 +02:00
Alex Plate
7f8021e37e
Update the function that waits for smart mode 2024-02-20 15:49:42 +02:00
Alex Plate
9701b7e79b
Add test reports to artifacts 2024-02-20 15:10:15 +02:00
Alex Plate
7a52c6fec9
Cleanup tests 2024-02-20 14:51:13 +02:00
Alex Plate
1503639d4b
Remove generated lexing from qodana analyze 2024-02-20 14:51:06 +02:00
Alex Plate
e82f19c852
Add test for checking an issue that
was caught by property tests
2024-02-20 13:52:17 +02:00
Alex Plate
edd69c9c25
Apply patch for qodata TC config 2024-02-20 13:12:14 +02:00
Alex Plate
fc61e369fb
Fix some deprecated calls 2024-02-20 13:11:10 +02:00
aleksei.plate@jetbrains.com
113586b59b TeamCity change in 'Ideavim' project: runners of 'Qodana checks' build configuration were updated 2024-02-20 10:53:37 +00:00
Alex Plate
5dbd5e1c89
Update the changelog 2024-02-20 12:47:06 +02:00
IdeaVim Bot
04b7d9e2c3 Preparation to 2.9.0 release 2024-02-20 10:41:06 +00:00
Alex Plate
5f2743176a
Update qodana configuration on TC 2024-02-20 12:18:24 +02:00
aleksei.plate@jetbrains.com
3723488617 TeamCity change in 'Ideavim' project: runners of 'Qodana checks' build configuration were updated 2024-02-20 10:16:06 +00:00
Alex Plate
0cc17a0791
Make a correct service level for VimProjectService 2024-02-20 12:12:50 +02:00
aleksei.plate@jetbrains.com
05a21e6091 TeamCity change in 'Ideavim' project: 'Qodana checks' build configuration settings were updated 2024-02-20 08:58:47 +00:00
Alex Plate
fc06bc7c6f
Update the qodana baseline 2024-02-20 10:58:08 +02:00
Alex Plate
1bd005adc1
Fix the name of the compatibility function 2024-02-20 10:39:34 +02:00
Alex Plate
4f208d1577
Add new plugin to the list 2024-02-20 10:37:10 +02:00
aleksei.plate@jetbrains.com
eb6e0557a7 TeamCity change in 'Ideavim' project: runners of 'Qodana checks' build configuration were updated 2024-02-20 08:23:51 +00:00
Alex Plate
cf09d66be6
Prototype for vimscript inspection 2024-02-20 06:13:26 +02:00
Alex Plate
76cd127a8a
Bring back function to fix compatibility 2024-02-20 05:25:22 +02:00
Alex Plate
f6dd2a9968
Do not call for setCaretVisible in tests as this causes project leak 2024-02-20 05:20:05 +02:00
aleksei.plate@jetbrains.com
ae05a33e14 TeamCity change in 'Ideavim' project: general settings of 'Tests for IntelliJ Latest EAP' build configuration were updated 2024-02-19 07:13:35 +00:00
aleksei.plate@jetbrains.com
b38fad323b TeamCity change in 'Ideavim' project: general settings of 'Tests for IntelliJ Latest EAP' build configuration were updated 2024-02-19 07:12:50 +00:00
Alex Plate
c6027fcf0f
Add new plugin for compatibility checks 2024-02-19 09:01:10 +02:00
298 changed files with 67716 additions and 38575 deletions

1
.gitattributes vendored Normal file
View File

@ -0,0 +1 @@
* text=auto eol=lf

View File

@ -11,7 +11,7 @@ on:
jobs:
build:
if: github.event.pull_request.merged == true && github.repository == 'JetBrains/ideavim'
if: false
runs-on: ubuntu-latest
steps:

View File

@ -9,9 +9,6 @@ jobs:
runs-on: macos-latest
steps:
- uses: actions/checkout@v4
- name: Apply Patch
run: |
git apply tests/ui-ij-tests/src/test/kotlin/ui/octopus.patch
- name: Setup Java
uses: actions/setup-java@v4
with:
@ -30,7 +27,7 @@ jobs:
- name: Run Idea
run: |
mkdir -p build/reports
gradle runIdeForUiTests > build/reports/idea.log &
gradle runIdeForUiTests -Doctopus.handler=false > build/reports/idea.log &
- name: Wait for Idea started
uses: jtalk/url-health-check-action@v3
with:
@ -52,6 +49,7 @@ jobs:
name: ui-test-fails-report-mac
path: |
build/reports
tests/ui-ij-tests/build/reports
sandbox-idea-log
# build-for-ui-test-linux:
# runs-on: ubuntu-latest

View File

@ -52,4 +52,5 @@ jobs:
name: ui-test-fails-report-mac
path: |
build/reports
tests/ui-py-tests/build/reports
sandbox-idea-log

View File

@ -49,6 +49,7 @@ jobs:
name: ui-test-fails-report-mac
path: |
build/reports
tests/ui-ij-tests/build/reports
sandbox-idea-log
# build-for-ui-test-linux:
# runs-on: ubuntu-latest

View File

@ -7,15 +7,12 @@ on:
workflow_dispatch:
schedule:
- cron: '0 10 * * *'
# Workflow run on push is disabled to avoid conflicts when merging PR
# push:
# branches: [ master ]
jobs:
build:
runs-on: ubuntu-latest
if: github.repository == 'JetBrains/ideavim'
if: false
steps:
- uses: actions/checkout@v3

View File

@ -6,6 +6,7 @@
<option name="CONTINUATION_INDENT_SIZE" value="4" />
</value>
</option>
<option name="LINE_SEPARATOR" value="&#10;" />
<JavaCodeStyleSettings>
<option name="FIELD_NAME_PREFIX" value="my" />
<option name="STATIC_FIELD_NAME_PREFIX" value="our" />

View File

@ -46,8 +46,8 @@ object Qodana : IdeaVimBuildType({
version = Qodana.JVMVersion.LATEST
}
reportAsTests = true
additionalDockerArguments = "-e QODANA_TOKEN=eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJvcmdhbml6YXRpb24iOiIzUFZrQSIsInByb2plY3QiOiIzN1FlQSIsInRva2VuIjoiM0t2bXoifQ.uohp81tM7iAfvvB6k8faarfpV-OjusAaEbWQ8iNrOgs"
additionalQodanaArguments = "--baseline qodana.sarif.json"
cloudToken = "credentialsJSON:6b79412e-9198-4862-9223-c5019488f903"
}
}
@ -63,7 +63,6 @@ object Qodana : IdeaVimBuildType({
timezone = "SERVER"
}
param("dayOfWeek", "Sunday")
enabled = false
}
}

View File

@ -97,14 +97,14 @@ sealed class ReleasePlugin(private val releaseType: String) : IdeaVimBuildType({
name = "Set TeamCity build number"
tasks = "scripts:setTeamCityBuildNumber"
}
gradle {
name = "Update change log"
tasks = "scripts:changelogUpdateUnreleased"
}
gradle {
name = "Commit preparation changes"
tasks = "scripts:commitChanges"
}
// gradle {
// name = "Update change log"
// tasks = "scripts:changelogUpdateUnreleased"
// }
// gradle {
// name = "Commit preparation changes"
// tasks = "scripts:commitChanges"
// }
gradle {
name = "Add release tag"
tasks = "scripts:addReleaseTag"
@ -117,33 +117,24 @@ sealed class ReleasePlugin(private val releaseType: String) : IdeaVimBuildType({
name = "Publish release"
tasks = "publishPlugin"
}
script {
name = "Checkout master branch"
scriptContent = """
echo Checkout master
git checkout master
""".trimIndent()
}
gradle {
name = "Update change log in master"
tasks = "scripts:changelogUpdateUnreleased"
}
gradle {
name = "Commit preparation changes in master"
tasks = "scripts:commitChanges"
}
// script {
// name = "Checkout master branch"
// scriptContent = """
// echo Checkout master
// git checkout master
// """.trimIndent()
// }
// gradle {
// name = "Update change log in master"
// tasks = "scripts:changelogUpdateUnreleased"
// }
// gradle {
// name = "Commit preparation changes in master"
// tasks = "scripts:commitChanges"
// }
script {
name = "Push changes to the repo"
scriptContent = """
branch=$(git branch --show-current)
echo Current branch is ${'$'}branch
if [ "master" != "${'$'}branch" ];
then
git checkout master
fi
git push origin
git checkout release
echo checkout release branch
git branch --set-upstream-to=origin/release release

View File

@ -1,11 +1,9 @@
package patches.buildTypes
import jetbrains.buildServer.configs.kotlin.v2019_2.RelativeId
import jetbrains.buildServer.configs.kotlin.v2019_2.*
import jetbrains.buildServer.configs.kotlin.v2019_2.buildSteps.GradleBuildStep
import jetbrains.buildServer.configs.kotlin.v2019_2.buildSteps.gradle
import jetbrains.buildServer.configs.kotlin.v2019_2.ui.changeBuildType
import jetbrains.buildServer.configs.kotlin.v2019_2.ui.expectSteps
import jetbrains.buildServer.configs.kotlin.v2019_2.ui.update
import jetbrains.buildServer.configs.kotlin.v2019_2.ui.*
/*
This patch script was generated by TeamCity on settings change in UI.
@ -13,6 +11,18 @@ To apply the patch, change the buildType with id = 'IdeaVimTests_Latest_EAP'
accordingly, and delete the patch script.
*/
changeBuildType(RelativeId("IdeaVimTests_Latest_EAP")) {
check(artifactRules == """
+:build/reports => build/reports
+:/mnt/agent/temp/buildTmp/ => /mnt/agent/temp/buildTmp/
""".trimIndent()) {
"Unexpected option value: artifactRules = $artifactRules"
}
artifactRules = """
+:build/reports => build/reports
+:/mnt/agent/temp/buildTmp/ => /mnt/agent/temp/buildTmp/
+:tests/java-tests/build/reports => tests/java-tests/build/reports
""".trimIndent()
expectSteps {
gradle {
tasks = "clean test"

View File

@ -495,6 +495,14 @@ Contributors:
[![icon][github]](https://github.com/emanuelgestosa)
&nbsp;
Emanuel Gestosa
* [![icon][mail]](mailto:81118900+lippfi@users.noreply.github.com)
[![icon][github]](https://github.com/lippfi)
&nbsp;
lippfi,
* [![icon][mail]](mailto:fillipser143@gmail.com)
[![icon][github]](https://github.com/Parker7123)
&nbsp;
FilipParker
Previous contributors:

View File

@ -23,15 +23,19 @@ 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
## End of changelog file maintenance
Since version 2.9.0, the changelog can be found on YouTrack
To Be Released: https://youtrack.jetbrains.com/issues/VIM?q=%23%7BReady%20To%20Release%7D%20
Latest Fixes: https://youtrack.jetbrains.com/issues/VIM?q=State:%20Fixed%20sort%20by:%20updated%20
## 2.9.0, 2024-02-20
### Fixes:
* [VIM-3055](https://youtrack.jetbrains.com/issue/VIM-3055) Fix the issue with double deleting after dot
* [VIM-3291](https://youtrack.jetbrains.com/issue/VIM-3291) Remove sync of editor selection between different opened editors
* [VIM-3234](https://youtrack.jetbrains.com/issue/VIM-3234) The space character won't mix in the tab chars after >> and << commands
### Merged PRs:
* [725](https://github.com/JetBrains/ideavim/pull/725) by [Emanuel Gestosa](https://github.com/emanuelgestosa): Regex
* [805](https://github.com/JetBrains/ideavim/pull/805) by [chylex](https://github.com/chylex): VIM-3238 Fix recording a macro that replays another macro
## 2.8.0, 2024-01-30

View File

@ -21,7 +21,7 @@ repositories {
}
dependencies {
compileOnly("com.google.devtools.ksp:symbol-processing-api:1.9.22-1.0.17")
compileOnly("com.google.devtools.ksp:symbol-processing-api:1.9.23-1.0.19")
implementation("org.jetbrains.kotlinx:kotlinx-serialization-json-jvm:$kotlinxSerializationVersion") {
// kotlin stdlib is provided by IJ, so there is no need to include it into the distribution
exclude("org.jetbrains.kotlin", "kotlin-stdlib")

View File

@ -32,7 +32,6 @@ import org.eclipse.jgit.api.Git
import org.eclipse.jgit.lib.RepositoryBuilder
import org.intellij.markdown.ast.getTextInNode
import org.jetbrains.changelog.Changelog
import org.jetbrains.changelog.exceptions.MissingVersionException
import org.kohsuke.github.GHUser
import java.net.HttpURLConnection
import java.net.URL
@ -49,14 +48,14 @@ buildscript {
classpath("org.eclipse.jgit:org.eclipse.jgit:6.6.0.202305301015-r")
// This is needed for jgit to connect to ssh
classpath("org.eclipse.jgit:org.eclipse.jgit.ssh.apache:6.8.0.202311291450-r")
classpath("org.eclipse.jgit:org.eclipse.jgit.ssh.apache:6.9.0.202403050737-r")
classpath("org.kohsuke:github-api:1.305")
classpath("io.ktor:ktor-client-core:2.3.7")
classpath("io.ktor:ktor-client-cio:2.3.7")
classpath("io.ktor:ktor-client-auth:2.3.7")
classpath("io.ktor:ktor-client-content-negotiation:2.3.7")
classpath("io.ktor:ktor-serialization-kotlinx-json:2.3.7")
classpath("io.ktor:ktor-client-core:2.3.9")
classpath("io.ktor:ktor-client-cio:2.3.9")
classpath("io.ktor:ktor-client-auth:2.3.9")
classpath("io.ktor:ktor-client-content-negotiation:2.3.9")
classpath("io.ktor:ktor-serialization-kotlinx-json:2.3.9")
// This comes from the changelog plugin
// classpath("org.jetbrains:markdown:0.3.1")
@ -70,7 +69,7 @@ plugins {
application
id("java-test-fixtures")
id("org.jetbrains.intellij") version "1.17.0"
id("org.jetbrains.intellij") version "1.17.2"
id("org.jetbrains.changelog") version "2.2.0"
id("org.jetbrains.kotlinx.kover") version "0.6.1"
@ -144,12 +143,12 @@ dependencies {
// https://mvnrepository.com/artifact/org.mockito.kotlin/mockito-kotlin
testImplementation("org.mockito.kotlin:mockito-kotlin:5.2.1")
testImplementation("org.junit.jupiter:junit-jupiter-api:5.10.1")
testImplementation("org.junit.jupiter:junit-jupiter-engine:5.10.1")
testImplementation("org.junit.jupiter:junit-jupiter-params:5.10.1")
testFixturesImplementation("org.junit.jupiter:junit-jupiter-api:5.10.1")
testFixturesImplementation("org.junit.jupiter:junit-jupiter-engine:5.10.1")
testFixturesImplementation("org.junit.jupiter:junit-jupiter-params:5.10.1")
testImplementation("org.junit.jupiter:junit-jupiter-api:5.10.2")
testImplementation("org.junit.jupiter:junit-jupiter-engine:5.10.2")
testImplementation("org.junit.jupiter:junit-jupiter-params:5.10.2")
testFixturesImplementation("org.junit.jupiter:junit-jupiter-api:5.10.2")
testFixturesImplementation("org.junit.jupiter:junit-jupiter-engine:5.10.2")
testFixturesImplementation("org.junit.jupiter:junit-jupiter-params:5.10.2")
}
configurations {
@ -207,6 +206,11 @@ tasks {
systemProperty("jb.privacy.policy.text", "<!--999.999-->")
systemProperty("jb.consents.confirmation.enabled", "false")
systemProperty("ide.show.tips.on.startup.default.value", "false")
systemProperty("octopus.handler", System.getProperty("octopus.handler") ?: true)
}
runIde {
systemProperty("octopus.handler", System.getProperty("octopus.handler") ?: true)
}
}
@ -260,7 +264,9 @@ tasks {
runPluginVerifier {
downloadDir.set("${project.buildDir}/pluginVerifier/ides")
teamCityOutputFormat.set(true)
// ideVersions.set(listOf("IC-2021.3.4"))
// The latest version of the plugin verifier is broken, so temporally use the stable version
verifierVersion = "1.307"
}
generateGrammarSource {
@ -305,26 +311,12 @@ tasks {
from(createOpenApiSourceJar) { into("lib/src") }
}
val pluginVersion = version
// Don't forget to update plugin.xml
patchPluginXml {
sinceBuild.set("233.11799.30")
// Don't forget to update plugin.xml
sinceBuild.set("233.11799.67")
// Get the latest available change notes from the changelog file
changeNotes.set(
provider {
with(changelog) {
val log = try {
getUnreleased()
} catch (e: MissingVersionException) {
getOrNull(pluginVersion.toString()) ?: getLatest()
}
renderItem(
log,
org.jetbrains.changelog.Changelog.OutputType.HTML,
)
}
},
"""<a href="https://youtrack.jetbrains.com/issues/VIM?q=State:%20Fixed%20Fix%20versions:%20${version.get()}">Changelog</a>"""
)
}
}
@ -431,12 +423,14 @@ val prId: String by project
tasks.register("updateMergedPr") {
doLast {
if (project.hasProperty("prId")) {
println("Got pr id: $prId")
updateMergedPr(prId.toInt())
} else {
error("Cannot get prId")
}
val x = changelog.getUnreleased()
println("x")
// if (project.hasProperty("prId")) {
// println("Got pr id: $prId")
// updateMergedPr(prId.toInt())
// } else {
// error("Cannot get prId")
// }
}
}

View File

@ -8,16 +8,18 @@
# suppress inspection "UnusedProperty" for whole file
ideaVersion=2023.3.2
#ideaVersion=LATEST-EAP-SNAPSHOT
ideaVersion=2023.3.3
# Values for type: https://plugins.jetbrains.com/docs/intellij/tools-gradle-intellij-plugin.html#intellij-extension-type
ideaType=IC
downloadIdeaSources=true
instrumentPluginCode=true
version=SNAPSHOT
version=chylex-29
javaVersion=17
remoteRobotVersion=0.11.22
antlrVersion=4.10.1
kotlin.incremental.useClasspathSnapshot=false
# Please don't forget to update kotlin version in buildscript section
# Also update kotlinxSerializationVersion version
@ -27,7 +29,7 @@ publishChannels=eap
# Kotlinx serialization also uses some version of kotlin stdlib under the hood. However,
# we exclude this version from the dependency and use our own version of kotlin that is specified above
kotlinxSerializationVersion=1.5.1
kotlinxSerializationVersion=1.6.2
slackUrl=
youtrackToken=
@ -40,3 +42,4 @@ kotlin.stdlib.default.dependency=false
# Disable incremental annotation processing
ksp.incremental=false

File diff suppressed because one or more lines are too long

View File

@ -21,6 +21,9 @@ exclude:
- src/test/java/org/jetbrains/plugins/ideavim/propertybased/samples/SimpleText.kt
- src/main/java/com/maddyhome/idea/vim/vimscript/parser/generated
- src/main/java/com/maddyhome/idea/vim/package-info.java
- vim-engine/src/main/java/com/maddyhome/idea/vim/regexp/parser/generated
- src/main/java/com/maddyhome/idea/vim/group/SearchGroup.java
- tests/ui-fixtures
dependencyIgnores:
- name: "acejump"
- name: "icu4j"

View File

@ -20,17 +20,17 @@ repositories {
}
dependencies {
compileOnly("org.jetbrains.kotlin:kotlin-stdlib:1.9.22")
compileOnly("org.jetbrains.kotlin:kotlin-stdlib:1.9.23")
implementation("io.ktor:ktor-client-core:2.3.7")
implementation("io.ktor:ktor-client-cio:2.3.7")
implementation("io.ktor:ktor-client-content-negotiation:2.3.7")
implementation("io.ktor:ktor-serialization-kotlinx-json:2.3.7")
implementation("io.ktor:ktor-client-auth:2.3.7")
implementation("io.ktor:ktor-client-core:2.3.9")
implementation("io.ktor:ktor-client-cio:2.3.9")
implementation("io.ktor:ktor-client-content-negotiation:2.3.9")
implementation("io.ktor:ktor-serialization-kotlinx-json:2.3.9")
implementation("io.ktor:ktor-client-auth:2.3.9")
implementation("org.eclipse.jgit:org.eclipse.jgit:6.6.0.202305301015-r")
// This is needed for jgit to connect to ssh
implementation("org.eclipse.jgit:org.eclipse.jgit.ssh.apache:6.8.0.202311291450-r")
implementation("org.eclipse.jgit:org.eclipse.jgit.ssh.apache:6.9.0.202403050737-r")
implementation("com.vdurmont:semver4j:3.1.0")
}

View File

@ -22,7 +22,7 @@ import kotlinx.serialization.json.jsonPrimitive
*/
@Suppress("SpellCheckingInspection")
val knownPlugins = listOf(
val knownPlugins = setOf(
"IdeaVimExtension",
"github.zgqq.intellij-enhance",
"org.jetbrains.IdeaVim-EasyMotion",
@ -31,7 +31,12 @@ val knownPlugins = listOf(
"com.github.copilot",
"com.github.dankinsoid.multicursor",
"com.joshestein.ideavim-quickscope",
"ca.alexgirard.HarpoonIJ",
"me.kyren223.harpoonforjb", // https://plugins.jetbrains.com/plugin/23771-harpoonforjb
"com.github.erotourtes.harpoon", // https://plugins.jetbrains.com/plugin/21796-harpooner
"me.kyren223.trident", // https://plugins.jetbrains.com/plugin/23818-trident
"com.protoseo.input-source-auto-converter",
// "cc.implicated.intellij.plugins.bunny", // I don't want to include this plugin in the list of IdeaVim plugins as I don't understand what this is for
@ -42,7 +47,7 @@ suspend fun main() {
parameter("dependency", "IdeaVIM")
parameter("includeOptional", true)
}
val output = response.body<List<String>>()
val output = response.body<List<String>>().toSet()
println(output)
if (knownPlugins != output) {
val newPlugins = (output - knownPlugins).map { it to (getPluginLinkByXmlId(it) ?: "Can't find plugin link") }

View File

@ -8,6 +8,12 @@
package scripts.release
import org.eclipse.jgit.lib.ObjectId
import org.eclipse.jgit.revwalk.RevCommit
import org.eclipse.jgit.revwalk.RevWalk
import org.eclipse.jgit.revwalk.filter.RevFilter
fun main(args: Array<String>) {
println("HI!")
val projectDir = args[0]
@ -19,10 +25,12 @@ fun main(args: Array<String>) {
check(branch == "master") {
"We should be on master branch"
}
val mergeBaseCommit = getMergeBaseWithMaster(projectDir, objectId)
println("Base commit $mergeBaseCommit")
withGit(projectDir) { git ->
val log = git.log().setMaxCount(500).call().toList()
println("First commit hash in log: " + log.first().name + " log size: ${log.size}")
val logDiff = log.takeWhile { it.id.name != objectId.name }
val logDiff = log.takeWhile { it.id.name != mergeBaseCommit }
val numCommits = logDiff.size
println("Log diff size is $numCommits")
check(numCommits < 450) {
@ -35,3 +43,18 @@ fun main(args: Array<String>) {
println("##teamcity[setParameter name='env.ORG_GRADLE_PROJECT_version' value='$nextVersion']")
}
}
private fun getMergeBaseWithMaster(projectDir: String, tag: ObjectId): String {
withRepo(projectDir) { repo ->
val master = repo.resolve("master")
RevWalk(repo).use { walk ->
val tagRevCommit = walk.parseCommit(tag)
val masterRevCommit = walk.parseCommit(master)
walk.setRevFilter(RevFilter.MERGE_BASE)
walk.markStart(tagRevCommit)
walk.markStart(masterRevCommit)
val mergeBase: RevCommit = walk.next()
return mergeBase.name
}
}
}

View File

@ -11,7 +11,6 @@ package com.maddyhome.idea.vim;
import com.intellij.openapi.Disposable;
import com.intellij.openapi.actionSystem.AnAction;
import com.intellij.openapi.actionSystem.ShortcutSet;
import com.intellij.openapi.editor.Document;
import com.intellij.openapi.editor.Editor;
import com.intellij.openapi.editor.EditorFactory;
import com.intellij.openapi.editor.actionSystem.TypedAction;
@ -80,14 +79,6 @@ public class EventFacade {
action.unregisterCustomShortcutSet(component);
}
public void addDocumentListener(@NotNull Document document, @NotNull DocumentListener listener) {
document.addDocumentListener(listener);
}
public void removeDocumentListener(@NotNull Document document, @NotNull DocumentListener listener) {
document.removeDocumentListener(listener);
}
public void addEditorFactoryListener(@NotNull EditorFactoryListener listener, @NotNull Disposable parentDisposable) {
EditorFactory.getInstance().addEditorFactoryListener(listener, parentDisposable);
}
@ -98,20 +89,12 @@ public class EventFacade {
editor.getCaretModel().addCaretListener(listener, disposable);
}
public void removeCaretListener(@NotNull Editor editor, @NotNull CaretListener listener) {
editor.getCaretModel().removeCaretListener(listener);
}
public void addEditorMouseListener(@NotNull Editor editor,
@NotNull EditorMouseListener listener,
@NotNull Disposable disposable) {
editor.addEditorMouseListener(listener, disposable);
}
public void removeEditorMouseListener(@NotNull Editor editor, @NotNull EditorMouseListener listener) {
editor.removeEditorMouseListener(listener);
}
public void addComponentMouseListener(@NotNull Component component,
@NotNull MouseListener mouseListener,
@NotNull Disposable disposable) {
@ -119,30 +102,18 @@ public class EventFacade {
Disposer.register(disposable, () -> component.removeMouseListener(mouseListener));
}
public void removeComponentMouseListener(@NotNull Component component, @NotNull MouseListener mouseListener) {
component.removeMouseListener(mouseListener);
}
public void addEditorMouseMotionListener(@NotNull Editor editor,
@NotNull EditorMouseMotionListener listener,
@NotNull Disposable disposable) {
editor.addEditorMouseMotionListener(listener, disposable);
}
public void removeEditorMouseMotionListener(@NotNull Editor editor, @NotNull EditorMouseMotionListener listener) {
editor.removeEditorMouseMotionListener(listener);
}
public void addEditorSelectionListener(@NotNull Editor editor,
@NotNull SelectionListener listener,
@NotNull Disposable disposable) {
editor.getSelectionModel().addSelectionListener(listener, disposable);
}
public void removeEditorSelectionListener(@NotNull Editor editor, @NotNull SelectionListener listener) {
editor.getSelectionModel().removeSelectionListener(listener);
}
private @NotNull TypedAction getTypedAction() {
return TypedAction.getInstance();
}

View File

@ -14,7 +14,7 @@ import com.intellij.openapi.project.ProjectManagerListener
import com.intellij.openapi.startup.ProjectActivity
import com.maddyhome.idea.vim.api.injector
import com.maddyhome.idea.vim.helper.EditorHelper
import com.maddyhome.idea.vim.helper.localEditors
import com.maddyhome.idea.vim.newapi.IjVimEditor
import com.maddyhome.idea.vim.newapi.globalIjOptions
/**
@ -36,8 +36,10 @@ internal class PluginStartup : ProjectActivity/*, LightEditCompatible*/ {
// This is a temporal workaround for VIM-2487
internal class PyNotebooksCloseWorkaround : ProjectManagerListener {
override fun projectClosingBeforeSave(project: Project) {
// TODO: Confirm context in CWM scenario
if (injector.globalIjOptions().closenotebooks) {
localEditors().forEach { editor ->
injector.editorGroup.getEditors().forEach { vimEditor ->
val editor = (vimEditor as IjVimEditor).editor
val virtualFile = EditorHelper.getVirtualFile(editor)
if (virtualFile?.extension == "ipynb") {
val fileEditorManager = FileEditorManagerEx.getInstanceEx(project)

View File

@ -21,7 +21,7 @@ public object RegisterActions {
@JvmStatic
public fun registerActions() {
registerVimCommandActions()
registerEmptyShortcuts() // todo most likely it is not needed
registerShortcutsWithoutActions()
}
public fun findAction(id: String): EditorActionHandlerBase? {
@ -46,12 +46,11 @@ public object RegisterActions {
IntellijCommandProvider.getCommands().forEach { parser.registerCommandAction(it) }
}
private fun registerEmptyShortcuts() {
private fun registerShortcutsWithoutActions() {
val parser = VimPlugin.getKey()
// The {char1} <BS> {char2} shortcut is handled directly by KeyHandler#handleKey, so doesn't have an action. But we
// still need to register the shortcut, to make sure the editor doesn't swallow it.
parser
.registerShortcutWithoutAction(KeyStroke.getKeyStroke(KeyEvent.VK_BACK_SPACE, 0), MappingOwner.IdeaVim.System)
parser.registerShortcutWithoutAction(KeyStroke.getKeyStroke(KeyEvent.VK_BACK_SPACE, 0), MappingOwner.IdeaVim.System)
}
}

View File

@ -211,22 +211,22 @@ public class VimPlugin implements PersistentStateComponent<Element>, Disposable
public static void setEnabled(final boolean enabled) {
if (isEnabled() == enabled) return;
if (!enabled) {
getInstance().turnOffPlugin(true);
}
getInstance().enabled = enabled;
if (enabled) {
getInstance().turnOnPlugin();
}
if (enabled) {
VimInjectorKt.getInjector().getListenersNotifier().notifyPluginTurnedOn();
} else {
VimInjectorKt.getInjector().getListenersNotifier().notifyPluginTurnedOff();
}
if (!enabled) {
getInstance().turnOffPlugin(true);
}
if (enabled) {
getInstance().turnOnPlugin();
}
StatusBarIconFactory.Util.INSTANCE.updateIcon();
}
@ -353,6 +353,7 @@ public class VimPlugin implements PersistentStateComponent<Element>, Disposable
if (onOffDisposable != null) {
Disposer.dispose(onOffDisposable);
onOffDisposable = null;
}
}

View File

@ -14,7 +14,7 @@ import com.intellij.openapi.components.service
import com.intellij.openapi.project.Project
import com.maddyhome.idea.vim.group.EditorHolderService
@Service
@Service(Service.Level.PROJECT)
internal class VimProjectService(val project: Project) : Disposable {
override fun dispose() {
// Not sure if this is a best solution

View File

@ -77,7 +77,7 @@ public class VimTypedActionHandler(origHandler: TypedActionHandler) : TypedActio
val modifiers = if (charTyped == ' ' && VimKeyListener.isSpaceShift) KeyEvent.SHIFT_DOWN_MASK else 0
val keyStroke = KeyStroke.getKeyStroke(charTyped, modifiers)
val startTime = if (traceTime) System.currentTimeMillis() else null
handler.handleKey(editor.vim, keyStroke, injector.executionContextManager.onEditor(editor.vim, context.vim))
handler.handleKey(editor.vim, keyStroke, injector.executionContextManager.onEditor(editor.vim, context.vim), handler.keyHandlerState)
if (startTime != null) {
val duration = System.currentTimeMillis() - startTime
LOG.info("VimTypedAction '$charTyped': $duration ms")

View File

@ -44,7 +44,6 @@ import com.maddyhome.idea.vim.listener.AceJumpService
import com.maddyhome.idea.vim.listener.AppCodeTemplates.appCodeTemplateCaptured
import com.maddyhome.idea.vim.newapi.globalIjOptions
import com.maddyhome.idea.vim.newapi.vim
import com.maddyhome.idea.vim.state.mode.mode
import com.maddyhome.idea.vim.vimscript.model.datatypes.VimString
import java.awt.event.InputEvent
import java.awt.event.KeyEvent
@ -79,10 +78,12 @@ public class VimShortcutKeyAction : AnAction(), DumbAware/*, LightEditCompatible
// Should we use HelperKt.getTopLevelEditor(editor) here, as we did in former EditorKeyHandler?
try {
val start = if (traceTime) System.currentTimeMillis() else null
KeyHandler.getInstance().handleKey(
val keyHandler = KeyHandler.getInstance()
keyHandler.handleKey(
editor.vim,
keyStroke,
injector.executionContextManager.onEditor(editor.vim, e.dataContext.vim),
keyHandler.keyHandlerState,
)
if (start != null) {
val duration = System.currentTimeMillis() - start

View File

@ -14,6 +14,7 @@ import com.maddyhome.idea.vim.VimPlugin
import com.maddyhome.idea.vim.api.ExecutionContext
import com.maddyhome.idea.vim.api.VimCaret
import com.maddyhome.idea.vim.api.VimEditor
import com.maddyhome.idea.vim.api.globalOptions
import com.maddyhome.idea.vim.api.injector
import com.maddyhome.idea.vim.api.setChangeMarks
import com.maddyhome.idea.vim.command.Argument
@ -21,6 +22,7 @@ import com.maddyhome.idea.vim.command.Command
import com.maddyhome.idea.vim.command.OperatorArguments
import com.maddyhome.idea.vim.common.TextRange
import com.maddyhome.idea.vim.common.argumentCaptured
import com.maddyhome.idea.vim.ex.ExException
import com.maddyhome.idea.vim.group.MotionGroup
import com.maddyhome.idea.vim.group.visual.VimSelection
import com.maddyhome.idea.vim.handler.VimActionHandler
@ -29,21 +31,67 @@ import com.maddyhome.idea.vim.helper.MessageHelper
import com.maddyhome.idea.vim.helper.vimStateMachine
import com.maddyhome.idea.vim.newapi.ij
import com.maddyhome.idea.vim.state.mode.SelectionType
import com.maddyhome.idea.vim.vimscript.model.CommandLineVimLContext
import com.maddyhome.idea.vim.vimscript.model.datatypes.VimFuncref
import com.maddyhome.idea.vim.vimscript.model.expressions.FunctionCallExpression
import com.maddyhome.idea.vim.vimscript.model.expressions.SimpleExpression
// todo make it multicaret
private fun doOperatorAction(editor: VimEditor, context: ExecutionContext, textRange: TextRange, selectionType: SelectionType): Boolean {
val operatorFunction = injector.keyGroup.operatorFunction
if (operatorFunction == null) {
val func = injector.globalOptions().operatorfunc
if (func.isEmpty()) {
VimPlugin.showMessage(MessageHelper.message("E774"))
return false
}
val scriptContext = CommandLineVimLContext
// The option value is either a function name, which should have a handler, or it might be a lambda expression, or a
// `function` or `funcref` call expression, all of which will return a funcref (with a handler)
var handler = injector.functionService.getFunctionHandlerOrNull(null, func, scriptContext)
if (handler == null) {
val expression = injector.vimscriptParser.parseExpression(func)
if (expression != null) {
try {
val value = expression.evaluate(editor, context, scriptContext)
if (value is VimFuncref) {
handler = value.handler
}
} catch (ex: ExException) {
// Get the argument for function('...') or funcref('...') for the error message
val functionName = if (expression is FunctionCallExpression && expression.arguments.size > 0) {
expression.arguments[0].evaluate(editor, context, scriptContext).toString()
}
else {
func
}
VimPlugin.showMessage("E117: Unknown function: $functionName")
return false
}
}
}
if (handler == null) {
VimPlugin.showMessage("E117: Unknown function: $func")
return false
}
val arg = when (selectionType) {
SelectionType.LINE_WISE -> "line"
SelectionType.CHARACTER_WISE -> "char"
SelectionType.BLOCK_WISE -> "block"
}
val saveRepeatHandler = VimRepeater.repeatHandler
injector.markService.setChangeMarks(editor.primaryCaret(), textRange)
KeyHandler.getInstance().reset(editor)
val result = operatorFunction.apply(editor, context, selectionType)
val arguments = listOf(SimpleExpression(arg))
handler.executeFunction(arguments, editor, context, scriptContext)
VimRepeater.repeatHandler = saveRepeatHandler
return result
return true
}
@CommandOrMotion(keys = ["g@"], modes = [Mode.NORMAL])

View File

@ -7,9 +7,9 @@
*/
package com.maddyhome.idea.vim.action.change
import com.intellij.openapi.command.CommandProcessor
import com.intellij.vim.annotations.CommandOrMotion
import com.intellij.vim.annotations.Mode
import com.intellij.openapi.command.CommandProcessor
import com.maddyhome.idea.vim.VimPlugin
import com.maddyhome.idea.vim.api.ExecutionContext
import com.maddyhome.idea.vim.api.VimEditor

View File

@ -21,19 +21,19 @@ import com.maddyhome.idea.vim.state.mode.SelectionType
public class CommandState(private val machine: VimStateMachine) {
public val isOperatorPending: Boolean
get() = machine.isOperatorPending
get() = machine.isOperatorPending(machine.mode)
public val mode: CommandState.Mode
public val mode: Mode
get() {
val myMode = machine.mode
return when (myMode) {
is com.maddyhome.idea.vim.state.mode.Mode.CMD_LINE -> CommandState.Mode.CMD_LINE
com.maddyhome.idea.vim.state.mode.Mode.INSERT -> CommandState.Mode.INSERT
is com.maddyhome.idea.vim.state.mode.Mode.NORMAL -> CommandState.Mode.COMMAND
is com.maddyhome.idea.vim.state.mode.Mode.OP_PENDING -> CommandState.Mode.OP_PENDING
com.maddyhome.idea.vim.state.mode.Mode.REPLACE -> CommandState.Mode.REPLACE
is com.maddyhome.idea.vim.state.mode.Mode.SELECT -> CommandState.Mode.SELECT
is com.maddyhome.idea.vim.state.mode.Mode.VISUAL -> CommandState.Mode.VISUAL
is com.maddyhome.idea.vim.state.mode.Mode.CMD_LINE -> Mode.CMD_LINE
com.maddyhome.idea.vim.state.mode.Mode.INSERT -> Mode.INSERT
is com.maddyhome.idea.vim.state.mode.Mode.NORMAL -> Mode.COMMAND
is com.maddyhome.idea.vim.state.mode.Mode.OP_PENDING -> Mode.OP_PENDING
com.maddyhome.idea.vim.state.mode.Mode.REPLACE -> Mode.REPLACE
is com.maddyhome.idea.vim.state.mode.Mode.SELECT -> Mode.SELECT
is com.maddyhome.idea.vim.state.mode.Mode.VISUAL -> Mode.VISUAL
}
}

View File

@ -14,21 +14,34 @@ import com.intellij.openapi.editor.Editor
import com.maddyhome.idea.vim.KeyHandler
import com.maddyhome.idea.vim.VimPlugin
import com.maddyhome.idea.vim.action.change.Extension
import com.maddyhome.idea.vim.api.ExecutionContext
import com.maddyhome.idea.vim.api.ImmutableVimCaret
import com.maddyhome.idea.vim.api.VimCaret
import com.maddyhome.idea.vim.api.VimEditor
import com.maddyhome.idea.vim.api.injector
import com.maddyhome.idea.vim.command.MappingMode
import com.maddyhome.idea.vim.common.CommandAlias
import com.maddyhome.idea.vim.common.CommandAliasHandler
import com.maddyhome.idea.vim.common.TextRange
import com.maddyhome.idea.vim.helper.CommandLineHelper
import com.maddyhome.idea.vim.helper.TestInputModel
import com.maddyhome.idea.vim.helper.noneOfEnum
import com.maddyhome.idea.vim.helper.vimStateMachine
import com.maddyhome.idea.vim.key.MappingOwner
import com.maddyhome.idea.vim.key.OperatorFunction
import com.maddyhome.idea.vim.newapi.vim
import com.maddyhome.idea.vim.state.mode.SelectionType
import com.maddyhome.idea.vim.ui.ModalEntry
import com.maddyhome.idea.vim.vimscript.model.Executable
import com.maddyhome.idea.vim.vimscript.model.ExecutionResult
import com.maddyhome.idea.vim.vimscript.model.VimLContext
import com.maddyhome.idea.vim.vimscript.model.datatypes.VimDataType
import com.maddyhome.idea.vim.vimscript.model.expressions.Expression
import com.maddyhome.idea.vim.vimscript.model.expressions.Scope
import com.maddyhome.idea.vim.vimscript.model.statements.FunctionDeclaration
import com.maddyhome.idea.vim.vimscript.model.statements.FunctionFlag
import java.awt.event.KeyEvent
import java.util.*
import javax.swing.KeyStroke
/**
@ -120,12 +133,6 @@ public object VimExtensionFacade {
.setAlias(name, CommandAlias.Call(minimumNumberOfArguments, maximumNumberOfArguments, name, handler))
}
/** Sets the value of 'operatorfunc' to be used as the operator function in 'g@'. */
@JvmStatic
public fun setOperatorFunction(function: OperatorFunction) {
VimPlugin.getKey().operatorFunction = function
}
/**
* Runs normal mode commands similar to ':normal! {commands}'.
* Mappings doesn't work with this function
@ -136,7 +143,8 @@ public object VimExtensionFacade {
@JvmStatic
public fun executeNormalWithoutMapping(keys: List<KeyStroke>, editor: Editor) {
val context = injector.executionContextManager.onEditor(editor.vim)
keys.forEach { KeyHandler.getInstance().handleKey(editor.vim, it, context, false, false) }
val keyHandler = KeyHandler.getInstance()
keys.forEach { keyHandler.handleKey(editor.vim, it, context, false, false, keyHandler.keyHandlerState) }
}
/** Returns a single key stroke from the user input similar to 'getchar()'. */
@ -152,7 +160,7 @@ public object VimExtensionFacade {
LOG.trace("Unit test mode is active")
val mappingStack = KeyHandler.getInstance().keyStack
mappingStack.feedSomeStroke() ?: TestInputModel.getInstance(editor).nextKeyStroke()?.also {
if (editor.vim.vimStateMachine.isRecording) {
if (injector.registerGroup.isRecording) {
KeyHandler.getInstance().modalEntryKeys += it
}
}
@ -207,4 +215,65 @@ public object VimExtensionFacade {
public fun setRegister(register: Char, keys: List<KeyStroke?>?, type: SelectionType) {
VimPlugin.getRegister().setKeys(register, keys?.filterNotNull() ?: emptyList(), type)
}
@JvmStatic
public fun exportScriptFunction(
scope: Scope?,
name: String,
args: List<String>,
defaultArgs: List<Pair<String, Expression>>,
hasOptionalArguments: Boolean,
flags: EnumSet<FunctionFlag>,
function: ScriptFunction
) {
var functionDeclaration: FunctionDeclaration? = null
val body = listOf(object : Executable {
// This context is set to the function declaration during initialisation and then set to the function execution
// context during execution
override lateinit var vimContext: VimLContext
override var rangeInScript: TextRange = TextRange(0, 0)
override fun execute(editor: VimEditor, context: ExecutionContext): ExecutionResult {
return function.execute(editor, context, functionDeclaration!!.functionVariables)
}
})
functionDeclaration = FunctionDeclaration(
scope,
name,
args,
defaultArgs,
body,
replaceExisting = true,
flags,
hasOptionalArguments
)
functionDeclaration.rangeInScript = TextRange(0, 0)
body.forEach { it.vimContext = functionDeclaration }
injector.functionService.storeFunction(functionDeclaration)
}
}
public fun VimExtensionFacade.exportOperatorFunction(name: String, function: OperatorFunction) {
exportScriptFunction(null, name, listOf("type"), emptyList(), false, noneOfEnum()) {
editor, context, args ->
val type = args["type"]?.asString()
val selectionType = when (type) {
"line" -> SelectionType.LINE_WISE
"block" -> SelectionType.BLOCK_WISE
"char" -> SelectionType.CHARACTER_WISE
else -> return@exportScriptFunction ExecutionResult.Error
}
if (function.apply(editor, context, selectionType)) {
ExecutionResult.Success
}
else {
ExecutionResult.Error
}
}
}
public fun interface ScriptFunction {
public fun execute(editor: VimEditor, context: ExecutionContext, args: Map<String, VimDataType>): ExecutionResult
}

View File

@ -67,7 +67,7 @@ internal object VimExtensionRegistrar : VimExtensionRegistrator {
VimPlugin.getOptionGroup().addGlobalOptionChangeListener(option) {
if (injector.optionGroup.getOptionValue(option, OptionAccessScope.GLOBAL(null)).asBoolean()) {
initExtension(extensionBean, name)
PluginState.enabledExtensions.add(name)
PluginState.Util.enabledExtensions.add(name)
} else {
extensionBean.instance.dispose()
}

View File

@ -251,7 +251,7 @@ public class VimArgTextObjExtension implements VimExtension {
final ArgumentTextObjectHandler textObjectHandler = new ArgumentTextObjectHandler(isInner);
//noinspection DuplicatedCode
if (!vimStateMachine.isOperatorPending()) {
if (!vimStateMachine.isOperatorPending(editor.getMode())) {
editor.nativeCarets().forEach((VimCaret caret) -> {
final TextRange range = textObjectHandler.getRange(editor, caret, context, count, 0);
if (range != null) {

View File

@ -22,26 +22,26 @@ import com.maddyhome.idea.vim.api.ExecutionContext
import com.maddyhome.idea.vim.api.ImmutableVimCaret
import com.maddyhome.idea.vim.api.VimEditor
import com.maddyhome.idea.vim.api.getLineEndOffset
import com.maddyhome.idea.vim.api.globalOptions
import com.maddyhome.idea.vim.api.injector
import com.maddyhome.idea.vim.command.Argument
import com.maddyhome.idea.vim.command.Command
import com.maddyhome.idea.vim.command.CommandFlags
import com.maddyhome.idea.vim.command.MappingMode
import com.maddyhome.idea.vim.state.mode.Mode
import com.maddyhome.idea.vim.command.OperatorArguments
import com.maddyhome.idea.vim.state.mode.SelectionType
import com.maddyhome.idea.vim.command.TextObjectVisualType
import com.maddyhome.idea.vim.common.CommandAliasHandler
import com.maddyhome.idea.vim.common.TextRange
import com.maddyhome.idea.vim.ex.ranges.Ranges
import com.maddyhome.idea.vim.extension.ExtensionHandler
import com.maddyhome.idea.vim.extension.VimExtension
import com.maddyhome.idea.vim.extension.VimExtensionFacade
import com.maddyhome.idea.vim.extension.VimExtensionFacade.addCommand
import com.maddyhome.idea.vim.extension.VimExtensionFacade.executeNormalWithoutMapping
import com.maddyhome.idea.vim.extension.VimExtensionFacade.putExtensionHandlerMapping
import com.maddyhome.idea.vim.extension.VimExtensionFacade.putKeyMapping
import com.maddyhome.idea.vim.extension.VimExtensionFacade.putKeyMappingIfMissing
import com.maddyhome.idea.vim.extension.VimExtensionFacade.setOperatorFunction
import com.maddyhome.idea.vim.extension.exportOperatorFunction
import com.maddyhome.idea.vim.handler.TextObjectActionHandler
import com.maddyhome.idea.vim.helper.PsiHelper
import com.maddyhome.idea.vim.helper.vimStateMachine
@ -49,17 +49,19 @@ import com.maddyhome.idea.vim.key.OperatorFunction
import com.maddyhome.idea.vim.newapi.IjVimEditor
import com.maddyhome.idea.vim.newapi.ij
import com.maddyhome.idea.vim.newapi.vim
import com.maddyhome.idea.vim.state.mode.Mode
import com.maddyhome.idea.vim.state.mode.SelectionType
import java.util.*
internal class CommentaryExtension : VimExtension {
companion object {
object Util {
fun doCommentary(
editor: VimEditor,
context: ExecutionContext,
range: TextRange,
selectionType: SelectionType,
resetCaret: Boolean,
resetCaret: Boolean = true,
): Boolean {
val mode = editor.vimStateMachine.mode
if (mode !is Mode.VISUAL) {
@ -67,8 +69,7 @@ internal class CommentaryExtension : VimExtension {
}
return runWriteAction {
// Treat block- and character-wise selections as block comments. Be ready to fall back to if the first action
// isn't available
// Treat block- and character-wise selections as block comments. Fall back if the first action isn't available
val actions = if (selectionType === SelectionType.LINE_WISE) {
listOf(IdeActions.ACTION_COMMENT_LINE, IdeActions.ACTION_COMMENT_BLOCK)
} else {
@ -113,12 +114,17 @@ internal class CommentaryExtension : VimExtension {
// first non-whitespace character, then the caret is in the right place. If it's inserted at the first column,
// then the caret is now in a bit of a weird place. We can't detect this scenario, so we just have to accept
// the difference
// TODO: If we don't move the caret to the start offset, we should maintain the current logical position
if (resetCaret) {
editor.primaryCaret().moveToOffset(range.startOffset)
}
}
}
companion object {
private const val OPERATOR_FUNC = "CommentaryOperatorFunc"
}
override fun getName() = "commentary"
override fun init() {
@ -145,6 +151,16 @@ internal class CommentaryExtension : VimExtension {
putKeyMapping(MappingMode.N, injector.parser.parseKeys("<Plug>(CommentLine)"), owner, plugCommentaryLineKeys, true)
addCommand("Commentary", CommentaryCommandAliasHandler())
VimExtensionFacade.exportOperatorFunction(OPERATOR_FUNC, CommentaryOperatorFunction())
}
private class CommentaryOperatorFunction : OperatorFunction {
// todo make it multicaret
override fun apply(editor: VimEditor, context: ExecutionContext, selectionType: SelectionType?): Boolean {
val range = injector.markService.getChangeMarks(editor.primaryCaret()) ?: return false
return Util.doCommentary(editor, context, range, selectionType ?: SelectionType.CHARACTER_WISE, true)
}
}
/**
@ -153,19 +169,13 @@ internal class CommentaryExtension : VimExtension {
* E.g. handles the `gc` in `gc_`, by setting the operator function, then invoking `g@` to receive the `_` motion to
* invoke the operator. This object is both the mapping handler and the operator function.
*/
private class CommentaryOperatorHandler : OperatorFunction, ExtensionHandler {
private class CommentaryOperatorHandler : ExtensionHandler {
override val isRepeatable = true
override fun execute(editor: VimEditor, context: ExecutionContext, operatorArguments: OperatorArguments) {
setOperatorFunction(this)
injector.globalOptions().operatorfunc = OPERATOR_FUNC
executeNormalWithoutMapping(injector.parser.parseKeys("g@"), editor.ij)
}
// todo make it multicaret
override fun apply(editor: VimEditor, context: ExecutionContext, selectionType: SelectionType?): Boolean {
val range = injector.markService.getChangeMarks(editor.primaryCaret()) ?: return false
return doCommentary(editor, context, range, selectionType ?: SelectionType.CHARACTER_WISE, true)
}
}
private class CommentaryMappingHandler : ExtensionHandler {
@ -239,7 +249,7 @@ internal class CommentaryExtension : VimExtension {
*/
private class CommentaryCommandAliasHandler : CommandAliasHandler {
override fun execute(command: String, ranges: Ranges, editor: VimEditor, context: ExecutionContext) {
doCommentary(editor, context, ranges.getTextRange(editor, -1), SelectionType.LINE_WISE, false)
Util.doCommentary(editor, context, ranges.getTextRange(editor, -1), SelectionType.LINE_WISE, false)
}
}
}

View File

@ -19,24 +19,22 @@ import com.intellij.openapi.util.Key
import com.maddyhome.idea.vim.api.ExecutionContext
import com.maddyhome.idea.vim.api.VimEditor
import com.maddyhome.idea.vim.api.getOffset
import com.maddyhome.idea.vim.api.globalOptions
import com.maddyhome.idea.vim.api.injector
import com.maddyhome.idea.vim.api.setChangeMarks
import com.maddyhome.idea.vim.command.MappingMode
import com.maddyhome.idea.vim.command.OperatorArguments
import com.maddyhome.idea.vim.state.mode.SelectionType
import com.maddyhome.idea.vim.state.mode.SelectionType.CHARACTER_WISE
import com.maddyhome.idea.vim.state.mode.selectionType
import com.maddyhome.idea.vim.common.TextRange
import com.maddyhome.idea.vim.extension.ExtensionHandler
import com.maddyhome.idea.vim.extension.VimExtension
import com.maddyhome.idea.vim.extension.VimExtensionFacade
import com.maddyhome.idea.vim.extension.VimExtensionFacade.executeNormalWithoutMapping
import com.maddyhome.idea.vim.extension.VimExtensionFacade.getRegister
import com.maddyhome.idea.vim.extension.VimExtensionFacade.putExtensionHandlerMapping
import com.maddyhome.idea.vim.extension.VimExtensionFacade.putKeyMappingIfMissing
import com.maddyhome.idea.vim.extension.VimExtensionFacade.setOperatorFunction
import com.maddyhome.idea.vim.extension.VimExtensionFacade.setRegister
import com.maddyhome.idea.vim.extension.exportOperatorFunction
import com.maddyhome.idea.vim.helper.fileSize
import com.maddyhome.idea.vim.state.mode.mode
import com.maddyhome.idea.vim.helper.moveToInlayAwareLogicalPosition
import com.maddyhome.idea.vim.helper.moveToInlayAwareOffset
import com.maddyhome.idea.vim.key.OperatorFunction
@ -45,6 +43,8 @@ import com.maddyhome.idea.vim.mark.VimMarkConstants
import com.maddyhome.idea.vim.newapi.IjVimEditor
import com.maddyhome.idea.vim.newapi.ij
import com.maddyhome.idea.vim.newapi.vim
import com.maddyhome.idea.vim.state.mode.SelectionType
import com.maddyhome.idea.vim.state.mode.selectionType
import org.jetbrains.annotations.NonNls
/**
@ -72,30 +72,13 @@ internal class VimExchangeExtension : VimExtension {
putKeyMappingIfMissing(MappingMode.X, injector.parser.parseKeys("X"), owner, injector.parser.parseKeys(EXCHANGE_CMD), true)
putKeyMappingIfMissing(MappingMode.N, injector.parser.parseKeys("cxc"), owner, injector.parser.parseKeys(EXCHANGE_CLEAR_CMD), true)
putKeyMappingIfMissing(MappingMode.N, injector.parser.parseKeys("cxx"), owner, injector.parser.parseKeys(EXCHANGE_LINE_CMD), true)
VimExtensionFacade.exportOperatorFunction(OPERATOR_FUNC, Operator())
}
companion object {
@NonNls
const val EXCHANGE_CMD = "<Plug>(Exchange)"
@NonNls
const val EXCHANGE_CLEAR_CMD = "<Plug>(ExchangeClear)"
@NonNls
const val EXCHANGE_LINE_CMD = "<Plug>(ExchangeLine)"
object Util {
val EXCHANGE_KEY = Key<Exchange>("exchange")
// End mark has always greater of eq offset than start mark
class Exchange(val type: SelectionType, 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)
@ -104,18 +87,25 @@ internal class VimExchangeExtension : VimExtension {
}
}
companion object {
@NonNls private const val EXCHANGE_CMD = "<Plug>(Exchange)"
@NonNls private const val EXCHANGE_CLEAR_CMD = "<Plug>(ExchangeClear)"
@NonNls private const val EXCHANGE_LINE_CMD = "<Plug>(ExchangeLine)"
@NonNls private const val OPERATOR_FUNC = "ExchangeOperatorFunc"
}
private class ExchangeHandler(private val isLine: Boolean) : ExtensionHandler {
override val isRepeatable = true
override fun execute(editor: VimEditor, context: ExecutionContext, operatorArguments: OperatorArguments) {
setOperatorFunction(Operator(false))
injector.globalOptions().operatorfunc = OPERATOR_FUNC
executeNormalWithoutMapping(injector.parser.parseKeys(if (isLine) "g@_" else "g@"), editor.ij)
}
}
private class ExchangeClearHandler : ExtensionHandler {
override fun execute(editor: VimEditor, context: ExecutionContext, operatorArguments: OperatorArguments) {
clearExchange(editor.ij)
Util.clearExchange(editor.ij)
}
}
@ -125,12 +115,12 @@ internal class VimExchangeExtension : VimExtension {
val mode = editor.mode
// Leave visual mode to create selection marks
executeNormalWithoutMapping(injector.parser.parseKeys("<Esc>"), editor.ij)
Operator(true).apply(editor, context, mode.selectionType ?: CHARACTER_WISE)
Operator(true).apply(editor, context, mode.selectionType ?: SelectionType.CHARACTER_WISE)
}
}
}
private class Operator(private val isVisual: Boolean) : OperatorFunction {
private class Operator(private val isVisual: Boolean = false) : OperatorFunction {
fun Editor.getMarkOffset(mark: Mark) = IjVimEditor(this).getOffset(mark.line, mark.col)
fun SelectionType.getString() = when (this) {
SelectionType.CHARACTER_WISE -> "v"
@ -148,7 +138,7 @@ internal class VimExchangeExtension : VimExtension {
else -> HighlighterTargetArea.EXACT_RANGE
}
val isVisualLine = ex.type == SelectionType.LINE_WISE
val endAdj = if (!(isVisualLine) && (hlArea == HighlighterTargetArea.EXACT_RANGE || (isVisual))) 1 else 0
val endAdj = if (!(isVisualLine) && (hlArea == HighlighterTargetArea.EXACT_RANGE || isVisual)) 1 else 0
return ijEditor.markupModel.addRangeHighlighter(
ijEditor.getMarkOffset(ex.start),
(ijEditor.getMarkOffset(ex.end) + endAdj).coerceAtMost(ijEditor.fileSize),
@ -158,12 +148,12 @@ internal class VimExchangeExtension : VimExtension {
)
}
val currentExchange = getExchange(ijEditor, isVisual, selectionType ?: CHARACTER_WISE)
val exchange1 = ijEditor.getUserData(EXCHANGE_KEY)
val currentExchange = getExchange(ijEditor, isVisual, selectionType ?: SelectionType.CHARACTER_WISE)
val exchange1 = ijEditor.getUserData(Util.EXCHANGE_KEY)
if (exchange1 == null) {
val highlighter = highlightExchange(currentExchange)
currentExchange.setHighlighter(highlighter)
ijEditor.putUserData(EXCHANGE_KEY, currentExchange)
ijEditor.putUserData(Util.EXCHANGE_KEY, currentExchange)
return true
} else {
val cmp = compareExchanges(exchange1, currentExchange)
@ -189,7 +179,7 @@ internal class VimExchangeExtension : VimExtension {
}
}
exchange(ijEditor, ex1, ex2, reverse, expand)
clearExchange(ijEditor)
Util.clearExchange(ijEditor)
return true
}
}
@ -354,4 +344,14 @@ internal class VimExchangeExtension : VimExtension {
}
}
}
// End mark has always greater of eq offset than start mark
class Exchange(val type: SelectionType, val start: Mark, val end: Mark, val text: String) {
private var myHighlighter: RangeHighlighter? = null
fun setHighlighter(highlighter: RangeHighlighter) {
myHighlighter = highlighter
}
fun getHighlighter(): RangeHighlighter? = myHighlighter
}
}

View File

@ -99,7 +99,7 @@ internal class Matchit : VimExtension {
// Normally we want to jump to the start of the matching pair. But when moving forward in operator
// pending mode, we want to include the entire match. isInOpPending makes that distinction.
val isInOpPending = commandState.isOperatorPending
val isInOpPending = commandState.isOperatorPending(editor.mode)
if (isInOpPending) {
val matchitAction = MatchitAction()
@ -217,6 +217,8 @@ private object FileTypePatterns {
return if (fileTypeName in htmlLikeFileTypes) {
this.htmlPatterns
} else if (fileTypeName == "JAVA" || fileExtension == "java") {
this.javaPatterns
} else if (fileTypeName == "Ruby" || fileExtension == "rb") {
this.rubyPatterns
} else if (fileTypeName == "RHTML" || fileExtension == "erb") {
@ -231,7 +233,7 @@ private object FileTypePatterns {
} else if (fileTypeName == "CMakeLists.txt" || fileName == "CMakeLists") {
this.cMakePatterns
} else {
return null
this.htmlPatterns
}
}
@ -242,6 +244,7 @@ private object FileTypePatterns {
)
private val htmlPatterns = createHtmlPatterns()
private val javaPatterns = createJavaPatterns()
private val rubyPatterns = createRubyPatterns()
private val rubyAndHtmlPatterns = rubyPatterns + htmlPatterns
private val phpPatterns = createPhpPatterns()
@ -271,6 +274,14 @@ private object FileTypePatterns {
)
}
private fun createJavaPatterns(): LanguagePatterns {
return (
LanguagePatterns("\\b(?<!else\\s+)if\\b", "\\belse\\s+if\\b", "\\belse(?!\\s+if)\\b") +
LanguagePatterns("\\bdo\\b", "\\bwhile\\b") +
LanguagePatterns("\\btry\\b", "\\bcatch\\b", "\\bfinally\\b")
)
}
private fun createRubyPatterns(): LanguagePatterns {
// Original patterns: https://github.com/vim/vim/blob/master/runtime/ftplugin/ruby.vim
// We use non-capturing groups (?:) since we don't need any back refs. The \\b marker takes care of word boundaries.

View File

@ -130,15 +130,15 @@ internal class NerdTree : VimExtension {
addCommand("NERDTreeFind", IjCommandHandler("SelectInProjectView"))
addCommand("NERDTreeRefreshRoot", IjCommandHandler("Synchronize"))
synchronized(monitor) {
commandsRegistered = true
synchronized(Util.monitor) {
Util.commandsRegistered = true
ProjectManager.getInstance().openProjects.forEach { project -> installDispatcher(project) }
}
}
class IjCommandHandler(private val actionId: String) : CommandAliasHandler {
override fun execute(command: String, ranges: Ranges, editor: VimEditor, context: ExecutionContext) {
callAction(editor, actionId, context)
Util.callAction(editor, actionId, context)
}
}
@ -149,7 +149,7 @@ internal class NerdTree : VimExtension {
if (toolWindow.isVisible) {
toolWindow.hide()
} else {
callAction(editor, "ActivateProjectToolWindow", context)
Util.callAction(editor, "ActivateProjectToolWindow", context)
}
}
}
@ -187,8 +187,8 @@ internal class NerdTree : VimExtension {
// TODO I'm not sure is this activity runs at all? Should we use [RunOnceUtil] instead?
class NerdStartupActivity : ProjectActivity {
override suspend fun execute(project: Project) {
synchronized(monitor) {
if (!commandsRegistered) return
synchronized(Util.monitor) {
if (!Util.commandsRegistered) return
installDispatcher(project)
}
}
@ -214,7 +214,7 @@ internal class NerdTree : VimExtension {
val action = nextNode.actionHolder
when (action) {
is NerdAction.ToIj -> callAction(null, action.name, e.dataContext.vim)
is NerdAction.ToIj -> Util.callAction(null, action.name, e.dataContext.vim)
is NerdAction.Code -> e.project?.let { action.action(it, e.dataContext, e) }
}
}
@ -356,7 +356,7 @@ internal class NerdTree : VimExtension {
currentWindow?.split(SwingConstants.VERTICAL, true, file, true)
// FIXME: 22.01.2021 This solution bouncing a bit
callAction(null, "ActivateProjectToolWindow", context.vim)
Util.callAction(null, "ActivateProjectToolWindow", context.vim)
},
)
registerCommand(
@ -368,7 +368,7 @@ internal class NerdTree : VimExtension {
val currentWindow = splitters.currentWindow
currentWindow?.split(SwingConstants.HORIZONTAL, true, file, true)
callAction(null, "ActivateProjectToolWindow", context.vim)
Util.callAction(null, "ActivateProjectToolWindow", context.vim)
},
)
registerCommand(
@ -502,16 +502,18 @@ internal class NerdTree : VimExtension {
}
},
)
for (c in ('a'..'z') + ('A'..'Z')) {
val ks = KeyStroke.getKeyStroke(c)
if (ks !in actionsRoot) {
registerCommand(c.toString(), NerdAction.Code { _, _, _ -> })
}
}
}
companion object {
const val pluginName = "NERDTree"
object Util {
internal val monitor = Any()
internal var commandsRegistered = false
private val LOG = logger<NerdTree>()
fun callAction(editor: VimEditor?, name: String, context: ExecutionContext) {
val action = ActionManager.getInstance().getAction(name) ?: run {
VimPlugin.showMessage(MessageHelper.message("action.not.found.0", name))
@ -526,6 +528,13 @@ internal class NerdTree : VimExtension {
}
}
}
}
companion object {
const val pluginName = "NERDTree"
private val LOG = logger<NerdTree>()
}
}
private fun addCommand(alias: String, handler: CommandAliasHandler) {
VimPlugin.getCommand().setAlias(alias, CommandAlias.Call(0, -1, alias, handler))
@ -545,9 +554,9 @@ internal class NerdTree : VimExtension {
actionsRoot.addLeafs(default, action)
}
private val actionsRoot: RootNode<NerdAction> = RootNode()
private var currentNode: CommandPartNode<NerdAction> = actionsRoot
private fun collectShortcuts(node: Node<NerdAction>): Set<KeyStroke> {
return if (node is CommandPartNode<NerdAction>) {
val res = node.keys.toMutableSet()
@ -559,12 +568,11 @@ internal class NerdTree : VimExtension {
}
private fun installDispatcher(project: Project) {
val dispatcher = NerdDispatcher.getInstance(project)
val shortcuts = collectShortcuts(actionsRoot).map { RequiredShortcut(it, MappingOwner.Plugin.get(pluginName)) }
val dispatcher = NerdTree.NerdDispatcher.getInstance(project)
val shortcuts =
collectShortcuts(actionsRoot).map { RequiredShortcut(it, MappingOwner.Plugin.get(NerdTree.pluginName)) }
dispatcher.registerCustomShortcutSet(
KeyGroup.toShortcutSet(shortcuts),
(ProjectView.getInstance(project) as ProjectViewImpl).component,
)
}
}
}

View File

@ -9,6 +9,7 @@
package com.maddyhome.idea.vim.extension.paragraphmotion
import com.intellij.openapi.editor.Caret
import com.maddyhome.idea.vim.VimPlugin
import com.maddyhome.idea.vim.api.ExecutionContext
import com.maddyhome.idea.vim.api.VimEditor
import com.maddyhome.idea.vim.api.injector
@ -20,8 +21,10 @@ import com.maddyhome.idea.vim.extension.VimExtension
import com.maddyhome.idea.vim.extension.VimExtensionFacade
import com.maddyhome.idea.vim.extension.VimExtensionFacade.putKeyMappingIfMissing
import com.maddyhome.idea.vim.helper.vimForEachCaret
import com.maddyhome.idea.vim.key.MappingOwner
import com.maddyhome.idea.vim.newapi.ij
import com.maddyhome.idea.vim.newapi.vim
import javax.swing.KeyStroke
internal class ParagraphMotion : VimExtension {
override fun getName(): String = "vim-paragraph-motion"
@ -30,8 +33,8 @@ internal class ParagraphMotion : VimExtension {
VimExtensionFacade.putExtensionHandlerMapping(MappingMode.NXO, injector.parser.parseKeys("<Plug>(ParagraphNextMotion)"), owner, ParagraphMotionHandler(1), false)
VimExtensionFacade.putExtensionHandlerMapping(MappingMode.NXO, injector.parser.parseKeys("<Plug>(ParagraphPrevMotion)"), owner, ParagraphMotionHandler(-1), false)
putKeyMappingIfMissing(MappingMode.NXO, injector.parser.parseKeys("}"), owner, injector.parser.parseKeys("<Plug>(ParagraphNextMotion)"), true)
putKeyMappingIfMissing(MappingMode.NXO, injector.parser.parseKeys("{"), owner, injector.parser.parseKeys("<Plug>(ParagraphPrevMotion)"), true)
putKeyMappingIfMissingFromAndToKeys(MappingMode.NXO, injector.parser.parseKeys("}"), owner, injector.parser.parseKeys("<Plug>(ParagraphNextMotion)"), true)
putKeyMappingIfMissingFromAndToKeys(MappingMode.NXO, injector.parser.parseKeys("{"), owner, injector.parser.parseKeys("<Plug>(ParagraphPrevMotion)"), true)
}
private class ParagraphMotionHandler(private val count: Int) : ExtensionHandler {
@ -49,4 +52,17 @@ internal class ParagraphMotion : VimExtension {
?.let { editor.normalizeOffset(it, true) }
}
}
// For VIM-3306
@Suppress("SameParameterValue")
private fun putKeyMappingIfMissingFromAndToKeys(
modes: Set<MappingMode>,
fromKeys: List<KeyStroke>,
pluginOwner: MappingOwner,
toKeys: List<KeyStroke>,
recursive: Boolean,
) {
val filteredModes = modes.filterNotTo(HashSet()) { VimPlugin.getKey().hasmapfrom(it, fromKeys) }
putKeyMappingIfMissing(filteredModes, fromKeys, pluginOwner, toKeys, recursive)
}
}

View File

@ -14,24 +14,19 @@ import com.maddyhome.idea.vim.api.ExecutionContext
import com.maddyhome.idea.vim.api.ImmutableVimCaret
import com.maddyhome.idea.vim.api.VimEditor
import com.maddyhome.idea.vim.api.getLineEndOffset
import com.maddyhome.idea.vim.api.globalOptions
import com.maddyhome.idea.vim.api.injector
import com.maddyhome.idea.vim.command.MappingMode
import com.maddyhome.idea.vim.state.mode.Mode
import com.maddyhome.idea.vim.command.OperatorArguments
import com.maddyhome.idea.vim.state.mode.SelectionType
import com.maddyhome.idea.vim.state.mode.SelectionType.CHARACTER_WISE
import com.maddyhome.idea.vim.state.mode.isLine
import com.maddyhome.idea.vim.state.mode.selectionType
import com.maddyhome.idea.vim.common.TextRange
import com.maddyhome.idea.vim.extension.ExtensionHandler
import com.maddyhome.idea.vim.extension.VimExtension
import com.maddyhome.idea.vim.extension.VimExtensionFacade
import com.maddyhome.idea.vim.extension.VimExtensionFacade.executeNormalWithoutMapping
import com.maddyhome.idea.vim.extension.VimExtensionFacade.putKeyMappingIfMissing
import com.maddyhome.idea.vim.extension.VimExtensionFacade.setOperatorFunction
import com.maddyhome.idea.vim.extension.exportOperatorFunction
import com.maddyhome.idea.vim.group.visual.VimSelection
import com.maddyhome.idea.vim.helper.exitVisualMode
import com.maddyhome.idea.vim.state.mode.mode
import com.maddyhome.idea.vim.helper.vimStateMachine
import com.maddyhome.idea.vim.key.OperatorFunction
import com.maddyhome.idea.vim.newapi.IjVimEditor
@ -39,6 +34,10 @@ import com.maddyhome.idea.vim.newapi.ij
import com.maddyhome.idea.vim.newapi.vim
import com.maddyhome.idea.vim.options.helpers.ClipboardOptionHelper
import com.maddyhome.idea.vim.put.PutData
import com.maddyhome.idea.vim.state.mode.Mode
import com.maddyhome.idea.vim.state.mode.SelectionType
import com.maddyhome.idea.vim.state.mode.isLine
import com.maddyhome.idea.vim.state.mode.selectionType
import org.jetbrains.annotations.NonNls
internal class ReplaceWithRegister : VimExtension {
@ -53,11 +52,13 @@ internal class ReplaceWithRegister : VimExtension {
putKeyMappingIfMissing(MappingMode.N, injector.parser.parseKeys("gr"), owner, injector.parser.parseKeys(RWR_OPERATOR), true)
putKeyMappingIfMissing(MappingMode.N, injector.parser.parseKeys("grr"), owner, injector.parser.parseKeys(RWR_LINE), true)
putKeyMappingIfMissing(MappingMode.X, injector.parser.parseKeys("gr"), owner, injector.parser.parseKeys(RWR_VISUAL), true)
VimExtensionFacade.exportOperatorFunction(OPERATOR_FUNC, Operator())
}
private class RwrVisual : ExtensionHandler {
override fun execute(editor: VimEditor, context: ExecutionContext, operatorArguments: OperatorArguments) {
val typeInEditor = editor.mode.selectionType ?: CHARACTER_WISE
val typeInEditor = editor.mode.selectionType ?: SelectionType.CHARACTER_WISE
editor.sortedCarets().forEach { caret ->
val selectionStart = caret.selectionStart
val selectionEnd = caret.selectionEnd
@ -73,7 +74,7 @@ internal class ReplaceWithRegister : VimExtension {
override val isRepeatable: Boolean = true
override fun execute(editor: VimEditor, context: ExecutionContext, operatorArguments: OperatorArguments) {
setOperatorFunction(Operator())
injector.globalOptions().operatorfunc = OPERATOR_FUNC
executeNormalWithoutMapping(injector.parser.parseKeys("g@"), editor.ij)
}
}
@ -112,11 +113,11 @@ internal class ReplaceWithRegister : VimExtension {
editor.primaryCaret() to VimSelection.create(
range.startOffset,
range.endOffset - 1,
selectionType ?: CHARACTER_WISE,
selectionType ?: SelectionType.CHARACTER_WISE,
editor,
),
),
selectionType ?: CHARACTER_WISE,
selectionType ?: SelectionType.CHARACTER_WISE,
)
// todo multicaret
doReplace(ijEditor, editor.primaryCaret(), visualSelection)
@ -132,14 +133,12 @@ internal class ReplaceWithRegister : VimExtension {
}
companion object {
@NonNls
private const val RWR_OPERATOR = "<Plug>ReplaceWithRegisterOperator"
@NonNls
private const val RWR_LINE = "<Plug>ReplaceWithRegisterLine"
@NonNls
private const val RWR_VISUAL = "<Plug>ReplaceWithRegisterVisual"
@NonNls private const val RWR_OPERATOR = "<Plug>ReplaceWithRegisterOperator"
@NonNls private const val RWR_LINE = "<Plug>ReplaceWithRegisterLine"
@NonNls private const val RWR_VISUAL = "<Plug>ReplaceWithRegisterVisual"
@NonNls private const val OPERATOR_FUNC = "ReplaceWithRegisterOperatorFunc"
}
}
private fun doReplace(editor: Editor, caret: ImmutableVimCaret, visualSelection: PutData.VisualSelection) {
val registerGroup = injector.registerGroup
@ -165,13 +164,14 @@ internal class ReplaceWithRegister : VimExtension {
caretAfterInsertedText = false,
putToLine = -1,
)
val vimEditor = editor.vim
ClipboardOptionHelper.IdeaputDisabler().use {
VimPlugin.getPut().putText(
IjVimEditor(editor),
vimEditor,
injector.executionContextManager.onEditor(editor.vim),
putData,
operatorArguments = OperatorArguments(
editor.vimStateMachine?.isOperatorPending ?: false,
editor.vimStateMachine?.isOperatorPending(vimEditor.mode) ?: false,
0,
editor.vim.mode,
),
@ -179,5 +179,3 @@ internal class ReplaceWithRegister : VimExtension {
)
}
}
}
}

View File

@ -36,6 +36,7 @@ import com.maddyhome.idea.vim.helper.StrictMode
import com.maddyhome.idea.vim.newapi.ij
import java.awt.Font
import java.awt.event.KeyEvent
import java.util.*
import javax.swing.Timer
@ -48,17 +49,20 @@ internal class IdeaVimSneakExtension : VimExtension {
override fun init() {
val highlightHandler = HighlightHandler()
mapToFunctionAndProvideKeys("s", SneakHandler(highlightHandler, Direction.FORWARD))
mapToFunctionAndProvideKeys("S", SneakHandler(highlightHandler, Direction.BACKWARD))
mapToFunctionAndProvideKeys("s", SneakHandler(highlightHandler, Direction.FORWARD), MappingMode.NXO)
// vim-sneak uses `Z` for visual mode because `S` conflict with vim-sneak plugin VIM-3330
mapToFunctionAndProvideKeys("S", SneakHandler(highlightHandler, Direction.BACKWARD), MappingMode.NO)
mapToFunctionAndProvideKeys("Z", SneakHandler(highlightHandler, Direction.BACKWARD), MappingMode.X)
// workaround to support ; and , commands
mapToFunctionAndProvideKeys("f", SneakMemoryHandler("f"))
mapToFunctionAndProvideKeys("F", SneakMemoryHandler("F"))
mapToFunctionAndProvideKeys("t", SneakMemoryHandler("t"))
mapToFunctionAndProvideKeys("T", SneakMemoryHandler("T"))
mapToFunctionAndProvideKeys("f", SneakMemoryHandler("f"), MappingMode.NXO)
mapToFunctionAndProvideKeys("F", SneakMemoryHandler("F"), MappingMode.NXO)
mapToFunctionAndProvideKeys("t", SneakMemoryHandler("t"), MappingMode.NXO)
mapToFunctionAndProvideKeys("T", SneakMemoryHandler("T"), MappingMode.NXO)
mapToFunctionAndProvideKeys(";", SneakRepeatHandler(highlightHandler, RepeatDirection.IDENTICAL))
mapToFunctionAndProvideKeys(",", SneakRepeatHandler(highlightHandler, RepeatDirection.REVERSE))
mapToFunctionAndProvideKeys(";", SneakRepeatHandler(highlightHandler, RepeatDirection.IDENTICAL), MappingMode.NXO)
mapToFunctionAndProvideKeys(",", SneakRepeatHandler(highlightHandler, RepeatDirection.REVERSE), MappingMode.NXO)
}
private class SneakHandler(
@ -273,16 +277,18 @@ internal class IdeaVimSneakExtension : VimExtension {
* Map some <Plug>(keys) command to given handler
* and create mapping to <Plug>(prefix)[keys]
*/
private fun VimExtension.mapToFunctionAndProvideKeys(keys: String, handler: ExtensionHandler) {
private fun VimExtension.mapToFunctionAndProvideKeys(
keys: String, handler: ExtensionHandler, mappingModes: EnumSet<MappingMode>
) {
VimExtensionFacade.putExtensionHandlerMapping(
MappingMode.NXO,
mappingModes,
injector.parser.parseKeys(command(keys)),
owner,
handler,
false
)
VimExtensionFacade.putExtensionHandlerMapping(
MappingMode.NXO,
mappingModes,
injector.parser.parseKeys(commandFromOriginalPlugin(keys)),
owner,
handler,
@ -294,17 +300,17 @@ private fun VimExtension.mapToFunctionAndProvideKeys(keys: String, handler: Exte
// - The shortcut should not be registered if any of these mappings is overridden in .ideavimrc
// - The shortcut should not be registered if some other shortcut for this key exists
val fromKeys = injector.parser.parseKeys(keys)
val filteredModes = MappingMode.NXO.filterNotTo(HashSet()) {
val filteredModes = mappingModes.filterNotTo(HashSet()) {
VimPlugin.getKey().hasmapto(it, injector.parser.parseKeys(command(keys)))
}
val filteredModes2 = MappingMode.NXO.filterNotTo(HashSet()) {
val filteredModes2 = mappingModes.filterNotTo(HashSet()) {
VimPlugin.getKey().hasmapto(it, injector.parser.parseKeys(commandFromOriginalPlugin(keys)))
}
val filteredFromModes = MappingMode.NXO.filterNotTo(HashSet()) {
val filteredFromModes = mappingModes.filterNotTo(HashSet()) {
injector.keyGroup.hasmapfrom(it, fromKeys)
}
val doubleFiltered = MappingMode.NXO
val doubleFiltered = mappingModes
.filter { it in filteredModes2 && it in filteredModes && it in filteredFromModes }
.toSet()
putKeyMapping(doubleFiltered, fromKeys, owner, injector.parser.parseKeys(command(keys)), true)

View File

@ -0,0 +1,30 @@
package com.maddyhome.idea.vim.extension.surround
import com.intellij.util.text.CharSequenceSubSequence
internal data class RepeatedCharSequence(val text: CharSequence, val count: Int) : CharSequence {
override val length = text.length * count
override fun get(index: Int): Char {
if (index < 0 || index >= length) throw IndexOutOfBoundsException()
return text[index % text.length]
}
override fun subSequence(startIndex: Int, endIndex: Int): CharSequence {
return CharSequenceSubSequence(this, startIndex, endIndex)
}
override fun toString(): String {
return text.repeat(count)
}
companion object {
fun of(text: CharSequence, count: Int): CharSequence {
return when (count) {
0 -> ""
1 -> text
else -> RepeatedCharSequence(text, count)
}
}
}
}

View File

@ -13,9 +13,11 @@ import com.intellij.openapi.editor.Editor
import com.maddyhome.idea.vim.VimPlugin
import com.maddyhome.idea.vim.api.ExecutionContext
import com.maddyhome.idea.vim.api.VimCaret
import com.maddyhome.idea.vim.api.VimChangeGroup
import com.maddyhome.idea.vim.api.VimEditor
import com.maddyhome.idea.vim.api.endsWithNewLine
import com.maddyhome.idea.vim.api.getLeadingCharacterOffset
import com.maddyhome.idea.vim.api.globalOptions
import com.maddyhome.idea.vim.api.injector
import com.maddyhome.idea.vim.api.setChangeMarks
import com.maddyhome.idea.vim.command.MappingMode
@ -23,15 +25,19 @@ import com.maddyhome.idea.vim.command.OperatorArguments
import com.maddyhome.idea.vim.common.TextRange
import com.maddyhome.idea.vim.extension.ExtensionHandler
import com.maddyhome.idea.vim.extension.VimExtension
import com.maddyhome.idea.vim.extension.VimExtensionFacade
import com.maddyhome.idea.vim.extension.VimExtensionFacade.executeNormalWithoutMapping
import com.maddyhome.idea.vim.extension.VimExtensionFacade.getRegisterForCaret
import com.maddyhome.idea.vim.extension.VimExtensionFacade.inputKeyStroke
import com.maddyhome.idea.vim.extension.VimExtensionFacade.inputString
import com.maddyhome.idea.vim.extension.VimExtensionFacade.putExtensionHandlerMapping
import com.maddyhome.idea.vim.extension.VimExtensionFacade.putKeyMappingIfMissing
import com.maddyhome.idea.vim.extension.VimExtensionFacade.setOperatorFunction
import com.maddyhome.idea.vim.extension.VimExtensionFacade.setRegisterForCaret
import com.maddyhome.idea.vim.extension.exportOperatorFunction
import com.maddyhome.idea.vim.helper.runWithEveryCaretAndRestore
import com.maddyhome.idea.vim.key.OperatorFunction
import com.maddyhome.idea.vim.newapi.IjVimCaret
import com.maddyhome.idea.vim.newapi.IjVimEditor
import com.maddyhome.idea.vim.newapi.ij
import com.maddyhome.idea.vim.newapi.vim
import com.maddyhome.idea.vim.options.helpers.ClipboardOptionHelper
@ -74,13 +80,15 @@ internal class VimSurroundExtension : VimExtension {
putKeyMappingIfMissing(MappingMode.N, injector.parser.parseKeys("ds"), owner, injector.parser.parseKeys("<Plug>DSurround"), true)
putKeyMappingIfMissing(MappingMode.XO, injector.parser.parseKeys("S"), owner, injector.parser.parseKeys("<Plug>VSurround"), true)
}
VimExtensionFacade.exportOperatorFunction(OPERATOR_FUNC, Operator(supportsMultipleCursors = false, count = 1)) // TODO
}
private class YSurroundHandler : ExtensionHandler {
override val isRepeatable = true
override fun execute(editor: VimEditor, context: ExecutionContext, operatorArguments: OperatorArguments) {
setOperatorFunction(Operator())
injector.globalOptions().operatorfunc = OPERATOR_FUNC
executeNormalWithoutMapping(injector.parser.parseKeys("g@"), editor.ij)
}
}
@ -101,7 +109,7 @@ internal class VimSurroundExtension : VimExtension {
val lastNonWhiteSpaceOffset = getLastNonWhitespaceCharacterOffset(editor.text(), lineStartOffset, lineEndOffset)
if (lastNonWhiteSpaceOffset != null) {
val range = TextRange(lineStartOffset, lastNonWhiteSpaceOffset + 1)
performSurround(pair, range, it)
performSurround(pair, range, it, count = operatorArguments.count1)
}
// it.moveToOffset(lineStartOffset)
}
@ -121,15 +129,13 @@ internal class VimSurroundExtension : VimExtension {
private class VSurroundHandler : ExtensionHandler {
override fun execute(editor: VimEditor, context: ExecutionContext, operatorArguments: OperatorArguments) {
val selectionStart = editor.ij.caretModel.primaryCaret.selectionStart
// NB: Operator ignores SelectionType anyway
if (!Operator().apply(editor, context, editor.mode.selectionType)) {
if (!Operator(supportsMultipleCursors = true, count = operatorArguments.count1).apply(editor, context, editor.mode.selectionType)) {
return
}
runWriteAction {
// Leave visual mode
executeNormalWithoutMapping(injector.parser.parseKeys("<Esc>"), editor.ij)
editor.ij.caretModel.moveToOffset(selectionStart)
}
}
}
@ -150,6 +156,10 @@ internal class VimSurroundExtension : VimExtension {
companion object {
fun change(editor: VimEditor, context: ExecutionContext, charFrom: Char, newSurround: Pair<String, String>?) {
editor.ij.runWithEveryCaretAndRestore { changeAtCaret(editor, context, charFrom, newSurround) }
}
fun changeAtCaret(editor: VimEditor, context: ExecutionContext, charFrom: Char, newSurround: Pair<String, String>?) {
// Save old register values for carets
val surroundings = editor.sortedCarets()
.map {
@ -257,21 +267,42 @@ internal class VimSurroundExtension : VimExtension {
}
}
private class Operator : OperatorFunction {
override fun apply(editor: VimEditor, context: ExecutionContext, selectionType: SelectionType?): Boolean {
val ijEditor = editor.ij
val c = getChar(ijEditor)
private class Operator(private val supportsMultipleCursors: Boolean, private val count: Int) : OperatorFunction {
override fun apply(vimEditor: VimEditor, context: ExecutionContext, selectionType: SelectionType?): Boolean {
val editor = vimEditor.ij
val c = getChar(editor)
if (c.code == 0) return true
val pair = getOrInputPair(c, ijEditor) ?: return false
// XXX: Will it work with line-wise or block-wise selections?
val range = getSurroundRange(editor.currentCaret()) ?: return false
performSurround(pair, range, editor.currentCaret(), selectionType == SelectionType.LINE_WISE)
val pair = getOrInputPair(c, editor) ?: return false
runWriteAction {
val change = VimPlugin.getChange()
if (supportsMultipleCursors) {
editor.runWithEveryCaretAndRestore {
applyOnce(editor, change, pair, count)
}
}
else {
applyOnce(editor, change, pair, count)
// Jump back to start
executeNormalWithoutMapping(injector.parser.parseKeys("`["), ijEditor)
executeNormalWithoutMapping(injector.parser.parseKeys("`["), editor)
}
}
return true
}
private fun applyOnce(editor: Editor, change: VimChangeGroup, pair: Pair<String, String>, count: Int) {
// XXX: Will it work with line-wise or block-wise selections?
val primaryCaret = editor.caretModel.primaryCaret
val range = getSurroundRange(primaryCaret.vim)
if (range != null) {
val start = RepeatedCharSequence.of(pair.first, count)
val end = RepeatedCharSequence.of(pair.second, count)
change.insertText(IjVimEditor(editor), IjVimCaret(primaryCaret), range.startOffset, start)
change.insertText(IjVimEditor(editor), IjVimCaret(primaryCaret), range.endOffset + start.length, end)
}
}
private fun getSurroundRange(caret: VimCaret): TextRange? {
val editor = caret.editor
val ijEditor = editor.ij
@ -288,6 +319,8 @@ private val LOG = logger<VimSurroundExtension>()
private const val REGISTER = '"'
private const val OPERATOR_FUNC = "SurroundOperatorFunc"
private val tagNameAndAttributesCapturePattern = "(\\S+)([^>]*)>".toPattern()
private val SURROUND_PAIRS = mapOf(
@ -354,15 +387,15 @@ private fun getChar(editor: Editor): Char {
return res
}
private fun performSurround(pair: Pair<String, String>, range: TextRange, caret: VimCaret, tagsOnNewLines: Boolean = false) {
private fun performSurround(pair: Pair<String, String>, range: TextRange, caret: VimCaret, count: Int, tagsOnNewLines: Boolean = false) {
runWriteAction {
val editor = caret.editor
val change = VimPlugin.getChange()
val leftSurround = pair.first + if (tagsOnNewLines) "\n" else ""
val leftSurround = RepeatedCharSequence.of(pair.first + if (tagsOnNewLines) "\n" else "", count)
val isEOF = range.endOffset == editor.text().length
val hasNewLine = editor.endsWithNewLine()
val rightSurround = if (tagsOnNewLines) {
val rightSurround = (if (tagsOnNewLines) {
if (isEOF && !hasNewLine) {
"\n" + pair.second
} else {
@ -370,7 +403,7 @@ private fun performSurround(pair: Pair<String, String>, range: TextRange, caret:
}
} else {
pair.second
}
}).let { RepeatedCharSequence.of(it, count) }
change.insertText(editor, caret, range.startOffset, leftSurround)
change.insertText(editor, caret, range.endOffset + leftSurround.length, rightSurround)

View File

@ -138,7 +138,7 @@ public class VimTextObjEntireExtension implements VimExtension {
final EntireTextObjectHandler textObjectHandler = new EntireTextObjectHandler(ignoreLeadingAndTrailing);
//noinspection DuplicatedCode
if (!vimStateMachine.isOperatorPending()) {
if (!vimStateMachine.isOperatorPending(editor.getMode())) {
((IjVimEditor) editor).getEditor().getCaretModel().runForEachCaret((Caret caret) -> {
final TextRange range = textObjectHandler.getRange(editor, new IjVimCaret(caret), context, count, 0);
if (range != null) {

View File

@ -267,7 +267,7 @@ public class VimIndentObject implements VimExtension {
final IndentObjectHandler textObjectHandler = new IndentObjectHandler(includeAbove, includeBelow);
if (!vimStateMachine.isOperatorPending()) {
if (!vimStateMachine.isOperatorPending(editor.getMode())) {
((IjVimEditor)editor).getEditor().getCaretModel().runForEachCaret((Caret caret) -> {
final TextRange range = textObjectHandler.getRange(vimEditor, new IjVimCaret(caret), context, count, 0);
if (range != null) {

View File

@ -68,17 +68,17 @@ import com.maddyhome.idea.vim.newapi.IjEditorExecutionContext
import com.maddyhome.idea.vim.newapi.IjVimCaret
import com.maddyhome.idea.vim.newapi.IjVimEditor
import com.maddyhome.idea.vim.newapi.ij
import com.maddyhome.idea.vim.regexp.VimRegex
import com.maddyhome.idea.vim.regexp.match.VimMatchResult
import com.maddyhome.idea.vim.state.mode.Mode
import com.maddyhome.idea.vim.state.mode.Mode.VISUAL
import com.maddyhome.idea.vim.state.mode.SelectionType
import com.maddyhome.idea.vim.state.mode.mode
import com.maddyhome.idea.vim.vimscript.model.commands.SortOption
import org.jetbrains.annotations.TestOnly
import java.math.BigInteger
import java.util.*
import java.util.function.Consumer
import kotlin.math.max
import kotlin.math.min
/**
* Provides all the insert/replace related functionality
@ -395,6 +395,7 @@ public class ChangeGroup : VimChangeGroupBase() {
context: ExecutionContext,
range: TextRange,
) {
val startPos = editor.offsetToBufferPosition(caret.offset.point)
val startOffset = editor.getLineStartForOffset(range.startOffset)
val endOffset = editor.getLineEndForOffset(range.endOffset)
val ijEditor = (editor as IjVimEditor).editor
@ -419,11 +420,7 @@ public class ChangeGroup : VimChangeGroupBase() {
}
}
val afterAction = {
val firstLine = editor.offsetToBufferPosition(
min(startOffset.toDouble(), endOffset.toDouble()).toInt()
).line
val newOffset = injector.motion.moveCaretToLineStartSkipLeading(editor, firstLine)
caret.moveToOffset(newOffset)
caret.moveToOffset(injector.motion.moveCaretToLineStartSkipLeading(editor, startPos.line))
restoreCursor(editor, caret, (caret as IjVimCaret).caret.logicalPosition.line)
}
if (project != null) {
@ -577,48 +574,62 @@ public class ChangeGroup : VimChangeGroupBase() {
}
val startOffset = editor.getLineStartOffset(startLine)
val endOffset = editor.getLineEndOffset(endLine)
return sortTextRange(editor, caret, startOffset, endOffset, lineComparator, sortOptions)
}
/**
* Sorts a text range with a comparator. Returns true if a replace was performed, false otherwise.
*
* @param editor The editor to replace text in
* @param start The starting position for the sort
* @param end The ending position for the sort
* @param lineComparator The comparator to use to sort
* @param sortOption The option to sort the range
* @return true if able to sort the text, false if not
*/
private fun sortTextRange(
editor: VimEditor,
caret: VimCaret,
start: Int,
end: Int,
lineComparator: Comparator<String>,
sortOption: SortOption,
): Boolean {
val selectedText = (editor as IjVimEditor).editor.document.getText(TextRangeInterval(start, end))
val lines: MutableList<String> = selectedText.split("\n").sortedWith(lineComparator).toMutableList()
if (sortOption.unique) {
val iterator = lines.iterator()
val selectedText = (editor as IjVimEditor).editor.document.getText(TextRangeInterval(startOffset, endOffset))
val lines = selectedText.split("\n")
val modifiedLines = sortOptions.pattern?.let {
if (sortOptions.sortOnPattern) {
extractPatternFromLines(editor, lines, startLine, it)
} else {
deletePatternFromLines(editor, lines, startLine, it)
}
} ?: lines
val sortedLines = lines.zip(modifiedLines)
.sortedWith { l1, l2 -> lineComparator.compare(l1.second, l2.second) }
.map {it.first}
.toMutableList()
if (sortOptions.unique) {
val iterator = sortedLines.iterator()
var previous: String? = null
while (iterator.hasNext()) {
val current = iterator.next()
if (current == previous || sortOption.ignoreCase && current.equals(previous, ignoreCase = true)) {
if (current == previous || sortOptions.ignoreCase && current.equals(previous, ignoreCase = true)) {
iterator.remove()
} else {
previous = current
}
}
}
if (lines.size < 1) {
if (sortedLines.isEmpty()) {
return false
}
replaceText(editor, caret, start, end, StringUtil.join(lines, "\n"))
replaceText(editor, caret, startOffset, endOffset, StringUtil.join(sortedLines, "\n"))
return true
}
private fun extractPatternFromLines(editor: VimEditor, lines: List<String>, startLine: Int, pattern: String): List<String> {
val regex = VimRegex(pattern)
return lines.mapIndexed { i: Int, line: String ->
val result = regex.findInLine(editor, startLine + i, 0)
when (result) {
is VimMatchResult.Success -> result.value
is VimMatchResult.Failure -> line
}
}
}
private fun deletePatternFromLines(editor: VimEditor, lines: List<String>, startLine: Int, pattern: String): List<String> {
val regex = VimRegex(pattern)
return lines.mapIndexed { i: Int, line: String ->
val result = regex.findInLine(editor, startLine + i, 0)
when (result) {
is VimMatchResult.Success -> line.substring(result.value.length, line.length)
is VimMatchResult.Failure -> line
}
}
}
/**
* Perform increment and decrement for numbers in visual mode
*

View File

@ -8,9 +8,11 @@
package com.maddyhome.idea.vim.group
import com.intellij.openapi.components.Service
import com.maddyhome.idea.vim.api.VimCommandGroupBase
/**
* @author Elliot Courant
*/
@Service
internal class CommandGroup : VimCommandGroupBase()

View File

@ -11,6 +11,9 @@ package com.maddyhome.idea.vim.group;
import com.intellij.execution.impl.ConsoleViewImpl;
import com.intellij.find.EditorSearchSession;
import com.intellij.openapi.application.ApplicationManager;
import com.intellij.openapi.client.ClientAppSession;
import com.intellij.openapi.client.ClientKind;
import com.intellij.openapi.client.ClientSessionsManager;
import com.intellij.openapi.components.PersistentStateComponent;
import com.intellij.openapi.components.State;
import com.intellij.openapi.components.Storage;
@ -22,7 +25,10 @@ import com.intellij.openapi.project.Project;
import com.maddyhome.idea.vim.KeyHandler;
import com.maddyhome.idea.vim.VimPlugin;
import com.maddyhome.idea.vim.api.*;
import com.maddyhome.idea.vim.helper.*;
import com.maddyhome.idea.vim.helper.CaretVisualAttributesHelperKt;
import com.maddyhome.idea.vim.helper.CommandStateHelper;
import com.maddyhome.idea.vim.helper.EditorHelper;
import com.maddyhome.idea.vim.helper.UserDataManager;
import com.maddyhome.idea.vim.newapi.IjVimDocument;
import com.maddyhome.idea.vim.newapi.IjVimEditor;
import com.maddyhome.idea.vim.options.EffectiveOptionValueChangeListener;
@ -34,10 +40,10 @@ import org.jetbrains.annotations.Nullable;
import java.util.Collection;
import java.util.stream.Collectors;
import java.util.stream.Stream;
import static com.maddyhome.idea.vim.api.VimInjectorKt.injector;
import static com.maddyhome.idea.vim.api.VimInjectorKt.options;
import static com.maddyhome.idea.vim.helper.CaretVisualAttributesHelperKt.updateCaretsVisualAttributes;
import static com.maddyhome.idea.vim.newapi.IjVimInjectorKt.ijOptions;
/**
@ -204,7 +210,8 @@ public class EditorGroup implements PersistentStateComponent<Element>, VimEditor
}
public void editorCreated(@NotNull Editor editor) {
DocumentManager.INSTANCE.addListeners(editor.getDocument());
UserDataManager.setVimInitialised(editor, true);
VimPlugin.getKey().registerRequiredShortcutKeys(new IjVimEditor(editor));
initLineNumbers(editor);
@ -246,14 +253,13 @@ public class EditorGroup implements PersistentStateComponent<Element>, VimEditor
switchToInsertMode.run();
}
});
updateCaretsVisualAttributes(editor);
updateCaretsVisualAttributes(new IjVimEditor(editor));
}
public void editorDeinit(@NotNull Editor editor, boolean isReleased) {
deinitLineNumbers(editor, isReleased);
UserDataManager.unInitializeEditor(editor);
VimPlugin.getKey().unregisterShortcutKeys(new IjVimEditor(editor));
DocumentManager.INSTANCE.removeListeners(editor.getDocument());
CaretVisualAttributesHelperKt.removeCaretsVisualAttributes(editor);
}
@ -284,6 +290,18 @@ public class EditorGroup implements PersistentStateComponent<Element>, VimEditor
notifyIdeaJoin(((IjVimEditor) editor).getEditor().getProject(), editor);
}
@Override
public void updateCaretsVisualAttributes(@NotNull VimEditor editor) {
Editor ijEditor = ((IjVimEditor) editor).getEditor();
CaretVisualAttributesHelperKt.updateCaretsVisualAttributes(ijEditor);
}
@Override
public void updateCaretsVisualPosition(@NotNull VimEditor editor) {
Editor ijEditor = ((IjVimEditor) editor).getEditor();
CaretVisualAttributesHelperKt.updateCaretsVisualAttributes(ijEditor);
}
public static class NumberChangeListener implements EffectiveOptionValueChangeListener {
public static NumberChangeListener INSTANCE = new NumberChangeListener();
@ -324,20 +342,45 @@ public class EditorGroup implements PersistentStateComponent<Element>, VimEditor
}
}
@NotNull
@Override
public Collection<VimEditor> localEditors() {
return HelperKt.localEditors().stream()
public @NotNull Collection<VimEditor> getEditorsRaw() {
return getLocalEditors()
.map(IjVimEditor::new)
.collect(Collectors.toList());
}
@NotNull
@Override
public Collection<VimEditor> localEditors(@NotNull VimDocument buffer) {
final Document document = ((IjVimDocument)buffer).getDocument();
return HelperKt.localEditors(document).stream()
public Collection<VimEditor> getEditors() {
return getLocalEditors()
.filter(UserDataManager::getVimInitialised)
.map(IjVimEditor::new)
.collect(Collectors.toList());
}
@NotNull
@Override
public Collection<VimEditor> getEditors(@NotNull VimDocument buffer) {
final Document document = ((IjVimDocument)buffer).getDocument();
return getLocalEditors()
.filter(editor -> UserDataManager.getVimInitialised(editor) && editor.getDocument().equals(document))
.map(IjVimEditor::new)
.collect(Collectors.toList());
}
private Stream<Editor> getLocalEditors() {
// Always fetch local editors. If we're hosting a Code With Me session, any connected guests will create hidden
// editors to handle syntax highlighting, completion requests, etc. We need to make sure that IdeaVim only makes
// changes (e.g. adding search highlights) to local editors, so things don't incorrectly flow through to any Clients.
// In non-CWM scenarios, or if IdeaVim is installed on the Client, there are only ever local editors, so this will
// also work there. In Gateway remote development scenarios, IdeaVim should not be installed on the host, only the
// Client, so all should work there too.
// Note that most IdeaVim operations are in response to interactive keystrokes, which would mean that
// ClientEditorManager.getCurrentInstance would return local editors. However, some operations are in response to
// events such as document change (to update search highlights) and these can come from CWM guests, and we'd get the
// remote editors.
// This invocation will always get local editors, regardless of current context.
final ClientAppSession localSession = ClientSessionsManager.getAppSessions(ClientKind.LOCAL).get(0);
return localSession.getService(ClientEditorManager.class).editors();
}
}

View File

@ -30,8 +30,6 @@ import com.intellij.psi.search.GlobalSearchScope;
import com.intellij.psi.search.ProjectScope;
import com.maddyhome.idea.vim.VimPlugin;
import com.maddyhome.idea.vim.api.*;
import com.maddyhome.idea.vim.state.mode.Mode;
import com.maddyhome.idea.vim.state.VimStateMachine;
import com.maddyhome.idea.vim.common.TextRange;
import com.maddyhome.idea.vim.helper.EditorHelper;
import com.maddyhome.idea.vim.helper.EditorHelperRt;
@ -40,6 +38,8 @@ import com.maddyhome.idea.vim.helper.SearchHelper;
import com.maddyhome.idea.vim.newapi.ExecuteExtensionKt;
import com.maddyhome.idea.vim.newapi.IjEditorExecutionContext;
import com.maddyhome.idea.vim.newapi.IjVimEditor;
import com.maddyhome.idea.vim.state.VimStateMachine;
import com.maddyhome.idea.vim.state.mode.Mode;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
@ -438,14 +438,11 @@ public class FileGroup extends VimFileBase {
private static final @NotNull Logger logger = Logger.getInstance(FileGroup.class.getName());
/**
* This method listens for editor tab changes so any insert/replace modes that need to be reset can be.
* Respond to editor tab selection and remember the last used tab
*/
public static void fileEditorManagerSelectionChangedCallback(@NotNull FileEditorManagerEvent event) {
// The user has changed the editor they are working with - exit insert/replace mode, and complete any
// appropriate repeat
if (event.getOldFile() != null) {
LastTabService.getInstance(event.getManager().getProject()).setLastTab(event.getOldFile());
}
}
}

View File

@ -20,7 +20,6 @@ import com.maddyhome.idea.vim.options.OptionAccessScope
*/
@Suppress("SpellCheckingInspection")
public open class GlobalIjOptions(scope: OptionAccessScope) : OptionsPropertiesBase(scope) {
public var closenotebooks: Boolean by optionProperty(IjOptions.closenotebooks)
public var ide: String by optionProperty(IjOptions.ide)
public var ideamarks: Boolean by optionProperty(IjOptions.ideamarks)
public var ideastatusicon: String by optionProperty(IjOptions.ideastatusicon)
@ -29,15 +28,15 @@ public open class GlobalIjOptions(scope: OptionAccessScope) : OptionsPropertiesB
public val lookupkeys: StringListOptionValue by optionProperty(IjOptions.lookupkeys)
public var trackactionids: Boolean by optionProperty(IjOptions.trackactionids)
public var visualdelay: Int by optionProperty(IjOptions.visualdelay)
public var showmodewidget: Boolean by optionProperty(IjOptions.showmodewidget)
// Temporary options to control work-in-progress behaviour
public var closenotebooks: Boolean by optionProperty(IjOptions.closenotebooks)
public var commandOrMotionAnnotation: Boolean by optionProperty(IjOptions.commandOrMotionAnnotation)
public var exCommandAnnotation: Boolean by optionProperty(IjOptions.exCommandAnnotation)
public var oldundo: Boolean by optionProperty(IjOptions.oldundo)
public var unifyjumps: Boolean by optionProperty(IjOptions.unifyjumps)
public var exCommandAnnotation: Boolean by optionProperty(IjOptions.exCommandAnnotation)
public var vimscriptFunctionAnnotation: Boolean by optionProperty(IjOptions.vimscriptFunctionAnnotation)
public var commandOrMotionAnnotation: Boolean by optionProperty(IjOptions.commandOrMotionAnnotation)
public var useNewRegex: Boolean by optionProperty(IjOptions.useNewRegex)
public var vimscriptFunctionAnnotation: Boolean by optionProperty(IjOptions.vimscriptFunctionAnnotation)
}
/**

View File

@ -33,8 +33,6 @@ public object IjOptions {
Options.overrideDefaultValue(Options.clipboard, VimString("ideaput,autoselect,exclude:cons\\|linux"))
}
public val closenotebooks: ToggleOption = addOption(ToggleOption("closenotebooks", GLOBAL, "closenotebooks", true))
public val exCommandAnnotation: ToggleOption = addOption(ToggleOption("excommandannotation", GLOBAL, "excommandannotation", true))
public val ide: StringOption = addOption(
StringOption("ide", GLOBAL, "ide", ApplicationNamesInfo.getInstance().fullProductNameWithEdition)
)
@ -81,13 +79,16 @@ public object IjOptions {
"<Tab>,<Down>,<Up>,<Enter>,<Left>,<Right>,<C-Down>,<C-Up>,<PageUp>,<PageDown>,<C-J>,<C-Q>")
)
public val trackactionids: ToggleOption = addOption(ToggleOption("trackactionids", GLOBAL, "tai", false))
public val unifyjumps: ToggleOption = addOption(ToggleOption("unifyjumps", GLOBAL, "unifyjumps", true))
public val visualdelay: UnsignedNumberOption = addOption(UnsignedNumberOption("visualdelay", GLOBAL, "visualdelay", 100))
public val oldundo: ToggleOption = addOption(ToggleOption("oldundo", GLOBAL, "oldundo", false, isTemporary = true))
public val vimscriptFunctionAnnotation: ToggleOption = addOption(ToggleOption("vimscriptfunctionannotation", GLOBAL, "vimscriptfunctionannotation", true, isTemporary = true))
public val commandOrMotionAnnotation: ToggleOption = addOption(ToggleOption("commandormotionannotation", GLOBAL, "commandormotionannotation", true, isTemporary = true))
public val showmodewidget: ToggleOption = addOption(ToggleOption("showmodewidget", GLOBAL, "showmodewidget", false, isTemporary = true))
public val useNewRegex: ToggleOption = addOption(ToggleOption("usenewregex", GLOBAL, "usenewregex", true, isTemporary = true))
// Temporary feature flags during development, not really intended for external use
public val closenotebooks: ToggleOption = addOption(ToggleOption("closenotebooks", GLOBAL, "closenotebooks", true, isHidden = true))
public val commandOrMotionAnnotation: ToggleOption = addOption(ToggleOption("commandormotionannotation", GLOBAL, "commandormotionannotation", true, isHidden = true))
public val exCommandAnnotation: ToggleOption = addOption(ToggleOption("excommandannotation", GLOBAL, "excommandannotation", true, isHidden = true))
public val oldundo: ToggleOption = addOption(ToggleOption("oldundo", GLOBAL, "oldundo", true, isHidden = true))
public val unifyjumps: ToggleOption = addOption(ToggleOption("unifyjumps", GLOBAL, "unifyjumps", true, isHidden = true))
public val useNewRegex: ToggleOption = addOption(ToggleOption("usenewregex", GLOBAL, "usenewregex", true, isHidden = true))
public val vimscriptFunctionAnnotation: ToggleOption = addOption(ToggleOption("vimscriptfunctionannotation", GLOBAL, "vimscriptfunctionannotation", true, isHidden = true))
// This needs to be Option<out VimDataType> so that it can work with derived option types, such as NumberOption, which
// derives from Option<VimInt>

View File

@ -15,38 +15,38 @@ import com.maddyhome.idea.vim.statistic.VimscriptState
internal class IjStatisticsService : VimStatistics {
override fun logTrackedAction(actionId: String) {
ActionTracker.logTrackedAction(actionId)
ActionTracker.Util.logTrackedAction(actionId)
}
override fun logCopiedAction(actionId: String) {
ActionTracker.logCopiedAction(actionId)
ActionTracker.Util.logCopiedAction(actionId)
}
override fun setIfLoopUsed(value: Boolean) {
VimscriptState.isLoopUsed = value
VimscriptState.Util.isLoopUsed = value
}
override fun setIfMapExprUsed(value: Boolean) {
VimscriptState.isMapExprUsed = value
VimscriptState.Util.isMapExprUsed = value
}
override fun setIfFunctionCallUsed(value: Boolean) {
VimscriptState.isFunctionCallUsed = value
VimscriptState.Util.isFunctionCallUsed = value
}
override fun setIfFunctionDeclarationUsed(value: Boolean) {
VimscriptState.isFunctionDeclarationUsed = value
VimscriptState.Util.isFunctionDeclarationUsed = value
}
override fun setIfIfUsed(value: Boolean) {
VimscriptState.isIfUsed = value
VimscriptState.Util.isIfUsed = value
}
override fun addExtensionEnabledWithPlug(extension: String) {
VimscriptState.extensionsEnabledWithPlug.add(extension)
VimscriptState.Util.extensionsEnabledWithPlug.add(extension)
}
override fun addSourcedFile(path: String) {
VimscriptState.sourcedFiles.add(path)
VimscriptState.Util.sourcedFiles.add(path)
}
}

View File

@ -26,10 +26,12 @@ import com.maddyhome.idea.vim.EventFacade;
import com.maddyhome.idea.vim.VimPlugin;
import com.maddyhome.idea.vim.action.VimShortcutKeyAction;
import com.maddyhome.idea.vim.action.change.LazyVimCommand;
import com.maddyhome.idea.vim.api.*;
import com.maddyhome.idea.vim.api.NativeAction;
import com.maddyhome.idea.vim.api.VimEditor;
import com.maddyhome.idea.vim.api.VimInjectorKt;
import com.maddyhome.idea.vim.api.VimKeyGroupBase;
import com.maddyhome.idea.vim.command.MappingMode;
import com.maddyhome.idea.vim.ex.ExOutputModel;
import com.maddyhome.idea.vim.helper.HelperKt;
import com.maddyhome.idea.vim.key.*;
import com.maddyhome.idea.vim.newapi.IjNativeAction;
import com.maddyhome.idea.vim.newapi.IjVimEditor;
@ -99,9 +101,9 @@ public class KeyGroup extends VimKeyGroupBase implements PersistentStateComponen
@Override
public void updateShortcutKeysRegistration() {
for (Editor editor : HelperKt.localEditors()) {
unregisterShortcutKeys(new IjVimEditor(editor));
registerRequiredShortcutKeys(new IjVimEditor(editor));
for (VimEditor editor : injector.getEditorGroup().getEditors()) {
unregisterShortcutKeys(editor);
registerRequiredShortcutKeys(editor);
}
}
@ -228,7 +230,7 @@ public class KeyGroup extends VimKeyGroupBase implements PersistentStateComponen
private void registerRequiredShortcut(@NotNull List<KeyStroke> keys, MappingOwner owner) {
for (KeyStroke key : keys) {
if (key.getKeyChar() == KeyEvent.CHAR_UNDEFINED) {
if (!injector.getOptionGroup().getGlobalOptions().getOctopushandler() ||
if (!injector.getApplication().isOctopusEnabled() ||
!(key.getKeyCode() == KeyEvent.VK_ESCAPE && key.getModifiers() == 0) &&
!(key.getKeyCode() == KeyEvent.VK_ENTER && key.getModifiers() == 0)) {
getRequiredShortcutKeys().add(new RequiredShortcut(key, owner));

View File

@ -0,0 +1,68 @@
package com.maddyhome.idea.vim.group
import com.intellij.codeInsight.daemon.ReferenceImporter
import com.intellij.openapi.actionSystem.CommonDataKeys
import com.intellij.openapi.actionSystem.DataContext
import com.intellij.openapi.application.ApplicationManager
import com.intellij.openapi.application.ReadAction
import com.intellij.openapi.command.WriteCommandAction
import com.intellij.openapi.editor.Editor
import com.intellij.openapi.fileEditor.FileDocumentManager
import com.intellij.openapi.progress.ProgressIndicator
import com.intellij.openapi.progress.ProgressManager
import com.intellij.openapi.progress.Task
import com.intellij.psi.PsiDocumentManager
import com.intellij.psi.PsiElement
import com.intellij.psi.PsiRecursiveElementWalkingVisitor
import java.util.function.BooleanSupplier
internal object MacroAutoImport {
fun run(editor: Editor, dataContext: DataContext) {
val project = CommonDataKeys.PROJECT.getData(dataContext) ?: return
val file = PsiDocumentManager.getInstance(project).getPsiFile(editor.document) ?: return
if (!FileDocumentManager.getInstance().requestWriting(editor.document, project)) {
return
}
val importers = ReferenceImporter.EP_NAME.extensionList
if (importers.isEmpty()) {
return
}
ProgressManager.getInstance().run(object : Task.Backgroundable(project, "Auto import", true) {
override fun run(indicator: ProgressIndicator) {
val fixes = ReadAction.nonBlocking<List<BooleanSupplier>> {
val fixes = mutableListOf<BooleanSupplier>()
file.accept(object : PsiRecursiveElementWalkingVisitor() {
override fun visitElement(element: PsiElement) {
for (reference in element.references) {
if (reference.resolve() != null) {
continue
}
for (importer in importers) {
importer.computeAutoImportAtOffset(editor, file, element.textRange.startOffset, true)
?.let(fixes::add)
}
}
super.visitElement(element)
}
})
return@nonBlocking fixes
}.executeSynchronously()
ApplicationManager.getApplication().invokeAndWait {
WriteCommandAction.writeCommandAction(project)
.withName("Auto Import")
.withGroupId("IdeaVimAutoImportAfterMacro")
.shouldRecordActionForActiveDocument(true)
.run<RuntimeException> {
fixes.forEach { it.asBoolean }
}
}
}
})
}
}

View File

@ -10,6 +10,7 @@ package com.maddyhome.idea.vim.group
import com.intellij.codeInsight.completion.CompletionPhase
import com.intellij.codeInsight.completion.impl.CompletionServiceImpl
import com.intellij.openapi.application.ApplicationManager
import com.intellij.openapi.components.Service
import com.intellij.openapi.diagnostic.logger
import com.intellij.openapi.progress.ProcessCanceledException
import com.intellij.openapi.progress.ProgressManager
@ -21,10 +22,12 @@ import com.maddyhome.idea.vim.api.injector
import com.maddyhome.idea.vim.helper.MessageHelper.message
import com.maddyhome.idea.vim.macro.VimMacroBase
import com.maddyhome.idea.vim.newapi.IjVimEditor
import com.maddyhome.idea.vim.newapi.ij
/**
* Used to handle playback of macros
*/
@Service
internal class MacroGroup : VimMacroBase() {
// If it's null, this is the top macro (as in most cases). If it's not null, this macro is executed from top macro
@ -75,11 +78,12 @@ internal class MacroGroup : VimMacroBase() {
} catch (e: ProcessCanceledException) {
return@runnable
}
val keyHandler = getInstance()
ProgressManager.getInstance().executeNonCancelableSection {
// Prevent autocompletion during macros.
// See https://github.com/JetBrains/ideavim/pull/772 for details
CompletionServiceImpl.setCompletionPhase(CompletionPhase.NoCompletion)
getInstance().handleKey(editor, key, context)
keyHandler.handleKey(editor, key, context, keyHandler.keyHandlerState)
}
if (injector.messages.isError()) return@runnable
}
@ -90,6 +94,9 @@ internal class MacroGroup : VimMacroBase() {
} finally {
keyStack.removeFirst()
}
if (!isInternalMacro) {
MacroAutoImport.run(editor.ij, context.ij)
}
}
if (isInternalMacro) {

View File

@ -8,6 +8,7 @@
package com.maddyhome.idea.vim.group
import com.intellij.openapi.actionSystem.DataContext
import com.intellij.openapi.components.Service
import com.intellij.openapi.editor.Caret
import com.intellij.openapi.editor.Editor
import com.intellij.openapi.editor.LogicalPosition
@ -83,6 +84,7 @@ import kotlin.math.min
/**
* This handles all motion related commands and marks
*/
@Service
internal class MotionGroup : VimMotionGroupBase() {
override fun onAppCodeMovement(editor: VimEditor, caret: VimCaret, offset: Int, oldOffset: Int) {
AppCodeTemplates.onMovement(editor.ij, caret.ij, oldOffset < offset)

View File

@ -21,6 +21,7 @@ import com.intellij.openapi.actionSystem.ActionUpdateThread
import com.intellij.openapi.actionSystem.AnAction
import com.intellij.openapi.actionSystem.AnActionEvent
import com.intellij.openapi.actionSystem.KeyboardShortcut
import com.intellij.openapi.components.Service
import com.intellij.openapi.diagnostic.logger
import com.intellij.openapi.ide.CopyPasteManager
import com.intellij.openapi.keymap.KeymapUtil
@ -55,6 +56,7 @@ import javax.swing.KeyStroke
* This service is can be used as application level and as project level service.
* If project is null, this means that this is an application level service and notification will be shown for all projects
*/
@Service(Service.Level.PROJECT, Service.Level.APP)
internal class NotificationService(private val project: Project?) {
// This constructor is used to create an applicationService
@Suppress("unused")
@ -276,7 +278,7 @@ internal class NotificationService(private val project: Project?) {
}
if (id != null) {
ActionTracker.logTrackedAction(id)
ActionTracker.Util.logTrackedAction(id)
}
}
@ -284,7 +286,7 @@ internal class NotificationService(private val project: Project?) {
override fun actionPerformed(e: AnActionEvent) {
CopyPasteManager.getInstance().setContents(StringSelection(id ?: ""))
if (id != null) {
ActionTracker.logCopiedAction(id)
ActionTracker.Util.logCopiedAction(id)
}
notification?.expire()

View File

@ -20,6 +20,7 @@ import com.intellij.openapi.progress.ProgressManager
import com.intellij.util.execution.ParametersListUtil
import com.intellij.util.text.CharSequenceReader
import com.maddyhome.idea.vim.KeyHandler.Companion.getInstance
import com.maddyhome.idea.vim.KeyProcessResult
import com.maddyhome.idea.vim.VimPlugin
import com.maddyhome.idea.vim.api.ExecutionContext
import com.maddyhome.idea.vim.api.VimEditor
@ -37,7 +38,6 @@ import com.maddyhome.idea.vim.state.mode.Mode
import com.maddyhome.idea.vim.state.mode.Mode.NORMAL
import com.maddyhome.idea.vim.state.mode.Mode.VISUAL
import com.maddyhome.idea.vim.state.mode.ReturnableFromCmd
import com.maddyhome.idea.vim.state.mode.mode
import com.maddyhome.idea.vim.ui.ex.ExEntryPanel
import com.maddyhome.idea.vim.vimscript.model.CommandLineVimLContext
import java.io.BufferedWriter
@ -85,24 +85,27 @@ public class ProcessGroup : VimProcessGroupBase() {
modeBeforeCommandProcessing = currentMode
val initText = getRange(editor, cmd)
injector.markService.setVisualSelectionMarks(editor)
editor.vimStateMachine.mode = Mode.CMD_LINE(currentMode)
editor.mode = Mode.CMD_LINE(currentMode)
val panel = ExEntryPanel.getInstance()
panel.activate(editor.ij, context.ij, ":", initText, 1)
}
public override fun processExKey(editor: VimEditor, stroke: KeyStroke): Boolean {
public override fun processExKey(editor: VimEditor, stroke: KeyStroke, processResultBuilder: KeyProcessResult.KeyProcessResultBuilder): Boolean {
// This will only get called if somehow the key focus ended up in the editor while the ex entry window
// is open. So I'll put focus back in the editor and process the key.
val panel = ExEntryPanel.getInstance()
if (panel.isActive) {
processResultBuilder.addExecutionStep { _, _, _ ->
requestFocus(panel.entry)
panel.handleKey(stroke)
}
return true
} else {
getInstance(editor).mode = NORMAL()
getInstance().reset(editor)
processResultBuilder.addExecutionStep { _, lambdaEditor, _ ->
lambdaEditor.mode = NORMAL()
getInstance().reset(lambdaEditor)
}
return false
}
}
@ -112,7 +115,7 @@ public class ProcessGroup : VimProcessGroupBase() {
panel.deactivate(true)
var res = true
try {
getInstance(editor).mode = NORMAL()
editor.mode = NORMAL()
logger.debug("processing command")
@ -152,7 +155,7 @@ public class ProcessGroup : VimProcessGroupBase() {
}
public override fun cancelExEntry(editor: VimEditor, resetCaret: Boolean) {
editor.vimStateMachine.mode = NORMAL()
editor.mode = NORMAL()
getInstance().reset(editor)
val panel = ExEntryPanel.getInstance()
panel.deactivate(true, resetCaret)
@ -162,7 +165,7 @@ public class ProcessGroup : VimProcessGroupBase() {
val initText = getRange(editor, cmd) + "!"
val currentMode = editor.mode
check(currentMode is ReturnableFromCmd) { "Cannot enable cmd mode from $currentMode" }
editor.vimStateMachine.mode = Mode.CMD_LINE(currentMode)
editor.mode = Mode.CMD_LINE(currentMode)
val panel = ExEntryPanel.getInstance()
panel.activate(editor.ij, context.ij, ":", initText, 1)
}

View File

@ -14,9 +14,9 @@ import com.intellij.openapi.components.State;
import com.intellij.openapi.components.Storage;
import com.intellij.openapi.diagnostic.Logger;
import com.maddyhome.idea.vim.VimPlugin;
import com.maddyhome.idea.vim.state.mode.SelectionType;
import com.maddyhome.idea.vim.register.Register;
import com.maddyhome.idea.vim.register.VimRegisterGroupBase;
import com.maddyhome.idea.vim.state.mode.SelectionType;
import org.jdom.Element;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
@ -37,6 +37,10 @@ public class RegisterGroup extends VimRegisterGroupBase implements PersistentSta
private static final Logger logger = Logger.getInstance(RegisterGroup.class);
public RegisterGroup() {
this.initClipboardOptionListener();
}
public void saveData(final @NotNull Element element) {
logger.debug("Save registers data");
final Element registersElement = new Element("registers");

View File

@ -21,8 +21,6 @@ import com.intellij.openapi.editor.event.DocumentEvent;
import com.intellij.openapi.editor.event.DocumentListener;
import com.intellij.openapi.editor.markup.RangeHighlighter;
import com.intellij.openapi.fileEditor.FileEditorManagerEvent;
import com.intellij.openapi.project.Project;
import com.intellij.openapi.project.ProjectManager;
import com.intellij.openapi.util.Ref;
import com.maddyhome.idea.vim.VimPlugin;
import com.maddyhome.idea.vim.api.*;
@ -33,12 +31,11 @@ import com.maddyhome.idea.vim.ex.ExException;
import com.maddyhome.idea.vim.ex.ranges.LineRange;
import com.maddyhome.idea.vim.helper.*;
import com.maddyhome.idea.vim.history.HistoryConstants;
import com.maddyhome.idea.vim.newapi.IjEditorExecutionContext;
import com.maddyhome.idea.vim.newapi.IjVimCaret;
import com.maddyhome.idea.vim.newapi.IjVimEditor;
import com.maddyhome.idea.vim.newapi.IjVimSearchGroup;
import com.maddyhome.idea.vim.newapi.*;
import com.maddyhome.idea.vim.options.GlobalOptionChangeListener;
import com.maddyhome.idea.vim.regexp.*;
import com.maddyhome.idea.vim.regexp.CharPointer;
import com.maddyhome.idea.vim.regexp.CharacterClasses;
import com.maddyhome.idea.vim.regexp.RegExp;
import com.maddyhome.idea.vim.ui.ModalEntry;
import com.maddyhome.idea.vim.ui.ex.ExEntryPanel;
import com.maddyhome.idea.vim.vimscript.model.VimLContext;
@ -59,7 +56,6 @@ import java.text.ParsePosition;
import java.util.*;
import static com.maddyhome.idea.vim.api.VimInjectorKt.*;
import static com.maddyhome.idea.vim.helper.HelperKt.localEditors;
import static com.maddyhome.idea.vim.helper.SearchHelperKtKt.shouldIgnoreCase;
import static com.maddyhome.idea.vim.newapi.IjVimInjectorKt.globalIjOptions;
import static com.maddyhome.idea.vim.register.RegisterConstants.LAST_SEARCH_REGISTER;
@ -214,8 +210,8 @@ public class SearchGroup extends IjVimSearchGroup implements PersistentStateComp
* @param patternOffset The pattern offset, e.g. `/{pattern}/{offset}`
* @param direction The direction to search
*/
@TestOnly
public void setLastSearchState(@SuppressWarnings("unused") @NotNull Editor editor, @NotNull String pattern,
@Override
public void setLastSearchState(@SuppressWarnings("unused") @NotNull VimEditor editor, @NotNull String pattern,
@NotNull String patternOffset, Direction direction) {
if (globalIjOptions(injector).getUseNewRegex()) {
super.setLastSearchState(pattern, patternOffset, direction);
@ -1205,10 +1201,13 @@ public class SearchGroup extends IjVimSearchGroup implements PersistentStateComp
@Override
public void documentChanged(@NotNull DocumentEvent event) {
for (Project project : ProjectManager.getInstance().getOpenProjects()) {
// Loop over all local editors for the changed document, across all projects, and update search highlights.
// Note that the change may have come from a remote guest in Code With Me scenarios (in which case
// ClientId.current will be a guest ID), but we don't care - we still need to add/remove highlights for the
// changed text. Make sure we only update local editors, though.
final Document document = event.getDocument();
for (Editor editor : localEditors(document, project)) {
for (VimEditor vimEditor : injector.getEditorGroup().getEditors(new IjVimDocument(document))) {
final Editor editor = ((IjVimEditor)vimEditor).getEditor();
Collection<RangeHighlighter> hls = UserDataManager.getVimLastHighlighters(editor);
if (hls == null) {
continue;
@ -1228,7 +1227,8 @@ public class SearchGroup extends IjVimSearchGroup implements PersistentStateComp
final Iterator<RangeHighlighter> iter = hls.iterator();
while (iter.hasNext()) {
final RangeHighlighter highlighter = iter.next();
if (!highlighter.isValid() || (highlighter.getStartOffset() >= startLineOffset && highlighter.getEndOffset() <= endLineOffset)) {
if (!highlighter.isValid() ||
(highlighter.getStartOffset() >= startLineOffset && highlighter.getEndOffset() <= endLineOffset)) {
iter.remove();
editor.getMarkupModel().removeHighlighter(highlighter);
}
@ -1244,7 +1244,6 @@ public class SearchGroup extends IjVimSearchGroup implements PersistentStateComp
}
}
}
}
//endregion

View File

@ -111,7 +111,7 @@ internal class JumpsListener(val project: Project) : RecentPlacesListener {
}
private fun buildJump(place: PlaceInfo): Jump? {
val editor = injector.editorGroup.localEditors().firstOrNull { it.ij.virtualFile == place.file } ?: return null
val editor = injector.editorGroup.getEditors().firstOrNull { it.ij.virtualFile == place.file } ?: return null
val offset = place.caretPosition?.startOffset ?: return null
val bufferPosition = editor.offsetToBufferPosition(offset)

View File

@ -7,6 +7,7 @@
*/
package com.maddyhome.idea.vim.group
import com.intellij.codeWithMe.ClientId
import com.intellij.ide.bookmark.Bookmark
import com.intellij.ide.bookmark.BookmarkGroup
import com.intellij.ide.bookmark.BookmarksListener
@ -18,7 +19,7 @@ import com.intellij.openapi.components.State
import com.intellij.openapi.components.Storage
import com.intellij.openapi.diagnostic.Logger
import com.intellij.openapi.editor.Document
import com.intellij.openapi.editor.Editor
import com.intellij.openapi.editor.EditorFactory
import com.intellij.openapi.editor.event.DocumentEvent
import com.intellij.openapi.editor.event.DocumentListener
import com.intellij.openapi.fileEditor.FileEditorManager
@ -28,11 +29,11 @@ import com.intellij.openapi.util.text.StringUtil
import com.intellij.util.asSafely
import com.maddyhome.idea.vim.VimPlugin
import com.maddyhome.idea.vim.api.VimEditor
import com.maddyhome.idea.vim.api.VimEditorGroup
import com.maddyhome.idea.vim.api.VimMarkService
import com.maddyhome.idea.vim.api.VimMarkServiceBase
import com.maddyhome.idea.vim.api.injector
import com.maddyhome.idea.vim.group.SystemMarks.Companion.createOrGetSystemMark
import com.maddyhome.idea.vim.helper.localEditors
import com.maddyhome.idea.vim.mark.IntellijMark
import com.maddyhome.idea.vim.mark.Mark
import com.maddyhome.idea.vim.mark.VimMark.Companion.create
@ -193,6 +194,10 @@ internal class VimMarkServiceImpl : VimMarkServiceBase(), PersistentStateCompone
* This event indicates that a document is about to be changed. We use this event to update all the
* editor's marks if text is about to be deleted.
*
* Note that the event is fired for both local changes and changes from remote guests in Code With Me scenarios (in
* which case [ClientId.current] will be the remote client). We don't care who caused it, we just need to update the
* stored marks.
*
* @param event The change event
*/
override fun beforeDocumentChange(event: DocumentEvent) {
@ -200,15 +205,18 @@ internal class VimMarkServiceImpl : VimMarkServiceBase(), PersistentStateCompone
if (logger.isDebugEnabled) logger.debug("MarkUpdater before, event = $event")
if (event.oldLength == 0) return
val doc = event.document
val anEditor = getAnEditor(doc) ?: return
injector.markService
.updateMarksFromDelete(IjVimEditor(anEditor), event.offset, event.oldLength)
val anEditor = getAnyEditorForDocument(doc) ?: return
injector.markService.updateMarksFromDelete(anEditor, event.offset, event.oldLength)
}
/**
* This event indicates that a document was just changed. We use this event to update all the editor's
* marks if text was just added.
*
* Note that the event is fired for both local changes and changes from remote guests in Code With Me scenarios (in
* which case [ClientId.current] will be the remote client). We don't care who caused it, we just need to update the
* stored marks.
*
* @param event The change event
*/
override fun documentChanged(event: DocumentEvent) {
@ -216,19 +224,19 @@ internal class VimMarkServiceImpl : VimMarkServiceBase(), PersistentStateCompone
if (logger.isDebugEnabled) logger.debug("MarkUpdater after, event = $event")
if (event.newLength == 0 || event.newLength == 1 && event.newFragment[0] != '\n') return
val doc = event.document
val anEditor = getAnEditor(doc) ?: return
injector.markService
.updateMarksFromInsert(IjVimEditor(anEditor), event.offset, event.newLength)
val anEditor = getAnyEditorForDocument(doc) ?: return
injector.markService.updateMarksFromInsert(anEditor, event.offset, event.newLength)
}
private fun getAnEditor(doc: Document): Editor? {
val editors = localEditors(doc)
return if (editors.size > 0) {
editors[0]
} else {
null
}
}
/**
* Get any editor for the given document
*
* We need an editor to help calculate offsets for marks, and it doesn't matter which one we use, because they would
* all return the same results. However, we cannot use [VimEditorGroup.getEditors] because the change might have
* come from a remote guest and there might not be an open local editor.
*/
private fun getAnyEditorForDocument(doc: Document) =
EditorFactory.getInstance().getEditors(doc).firstOrNull()?.let { IjVimEditor(it) }
}
class VimBookmarksListener(private val myProject: Project) : BookmarksListener {

View File

@ -8,9 +8,11 @@
package com.maddyhome.idea.vim.group
import com.intellij.openapi.components.Service
import org.apache.commons.codec.binary.Base64
import org.jdom.Element
@Service
internal class XMLGroup {
/**
* Set the text of an XML element, safely encode it if needed.

View File

@ -14,6 +14,7 @@ import com.intellij.ide.DataManager
import com.intellij.ide.PasteProvider
import com.intellij.openapi.actionSystem.DataContext
import com.intellij.openapi.actionSystem.PlatformDataKeys
import com.intellij.openapi.components.Service
import com.intellij.openapi.editor.Caret
import com.intellij.openapi.editor.RangeMarker
import com.intellij.openapi.editor.ex.EditorEx
@ -51,6 +52,7 @@ import com.maddyhome.idea.vim.state.mode.isChar
import com.maddyhome.idea.vim.state.mode.isLine
import java.awt.datatransfer.DataFlavor
@Service
internal class PutGroup : VimPutBase() {
override fun getProviderForPasteViaIde(

View File

@ -20,9 +20,8 @@ import com.maddyhome.idea.vim.helper.exitVisualMode
import com.maddyhome.idea.vim.helper.hasVisualSelection
import com.maddyhome.idea.vim.helper.inInsertMode
import com.maddyhome.idea.vim.helper.inNormalMode
import com.maddyhome.idea.vim.helper.isIdeaVimDisabledHere
import com.maddyhome.idea.vim.helper.isTemplateActive
import com.maddyhome.idea.vim.helper.vimStateMachine
import com.maddyhome.idea.vim.helper.vimDisabled
import com.maddyhome.idea.vim.listener.VimListenerManager
import com.maddyhome.idea.vim.newapi.vim
import com.maddyhome.idea.vim.options.OptionConstants
@ -30,7 +29,6 @@ import com.maddyhome.idea.vim.state.mode.Mode
import com.maddyhome.idea.vim.state.mode.inNormalMode
import com.maddyhome.idea.vim.state.mode.inSelectMode
import com.maddyhome.idea.vim.state.mode.inVisualMode
import com.maddyhome.idea.vim.state.mode.mode
import com.maddyhome.idea.vim.vimscript.model.options.helpers.IdeaRefactorModeHelper
import com.maddyhome.idea.vim.vimscript.model.options.helpers.isIdeaRefactorModeKeep
import com.maddyhome.idea.vim.vimscript.model.options.helpers.isIdeaRefactorModeSelect
@ -55,9 +53,7 @@ internal object IdeaSelectionControl {
selectionSource: VimListenerManager.SelectionSource = VimListenerManager.SelectionSource.OTHER,
) {
VimVisualTimer.singleTask(editor.vim.mode) { initialMode ->
if (VimPlugin.isNotEnabled()) return@singleTask
if (editor.isIdeaVimDisabledHere) return@singleTask
if (vimDisabled(editor)) return@singleTask
logger.debug("Adjust non-vim selection. Source: $selectionSource, initialMode: $initialMode")
@ -79,7 +75,7 @@ internal object IdeaSelectionControl {
logger.debug("Some carets have selection. State before adjustment: ${editor.vim.mode}")
editor.vim.vimStateMachine.mode = Mode.NORMAL()
editor.vim.mode = Mode.NORMAL()
activateMode(editor, chooseSelectionMode(editor, selectionSource, true))
} else {

View File

@ -12,13 +12,13 @@ import com.intellij.openapi.editor.Caret
import com.intellij.openapi.editor.Editor
import com.maddyhome.idea.vim.api.getLineEndForOffset
import com.maddyhome.idea.vim.api.getLineStartForOffset
import com.maddyhome.idea.vim.state.mode.Mode
import com.maddyhome.idea.vim.state.mode.inBlockSelection
import com.maddyhome.idea.vim.helper.isEndAllowed
import com.maddyhome.idea.vim.helper.moveToInlayAwareOffset
import com.maddyhome.idea.vim.helper.vimSelectionStart
import com.maddyhome.idea.vim.newapi.IjVimEditor
import com.maddyhome.idea.vim.newapi.vim
import com.maddyhome.idea.vim.state.mode.Mode
import com.maddyhome.idea.vim.state.mode.inBlockSelection
internal fun moveCaretOneCharLeftFromSelectionEnd(editor: Editor, predictedMode: Mode) {
if (predictedMode !is Mode.VISUAL) {

View File

@ -13,10 +13,10 @@ import com.intellij.openapi.editor.Editor
import com.maddyhome.idea.vim.api.VimEditor
import com.maddyhome.idea.vim.api.VimVisualMotionGroupBase
import com.maddyhome.idea.vim.command.CommandState
import com.maddyhome.idea.vim.state.mode.SelectionType
import com.maddyhome.idea.vim.command.engine
import com.maddyhome.idea.vim.newapi.ij
import com.maddyhome.idea.vim.newapi.vim
import com.maddyhome.idea.vim.state.mode.SelectionType
/**
* @author Alex Plate

View File

@ -27,7 +27,6 @@ import com.intellij.openapi.util.UserDataHolder
import com.intellij.openapi.util.removeUserData
import com.maddyhome.idea.vim.KeyHandler
import com.maddyhome.idea.vim.VimPlugin
import com.maddyhome.idea.vim.api.globalOptions
import com.maddyhome.idea.vim.api.injector
import com.maddyhome.idea.vim.api.key
import com.maddyhome.idea.vim.group.IjOptionConstants
@ -39,7 +38,6 @@ import com.maddyhome.idea.vim.newapi.actionStartedFromVim
import com.maddyhome.idea.vim.newapi.globalIjOptions
import com.maddyhome.idea.vim.newapi.vim
import com.maddyhome.idea.vim.state.mode.Mode
import com.maddyhome.idea.vim.state.mode.mode
import java.awt.event.KeyEvent
import javax.swing.KeyStroke
@ -340,7 +338,8 @@ internal abstract class VimKeyHandler(nextHandler: EditorActionHandler?) : Octop
override fun executeHandler(editor: Editor, caret: Caret?, dataContext: DataContext?) {
val enterKey = key(key)
val context = injector.executionContextManager.onEditor(editor.vim, dataContext?.vim)
KeyHandler.getInstance().handleKey(editor.vim, enterKey, context)
val keyHandler = KeyHandler.getInstance()
keyHandler.handleKey(editor.vim, enterKey, context, keyHandler.keyHandlerState)
}
override fun isHandlerEnabled(editor: Editor, dataContext: DataContext?): Boolean {
@ -362,4 +361,4 @@ internal fun isOctopusEnabled(s: KeyStroke, editor: Editor): Boolean {
}
internal val enableOctopus: Boolean
get() = injector.globalOptions().octopushandler
get() = injector.application.isOctopusEnabled()

View File

@ -8,6 +8,7 @@
package com.maddyhome.idea.vim.helper
import com.intellij.openapi.application.ApplicationManager
import com.intellij.openapi.diagnostic.thisLogger
import com.intellij.openapi.editor.Caret
import com.intellij.openapi.editor.CaretVisualAttributes
@ -18,14 +19,17 @@ import com.maddyhome.idea.vim.VimPlugin
import com.maddyhome.idea.vim.api.VimEditor
import com.maddyhome.idea.vim.api.globalOptions
import com.maddyhome.idea.vim.api.injector
import com.maddyhome.idea.vim.common.IsReplaceCharListener
import com.maddyhome.idea.vim.common.ModeChangeListener
import com.maddyhome.idea.vim.newapi.IjVimEditor
import com.maddyhome.idea.vim.newapi.ij
import com.maddyhome.idea.vim.newapi.vim
import com.maddyhome.idea.vim.options.EffectiveOptionValueChangeListener
import com.maddyhome.idea.vim.options.helpers.GuiCursorMode
import com.maddyhome.idea.vim.options.helpers.GuiCursorOptionHelper
import com.maddyhome.idea.vim.options.helpers.GuiCursorType
import com.maddyhome.idea.vim.state.mode.Mode
import com.maddyhome.idea.vim.state.mode.inBlockSelection
import com.maddyhome.idea.vim.state.mode.mode
import org.jetbrains.annotations.TestOnly
import java.awt.Color
@ -85,8 +89,11 @@ private fun Editor.updatePrimaryCaretVisualAttributes() {
caretModel.primaryCaret.visualAttributes = AttributesCache.getCaretVisualAttributes(this)
// Make sure the caret is visible as soon as it's set. It might be invisible while blinking
// NOTE: At the moment, this causes project leak in tests
if (!ApplicationManager.getApplication().isUnitTestMode) {
(this as? EditorEx)?.setCaretVisible(true)
}
}
private fun Editor.updateSecondaryCaretsVisualAttributes() {
if (VimPlugin.isNotEnabled()) thisLogger().error("The caret attributes should not be updated if the IdeaVim is disabled")
@ -134,3 +141,31 @@ private object AttributesCache {
@TestOnly
internal fun getGuiCursorMode(editor: Editor) = editor.guicursorMode()
public class CaretVisualAttributesListener : IsReplaceCharListener, ModeChangeListener {
override fun isReplaceCharChanged(editor: VimEditor) {
updateCaretsVisual(editor)
}
override fun modeChanged(editor: VimEditor, oldMode: Mode) {
updateCaretsVisual(editor)
}
private fun updateCaretsVisual(editor: VimEditor) {
if (injector.globalOptions().ideaglobalmode) {
updateAllEditorsCaretsVisual()
} else {
val ijEditor = (editor as IjVimEditor).editor
ijEditor.updateCaretsVisualAttributes()
ijEditor.updateCaretsVisualPosition()
}
}
public fun updateAllEditorsCaretsVisual() {
injector.editorGroup.getEditors().forEach { editor ->
val ijEditor = (editor as IjVimEditor).editor
ijEditor.updateCaretsVisualAttributes()
ijEditor.updateCaretsVisualPosition()
}
}
}

View File

@ -20,7 +20,6 @@ import com.maddyhome.idea.vim.options.OptionAccessScope
import com.maddyhome.idea.vim.options.OptionConstants
import com.maddyhome.idea.vim.state.mode.Mode
import com.maddyhome.idea.vim.state.mode.inVisualMode
import com.maddyhome.idea.vim.state.mode.mode
internal val Mode.hasVisualSelection
get() = when (this) {

View File

@ -1,45 +0,0 @@
/*
* Copyright 2003-2023 The IdeaVim authors
*
* Use of this source code is governed by an MIT-style
* license that can be found in the LICENSE.txt file or at
* https://opensource.org/licenses/MIT.
*/
package com.maddyhome.idea.vim.helper
import com.intellij.openapi.editor.Document
import com.intellij.openapi.editor.event.DocumentListener
import com.intellij.openapi.util.Key
import com.maddyhome.idea.vim.EventFacade
import com.maddyhome.idea.vim.group.SearchGroup
import com.maddyhome.idea.vim.group.VimMarkServiceImpl
internal object DocumentManager {
private val docListeners = mutableSetOf<DocumentListener>()
private val LISTENER_MARKER = Key<String>("VimlistenerMarker")
init {
docListeners += VimMarkServiceImpl.MarkUpdater
docListeners += SearchGroup.DocumentSearchListener.INSTANCE
}
fun addListeners(doc: Document) {
val marker = doc.getUserData(LISTENER_MARKER)
if (marker != null) return
doc.putUserData(LISTENER_MARKER, "foo")
for (docListener in docListeners) {
EventFacade.getInstance().addDocumentListener(doc, docListener)
}
}
fun removeListeners(doc: Document) {
doc.getUserData(LISTENER_MARKER) ?: return
doc.putUserData(LISTENER_MARKER, null)
for (docListener in docListeners) {
EventFacade.getInstance().removeDocumentListener(doc, docListener)
}
}
}

View File

@ -17,6 +17,7 @@ import com.intellij.testFramework.LightVirtualFile;
import com.maddyhome.idea.vim.api.EngineEditorHelperKt;
import com.maddyhome.idea.vim.api.VimEditor;
import com.maddyhome.idea.vim.common.IndentConfig;
import com.maddyhome.idea.vim.newapi.IjVimDocument;
import com.maddyhome.idea.vim.newapi.IjVimEditor;
import com.maddyhome.idea.vim.ui.ex.ExEntryPanel;
import kotlin.Pair;
@ -29,6 +30,7 @@ import java.util.Collections;
import java.util.Comparator;
import java.util.List;
import static com.maddyhome.idea.vim.api.VimInjectorKt.injector;
import static java.lang.Integer.max;
import static java.lang.Integer.min;
@ -197,7 +199,7 @@ public class EditorHelper {
* @param file The virtual file get the editor for
* @return The matching editor or null if no match was found
*/
public static @Nullable Editor getEditor(final @Nullable VirtualFile file) {
public static @Nullable VimEditor getEditor(final @Nullable VirtualFile file) {
if (file == null) {
return null;
}
@ -206,12 +208,7 @@ public class EditorHelper {
if (doc == null) {
return null;
}
final List<Editor> editors = HelperKt.localEditors(doc);
if (editors.size() > 0) {
return editors.get(0);
}
return null;
return injector.getEditorGroup().getEditors(new IjVimDocument(doc)).stream().findFirst().orElse(null);
}
public static @NotNull String pad(final @NotNull Editor editor,
@ -329,7 +326,7 @@ public class EditorHelper {
final int offset = y - ((screenHeight - lineHeight) / lineHeight / 2 * lineHeight);
@NotNull final VimEditor editor1 = new IjVimEditor(editor);
final int lastVisualLine = EngineEditorHelperKt.getVisualLineCount(editor1) - 1;
final int lastVisualLine = EngineEditorHelperKt.getVisualLineCount(editor1) + editor.getSettings().getAdditionalLinesCount();
final int offsetForLastLineAtBottom = getOffsetToScrollVisualLineToBottomOfScreen(editor, lastVisualLine);
// For `zz`, we want to use virtual space and move any line, including the last one, to the middle of the screen.

View File

@ -12,13 +12,17 @@ package com.maddyhome.idea.vim.helper
import com.intellij.codeWithMe.ClientId
import com.intellij.openapi.editor.Caret
import com.intellij.openapi.editor.CaretState
import com.intellij.openapi.editor.Editor
import com.intellij.openapi.editor.ex.util.EditorUtil
import com.intellij.openapi.fileEditor.ex.FileEditorManagerEx
import com.intellij.util.ui.table.JBTableRowEditor
import com.maddyhome.idea.vim.api.StringListOptionValue
import com.maddyhome.idea.vim.api.injector
import com.maddyhome.idea.vim.group.IjOptionConstants
import com.maddyhome.idea.vim.newapi.globalIjOptions
import com.maddyhome.idea.vim.newapi.vim
import com.maddyhome.idea.vim.state.mode.inBlockSelection
import java.awt.Component
import javax.swing.JComponent
import javax.swing.JTable
@ -34,24 +38,26 @@ public val Editor.fileSize: Int
internal val Editor.isIdeaVimDisabledHere: Boolean
get() {
val ideaVimSupportValue = injector.globalIjOptions().ideavimsupport
return disabledInDialog ||
(!ClientId.isCurrentlyUnderLocalId) || // CWM-927
(!ideaVimSupportValue.contains(IjOptionConstants.ideavimsupport_singleline) && isDatabaseCell()) ||
(!ideaVimSupportValue.contains(IjOptionConstants.ideavimsupport_singleline) && isOneLineMode)
return (ideaVimDisabledInDialog(ideaVimSupportValue) && isInDialog()) ||
!ClientId.isCurrentlyUnderLocalId || // CWM-927
(ideaVimDisabledForSingleLine(ideaVimSupportValue) && isSingleLine())
}
private fun Editor.isDatabaseCell(): Boolean {
return isTableCellEditor(this.component)
private fun ideaVimDisabledInDialog(ideaVimSupportValue: StringListOptionValue): Boolean {
return !ideaVimSupportValue.contains(IjOptionConstants.ideavimsupport_dialog)
&& !ideaVimSupportValue.contains(IjOptionConstants.ideavimsupport_dialoglegacy)
}
private val Editor.disabledInDialog: Boolean
get() {
val ideaVimSupportValue = injector.globalIjOptions().ideavimsupport
return (
!ideaVimSupportValue.contains(IjOptionConstants.ideavimsupport_dialog) &&
!ideaVimSupportValue.contains(IjOptionConstants.ideavimsupport_dialoglegacy)
) &&
(!this.isPrimaryEditor() && !EditorHelper.isFileEditor(this))
private fun ideaVimDisabledForSingleLine(ideaVimSupportValue: StringListOptionValue): Boolean {
return !ideaVimSupportValue.contains(IjOptionConstants.ideavimsupport_singleline)
}
private fun Editor.isInDialog(): Boolean {
return !this.isPrimaryEditor() && !EditorHelper.isFileEditor(this)
}
private fun Editor.isSingleLine(): Boolean {
return isTableCellEditor(this.component) || isOneLineMode
}
/**
@ -93,3 +99,41 @@ internal val Caret.vimLine: Int
*/
internal val Editor.vimLine: Int
get() = this.caretModel.currentCaret.vimLine
internal inline fun Editor.runWithEveryCaretAndRestore(action: () -> Unit) {
val caretModel = this.caretModel
val carets = if (this.vim.inBlockSelection) null else caretModel.allCarets
if (carets == null || carets.size == 1) {
action()
}
else {
var initialDocumentSize = this.document.textLength
var documentSizeDifference = 0
val caretOffsets = carets.map { it.selectionStart to it.selectionEnd }
val restoredCarets = mutableListOf<CaretState>()
caretModel.removeSecondaryCarets()
for ((selectionStart, selectionEnd) in caretOffsets) {
if (selectionStart == selectionEnd) {
caretModel.primaryCaret.moveToOffset(selectionStart + documentSizeDifference)
}
else {
caretModel.primaryCaret.setSelection(
selectionStart + documentSizeDifference,
selectionEnd + documentSizeDifference
)
}
action()
restoredCarets.add(caretModel.caretsAndSelections.single())
val documentLength = this.document.textLength
documentSizeDifference += documentLength - initialDocumentSize
initialDocumentSize = documentLength
}
caretModel.caretsAndSelections = restoredCarets
}
}

View File

@ -9,19 +9,12 @@
package com.maddyhome.idea.vim.helper
import com.intellij.codeInsight.template.TemplateManager
import com.intellij.codeWithMe.ClientId
import com.intellij.injected.editor.EditorWindow
import com.intellij.openapi.editor.Caret
import com.intellij.openapi.editor.ClientEditorManager
import com.intellij.openapi.editor.Document
import com.intellij.openapi.editor.Editor
import com.intellij.openapi.editor.EditorFactory
import com.intellij.openapi.project.Project
import com.intellij.openapi.util.Key
import com.maddyhome.idea.vim.VimPlugin
import com.maddyhome.idea.vim.newapi.vim
import com.maddyhome.idea.vim.state.mode.inBlockSelection
import java.util.stream.Collectors
internal fun <T : Comparable<T>> sort(a: T, b: T) = if (a > b) b to a else a to b
@ -36,34 +29,6 @@ internal inline fun Editor.vimForEachCaret(action: (caret: Caret) -> Unit) {
internal fun Editor.getTopLevelEditor() = if (this is EditorWindow) this.delegate else this
/**
* Return list of editors for local host (for code with me plugin)
*/
public fun localEditors(): List<Editor> {
return ClientEditorManager.getCurrentInstance().editors().collect(Collectors.toList())
}
public fun localEditors(doc: Document): List<Editor> {
return EditorFactory.getInstance().getEditors(doc)
.filter { editor -> editor.editorClientId.let { it == null || it == ClientId.currentOrNull } }
}
public fun localEditors(doc: Document, project: Project): List<Editor> {
return EditorFactory.getInstance().getEditors(doc, project)
.filter { editor -> editor.editorClientId.let { it == null || it == ClientId.currentOrNull } }
}
private val Editor.editorClientId: ClientId?
get() {
if (editorClientKey == null) {
@Suppress("DEPRECATION")
editorClientKey = Key.findKeyByName("editorClientIdby userData()") ?: return null
}
return editorClientKey?.let { this.getUserData(it) as? ClientId }
}
private var editorClientKey: Key<*>? = null
@Suppress("IncorrectParentDisposable")
internal fun Editor.isTemplateActive(): Boolean {
val project = this.project ?: return false

View File

@ -90,7 +90,8 @@ internal class IjActionExecutor : VimActionExecutor {
// because rider use async update method. See VIM-1819.
// This method executes inside of lastUpdateAndCheckDumb
// Another related issue: VIM-2604
if (!ActionUtil.lastUpdateAndCheckDumb(ijAction, event, false)) return false
ijAction.beforeActionPerformedUpdate(event)
if (!event.presentation.isEnabled) return false
if (ijAction is ActionGroup && !event.presentation.isPerformGroup) {
// Some ActionGroups should not be performed, but shown as a popup
val popup = JBPopupFactory.getInstance()

View File

@ -34,15 +34,15 @@ internal fun Editor.exitSelectMode(adjustCaretPosition: Boolean) {
val returnTo = this.vim.vimStateMachine.mode.returnTo
when (returnTo) {
ReturnTo.INSERT -> {
this.vim.vimStateMachine.mode = Mode.INSERT
this.vim.mode = Mode.INSERT
}
ReturnTo.REPLACE -> {
this.vim.vimStateMachine.mode = Mode.REPLACE
this.vim.mode = Mode.REPLACE
}
null -> {
this.vim.vimStateMachine.mode = Mode.NORMAL()
this.vim.mode = Mode.NORMAL()
}
}
SelectionVimListenerSuppressor.lock().use {
@ -67,15 +67,15 @@ internal fun VimEditor.exitSelectMode(adjustCaretPosition: Boolean) {
val returnTo = this.vimStateMachine.mode.returnTo
when (returnTo) {
ReturnTo.INSERT -> {
this.vimStateMachine.mode = Mode.INSERT
this.mode = Mode.INSERT
}
ReturnTo.REPLACE -> {
this.vimStateMachine.mode = Mode.REPLACE
this.mode = Mode.REPLACE
}
null -> {
this.vimStateMachine.mode = Mode.NORMAL()
this.mode = Mode.NORMAL()
}
}
SelectionVimListenerSuppressor.lock().use {

View File

@ -14,7 +14,6 @@ import com.maddyhome.idea.vim.api.injector
import com.maddyhome.idea.vim.api.normalizeVisualColumn
import com.maddyhome.idea.vim.api.options
import com.maddyhome.idea.vim.command.CommandFlags
import com.maddyhome.idea.vim.state.VimStateMachine
import com.maddyhome.idea.vim.helper.EditorHelper.getApproximateScreenHeight
import com.maddyhome.idea.vim.helper.EditorHelper.getApproximateScreenWidth
import com.maddyhome.idea.vim.helper.EditorHelper.getNonNormalizedVisualLineAtBottomOfScreen
@ -29,6 +28,7 @@ import com.maddyhome.idea.vim.helper.EditorHelper.scrollVisualLineToBottomOfScre
import com.maddyhome.idea.vim.helper.EditorHelper.scrollVisualLineToMiddleOfScreen
import com.maddyhome.idea.vim.helper.EditorHelper.scrollVisualLineToTopOfScreen
import com.maddyhome.idea.vim.newapi.vim
import com.maddyhome.idea.vim.state.VimStateMachine
import kotlin.math.max
import kotlin.math.min
import kotlin.math.roundToInt
@ -56,7 +56,7 @@ internal object ScrollViewHelper {
// that this needs to be replaced as a more or less dumb line for line rewrite.
val topLine = getVisualLineAtTopOfScreen(editor)
val bottomLine = getVisualLineAtBottomOfScreen(editor)
val lastLine = vimEditor.getVisualLineCount() - 1
val lastLine = vimEditor.getVisualLineCount() + editor.settings.additionalLinesCount
// We need the non-normalised value here, so we can handle cases such as so=999 to keep the current line centred
val scrollOffset = injector.options(vimEditor).scrolloff

View File

@ -9,6 +9,7 @@
package com.maddyhome.idea.vim.helper;
import com.google.common.collect.Lists;
import com.intellij.codeInsight.daemon.impl.DaemonCodeAnalyzerEx;
import com.intellij.lang.CodeDocumentationAwareCommenter;
import com.intellij.lang.Commenter;
import com.intellij.lang.Language;
@ -16,22 +17,28 @@ import com.intellij.lang.LanguageCommenters;
import com.intellij.openapi.diagnostic.Logger;
import com.intellij.openapi.editor.Caret;
import com.intellij.openapi.editor.Editor;
import com.intellij.openapi.project.Project;
import com.intellij.psi.PsiComment;
import com.intellij.psi.PsiElement;
import com.intellij.psi.PsiFile;
import com.intellij.psi.util.PsiTreeUtil;
import com.intellij.spellchecker.SpellCheckerSeveritiesProvider;
import com.maddyhome.idea.vim.VimPlugin;
import com.maddyhome.idea.vim.api.EngineEditorHelperKt;
import com.maddyhome.idea.vim.api.VimEditor;
import com.maddyhome.idea.vim.regexp.*;
import com.maddyhome.idea.vim.regexp.match.VimMatchResult;
import com.maddyhome.idea.vim.state.mode.Mode;
import com.maddyhome.idea.vim.state.VimStateMachine;
import com.maddyhome.idea.vim.common.CharacterPosition;
import com.maddyhome.idea.vim.common.Direction;
import com.maddyhome.idea.vim.common.TextRange;
import com.maddyhome.idea.vim.newapi.IjVimCaret;
import com.maddyhome.idea.vim.newapi.IjVimEditor;
import com.maddyhome.idea.vim.regexp.*;
import com.maddyhome.idea.vim.regexp.match.VimMatchResult;
import com.maddyhome.idea.vim.state.VimStateMachine;
import com.maddyhome.idea.vim.state.mode.Mode;
import it.unimi.dsi.fastutil.ints.IntComparator;
import it.unimi.dsi.fastutil.ints.IntIterator;
import it.unimi.dsi.fastutil.ints.IntRBTreeSet;
import it.unimi.dsi.fastutil.ints.IntSortedSet;
import kotlin.Pair;
import org.jetbrains.annotations.Contract;
import org.jetbrains.annotations.NotNull;
@ -1573,6 +1580,48 @@ public class SearchHelper {
return PsiHelper.findMethodEnd(editor, caret.getOffset(), count);
}
public static int findMisspelledWords(@NotNull Editor editor,
int startOffset,
int endOffset,
int skipCount,
IntComparator offsetOrdering) {
Project project = editor.getProject();
if (project == null) {
return -1;
}
IntSortedSet offsets = new IntRBTreeSet(offsetOrdering);
DaemonCodeAnalyzerEx.processHighlights(editor.getDocument(), project, SpellCheckerSeveritiesProvider.TYPO,
startOffset, endOffset, highlight -> {
if (highlight.getSeverity() == SpellCheckerSeveritiesProvider.TYPO) {
int offset = highlight.getStartOffset();
if (offset >= startOffset && offset <= endOffset) {
offsets.add(offset);
}
}
return true;
});
if (offsets.isEmpty()) {
return -1;
}
if (skipCount >= offsets.size()) {
return offsets.lastInt();
}
else {
IntIterator offsetIterator = offsets.iterator();
skip(offsetIterator, skipCount);
return offsetIterator.nextInt();
}
}
private static void skip(IntIterator iterator, final int n) {
if (n < 0) throw new IllegalArgumentException("Argument must be nonnegative: " + n);
int i = n;
while (i-- != 0 && iterator.hasNext()) iterator.nextInt();
}
private static @NotNull String parseMatchPairsOption(final VimEditor vimEditor) {
List<String> pairs = options(injector, vimEditor).getMatchpairs();
StringBuilder res = new StringBuilder();

View File

@ -26,6 +26,7 @@ import com.maddyhome.idea.vim.api.injector
import com.maddyhome.idea.vim.api.options
import com.maddyhome.idea.vim.common.TextRange
import com.maddyhome.idea.vim.ex.ranges.LineRange
import com.maddyhome.idea.vim.newapi.IjVimDocument
import com.maddyhome.idea.vim.newapi.IjVimEditor
import com.maddyhome.idea.vim.newapi.vim
import org.jetbrains.annotations.Contract
@ -86,11 +87,24 @@ private fun updateSearchHighlights(
): Int {
var currentMatchOffset = -1
val projectManager = ProjectManager.getInstanceIfCreated() ?: return currentMatchOffset
// TODO: This implementation needs rethinking
// It's a bit weird that we update search highlights across all open projects. It would make more sense to treat top
// level project frame windows as separate applications, but we can't do this because IdeaVim does not maintain state
// per-project.
// So, to be clear, this will loop over each project, and therefore, for each project top-level frame, will update
// search highlights in all editors for the document of the currently selected editor. It does not update highlights
// for editors for the document that are in other projects.
for (project in projectManager.openProjects) {
val current = FileEditorManager.getInstance(project).selectedTextEditor ?: continue
// [VERSION UPDATE] 202+ Use editors
val editors = localEditors(current.document, project)
for (editor in editors) {
val editors = injector.editorGroup.getEditors(IjVimDocument(current.document))
for (vimEditor in editors) {
val editor = (vimEditor as IjVimEditor).editor
if (editor.project != project) {
continue
}
// Try to keep existing highlights if possible. Update if hlsearch has changed or if the pattern has changed.
// Force update for the situations where the text is the same, but the ignore case values have changed.
// E.g. Use `*` to search for a word (which ignores smartcase), then use `/<Up>` to search for the same pattern,

View File

@ -31,4 +31,8 @@ public object StringHelper {
return Arrays.stream(string).flatMap { o: String -> injector.parser.parseKeys(o).stream() }
.collect(Collectors.toList())
}
@JvmStatic
@Deprecated("Use key.isCloseKeyStroke()", ReplaceWith("key.isCloseKeyStroke()"))
public fun isCloseKeyStroke(key: KeyStroke): Boolean = key.isCloseKeyStroke()
}

View File

@ -14,6 +14,7 @@ import com.intellij.openapi.command.CommandProcessor
import com.intellij.openapi.command.undo.UndoManager
import com.intellij.openapi.components.Service
import com.intellij.openapi.fileEditor.impl.text.TextEditorProvider
import com.maddyhome.idea.vim.VimPlugin
import com.maddyhome.idea.vim.api.ExecutionContext
import com.maddyhome.idea.vim.api.VimEditor
import com.maddyhome.idea.vim.api.injector
@ -21,6 +22,8 @@ import com.maddyhome.idea.vim.common.ChangesListener
import com.maddyhome.idea.vim.listener.SelectionVimListenerSuppressor
import com.maddyhome.idea.vim.newapi.globalIjOptions
import com.maddyhome.idea.vim.newapi.ij
import com.maddyhome.idea.vim.state.mode.SelectionType
import com.maddyhome.idea.vim.state.mode.inVisualMode
import com.maddyhome.idea.vim.undo.UndoRedoBase
/**
@ -39,6 +42,7 @@ internal class UndoRedoHelper : UndoRedoBase() {
if (injector.globalIjOptions().oldundo) {
SelectionVimListenerSuppressor.lock().use { undoManager.undo(fileEditor) }
restoreVisualMode(editor)
} else {
// TODO refactor me after VIM-308 when restoring selection and caret movement will be ignored by undo
editor.runWithChangeTracking {
@ -74,6 +78,7 @@ internal class UndoRedoHelper : UndoRedoBase() {
if (undoManager.isRedoAvailable(fileEditor)) {
if (injector.globalIjOptions().oldundo) {
SelectionVimListenerSuppressor.lock().use { undoManager.redo(fileEditor) }
restoreVisualMode(editor)
} else {
undoManager.redo(fileEditor)
CommandProcessor.getInstance().runUndoTransparentAction {
@ -131,4 +136,21 @@ internal class UndoRedoHelper : UndoRedoBase() {
val hasChanges: Boolean
get() = changeListener.hasChanged || initialPath != editor.getPath()
}
private fun restoreVisualMode(editor: VimEditor) {
if (!editor.inVisualMode && editor.getSelectionModel().hasSelection()) {
val detectedMode = VimPlugin.getVisualMotion().autodetectVisualSubmode(editor)
// Visual block selection is restored into multiple carets, so multi-carets that form a block are always
// identified as visual block mode, leading to false positives.
// Since I use visual block mode much less often than multi-carets, this is a judgment call to never restore
// visual block mode.
val wantedMode = if (detectedMode == SelectionType.BLOCK_WISE)
SelectionType.CHARACTER_WISE
else
detectedMode
VimPlugin.getVisualMotion().enterVisualMode(editor, wantedMode)
}
}
}

View File

@ -21,13 +21,13 @@ import com.intellij.openapi.util.UserDataHolder
import com.maddyhome.idea.vim.api.CaretRegisterStorageBase
import com.maddyhome.idea.vim.api.LocalMarkStorage
import com.maddyhome.idea.vim.api.SelectionInfo
import com.maddyhome.idea.vim.state.mode.Mode
import com.maddyhome.idea.vim.state.mode.SelectionType
import com.maddyhome.idea.vim.state.VimStateMachine
import com.maddyhome.idea.vim.ex.ExOutputModel
import com.maddyhome.idea.vim.group.visual.VisualChange
import com.maddyhome.idea.vim.group.visual.vimLeadSelectionOffset
import com.maddyhome.idea.vim.newapi.vim
import com.maddyhome.idea.vim.state.VimStateMachine
import com.maddyhome.idea.vim.state.mode.Mode
import com.maddyhome.idea.vim.state.mode.SelectionType
import com.maddyhome.idea.vim.ui.ExOutputPanel
import kotlin.properties.ReadWriteProperty
import kotlin.reflect.KProperty
@ -99,6 +99,8 @@ internal var Caret.registerStorage: CaretRegisterStorageBase? by userDataCaretTo
internal var Caret.markStorage: LocalMarkStorage? by userDataCaretToEditor()
internal var Caret.lastSelectionInfo: SelectionInfo? by userDataCaretToEditor()
internal var Editor.vimInitialised: Boolean by userDataOr { false }
// ------------------ Editor
internal fun unInitializeEditor(editor: Editor) {
editor.vimLastSelectionType = null
@ -106,6 +108,7 @@ internal fun unInitializeEditor(editor: Editor) {
editor.vimMorePanel = null
editor.vimExOutput = null
editor.vimLastHighlighters = null
editor.vimInitialised = false
}
internal var Editor.vimLastSearch: String? by userData()

View File

@ -1,32 +0,0 @@
/*
* Copyright 2003-2023 The IdeaVim authors
*
* Use of this source code is governed by an MIT-style
* license that can be found in the LICENSE.txt file or at
* https://opensource.org/licenses/MIT.
*/
package com.maddyhome.idea.vim.helper
import com.intellij.ide.plugins.StandalonePluginUpdateChecker
import com.intellij.openapi.components.Service
import com.intellij.openapi.components.service
import com.maddyhome.idea.vim.VimPlugin
import com.maddyhome.idea.vim.group.NotificationService
import com.maddyhome.idea.vim.icons.VimIcons
@Service(Service.Level.APP)
internal class VimStandalonePluginUpdateChecker : StandalonePluginUpdateChecker(
VimPlugin.getPluginId(),
updateTimestampProperty = PROPERTY_NAME,
NotificationService.IDEAVIM_STICKY_GROUP,
VimIcons.IDEAVIM,
) {
override fun skipUpdateCheck(): Boolean = VimPlugin.isNotEnabled() || "dev" in VimPlugin.getVersion()
companion object {
private const val PROPERTY_NAME = "ideavim.statistics.timestamp"
val instance: VimStandalonePluginUpdateChecker = service()
}
}

View File

@ -21,6 +21,6 @@ public final class VimIcons {
public static final @NotNull Icon YOUTRACK = load("/icons/youtrack.svg");
private static @NotNull Icon load(@NotNull @NonNls String path) {
return IconManager.getInstance().getIcon(path, VimIcons.class);
return IconManager.getInstance().getIcon(path, VimIcons.class.getClassLoader());
}
}

View File

@ -9,11 +9,19 @@
package com.maddyhome.idea.vim.inspections
import com.intellij.codeInspection.LocalInspectionTool
import com.intellij.codeInspection.LocalQuickFix
import com.intellij.codeInspection.ProblemDescriptor
import com.intellij.codeInspection.ProblemsHolder
import com.intellij.openapi.project.Project
import com.intellij.openapi.util.TextRange
import com.intellij.psi.PsiElement
import com.intellij.psi.PsiElementVisitor
import com.intellij.psi.impl.source.tree.LeafPsiElement
import com.intellij.psi.util.PsiEditorUtil
import com.maddyhome.idea.vim.extension.ExtensionBeanClass
import com.maddyhome.idea.vim.extension.VimExtension
import com.maddyhome.idea.vim.vimscript.model.commands.SetCommand
import com.maddyhome.idea.vim.vimscript.parser.VimscriptParser
internal class UsePlugSyntaxInspection : LocalInspectionTool() {
override fun getGroupDisplayName(): String {
@ -23,11 +31,54 @@ internal class UsePlugSyntaxInspection : LocalInspectionTool() {
override fun buildVisitor(holder: ProblemsHolder, isOnTheFly: Boolean): PsiElementVisitor {
val file = holder.file
if (file.name != ".ideavimrc" && file.name != "_ideavimrc") return PsiElementVisitor.EMPTY_VISITOR
val plugins = buildPlugins()
return object : PsiElementVisitor() {
override fun visitElement(element: PsiElement) {
if (element !is LeafPsiElement) return
holder.registerProblem(element, TextRange.create(10, 20), "Hi there")
val myScript = VimscriptParser.parse(element.text)
myScript.units.forEach { unit ->
if (unit is SetCommand) {
val argument = unit.argument
val alias = plugins[argument]
if (alias != null) {
holder.registerProblem(
element,
unit.rangeInScript.let { TextRange(it.startOffset, it.endOffset - 1) },
"""
Use `Plug` syntax for defining extensions
""".trimIndent(),
object : LocalQuickFix {
override fun getFamilyName(): String {
return "Use Plug syntax"
}
override fun applyFix(p0: Project, p1: ProblemDescriptor) {
val editor = PsiEditorUtil.findEditor(file)
editor?.document?.replaceString(
unit.rangeInScript.startOffset,
unit.rangeInScript.endOffset - 1,
"Plug '$alias'"
)
}
}
)
}
}
}
}
}
}
private fun buildPlugins(): HashMap<String, String> {
val res = HashMap<String, String>()
VimExtension.EP_NAME.extensions.forEach { extension: ExtensionBeanClass ->
val alias = extension.aliases?.first { it.name?.count { it == '/' } == 1 }?.name
?: extension.aliases?.firstOrNull()?.name
val name = extension.name
if (alias != null && name != null) {
res[name] = alias
}
}
return res
}
}

View File

@ -8,7 +8,7 @@
package com.maddyhome.idea.vim.listener
import com.intellij.openapi.components.ServiceManager
import com.intellij.openapi.application.ApplicationManager
import com.intellij.openapi.editor.Editor
import org.acejump.session.SessionManager
@ -16,12 +16,11 @@ import org.acejump.session.SessionManager
* Key handling for IdeaVim should be updated to editorHandler usage. In this case this class can be safely removed.
*/
@Suppress("DEPRECATION")
internal interface AceJumpService {
fun isActive(editor: Editor): Boolean
companion object {
fun getInstance(): AceJumpService? = ServiceManager.getService(AceJumpService::class.java)
fun getInstance(): AceJumpService? = ApplicationManager.getApplication().getService(AceJumpService::class.java)
}
}

View File

@ -27,6 +27,7 @@ import com.intellij.openapi.actionSystem.CommonDataKeys
import com.intellij.openapi.actionSystem.ex.AnActionListener
import com.intellij.openapi.actionSystem.impl.ProxyShortcutSet
import com.intellij.openapi.editor.Editor
import com.intellij.openapi.editor.impl.ScrollingModelImpl
import com.intellij.openapi.project.DumbAwareToggleAction
import com.intellij.openapi.util.TextRange
import com.maddyhome.idea.vim.KeyHandler
@ -56,6 +57,7 @@ internal object IdeaSpecifics {
private val surrounderAction =
"com.intellij.codeInsight.generation.surroundWith.SurroundWithHandler\$InvokeSurrounderAction"
private var editor: Editor? = null
private var caretOffset = -1
private var completionPrevDocumentLength: Int? = null
private var completionPrevDocumentOffset: Int? = null
override fun beforeActionPerformed(action: AnAction, event: AnActionEvent) {
@ -64,6 +66,7 @@ internal object IdeaSpecifics {
val hostEditor = event.dataContext.getData(CommonDataKeys.HOST_EDITOR)
if (hostEditor != null) {
editor = hostEditor
caretOffset = hostEditor.caretModel.offset
}
val isVimAction = (action as? AnActionWrapper)?.delegate is VimShortcutKeyAction
@ -74,7 +77,7 @@ internal object IdeaSpecifics {
}
}
if (hostEditor != null && action is ChooseItemAction && hostEditor.vimStateMachine?.isRecording == true) {
if (hostEditor != null && action is ChooseItemAction && injector.registerGroup.isRecording) {
val lookup = LookupManager.getActiveLookup(hostEditor)
if (lookup != null) {
val charsToRemove = hostEditor.caretModel.primaryCaret.offset - lookup.lookupStart
@ -95,7 +98,8 @@ internal object IdeaSpecifics {
if (VimPlugin.isNotEnabled()) return
val editor = editor
if (editor != null && action is ChooseItemAction && editor.vimStateMachine?.isRecording == true) {
if (editor != null) {
if (action is ChooseItemAction && injector.registerGroup.isRecording) {
val prevDocumentLength = completionPrevDocumentLength
val prevDocumentOffset = completionPrevDocumentOffset
@ -124,14 +128,28 @@ internal object IdeaSpecifics {
) {
editor?.let {
val commandState = it.vim.vimStateMachine
commandState.mode = Mode.NORMAL()
it.vim.mode = Mode.NORMAL()
VimPlugin.getChange().insertBeforeCursor(it.vim, event.dataContext.vim)
KeyHandler.getInstance().reset(it.vim)
}
}
//endregion
if (caretOffset != -1 && caretOffset != editor.caretModel.offset) {
val scrollModel = editor.scrollingModel as ScrollingModelImpl
if (scrollModel.isScrollingNow) {
val v = scrollModel.verticalScrollOffset
val h = scrollModel.horizontalScrollOffset
scrollModel.finishAnimation()
scrollModel.scroll(h, v)
scrollModel.finishAnimation()
}
injector.scroll.scrollCaretIntoView(editor.vim)
}
}
this.editor = null
this.caretOffset = -1
}
}

View File

@ -8,6 +8,7 @@
package com.maddyhome.idea.vim.listener
import com.intellij.codeWithMe.ClientId
import com.intellij.ide.ui.UISettings
import com.intellij.openapi.Disposable
import com.intellij.openapi.application.ApplicationManager
@ -20,6 +21,8 @@ import com.intellij.openapi.editor.EditorKind
import com.intellij.openapi.editor.actionSystem.TypedAction
import com.intellij.openapi.editor.event.CaretEvent
import com.intellij.openapi.editor.event.CaretListener
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.editor.event.EditorFactoryListener
import com.intellij.openapi.editor.event.EditorMouseEvent
@ -49,6 +52,7 @@ import com.intellij.openapi.vfs.VirtualFile
import com.intellij.util.ExceptionUtil
import com.maddyhome.idea.vim.EventFacade
import com.maddyhome.idea.vim.KeyHandler
import com.maddyhome.idea.vim.KeyHandlerStateResetter
import com.maddyhome.idea.vim.VimKeyListener
import com.maddyhome.idea.vim.VimPlugin
import com.maddyhome.idea.vim.VimTypedActionHandler
@ -62,40 +66,41 @@ import com.maddyhome.idea.vim.api.injector
import com.maddyhome.idea.vim.ex.ExOutputModel
import com.maddyhome.idea.vim.group.EditorGroup
import com.maddyhome.idea.vim.group.FileGroup
import com.maddyhome.idea.vim.group.IjOptions
import com.maddyhome.idea.vim.group.MotionGroup
import com.maddyhome.idea.vim.group.OptionGroup
import com.maddyhome.idea.vim.group.ScrollGroup
import com.maddyhome.idea.vim.group.SearchGroup
import com.maddyhome.idea.vim.group.VimMarkServiceImpl
import com.maddyhome.idea.vim.group.visual.IdeaSelectionControl
import com.maddyhome.idea.vim.group.visual.VimVisualTimer
import com.maddyhome.idea.vim.group.visual.moveCaretOneCharLeftFromSelectionEnd
import com.maddyhome.idea.vim.handler.correctorRequester
import com.maddyhome.idea.vim.handler.keyCheckRequests
import com.maddyhome.idea.vim.helper.CaretVisualAttributesListener
import com.maddyhome.idea.vim.helper.GuicursorChangeListener
import com.maddyhome.idea.vim.helper.StrictMode
import com.maddyhome.idea.vim.helper.VimStandalonePluginUpdateChecker
import com.maddyhome.idea.vim.helper.exitSelectMode
import com.maddyhome.idea.vim.helper.exitVisualMode
import com.maddyhome.idea.vim.helper.forceBarCursor
import com.maddyhome.idea.vim.helper.inVisualMode
import com.maddyhome.idea.vim.helper.isEndAllowed
import com.maddyhome.idea.vim.helper.isIdeaVimDisabledHere
import com.maddyhome.idea.vim.helper.localEditors
import com.maddyhome.idea.vim.helper.moveToInlayAwareOffset
import com.maddyhome.idea.vim.helper.resetVimLastColumn
import com.maddyhome.idea.vim.helper.updateCaretsVisualAttributes
import com.maddyhome.idea.vim.helper.vimDisabled
import com.maddyhome.idea.vim.listener.MouseEventsDataHolder.skipEvents
import com.maddyhome.idea.vim.listener.MouseEventsDataHolder.skipNDragEvents
import com.maddyhome.idea.vim.listener.VimListenerManager.EditorListeners.add
import com.maddyhome.idea.vim.newapi.IjVimEditor
import com.maddyhome.idea.vim.newapi.ij
import com.maddyhome.idea.vim.newapi.vim
import com.maddyhome.idea.vim.state.VimStateMachine
import com.maddyhome.idea.vim.state.mode.Mode
import com.maddyhome.idea.vim.state.mode.inSelectMode
import com.maddyhome.idea.vim.state.mode.mode
import com.maddyhome.idea.vim.state.mode.selectionType
import com.maddyhome.idea.vim.ui.ShowCmdOptionChangeListener
import com.maddyhome.idea.vim.ui.ShowCmdWidgetUpdater
import com.maddyhome.idea.vim.ui.ex.ExEntryPanel
import com.maddyhome.idea.vim.ui.widgets.macro.MacroWidgetListener
import com.maddyhome.idea.vim.ui.widgets.macro.macroWidgetOptionListener
import com.maddyhome.idea.vim.ui.widgets.mode.listeners.ModeWidgetListener
import com.maddyhome.idea.vim.ui.widgets.mode.modeWidgetOptionListener
import com.maddyhome.idea.vim.vimDisposable
import java.awt.event.MouseAdapter
@ -127,6 +132,7 @@ private fun getOpeningEditor(newEditor: Editor) = newEditor.project?.let { proje
internal object VimListenerManager {
private val logger = Logger.getInstance(VimListenerManager::class.java)
private val editorListenersDisposableKey = Key.create<Disposable>("IdeaVim listeners disposable")
private var firstEditorInitialised = false
fun turnOn() {
@ -134,11 +140,30 @@ internal object VimListenerManager {
EditorListeners.addAll()
check(correctorRequester.tryEmit(Unit))
check(keyCheckRequests.tryEmit(Unit))
val caretVisualAttributesListener = CaretVisualAttributesListener()
injector.listenersNotifier.modeChangeListeners.add(caretVisualAttributesListener)
injector.listenersNotifier.isReplaceCharListeners.add(caretVisualAttributesListener)
caretVisualAttributesListener.updateAllEditorsCaretsVisual()
val modeWidgetListener = ModeWidgetListener()
injector.listenersNotifier.modeChangeListeners.add(modeWidgetListener)
injector.listenersNotifier.myEditorListeners.add(modeWidgetListener)
injector.listenersNotifier.vimPluginListeners.add(modeWidgetListener)
val macroWidgetListener = MacroWidgetListener()
injector.listenersNotifier.macroRecordingListeners.add(macroWidgetListener)
injector.listenersNotifier.vimPluginListeners.add(macroWidgetListener)
injector.listenersNotifier.myEditorListeners.add(KeyHandlerStateResetter())
injector.listenersNotifier.myEditorListeners.add(ShowCmdWidgetUpdater())
}
fun turnOff() {
GlobalListeners.disable()
EditorListeners.removeAll()
injector.listenersNotifier.reset()
check(correctorRequester.tryEmit(Unit))
}
@ -156,23 +181,29 @@ internal object VimListenerManager {
optionGroup.addEffectiveOptionValueChangeListener(Options.number, EditorGroup.NumberChangeListener.INSTANCE)
optionGroup.addEffectiveOptionValueChangeListener(Options.relativenumber, EditorGroup.NumberChangeListener.INSTANCE)
optionGroup.addEffectiveOptionValueChangeListener(Options.scrolloff, ScrollGroup.ScrollOptionsChangeListener)
optionGroup.addEffectiveOptionValueChangeListener(Options.guicursor, GuicursorChangeListener)
optionGroup.addGlobalOptionChangeListener(Options.showcmd, ShowCmdOptionChangeListener)
// This code is executed after ideavimrc execution, so we trigger onGlobalOptionChanged just in case
optionGroup.addGlobalOptionChangeListener(IjOptions.showmodewidget, modeWidgetOptionListener)
optionGroup.addGlobalOptionChangeListener(IjOptions.showmodewidget, macroWidgetOptionListener)
optionGroup.addGlobalOptionChangeListener(Options.showmode, modeWidgetOptionListener)
optionGroup.addGlobalOptionChangeListener(Options.showmode, macroWidgetOptionListener)
modeWidgetOptionListener.onGlobalOptionChanged()
macroWidgetOptionListener.onGlobalOptionChanged()
optionGroup.addEffectiveOptionValueChangeListener(Options.guicursor, GuicursorChangeListener)
// Listen for and initialise new editors
EventFacade.getInstance().addEditorFactoryListener(VimEditorFactoryListener, VimPlugin.getInstance().onOffDisposable)
val busConnection = ApplicationManager.getApplication().messageBus.connect(VimPlugin.getInstance().onOffDisposable)
busConnection.subscribe(FileOpenedSyncListener.TOPIC, VimEditorFactoryListener)
EditorFactory.getInstance().eventMulticaster.addCaretListener(VimCaretListener, VimPlugin.getInstance().onOffDisposable)
val eventMulticaster = EditorFactory.getInstance().eventMulticaster as? EditorEventMulticasterEx
eventMulticaster?.addFocusChangeListener(VimFocusListener, VimPlugin.getInstance().onOffDisposable)
// Listen for focus change to update various features such as mode widget
val eventMulticaster = EditorFactory.getInstance().eventMulticaster
(eventMulticaster as? EditorEventMulticasterEx)?.addFocusChangeListener(
VimFocusListener,
VimPlugin.getInstance().onOffDisposable
)
// Listen for document changes to update document state such as marks
eventMulticaster.addDocumentListener(VimDocumentListener, VimPlugin.getInstance().onOffDisposable)
}
fun disable() {
@ -183,8 +214,8 @@ internal object VimListenerManager {
optionGroup.removeEffectiveOptionValueChangeListener(Options.relativenumber, EditorGroup.NumberChangeListener.INSTANCE)
optionGroup.removeEffectiveOptionValueChangeListener(Options.scrolloff, ScrollGroup.ScrollOptionsChangeListener)
optionGroup.removeGlobalOptionChangeListener(Options.showcmd, ShowCmdOptionChangeListener)
optionGroup.removeGlobalOptionChangeListener(IjOptions.showmodewidget, modeWidgetOptionListener)
optionGroup.removeGlobalOptionChangeListener(IjOptions.showmodewidget, macroWidgetOptionListener)
optionGroup.removeGlobalOptionChangeListener(Options.showmode, modeWidgetOptionListener)
optionGroup.removeGlobalOptionChangeListener(Options.showmode, macroWidgetOptionListener)
optionGroup.removeEffectiveOptionValueChangeListener(Options.guicursor, GuicursorChangeListener)
}
}
@ -213,7 +244,9 @@ internal object VimListenerManager {
// We could have a split window in this list, but since they're all being initialised from the same opening editor
// there's no need to use the SPLIT scenario
localEditors().forEach { editor ->
// Make sure we get all editors, including uninitialised
injector.editorGroup.getEditorsRaw().forEach { vimEditor ->
val editor = vimEditor.ij
if (!initialisedEditors.contains(editor)) {
add(editor, getOpeningEditor(editor)?.vim ?: injector.fallbackWindow, LocalOptionInitialisationScenario.NEW)
}
@ -221,18 +254,26 @@ internal object VimListenerManager {
}
fun removeAll() {
localEditors().forEach { editor ->
remove(editor, false)
injector.editorGroup.getEditors().forEach { editor ->
remove(editor.ij)
}
}
fun add(editor: Editor, openingEditor: VimEditor, scenario: LocalOptionInitialisationScenario) {
// We shouldn't be called with anything other than local editors, but let's just be sure. This will prevent any
// unsupported editor from incorrectly being initialised.
// TODO: If the user changes the 'ideavimsupport' option, existing editors won't be initialised
if (vimDisabled(editor)) return
// As I understand, there is no need to pass a disposable that also disposes on editor close
// because all editor resources will be garbage collected anyway on editor close
// Note that this uses the plugin's main disposable, rather than VimPlugin.onOffDisposable, because we don't need
// to - we explicitly call VimListenerManager.removeAll from VimPlugin.turnOffPlugin, and this disposes each
// editor's disposable individually.
val disposable = editor.project?.vimDisposable ?: return
val listenersDisposable = Disposer.newDisposable(disposable)
editor.putUserData(editorListenersDisposable, listenersDisposable)
editor.putUserData(editorListenersDisposableKey, listenersDisposable)
Disposer.register(listenersDisposable) {
if (VimListenerTestObject.enabled) {
@ -255,54 +296,79 @@ internal object VimListenerManager {
eventFacade.addCaretListener(editor, EditorCaretHandler, listenersDisposable)
VimPlugin.getEditor().editorCreated(editor)
VimPlugin.getChange().editorCreated(editor, listenersDisposable)
injector.listenersNotifier.notifyEditorCreated(vimEditor)
Disposer.register(listenersDisposable) {
VimPlugin.getEditorIfCreated()?.editorDeinit(editor, true)
VimPlugin.getEditor().editorDeinit(editor, true)
}
}
fun remove(editor: Editor, isReleased: Boolean) {
val editorDisposable = editor.getUserData(editorListenersDisposable)
fun remove(editor: Editor) {
val editorDisposable = editor.removeUserData(editorListenersDisposableKey)
if (editorDisposable != null) {
Disposer.dispose(editorDisposable)
}
else StrictMode.fail("Editor doesn't have disposable attached. $editor")
VimPlugin.getEditorIfCreated()?.editorDeinit(editor, isReleased)
else {
// We definitely do not expect this to happen
StrictMode.fail("Editor doesn't have disposable attached. $editor")
}
}
}
/**
* Notifies other IdeaVim components of focus gain/loss, e.g. the mode widget. This will be called with non-local Code
* With Me editors.
*/
private object VimFocusListener : FocusChangeListener {
override fun focusGained(editor: Editor) {
if (vimDisabled(editor)) return
injector.listenersNotifier.notifyEditorFocusGained(editor.vim)
}
override fun focusLost(editor: Editor) {
if (vimDisabled(editor)) return
injector.listenersNotifier.notifyEditorFocusLost(editor.vim)
}
}
val editorListenersDisposable = Key.create<Disposable>("IdeaVim listeners disposable")
object VimCaretListener : CaretListener {
override fun caretAdded(event: CaretEvent) {
if (vimDisabled(event.editor)) return
event.editor.updateCaretsVisualAttributes()
/**
* Notifies other IdeaVim components of document changes. This will be called for all documents, even those only
* open in non-local Code With Me guest editors, which we still want to process (e.g. to update marks when a guest
* edits a file. Updating search highlights will be a no-op if there are no open local editors)
*/
private object VimDocumentListener : DocumentListener {
override fun beforeDocumentChange(event: DocumentEvent) {
VimMarkServiceImpl.MarkUpdater.beforeDocumentChange(event)
SearchGroup.DocumentSearchListener.INSTANCE.beforeDocumentChange(event)
}
override fun caretRemoved(event: CaretEvent) {
if (vimDisabled(event.editor)) return
event.editor.updateCaretsVisualAttributes()
override fun documentChanged(event: DocumentEvent) {
VimMarkServiceImpl.MarkUpdater.documentChanged(event)
SearchGroup.DocumentSearchListener.INSTANCE.documentChanged(event)
}
}
/**
* Called when the selected file editor changes. In other words, when the user selects a new tab. Used to remember the
* last selected file, update search highlights in the new tab, etc. This will be called with non-local Code With Me
* guest editors.
*/
class VimFileEditorManagerListener : FileEditorManagerListener {
override fun selectionChanged(event: FileEditorManagerEvent) {
if (VimPlugin.isNotEnabled()) return
// We can't rely on being passed a non-null editor, so check for Code With Me scenarios explicitly
if (VimPlugin.isNotEnabled() || !ClientId.isCurrentlyUnderLocalId) return
val newEditor = event.newEditor
if (newEditor is TextEditor) {
val editor = newEditor.editor
if (editor.isInsertMode) {
VimStateMachine.getInstance(editor).mode = Mode.NORMAL()
KeyHandler.getInstance().reset(editor.vim)
}
}
MotionGroup.fileEditorManagerSelectionChangedCallback(event)
FileGroup.fileEditorManagerSelectionChangedCallback(event)
VimPlugin.getSearch().fileEditorManagerSelectionChangedCallback(event)
@ -310,6 +376,10 @@ internal object VimListenerManager {
}
}
/**
* Listen to editor creation events in order to initialise IdeaVim compatible editors. This listener is called for all
* editors, including non-local hidden Code With Me editors.
*/
private object VimEditorFactoryListener : EditorFactoryListener, FileOpenedSyncListener {
private data class OpeningEditor(
val editor: Editor,
@ -321,6 +391,8 @@ internal object VimListenerManager {
private val openingEditorKey: Key<OpeningEditor> = Key("IdeaVim::OpeningEditor")
override fun editorCreated(event: EditorFactoryEvent) {
if (vimDisabled(event.editor)) return
// This callback is called when an editor is created, but we cannot completely rely on it to initialise options.
// We can find the currently selected editor, which we can use as the opening editor, and we're given the new
// editor, but we don't know enough about it - this function is called before the new editor is added to an
@ -341,7 +413,7 @@ internal object VimListenerManager {
openingEditor == null -> LocalOptionInitialisationScenario.EDIT
else -> LocalOptionInitialisationScenario.NEW
}
add(event.editor, openingEditor?.vim ?: injector.fallbackWindow, scenario)
EditorListeners.add(event.editor, openingEditor?.vim ?: injector.fallbackWindow, scenario)
firstEditorInitialised = true
}
else {
@ -367,11 +439,10 @@ internal object VimListenerManager {
event.editor.putUserData(openingEditorKey, OpeningEditor(openingEditor, owningEditorWindow, isPreview, canBeReused))
}
VimStandalonePluginUpdateChecker.instance.pluginUsed()
}
override fun editorReleased(event: EditorFactoryEvent) {
if (vimDisabled(event.editor)) return
val vimEditor = event.editor.vim
injector.listenersNotifier.notifyEditorReleased(vimEditor)
injector.markService.editorReleased(vimEditor)
@ -395,6 +466,8 @@ internal object VimListenerManager {
// editor is modified
editorsWithProviders.forEach {
(it.fileEditor as? TextEditor)?.editor?.let { editor ->
if (vimDisabled(editor)) return@let
val openingEditor = editor.removeUserData(openingEditorKey)
val owningEditorWindow = getOwningEditorWindow(editor)
val isInSameSplit = owningEditorWindow == openingEditor?.owningEditorWindow
@ -415,7 +488,7 @@ internal object VimListenerManager {
(openingEditor.canBeReused || openingEditor.isPreview) && isInSameSplit && openingEditorIsClosed -> LocalOptionInitialisationScenario.EDIT
else -> LocalOptionInitialisationScenario.NEW
}
add(editor, openingEditor?.editor?.vim ?: injector.fallbackWindow, scenario)
EditorListeners.add(editor, openingEditor?.editor?.vim ?: injector.fallbackWindow, scenario)
firstEditorInitialised = true
}
}
@ -430,12 +503,16 @@ internal object VimListenerManager {
}
}
/**
* Callback for when an editor's text selection changes. Only registered for editors that we're interested in (so only
* local editors). Fixes incorrect mouse selection at end of line, and synchronises selections across other editors.
*/
private object EditorSelectionHandler : SelectionListener {
/**
* This event is executed for each caret using [com.intellij.openapi.editor.CaretModel.runForEachCaret]
*/
override fun selectionChanged(selectionEvent: SelectionEvent) {
if (selectionEvent.editor.isIdeaVimDisabledHere) return
VimVisualTimer.drop()
val editor = selectionEvent.editor
val document = editor.document
@ -455,7 +532,9 @@ internal object VimListenerManager {
val startOffset = selectionEvent.newRange.startOffset
val endOffset = selectionEvent.newRange.endOffset
if (skipNDragEvents < skipEvents && lineStart != lineEnd && startOffset == caretOffset) {
// TODO: It is very confusing that this logic is split between EditorSelectionHandler and EditorMouseHandler
if (MouseEventsDataHolder.dragEventCount < MouseEventsDataHolder.allowedSkippedDragEvents
&& lineStart != lineEnd && startOffset == caretOffset) {
if (lineEnd == endOffset - 1) {
// When starting on an empty line and dragging vertically upwards onto
// another line, the selection should include the entirety of the empty line
@ -485,13 +564,24 @@ internal object VimListenerManager {
}
}
/**
* Listener for mouse events registered with editors that we are interested (so only local editors). Responsible for:
* * Hiding ex entry and output panels when clicking inside editor area (but not when right-clicking)
* * Removing secondary carets on mouse click (such as visual block selection)
* * Exiting visual or select mode on mouse click
* * Resets the dragEventCount on mouse press + release
* * Fix up Vim selected mode on mouse release, after dragging
* * Force bar cursor while dragging, which looks better because IntelliJ selects a character once selection has got
* over halfway through the char, while Vim selects when it enters the character bounding box
*
* @see ComponentMouseListener
*/
// TODO: Can we merge this with ComponentMouseListener to fully handle all mouse actions in one place?
private object EditorMouseHandler : EditorMouseListener, EditorMouseMotionListener {
private var mouseDragging = false
private var cutOffFixed = false
override fun mouseDragged(e: EditorMouseEvent) {
if (e.editor.isIdeaVimDisabledHere) return
val caret = e.editor.caretModel.primaryCaret
clearFirstSelectionEvents(e)
@ -543,7 +633,7 @@ internal object VimListenerManager {
}
}
}
skipNDragEvents -= 1
MouseEventsDataHolder.dragEventCount -= 1
}
/**
@ -558,7 +648,7 @@ internal object VimListenerManager {
* (Both with mouse and with v$. IdeaVim treats v$ as an exclusive selection)
*/
private fun clearFirstSelectionEvents(e: EditorMouseEvent) {
if (skipNDragEvents > 0) {
if (MouseEventsDataHolder.dragEventCount > 0) {
logger.debug("Mouse dragging")
VimVisualTimer.swingTimer?.stop()
if (!mouseDragging) {
@ -584,9 +674,7 @@ internal object VimListenerManager {
}
override fun mousePressed(event: EditorMouseEvent) {
if (event.editor.isIdeaVimDisabledHere) return
skipNDragEvents = skipEvents
MouseEventsDataHolder.dragEventCount = MouseEventsDataHolder.allowedSkippedDragEvents
SelectionVimListenerSuppressor.reset()
}
@ -597,12 +685,10 @@ internal object VimListenerManager {
* - Click-hold and switch editor (ctrl-tab)
*/
override fun mouseReleased(event: EditorMouseEvent) {
if (event.editor.isIdeaVimDisabledHere) return
SelectionVimListenerSuppressor.unlock()
clearFirstSelectionEvents(event)
skipNDragEvents = skipEvents
MouseEventsDataHolder.dragEventCount = MouseEventsDataHolder.allowedSkippedDragEvents
if (mouseDragging) {
logger.debug("Release mouse after dragging")
val editor = event.editor
@ -622,7 +708,6 @@ internal object VimListenerManager {
}
override fun mouseClicked(event: EditorMouseEvent) {
if (event.editor.isIdeaVimDisabledHere) return
logger.debug("Mouse clicked")
if (event.area == EditorMouseEventArea.EDITING_AREA) {
@ -668,13 +753,21 @@ internal object VimListenerManager {
}
}
/**
* A mouse listener registered to the editor component for editors that we are interested in (so only local editors).
* Fixes some issues with mouse selection, namely:
* * Clicking at the end of the line will place the caret on the last character rather than after it
* * Double-clicking a word will place the caret on the last character rather than after it
*
* @see EditorMouseHandler
*/
// TODO: Can we merge this with ComponentMouseListener to fully handle all mouse actions in one place?
private object ComponentMouseListener : MouseAdapter() {
var cutOffEnd = false
override fun mousePressed(e: MouseEvent?) {
val editor = (e?.component as? EditorComponentImpl)?.editor ?: return
if (editor.isIdeaVimDisabledHere) return
val predictedMode = IdeaSelectionControl.predictMode(editor, SelectionSource.MOUSE)
when (e.clickCount) {
1 -> {
@ -711,10 +804,22 @@ internal object VimListenerManager {
}
}
/**
* Caret listener registered only for editors that we're interested in. Used to update caret shapes when carets are
* added/removed. Also tracks the expected last column location of the caret.
*/
private object EditorCaretHandler : CaretListener {
override fun caretPositionChanged(event: CaretEvent) {
event.caret?.resetVimLastColumn()
}
override fun caretAdded(event: CaretEvent) {
event.editor.updateCaretsVisualAttributes()
}
override fun caretRemoved(event: CaretEvent) {
event.editor.updateCaretsVisualAttributes()
}
}
enum class SelectionSource {
@ -729,6 +834,6 @@ internal object VimListenerTestObject {
}
private object MouseEventsDataHolder {
const val skipEvents = 3
var skipNDragEvents = skipEvents
const val allowedSkippedDragEvents = 3
var dragEventCount = allowedSkippedDragEvents
}

View File

@ -43,6 +43,10 @@ internal class IjVimApplication : VimApplicationBase() {
return ApplicationManager.getApplication().isUnitTestMode
}
override fun isInternal(): Boolean {
return ApplicationManager.getApplication().isInternal
}
override fun postKey(stroke: KeyStroke, editor: VimEditor) {
val component: Component = SwingUtilities.getAncestorOfClass(Window::class.java, editor.ij.component)
val event = createKeyEvent(stroke, component)
@ -54,10 +58,6 @@ internal class IjVimApplication : VimApplicationBase() {
}
}
override fun localEditors(): List<VimEditor> {
return com.maddyhome.idea.vim.helper.localEditors().map { IjVimEditor(it) }
}
override fun runWriteCommand(editor: VimEditor, name: String?, groupId: Any?, command: Runnable) {
RunnableHelper.runWriteCommand((editor as IjVimEditor).editor.project, command, name, groupId)
}
@ -82,6 +82,12 @@ internal class IjVimApplication : VimApplicationBase() {
com.maddyhome.idea.vim.helper.runAfterGotFocus(runnable)
}
override fun isOctopusEnabled(): Boolean {
val property = System.getProperty("octopus.handler") ?: "true"
if (property.isBlank()) return true
return property.toBoolean()
}
private fun createKeyEvent(stroke: KeyStroke, component: Component): KeyEvent {
return KeyEvent(
component,

View File

@ -11,7 +11,6 @@ package com.maddyhome.idea.vim.newapi
import com.intellij.openapi.editor.Caret
import com.intellij.openapi.editor.LogicalPosition
import com.intellij.openapi.editor.VisualPosition
import com.intellij.openapi.util.Disposer
import com.maddyhome.idea.vim.api.BufferPosition
import com.maddyhome.idea.vim.api.CaretRegisterStorage
import com.maddyhome.idea.vim.api.CaretRegisterStorageBase
@ -42,14 +41,6 @@ import com.maddyhome.idea.vim.state.mode.SelectionType
internal class IjVimCaret(val caret: Caret) : VimCaretBase() {
init {
if (caret.isValid) {
Disposer.register(caret) {
(registerStorage as CaretRegisterStorageBase).clearListener()
}
}
}
override val registerStorage: CaretRegisterStorage
get() {
var storage = this.caret.registerStorage

View File

@ -51,8 +51,6 @@ import com.maddyhome.idea.vim.helper.fileSize
import com.maddyhome.idea.vim.helper.getTopLevelEditor
import com.maddyhome.idea.vim.helper.inExMode
import com.maddyhome.idea.vim.helper.isTemplateActive
import com.maddyhome.idea.vim.helper.updateCaretsVisualAttributes
import com.maddyhome.idea.vim.helper.updateCaretsVisualPosition
import com.maddyhome.idea.vim.helper.vimChangeActionSwitchMode
import com.maddyhome.idea.vim.helper.vimLastSelectionType
import com.maddyhome.idea.vim.state.mode.Mode
@ -243,14 +241,6 @@ internal class IjVimEditor(editor: Editor) : MutableLinearEditor() {
}
}
override fun updateCaretsVisualAttributes() {
editor.updateCaretsVisualAttributes()
}
override fun updateCaretsVisualPosition() {
editor.updateCaretsVisualPosition()
}
override fun offsetToVisualPosition(offset: Int): VimVisualPosition {
return editor.offsetToVisualPosition(offset).let { VimVisualPosition(it.line, it.column, it.leansRight) }
}

View File

@ -80,11 +80,11 @@ import com.maddyhome.idea.vim.helper.UndoRedoHelper
import com.maddyhome.idea.vim.helper.VimCommandLineHelper
import com.maddyhome.idea.vim.helper.vimStateMachine
import com.maddyhome.idea.vim.history.VimHistory
import com.maddyhome.idea.vim.impl.state.VimStateMachineImpl
import com.maddyhome.idea.vim.macro.VimMacro
import com.maddyhome.idea.vim.put.VimPut
import com.maddyhome.idea.vim.register.VimRegisterGroup
import com.maddyhome.idea.vim.state.VimStateMachine
import com.maddyhome.idea.vim.impl.state.VimStateMachineImpl
import com.maddyhome.idea.vim.ui.VimRcFileState
import com.maddyhome.idea.vim.undo.VimUndoRedo
import com.maddyhome.idea.vim.vimscript.Executor
@ -207,7 +207,7 @@ internal class IjVimInjector : VimInjectorBase() {
override fun commandStateFor(editor: VimEditor): VimStateMachine {
var res = editor.ij.vimStateMachine
if (res == null) {
res = VimStateMachineImpl(editor)
res = VimStateMachineImpl()
editor.ij.vimStateMachine = res
}
return res

View File

@ -32,7 +32,7 @@ import com.maddyhome.idea.vim.vimscript.parser.VimscriptParser.parseExpression
import org.jetbrains.annotations.TestOnly
import javax.swing.KeyStroke
public open class IjVimSearchGroup : VimSearchGroupBase() {
public abstract class IjVimSearchGroup : VimSearchGroupBase() {
init {
// TODO: Investigate migrating these listeners to use the effective value change listener

View File

@ -29,6 +29,8 @@ import com.maddyhome.idea.vim.helper.checkInString
import com.maddyhome.idea.vim.helper.fileSize
import com.maddyhome.idea.vim.state.VimStateMachine.Companion.getInstance
import com.maddyhome.idea.vim.state.mode.Mode.VISUAL
import it.unimi.dsi.fastutil.ints.IntComparator
import it.unimi.dsi.fastutil.ints.IntComparators
import java.util.*
import java.util.function.Function
import java.util.regex.Pattern
@ -689,4 +691,26 @@ internal class IjVimSearchHelper : VimSearchHelperBase() {
// End offset exclusive
return TextRange(bstart, bend + 1)
}
override fun findMisspelledWord(editor: VimEditor, caret: ImmutableVimCaret, count: Int): Int {
val startOffset: Int
val endOffset: Int
val skipCount: Int
val offsetOrdering: IntComparator
if (count < 0) {
startOffset = 0
endOffset = caret.offset.point - 1
skipCount = -count - 1
offsetOrdering = IntComparators.OPPOSITE_COMPARATOR
}
else {
startOffset = caret.offset.point + 1
endOffset = editor.ij.document.textLength
skipCount = count - 1
offsetOrdering = IntComparators.NATURAL_COMPARATOR
}
return SearchHelper.findMisspelledWords(editor.ij, startOffset, endOffset, skipCount, offsetOrdering)
}
}

View File

@ -333,7 +333,7 @@
* |[m| {@link com.maddyhome.idea.vim.action.motion.text.MotionMethodPreviousStartAction}
* |[p| {@link com.maddyhome.idea.vim.action.copy.PutVisualTextAfterCursorNoIndentAction}
* |[p| {@link com.maddyhome.idea.vim.action.copy.PutTextAfterCursorNoIndentAction}
* |[s| TO BE IMPLEMENTED
* |[s| {@link com.maddyhome.idea.vim.action.motion.text.MotionMisspelledWordPreviousAction}
* |[z| TO BE IMPLEMENTED
* |[{| {@link com.maddyhome.idea.vim.action.motion.text.MotionUnmatchedBraceOpenAction}
* |]_CTRL-D| TO BE IMPLEMENTED
@ -358,7 +358,7 @@
* |]m| {@link com.maddyhome.idea.vim.action.motion.text.MotionMethodNextStartAction}
* |]p| {@link com.maddyhome.idea.vim.action.copy.PutVisualTextAfterCursorNoIndentAction}
* |]p| {@link com.maddyhome.idea.vim.action.copy.PutTextAfterCursorNoIndentAction}
* |]s| TO BE IMPLEMENTED
* |]s| {@link com.maddyhome.idea.vim.action.motion.text.MotionMisspelledWordNextAction}
* |]z| TO BE IMPLEMENTED
* |]}| {@link com.maddyhome.idea.vim.action.motion.text.MotionUnmatchedBraceCloseAction}
*

View File

@ -14,17 +14,7 @@ import com.intellij.internal.statistic.eventLog.events.EventFields
import com.intellij.internal.statistic.service.fus.collectors.CounterUsagesCollector
internal class ActionTracker : CounterUsagesCollector() {
companion object {
private val GROUP = EventLogGroup("vim.actions", 1, "FUS")
private val TRACKED_ACTIONS = GROUP.registerEvent(
"tracked",
EventFields.StringValidatedByCustomRule("action_id", ActionRuleValidator::class.java),
)
private val COPIED_ACTIONS = GROUP.registerEvent(
"copied",
EventFields.StringValidatedByCustomRule("action_id", ActionRuleValidator::class.java),
)
object Util {
fun logTrackedAction(actionId: String) {
TRACKED_ACTIONS.log(actionId)
}
@ -36,3 +26,14 @@ internal class ActionTracker : CounterUsagesCollector() {
override fun getGroup(): EventLogGroup = GROUP
}
private val GROUP = EventLogGroup("vim.actions", 1)
private val TRACKED_ACTIONS = GROUP.registerEvent(
"tracked",
EventFields.StringValidatedByCustomRule("action_id", ActionRuleValidator::class.java),
)
private val COPIED_ACTIONS = GROUP.registerEvent(
"copied",
EventFields.StringValidatedByCustomRule("action_id", ActionRuleValidator::class.java),
)

View File

@ -47,18 +47,20 @@ internal class OptionsState : ApplicationUsagesCollector() {
)
}
companion object {
private val GROUP = EventLogGroup("vim.options", 1, "FUS")
}
private val GROUP = EventLogGroup("vim.options", 1)
private val IDEAJOIN = BooleanEventField(IjOptions.ideajoin.name)
private val IDEAMARKS = BooleanEventField(IjOptions.ideamarks.name)
private val IDEAREFACTOR = EventFields.String(IjOptions.idearefactormode.name, IjOptionConstants.ideaRefactorModeValues.toList())
private val IDEAREFACTOR =
EventFields.String(IjOptions.idearefactormode.name, IjOptionConstants.ideaRefactorModeValues.toList())
private val IDEAPUT = BooleanEventField(OptionConstants.clipboard_ideaput)
private val IDEASTATUSICON = EventFields.String(IjOptions.ideastatusicon.name, IjOptionConstants.ideaStatusIconValues.toList())
private val IDEASTATUSICON =
EventFields.String(IjOptions.ideastatusicon.name, IjOptionConstants.ideaStatusIconValues.toList())
private val IDEAWRITE = EventFields.String(IjOptions.ideawrite.name, IjOptionConstants.ideaWriteValues.toList())
private val IDEASELECTION = BooleanEventField(OptionConstants.selectmode_ideaselection)
private val IDEAVIMSUPPORT = EventFields.StringList(IjOptions.ideavimsupport.name, IjOptionConstants.ideavimsupportValues.toList())
private val IDEAVIMSUPPORT =
EventFields.StringList(IjOptions.ideavimsupport.name, IjOptionConstants.ideavimsupportValues.toList())
private val OPTIONS: VarargEventId = GROUP.registerVarargEvent(
"vim.options",
IDEAJOIN,
@ -70,5 +72,3 @@ internal class OptionsState : ApplicationUsagesCollector() {
IDEASELECTION,
IDEAVIMSUPPORT,
)
}
}

View File

@ -14,6 +14,8 @@ import com.intellij.internal.statistic.eventLog.events.EventFields
import com.intellij.internal.statistic.eventLog.events.VarargEventId
import com.intellij.internal.statistic.service.fus.collectors.ApplicationUsagesCollector
import com.maddyhome.idea.vim.VimPlugin
import com.maddyhome.idea.vim.statistic.PluginState.Util.enabledExtensions
import com.maddyhome.idea.vim.statistic.PluginState.Util.extensionNames
import com.maddyhome.idea.vim.ui.JoinEap
internal class PluginState : ApplicationUsagesCollector() {
@ -30,21 +32,32 @@ internal class PluginState : ApplicationUsagesCollector() {
)
}
companion object {
private val GROUP = EventLogGroup("vim.common", 1, "FUS")
val extensionNames = listOf("textobj-entire", "argtextobj", "ReplaceWithRegister", "vim-paragraph-motion", "highlightedyank", "multiple-cursors", "exchange", "NERDTree", "surround", "commentary", "matchit", "textobj-indent")
val enabledExtensions = HashSet<String>()
object Util {
internal val extensionNames = listOf(
"textobj-entire",
"argtextobj",
"ReplaceWithRegister",
"vim-paragraph-motion",
"highlightedyank",
"multiple-cursors",
"exchange",
"NERDTree",
"surround",
"commentary",
"matchit",
"textobj-indent"
)
internal val enabledExtensions = HashSet<String>()
}
}
private val GROUP = EventLogGroup("vim.common", 1)
private val PLUGIN_ENABLED = EventFields.Boolean("is_plugin_enabled")
private val IS_EAP = EventFields.Boolean("is_EAP_active")
private val ENABLED_EXTENSIONS = EventFields.StringList("enabled_extensions", extensionNames)
private val PLUGIN_STATE: VarargEventId = GROUP.registerVarargEvent(
"vim.common",
PLUGIN_ENABLED,
IS_EAP,
ENABLED_EXTENSIONS,
)
}
}

View File

@ -70,10 +70,36 @@ internal class ShortcutConflictState : ApplicationUsagesCollector() {
}
}
}
}
companion object {
private val GROUP = EventLogGroup("vim.handlers", 1, "FUS")
private fun KeyStroke.toReadableString(): String {
val result = StringBuilder()
val modifiers = this.modifiers
if (modifiers > 0) {
result.append(preprocessKeys(InputEvent.getModifiersExText(modifiers)))
.append("+")
}
result.append(KeyEvent.getKeyText(this.keyCode))
return result.toString()
}
private fun preprocessKeys(string: String): String {
return string.replace("", "Ctrl").replace("", "Shift")
}
private enum class HandledModes {
NORMAL_UNDEFINED,
NORMAL_IDE,
NORMAL_VIM,
INSERT_UNDEFINED,
INSERT_IDE,
INSERT_VIM,
VISUAL_AND_SELECT_UNDEFINED,
VISUAL_AND_SELECT_IDE,
VISUAL_AND_SELECT_VIM,
}
private val GROUP = EventLogGroup("vim.handlers", 1)
private val keyStrokes = listOf(
KeyStroke.getKeyStroke('1'.code, CTRL_DOWN_MASK),
KeyStroke.getKeyStroke('2'.code, CTRL_DOWN_MASK),
@ -158,32 +184,3 @@ internal class ShortcutConflictState : ApplicationUsagesCollector() {
private val KEY_STROKE = EventFields.String("key_stroke", keyStrokes.map { it.toReadableString() })
private val HANDLER_MODE = EventFields.Enum<HandledModes>("handler")
private val HANDLER = GROUP.registerEvent("vim.handler", KEY_STROKE, HANDLER_MODE)
}
}
private fun KeyStroke.toReadableString(): String {
val result = StringBuilder()
val modifiers = this.modifiers
if (modifiers > 0) {
result.append(preprocessKeys(InputEvent.getModifiersExText(modifiers)))
.append("+")
}
result.append(KeyEvent.getKeyText(this.keyCode))
return result.toString()
}
private fun preprocessKeys(string: String): String {
return string.replace("", "Ctrl").replace("", "Shift")
}
private enum class HandledModes {
NORMAL_UNDEFINED,
NORMAL_IDE,
NORMAL_VIM,
INSERT_UNDEFINED,
INSERT_IDE,
INSERT_VIM,
VISUAL_AND_SELECT_UNDEFINED,
VISUAL_AND_SELECT_IDE,
VISUAL_AND_SELECT_VIM,
}

View File

@ -13,6 +13,7 @@ import com.intellij.internal.statistic.eventLog.EventLogGroup
import com.intellij.internal.statistic.eventLog.events.EventFields
import com.intellij.internal.statistic.eventLog.events.VarargEventId
import com.intellij.internal.statistic.service.fus.collectors.ApplicationUsagesCollector
import com.maddyhome.idea.vim.statistic.PluginState.Util.extensionNames
import com.maddyhome.idea.vim.vimscript.services.VimRcService
internal class VimscriptState : ApplicationUsagesCollector() {
@ -22,37 +23,39 @@ internal class VimscriptState : ApplicationUsagesCollector() {
override fun getMetrics(): Set<MetricEvent> {
return setOf(
VIMSCRIPT.metric(
SOURCED_FILES with sourcedFiles.size,
SOURCED_FILES with Util.sourcedFiles.size,
IDEAVIMRC_SIZE with (VimRcService.findIdeaVimRc()?.readLines()?.filter { !it.matches(Regex("\\s*\".*")) && it.isNotBlank() }?.size ?: -1),
EXTENSIONS_ENABLED_BY_SET with (PluginState.enabledExtensions - extensionsEnabledWithPlug).toList(),
EXTENSIONS_ENABLED_BY_PLUG with extensionsEnabledWithPlug.toList(),
IS_IDE_SPECIFIC_CONFIGURATION_USED with isIDESpecificConfigurationUsed,
IS_LOOP_USED with isLoopUsed,
IS_IF_USED with isIfUsed,
IS_MAP_EXPR_USED with isMapExprUsed,
IS_FUNCTION_DEF_USED with isFunctionDeclarationUsed,
IS_FUNCTION_CALL_USED with isFunctionCallUsed,
EXTENSIONS_ENABLED_BY_SET with (PluginState.Util.enabledExtensions - Util.extensionsEnabledWithPlug).toList(),
EXTENSIONS_ENABLED_BY_PLUG with Util.extensionsEnabledWithPlug.toList(),
IS_IDE_SPECIFIC_CONFIGURATION_USED with Util.isIDESpecificConfigurationUsed,
IS_LOOP_USED with Util.isLoopUsed,
IS_IF_USED with Util.isIfUsed,
IS_MAP_EXPR_USED with Util.isMapExprUsed,
IS_FUNCTION_DEF_USED with Util.isFunctionDeclarationUsed,
IS_FUNCTION_CALL_USED with Util.isFunctionCallUsed,
),
)
}
companion object {
private val GROUP = EventLogGroup("vim.vimscript", 1, "FUS")
object Util {
val sourcedFiles = HashSet<String>()
val extensionsEnabledWithPlug = HashSet<String>()
var isIDESpecificConfigurationUsed = false
var isIDESpecificConfigurationUsed = false
var isLoopUsed = false
var isIfUsed = false
var isMapExprUsed = false
var isFunctionDeclarationUsed = false
var isFunctionCallUsed = false
}
}
private val GROUP = EventLogGroup("vim.vimscript", 1)
private val SOURCED_FILES = EventFields.RoundedInt("number_of_sourced_files")
private val IDEAVIMRC_SIZE = EventFields.RoundedInt("ideavimrc_size")
private val EXTENSIONS_ENABLED_BY_SET = EventFields.StringList("extensions_enabled_by_set", PluginState.extensionNames)
private val EXTENSIONS_ENABLED_BY_PLUG = EventFields.StringList("extensions_enabled_by_plug", PluginState.extensionNames)
private val EXTENSIONS_ENABLED_BY_SET = EventFields.StringList("extensions_enabled_by_set", extensionNames)
private val EXTENSIONS_ENABLED_BY_PLUG = EventFields.StringList("extensions_enabled_by_plug", extensionNames)
private val IS_IDE_SPECIFIC_CONFIGURATION_USED = EventFields.Boolean("is_IDE-specific_configuration_used")
private val IS_LOOP_USED = EventFields.Boolean("is_loop_used")
private val IS_IF_USED = EventFields.Boolean("is_if_used")
@ -73,5 +76,3 @@ internal class VimscriptState : ApplicationUsagesCollector() {
IS_FUNCTION_DEF_USED,
IS_FUNCTION_CALL_USED,
)
}
}

View File

@ -0,0 +1,71 @@
/*
* Copyright 2003-2024 The IdeaVim authors
*
* Use of this source code is governed by an MIT-style
* license that can be found in the LICENSE.txt file or at
* https://opensource.org/licenses/MIT.
*/
package com.maddyhome.idea.vim.statistic
import com.intellij.internal.statistic.beans.MetricEvent
import com.intellij.internal.statistic.eventLog.EventLogGroup
import com.intellij.internal.statistic.eventLog.events.EventFields
import com.intellij.internal.statistic.eventLog.events.VarargEventId
import com.intellij.internal.statistic.service.fus.collectors.ApplicationUsagesCollector
import com.intellij.openapi.project.ProjectManager
import com.intellij.openapi.wm.WindowManager
import com.maddyhome.idea.vim.api.injector
import com.maddyhome.idea.vim.ui.widgets.mode.ModeWidgetFactory
public class WidgetState : ApplicationUsagesCollector() {
override fun getGroup(): EventLogGroup = GROUP
override fun getMetrics(): Set<MetricEvent> {
return setOf(
WIDGET.metric(
IS_MODE_WIDGET_SHOWN with isWidgetShown(),
MODE_WIDGET_THEME_LIGHT with getModeWidgetTheme("_light"),
MODE_WIDGET_THEME_DARK with getModeWidgetTheme("_dark"),
),
)
}
private fun isWidgetShown(): Boolean {
for (project in ProjectManager.getInstance().openProjects) {
val statusBar = WindowManager.getInstance()?.getStatusBar(project) ?: continue
val widgets = statusBar.allWidgets ?: continue
if (widgets.any { it.ID() == ModeWidgetFactory.ID }) {
return true
}
}
return false
}
private fun getModeWidgetTheme(postfix: String): String {
if (injector.variableService.getVimVariable("widget_mode_is_full_customization$postfix")?.asBoolean() == true) {
return "ADVANCED CUSTOMIZATION"
}
val themeString = injector.variableService.getVimVariable("widget_mode_theme$postfix")?.asString()
return if (themeString?.lowercase() == "colorless") {
"COLORLESS"
} else {
"TERM"
}
}
public companion object {
private val GROUP = EventLogGroup("vim.widget", 1, "FUS")
private val IS_MODE_WIDGET_SHOWN = EventFields.Boolean("is-mode-widget-shown")
private val MODE_WIDGET_THEME_LIGHT = EventFields.String("mode-widget-theme-light", listOf("TERM", "COLORLESS", "ADVANCED CUSTOMIZATION"))
private val MODE_WIDGET_THEME_DARK = EventFields.String("mode-widget-theme-dark", listOf("TERM", "COLORLESS", "ADVANCED CUSTOMIZATION"))
private val WIDGET: VarargEventId = GROUP.registerVarargEvent(
"vim.widget",
IS_MODE_WIDGET_SHOWN,
MODE_WIDGET_THEME_LIGHT,
MODE_WIDGET_THEME_DARK,
)
}
}

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