1
0
mirror of https://github.com/chylex/IntelliJ-IdeaVim.git synced 2025-01-30 20:45:59 +01:00

Compare commits

...

178 Commits

Author SHA1 Message Date
f33589fbce
Set plugin version to chylex-42 2024-12-17 13:04:35 +01:00
35445cb730
Exit insert mode after refactoring 2024-12-17 13:04:35 +01:00
9cba953bf7
Add action to run last macro in all opened files 2024-12-17 13:04:35 +01:00
eb7c1953ae
Stop macro execution after a failed search 2024-12-17 13:04:35 +01:00
8d4a1cd7d5
Revert per-caret registers 2024-12-17 13:04:35 +01:00
fd33e4254b
Fix(VIM-3364): Exception with mapped Generate action 2024-12-17 12:44:01 +01:00
f242cf18d7
Apply scrolloff after executing native IDEA actions 2024-12-17 12:44:01 +01:00
ac46a45f26
Stay on same line after reindenting 2024-12-17 12:44:01 +01:00
9d1e29d4bc
Update search register when using f/t 2024-12-17 12:44:01 +01:00
8c30f802ca
Automatically add unambiguous imports after running a macro 2024-12-17 12:44:01 +01:00
766f3f498d
Fix(VIM-3179): Respect virtual space below editor (imperfectly) 2024-12-17 12:44:00 +01:00
60d9c59a3e
Fix(VIM-3178): Workaround to support "Jump to Source" action mapping 2024-12-17 12:44:00 +01:00
025bfe5aba
Add support for count for visual and line motion surround 2024-12-17 12:43:59 +01:00
2edb650830
Fix vim-surround not working with multiple cursors
Fixes multiple cursors with vim-surround commands `cs, ds, S` (but not `ys`).
2024-12-17 12:43:48 +01:00
3d75e14dc4
Fix(VIM-696) Restore visual mode after undo/redo, and disable incompatible actions 2024-12-17 12:06:53 +01:00
db0290d218
Respect count with <Action> mappings 2024-12-17 12:06:53 +01:00
36716fe849
Change matchit plugin to use HTML patterns in unrecognized files 2024-12-17 12:06:53 +01:00
7cfe42688b
Reset insert mode when switching active editor 2024-12-17 12:06:53 +01:00
1d9fb7f6a7
Disable switching to insert mode for some editors 2024-12-17 12:06:53 +01:00
8c4828a6fd
Remove update checker 2024-12-17 12:06:53 +01:00
86f11a9554
Set custom plugin version 2024-12-17 12:06:53 +01:00
Alex Plate
4614e2ad54
[VIM-3617] Remove the forgotten recursion call 2024-12-17 10:06:48 +02:00
Alex Plate
077de91e01
[VIM-3617] Set a recursion guard for obtaining the editor
Function `selectedTextEditor` in some cases may create a new editor, what causes the recursion and IDE freeze. The guard should protect from it.
2024-12-17 10:02:34 +02:00
Alex Plate
1ce7a97f2a
Fix(VIM-3747): There is no need to remove the visual mode if there is still a selection 2024-12-16 13:38:29 +02:00
Alex Plate
02a42843a6
Update gradle wrapper 2024-12-16 09:39:22 +02:00
dependabot[bot]
eb72762073 Bump org.jetbrains.intellij.platform from 2.1.0 to 2.2.0
Bumps org.jetbrains.intellij.platform from 2.1.0 to 2.2.0.

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

Signed-off-by: dependabot[bot] <support@github.com>
2024-12-11 17:44:09 +02:00
dependabot[bot]
43a94b3d71 Bump io.ktor:ktor-client-auth from 3.0.1 to 3.0.2
Bumps [io.ktor:ktor-client-auth](https://github.com/ktorio/ktor) from 3.0.1 to 3.0.2.
- [Release notes](https://github.com/ktorio/ktor/releases)
- [Changelog](https://github.com/ktorio/ktor/blob/main/CHANGELOG.md)
- [Commits](https://github.com/ktorio/ktor/compare/3.0.1...3.0.2)

---
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-12-11 17:42:42 +02:00
dependabot[bot]
1984873b1c Bump org.eclipse.jgit:org.eclipse.jgit.ssh.apache
Bumps [org.eclipse.jgit:org.eclipse.jgit.ssh.apache](https://github.com/eclipse-jgit/jgit) from 6.10.0.202406032230-r to 7.1.0.202411261347-r.
- [Commits](https://github.com/eclipse-jgit/jgit/compare/v6.10.0.202406032230-r...v7.1.0.202411261347-r)

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

Signed-off-by: dependabot[bot] <support@github.com>
2024-12-11 17:42:28 +02:00
Julien Phalip
4962baabad Add support for hlsearch variable 2024-12-06 17:21:33 +02:00
Julien Phalip
2e550a0960 Add support for escape() vim function 2024-12-06 17:09:59 +02:00
IdeaVim Bot
bc48a45cd1 Add Justas Trimailovas, Justas Trimailovas to contributors list 2024-12-05 09:02:20 +00:00
Matt Ellis
a005eb0612 Fix repeating change action with count
Fixes VIM-3729
2024-12-04 18:23:16 +02:00
Justas Trimailovas
63297e685c Correctly change surround if it's empty or only spaces
This commit fixes 2 bugs at once:

1. Correctly trim surround with closing brackets motion, e.g.: `cs()`.

   It should trim all surrounding white space or leave things unchanged if
   there's no space.

   For example cases like these:

	"( ${c}foo )"  // single spaces
	"(   ${c}foo   )"  // multiple spaces
	"( ${c}foo)"  // assymetric spaces
	"(${c} )"  // single space without text
	"(${c}   )"  // multiple spaces without text

   Should trim white spaces into these results accordingly:

	"${c}(foo)"
	"${c}(foo)"
	"${c}(foo)"
	"${c}()"
	"${c}()"

In case of no spaces - they should be left unchanged, e.g.:

	"(${c}foo)"  // no spaces around the word
	"(${c})"  // empty parenthesis

2. Leave empty parenthesis unchanged. IdeaVim now deleted them instead.
2024-12-04 18:07:02 +02:00
Justas Trimailovas
6c9a5e1f2d [VIM-1824] surround - remove whitespace with closing bracket
**Context**:

In vim surround extendsion closing brackets (`}, ], )`) should remove
whitespace when using `cs` movement.

Example:
- Before: `{ example }`
- Movement: `cs{}`
- After: `{example}`

This was because  were replaced with a string from `SURROUND_PAIRS` map,
which does not have any context about removing characters.

**Solution**:

Inspired from VSCode's VIM plugin[^1], I have introduced new class
`SurroundPair` that will carry this context about need to trim
characters.

**Disclaimer**:

I have never written in `Kotlin` so solution may be not use best
practices, though at least this PR seems to fix the problem and tests
are passing.

**Ticket**:
- https://youtrack.jetbrains.com/issue/VIM-1824/Vim-Surround-Does-not-remove-whitespace-with-the-closing-bracket

[^1]: 04fe42aa81/src/actions/plugins/surround.ts (L455)
2024-12-04 18:07:02 +02:00
dependabot[bot]
34a5ba0ba7 Bump io.ktor:ktor-client-cio from 3.0.1 to 3.0.2
Bumps [io.ktor:ktor-client-cio](https://github.com/ktorio/ktor) from 3.0.1 to 3.0.2.
- [Release notes](https://github.com/ktorio/ktor/releases)
- [Changelog](https://github.com/ktorio/ktor/blob/main/CHANGELOG.md)
- [Commits](https://github.com/ktorio/ktor/compare/3.0.1...3.0.2)

---
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-12-04 17:59:34 +02:00
dependabot[bot]
2354dc9af9 Bump io.ktor:ktor-client-content-negotiation from 3.0.1 to 3.0.2
Bumps [io.ktor:ktor-client-content-negotiation](https://github.com/ktorio/ktor) from 3.0.1 to 3.0.2.
- [Release notes](https://github.com/ktorio/ktor/releases)
- [Changelog](https://github.com/ktorio/ktor/blob/main/CHANGELOG.md)
- [Commits](https://github.com/ktorio/ktor/compare/3.0.1...3.0.2)

---
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-12-04 17:45:34 +02:00
dependabot[bot]
be31963a4f Bump io.ktor:ktor-serialization-kotlinx-json from 3.0.1 to 3.0.2
Bumps [io.ktor:ktor-serialization-kotlinx-json](https://github.com/ktorio/ktor) from 3.0.1 to 3.0.2.
- [Release notes](https://github.com/ktorio/ktor/releases)
- [Changelog](https://github.com/ktorio/ktor/blob/main/CHANGELOG.md)
- [Commits](https://github.com/ktorio/ktor/compare/3.0.1...3.0.2)

---
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-12-04 17:32:02 +02:00
dependabot[bot]
277c5ae7a5 Bump io.ktor:ktor-client-core from 3.0.1 to 3.0.2
Bumps [io.ktor:ktor-client-core](https://github.com/ktorio/ktor) from 3.0.1 to 3.0.2.
- [Release notes](https://github.com/ktorio/ktor/releases)
- [Changelog](https://github.com/ktorio/ktor/blob/main/CHANGELOG.md)
- [Commits](https://github.com/ktorio/ktor/compare/3.0.1...3.0.2)

---
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-12-04 17:31:51 +02:00
dependabot[bot]
49d03cf7f1 Bump com.google.devtools.ksp:symbol-processing-api
Bumps [com.google.devtools.ksp:symbol-processing-api](https://github.com/google/ksp) from 2.0.0-1.0.24 to 2.1.0-1.0.29.
- [Release notes](https://github.com/google/ksp/releases)
- [Commits](https://github.com/google/ksp/compare/2.0.0-1.0.24...2.1.0-1.0.29)

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

Signed-off-by: dependabot[bot] <support@github.com>
2024-12-04 17:31:33 +02:00
Alex Plate
f5e7459b37
[IJPL-165238] It seems the issue was fixed 2024-12-03 15:18:20 +02:00
Alex Plate
df8144fc00
Temporally bring back first and last checks 2024-12-03 15:03:09 +02:00
dependabot[bot]
05f1d7abd7 Bump org.jetbrains.kotlin:kotlin-stdlib from 1.9.25 to 2.1.0
Bumps [org.jetbrains.kotlin:kotlin-stdlib](https://github.com/JetBrains/kotlin) from 1.9.25 to 2.1.0.
- [Release notes](https://github.com/JetBrains/kotlin/releases)
- [Changelog](https://github.com/JetBrains/kotlin/blob/v2.1.0/ChangeLog.md)
- [Commits](https://github.com/JetBrains/kotlin/compare/v1.9.25...v2.1.0)

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

Signed-off-by: dependabot[bot] <support@github.com>
2024-11-27 17:44:55 +02:00
Alex Pláte
6ca17ae09d
Update codeql-analysis.yml 2024-11-26 14:53:10 +02:00
Alex Pláte
f9d8b3a59e
Update syncDoc.yml 2024-11-26 14:50:29 +02:00
Alex Pláte
3279325694
Update closeYoutrackOnCommit.yml 2024-11-26 14:46:54 +02:00
Julien Phalip
b2a6d0d5c7 Add switch and functiontextobj to list of plugins 2024-11-26 14:15:38 +02:00
Julien Phalip
e32fa6dd11 Change Variable to an interface 2024-11-26 10:32:11 +02:00
Julien Phalip
ec3db81c6d Move register variable to its own separate class 2024-11-26 10:32:11 +02:00
Julien Phalip
7062dc4860 Add support for the v:register variable 2024-11-26 10:32:11 +02:00
Alex Plate
46619040b1
Add new plugins for checking
From here: https://github.com/JetBrains/ideavim/discussions/1047
2024-11-25 15:32:22 +02:00
Alex Plate
035804860c
Fix incorrect configuration in gradle 2024-11-25 15:32:01 +02:00
Alex Plate
0f1a4d523f
Move the docs to the init method of VimExtension 2024-11-25 14:01:23 +02:00
Alex Plate
5f5a97a7e1
Lazily load the logger to avoid injector initialization problems 2024-11-25 13:39:24 +02:00
IdeaVim Bot
03996dce59 Add Julien Phalip to contributors list 2024-11-23 09:03:04 +00:00
filipp
0dd63c7b57 Fix nullable editor in tests 2024-11-22 17:15:07 +02:00
filipp
94d7902ef2 Remove more deprecated methods 2024-11-22 17:15:07 +02:00
filipp
3d08170d54 Post-rebase fixes 2024-11-22 17:15:07 +02:00
filipp
cccb23d9ee Specify which commands perform mode change
We need this for undo subsystem. Mode change is not something that we want to be a separate entity in the undo log

P.S. It's not a full list of such commands, e.g. <ESC> for leaving insert mode is missing. It is because <ESC> may insert some text after visual block mode, I'll figure out a solution for this later
2024-11-22 17:15:07 +02:00
Filipp Vakhitov
eae3fb3ebe Introduce VimKeyBasedUndoService and VimTimestampBasedUndoService
Undo is managed differently in Fleet and IJ Platform, so now we have two different interfaces that are aware of that
2024-11-22 17:15:07 +02:00
Filipp Vakhitov
7f5dce4051 Stop using some deprecated methods 2024-11-22 17:15:07 +02:00
Filipp Vakhitov
25e3988dcf Introduce VimCopiedText class
I don't really like that we have `transferableData: List<Any>`
2024-11-22 17:15:07 +02:00
Filipp Vakhitov
36589c5250 Add context argument in clipboard methods
Alas, adding in just to clipboard was not possible, as there are a lot of depending on methods
2024-11-22 17:15:07 +02:00
Filipp Vakhitov
f1f86b5cd2 Simplify Register class
1. `rawText` vs `text` is confusing and `rawText` was misused, we do not need this field in IdeaVim at all
2. `rawText` and `keys` are the same thing, just parsed. And for some reason, rawText was a nullable field
3. Register is an immutable class now
4. Working with Register class is easier and more compact now
2024-11-22 17:15:07 +02:00
Filipp Vakhitov
628b3ca89f Fix <CR> parsing
It's ^M, not ^J
2024-11-22 17:15:07 +02:00
Filipp Vakhitov
d2b929ddd0 Fix parsing for clipboard option
When I added the selection clipboard, it was confusing to explain to people how this option works and why they should prepend, because the parsing was broken
I've also removed "exclude" part, because it doesn't work in IdeaVim and can confuse people
2024-11-22 17:15:07 +02:00
filipp
43d770ff5a Simplify getTransferableData method signature 2024-11-22 17:15:07 +02:00
Filipp Vakhitov
cde9bc94e6 Fix select mode for immutable caret
Due to implementation details, caret cannot be moved after setting selection
2024-11-22 17:15:07 +02:00
Filipp Vakhitov
63f6e73223 Fix select mode for immutable caret 2024-11-22 17:15:07 +02:00
Filipp Vakhitov
ad28e09fec Move caret when deleting a char
In Fleet deleting text does not move caret, so we add caret moving logic
2024-11-22 17:15:07 +02:00
Filipp Vakhitov
f616f2c3c1 Fix gv in Fleet
Moving caret after setting selection removes the selection
2024-11-22 17:15:07 +02:00
Filipp Vakhitov
66aec26091 Move ColLineFunctionHandler.kt to vim-engine 2024-11-22 17:15:07 +02:00
Filipp Vakhitov
1d59c49b95 Move more logic to vim-engine 2024-11-22 17:15:07 +02:00
Matt Ellis
8da15b8c08 Manually track last selected editor
This is important for initialising options. We can't rely on FileEditorManager.selectedTextEditor, as 2024.2 changed behaviour to reset to null during creation of a new editor. This fixes tests (and option initialisation) for 242.
2024-11-22 17:01:38 +02:00
Matt Ellis
e22c2b6473 Improve updating fallback window option values
The assumption for selection change events is no longer valid in 242, so switch approach to checking when editors are released
2024-11-22 17:01:38 +02:00
Matt Ellis
e293c1b786 Add tests for updating the fallback window 2024-11-22 17:01:38 +02:00
Matt Ellis
468ca840ed Update options tests
Expanded to assert changes on all open windows, and formatted to make it a little easier to read/comprehend what's being tested
2024-11-22 17:01:38 +02:00
Julien Phalip
c75e6756c0 Add some tests for the getcmdtype() function 2024-11-22 16:49:04 +02:00
Julien Phalip
52737c60cf Add JSON entry for getcmdtype function 2024-11-22 16:49:04 +02:00
Julien Phalip
e99c191d49 Add partial support for getcmdtype() function
Tests not included
2024-11-22 16:49:04 +02:00
Julien Phalip
5db2984fdd Add ability to set the highlightedyank foreground color 2024-11-22 15:48:22 +02:00
Alex Plate
365b58eb56
Update build.gradle checker 2024-11-15 20:54:07 +02:00
Alex Plate
b026144254
Add RWLock label to selectionStart & selectionEnd 2024-11-15 20:53:54 +02:00
Alex Plate
0e3cda827c
Fix(VIM-3695): Wrap getting a vimLeadSelectionOffset with a read action 2024-11-15 20:44:00 +02:00
Alex Plate
ed2fe3dcf0
Fix(VIM-3696): Get the state of the selection in read action 2024-11-15 20:34:41 +02:00
Alex Plate
791edbd29b
Restore compatibility with peekaboo plugin
https://plugins.jetbrains.com/plugin/25776-vim-peekaboo
2024-11-13 19:39:24 +02:00
Matt Ellis
4f9d76ef66 Fix memory leak with non-disposed listeners
Fixes VIM-3319
2024-11-13 18:00:47 +02:00
Matt Ellis
4b7381901d Fix bug removing map that is also a prefix 2024-11-13 17:57:31 +02:00
Matt Ellis
6e2cb9ba11 Reorder functions. No logic changes 2024-11-13 17:57:31 +02:00
Matt Ellis
91cd4ab01f Add sequences to iterate keystroke trie
Allows iterating the trie entries without having to create a list or create a list for each entry's keystrokes.
2024-11-13 17:57:31 +02:00
Matt Ellis
34d23180bd Avoid wrapping the keys list for each map attempt 2024-11-13 17:57:31 +02:00
Matt Ellis
fc5aaa50d8 Remove unnecessary hasmapfrom function 2024-11-13 17:57:31 +02:00
Matt Ellis
c7bbfdcaf5 Remove some test only functions 2024-11-13 17:57:31 +02:00
Matt Ellis
562906fe6d Filter map output by prefix
Fixes VIM-2981
2024-11-13 17:57:31 +02:00
Matt Ellis
976771d11a Implement smap and snoremap
Support for sunmap and smapclear already exists, and vmap would introduce a Select mode map.

Fixes VIM-2260
2024-11-13 17:57:31 +02:00
Matt Ellis
5fc4462b03 Support map! to map insert and cmdline modes
Also supports `mapclear!` and `unmap!`

Moves parsing of the bang modifier to the parser so we can tell the difference between `map! foo bar` and `map ! foo bar`
2024-11-13 17:57:31 +02:00
Matt Ellis
5f263e7014 Fix output of maps with same keys in multiple modes
E.g. `map foo bar` and `vmap foo baz` would only output one map for `foo` when calling `:map`. Now it will output all maps that match the ex command's modes. This change also improves the marker characters in the first column of map output.
2024-11-13 17:57:31 +02:00
Matt Ellis
4c899fcc93 Simplify test code 2024-11-13 17:57:31 +02:00
Matt Ellis
2f8fe392af Use KeyStrokeTrie for maps 2024-11-13 17:57:31 +02:00
Matt Ellis
84c7e1159b Introduce KeyStrokeTrie to find commands
Should also restore compatibility with idea-which-key
2024-11-13 17:57:31 +02:00
Matt Ellis
18d6f79796 Add missing C-PageUp/Down shortcuts to switch tab
Fixes VIM-2044
2024-11-13 17:52:28 +02:00
Matt Ellis
a745da9761 Add Shift+Enter mapping to scroll page forward
Fixes VIM-2752
2024-11-13 17:52:28 +02:00
Matt Ellis
5eb36ce428 Remove unneeded extended special name parsing
This was needed when action keys were registered in a comma separated list in a single XML attribute string. Additional encoding was needed for angle brackets and commas. Registration has changed, and this is no longer needed
2024-11-13 17:52:28 +02:00
Alex Plate
b0951f4a38
Add information about the Vim-Peekaboo plugin 2024-11-08 11:06:32 +02:00
dependabot[bot]
bcf519027e Bump io.ktor:ktor-client-content-negotiation from 2.3.10 to 3.0.1
Bumps [io.ktor:ktor-client-content-negotiation](https://github.com/ktorio/ktor) from 2.3.10 to 3.0.1.
- [Release notes](https://github.com/ktorio/ktor/releases)
- [Changelog](https://github.com/ktorio/ktor/blob/main/CHANGELOG.md)
- [Commits](https://github.com/ktorio/ktor/compare/2.3.10...3.0.1)

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

Signed-off-by: dependabot[bot] <support@github.com>
2024-11-06 17:51:11 +02:00
dependabot[bot]
9aece44a00 Bump io.ktor:ktor-client-cio from 3.0.0 to 3.0.1
Bumps [io.ktor:ktor-client-cio](https://github.com/ktorio/ktor) from 3.0.0 to 3.0.1.
- [Release notes](https://github.com/ktorio/ktor/releases)
- [Changelog](https://github.com/ktorio/ktor/blob/main/CHANGELOG.md)
- [Commits](https://github.com/ktorio/ktor/compare/3.0.0...3.0.1)

---
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-11-06 17:37:07 +02:00
dependabot[bot]
61912e2c9b Bump io.ktor:ktor-serialization-kotlinx-json from 2.3.12 to 3.0.1
Bumps [io.ktor:ktor-serialization-kotlinx-json](https://github.com/ktorio/ktor) from 2.3.12 to 3.0.1.
- [Release notes](https://github.com/ktorio/ktor/releases)
- [Changelog](https://github.com/ktorio/ktor/blob/main/CHANGELOG.md)
- [Commits](https://github.com/ktorio/ktor/compare/2.3.12...3.0.1)

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

Signed-off-by: dependabot[bot] <support@github.com>
2024-11-06 17:21:37 +02:00
Matt Ellis
92ee271f1e Add fallback if ve guicursor option not specified 2024-11-05 12:29:13 +02:00
Matt Ellis
babc5daf3b Use Visual-exclusive caret shape from guicursor 2024-11-05 12:29:13 +02:00
Alex Plate
c0a10c65e1
Upgrade TC agents 2024-11-05 11:56:20 +02:00
Alex Plate
5145bb4193
Mute some NeoVim tests 2024-11-05 11:50:44 +02:00
Alex Plate
8825f790d4
Fix the problem with the JSON in tests
In 2025.1 the JSON plugin is extracted from the platform. See IJPL-159354
2024-11-05 11:45:32 +02:00
Alex Plate
a0f0f71b6a
Revert "Fix deprecated API usage"
This reverts commit b0dd75f77c.

Because of some reason, this place fails on TC. Let's deal with this later
2024-11-05 05:43:14 +02:00
Alex Plate
6a73f9e65a
Bump TC configurations to use 2024.2.1 2024-11-05 05:09:19 +02:00
Alex Plate
4ee858877d
Revert "Remove pre-242 workaround"
This reverts commit 727cfb36ba.
2024-11-05 04:16:32 +02:00
Matt Ellis
82d18cfbb9 Remove unused imports 2024-11-05 04:14:12 +02:00
Matt Ellis
b0dd75f77c Fix deprecated API usage 2024-11-05 04:14:12 +02:00
Matt Ellis
727cfb36ba Remove pre-242 workaround 2024-11-05 04:14:12 +02:00
Matt Ellis
a7d5c5f2d4 Remove custom version for split mode
Split mode run task will use the same version as runIde
2024-11-05 04:14:12 +02:00
Matt Ellis
1e02848a66 Update target IDEA version to 242
Note that this uses 2024.2.1 as the minimum version because 2024.2.0 is no longer available.
2024-11-05 04:14:12 +02:00
Alex Plate
9d8afc5fcb
TC: Use the idea version from the gradle configuration to run github tests 2024-11-05 03:46:32 +02:00
dependabot[bot]
15b2a17ae6 Bump org.junit.jupiter:junit-jupiter-engine from 5.10.3 to 5.10.5
Bumps [org.junit.jupiter:junit-jupiter-engine](https://github.com/junit-team/junit5) from 5.10.3 to 5.10.5.
- [Release notes](https://github.com/junit-team/junit5/releases)
- [Commits](https://github.com/junit-team/junit5/compare/r5.10.3...r5.10.5)

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

Signed-off-by: dependabot[bot] <support@github.com>
2024-10-30 18:10:50 +02:00
dependabot[bot]
39a2cdcf50 Bump io.ktor:ktor-client-auth from 2.3.12 to 3.0.1
Bumps [io.ktor:ktor-client-auth](https://github.com/ktorio/ktor) from 2.3.12 to 3.0.1.
- [Release notes](https://github.com/ktorio/ktor/releases)
- [Changelog](https://github.com/ktorio/ktor/blob/main/CHANGELOG.md)
- [Commits](https://github.com/ktorio/ktor/compare/2.3.12...3.0.1)

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

Signed-off-by: dependabot[bot] <support@github.com>
2024-10-30 18:09:09 +02:00
dependabot[bot]
2fd488531b Bump org.junit.vintage:junit-vintage-engine from 5.10.3 to 5.10.5
Bumps [org.junit.vintage:junit-vintage-engine](https://github.com/junit-team/junit5) from 5.10.3 to 5.10.5.
- [Release notes](https://github.com/junit-team/junit5/releases)
- [Commits](https://github.com/junit-team/junit5/compare/r5.10.3...r5.10.5)

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

Signed-off-by: dependabot[bot] <support@github.com>
2024-10-30 17:55:56 +02:00
dependabot[bot]
e69b30796c Bump org.junit.jupiter:junit-jupiter-api from 5.10.3 to 5.10.5
Bumps [org.junit.jupiter:junit-jupiter-api](https://github.com/junit-team/junit5) from 5.10.3 to 5.10.5.
- [Release notes](https://github.com/junit-team/junit5/releases)
- [Commits](https://github.com/junit-team/junit5/compare/r5.10.3...r5.10.5)

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

Signed-off-by: dependabot[bot] <support@github.com>
2024-10-30 17:54:34 +02:00
dependabot[bot]
790a0afdf3 Bump io.ktor:ktor-client-core from 2.3.12 to 3.0.1
Bumps [io.ktor:ktor-client-core](https://github.com/ktorio/ktor) from 2.3.12 to 3.0.1.
- [Release notes](https://github.com/ktorio/ktor/releases)
- [Changelog](https://github.com/ktorio/ktor/blob/main/CHANGELOG.md)
- [Commits](https://github.com/ktorio/ktor/compare/2.3.12...3.0.1)

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

Signed-off-by: dependabot[bot] <support@github.com>
2024-10-30 17:54:12 +02:00
IdeaVim Bot
66b01b0b0d Add Greg Shrago to contributors list 2024-10-26 09:02:33 +00:00
Alex Plate
71ea6184a0
Add Greg Shrago to the contributors list 2024-10-25 18:18:12 +03:00
Greg Shrago
0b1c79d961
Disable some IJ additional checks 2024-10-25 18:14:37 +03:00
Alex Plate
6b751fae6e
Disable some IJ additional checks 2024-10-25 17:29:07 +03:00
Alex Plate
152066b73c
Apply TeamCity patches 2024-10-25 17:00:44 +03:00
aleksei.plate@jetbrains.com
dfe645c4eb TeamCity change in 'Ideavim' project: general settings of 'Tests for IntelliJ 2024.2' build configuration were updated 2024-10-25 13:52:21 +00:00
Alex Plate
5024fd94da
There are fails in the configuration, let's revert part of it 2024-10-25 16:47:57 +03:00
Alex Plate
4d3a231a35
Add java and other tests to artifacts 2024-10-25 16:27:53 +03:00
Alex Plate
e1a803d310
Disable one more test that doesn't work on 242 2024-10-25 15:59:05 +03:00
Alex Plate
96b224c0d8
Fix compilation issues 2024-10-25 14:47:54 +03:00
Alex Plate
8b6de3f5c6
Fix split window tests for 242
The window splitting was refactored to initialize the editor async. So, we have to wait till the editor will become available.

This can't be done while holding the EDT, so we start this bunch of tests from the non-EDT thread.
2024-10-25 14:35:55 +03:00
Alex Plate
9014d99cde
Add a note that PluginStartup code should be updated and migrated to a different approach 2024-10-25 14:35:55 +03:00
Alex Plate
8dfd41a891
Refactoring: Move VimDocumentListener from multicaster to plugin.xml registration
The problem happens in tests: after the refactorings in 242, the `EditorListenerTracker` may be called before the initialization of the IdeaVim. In this case, it'll report the VimDocumentListener as a leaked listener, however, it's incorrect.
Generally, I think that this situation with the listener tracker is a bug.

There should be no changes in IdeaVim behaviour as the multicaster does the same thing: subscribes every editor on this listener. However, the multicaster does this in the "registerEditor" stage. However, I don't think this is a problem.
2024-10-25 14:35:55 +03:00
Alex Plate
7e62e816a5
Refactoring: Move FocusChangeListener from multicaster to per-editor subscription
The problem happens in tests: after the refactorings in 242, the `EditorListenerTracker` may be called before the initialization of the IdeaVim. In this case, it'll report the FocusChangeListener as a leaked listener, however, it's incorrect.
Generally, I think that this situation with the listener tracker is a bug.

There should be no changes in IdeaVim behaviour as the multicaster does the same thing: subscribes every editor on this listener. However, the multicaster does this in the "registerEditor" stage. However, I don't think this is a problem.
2024-10-25 14:35:55 +03:00
dependabot[bot]
9c4965581a Bump org.junit.jupiter:junit-jupiter from 5.11.0 to 5.11.3
Bumps [org.junit.jupiter:junit-jupiter](https://github.com/junit-team/junit5) from 5.11.0 to 5.11.3.
- [Release notes](https://github.com/junit-team/junit5/releases)
- [Commits](https://github.com/junit-team/junit5/compare/r5.11.0...r5.11.3)

---
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-10-23 19:04:24 +03:00
dependabot[bot]
d1abc0c9f9 Bump io.ktor:ktor-client-cio from 2.3.10 to 3.0.0
Bumps [io.ktor:ktor-client-cio](https://github.com/ktorio/ktor) from 2.3.10 to 3.0.0.
- [Release notes](https://github.com/ktorio/ktor/releases)
- [Changelog](https://github.com/ktorio/ktor/blob/main/CHANGELOG.md)
- [Commits](https://github.com/ktorio/ktor/compare/2.3.10...3.0.0)

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

Signed-off-by: dependabot[bot] <support@github.com>
2024-10-23 18:51:08 +03:00
dependabot[bot]
43555ad581 Bump org.jetbrains:annotations from 24.1.0 to 26.0.1
Bumps [org.jetbrains:annotations](https://github.com/JetBrains/java-annotations) from 24.1.0 to 26.0.1.
- [Release notes](https://github.com/JetBrains/java-annotations/releases)
- [Changelog](https://github.com/JetBrains/java-annotations/blob/master/CHANGELOG.md)
- [Commits](https://github.com/JetBrains/java-annotations/compare/24.1.0...26.0.1)

---
updated-dependencies:
- dependency-name: org.jetbrains:annotations
  dependency-type: direct:production
  update-type: version-update:semver-major
...

Signed-off-by: dependabot[bot] <support@github.com>
2024-10-23 18:50:30 +03:00
dependabot[bot]
640e5ac552 Bump org.jetbrains.intellij.platform from 2.0.0-rc2 to 2.1.0
Bumps org.jetbrains.intellij.platform from 2.0.0-rc2 to 2.1.0.

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

Signed-off-by: dependabot[bot] <support@github.com>
2024-10-23 18:49:52 +03:00
dependabot[bot]
40b706bcf4 Bump org.junit.jupiter:junit-jupiter-params from 5.10.3 to 5.10.5
Bumps [org.junit.jupiter:junit-jupiter-params](https://github.com/junit-team/junit5) from 5.10.3 to 5.10.5.
- [Release notes](https://github.com/junit-team/junit5/releases)
- [Commits](https://github.com/junit-team/junit5/compare/r5.10.3...r5.10.5)

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

Signed-off-by: dependabot[bot] <support@github.com>
2024-10-23 18:49:36 +03:00
Alex Plate
5b355a8e95
TC: Remove old tests configurations
There was not a single case when these tests were needed
2024-10-23 13:31:33 +03:00
IdeaVim Bot
8ed53284e4 Add Kirill Karnaukhov, SanderHestvik to contributors list 2024-10-22 09:01:48 +00:00
Alex Plate
1f6e124f9d
Fix(VIM-3676): Allow to set the handle for shortcuts with arrows
With the AI functionality, the shortcut ctrl-right got more important. However, previously it was defined as VIM_ONLY shortcut. However, taking the fact that IJ defines several actions for such shortcuts, it's not clear why we prohibit the users from using these shortcuts with the IDE actions.

Taking the fact, that we default shortcuts to VIM, I expect no changes in the behaviour.

However, just arrows are still hidden from setting the IDE handler. I think, it reduces the cognitive load for the user, especially taking the fact that arrows work almost equally in vim and IJ.
2024-10-21 17:56:44 +03:00
Kirill Karnaukhov
5198864f34 [ai-completion] LLM-11886: make AI completion invoke on Enter when IdeaVim is turned on 2024-10-21 17:02:36 +03:00
Alex Plate
ecfff61aad
Fix the issue in the replacement script 2024-10-21 16:17:49 +03:00
Alex Plate
26909af5de
Update the doc sync script to remove the .md from the links. 2024-10-21 16:16:17 +03:00
Alex Plate
ad762379fb
Update IdeaVim Plugins.md 2024-10-21 16:12:11 +03:00
SanderHestvik
dd4f2de368 Fix links in doc/Home.md
Currently these links give a 404
2024-10-21 16:11:29 +03:00
Matt Ellis
879d191800 Scroll caret into view after undo/redo
This is to ensure that 'scrolloff' is applied. Relates to VIM-3671
2024-10-21 15:50:58 +03:00
Matt Ellis
9d9e38843d Invoke undo/redo on correct editor
Fixes VIM-3671
2024-10-21 15:50:58 +03:00
Matt Ellis
f7aded99e8 Only perform incsearch in current project
Fixes VIM-3682
2024-10-21 15:50:58 +03:00
Igor Babko
4e1de61d77 Update support-guide.md 2024-10-21 14:13:31 +03:00
Matt Ellis
34fe09c8f9 Use full width of output panel for text 2024-09-05 19:22:04 +03:00
Matt Ellis
10283ce2f8 Add support for custom digraphs 2024-09-05 19:22:04 +03:00
Matt Ellis
cc53b59658 Add digraph headers 2024-09-05 19:22:04 +03:00
Matt Ellis
c758a79f79 Minor refactor outputting digraphs 2024-09-05 19:22:04 +03:00
Matt Ellis
01d00d45d8 Add a default size for the digraph string builder 2024-09-05 19:22:04 +03:00
Matt Ellis
5c7edc498f Rename getDigraph - it gets a char from a digraph 2024-09-05 19:22:04 +03:00
Matt Ellis
9c403d2862 Simplify creating string digraph key 2024-09-05 19:22:04 +03:00
Matt Ellis
ebbd733bba Update default digraphs to match current Vim
List is based on Vim's documentation, although not all digraphs are documented. Additional digraphs are added based on the output of `:digraphs`. These additional digraphs include some digraphs that produce the same character, so the code is updated to handle duplicates, with the same ordering/priority as Vim.

Extra digraphs include the Euro symbol (`=e` and `Eu`), quadruple prime (`4'`) and bullet (`oo`), amongst others.

Also removes a number of non-standard digraphs. The symbols generated don't match the descriptions. The code appears to be private use, so are not reliable. Once custom digraphs are implemented, they can be easily added back in `~/.ideavimrc`
2024-09-05 19:22:04 +03:00
Matt Ellis
88d1e1d24a Suppress language download inspection 2024-09-05 19:22:04 +03:00
Matt Ellis
c19f2aeee4 Update comments for default digraphs 2024-09-05 19:22:04 +03:00
Matt Ellis
86202c846a Remove unused commented digraph data 2024-09-05 19:22:04 +03:00
Matt Ellis
9a7ff442f3 Correctly format RTL and combining digraph chars 2024-09-05 19:22:04 +03:00
Matt Ellis
3752a97d74 Output digraph character codes in decimal, like Vim
Not sure why IdeaVim has used hex. Vim appears to have used decimal for at least 20 years.
2024-09-05 19:22:04 +03:00
Matt Ellis
5572dfc80d Update digraph formatting to match Vim
Vim only use the `~` prefix if the encoding is "latin1". We can just treat it as though the encoding is Unicode, and match the other places we format printable characters. Note that for Vim, if `'display'` contains "uhex", then all unprintable characters are shown in hex, including control characters (`^C`, etc.). IdeaVim does not support the `'display'` option.
2024-09-05 19:22:04 +03:00
Matt Ellis
100f420d46 Strip trailing spaces 2024-09-05 19:22:04 +03:00
Matt Ellis
3fcc4746d8 Migrate digraph output to engine 2024-09-05 19:22:04 +03:00
Matt Ellis
5c5ac192da Add tests for digraph output 2024-09-05 19:22:04 +03:00
Alex Plate
a0a2163ba0
Disable some action tests because they're broken in 242
Related: VIM-3376
2024-09-05 18:58:20 +03:00
Alex Plate
02724cadce
Fix the leaking timer in tests 2024-09-05 18:57:43 +03:00
Alex Plate
b205f87902
Disable tests for options for split window
The behaviour of FileEditorManagerEx.split has been changed, causing the tests to fail. This should be fixed in a separate commit
2024-09-05 18:57:12 +03:00
Alex Plate
314304246a
Fix the LATEST-EAP-SNAPSHOT tests 2024-09-05 17:10:14 +03:00
Alex Plate
785688b1ca
Fix compatibility in tests with 2024.2 2024-09-05 16:42:16 +03:00
349 changed files with 8636 additions and 4163 deletions

1
.gitattributes vendored Normal file
View File

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

View File

@ -7,6 +7,9 @@ on:
workflow_dispatch:
push:
branches: [ master ]
permissions:
contents: write
jobs:
build:

View File

@ -20,6 +20,11 @@ on:
schedule:
- cron: '44 12 * * 4'
permissions:
actions: read
contents: read
security-events: write
jobs:
analyze:
name: Analyze

View File

@ -10,6 +10,9 @@ on:
push:
branches: [ master ]
permissions:
contents: write
jobs:
build:
@ -34,6 +37,17 @@ jobs:
id: update_authors
run: cp -a origin/doc/. docs
# The Wiki for github should have no `.md` in references
# Otherwise, such links will lead to the raw text.
# However, the `.md` should exist to have a navigation in GitHub code.
- name: Replace `.md)` with `)`
run: |
# Define the directory you want to process
DIRECTORY="docs"
# Find all files in the directory and perform the replacement
find $DIRECTORY -type f -exec sed -i 's/\.md)/)/g' {} +
- name: Commit changes
uses: stefanzweifel/git-auto-commit-action@v4
with:

View File

@ -5,13 +5,12 @@ object Constants {
const val EAP_CHANNEL = "eap"
const val DEV_CHANNEL = "Dev"
const val GITHUB_TESTS = "2024.1.1"
const val NVIM_TESTS = "2024.1.1"
const val PROPERTY_TESTS = "2024.1.1"
const val LONG_RUNNING_TESTS = "2024.1.1"
const val QODANA_TESTS = "2024.1.1"
const val RELEASE = "2024.1.1"
const val NVIM_TESTS = "2024.2.1"
const val PROPERTY_TESTS = "2024.2.1"
const val LONG_RUNNING_TESTS = "2024.2.1"
const val QODANA_TESTS = "2024.2.1"
const val RELEASE = "2024.2.1"
const val RELEASE_DEV = "2024.1.1"
const val RELEASE_EAP = "2024.1.1"
const val RELEASE_DEV = "2024.2.1"
const val RELEASE_EAP = "2024.2.1"
}

View File

@ -8,7 +8,6 @@ import _Self.buildTypes.PropertyBased
import _Self.buildTypes.Qodana
import _Self.buildTypes.TestingBuildType
import _Self.subprojects.GitHub
import _Self.subprojects.OldTests
import _Self.subprojects.Releases
import _Self.vcsRoots.GitHubPullRequest
import _Self.vcsRoots.ReleasesVcsRoot
@ -18,7 +17,7 @@ import jetbrains.buildServer.configs.kotlin.v2019_2.Project
object Project : Project({
description = "Vim engine for JetBrains IDEs"
subProjects(Releases, OldTests, GitHub)
subProjects(Releases, GitHub)
// VCS roots
vcsRoot(GitHubPullRequest)
@ -26,8 +25,7 @@ object Project : Project({
// Active tests
buildType(TestingBuildType("Latest EAP", "<default>", version = "LATEST-EAP-SNAPSHOT"))
buildType(TestingBuildType("2024.1.1", "<default>"))
buildType(TestingBuildType("2024.2", "<default>"))
buildType(TestingBuildType("2024.2.1", "<default>"))
buildType(TestingBuildType("Latest EAP With Xorg", "<default>", version = "LATEST-EAP-SNAPSHOT"))
buildType(PropertyBased)
@ -44,6 +42,9 @@ object Project : Project({
abstract class IdeaVimBuildType(init: BuildType.() -> Unit) : BuildType({
artifactRules = """
+:build/reports => build/reports
+:tests/java-tests/build/reports => java-tests/build/reports
+:tests/long-running-tests/build/reports => long-running-tests/build/reports
+:tests/property-tests/build/reports => property-tests/build/reports
+:/mnt/agent/temp/buildTmp/ => /mnt/agent/temp/buildTmp/
""".trimIndent()
@ -53,7 +54,7 @@ abstract class IdeaVimBuildType(init: BuildType.() -> Unit) : BuildType({
// These requirements define Linux-Medium configuration.
// Unfortunately, requirement by name (teamcity.agent.name) doesn't work
// IDK the reason for it, but on our agents this property is empty
equals("teamcity.agent.hardware.cpuCount", "4")
equals("teamcity.agent.hardware.cpuCount", "16")
equals("teamcity.agent.os.family", "Linux")
}

View File

@ -40,6 +40,9 @@ object Compatibility : IdeaVimBuildType({
# java -jar verifier1/verifier-cli-dev-all-1.jar check-plugin '${'$'}com.github.copilot' [latest-IU] -team-city
java -jar verifier1/verifier-cli-dev-all-1.jar check-plugin '${'$'}com.github.dankinsoid.multicursor' [latest-IU] -team-city
java -jar verifier1/verifier-cli-dev-all-1.jar check-plugin '${'$'}com.joshestein.ideavim-quickscope' [latest-IU] -team-city
java -jar verifier1/verifier-cli-dev-all-1.jar check-plugin '${'$'}com.julienphalip.ideavim.peekaboo' [latest-IU] -team-city
java -jar verifier1/verifier-cli-dev-all-1.jar check-plugin '${'$'}com.julienphalip.ideavim.switch' [latest-IU] -team-city
java -jar verifier1/verifier-cli-dev-all-1.jar check-plugin '${'$'}com.julienphalip.ideavim.functiontextobj' [latest-IU] -team-city
""".trimIndent()
}
}

View File

@ -41,7 +41,7 @@ object ReleaseEapFromBranch : IdeaVimBuildType({
vcs {
root(ReleasesVcsRoot)
branchFilter = """
+:heads/(releases/*)
+:heads/releases/*
""".trimIndent()
checkoutMode = CheckoutMode.AUTO

View File

@ -39,9 +39,11 @@ open class TestingBuildType(
steps {
gradle {
clearConditions()
tasks = "clean test"
buildFile = ""
enableStacktrace = true
jdkHome = "/usr/lib/jvm/java-17-amazon-corretto"
}
}

View File

@ -1,6 +1,5 @@
package _Self.subprojects
import _Self.Constants
import _Self.IdeaVimBuildType
import _Self.vcsRoots.GitHubPullRequest
import jetbrains.buildServer.configs.kotlin.v2019_2.CheckoutMode
@ -25,7 +24,6 @@ class GithubBuildType(command: String, desc: String) : IdeaVimBuildType({
params {
param("env.ORG_GRADLE_PROJECT_downloadIdeaSources", "false")
param("env.ORG_GRADLE_PROJECT_ideaVersion", Constants.GITHUB_TESTS)
param("env.ORG_GRADLE_PROJECT_instrumentPluginCode", "false")
}

View File

@ -1,25 +0,0 @@
package _Self.subprojects
import _Self.buildTypes.TestingBuildType
import jetbrains.buildServer.configs.kotlin.v2019_2.Project
object OldTests : Project({
name = "Old IdeaVim tests"
description = "Tests for older versions of IJ"
buildType(TestingBuildType("IC-2018.1", "181-182", javaVersion = "1.8", javaPlugin = false))
buildType(TestingBuildType("IC-2018.2", "181-182", javaVersion = "1.8", javaPlugin = false))
buildType(TestingBuildType("IC-2018.3", "183", javaVersion = "1.8", javaPlugin = false))
buildType(TestingBuildType("IC-2019.1", "191-193", javaVersion = "1.8", javaPlugin = false))
buildType(TestingBuildType("IC-2019.2", "191-193", javaVersion = "1.8", javaPlugin = false))
buildType(TestingBuildType("IC-2019.3", "191-193", javaVersion = "1.8", javaPlugin = false))
buildType(TestingBuildType("IC-2020.1", "201", javaVersion = "1.8", javaPlugin = false))
buildType(TestingBuildType("IC-2020.2", "202", javaVersion = "1.8", javaPlugin = false))
buildType(TestingBuildType("IC-2020.3", "203-212", javaVersion = "1.8", javaPlugin = false))
buildType(TestingBuildType("IC-2021.1", "203-212", javaVersion = "1.8", javaPlugin = false))
buildType(TestingBuildType("IC-2021.2.2", "203-212", javaVersion = "1.8", javaPlugin = false))
buildType(TestingBuildType("IC-2021.3.2", "213-221", javaVersion = "1.8", javaPlugin = false))
buildType(TestingBuildType("IC-2022.2.3", branch = "222", javaPlugin = false))
buildType(TestingBuildType("IC-2023.1", "231-232", javaPlugin = false))
buildType(TestingBuildType("IC-2023.2", "231-232", javaPlugin = false))
})

View File

@ -1,39 +0,0 @@
package patches.buildTypes
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.*
/*
This patch script was generated by TeamCity on settings change in UI.
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"
buildFile = ""
enableStacktrace = true
}
}
steps {
update<GradleBuildStep>(0) {
clearConditions()
jdkHome = "/usr/lib/jvm/java-17-amazon-corretto"
}
}
}

View File

@ -1,19 +0,0 @@
package patches.buildTypes
import jetbrains.buildServer.configs.kotlin.v2019_2.*
import jetbrains.buildServer.configs.kotlin.v2019_2.ui.*
/*
This patch script was generated by TeamCity on settings change in UI.
To apply the patch, change the buildType with id = 'ReleaseEapFromBranch'
accordingly, and delete the patch script.
*/
changeBuildType(RelativeId("ReleaseEapFromBranch")) {
vcs {
check(branchFilter == "+:heads/(releases/*)") {
"Unexpected option value: branchFilter = $branchFilter"
}
branchFilter = "heads/releases/*"
}
}

View File

@ -539,6 +539,30 @@ Contributors:
[![icon][github]](https://github.com/felixwiemuth)
&nbsp;
Felix Wiemuth
* [![icon][mail]](mailto:kirill.karnaukhov@jetbrains.com)
[![icon][github]](https://github.com/kkarnauk)
&nbsp;
Kirill Karnaukhov,
* [![icon][mail]](mailto:sander.hestvik@gmail.com)
[![icon][github]](https://github.com/SanderHestvik)
&nbsp;
SanderHestvik
* [![icon][mail]](mailto:gregory.shrago@jetbrains.com)
[![icon][github]](https://github.com/gregsh)
&nbsp;
Greg Shrago
* [![icon][mail]](mailto:jphalip@gmail.com)
[![icon][github]](https://github.com/jphalip)
&nbsp;
Julien Phalip
* [![icon][mail]](mailto:j.trimailovas@gmail.com)
[![icon][github]](https://github.com/trimailov)
&nbsp;
Justas Trimailovas,
* [![icon][mail]](mailto:justast@wix.com)
[![icon][github]](https://github.com/justast-wix)
&nbsp;
Justas Trimailovas
Previous contributors:
@ -550,6 +574,10 @@ Previous contributors:
[![icon][github]](https://github.com/kevin70)
&nbsp;
kk
* [![icon][mail]](mailto:gregory.shrago@jetbrains.com)
[![icon][github]](https://github.com/gregsh)
&nbsp;
Greg Shrago
If you are a contributor and your name is not listed here, feel free to

View File

@ -21,7 +21,7 @@ repositories {
}
dependencies {
compileOnly("com.google.devtools.ksp:symbol-processing-api:2.0.0-1.0.24")
compileOnly("com.google.devtools.ksp:symbol-processing-api:2.1.0-1.0.29")
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

@ -50,14 +50,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.10.0.202406032230-r")
classpath("org.eclipse.jgit:org.eclipse.jgit.ssh.apache:7.1.0.202411261347-r")
classpath("org.kohsuke:github-api:1.305")
classpath("io.ktor:ktor-client-core:2.3.12")
classpath("io.ktor:ktor-client-cio:2.3.10")
classpath("io.ktor:ktor-client-auth:2.3.12")
classpath("io.ktor:ktor-client-content-negotiation:2.3.10")
classpath("io.ktor:ktor-serialization-kotlinx-json:2.3.12")
classpath("io.ktor:ktor-client-core:3.0.2")
classpath("io.ktor:ktor-client-cio:3.0.2")
classpath("io.ktor:ktor-client-auth:3.0.2")
classpath("io.ktor:ktor-client-content-negotiation:3.0.2")
classpath("io.ktor:ktor-serialization-kotlinx-json:3.0.2")
// This comes from the changelog plugin
// classpath("org.jetbrains:markdown:0.3.1")
@ -69,7 +69,7 @@ plugins {
kotlin("jvm") version "2.0.0"
application
id("java-test-fixtures")
id("org.jetbrains.intellij.platform") version "2.0.0-rc2"
id("org.jetbrains.intellij.platform") version "2.2.0"
id("org.jetbrains.changelog") version "2.2.1"
id("org.jetbrains.kotlinx.kover") version "0.6.1"
id("com.dorongold.task-tree") version "4.0.0"
@ -85,7 +85,6 @@ val ideaVersion: String by project
val ideaType: String by project
val instrumentPluginCode: String by project
val remoteRobotVersion: String by project
val splitModeVersion: String by project
val publishChannels: String by project
val publishToken: String by project
@ -108,13 +107,17 @@ dependencies {
compileOnly(project(":annotation-processors"))
compileOnly("org.jetbrains.kotlin:kotlin-stdlib:$kotlinVersion")
compileOnly("org.jetbrains:annotations:24.1.0")
compileOnly("org.jetbrains:annotations:26.0.1")
intellijPlatform {
// Snapshots don't use installers
// https://plugins.jetbrains.com/docs/intellij/tools-intellij-platform-gradle-plugin-dependencies-extension.html#target-versions-installers
val useInstaller = "EAP-SNAPSHOT" !in ideaVersion
// Note that it is also possible to use local("...") to compile against a locally installed IDE
// E.g. local("/Users/{user}/Applications/IntelliJ IDEA Ultimate.app")
// Or something like: intellijIdeaUltimate(ideaVersion)
create(ideaType, ideaVersion)
create(ideaType, ideaVersion, useInstaller)
pluginVerifier()
zipSigner()
@ -125,6 +128,7 @@ dependencies {
// AceJump is an optional dependency. We use their SessionManager class to check if it's active
plugin("AceJump", "3.8.19")
plugin("com.intellij.classic.ui", "242.20224.159")
}
moduleSources(project(":vim-engine", "sourcesJarArtifacts"))
@ -146,17 +150,17 @@ dependencies {
// https://mvnrepository.com/artifact/org.mockito.kotlin/mockito-kotlin
testImplementation("org.mockito.kotlin:mockito-kotlin:5.4.0")
testImplementation("org.junit.jupiter:junit-jupiter-api:5.10.3")
testImplementation("org.junit.jupiter:junit-jupiter-engine:5.10.3")
testImplementation("org.junit.jupiter:junit-jupiter-params:5.10.3")
testFixturesImplementation("org.junit.jupiter:junit-jupiter-api:5.10.3")
testFixturesImplementation("org.junit.jupiter:junit-jupiter-engine:5.10.3")
testFixturesImplementation("org.junit.jupiter:junit-jupiter-params:5.10.3")
testImplementation("org.junit.jupiter:junit-jupiter-api:5.10.5")
testImplementation("org.junit.jupiter:junit-jupiter-engine:5.10.5")
testImplementation("org.junit.jupiter:junit-jupiter-params:5.10.5")
testFixturesImplementation("org.junit.jupiter:junit-jupiter-api:5.10.5")
testFixturesImplementation("org.junit.jupiter:junit-jupiter-engine:5.10.5")
testFixturesImplementation("org.junit.jupiter:junit-jupiter-params:5.10.5")
// Temp workaround suggested in https://plugins.jetbrains.com/docs/intellij/tools-intellij-platform-gradle-plugin-faq.html#junit5-test-framework-refers-to-junit4
// Can be removed when IJPL-159134 is fixed
// testRuntimeOnly("junit:junit:4.13.2")
testImplementation("org.junit.vintage:junit-vintage-engine:5.10.3")
testImplementation("org.junit.vintage:junit-vintage-engine:5.10.5")
// testFixturesImplementation("org.junit.vintage:junit-vintage-engine:5.10.3")
}
@ -210,6 +214,8 @@ tasks {
}
compileTestKotlin {
enabled = false
kotlinOptions {
jvmTarget = javaVersion
apiVersion = "1.9"
@ -229,6 +235,7 @@ tasks {
// Uncomment to run the plugin in a custom IDE, rather than the IDE specified as a compile target in dependencies
// Note that the version must be greater than the plugin's target version, for obvious reasons
// You can also set splitMode and splitModeTarget here to test split mode in a custom IDE
// val runIdeCustom by intellijPlatformTesting.runIde.registering {
// type = IntelliJPlatformType.Rider
// version = "2024.1.2"
@ -261,10 +268,6 @@ tasks {
val runIdeSplitMode by intellijPlatformTesting.runIde.registering {
splitMode = true
splitModeTarget = SplitModeAware.SplitModeTarget.FRONTEND
// Frontend split mode support requires 242+
// TODO: Remove this once IdeaVim targets 242, as the task will naturally use the target version to run
version.set(splitModeVersion)
}
// Add plugin open API sources to the plugin ZIP

View File

@ -1,6 +1,6 @@
Welcome to the IdeaVim wiki!
- List of IdeaVim plugins: [plugins](IdeaVim%20Plugins)
- Examples of `ideajoin` option (also known as "smart join"): ["ideajoin" examples](ideajoin-examples)
- List of "set" commands: ["set" commands](set-commands)
- Docs about "select" mode in vim: [select mode](Select-mode)
- List of IdeaVim plugins: [plugins](IdeaVim%20Plugins.md)
- Examples of `ideajoin` option (also known as "smart join"): ["ideajoin" examples](ideajoin-examples.md)
- List of "set" commands: ["set" commands](set-commands.md)
- Docs about "select" mode in vim: [select mode](Select-mode.md)

View File

@ -82,7 +82,7 @@ Original plugin: [NERDTree](https://github.com/preservim/nerdtree).
### Instructions
[See here](NERDTree-support).
[See here](NERDTree-support.md).
</details>
@ -321,7 +321,10 @@ If you want to optimize highlight duration, assign a time in milliseconds:
If you want to change background color of highlight you can provide the rgba of the color you want e.g.
`let g:highlightedyank_highlight_color = "rgba(160, 160, 160, 155)"`
If you want to change text color of highlight you can provide the rgba of the color you want e.g.
`let g:highlightedyank_highlight_foreground_color = "rgba(0, 0, 0, 255)"`
https://github.com/machakann/vim-highlightedyank/blob/master/doc/highlightedyank.txt
</details>
@ -435,3 +438,50 @@ Original plugin: [vim-which-key](https://github.com/liuchengxu/vim-which-key).
https://github.com/TheBlob42/idea-which-key?tab=readme-ov-file#installation
</details>
<details>
<summary><h2>Vim Peekaboo</h2></summary>
By Julien Phalip
Original plugin: [vim-peekaboo](https://github.com/junegunn/vim-peekaboo).
### Setup
Add `set peekaboo` to your `~/.ideavimrc` file, then run `:source ~/.ideavimrc`
or restart the IDE.
### Instructions
https://plugins.jetbrains.com/plugin/25776-vim-peekaboo
</details>
<details>
<summary><h2>FunctionTextObj</h2></summary>
By Julien Phalip
### Setup
Add `set functiontextobj` to your `~/.ideavimrc` file, then run `:source ~/.ideavimrc`
or restart the IDE.
### Instructions
https://plugins.jetbrains.com/plugin/25897-vim-functiontextobj
</details>
<details>
<summary><h2>Switch</h2></summary>
By Julien Phalip
Original plugin: [switch.vim](https://github.com/AndrewRadev/switch.vim).
### Setup
Add `set switch` to your `~/.ideavimrc` file, then run `:source ~/.ideavimrc`
or restart the IDE.
### Instructions
https://plugins.jetbrains.com/plugin/25899-vim-switch
</details>

View File

@ -25,7 +25,7 @@ It's not intended to be read by the users as it brings no value to them.
IdeaVim has multiple YouTrack statuses, main are:
- Submitted: issue is created by user, but not processed by our team. This is the default status for new tickets.
- Open: issues is processed by out team, what means that the issues is reproduced and accepted
- Open: issues is processed by our team, what means that the issues is reproduced and accepted
- Waiting For Reply: Waiting for further information from the user. These issues are automatically closed if the
user doesn't reply in 30 days.
- Ready To Release: Bug is fixed, but not yet released

View File

@ -16,21 +16,15 @@
# https://data.services.jetbrains.com/products?code=IC
# Maven releases are here: https://www.jetbrains.com/intellij-repository/releases
# And snapshots: https://www.jetbrains.com/intellij-repository/snapshots
ideaVersion=2024.1.1
ideaVersion=2024.2
# Values for type: https://plugins.jetbrains.com/docs/intellij/tools-gradle-intellij-plugin.html#intellij-extension-type
ideaType=IC
instrumentPluginCode=true
version=SNAPSHOT
version=chylex-42
javaVersion=17
remoteRobotVersion=0.11.23
antlrVersion=4.10.1
# [VERSION UPDATE] 2024.2 - remove when IdeaVim targets 2024.2
# Running IdeaVim in split mode requires 242. Update this version once 242 has been released, and remove it completely
# when IdeaVim targets 242, at which point runIdeSplitMode will run correctly with the target version.
# See also runIdeSplitMode
splitModeVersion=242-EAP-SNAPSHOT
# Please don't forget to update kotlin version in buildscript section
# Also update kotlinxSerializationVersion version
@ -47,7 +41,6 @@ youtrackToken=
# Gradle settings
org.gradle.jvmargs='-Dfile.encoding=UTF-8'
org.gradle.configuration-cache=true
org.gradle.caching=true
# Disable warning from gradle-intellij-plugin. Kotlin stdlib is included as compileOnly, so the warning is unnecessary

Binary file not shown.

View File

@ -1,6 +1,6 @@
distributionBase=GRADLE_USER_HOME
distributionPath=wrapper/dists
distributionUrl=https\://services.gradle.org/distributions/gradle-8.6-bin.zip
distributionUrl=https\://services.gradle.org/distributions/gradle-8.11.1-bin.zip
networkTimeout=10000
validateDistributionUrl=true
zipStoreBase=GRADLE_USER_HOME

22
gradlew vendored
View File

@ -15,6 +15,8 @@
# See the License for the specific language governing permissions and
# limitations under the License.
#
# SPDX-License-Identifier: Apache-2.0
#
##############################################################################
#
@ -55,7 +57,7 @@
# Darwin, MinGW, and NonStop.
#
# (3) This script is generated from the Groovy template
# https://github.com/gradle/gradle/blob/HEAD/subprojects/plugins/src/main/resources/org/gradle/api/internal/plugins/unixStartScript.txt
# https://github.com/gradle/gradle/blob/HEAD/platforms/jvm/plugins-application/src/main/resources/org/gradle/api/internal/plugins/unixStartScript.txt
# within the Gradle project.
#
# You can find Gradle at https://github.com/gradle/gradle/.
@ -83,7 +85,9 @@ done
# This is normally unused
# shellcheck disable=SC2034
APP_BASE_NAME=${0##*/}
APP_HOME=$( cd "${APP_HOME:-./}" && pwd -P ) || exit
# Discard cd standard output in case $CDPATH is set (https://github.com/gradle/gradle/issues/25036)
APP_HOME=$( cd -P "${APP_HOME:-./}" > /dev/null && printf '%s
' "$PWD" ) || exit
# Use the maximum available, or set MAX_FD != -1 to use that value.
MAX_FD=maximum
@ -144,7 +148,7 @@ if ! "$cygwin" && ! "$darwin" && ! "$nonstop" ; then
case $MAX_FD in #(
max*)
# In POSIX sh, ulimit -H is undefined. That's why the result is checked to see if it worked.
# shellcheck disable=SC3045
# shellcheck disable=SC2039,SC3045
MAX_FD=$( ulimit -H -n ) ||
warn "Could not query maximum file descriptor limit"
esac
@ -152,7 +156,7 @@ if ! "$cygwin" && ! "$darwin" && ! "$nonstop" ; then
'' | soft) :;; #(
*)
# In POSIX sh, ulimit -n is undefined. That's why the result is checked to see if it worked.
# shellcheck disable=SC3045
# shellcheck disable=SC2039,SC3045
ulimit -n "$MAX_FD" ||
warn "Could not set maximum file descriptor limit to $MAX_FD"
esac
@ -201,11 +205,11 @@ fi
# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"'
# Collect all arguments for the java command;
# * $DEFAULT_JVM_OPTS, $JAVA_OPTS, and $GRADLE_OPTS can contain fragments of
# shell script including quotes and variable substitutions, so put them in
# double quotes to make sure that they get re-expanded; and
# * put everything else in single quotes, so that it's not re-expanded.
# Collect all arguments for the java command:
# * DEFAULT_JVM_OPTS, JAVA_OPTS, JAVA_OPTS, and optsEnvironmentVar are not allowed to contain shell fragments,
# and any embedded shellness will be escaped.
# * For example: A user cannot expect ${Hostname} to be expanded, as it is an environment variable and will be
# treated as '${Hostname}' itself on the command line.
set -- \
"-Dorg.gradle.appname=$APP_BASE_NAME" \

22
gradlew.bat vendored
View File

@ -13,6 +13,8 @@
@rem See the License for the specific language governing permissions and
@rem limitations under the License.
@rem
@rem SPDX-License-Identifier: Apache-2.0
@rem
@if "%DEBUG%"=="" @echo off
@rem ##########################################################################
@ -43,11 +45,11 @@ set JAVA_EXE=java.exe
%JAVA_EXE% -version >NUL 2>&1
if %ERRORLEVEL% equ 0 goto execute
echo.
echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
echo.
echo Please set the JAVA_HOME variable in your environment to match the
echo location of your Java installation.
echo. 1>&2
echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 1>&2
echo. 1>&2
echo Please set the JAVA_HOME variable in your environment to match the 1>&2
echo location of your Java installation. 1>&2
goto fail
@ -57,11 +59,11 @@ set JAVA_EXE=%JAVA_HOME%/bin/java.exe
if exist "%JAVA_EXE%" goto execute
echo.
echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME%
echo.
echo Please set the JAVA_HOME variable in your environment to match the
echo location of your Java installation.
echo. 1>&2
echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% 1>&2
echo. 1>&2
echo Please set the JAVA_HOME variable in your environment to match the 1>&2
echo location of your Java installation. 1>&2
goto fail

View File

@ -20,17 +20,17 @@ repositories {
}
dependencies {
compileOnly("org.jetbrains.kotlin:kotlin-stdlib:1.9.25")
compileOnly("org.jetbrains.kotlin:kotlin-stdlib:2.1.0")
implementation("io.ktor:ktor-client-core:2.3.12")
implementation("io.ktor:ktor-client-cio:2.3.10")
implementation("io.ktor:ktor-client-content-negotiation:2.3.10")
implementation("io.ktor:ktor-serialization-kotlinx-json:2.3.12")
implementation("io.ktor:ktor-client-auth:2.3.12")
implementation("io.ktor:ktor-client-core:3.0.2")
implementation("io.ktor:ktor-client-cio:3.0.2")
implementation("io.ktor:ktor-client-content-negotiation:3.0.2")
implementation("io.ktor:ktor-serialization-kotlinx-json:3.0.2")
implementation("io.ktor:ktor-client-auth:3.0.2")
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.10.0.202406032230-r")
implementation("org.eclipse.jgit:org.eclipse.jgit.ssh.apache:7.1.0.202411261347-r")
implementation("com.vdurmont:semver4j:3.1.0")
}

View File

@ -40,6 +40,9 @@ val knownPlugins = setOf(
"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
"com.julienphalip.ideavim.peekaboo", // https://plugins.jetbrains.com/plugin/25776-vim-peekaboo
"com.julienphalip.ideavim.switch", // https://plugins.jetbrains.com/plugin/25899-vim-switch
"com.julienphalip.ideavim.functiontextobj", // https://plugins.jetbrains.com/plugin/25897-vim-functiontextobj
)
suspend fun main() {

View File

@ -33,6 +33,9 @@ internal class PluginStartup : ProjectActivity/*, LightEditCompatible*/ {
private var firstInitializationOccurred = false
// TODO
// We should migrate to some solution from https://plugins.jetbrains.com/docs/intellij/plugin-components.html#application-startup
// If you'd like to add a new code here, please consider using one of the things described there.
override suspend fun execute(project: Project) {
if (firstInitializationOccurred) return
firstInitializationOccurred = true

View File

@ -10,6 +10,7 @@ package com.maddyhome.idea.vim;
import com.intellij.ide.plugins.IdeaPluginDescriptor;
import com.intellij.ide.plugins.PluginManagerCore;
import com.intellij.openapi.Disposable;
import com.intellij.openapi.application.AccessToken;
import com.intellij.openapi.application.Application;
import com.intellij.openapi.application.ApplicationManager;
import com.intellij.openapi.components.PersistentStateComponent;
@ -24,10 +25,8 @@ import com.intellij.openapi.project.Project;
import com.intellij.openapi.ui.Messages;
import com.intellij.openapi.util.Disposer;
import com.intellij.openapi.util.SystemInfo;
import com.maddyhome.idea.vim.api.VimEditor;
import com.maddyhome.idea.vim.api.VimInjectorKt;
import com.maddyhome.idea.vim.api.VimKeyGroup;
import com.maddyhome.idea.vim.api.VimOptionGroup;
import com.intellij.util.SlowOperations;
import com.maddyhome.idea.vim.api.*;
import com.maddyhome.idea.vim.config.VimState;
import com.maddyhome.idea.vim.config.migration.ApplicationConfigurationMigrator;
import com.maddyhome.idea.vim.extension.VimExtensionRegistrar;
@ -36,7 +35,6 @@ import com.maddyhome.idea.vim.group.copy.PutGroup;
import com.maddyhome.idea.vim.group.visual.VisualMotionGroup;
import com.maddyhome.idea.vim.helper.MacKeyRepeat;
import com.maddyhome.idea.vim.listener.VimListenerManager;
import com.maddyhome.idea.vim.newapi.IjVimInjector;
import com.maddyhome.idea.vim.newapi.IjVimInjectorKt;
import com.maddyhome.idea.vim.newapi.IjVimSearchGroup;
import com.maddyhome.idea.vim.ui.StatusBarIconFactory;
@ -141,8 +139,8 @@ public class VimPlugin implements PersistentStateComponent<Element>, Disposable
return (MacroGroup)VimInjectorKt.getInjector().getMacro();
}
public static @NotNull DigraphGroup getDigraph() {
return (DigraphGroup)VimInjectorKt.getInjector().getDigraphGroup();
public static @NotNull VimDigraphGroup getDigraph() {
return VimInjectorKt.getInjector().getDigraphGroup();
}
public static @NotNull HistoryGroup getHistory() {
@ -339,7 +337,9 @@ public class VimPlugin implements PersistentStateComponent<Element>, Disposable
// 4) ~/.ideavimrc execution
// Evaluate in the context of the fallback window, to capture local option state, to copy to the first editor window
registerIdeavimrc(VimInjectorKt.getInjector().getFallbackWindow());
try (AccessToken ignore = SlowOperations.knownIssue("VIM-3661")) {
registerIdeavimrc(VimInjectorKt.getInjector().getFallbackWindow());
}
// Turing on should be performed after all commands registration
getSearch().turnOn();

View File

@ -0,0 +1,52 @@
package com.maddyhome.idea.vim.action
import com.intellij.openapi.actionSystem.ActionUpdateThread
import com.intellij.openapi.actionSystem.AnActionEvent
import com.intellij.openapi.command.UndoConfirmationPolicy
import com.intellij.openapi.command.WriteCommandAction
import com.intellij.openapi.fileEditor.TextEditor
import com.intellij.openapi.fileEditor.ex.FileEditorManagerEx
import com.intellij.openapi.project.DumbAwareAction
import com.maddyhome.idea.vim.KeyHandler
import com.maddyhome.idea.vim.api.injector
import com.maddyhome.idea.vim.newapi.IjEditorExecutionContext
import com.maddyhome.idea.vim.newapi.vim
import com.maddyhome.idea.vim.state.mode.Mode
class VimRunLastMacroInOpenFiles : DumbAwareAction() {
override fun update(e: AnActionEvent) {
val lastRegister = injector.macro.lastRegister
val isEnabled = lastRegister != 0.toChar()
e.presentation.isEnabled = isEnabled
e.presentation.text = if (isEnabled) "Run Macro '${lastRegister}' in Open Files" else "Run Last Macro in Open Files"
}
override fun getActionUpdateThread(): ActionUpdateThread {
return ActionUpdateThread.EDT
}
override fun actionPerformed(e: AnActionEvent) {
val project = e.project ?: return
val fileEditorManager = FileEditorManagerEx.getInstanceExIfCreated(project) ?: return
val editors = fileEditorManager.allEditors.filterIsInstance<TextEditor>()
WriteCommandAction.writeCommandAction(project)
.withName(e.presentation.text)
.withGlobalUndo()
.withUndoConfirmationPolicy(UndoConfirmationPolicy.REQUEST_CONFIRMATION)
.run<RuntimeException> {
val reg = injector.macro.lastRegister
for (editor in editors) {
fileEditorManager.openFile(editor.file, true)
val vimEditor = editor.editor.vim
vimEditor.mode = Mode.NORMAL()
KeyHandler.getInstance().reset(vimEditor)
injector.macro.playbackRegister(vimEditor, IjEditorExecutionContext(e.dataContext), reg, 1)
}
}
}
}

View File

@ -298,26 +298,10 @@ class VimShortcutKeyAction : AnAction(), DumbAware/*, LightEditCompatible*/ {
.addAll(getKeyStrokes(KeyEvent.VK_BACK_SPACE, 0, InputEvent.CTRL_DOWN_MASK))
.addAll(getKeyStrokes(KeyEvent.VK_INSERT, 0))
.addAll(getKeyStrokes(KeyEvent.VK_DELETE, 0, InputEvent.CTRL_DOWN_MASK))
.addAll(getKeyStrokes(KeyEvent.VK_UP, 0, InputEvent.CTRL_DOWN_MASK, InputEvent.SHIFT_DOWN_MASK))
.addAll(getKeyStrokes(KeyEvent.VK_DOWN, 0, InputEvent.CTRL_DOWN_MASK, InputEvent.SHIFT_DOWN_MASK))
.addAll(
getKeyStrokes(
KeyEvent.VK_LEFT,
0,
InputEvent.CTRL_DOWN_MASK,
InputEvent.SHIFT_DOWN_MASK,
InputEvent.CTRL_DOWN_MASK or InputEvent.SHIFT_DOWN_MASK,
),
)
.addAll(
getKeyStrokes(
KeyEvent.VK_RIGHT,
0,
InputEvent.CTRL_DOWN_MASK,
InputEvent.SHIFT_DOWN_MASK,
InputEvent.CTRL_DOWN_MASK or InputEvent.SHIFT_DOWN_MASK,
),
)
.addAll(getKeyStrokes(KeyEvent.VK_UP, 0))
.addAll(getKeyStrokes(KeyEvent.VK_DOWN, 0))
.addAll(getKeyStrokes(KeyEvent.VK_LEFT, 0))
.addAll(getKeyStrokes(KeyEvent.VK_RIGHT, 0))
.addAll(
getKeyStrokes(
KeyEvent.VK_HOME,

View File

@ -40,7 +40,7 @@ class DeleteJoinLinesAction : ChangeEditorActionHandler.ConditionalSingleExecuti
): Boolean {
injector.editorGroup.notifyIdeaJoin(editor)
return injector.changeGroup.deleteJoinLines(editor, caret, operatorArguments.count1, false)
return injector.changeGroup.deleteJoinLines(editor, context, caret, operatorArguments.count1, false)
}
override fun execute(

View File

@ -35,7 +35,7 @@ class DeleteJoinLinesSpacesAction : ChangeEditorActionHandler.SingleExecution()
injector.editorGroup.notifyIdeaJoin(editor)
var res = true
editor.nativeCarets().sortedByDescending { it.offset }.forEach { caret ->
if (!injector.changeGroup.deleteJoinLines(editor, caret, operatorArguments.count1, true)) {
if (!injector.changeGroup.deleteJoinLines(editor, context, caret, operatorArguments.count1, true)) {
res = false
}
}

View File

@ -44,6 +44,7 @@ class DeleteJoinVisualLinesAction : VisualOperatorActionHandler.SingleExecution(
val range = caretsAndSelections[caret] ?: return@forEach
if (!injector.changeGroup.deleteJoinRange(
editor,
context,
caret,
range.toVimTextRange(true).normalize(),
false,

View File

@ -44,6 +44,7 @@ class DeleteJoinVisualLinesSpacesAction : VisualOperatorActionHandler.SingleExec
val range = caretsAndSelections[caret] ?: return@forEach
if (!injector.changeGroup.deleteJoinRange(
editor,
context,
caret,
range.toVimTextRange(true).normalize(),
true,

View File

@ -20,6 +20,8 @@ import com.maddyhome.idea.vim.command.OperatorArguments
import com.maddyhome.idea.vim.handler.IdeActionHandler
import com.maddyhome.idea.vim.handler.VimActionHandler
import com.maddyhome.idea.vim.helper.enumSetOf
import com.maddyhome.idea.vim.undo.VimKeyBasedUndoService
import com.maddyhome.idea.vim.undo.VimTimestampBasedUndoService
import java.util.*
@CommandOrMotion(keys = ["<Del>"], modes = [Mode.INSERT])
@ -39,8 +41,13 @@ internal class VimEditorDown : IdeActionHandler(IdeActions.ACTION_EDITOR_MOVE_CA
operatorArguments: OperatorArguments
): Boolean {
val undo = injector.undo
val nanoTime = System.nanoTime()
editor.forEachCaret { undo.endInsertSequence(it, it.offset, nanoTime) }
when (undo) {
is VimKeyBasedUndoService -> undo.setMergeUndoKey()
is VimTimestampBasedUndoService -> {
val nanoTime = System.nanoTime()
editor.forEachCaret { undo.endInsertSequence(it, it.offset, nanoTime) }
}
}
return super.execute(editor, context, cmd, operatorArguments)
}
}
@ -63,8 +70,13 @@ internal class VimEditorUp : IdeActionHandler(IdeActions.ACTION_EDITOR_MOVE_CARE
operatorArguments: OperatorArguments
): Boolean {
val undo = injector.undo
val nanoTime = System.nanoTime()
editor.forEachCaret { undo.endInsertSequence(it, it.offset, nanoTime) }
when (undo) {
is VimKeyBasedUndoService -> undo.setMergeUndoKey()
is VimTimestampBasedUndoService -> {
val nanoTime = System.nanoTime()
editor.forEachCaret { undo.endInsertSequence(it, it.offset, nanoTime) }
}
}
return super.execute(editor, context, cmd, operatorArguments)
}
}

View File

@ -27,6 +27,30 @@ public interface VimExtension {
return MappingOwner.Plugin.Companion.get(getName());
}
/**
* This method is always called AFTER the full execution of the `.ideavimrc` file.
* <p>
* During vim initialization process, it firstly loads the .vimrc file, then executes scripts from the plugins folder.
* This practically means that the .vimrc file is initialized first; then the plugins are loaded.
* See `:h initialization`
* <p>
* Why does this matter? Because this affects the order of commands are executed. For example,
* ```
* plug 'tommcdo/vim-exchange'
* let g:exchange_no_mappings=1
* ```
* Here the user will expect that the exchange plugin won't have default mappings. However, if we load vim-exchange
* immediately, this variable won't be initialized at the moment of plugin initialization.
* <p>
* There is also a tricky case for mappings override:
* ```
* plug 'tommcdo/vim-exchange'
* map X <Plug>(ExchangeLine)
* ```
* For this case, a plugin with a good implementation detects that there is already a defined mapping for
* `<Plug>(ExchangeLine)` and doesn't register the default cxx mapping. However, such detection requires the mapping
* to be defined before the plugin initialization.
*/
void init();
default void dispose() {

View File

@ -188,11 +188,19 @@ object VimExtensionFacade {
/** Get the current contents of the given register similar to 'getreg()'. */
@JvmStatic
@Deprecated("Please use com.maddyhome.idea.vim.extension.VimExtensionFacade.getRegister(com.maddyhome.idea.vim.api.VimEditor, char)")
fun getRegister(register: Char): List<KeyStroke>? {
val reg = VimPlugin.getRegister().getRegister(register) ?: return null
return reg.keys
}
/** Get the current contents of the given register similar to 'getreg()'. */
@JvmStatic
fun getRegister(editor: VimEditor, register: Char): List<KeyStroke>? {
val reg = VimPlugin.getRegister().getRegister(editor, injector.executionContextManager.getEditorExecutionContext(editor), register) ?: return null
return reg.keys
}
@JvmStatic
fun getRegisterForCaret(register: Char, caret: VimCaret): List<KeyStroke>? {
val reg = caret.registerStorage.getRegister(register) ?: return null
@ -277,4 +285,4 @@ fun VimExtensionFacade.exportOperatorFunction(name: String, function: OperatorFu
fun interface ScriptFunction {
fun execute(editor: VimEditor, context: ExecutionContext, args: Map<String, VimDataType>): ExecutionResult
}
}

View File

@ -88,29 +88,10 @@ internal object VimExtensionRegistrar : VimExtensionRegistrator {
}
/**
* During vim initialization process, it firstly loads the .vimrc file, then executes scripts from the plugins folder.
* This practically means that the .vimrc file is initialized first, then the plugins are loaded.
* See `:h initialization`
* See the docs for [VimExtension.init]
*
* In IdeaVim we don't have a separate plugins folder to load it after .ideavimrc load. However, we can collect
* the list of plugins mentioned in the .ideavimrc and load them after .ideavimrc execution is finished.
*
* Why this matters? Because this affects the order of commands are executed. For example:
* ```
* plug 'tommcdo/vim-exchange'
* let g:exchange_no_mappings=1
* ```
* Here the user will expect that the exchange plugin won't have default mappings. However, if we load vim-exchange
* immediately, this variable won't be initialized at the moment of plugin initialization.
*
* There is also a tricky case for mappings override:
* ```
* plug 'tommcdo/vim-exchange'
* map X <Plug>(ExchangeLine)
* ```
* For this case, a plugin with a good implementation detects that there is already a defined mapping for
* `<Plug>(ExchangeLine)` and doesn't register the default cxx mapping. However, such detection requires the mapping
* to be defined before the plugin initialization.
*/
@JvmStatic
fun enableDelayedExtensions() {

View File

@ -222,10 +222,10 @@ internal class VimExchangeExtension : VimExtension {
}
}
val zRegText = getRegister('z')
val unnRegText = getRegister('"')
val startRegText = getRegister('*')
val plusRegText = getRegister('+')
val zRegText = getRegister(editor.vim, 'z')
val unnRegText = getRegister(editor.vim, '"')
val startRegText = getRegister(editor.vim, '*')
val plusRegText = getRegister(editor.vim, '+')
runWriteAction {
// TODO handle:
// " Compare using =~ because "'==' != 0" returns 0
@ -299,7 +299,7 @@ internal class VimExchangeExtension : VimExtension {
private fun getExchange(editor: Editor, isVisual: Boolean, selectionType: SelectionType): Exchange {
// TODO: improve KeyStroke list to sting conversion
fun getRegisterText(reg: Char): String = getRegister(reg)?.map { it.keyChar }?.joinToString("") ?: ""
fun getRegisterText(reg: Char): String = getRegister(editor.vim, reg)?.map { it.keyChar }?.joinToString("") ?: ""
fun getMarks(isVisual: Boolean): Pair<Mark, Mark> {
val (startMark, endMark) =
if (isVisual) {
@ -313,9 +313,9 @@ internal class VimExchangeExtension : VimExtension {
return Pair(marks.getMark(vimEditor.primaryCaret(), startMark)!!, marks.getMark(vimEditor.primaryCaret(), endMark)!!)
}
val unnRegText = getRegister('"')
val starRegText = getRegister('*')
val plusRegText = getRegister('+')
val unnRegText = getRegister(editor.vim, '"')
val starRegText = getRegister(editor.vim, '*')
val plusRegText = getRegister(editor.vim, '+')
val (selectionStart, selectionEnd) = getMarks(isVisual)
if (isVisual) {

View File

@ -21,10 +21,7 @@ import com.intellij.openapi.editor.markup.TextAttributes
import com.intellij.openapi.util.Disposer
import com.intellij.util.Alarm
import com.intellij.util.Alarm.ThreadToUse
import com.jetbrains.rd.util.first
import com.maddyhome.idea.vim.VimPlugin
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.common.ModeChangeListener
@ -48,6 +45,10 @@ private val HIGHLIGHT_DURATION_VARIABLE_NAME = "highlightedyank_highlight_durati
@NonNls
private val HIGHLIGHT_COLOR_VARIABLE_NAME = "highlightedyank_highlight_color"
@NonNls
private val HIGHLIGHT_FOREGROUND_COLOR_VARIABLE_NAME = "highlightedyank_highlight_foreground_color"
private var defaultHighlightTextColor: Color? = null
private fun getDefaultHighlightTextColor(): Color {
@ -78,6 +79,9 @@ internal class HighlightColorResetter : LafManagerListener {
* if you want to change background color of highlight you can provide the rgba of the color you want e.g.
* let g:highlightedyank_highlight_color = "rgba(160, 160, 160, 155)"
*
* if you want to change text color of highlight you can provide the rgba of the color you want e.g.
* let g:highlightedyank_highlight_foreground_color = "rgba(0, 0, 0, 255)"
*
* When a new text is yanked or user starts editing, the old highlighting would be deleted.
*/
internal class VimHighlightedYank : VimExtension, VimYankListener, ModeChangeListener {
@ -117,9 +121,9 @@ internal class VimHighlightedYank : VimExtension, VimYankListener, ModeChangeLis
initialised = false
}
override fun yankPerformed(caretToRange: Map<ImmutableVimCaret, TextRange>) {
override fun yankPerformed(editor: VimEditor, range: TextRange) {
ensureInitialised()
highlightHandler.highlightYankRange(caretToRange)
highlightHandler.highlightYankRange(editor.ij, range)
}
override fun modeChanged(editor: VimEditor, oldMode: Mode) {
@ -140,25 +144,22 @@ internal class VimHighlightedYank : VimExtension, VimYankListener, ModeChangeLis
private var lastEditor: Editor? = null
private val highlighters = mutableSetOf<RangeHighlighter>()
fun highlightYankRange(caretToRange: Map<ImmutableVimCaret, TextRange>) {
fun highlightYankRange(editor: Editor, range: TextRange) {
// from vim-highlightedyank docs: When a new text is yanked or user starts editing, the old highlighting would be deleted
clearYankHighlighters()
val editor = caretToRange.first().key.editor.ij
lastEditor = editor
val attributes = getHighlightTextAttributes(editor)
for (range in caretToRange.values) {
for (i in 0 until range.size()) {
val highlighter = editor.markupModel.addRangeHighlighter(
range.startOffsets[i],
range.endOffsets[i],
HighlighterLayer.SELECTION,
attributes,
HighlighterTargetArea.EXACT_RANGE,
)
highlighters.add(highlighter)
}
for (i in 0 until range.size()) {
val highlighter = editor.markupModel.addRangeHighlighter(
range.startOffsets[i],
range.endOffsets[i],
HighlighterLayer.SELECTION,
attributes,
HighlighterTargetArea.EXACT_RANGE,
)
highlighters.add(highlighter)
}
// from vim-highlightedyank docs: A negative number makes the highlight persistent.
@ -187,13 +188,15 @@ internal class VimHighlightedYank : VimExtension, VimYankListener, ModeChangeLis
highlighters.clear()
}
private fun getHighlightTextAttributes(editor: Editor) = TextAttributes(
null,
extractUsersHighlightColor(),
editor.colorsScheme.getColor(EditorColors.CARET_COLOR),
EffectType.SEARCH_MATCH,
Font.PLAIN,
)
private fun getHighlightTextAttributes(editor: Editor): TextAttributes {
return TextAttributes(
extractUserHighlightForegroundColor(),
extractUsersHighlightColor(),
editor.colorsScheme.getColor(EditorColors.CARET_COLOR),
EffectType.SEARCH_MATCH,
Font.PLAIN,
)
}
private fun extractUsersHighlightDuration(): Int {
return extractVariable(HIGHLIGHT_DURATION_VARIABLE_NAME, DEFAULT_HIGHLIGHT_DURATION) {
@ -206,15 +209,52 @@ internal class VimHighlightedYank : VimExtension, VimYankListener, ModeChangeLis
}
private fun extractUsersHighlightColor(): Color {
return extractVariable(HIGHLIGHT_COLOR_VARIABLE_NAME, getDefaultHighlightTextColor()) { value ->
val rgba = value.asString()
.substring(4)
.filter { it != '(' && it != ')' && !it.isWhitespace() }
.split(',')
.map { it.toInt() }
Color(rgba[0], rgba[1], rgba[2], rgba[3])
val value = VimPlugin.getVariableService().getGlobalVariableValue(HIGHLIGHT_COLOR_VARIABLE_NAME)
if (value != null) {
return try {
parseRgbaColor(value.asString())
} catch (e: Exception) {
@VimNlsSafe val message = MessageHelper.message(
"highlightedyank.invalid.value.of.0.1",
"g:$HIGHLIGHT_COLOR_VARIABLE_NAME",
e.message ?: "",
)
VimPlugin.showMessage(message)
getDefaultHighlightTextColor()
}
}
return getDefaultHighlightTextColor()
}
private fun extractUserHighlightForegroundColor(): Color? {
val value = VimPlugin.getVariableService().getGlobalVariableValue(HIGHLIGHT_FOREGROUND_COLOR_VARIABLE_NAME)
?: return null
return try {
parseRgbaColor(value.asString())
} catch (e: Exception) {
@VimNlsSafe val message = MessageHelper.message(
"highlightedyank.invalid.value.of.0.1",
"g:$HIGHLIGHT_FOREGROUND_COLOR_VARIABLE_NAME",
e.message ?: "",
)
VimPlugin.showMessage(message)
null
}
}
private fun parseRgbaColor(colorString: String): Color {
val rgba = colorString
.substring(4)
.filter { it != '(' && it != ')' && !it.isWhitespace() }
.split(',')
.map { it.toInt() }
if (rgba.size != 4 || rgba.any { it < 0 || it > 255 }) {
throw IllegalArgumentException("Invalid RGBA values. Each component must be between 0 and 255")
}
return Color(rgba[0], rgba[1], rgba[2], rgba[3])
}
private fun <T> extractVariable(variable: String, default: T, extractFun: (value: VimDataType) -> T): T {

View File

@ -230,7 +230,7 @@ private object FileTypePatterns {
} else if (fileTypeName == "CMakeLists.txt" || fileName == "CMakeLists") {
this.cMakePatterns
} else {
return null
this.htmlPatterns
}
}

View File

@ -42,13 +42,10 @@ import com.maddyhome.idea.vim.extension.VimExtension
import com.maddyhome.idea.vim.group.KeyGroup
import com.maddyhome.idea.vim.helper.MessageHelper
import com.maddyhome.idea.vim.helper.runAfterGotFocus
import com.maddyhome.idea.vim.key.CommandNode
import com.maddyhome.idea.vim.key.CommandPartNode
import com.maddyhome.idea.vim.key.KeyStrokeTrie
import com.maddyhome.idea.vim.key.MappingOwner
import com.maddyhome.idea.vim.key.Node
import com.maddyhome.idea.vim.key.RequiredShortcut
import com.maddyhome.idea.vim.key.RootNode
import com.maddyhome.idea.vim.key.addLeafs
import com.maddyhome.idea.vim.key.add
import com.maddyhome.idea.vim.newapi.ij
import com.maddyhome.idea.vim.newapi.vim
import com.maddyhome.idea.vim.vimscript.model.datatypes.VimString
@ -198,6 +195,8 @@ internal class NerdTree : VimExtension {
internal var waitForSearch = false
internal var speedSearchListenerInstalled = false
private val keys = mutableListOf<KeyStroke>()
override fun actionPerformed(e: AnActionEvent) {
var keyStroke = getKeyStroke(e) ?: return
val keyChar = keyStroke.keyChar
@ -205,20 +204,14 @@ internal class NerdTree : VimExtension {
keyStroke = KeyStroke.getKeyStroke(keyChar)
}
val nextNode = currentNode[keyStroke]
when (nextNode) {
null -> currentNode = actionsRoot
is CommandNode<NerdAction> -> {
currentNode = actionsRoot
val action = nextNode.actionHolder
when (action) {
is NerdAction.ToIj -> Util.callAction(null, action.name, e.dataContext.vim)
is NerdAction.Code -> e.project?.let { action.action(it, e.dataContext, e) }
}
keys.add(keyStroke)
actionsRoot.getData(keys)?.let { action ->
when (action) {
is NerdAction.ToIj -> Util.callAction(null, action.name, e.dataContext.vim)
is NerdAction.Code -> e.project?.let { action.action(it, e.dataContext, e) }
}
is CommandPartNode<NerdAction> -> currentNode = nextNode
keys.clear()
}
}
@ -540,38 +533,29 @@ private fun addCommand(alias: String, handler: CommandAliasHandler) {
VimPlugin.getCommand().setAlias(alias, CommandAlias.Call(0, -1, alias, handler))
}
private fun registerCommand(variable: String, default: String, action: NerdAction) {
private fun registerCommand(variable: String, defaultMapping: String, action: NerdAction) {
val variableValue = VimPlugin.getVariableService().getGlobalVariableValue(variable)
val mappings = if (variableValue is VimString) {
val mapping = if (variableValue is VimString) {
variableValue.value
} else {
default
defaultMapping
}
actionsRoot.addLeafs(mappings, action)
registerCommand(mapping, action)
}
private fun registerCommand(default: String, action: NerdAction) {
actionsRoot.addLeafs(default, action)
}
private val actionsRoot: RootNode<NerdAction> = RootNode("NERDTree")
private var currentNode: CommandPartNode<NerdAction> = actionsRoot
private fun collectShortcuts(node: Node<NerdAction>): Set<KeyStroke> {
return if (node is CommandPartNode<NerdAction>) {
val res = node.children.keys.toMutableSet()
res += node.children.values.map { collectShortcuts(it) }.flatten()
res
} else {
emptySet()
private fun registerCommand(mapping: String, action: NerdAction) {
actionsRoot.add(mapping, action)
injector.parser.parseKeys(mapping).forEach {
distinctShortcuts.add(it)
}
}
private val actionsRoot: KeyStrokeTrie<NerdAction> = KeyStrokeTrie<NerdAction>("NERDTree")
private val distinctShortcuts = mutableSetOf<KeyStroke>()
private fun installDispatcher(project: Project) {
val dispatcher = NerdTree.NerdDispatcher.getInstance(project)
val shortcuts =
collectShortcuts(actionsRoot).map { RequiredShortcut(it, MappingOwner.Plugin.get(NerdTree.pluginName)) }
val shortcuts = distinctShortcuts.map { RequiredShortcut(it, MappingOwner.Plugin.get(NerdTree.pluginName)) }
dispatcher.registerCustomShortcutSet(
KeyGroup.toShortcutSet(shortcuts),
(ProjectView.getInstance(project) as ProjectViewImpl).component,

View File

@ -62,7 +62,7 @@ internal class ParagraphMotion : VimExtension {
toKeys: List<KeyStroke>,
recursive: Boolean,
) {
val filteredModes = modes.filterNotTo(HashSet()) { VimPlugin.getKey().hasmapfrom(it, fromKeys) }
val filteredModes = modes.filterNotTo(HashSet()) { VimPlugin.getKey().getKeyMapping(it).getLayer(fromKeys) != null }
putKeyMappingIfMissing(filteredModes, fromKeys, pluginOwner, toKeys, recursive)
}
}

View File

@ -34,6 +34,7 @@ import com.maddyhome.idea.vim.extension.VimExtensionFacade.putKeyMapping
import com.maddyhome.idea.vim.extension.VimExtensionHandler
import com.maddyhome.idea.vim.helper.StrictMode
import com.maddyhome.idea.vim.newapi.ij
import org.jetbrains.annotations.TestOnly
import java.awt.Font
import java.awt.event.KeyEvent
import java.util.*
@ -45,15 +46,26 @@ private const val DEFAULT_HIGHLIGHT_DURATION_SNEAK = 300
// By [Mikhail Levchenko](https://github.com/Mishkun)
// Original repository with the plugin: https://github.com/Mishkun/ideavim-sneak
internal class IdeaVimSneakExtension : VimExtension {
@Suppress("CompanionObjectInExtension")
companion object {
private var highlightHandler: HighlightHandler? = null
@TestOnly
internal fun stopTimer() {
highlightHandler?.stopExistingTimer()
}
}
override fun getName(): String = "sneak"
override fun init() {
val highlightHandler = HighlightHandler()
mapToFunctionAndProvideKeys("s", SneakHandler(highlightHandler, Direction.FORWARD), MappingMode.NXO)
val _highlightHandler = HighlightHandler()
highlightHandler = _highlightHandler
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)
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"), MappingMode.NXO)
@ -61,8 +73,8 @@ internal class IdeaVimSneakExtension : VimExtension {
mapToFunctionAndProvideKeys("t", SneakMemoryHandler("t"), MappingMode.NXO)
mapToFunctionAndProvideKeys("T", SneakMemoryHandler("T"), MappingMode.NXO)
mapToFunctionAndProvideKeys(";", SneakRepeatHandler(highlightHandler, RepeatDirection.IDENTICAL), MappingMode.NXO)
mapToFunctionAndProvideKeys(",", SneakRepeatHandler(highlightHandler, RepeatDirection.REVERSE), MappingMode.NXO)
mapToFunctionAndProvideKeys(";", SneakRepeatHandler(_highlightHandler, RepeatDirection.IDENTICAL), MappingMode.NXO)
mapToFunctionAndProvideKeys(",", SneakRepeatHandler(_highlightHandler, RepeatDirection.REVERSE), MappingMode.NXO)
}
private class SneakHandler(
@ -209,6 +221,7 @@ internal class IdeaVimSneakExtension : VimExtension {
private class HighlightHandler {
private var editor: Editor? = null
private val sneakHighlighters: MutableSet<RangeHighlighter> = mutableSetOf()
private var timer: Timer? = null
fun highlightSneakRange(editor: Editor, range: TextRange) {
clearAllSneakHighlighters()
@ -254,13 +267,19 @@ internal class IdeaVimSneakExtension : VimExtension {
}
private fun setClearHighlightRangeTimer(highlighter: RangeHighlighter) {
val timer = Timer(DEFAULT_HIGHLIGHT_DURATION_SNEAK) {
stopExistingTimer()
timer = Timer(DEFAULT_HIGHLIGHT_DURATION_SNEAK) {
if (editor?.isDisposed != true) {
editor?.markupModel?.removeHighlighter(highlighter)
}
}
timer.isRepeats = false
timer.start()
timer?.isRepeats = false
timer?.start()
}
fun stopExistingTimer() {
timer?.stop()
timer?.actionListeners?.forEach { it.actionPerformed(null) }
}
private fun getHighlightTextAttributes() = TextAttributes(
@ -307,7 +326,7 @@ private fun VimExtension.mapToFunctionAndProvideKeys(
VimPlugin.getKey().hasmapto(it, injector.parser.parseKeys(commandFromOriginalPlugin(keys)))
}
val filteredFromModes = mappingModes.filterNotTo(HashSet()) {
injector.keyGroup.hasmapfrom(it, fromKeys)
injector.keyGroup.getKeyMapping(it).getLayer(fromKeys) != null
}
val doubleFiltered = mappingModes

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

@ -14,6 +14,7 @@ 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
@ -36,18 +37,21 @@ import com.maddyhome.idea.vim.extension.VimExtensionFacade.setRegisterForCaret
import com.maddyhome.idea.vim.extension.exportOperatorFunction
import com.maddyhome.idea.vim.group.findBlockRange
import com.maddyhome.idea.vim.helper.exitVisualMode
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
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.returnTo
import com.maddyhome.idea.vim.state.mode.selectionType
import org.jetbrains.annotations.NonNls
import java.awt.event.KeyEvent
import javax.swing.KeyStroke
import com.maddyhome.idea.vim.state.mode.returnTo
/**
* Port of vim-surround.
@ -80,7 +84,7 @@ internal class VimSurroundExtension : VimExtension {
putKeyMappingIfMissing(MappingMode.XO, injector.parser.parseKeys("S"), owner, injector.parser.parseKeys("<Plug>VSurround"), true)
}
VimExtensionFacade.exportOperatorFunction(OPERATOR_FUNC, Operator())
VimExtensionFacade.exportOperatorFunction(OPERATOR_FUNC, Operator(supportsMultipleCursors = false, count = 1)) // TODO
}
private class YSurroundHandler : ExtensionHandler {
@ -108,7 +112,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)
}
@ -131,15 +135,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
editor.exitVisualMode()
editor.ij.caretModel.moveToOffset(selectionStart)
}
}
}
@ -159,13 +161,17 @@ internal class VimSurroundExtension : VimExtension {
}
companion object {
fun change(editor: VimEditor, context: ExecutionContext, charFrom: Char, newSurround: Pair<String, String>?) {
fun change(editor: VimEditor, context: ExecutionContext, charFrom: Char, newSurround: SurroundPair?) {
editor.ij.runWithEveryCaretAndRestore { changeAtCaret(editor, context, charFrom, newSurround) }
}
fun changeAtCaret(editor: VimEditor, context: ExecutionContext, charFrom: Char, newSurround: SurroundPair?) {
// Save old register values for carets
val surroundings = editor.sortedCarets()
.map {
val oldValue: List<KeyStroke>? = getRegisterForCaret(REGISTER, it)
setRegisterForCaret(REGISTER, it, null)
SurroundingInfo(it, null, oldValue)
SurroundingInfo(it, null, oldValue, false)
}
// Delete surrounding's content
@ -181,21 +187,25 @@ internal class VimSurroundExtension : VimExtension {
}
val registerValue = getRegisterForCaret(REGISTER, it.caret)
val innerValue = if (registerValue.isNullOrEmpty()) null else registerValue
val innerValue = if (registerValue.isNullOrEmpty()) emptyList() else registerValue
it.innerText = innerValue
}
surroundings.forEach {
if (it.innerText == null && getRegisterForCaret(REGISTER, it.caret)?.isNotEmpty() == true) {
it.innerText = emptyList()
// Valid surroundings are only those that:
// - are validly wrapping with surround characters (i.e. parenthesis, brackets, tags, quotes, etc.);
// - or have non-empty inner text (e.g. when we are surrounding words: `csw"`)
if (currentSurrounding != null || innerValue.isNotEmpty()) {
it.isValidSurrounding = true
}
}
surroundings
.filter { it.innerText != null } // we do nothing with carets that are not inside the surrounding
.filter { it.isValidSurrounding } // we do nothing with carets that are not inside the surrounding
.map { surrounding ->
val innerValue = injector.parser.toPrintableString(surrounding.innerText!!)
val text = newSurround?.let { it.first + innerValue + it.second } ?: innerValue
val text = newSurround?.let {
val trimmedValue = if (newSurround.shouldTrim) innerValue.trim() else innerValue
it.first + trimmedValue + it.second
} ?: innerValue
val textData = PutData.TextData(text, SelectionType.CHARACTER_WISE, emptyList(), null)
val putData = PutData(textData, null, 1, insertTextBeforeCaret = true, rawIndent = true, caretAfterInsertedText = false)
@ -248,7 +258,7 @@ internal class VimSurroundExtension : VimExtension {
}
}
private data class SurroundingInfo(val caret: VimCaret, var innerText: List<KeyStroke>?, val oldRegisterContent: List<KeyStroke>?) {
private data class SurroundingInfo(val caret: VimCaret, var innerText: List<KeyStroke>?, val oldRegisterContent: List<KeyStroke>?, var isValidSurrounding: Boolean) {
fun restoreRegister() {
setRegisterForCaret(REGISTER, caret, oldRegisterContent)
}
@ -267,20 +277,41 @@ internal class VimSurroundExtension : VimExtension {
}
}
private class Operator : OperatorFunction {
override fun apply(editor: VimEditor, context: ExecutionContext, selectionType: SelectionType?): Boolean {
val ijEditor = editor.ij
private class Operator(private val supportsMultipleCursors: Boolean, private val count: Int) : OperatorFunction {
override fun apply(vimEditor: VimEditor, context: ExecutionContext, selectionType: SelectionType?): Boolean {
val ijEditor = vimEditor.ij
val c = getChar(ijEditor)
if (c.code == 0) return true
val pair = getOrInputPair(c, ijEditor, context.ij) ?: 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)
// Jump back to start
executeNormalWithoutMapping(injector.parser.parseKeys("`["), ijEditor)
runWriteAction {
val change = VimPlugin.getChange()
if (supportsMultipleCursors) {
ijEditor.runWithEveryCaretAndRestore {
applyOnce(ijEditor, change, pair, count)
}
}
else {
applyOnce(ijEditor, change, pair, count)
// Jump back to start
executeNormalWithoutMapping(injector.parser.parseKeys("`["), ijEditor)
}
}
return true
}
private fun applyOnce(editor: Editor, change: VimChangeGroup, pair: SurroundPair, 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
@ -302,33 +333,35 @@ private const val REGISTER = '"'
private const val OPERATOR_FUNC = "SurroundOperatorFunc"
private val tagNameAndAttributesCapturePattern = "(\\S+)([^>]*)>".toPattern()
private val tagNameAndAttributesCapturePattern = "(\\S+)([^>]*)>".toPattern()
private data class SurroundPair(val first: String, val second: String, val shouldTrim: Boolean)
private val SURROUND_PAIRS = mapOf(
'b' to ("(" to ")"),
'(' to ("( " to " )"),
')' to ("(" to ")"),
'B' to ("{" to "}"),
'{' to ("{ " to " }"),
'}' to ("{" to "}"),
'r' to ("[" to "]"),
'[' to ("[ " to " ]"),
']' to ("[" to "]"),
'a' to ("<" to ">"),
'>' to ("<" to ">"),
's' to (" " to ""),
'b' to SurroundPair("(", ")", false),
'(' to SurroundPair("( ", " )", false),
')' to SurroundPair("(", ")", true),
'B' to SurroundPair("{", "}", false),
'{' to SurroundPair("{ ", " }", false),
'}' to SurroundPair("{", "}", true),
'r' to SurroundPair("[", "]", false),
'[' to SurroundPair("[ ", " ]", false),
']' to SurroundPair("[", "]", true),
'a' to SurroundPair("<", ">", false),
'>' to SurroundPair("<", ">", false),
's' to SurroundPair(" ", "", false),
)
private fun getSurroundPair(c: Char): Pair<String, String>? = if (c in SURROUND_PAIRS) {
private fun getSurroundPair(c: Char): SurroundPair? = if (c in SURROUND_PAIRS) {
SURROUND_PAIRS[c]
} else if (!c.isLetter()) {
val s = c.toString()
s to s
SurroundPair(s, s, false)
} else {
null
}
private fun inputTagPair(editor: Editor, context: DataContext): Pair<String, String>? {
private fun inputTagPair(editor: Editor, context: DataContext): SurroundPair? {
val tagInput = inputString(editor, context, "<", '>')
if (editor.vim.mode is Mode.CMD_LINE) {
editor.vim.mode = editor.vim.mode.returnTo()
@ -337,7 +370,7 @@ private fun inputTagPair(editor: Editor, context: DataContext): Pair<String, Str
return if (matcher.find()) {
val tagName = matcher.group(1)
val tagAttributes = matcher.group(2)
"<$tagName$tagAttributes>" to "</$tagName>"
SurroundPair("<$tagName$tagAttributes>", "</$tagName>", false)
} else {
null
}
@ -347,16 +380,20 @@ private fun inputFunctionName(
editor: Editor,
context: DataContext,
withInternalSpaces: Boolean,
): Pair<String, String>? {
): SurroundPair? {
val functionNameInput = inputString(editor, context, "function: ", null)
if (editor.vim.mode is Mode.CMD_LINE) {
editor.vim.mode = editor.vim.mode.returnTo()
}
if (functionNameInput.isEmpty()) return null
return if (withInternalSpaces) "$functionNameInput( " to " )" else "$functionNameInput(" to ")"
return if (withInternalSpaces) {
SurroundPair("$functionNameInput( ", " )", false)
} else {
SurroundPair("$functionNameInput(", ")", false)
}
}
private fun getOrInputPair(c: Char, editor: Editor, context: DataContext): Pair<String, String>? = when (c) {
private fun getOrInputPair(c: Char, editor: Editor, context: DataContext): SurroundPair? = when (c) {
'<', 't' -> inputTagPair(editor, context)
'f' -> inputFunctionName(editor, context, false)
'F' -> inputFunctionName(editor, context, true)
@ -375,15 +412,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: SurroundPair, 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 {
@ -391,7 +428,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

@ -36,10 +36,12 @@ import com.maddyhome.idea.vim.helper.inInsertMode
import com.maddyhome.idea.vim.key.KeyHandlerKeeper.Companion.getInstance
import com.maddyhome.idea.vim.listener.VimInsertListener
import com.maddyhome.idea.vim.newapi.IjVimCaret
import com.maddyhome.idea.vim.newapi.IjVimCopiedText
import com.maddyhome.idea.vim.newapi.IjVimEditor
import com.maddyhome.idea.vim.newapi.ij
import com.maddyhome.idea.vim.state.mode.Mode
import kotlin.math.min
import com.maddyhome.idea.vim.undo.VimKeyBasedUndoService
import com.maddyhome.idea.vim.undo.VimTimestampBasedUndoService
/**
* Provides all the insert/replace related functionality
@ -62,9 +64,15 @@ class ChangeGroup : VimChangeGroupBase() {
val editor = (vimEditor as IjVimEditor).editor
val ijContext = context.ij
val doc = vimEditor.editor.document
val undo = injector.undo
val nanoTime = System.nanoTime()
vimEditor.forEachCaret { undo.startInsertSequence(it, it.offset, nanoTime) }
when (undo) {
is VimKeyBasedUndoService -> undo.setInsertNonMergeUndoKey()
is VimTimestampBasedUndoService -> {
val nanoTime = System.nanoTime()
vimEditor.forEachCaret { undo.startInsertSequence(it, it.offset, nanoTime) }
}
}
CommandProcessor.getInstance().executeCommand(
editor.project, {
ApplicationManager.getApplication()
@ -130,16 +138,17 @@ class ChangeGroup : VimChangeGroupBase() {
context: ExecutionContext,
range: TextRange,
) {
val startPos = editor.offsetToBufferPosition(caret.offset)
val startOffset = editor.getLineStartForOffset(range.startOffset)
val endOffset = editor.getLineEndForOffset(range.endOffset)
val ijEditor = (editor as IjVimEditor).editor
// FIXME: Here we do selection, and it is not a good idea, because it updates primary selection in Linux
// FIXME: I'll leave here a dirty fix that restores primary selection, but it would be better to rewrite this method
var primaryTextAndTransferableData: Pair<String, List<Any>?>? = null
var copiedText: IjVimCopiedText? = null
try {
if (injector.registerGroup.isPrimaryRegisterSupported()) {
primaryTextAndTransferableData = injector.clipboardManager.getPrimaryTextAndTransferableData()
copiedText = injector.clipboardManager.getPrimaryContent(editor, context) as IjVimCopiedText
}
} catch (e: Exception) {
// FIXME: [isPrimaryRegisterSupported()] is not implemented perfectly, so there might be thrown an exception after trying to access the primary selection
@ -154,11 +163,7 @@ 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) {
@ -169,12 +174,8 @@ class ChangeGroup : VimChangeGroupBase() {
afterAction.invoke()
}
try {
if (primaryTextAndTransferableData != null) {
injector.clipboardManager.setPrimaryText(
primaryTextAndTransferableData.first,
primaryTextAndTransferableData.first,
primaryTextAndTransferableData.second ?: emptyList()
)
if (copiedText != null) {
injector.clipboardManager.setPrimaryContent(editor, context, copiedText)
}
} catch (e: Exception) {
// FIXME: [isPrimaryRegisterSupported()] is not implemented perfectly, so there might be thrown an exception after trying to access the primary selection

View File

@ -1,83 +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.group;
import com.intellij.openapi.diagnostic.Logger;
import com.maddyhome.idea.vim.api.VimDigraphGroupBase;
import com.maddyhome.idea.vim.api.VimEditor;
import com.maddyhome.idea.vim.api.VimOutputPanel;
import com.maddyhome.idea.vim.ex.ExOutputModel;
import com.maddyhome.idea.vim.helper.EditorHelper;
import com.maddyhome.idea.vim.newapi.IjVimEditor;
import org.jetbrains.annotations.NotNull;
import static com.maddyhome.idea.vim.api.VimInjectorKt.injector;
public class DigraphGroup extends VimDigraphGroupBase {
public void showDigraphs(@NotNull VimEditor editor) {
int width = EditorHelper.getApproximateScreenWidth(((IjVimEditor) editor).getEditor());
if (width < 10) {
width = 80;
}
int colCount = width / 12;
int height = (int)Math.ceil((double) getDigraphs().size() / (double)colCount);
if (logger.isDebugEnabled()) {
logger.debug("width=" + width);
logger.debug("colCount=" + colCount);
logger.debug("height=" + height);
}
StringBuilder res = new StringBuilder();
int cnt = 0;
for (Character code : getKeys().keySet()) {
String key = getKeys().get(code);
res.append(key);
res.append(' ');
if (code < 32) {
res.append('^');
res.append((char)(code + '@'));
}
else if (code >= 128 && code <= 159) {
res.append('~');
res.append((char)(code - 128 + '@'));
}
else {
res.append(code);
res.append(' ');
}
res.append(' ');
if (code < 0x1000) {
res.append('0');
}
if (code < 0x100) {
res.append('0');
}
if (code < 0x10) {
res.append('0');
}
res.append(Integer.toHexString(code));
res.append(" ");
cnt++;
if (cnt == colCount) {
res.append('\n');
cnt = 0;
}
}
VimOutputPanel output = injector.getOutputPanel().getOrCreate(editor, injector.getExecutionContextManager().getEditorExecutionContext(editor));
output.addText(res.toString(), true );
output.show();
}
private static final Logger logger = Logger.getInstance(DigraphGroup.class.getName());
}

View File

@ -42,7 +42,6 @@ import com.maddyhome.idea.vim.newapi.IjEditorExecutionContext
import com.maddyhome.idea.vim.newapi.IjVimEditor
import com.maddyhome.idea.vim.newapi.execute
import com.maddyhome.idea.vim.newapi.globalIjOptions
import com.maddyhome.idea.vim.state.VimStateMachine.Companion.getInstance
import com.maddyhome.idea.vim.state.mode.Mode.VISUAL
import java.io.File
import java.util.*

View File

@ -139,7 +139,7 @@ object IjOptions {
// Temporary feature flags during development, not really intended for external use
val closenotebooks: ToggleOption = addOption(ToggleOption("closenotebooks", GLOBAL, "closenotebooks", true, isHidden = true))
val commandOrMotionAnnotation: ToggleOption = addOption(ToggleOption("commandormotionannotation", GLOBAL, "commandormotionannotation", true, isHidden = true))
val oldundo: ToggleOption = addOption(ToggleOption("oldundo", GLOBAL, "oldundo", false, isHidden = true))
val oldundo: ToggleOption = addOption(ToggleOption("oldundo", GLOBAL, "oldundo", true, isHidden = true))
val unifyjumps: ToggleOption = addOption(ToggleOption("unifyjumps", GLOBAL, "unifyjumps", true, isHidden = true))
val vimscriptFunctionAnnotation: ToggleOption = addOption(ToggleOption("vimscriptfunctionannotation", GLOBAL, "vimscriptfunctionannotation", true, isHidden = true))

View File

@ -8,7 +8,6 @@
package com.maddyhome.idea.vim.group;
import com.google.common.collect.ImmutableList;
import com.intellij.codeInsight.lookup.impl.LookupImpl;
import com.intellij.openapi.actionSystem.*;
import com.intellij.openapi.actionSystem.ex.ActionManagerEx;
@ -17,18 +16,16 @@ import com.intellij.openapi.application.ApplicationManager;
import com.intellij.openapi.components.PersistentStateComponent;
import com.intellij.openapi.components.State;
import com.intellij.openapi.components.Storage;
import com.intellij.openapi.diagnostic.Logger;
import com.intellij.openapi.editor.Editor;
import com.intellij.openapi.keymap.Keymap;
import com.intellij.openapi.keymap.KeymapManager;
import com.intellij.openapi.keymap.ex.KeymapManagerEx;
import com.intellij.util.containers.MultiMap;
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.command.MappingMode;
import com.maddyhome.idea.vim.ex.ExOutputModel;
import com.maddyhome.idea.vim.key.*;
import com.maddyhome.idea.vim.newapi.IjNativeAction;
import com.maddyhome.idea.vim.newapi.IjVimEditor;
@ -58,8 +55,6 @@ public class KeyGroup extends VimKeyGroupBase implements PersistentStateComponen
private static final @NonNls String OWNER_ATTRIBUTE = "owner";
private static final String TEXT_ELEMENT = "text";
private static final Logger logger = Logger.getInstance(KeyGroup.class);
public void registerRequiredShortcutKeys(@NotNull VimEditor editor) {
EventFacade.getInstance()
.registerCustomShortcutSet(VimShortcutKeyAction.getInstance(), toShortcutSet(getRequiredShortcutKeys()),
@ -199,8 +194,7 @@ public class KeyGroup extends VimKeyGroupBase implements PersistentStateComponen
registerRequiredShortcut(keyStrokes, MappingOwner.IdeaVim.System.INSTANCE);
for (MappingMode mappingMode : command.getModes()) {
Node<LazyVimCommand> node = getKeyRoot(mappingMode);
NodesKt.addLeafs(node, keyStrokes, command);
getBuiltinCommandsTrie(mappingMode).add(keyStrokes, command);
}
}
}
@ -225,53 +219,79 @@ public class KeyGroup extends VimKeyGroupBase implements PersistentStateComponen
return new CustomShortcutSet(shortcuts.toArray(new Shortcut[0]));
}
private static @NotNull List<Pair<EnumSet<MappingMode>, MappingInfo>> getKeyMappingRows(@NotNull Set<? extends MappingMode> modes) {
final Map<ImmutableList<KeyStroke>, EnumSet<MappingMode>> actualModes = new HashMap<>();
private static @NotNull List<Pair<Set<MappingMode>, MappingInfo>> getKeyMappingRows(@NotNull Set<? extends MappingMode> modes,
@NotNull List<? extends KeyStroke> prefix) {
// Some map commands set a mapping for more than one mode (e.g. `map` sets for Normal, Visual, Select and
// Op-pending). Vim treats this as a single mapping, and when listing all maps only lists it once, with the
// appropriate mode indicator(s) in the first column (NVO is a space char). If the lhs mapping is changed or cleared
// for one of the modes, the original mapping is still a single map for the remaining modes, and the indicator
// changes. E.g. `map foo bar` followed by `sunmap foo` would result in `nox foo bar` in the output to `map`.
// Vim doesn't do automatic grouping - `nmap foo bar` followed by `omap foo bar` and `vmap foo bar` would result in
// 3 lines in the output to `map` - one for `n`, one for `o` and one for `v`.
// We store mappings separately per mode (to simplify lookup, especially when matching prefixes), but want to have
// the same behaviour as Vim in map output. So we store the original modes with the mapping and check they're still
// valid as we collect output
final List<Pair<Set<MappingMode>, MappingInfo>> rows = new ArrayList<>();
final MultiMap<List<? extends KeyStroke>, Set<MappingMode>> multiModeMappings = MultiMap.create();
final List<KeyStroke> fromKeys = new ArrayList<>();
for (MappingMode mode : modes) {
final KeyMapping mapping = VimPlugin.getKey().getKeyMapping(mode);
for (List<? extends KeyStroke> fromKeys : mapping) {
final ImmutableList<KeyStroke> key = ImmutableList.copyOf(fromKeys);
final EnumSet<MappingMode> value = actualModes.get(key);
final EnumSet<MappingMode> newValue;
if (value != null) {
newValue = value.clone();
newValue.add(mode);
final Iterator<KeyMappingEntry> iterator = mapping.getAll(prefix).iterator();
while (iterator.hasNext()) {
final KeyMappingEntry entry = iterator.next();
final MappingInfo mappingInfo = entry.getMappingInfo();
final Set<@NotNull MappingMode> originalModes = mappingInfo.getOriginalModes();
if (originalModes.size() == 1) {
rows.add(new Pair<>(originalModes, mappingInfo));
}
else {
newValue = EnumSet.of(mode);
}
actualModes.put(key, newValue);
}
}
final List<Pair<EnumSet<MappingMode>, MappingInfo>> rows = new ArrayList<>();
for (Map.Entry<ImmutableList<KeyStroke>, EnumSet<MappingMode>> entry : actualModes.entrySet()) {
final ArrayList<KeyStroke> fromKeys = new ArrayList<>(entry.getKey());
final EnumSet<MappingMode> mappingModes = entry.getValue();
if (!mappingModes.isEmpty()) {
final MappingMode mode = mappingModes.iterator().next();
final KeyMapping mapping = VimPlugin.getKey().getKeyMapping(mode);
final MappingInfo mappingInfo = mapping.get(fromKeys);
if (mappingInfo != null) {
rows.add(new Pair<>(mappingModes, mappingInfo));
entry.collectPath(fromKeys);
if (!multiModeMappings.get(fromKeys).contains(originalModes)) {
multiModeMappings.putValue(new ArrayList<>(fromKeys), originalModes);
rows.add(new Pair<>(getModesForMapping(fromKeys, originalModes), mappingInfo));
}
}
}
}
rows.sort(Comparator.comparing(Pair<EnumSet<MappingMode>, MappingInfo>::getSecond));
rows.sort(Comparator.comparing(Pair<Set<MappingMode>, MappingInfo>::getSecond));
return rows;
}
private static @NotNull Set<MappingMode> getModesForMapping(@NotNull List<? extends KeyStroke> keyStrokes,
@NotNull Set<MappingMode> originalMappingModes) {
final Set<MappingMode> actualModes = EnumSet.noneOf(MappingMode.class);
for (MappingMode mode : originalMappingModes) {
final MappingInfo mappingInfo = VimPlugin.getKey().getKeyMapping(mode).get(keyStrokes);
if (mappingInfo != null && mappingInfo.getOriginalModes() == originalMappingModes) {
actualModes.add(mode);
}
}
return actualModes;
}
private static @NotNull @NonNls String getModesStringCode(@NotNull Set<MappingMode> modes) {
if (modes.equals(MappingMode.NVO)) {
return "";
if (modes.equals(MappingMode.IC)) return "!";
if (modes.equals(MappingMode.NVO)) return " ";
if (modes.equals(MappingMode.C)) return "c";
if (modes.equals(MappingMode.I)) return "i";
//if (modes.equals(MappingMode.L)) return "l";
// The following modes are concatenated
String mode = "";
if (modes.containsAll(MappingMode.N)) mode += "n";
if (modes.containsAll(MappingMode.O)) mode += "o";
if (modes.containsAll(MappingMode.V)) {
mode += "v";
}
else if (modes.contains(MappingMode.INSERT)) {
return "i";
else {
if (modes.containsAll(MappingMode.X)) mode += "x";
if (modes.containsAll(MappingMode.S)) mode += "s";
}
else if (modes.contains(MappingMode.NORMAL)) {
return "n";
}
// TODO: Add more codes
return "";
return mode;
}
private @NotNull List<AnAction> getActions(@NotNull Component component, @NotNull KeyStroke keyStroke) {
@ -335,20 +355,20 @@ public class KeyGroup extends VimKeyGroupBase implements PersistentStateComponen
}
@Override
public boolean showKeyMappings(@NotNull Set<? extends MappingMode> modes, @NotNull VimEditor editor) {
List<Pair<EnumSet<MappingMode>, MappingInfo>> rows = getKeyMappingRows(modes);
public boolean showKeyMappings(@NotNull Set<? extends MappingMode> modes, @NotNull List<? extends KeyStroke> prefix, @NotNull VimEditor editor) {
List<Pair<Set<MappingMode>, MappingInfo>> rows = getKeyMappingRows(modes, prefix);
final StringBuilder builder = new StringBuilder();
for (Pair<EnumSet<MappingMode>, MappingInfo> row : rows) {
for (Pair<Set<MappingMode>, MappingInfo> row : rows) {
MappingInfo mappingInfo = row.getSecond();
builder.append(StringsKt.padEnd(getModesStringCode(row.getFirst()), 2, ' '));
builder.append(" ");
builder.append(StringsKt.padEnd(VimInjectorKt.getInjector().getParser().toKeyNotation(mappingInfo.getFromKeys()), 11, ' '));
builder.append(" ");
builder.append(mappingInfo.isRecursive() ? " " : "*");
builder.append(" ");
builder.append(StringsKt.padEnd(getModesStringCode(row.getFirst()), 3, ' '));
builder.append(StringsKt.padEnd(VimInjectorKt.getInjector().getParser().toKeyNotation(mappingInfo.getFromKeys()) + " ", 12, ' '));
builder.append(mappingInfo.isRecursive() ? " " : "*"); // Or `&` if script-local mappings being recursive
builder.append(" "); // Should be `@` if it's a buffer-local mapping
builder.append(mappingInfo.getPresentableString());
builder.append("\n");
}
VimOutputPanel outputPanel = injector.getOutputPanel().getOrCreate(editor, injector.getExecutionContextManager().getEditorExecutionContext(editor));
outputPanel.addText(builder.toString(), true);
outputPanel.show();

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

@ -22,6 +22,7 @@ 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
@ -93,6 +94,9 @@ internal class MacroGroup : VimMacroBase() {
} finally {
keyStack.removeFirst()
}
if (!isInternalMacro) {
MacroAutoImport.run(editor.ij, context.ij)
}
}
if (isInternalMacro) {

View File

@ -12,6 +12,7 @@ import com.intellij.application.options.CodeStyle
import com.intellij.codeStyle.AbstractConvertLineSeparatorsAction
import com.intellij.openapi.Disposable
import com.intellij.openapi.application.ApplicationManager
import com.intellij.openapi.editor.Editor
import com.intellij.openapi.editor.EditorKind
import com.intellij.openapi.editor.EditorSettings.LineNumerationType
import com.intellij.openapi.editor.ScrollPositionCalculator
@ -19,8 +20,6 @@ import com.intellij.openapi.editor.ex.EditorEx
import com.intellij.openapi.editor.ex.EditorSettingsExternalizable
import com.intellij.openapi.editor.impl.softwrap.SoftWrapAppliancePlaces
import com.intellij.openapi.fileEditor.FileDocumentManager
import com.intellij.openapi.fileEditor.FileEditorManagerEvent
import com.intellij.openapi.fileEditor.TextEditor
import com.intellij.openapi.fileEditor.impl.LoadTextUtil
import com.intellij.openapi.fileEditor.impl.text.TextEditorImpl
import com.intellij.openapi.project.Project
@ -158,25 +157,24 @@ internal class OptionGroup : VimOptionGroupBase(), IjVimOptionGroup, InternalOpt
}
companion object {
fun fileEditorManagerSelectionChangedCallback(event: FileEditorManagerEvent) {
// Vim only has one window, and it's not possible to close it. This means that editing a new file will always
// reuse an existing window (opening a new window will always open from an existing window). More importantly,
// this means that any newly edited file will always get up-to-date local-to-window options. A new window is based
// on the opening window (treated as split then edit, so copy local + per-window "global" window values, then
// apply the per-window "global" values) and an edit reapplies the per-window "global" values.
// If we close all windows, and open a new one, we can only use the per-window "global" values from the fallback
// window, but this is only initialised when we first read `~/.ideavimrc` during startup. Vim would use the values
// from the current window, so to simulate this, we should update the fallback window with the values from the
// window that was selected at the time that the last window was closed.
// Unfortunately, we can't reliably know if a closing editor is the selected editor. Instead, we rely on selection
// change events. If an editor is losing selection and there is no new selection, we can assume this means that
// the last editor has been closed, and use the closed editor to update the fallback window
//
// XXX: event.oldEditor will must probably return a disposed editor. So, it should be treated with care
if (event.newEditor == null) {
(event.oldEditor as? TextEditor)?.editor?.let {
(VimPlugin.getOptionGroup() as OptionGroup).updateFallbackWindow(injector.fallbackWindow, it.vim)
}
fun editorReleased(editor: Editor) {
// Vim always has at least one window; it's not possible to close it. Editing a new file will open a new buffer in
// the current window, or it's possible to split the current buffer into a new window, or open a new buffer in a
// new window. This is important for us because when Vim opens a new window, the new window's local options are
// copied from the current window.
// In detail: splitting the current window gets a complete copy of local and per-window global option values.
// Editing a new file will split the current window and then edit the new buffer in-place.
// IntelliJ does not always have an open window. It would be weird to close the last editor tab, and then open
// the next tab with different options - the user would expect the editor to look like the last one did.
// Therefore, we have a dummy "fallback" window that captures the options of the last closed editor. When opening
// an editor and there are no currently open editors, we use the fallback window to initialise the new window.
// This callback tracks when editors are closed, and if the last editor in a project is being closed, updates the
// fallback window's options.
val project = editor.project ?: return
if (!injector.editorGroup.getEditorsRaw()
.any { it.ij != editor && it.ij.project === project && it.ij.editorKind == EditorKind.MAIN_EDITOR }
) {
(VimPlugin.getOptionGroup() as OptionGroup).updateFallbackWindow(injector.fallbackWindow, editor.vim)
}
}
}

View File

@ -14,7 +14,6 @@ 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.api.VimInjectorKt;
import com.maddyhome.idea.vim.newapi.IjVimInjectorKt;
import com.maddyhome.idea.vim.register.Register;
import com.maddyhome.idea.vim.register.VimRegisterGroupBase;

View File

@ -7,7 +7,6 @@
*/
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

View File

@ -51,6 +51,8 @@ import com.maddyhome.idea.vim.state.mode.SelectionType
import com.maddyhome.idea.vim.state.mode.isBlock
import com.maddyhome.idea.vim.state.mode.isChar
import com.maddyhome.idea.vim.state.mode.isLine
import com.maddyhome.idea.vim.undo.VimKeyBasedUndoService
import com.maddyhome.idea.vim.undo.VimTimestampBasedUndoService
import java.awt.datatransfer.DataFlavor
@Service
@ -85,9 +87,15 @@ internal class PutGroup : VimPutBase() {
val context = vimContext.context as DataContext
val carets: MutableMap<Caret, RangeMarker> = mutableMapOf()
if (injector.vimState.mode is Mode.INSERT) {
val undo = injector.undo
val nanoTime = System.nanoTime()
vimEditor.forEachCaret { undo.startInsertSequence(it, it.offset, nanoTime) }
val undo = injector.undo
when (undo) {
is VimKeyBasedUndoService -> undo.setInsertNonMergeUndoKey()
is VimTimestampBasedUndoService -> {
vimEditor.forEachCaret { undo.startInsertSequence(it, it.offset, nanoTime) }
}
}
}
EditorHelper.getOrderedCaretsList(editor).forEach { caret ->
val startOffset =

View File

@ -8,6 +8,7 @@
package com.maddyhome.idea.vim.group.visual
import com.intellij.openapi.application.ApplicationManager
import com.intellij.openapi.diagnostic.Logger
import com.intellij.openapi.diagnostic.trace
import com.intellij.openapi.editor.Editor
@ -15,6 +16,8 @@ import com.maddyhome.idea.vim.KeyHandler
import com.maddyhome.idea.vim.VimPlugin
import com.maddyhome.idea.vim.api.injector
import com.maddyhome.idea.vim.api.options
import com.maddyhome.idea.vim.group.visual.IdeaSelectionControl.controlNonVimSelectionChange
import com.maddyhome.idea.vim.group.visual.IdeaSelectionControl.predictMode
import com.maddyhome.idea.vim.helper.exitSelectMode
import com.maddyhome.idea.vim.helper.exitVisualMode
import com.maddyhome.idea.vim.helper.hasVisualSelection
@ -63,12 +66,15 @@ internal object IdeaSelectionControl {
// - There was no selection and now it is
// - There was a selection and now it doesn't exist
// - There was a selection and now it exists as well (transforming char selection to line selection, for example)
if (initialMode?.hasVisualSelection == false && !editor.selectionModel.hasSelection(true)) {
val hasSelection = ApplicationManager.getApplication().runReadAction<Boolean> {
editor.selectionModel.hasSelection(true)
}
if (initialMode?.hasVisualSelection == false && !hasSelection) {
logger.trace { "Exiting without selection adjusting" }
return@singleTask
}
if (editor.selectionModel.hasSelection(true)) {
if (hasSelection) {
if (editor.vim.inCommandLineMode && editor.vim.mode.returnTo().hasVisualSelection) {
logger.trace { "Modifying selection while in Command-line mode, most likely incsearch" }
return@singleTask

View File

@ -9,8 +9,6 @@
package com.maddyhome.idea.vim.group.visual
import com.maddyhome.idea.vim.api.injector
import com.maddyhome.idea.vim.group.visual.VimVisualTimer.mode
import com.maddyhome.idea.vim.group.visual.VimVisualTimer.singleTask
import com.maddyhome.idea.vim.newapi.globalIjOptions
import com.maddyhome.idea.vim.state.mode.Mode
import java.awt.event.ActionEvent

View File

@ -183,6 +183,7 @@ internal abstract class OctopusHandler(private val nextHandler: EditorActionHand
* - App code - set handler after
* - Template - doesn't intersect with enter anymore
* - rd.client.editor.enter - set handler before. Otherwise, rider will add new line on enter even in normal mode
* - inline.completion.enter - set handler before. Otherwise, AI completion is not invoked on enter.
*
* This rule is disabled due to VIM-3124
* - before terminalEnter - not necessary, but terminalEnter causes "file is read-only" tooltip for readonly files VIM-3122

View File

@ -157,6 +157,19 @@ public class EditorHelper {
return (int)(getVisibleArea(editor).width / getPlainSpaceWidthFloat(editor));
}
/**
* Gets the number of characters that can be fit inside the output panel for an editor.
* <p>
* This will be greater than the approximate screen width as it also includes any gutter components in the editor.
* </p>
*
* @param editor The editor
* @return The approximate number of columns that can fit in the output panel
*/
public static int getApproximateOutputPanelWidth(final @NotNull Editor editor) {
return (int)(editor.getComponent().getWidth() / getPlainSpaceWidthFloat(editor));
}
/**
* Gets the width of the space character in the editor's plain font as a float.
* <p>
@ -273,7 +286,7 @@ public class EditorHelper {
// Scroll the given visual line to the caret location, but do not scroll down passed the end of file, or the current
// virtual space at the bottom of the screen
@NotNull final VimEditor editor1 = new IjVimEditor(editor);
final @NotNull VimEditor editor1 = new IjVimEditor(editor);
final int lastVisualLine = EngineEditorHelperKt.getVisualLineCount(editor1) - 1;
final int yBottomLineOffset = max(getOffsetToScrollVisualLineToBottomOfScreen(editor, lastVisualLine), visibleArea.y);
scrollVertically(editor, min(yVisualLine - caretScreenOffset - inlayOffset, yBottomLineOffset));
@ -325,8 +338,8 @@ public class EditorHelper {
final int lineHeight = editor.getLineHeight();
final int offset = y - ((screenHeight - lineHeight) / lineHeight / 2 * lineHeight);
@NotNull final VimEditor editor1 = new IjVimEditor(editor);
final int lastVisualLine = EngineEditorHelperKt.getVisualLineCount(editor1) - 1;
final @NotNull VimEditor editor1 = new IjVimEditor(editor);
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.
@ -379,7 +392,7 @@ public class EditorHelper {
return 0;
}
private static int getHorizontalScrollbarHeight(@NotNull final Editor editor) {
private static int getHorizontalScrollbarHeight(final @NotNull Editor editor) {
// Horizontal scrollbars on macOS are either transparent AND auto-hide, so we don't need to worry about obscured
// text, or always visible, opaque and outside the content area, so we don't need to adjust for them
// Transparent scrollbars on Windows and Linux are overlays on the editor content area, and always visible. That
@ -462,7 +475,7 @@ public class EditorHelper {
*/
public static Pair<Boolean, Integer> scrollFullPageDown(final @NotNull Editor editor, int pages) {
final Rectangle visibleArea = getVisibleArea(editor);
@NotNull final VimEditor editor2 = new IjVimEditor(editor);
final @NotNull VimEditor editor2 = new IjVimEditor(editor);
final int lastVisualLine = EngineEditorHelperKt.getVisualLineCount(editor2) - 1;
int y = visibleArea.y + visibleArea.height;
@ -480,7 +493,7 @@ public class EditorHelper {
caretVisualLine = lastVisualLine;
}
else {
@NotNull final VimEditor editor1 = new IjVimEditor(editor);
final @NotNull VimEditor editor1 = new IjVimEditor(editor);
caretVisualLine = EngineEditorHelperKt.getVisualLineCount(editor1) - 1;
completed = false;
}
@ -515,7 +528,7 @@ public class EditorHelper {
public static Pair<Boolean, Integer> scrollFullPageUp(final @NotNull Editor editor, int pages) {
final Rectangle visibleArea = getVisibleArea(editor);
final int lineHeight = editor.getLineHeight();
@NotNull final VimEditor editor1 = new IjVimEditor(editor);
final @NotNull VimEditor editor1 = new IjVimEditor(editor);
final int lastVisualLine = EngineEditorHelperKt.getVisualLineCount(editor1) - 1;
int y = visibleArea.y;

View File

@ -12,6 +12,7 @@ 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
@ -20,6 +21,8 @@ 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
@ -96,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

@ -20,6 +20,7 @@ import com.intellij.openapi.actionSystem.PlatformDataKeys
import com.intellij.openapi.actionSystem.ex.ActionUtil
import com.intellij.openapi.actionSystem.ex.ActionUtil.performDumbAwareWithCallbacks
import com.intellij.openapi.actionSystem.impl.ProxyShortcutSet
import com.intellij.openapi.actionSystem.impl.Utils
import com.intellij.openapi.application.ex.ApplicationManagerEx
import com.intellij.openapi.command.CommandProcessor
import com.intellij.openapi.command.UndoConfirmationPolicy
@ -74,7 +75,7 @@ internal class IjActionExecutor : VimActionExecutor {
val applicationEx = ApplicationManagerEx.getApplicationEx()
if (ProgressIndicatorUtils.isWriteActionRunningOrPending(applicationEx)) {
// This is needed for VIM-3376 and it should turn into error at soeme moment
thisLogger().warn(RuntimeException("Actions cannot be updated when write-action is running or pending", ))
thisLogger().warn("Actions cannot be updated when write-action is running or pending")
}
val ijAction = (action as IjNativeAction).action
@ -95,6 +96,7 @@ internal class IjActionExecutor : VimActionExecutor {
ActionManager.getInstance(),
0,
)
Utils.initUpdateSession(event)
// beforeActionPerformedUpdate should be called to update the action. It fixes some rider-specific problems.
// because rider use async update method. See VIM-1819.
// This method executes inside of lastUpdateAndCheckDumb

View File

@ -13,7 +13,6 @@ import com.intellij.openapi.editor.ReadOnlyFragmentModificationException
import com.intellij.openapi.editor.VisualPosition
import com.intellij.openapi.editor.actionSystem.EditorActionManager
import com.intellij.openapi.editor.ex.util.EditorUtil
import com.maddyhome.idea.vim.api.EngineEditorHelper
import com.maddyhome.idea.vim.api.EngineEditorHelperBase
import com.maddyhome.idea.vim.api.VimEditor
import com.maddyhome.idea.vim.api.VimRangeMarker
@ -42,6 +41,10 @@ internal class IjEditorHelper : EngineEditorHelperBase() {
return EditorHelper.getApproximateScreenWidth(editor.ij)
}
override fun getApproximateOutputPanelWidth(editor: VimEditor): Int {
return EditorHelper.getApproximateOutputPanelWidth(editor.ij)
}
override fun handleWithReadonlyFragmentModificationHandler(editor: VimEditor, exception: Exception) {
return EditorActionManager.getInstance()
.getReadonlyFragmentModificationHandler(editor.ij.document)

View File

@ -29,7 +29,6 @@ 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
@ -60,7 +59,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

@ -102,7 +102,8 @@ private fun updateSearchHighlights(
// Update highlights in all visible editors. We update non-visible editors when they get focus.
// Note that this now includes all editors - main, diff windows, even toolwindows like the Commit editor and consoles
val editors = injector.editorGroup.getEditors().filter {
injector.application.isUnitTest() || it.ij.component.isShowing
(injector.application.isUnitTest() || it.ij.component.isShowing)
&& (currentEditor == null || it.projectId == currentEditor.projectId)
}
editors.forEach {

View File

@ -14,7 +14,9 @@ import com.intellij.openapi.command.CommandProcessor
import com.intellij.openapi.command.undo.UndoManager
import com.intellij.openapi.components.Service
import com.intellij.openapi.diagnostic.logger
import com.intellij.openapi.editor.Editor
import com.intellij.openapi.fileEditor.TextEditor
import com.intellij.openapi.fileEditor.TextEditorWithPreview
import com.intellij.openapi.fileEditor.impl.text.TextEditorProvider
import com.intellij.openapi.project.Project
import com.intellij.openapi.util.registry.Registry
@ -28,13 +30,15 @@ import com.maddyhome.idea.vim.common.InsertSequence
import com.maddyhome.idea.vim.newapi.IjVimCaret
import com.maddyhome.idea.vim.newapi.globalIjOptions
import com.maddyhome.idea.vim.newapi.ij
import com.maddyhome.idea.vim.undo.UndoRedoBase
import com.maddyhome.idea.vim.state.mode.SelectionType
import com.maddyhome.idea.vim.state.mode.inVisualMode
import com.maddyhome.idea.vim.undo.VimTimestampBasedUndoService
/**
* @author oleg
*/
@Service
internal class UndoRedoHelper : UndoRedoBase() {
internal class UndoRedoHelper : VimTimestampBasedUndoService {
companion object {
private val logger = logger<UndoRedoHelper>()
}
@ -42,13 +46,13 @@ internal class UndoRedoHelper : UndoRedoBase() {
override fun undo(editor: VimEditor, context: ExecutionContext): Boolean {
val ijContext = context.context as DataContext
val project = PlatformDataKeys.PROJECT.getData(ijContext) ?: return false
val fileEditor = TextEditorProvider.getInstance().getTextEditor(editor.ij)
val textEditor = getTextEditor(editor.ij)
val undoManager = UndoManager.getInstance(project)
if (undoManager.isUndoAvailable(fileEditor)) {
if (undoManager.isUndoAvailable(textEditor)) {
val scrollingModel = editor.getScrollingModel()
scrollingModel.accumulateViewportChanges()
performUndo(editor, undoManager, fileEditor)
performUndo(editor, undoManager, textEditor)
scrollingModel.flushViewportChanges()
@ -57,6 +61,15 @@ internal class UndoRedoHelper : UndoRedoBase() {
return false
}
private fun getTextEditor(editor: Editor): TextEditor {
// If the Editor is hosted in a TextEditor with a preview, then TextEditorProvider will return a TextEditor for the
// hosted instance, not for the main editor that also contains the preview. If we pass the inner TextEditor to the
// UndoManager, it doesn't correctly restore state. Specifically, the change is undone/redone, but the caret is not
// moved. See VIM-3671.
val currentTextEditor = TextEditorProvider.getInstance().getTextEditor(editor)
return TextEditorWithPreview.getParentSplitEditor(currentTextEditor) as? TextEditor ?: currentTextEditor
}
private fun performUndo(
editor: VimEditor,
undoManager: UndoManager,
@ -66,15 +79,7 @@ internal class UndoRedoHelper : UndoRedoBase() {
// TODO refactor me after VIM-308 when restoring selection and caret movement will be ignored by undo
editor.runWithChangeTracking {
undoManager.undo(fileEditor)
// We execute undo one more time if the previous one just restored selection
if (!hasChanges && hasSelection(editor) && undoManager.isUndoAvailable(fileEditor)) {
undoManager.undo(fileEditor)
}
}
CommandProcessor.getInstance().runUndoTransparentAction {
removeSelections(editor)
restoreVisualMode(editor)
}
} else {
notifyAboutNewUndo(editor.ij.project)
@ -108,14 +113,14 @@ internal class UndoRedoHelper : UndoRedoBase() {
private fun hasSelection(editor: VimEditor): Boolean {
return editor.primaryCaret().ij.hasSelection()
}
override fun redo(editor: VimEditor, context: ExecutionContext): Boolean {
val ijContext = context.context as DataContext
val project = PlatformDataKeys.PROJECT.getData(ijContext) ?: return false
val fileEditor = TextEditorProvider.getInstance().getTextEditor(editor.ij)
val textEditor = getTextEditor(editor.ij)
val undoManager = UndoManager.getInstance(project)
if (undoManager.isRedoAvailable(fileEditor)) {
performRedo(undoManager, fileEditor, editor)
if (undoManager.isRedoAvailable(textEditor)) {
performRedo(undoManager, textEditor, editor)
return true
}
@ -229,4 +234,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

@ -18,16 +18,14 @@ import com.intellij.openapi.editor.VisualPosition
import com.intellij.openapi.editor.markup.RangeHighlighter
import com.intellij.openapi.util.Key
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.common.InsertSequence
import com.maddyhome.idea.vim.common.VimEditorReplaceMask
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.common.InsertSequence
import com.maddyhome.idea.vim.common.VimEditorReplaceMask
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
@ -97,7 +95,6 @@ internal var Caret.vimInsertStart: RangeMarker by userDataOr {
}
// TODO: Data could be lost during visual block motion
internal var Caret.registerStorage: CaretRegisterStorageBase? by userDataCaretToEditor()
internal var Caret.markStorage: LocalMarkStorage? by userDataCaretToEditor()
internal var Caret.lastSelectionInfo: SelectionInfo? by userDataCaretToEditor()

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"
fun getInstance(): VimStandalonePluginUpdateChecker = service()
}
}

View File

@ -1,15 +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.key
import com.maddyhome.idea.vim.api.injector
internal fun <T> Node<T>.addLeafs(keys: String, actionHolder: T) {
addLeafs(injector.parser.parseKeys(keys), actionHolder)
}

View File

@ -10,15 +10,12 @@ package com.maddyhome.idea.vim.listener
import com.intellij.execution.impl.ConsoleViewImpl
import com.intellij.openapi.application.ApplicationManager
import com.intellij.openapi.editor.Editor
import com.intellij.openapi.editor.EditorKind
import com.maddyhome.idea.vim.KeyHandler
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
import com.maddyhome.idea.vim.common.EditorListener
import com.maddyhome.idea.vim.helper.EditorHelper
import com.maddyhome.idea.vim.helper.inInsertMode
import com.maddyhome.idea.vim.newapi.ij
import com.maddyhome.idea.vim.state.mode.Mode
@ -31,8 +28,8 @@ import com.maddyhome.idea.vim.state.mode.Mode
*/
class IJEditorFocusListener : EditorListener {
override fun focusGained(editor: VimEditor) {
val oldEditor = KeyHandler.getInstance().editorInFocus
if (oldEditor != null && oldEditor.ij == editor.ij) return
val editorInFocus = KeyHandler.getInstance().editorInFocus
if (editorInFocus != null && editorInFocus.ij == editor.ij) return
KeyHandler.getInstance().editorInFocus = editor
@ -63,9 +60,7 @@ class IJEditorFocusListener : EditorListener {
val context: ExecutionContext = injector.executionContextManager.getEditorExecutionContext(editor)
VimPlugin.getChange().insertBeforeCursor(editor, context)
}
if (isTerminal(ijEditor) && !ijEditor.inInsertMode) {
switchToInsertMode.run()
} else if (ijEditor.isInsertMode && ((oldEditor != null && isTerminal(oldEditor.ij)) || !ijEditor.document.isWritable)) {
if (!ijEditor.document.isWritable) {
val context: ExecutionContext = injector.executionContextManager.getEditorExecutionContext(editor)
val mode = injector.vimState.mode
when (mode) {
@ -82,12 +77,5 @@ class IJEditorFocusListener : EditorListener {
}
KeyHandler.getInstance().reset(editor)
}
// By "terminal" we refer to some editor that should switch to INSERT mode on focus
private fun isTerminal(ijEditor: Editor): Boolean {
return !ijEditor.isViewer &&
!EditorHelper.isFileEditor(ijEditor) &&
ijEditor.document.isWritable &&
ijEditor.editorKind != EditorKind.DIFF
}
}

View File

@ -16,7 +16,9 @@ import com.intellij.codeInsight.lookup.impl.actions.ChooseItemAction
import com.intellij.codeInsight.template.Template
import com.intellij.codeInsight.template.TemplateEditingAdapter
import com.intellij.codeInsight.template.TemplateManagerListener
import com.intellij.codeInsight.template.impl.TemplateManagerImpl
import com.intellij.codeInsight.template.impl.TemplateState
import com.intellij.codeInsight.template.impl.actions.NextVariableAction
import com.intellij.find.FindModelListener
import com.intellij.openapi.actionSystem.ActionManager
import com.intellij.openapi.actionSystem.ActionUpdateThread
@ -28,6 +30,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.keymap.KeymapManager
import com.intellij.openapi.project.DumbAwareToggleAction
import com.intellij.openapi.util.TextRange
@ -58,6 +61,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
@ -67,6 +71,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
@ -115,42 +120,61 @@ internal object IdeaSpecifics {
if (VimPlugin.isNotEnabled()) return
val editor = editor
if (editor != null && action is ChooseItemAction && injector.registerGroup.isRecording) {
val prevDocumentLength = completionPrevDocumentLength
val prevDocumentOffset = completionPrevDocumentOffset
if (editor != null) {
if (action is ChooseItemAction && injector.registerGroup.isRecording) {
val prevDocumentLength = completionPrevDocumentLength
val prevDocumentOffset = completionPrevDocumentOffset
if (prevDocumentLength != null && prevDocumentOffset != null) {
val register = VimPlugin.getRegister()
val addedTextLength = editor.document.textLength - prevDocumentLength
val caretShift = addedTextLength - (editor.caretModel.primaryCaret.offset - prevDocumentOffset)
val leftArrow = KeyStroke.getKeyStroke(KeyEvent.VK_LEFT, 0)
if (prevDocumentLength != null && prevDocumentOffset != null) {
val register = VimPlugin.getRegister()
val addedTextLength = editor.document.textLength - prevDocumentLength
val caretShift = addedTextLength - (editor.caretModel.primaryCaret.offset - prevDocumentOffset)
val leftArrow = KeyStroke.getKeyStroke(KeyEvent.VK_LEFT, 0)
register.recordText(editor.document.getText(TextRange(prevDocumentOffset, prevDocumentOffset + addedTextLength)))
repeat(caretShift.coerceAtLeast(0)) {
register.recordKeyStroke(leftArrow)
register.recordText(editor.document.getText(TextRange(prevDocumentOffset, prevDocumentOffset + addedTextLength)))
repeat(caretShift.coerceAtLeast(0)) {
register.recordKeyStroke(leftArrow)
}
}
this.completionPrevDocumentLength = null
this.completionPrevDocumentOffset = null
}
//region Enter insert mode after surround with if
if (surrounderAction == action.javaClass.name && surrounderItems.any {
action.templatePresentation.text.endsWith(
it,
)
}
) {
editor?.let {
it.vim.mode = Mode.NORMAL()
VimPlugin.getChange().insertBeforeCursor(it.vim, event.dataContext.vim)
KeyHandler.getInstance().reset(it.vim)
}
}
this.completionPrevDocumentLength = null
this.completionPrevDocumentOffset = null
}
//region Enter insert mode after surround with if
if (surrounderAction == action.javaClass.name && surrounderItems.any {
action.templatePresentation.text.endsWith(
it,
)
else if (action is NextVariableAction && TemplateManagerImpl.getTemplateState(editor) == null) {
editor.vim.exitInsertMode(event.dataContext.vim)
KeyHandler.getInstance().reset(editor.vim)
}
) {
editor?.let {
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)
}
}
//endregion
this.editor = null
this.caretOffset = -1
}
}

View File

@ -16,7 +16,6 @@ import com.intellij.openapi.diagnostic.Logger
import com.intellij.openapi.diagnostic.trace
import com.intellij.openapi.editor.Caret
import com.intellij.openapi.editor.Editor
import com.intellij.openapi.editor.EditorFactory
import com.intellij.openapi.editor.EditorKind
import com.intellij.openapi.editor.actionSystem.TypedAction
import com.intellij.openapi.editor.event.CaretEvent
@ -32,9 +31,10 @@ import com.intellij.openapi.editor.event.EditorMouseMotionListener
import com.intellij.openapi.editor.event.SelectionEvent
import com.intellij.openapi.editor.event.SelectionListener
import com.intellij.openapi.editor.ex.DocumentEx
import com.intellij.openapi.editor.ex.EditorEventMulticasterEx
import com.intellij.openapi.editor.ex.EditorEx
import com.intellij.openapi.editor.ex.FocusChangeListener
import com.intellij.openapi.editor.impl.EditorComponentImpl
import com.intellij.openapi.fileEditor.FileEditor
import com.intellij.openapi.fileEditor.FileEditorManager
import com.intellij.openapi.fileEditor.FileEditorManagerEvent
import com.intellij.openapi.fileEditor.FileEditorManagerListener
@ -44,12 +44,15 @@ import com.intellij.openapi.fileEditor.ex.FileEditorManagerEx
import com.intellij.openapi.fileEditor.ex.FileEditorWithProvider
import com.intellij.openapi.fileEditor.impl.EditorComposite
import com.intellij.openapi.fileEditor.impl.EditorWindow
import com.intellij.openapi.observable.util.addKeyListener
import com.intellij.openapi.project.Project
import com.intellij.openapi.project.ProjectManager
import com.intellij.openapi.util.Disposer
import com.intellij.openapi.util.Key
import com.intellij.openapi.util.removeUserData
import com.intellij.openapi.vfs.VirtualFile
import com.intellij.util.ExceptionUtil
import com.intellij.util.SlowOperations
import com.maddyhome.idea.vim.EventFacade
import com.maddyhome.idea.vim.KeyHandler
import com.maddyhome.idea.vim.VimKeyListener
@ -62,7 +65,6 @@ import com.maddyhome.idea.vim.api.coerceOffset
import com.maddyhome.idea.vim.api.getLineEndForOffset
import com.maddyhome.idea.vim.api.getLineStartForOffset
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
@ -79,7 +81,6 @@ 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
@ -95,6 +96,7 @@ import com.maddyhome.idea.vim.newapi.IjVimSearchGroup
import com.maddyhome.idea.vim.newapi.InsertTimeRecorder
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.inSelectMode
import com.maddyhome.idea.vim.state.mode.selectionType
import com.maddyhome.idea.vim.ui.ShowCmdOptionChangeListener
@ -103,9 +105,11 @@ 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 org.jetbrains.annotations.TestOnly
import java.awt.event.MouseAdapter
import java.awt.event.MouseEvent
import java.lang.ref.WeakReference
import java.util.*
import javax.swing.SwingUtilities
/**
@ -127,23 +131,15 @@ import javax.swing.SwingUtilities
* Make sure the selected editor isn't the new editor, which can happen if there are no other editors open.
*/
private fun getOpeningEditor(newEditor: Editor) = newEditor.project?.let { project ->
// Some TextEditor implementations create a dummy Editor instance on demand, e.g., while downloading a file to edit
// (see BaseRemoteFileEditor). This can cause recursion if the newly opened/created TextEditor is also the currently
// selected TextEditor, because we will be notified of the new dummy Editor before it has finished initialisation, and
// try to get its opening editor, causing a new dummy Editor to be created and notifications sent, and so on.
// This was reported for 232 and 233 (see VIM-3066), but I can't recreate in 241. The callstack looks different, now
// using coroutines, so it's possible the deadlock has been broken. However, it's sensible to leave the recursion
// guard in.
if (openingEditorRecursionGuard) return null
openingEditorRecursionGuard = true
try {
FileEditorManager.getInstance(project).selectedTextEditor?.takeUnless { it == newEditor }
}
finally {
openingEditorRecursionGuard = false
}
// We can't rely on FileEditorManager.selectedTextEditor because we're trying to retrieve the selected text editor
// while creating a text editor that is about to become the selected text editor.
// This worked fine for 2024.2, but internal changes for 2024.3 broke things. It appears that the currently selected
// text editor is reset to null while the soon-to-be-selected text editor is being created. We therefore track the
// last selected editor manually.
// Note that if we ever switch back to FileEditorManager.selectedTextEditor, be careful of recursion, because the
// actual editor might be created on-demand, which would notify our initialisation method, which would call us...
VimListenerManager.VimLastSelectedEditorTracker.getLastSelectedEditor(project)?.takeUnless { it == newEditor }
}
private var openingEditorRecursionGuard = false
internal object VimListenerManager {
@ -153,7 +149,9 @@ internal object VimListenerManager {
fun turnOn() {
GlobalListeners.enable()
EditorListeners.addAll()
SlowOperations.knownIssue("VIM-3648, VIM-3649").use {
EditorListeners.addAll()
}
check(correctorRequester.tryEmit(Unit))
check(keyCheckRequests.tryEmit(Unit))
@ -214,16 +212,6 @@ internal object VimListenerManager {
EventFacade.getInstance().addEditorFactoryListener(VimEditorFactoryListener, VimPlugin.getInstance().onOffDisposable)
val busConnection = ApplicationManager.getApplication().messageBus.connect(VimPlugin.getInstance().onOffDisposable)
busConnection.subscribe(FileOpenedSyncListener.TOPIC, VimEditorFactoryListener)
// 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() {
@ -285,47 +273,52 @@ internal object VimListenerManager {
// 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
// Protect against double initialisation
if (editor.getUserData(editorListenersDisposableKey) != null) {
return
}
val listenersDisposable = Disposer.newDisposable(disposable)
editor.putUserData(editorListenersDisposableKey, listenersDisposable)
// Make sure we explicitly dispose this per-editor disposable!
// Because the listeners are registered with a parent disposable, they add child disposables that have to call a
// method on the editor to remove the listener. This means the disposable contains a reference to the editor (even
// if the listener handler is a singleton that doesn't hold a reference).
// Unless the per-editor disposable is disposed, all of these disposables sit in the disposer tree until the
// parent disposable is disposed, which will mean we leak editor instances.
// The per-editor disposable is explicitly disposed when the editor is released, and disposed via its parent when
// the plugin's on/off functionality is toggled, and so also when the plugin is disabled/unloaded by the platform.
// It doesn't matter if we explicitly remove all listeners before disposing onOffDisposable, as that will remove
// the per-editor disposable from the disposer tree.
val perEditorDisposable = Disposer.newDisposable(VimPlugin.getInstance().onOffDisposable)
editor.putUserData(editorListenersDisposableKey, perEditorDisposable)
Disposer.register(listenersDisposable) {
Disposer.register(perEditorDisposable) {
if (VimListenerTestObject.enabled) {
VimListenerTestObject.disposedCounter += 1
}
}
editor.contentComponent.addKeyListener(VimKeyListener)
Disposer.register(listenersDisposable) { editor.contentComponent.removeKeyListener(VimKeyListener) }
// This listener and several below add a reference to the editor to the disposer tree
editor.contentComponent.addKeyListener(perEditorDisposable, VimKeyListener)
// Initialise the local options. We MUST do this before anything has the chance to query options
val vimEditor = editor.vim
VimPlugin.getOptionGroup().initialiseLocalOptions(vimEditor, openingEditor, scenario)
val eventFacade = EventFacade.getInstance()
eventFacade.addEditorMouseListener(editor, EditorMouseHandler, listenersDisposable)
eventFacade.addEditorMouseMotionListener(editor, EditorMouseHandler, listenersDisposable)
eventFacade.addEditorSelectionListener(editor, EditorSelectionHandler, listenersDisposable)
eventFacade.addComponentMouseListener(editor.contentComponent, ComponentMouseListener, listenersDisposable)
eventFacade.addCaretListener(editor, EditorCaretHandler, listenersDisposable)
eventFacade.addEditorMouseListener(editor, EditorMouseHandler, perEditorDisposable)
eventFacade.addEditorMouseMotionListener(editor, EditorMouseHandler, perEditorDisposable)
eventFacade.addEditorSelectionListener(editor, EditorSelectionHandler, perEditorDisposable)
eventFacade.addComponentMouseListener(editor.contentComponent, ComponentMouseListener, perEditorDisposable)
eventFacade.addCaretListener(editor, EditorCaretHandler, perEditorDisposable)
VimPlugin.getEditor().editorCreated(editor)
VimPlugin.getChange().editorCreated(editor, listenersDisposable)
VimPlugin.getChange().editorCreated(editor, perEditorDisposable)
(editor as EditorEx).addFocusListener(VimFocusListener, perEditorDisposable)
injector.listenersNotifier.notifyEditorCreated(vimEditor)
Disposer.register(listenersDisposable) {
Disposer.register(perEditorDisposable) {
VimPlugin.getEditor().editorDeinit(editor)
}
}
@ -363,7 +356,7 @@ internal object VimListenerManager {
* 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 {
class VimDocumentListener : DocumentListener {
override fun beforeDocumentChange(event: DocumentEvent) {
VimMarkServiceImpl.MarkUpdater.beforeDocumentChange(event)
IjVimSearchGroup.DocumentSearchListener.INSTANCE.beforeDocumentChange(event)
@ -377,6 +370,26 @@ internal object VimListenerManager {
}
}
internal object VimLastSelectedEditorTracker {
// This stores a weak reference to an editor against a weak reference to a project, which means there is nothing
// keeping the project or editor from being garbage collected at any time. Stale keys are automatically expunged
// whenever the map is used.
private val selectedEditors = WeakHashMap<Project, WeakReference<Editor>>()
fun getLastSelectedEditor(project: Project): Editor? = selectedEditors[project]?.get()
internal fun setLastSelectedEditor(fileEditor: FileEditor?) {
(fileEditor as? TextEditor)?.editor?.let { editor ->
editor.project?.let { project -> selectedEditors[project] = WeakReference(editor) }
}
}
@TestOnly
internal fun resetLastSelectedEditor(project: Project) {
selectedEditors.remove(project)
}
}
/**
* 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
@ -386,12 +399,23 @@ internal object VimListenerManager {
override fun selectionChanged(event: FileEditorManagerEvent) {
// 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) {
editor.vim.mode = Mode.NORMAL()
KeyHandler.getInstance().reset(editor.vim)
}
// Breaks relativenumber for some reason
// injector.scroll.scrollCaretIntoView(editor.vim)
}
MotionGroup.fileEditorManagerSelectionChangedCallback(event)
FileGroup.fileEditorManagerSelectionChangedCallback(event)
VimPlugin.getSearch().fileEditorManagerSelectionChangedCallback(event)
OptionGroup.fileEditorManagerSelectionChangedCallback(event)
IjVimRedrawService.fileEditorManagerSelectionChangedCallback(event)
VimLastSelectedEditorTracker.setLastSelectedEditor(event.newEditor)
}
}
@ -458,15 +482,26 @@ internal object VimListenerManager {
event.editor.putUserData(openingEditorKey, OpeningEditor(openingEditor, owningEditorWindow, isPreview, canBeReused))
}
VimStandalonePluginUpdateChecker.getInstance().pluginUsed()
}
override fun editorReleased(event: EditorFactoryEvent) {
if (vimDisabled(event.editor)) return
val vimEditor = event.editor.vim
EditorListeners.remove(event.editor)
injector.listenersNotifier.notifyEditorReleased(vimEditor)
injector.markService.editorReleased(vimEditor)
// This ticket will have a different stack trace, but it's the same problem. Originally, we tracked the last
// editor closing based on file selection (closing an editor would select the next editor - so a null selection
// was taken to mean that there were no more editors to select). This assumption broke in 242, so it's changed to
// check when the editor is released.
// However, the actions taken when the last editor closes can still be expensive/slow because we copy options, and
// some options are backed by PSI options. E.g. 'textwidth' is mapped to
// CodeStyle.getSettings(ijEditor).isWrapOnTyping(language)), and getting the document's PSI language is a slow
// operation. This underlying issue still needs to be addressed, even though the method has moved
SlowOperations.knownIssue("VIM-3658").use {
OptionGroup.editorReleased(event.editor)
}
}
override fun fileOpenedSync(
@ -754,11 +789,16 @@ internal object VimListenerManager {
// https://youtrack.jetbrains.com/issue/IDEA-277716
// https://youtrack.jetbrains.com/issue/VIM-2368
if (event.mouseEvent.clickCount == 1 && !SwingUtilities.isRightMouseButton(event.mouseEvent)) {
if (editor.inVisualMode) {
editor.vim.exitVisualMode()
} else if (editor.vim.inSelectMode) {
editor.exitSelectMode(false)
KeyHandler.getInstance().reset(editor.vim)
val hasSelection = ApplicationManager.getApplication().runReadAction<Boolean> {
editor.selectionModel.hasSelection(true)
}
if (!hasSelection) {
if (editor.inVisualMode) {
editor.vim.exitVisualMode()
} else if (editor.vim.inSelectMode) {
editor.exitSelectMode(false)
KeyHandler.getInstance().reset(editor.vim)
}
}
}
} else if (event.area != EditorMouseEventArea.ANNOTATIONS_AREA &&

View File

@ -22,10 +22,13 @@ import com.intellij.openapi.project.DumbService
import com.intellij.openapi.project.IndexNotReadyException
import com.intellij.psi.PsiDocumentManager
import com.intellij.util.ui.EmptyClipboardOwner
import com.maddyhome.idea.vim.api.ExecutionContext
import com.maddyhome.idea.vim.api.VimClipboardManager
import com.maddyhome.idea.vim.api.VimEditor
import com.maddyhome.idea.vim.api.getText
import com.maddyhome.idea.vim.api.injector
import com.maddyhome.idea.vim.common.TextRange
import com.maddyhome.idea.vim.common.VimCopiedText
import com.maddyhome.idea.vim.diagnostic.debug
import com.maddyhome.idea.vim.diagnostic.vimLogger
import java.awt.HeadlessException
@ -37,18 +40,52 @@ import java.io.IOException
@Service
internal class IjClipboardManager : VimClipboardManager {
@Deprecated("Please use com.maddyhome.idea.vim.api.VimClipboardManager#getPrimaryTextAndTransferableData")
override fun getPrimaryTextAndTransferableData(): Pair<String, List<Any>?>? {
val clipboard = Toolkit.getDefaultToolkit()?.systemSelection ?: return null
val contents = clipboard.getContents(null) ?: return null
return getTextAndTransferableData(contents)
}
override fun getPrimaryContent(editor: VimEditor, context: ExecutionContext): IjVimCopiedText? {
val (text, transferableData) = getPrimaryTextAndTransferableData() ?: return null
return IjVimCopiedText(text, transferableData ?: emptyList())
}
@Deprecated("Please use com.maddyhome.idea.vim.api.VimClipboardManager#getClipboardTextAndTransferableData")
override fun getClipboardTextAndTransferableData(): Pair<String, List<Any>?>? {
val contents = getContents() ?: return null
return getTextAndTransferableData(contents)
}
private fun getTextAndTransferableData(trans: Transferable): Pair<String, List<Any>?>? {
override fun getClipboardContent(editor: VimEditor, context: ExecutionContext): VimCopiedText? {
val (text, transferableData) = getClipboardTextAndTransferableData() ?: return null
return IjVimCopiedText(text, transferableData ?: emptyList())
}
override fun setClipboardContent(editor: VimEditor, context: ExecutionContext, textData: VimCopiedText): Boolean {
require(textData is IjVimCopiedText)
return handleTextSetting(textData.text, textData.text, textData.transferableData) { content -> setContents(content) } != null
}
// TODO prefer methods with ranges, because they collect and preprocess for us
// override fun createClipboardEntry(
// editor: VimEditor,
// context: ExecutionContext,
// text: String,
// range: TextRange,
// ): ClipboardEntry {
// val transferableData = getTransferableData(editor, range, text)
// val preprocessedText = preprocessText(editor, range, text, transferableData)
// return IJClipboardEntry(preprocessedText, text, transferableData)
// }
// override fun setClipboardText(editor: VimEditor, context: ExecutionContext, entry: ClipboardEntry): Boolean {
// require(entry is IJClipboardEntry)
// return setClipboardText(entry.text, entry.rawText, entry.transferableData) != null
// }
private fun getTextAndTransferableData(trans: Transferable): Pair<String, List<TextBlockTransferableData>?>? {
var res: String? = null
var transferableData: List<TextBlockTransferableData> = ArrayList()
try {
@ -63,10 +100,29 @@ internal class IjClipboardManager : VimClipboardManager {
return Pair(res, transferableData)
}
@Deprecated("Please use com.maddyhome.idea.vim.api.VimClipboardManager#setClipboardText")
override fun setClipboardText(text: String, rawText: String, transferableData: List<Any>): Transferable? {
return handleTextSetting(text, rawText, transferableData) { content -> setContents(content) }
}
override fun setPrimaryContent(
editor: VimEditor,
context: ExecutionContext,
textData: VimCopiedText,
): Boolean {
require(textData is IjVimCopiedText)
return handleTextSetting(textData.text, textData.text, textData.transferableData) { content ->
val clipboard = Toolkit.getDefaultToolkit()?.systemSelection ?: return@handleTextSetting null
clipboard.setContents(content, EmptyClipboardOwner.INSTANCE)
} != null
}
// override fun setPrimaryText(editor: VimEditor, context: ExecutionContext, entry: ClipboardEntry): Boolean {
// require(entry is IJClipboardEntry)
// return setPrimaryText(entry.text, entry.rawText, entry.transferableData) != null
// }
@Deprecated("Please use com.maddyhome.idea.vim.api.VimClipboardManager#setPrimaryText")
override fun setPrimaryText(text: String, rawText: String, transferableData: List<Any>): Transferable? {
return handleTextSetting(text, rawText, transferableData) { content ->
val clipboard = Toolkit.getDefaultToolkit()?.systemSelection ?: return@handleTextSetting null
@ -74,6 +130,16 @@ internal class IjClipboardManager : VimClipboardManager {
}
}
override fun collectCopiedText(editor: VimEditor, context: ExecutionContext, range: TextRange, text: String): VimCopiedText {
val transferableData = getTransferableData(editor, range)
val preprocessedText = preprocessText(editor, range, text, transferableData)
return IjVimCopiedText(preprocessedText, transferableData)
}
override fun dumbCopiedText(text: String): VimCopiedText {
return IjVimCopiedText(text, emptyList())
}
@Suppress("UNCHECKED_CAST")
private fun handleTextSetting(text: String, rawText: String, transferableData: List<Any>, setContent: (TextBlockTransferable) -> Unit?): Transferable? {
val mutableTransferableData = (transferableData as List<TextBlockTransferableData>).toMutableList()
@ -92,7 +158,7 @@ internal class IjClipboardManager : VimClipboardManager {
return null
}
override fun getTransferableData(vimEditor: VimEditor, textRange: TextRange, text: String): List<Any> {
override fun getTransferableData(vimEditor: VimEditor, textRange: TextRange): List<TextBlockTransferableData> {
val editor = (vimEditor as IjVimEditor).editor
val transferableData: MutableList<TextBlockTransferableData> = ArrayList()
val project = editor.project ?: return emptyList()
@ -117,7 +183,7 @@ internal class IjClipboardManager : VimClipboardManager {
}
}
}
transferableData.add(CaretStateTransferableData(intArrayOf(0), intArrayOf(text.length)))
transferableData.add(CaretStateTransferableData(intArrayOf(0), intArrayOf(textRange.endOffset - textRange.startOffset)))
// These data provided by {@link com.intellij.openapi.editor.richcopy.TextWithMarkupProcessor} doesn't work with
// IdeaVim and I don't see a way to fix it
@ -175,3 +241,7 @@ internal class IjClipboardManager : VimClipboardManager {
val logger = vimLogger<IjClipboardManager>()
}
}
data class IjVimCopiedText(override val text: String, val transferableData: List<Any>): VimCopiedText {
override fun updateText(newText: String): VimCopiedText = IjVimCopiedText(newText, transferableData)
}

View File

@ -12,8 +12,6 @@ import com.intellij.openapi.editor.Caret
import com.intellij.openapi.editor.LogicalPosition
import com.intellij.openapi.editor.VisualPosition
import com.maddyhome.idea.vim.api.BufferPosition
import com.maddyhome.idea.vim.api.CaretRegisterStorage
import com.maddyhome.idea.vim.api.CaretRegisterStorageBase
import com.maddyhome.idea.vim.api.ImmutableVimCaret
import com.maddyhome.idea.vim.api.LocalMarkStorage
import com.maddyhome.idea.vim.api.SelectionInfo
@ -21,6 +19,7 @@ import com.maddyhome.idea.vim.api.VimCaret
import com.maddyhome.idea.vim.api.VimCaretBase
import com.maddyhome.idea.vim.api.VimEditor
import com.maddyhome.idea.vim.api.VimVisualPosition
import com.maddyhome.idea.vim.api.injector
import com.maddyhome.idea.vim.common.InsertSequence
import com.maddyhome.idea.vim.common.LiveRange
import com.maddyhome.idea.vim.group.visual.VisualChange
@ -29,7 +28,6 @@ import com.maddyhome.idea.vim.helper.insertHistory
import com.maddyhome.idea.vim.helper.lastSelectionInfo
import com.maddyhome.idea.vim.helper.markStorage
import com.maddyhome.idea.vim.helper.moveToInlayAwareOffset
import com.maddyhome.idea.vim.helper.registerStorage
import com.maddyhome.idea.vim.helper.resetVimLastColumn
import com.maddyhome.idea.vim.helper.vimInsertStart
import com.maddyhome.idea.vim.helper.vimLastColumn
@ -37,22 +35,14 @@ import com.maddyhome.idea.vim.helper.vimLastVisualOperatorRange
import com.maddyhome.idea.vim.helper.vimLine
import com.maddyhome.idea.vim.helper.vimSelectionStart
import com.maddyhome.idea.vim.helper.vimSelectionStartClear
import com.maddyhome.idea.vim.register.VimRegisterGroup
import com.maddyhome.idea.vim.state.mode.SelectionType
internal class IjVimCaret(val caret: Caret) : VimCaretBase() {
override val registerStorage: CaretRegisterStorage
get() {
var storage = this.caret.registerStorage
if (storage == null) {
initInjector() // To initialize injector used in CaretRegisterStorageBase
storage = CaretRegisterStorageBase(this)
this.caret.registerStorage = storage
} else if (storage.caret != this) {
storage.caret = this
}
return storage
}
override val registerStorage: VimRegisterGroup
get() = injector.registerGroup
override val markStorage: LocalMarkStorage
get() {
var storage = this.caret.markStorage

View File

@ -60,6 +60,8 @@ import com.maddyhome.idea.vim.impl.state.VimStateMachineImpl
import com.maddyhome.idea.vim.state.mode.Mode
import com.maddyhome.idea.vim.state.mode.SelectionType
import com.maddyhome.idea.vim.state.mode.inBlockSelection
import com.maddyhome.idea.vim.undo.VimKeyBasedUndoService
import com.maddyhome.idea.vim.undo.VimTimestampBasedUndoService
import org.jetbrains.annotations.ApiStatus
import java.lang.System.identityHashCode
@ -140,7 +142,13 @@ internal class IjVimEditor(editor: Editor) : MutableLinearEditor, VimEditorBase(
override fun insertText(caret: VimCaret, atPosition: Int, text: CharSequence) {
if (injector.vimState.mode is Mode.INSERT) {
injector.undo.startInsertSequence(caret, atPosition, System.nanoTime())
val undo = injector.undo
when (undo) {
is VimKeyBasedUndoService -> undo.setInsertNonMergeUndoKey()
is VimTimestampBasedUndoService -> {
undo.startInsertSequence(caret, atPosition, System.nanoTime())
}
}
}
editor.document.insertString(atPosition, text)
}
@ -171,21 +179,38 @@ internal class IjVimEditor(editor: Editor) : MutableLinearEditor, VimEditorBase(
return editor.caretModel.allCarets.map { IjVimCaret(it) }
}
override var isFirstCaret = true
override var isReversingCarets = false
@Suppress("ideavimRunForEachCaret")
override fun forEachCaret(action: (VimCaret) -> Unit) {
if (editor.vim.inBlockSelection) {
action(IjVimCaret(editor.caretModel.primaryCaret))
} else {
editor.caretModel.runForEachCaret({
if (it.isValid) {
action(IjVimCaret(it))
}
}, false)
try {
editor.caretModel.runForEachCaret({
if (it.isValid) {
action(IjVimCaret(it))
isFirstCaret = false
}
}, false)
} finally {
isFirstCaret = true
}
}
}
override fun forEachNativeCaret(action: (VimCaret) -> Unit, reverse: Boolean) {
editor.caretModel.runForEachCaret({ action(IjVimCaret(it)) }, reverse)
isReversingCarets = reverse
try {
editor.caretModel.runForEachCaret({
action(IjVimCaret(it))
isFirstCaret = false
}, reverse)
} finally {
isFirstCaret = true
isReversingCarets = false
}
}
override fun isInForEachCaretScope(): Boolean {
@ -532,7 +557,7 @@ internal class InsertTimeRecorder: ModeChangeListener {
override fun modeChanged(editor: VimEditor, oldMode: Mode) {
editor as IjVimEditor
if (oldMode == Mode.INSERT) {
val undo = injector.undo
val undo = injector.undo as? VimTimestampBasedUndoService ?: return
val nanoTime = System.nanoTime()
editor.forEachCaret { undo.endInsertSequence(it, it.offset, nanoTime) }
}

View File

@ -22,7 +22,6 @@ import com.maddyhome.idea.vim.api.VimApplication
import com.maddyhome.idea.vim.api.VimChangeGroup
import com.maddyhome.idea.vim.api.VimClipboardManager
import com.maddyhome.idea.vim.api.VimCommandGroup
import com.maddyhome.idea.vim.api.VimCommandLine
import com.maddyhome.idea.vim.api.VimCommandLineService
import com.maddyhome.idea.vim.api.VimDigraphGroup
import com.maddyhome.idea.vim.api.VimEditor

View File

@ -18,11 +18,8 @@ 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.util.Ref
import com.maddyhome.idea.vim.VimPlugin
import com.maddyhome.idea.vim.api.ExecutionContext
import com.maddyhome.idea.vim.api.Options
import com.maddyhome.idea.vim.api.VimCaret
import com.maddyhome.idea.vim.api.VimEditor
import com.maddyhome.idea.vim.api.VimSearchGroupBase
import com.maddyhome.idea.vim.api.globalOptions
@ -30,21 +27,16 @@ import com.maddyhome.idea.vim.api.injector
import com.maddyhome.idea.vim.common.Direction
import com.maddyhome.idea.vim.common.Direction.Companion.fromInt
import com.maddyhome.idea.vim.diagnostic.vimLogger
import com.maddyhome.idea.vim.helper.MessageHelper
import com.maddyhome.idea.vim.helper.TestInputModel.Companion.getInstance
import com.maddyhome.idea.vim.helper.addSubstitutionConfirmationHighlight
import com.maddyhome.idea.vim.helper.highlightSearchResults
import com.maddyhome.idea.vim.helper.isCloseKeyStroke
import com.maddyhome.idea.vim.helper.shouldIgnoreCase
import com.maddyhome.idea.vim.helper.updateSearchHighlights
import com.maddyhome.idea.vim.helper.vimLastHighlighters
import com.maddyhome.idea.vim.options.GlobalOptionChangeListener
import com.maddyhome.idea.vim.ui.ModalEntry
import com.maddyhome.idea.vim.vimscript.model.functions.handlers.SubmatchFunctionHandler
import org.jdom.Element
import org.jetbrains.annotations.Contract
import org.jetbrains.annotations.TestOnly
import javax.swing.KeyStroke
@State(
name = "VimSearchSettings",
@ -52,7 +44,7 @@ import javax.swing.KeyStroke
)
open class IjVimSearchGroup : VimSearchGroupBase(), PersistentStateComponent<Element> {
companion object {
private val logger = vimLogger<IjVimSearchGroup>()
private val logger by lazy { vimLogger<IjVimSearchGroup>() }
}
init {
@ -112,27 +104,25 @@ open class IjVimSearchGroup : VimSearchGroupBase(), PersistentStateComponent<Ele
return IjSearchHighlight(ijEditor, highlighter)
}
override fun setLatestMatch(match: String) {
SubmatchFunctionHandler.getInstance().latestMatch = match
}
override fun replaceString(
editor: VimEditor,
startOffset: Int,
endOffset: Int,
newString: String,
) {
ApplicationManager.getApplication().runWriteAction {
(editor as IjVimEditor).editor.document.replaceString(startOffset, endOffset, newString)
}
}
@TestOnly
override fun resetState() {
super.resetState()
showSearchHighlight = injector.globalOptions().hlsearch
}
override fun isSomeTextHighlighted(): Boolean {
val vimEditors = injector.editorGroup.getEditors().filter {
(injector.application.isUnitTest() || it.ij.component.isShowing)
}
for (vimEditor in vimEditors) {
val editor = vimEditor.ij
if (editor.vimLastHighlighters != null) {
return true
}
}
return false
}
override fun setShouldShowSearchHighlights() {
showSearchHighlight = injector.globalOptions().hlsearch
}

View File

@ -32,9 +32,9 @@ internal class Troubleshooter {
fun findIncorrectMappings(): List<Problem> {
val problems = ArrayList<Problem>()
MappingMode.entries.forEach { mode ->
injector.keyGroup.getKeyMapping(mode).getByOwner(MappingOwner.IdeaVim.InitScript).forEach { (_, to) ->
if (to is ToKeysMappingInfo) {
if (":action" in to.toKeys.joinToString { it.keyChar.toString() }) {
injector.keyGroup.getKeyMapping(mode).getAllByOwner(MappingOwner.IdeaVim.InitScript).forEach { entry ->
(entry.mappingInfo as? ToKeysMappingInfo)?.let { mappingInfo ->
if (":action" in mappingInfo.toKeys.joinToString { it.keyChar.toString() }) {
problems += Problem("Mappings contain `:action` call")
}
}

View File

@ -7,7 +7,6 @@
*/
package com.maddyhome.idea.vim.ui.ex
import com.intellij.openapi.diagnostic.logger
import com.maddyhome.idea.vim.KeyHandler
import com.maddyhome.idea.vim.api.injector
import com.maddyhome.idea.vim.newapi.vim

View File

@ -23,7 +23,10 @@ import com.maddyhome.idea.vim.EventFacade;
import com.maddyhome.idea.vim.KeyHandler;
import com.maddyhome.idea.vim.VimPlugin;
import com.maddyhome.idea.vim.action.VimShortcutKeyAction;
import com.maddyhome.idea.vim.api.*;
import com.maddyhome.idea.vim.api.VimCommandLine;
import com.maddyhome.idea.vim.api.VimCommandLineCaret;
import com.maddyhome.idea.vim.api.VimEditor;
import com.maddyhome.idea.vim.api.VimKeyGroupBase;
import com.maddyhome.idea.vim.ex.ranges.LineRange;
import com.maddyhome.idea.vim.helper.SearchHighlightsHelper;
import com.maddyhome.idea.vim.helper.UiHelper;
@ -348,7 +351,7 @@ public class ExEntryPanel extends JPanel implements VimCommandLine {
int count1 = Math.max(1, KeyHandler.getInstance().getKeyHandlerState().getEditorCommandBuilder()
.calculateCount0Snapshot());
if (labelText.equals("/") || labelText.equals("?") || searchCommand) {
if ((labelText.equals("/") || labelText.equals("?") || searchCommand) && !injector.getMacro().isExecutingMacro()) {
final boolean forwards = !labelText.equals("?"); // :s, :g, :v are treated as forwards
int pattenEnd = injector.getSearchGroup().findEndOfPattern(searchText, separator, 0);
final String pattern = searchText.substring(0, pattenEnd);

View File

@ -11,6 +11,7 @@ package com.maddyhome.idea.vim.ui.widgets.mode.listeners
import com.intellij.openapi.editor.Editor
import com.intellij.openapi.fileEditor.FileEditorManager
import com.intellij.openapi.project.Project
import com.intellij.openapi.util.RecursionManager
import com.intellij.openapi.wm.WindowManager
import com.maddyhome.idea.vim.api.VimEditor
import com.maddyhome.idea.vim.common.EditorListener
@ -73,7 +74,14 @@ internal class ModeWidgetListener: ModeChangeListener, EditorListener, VimWidget
private fun getFocusedEditorForProject(editorProject: Project?): Editor? {
if (editorProject == null) return null
val fileEditorManager = FileEditorManager.getInstance(editorProject)
return fileEditorManager.selectedTextEditor
return recursionGuard.doPreventingRecursion(recursionKey, false) {
val fileEditorManager = FileEditorManager.getInstance(editorProject)
fileEditorManager.selectedTextEditor
}
}
companion object {
private val recursionGuard = RecursionManager.createGuard<Any>("IdeaVim.modeWidgetListener")
private val recursionKey = Any()
}
}

View File

@ -15,10 +15,8 @@ import com.maddyhome.idea.vim.api.ExecutionContext
import com.maddyhome.idea.vim.api.VimEditor
import com.maddyhome.idea.vim.api.injector
import com.maddyhome.idea.vim.command.OperatorArguments
import com.maddyhome.idea.vim.ex.ExOutputModel
import com.maddyhome.idea.vim.ex.ranges.Range
import com.maddyhome.idea.vim.helper.MessageHelper
import com.maddyhome.idea.vim.newapi.ij
import com.maddyhome.idea.vim.vimscript.model.ExecutionResult
import java.util.*
@ -26,7 +24,9 @@ import java.util.*
* @author smartbomb
*/
@ExCommand(command = "actionl[ist]")
internal data class ActionListCommand(val range: Range, val argument: String) : Command.SingleExecution(range) {
internal data class ActionListCommand(val range: Range, val modifier: CommandModifier, val argument: String) :
Command.SingleExecution(range, modifier) {
override val argFlags = flags(RangeFlag.RANGE_FORBIDDEN, ArgumentFlag.ARGUMENT_OPTIONAL, Access.READ_ONLY)
override fun processCommand(editor: VimEditor, context: ExecutionContext, operatorArguments: OperatorArguments): ExecutionResult {

View File

@ -27,13 +27,14 @@ import com.maddyhome.idea.vim.vimscript.model.ExecutionResult
* @author John Weigel
*/
@ExCommand(command = "b[uffer]")
internal data class BufferCommand(val range: Range, val argument: String) : Command.SingleExecution(range) {
internal data class BufferCommand(val range: Range, val modifier: CommandModifier, val argument: String) :
Command.SingleExecution(range, modifier) {
override val argFlags = flags(RangeFlag.RANGE_FORBIDDEN, ArgumentFlag.ARGUMENT_OPTIONAL, Access.READ_ONLY)
override fun processCommand(editor: VimEditor, context: ExecutionContext, operatorArguments: OperatorArguments): ExecutionResult {
val arg = argument.trim()
val overrideModified = arg.startsWith('!')
val buffer = if (overrideModified) arg.replace(Regex("^!\\s*"), "") else arg
val overrideModified = modifier == CommandModifier.BANG
val buffer = argument.trim()
var result = true
if (buffer.isNotEmpty()) {

View File

@ -32,7 +32,9 @@ import org.jetbrains.annotations.NonNls
* @author John Weigel
*/
@ExCommand(command = "ls,files,buffers")
internal data class BufferListCommand(val range: Range, val argument: String) : Command.SingleExecution(range) {
internal data class BufferListCommand(val range: Range, val modifier: CommandModifier, val argument: String) :
Command.SingleExecution(range, modifier) {
override val argFlags = flags(RangeFlag.RANGE_FORBIDDEN, ArgumentFlag.ARGUMENT_OPTIONAL, Access.READ_ONLY)
companion object {

View File

@ -18,7 +18,6 @@ import com.maddyhome.idea.vim.api.VimEditor
import com.maddyhome.idea.vim.api.injector
import com.maddyhome.idea.vim.command.OperatorArguments
import com.maddyhome.idea.vim.ex.ExException
import com.maddyhome.idea.vim.ex.ExOutputModel
import com.maddyhome.idea.vim.ex.ranges.Range
import com.maddyhome.idea.vim.ex.ranges.toTextRange
import com.maddyhome.idea.vim.helper.EditorHelper
@ -30,7 +29,9 @@ import com.maddyhome.idea.vim.vimscript.model.ExecutionResult
* see "h :!"
*/
@ExCommand(command = "!")
internal data class CmdFilterCommand(val range: Range, val argument: String) : Command.SingleExecution(range) {
internal data class CmdFilterCommand(val range: Range, val modifier: CommandModifier, val argument: String)
: Command.SingleExecution(range, modifier) {
override val argFlags = flags(RangeFlag.RANGE_OPTIONAL, ArgumentFlag.ARGUMENT_OPTIONAL, Access.SELF_SYNCHRONIZED)
override fun processCommand(editor: VimEditor, context: ExecutionContext, operatorArguments: OperatorArguments): ExecutionResult {
@ -99,7 +100,7 @@ internal data class CmdFilterCommand(val range: Range, val argument: String) : C
lastCommand = command
ExecutionResult.Success
}
} catch (e: ProcessCanceledException) {
} catch (_: ProcessCanceledException) {
throw ExException("Command terminated")
} catch (e: Exception) {
throw ExException(e.message)

View File

@ -24,8 +24,11 @@ import java.net.URLEncoder
* see "h :help"
*/
@ExCommand(command = "h[elp]")
internal data class HelpCommand(val range: Range, val argument: String) : Command.SingleExecution(range, argument) {
internal data class HelpCommand(val range: Range, val modifier: CommandModifier, val argument: String) :
Command.SingleExecution(range, modifier, argument) {
override val argFlags = flags(RangeFlag.RANGE_OPTIONAL, ArgumentFlag.ARGUMENT_OPTIONAL, Access.READ_ONLY)
override fun processCommand(editor: VimEditor, context: ExecutionContext, operatorArguments: OperatorArguments): ExecutionResult {
BrowserUtil.browse(helpTopicUrl(argument))
return ExecutionResult.Success
@ -37,7 +40,7 @@ internal data class HelpCommand(val range: Range, val argument: String) : Comman
return try {
String.format("%s?docs=help&search=%s", HELP_QUERY_URL, URLEncoder.encode(topic, "UTF-8"))
} catch (e: UnsupportedEncodingException) {
} catch (_: UnsupportedEncodingException) {
HELP_ROOT_URL
}
}

View File

@ -26,7 +26,7 @@
serviceInterface="com.maddyhome.idea.vim.api.VimCommandLineService"/>
<applicationService serviceImplementation="com.maddyhome.idea.vim.ui.ex.IjOutputPanelService"
serviceInterface="com.maddyhome.idea.vim.api.VimOutputPanelService"/>
<applicationService serviceImplementation="com.maddyhome.idea.vim.group.DigraphGroup"
<applicationService serviceImplementation="com.maddyhome.idea.vim.api.VimDigraphGroupBase"
serviceInterface="com.maddyhome.idea.vim.api.VimDigraphGroup"/>
<applicationService serviceImplementation="com.maddyhome.idea.vim.group.HistoryGroup"/>
<applicationService serviceImplementation="com.maddyhome.idea.vim.group.KeyGroup"

View File

@ -1,12 +1,4 @@
<!--
~ 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.
-->
<idea-plugin url="https://plugins.jetbrains.com/plugin/164" xmlns:xi="http://www.w3.org/2001/XInclude">
<idea-plugin xmlns:xi="http://www.w3.org/2001/XInclude">
<name>IdeaVim</name>
<id>IdeaVIM</id>
<description><![CDATA[
@ -21,7 +13,7 @@
<li><a href="https://youtrack.jetbrains.com/issues/VIM">Issue tracker</a>: feature requests and bug reports</li>
</ul>
]]></description>
<version>SNAPSHOT</version>
<version>chylex</version>
<vendor>JetBrains</vendor>
<!-- Mark the plugin as compatible with RubyMine and other products based on the IntelliJ platform (including CWM) -->
@ -103,7 +95,7 @@
<!-- Do not care about red handlers in order. They are necessary for proper ordering, and they'll be resolved when needed -->
<editorActionHandler action="EditorEnter" implementationClass="com.maddyhome.idea.vim.handler.VimEnterHandler"
id="ideavim-enter"
order="before editorEnter, before rd.client.editor.enter, after smart-step-into-enter, after AceHandlerEnter, after jupyterCommandModeEnterKeyHandler, after swift.placeholder.enter"/>
order="before editorEnter, before inline.completion.enter, before rd.client.editor.enter, after smart-step-into-enter, after AceHandlerEnter, after jupyterCommandModeEnterKeyHandler, after swift.placeholder.enter"/>
<editorActionHandler action="EditorEnter" implementationClass="com.maddyhome.idea.vim.handler.CaretShapeEnterEditorHandler"
id="ideavim-enter-shape"
order="before jupyterCommandModeEnterKeyHandler"/>
@ -126,6 +118,8 @@
implementationClass="com.maddyhome.idea.vim.handler.StartNewLineBeforeCurrentDetector"
id="ideavim-start-new-line-before-current-detector"
order="first"/>
<editorFactoryDocumentListener
implementation="com.maddyhome.idea.vim.listener.VimListenerManager$VimDocumentListener"/>
</extensions>
<xi:include href="/META-INF/includes/ApplicationServices.xml" xpointer="xpointer(/idea-plugin/*)"/>
@ -133,10 +127,12 @@
<xi:include href="/META-INF/includes/VimListeners.xml" xpointer="xpointer(/idea-plugin/*)"/>
<actions resource-bundle="messages.IdeaVimBundle">
<action id="VimPluginToggle" class="com.maddyhome.idea.vim.action.VimPluginToggleAction">
<group id="com.chylex.intellij.vim" text="Vim" popup="true">
<add-to-group group-id="ToolsMenu" anchor="last"/>
</action>
<action id="VimPluginToggle" class="com.maddyhome.idea.vim.action.VimPluginToggleAction"/>
<action id="VimRunLastMacroInOpenFiles" class="com.maddyhome.idea.vim.action.VimRunLastMacroInOpenFiles"/>
</group>
<!-- Internal -->
<!--suppress PluginXmlI18n -->
<action id="VimInternalAddBlockInlays" class="com.maddyhome.idea.vim.action.internal.AddBlockInlaysAction" text="Add Test Block Inlays | IdeaVim Internal" internal="true"/>
@ -154,5 +150,6 @@
</group>
<action id="VimFindActionIdAction" class="com.maddyhome.idea.vim.listener.FindActionIdAction"/>
<action id="VimJumpToSource" class="com.intellij.diff.actions.impl.OpenInEditorAction" />
</actions>
</idea-plugin>

View File

@ -1,6 +1,3 @@
{
"col": "com.maddyhome.idea.vim.vimscript.model.functions.handlers.ColFunctionHandler",
"has": "com.maddyhome.idea.vim.vimscript.model.functions.handlers.HasFunctionHandler",
"line": "com.maddyhome.idea.vim.vimscript.model.functions.handlers.LineFunctionHandler",
"submatch": "com.maddyhome.idea.vim.vimscript.model.functions.handlers.SubmatchFunctionHandler"
"has": "com.maddyhome.idea.vim.vimscript.model.functions.handlers.HasFunctionHandler"
}

View File

@ -28,6 +28,7 @@ E20=E20: Mark not set
e_nopresub=E33: No previous substitute regular expression
e_noprev=E34: No previous command
e_noprevre=E35: No previous regular expression
E39=E39: Number expected
e_re_damg=E43: Damaged match string
e_re_corr=E44: Currupted regexp program
E50=E50: Too many \\z(
@ -69,6 +70,7 @@ E385=E385: Search hit BOTTOM without match for: {0}
E471=E471: Argument required
E474=E474: Invalid argument: {0}
E475=E475: Invalid argument: {0}
E477=E477: No ! allowed
E481=E481: No range allowed
E486=E486: Pattern not found: {0}
E488=E488: Trailing characters: {0}
@ -84,6 +86,7 @@ E549=E549: Illegal percentage: {0}
E774=E774: 'operatorfunc' is empty
e841.reserved.name.cannot.be.used.for.user.defined.command=E841: Reserved name, cannot be used for user defined command
E939=E939: Positive count required
E1214=E1214: Digraph must be just two characters: {0}
message.search.hit.bottom=search hit BOTTOM, continuing at TOP
message.search.hit.top=search hit TOP, continuing at BOTTOM

View File

@ -69,6 +69,7 @@ class ChangeActionTest : VimTestCase() {
// VIM-620 |i_CTRL-O|
@Test
@TestWithoutNeovim(SkipNeovimReason.UNCLEAR)
fun testInsertSingleCommandAndNewLineInserting4() {
doTest(
listOf("i", "<C-O>", "v", "d"),
@ -129,6 +130,7 @@ class ChangeActionTest : VimTestCase() {
// VIM-311 |i_CTRL-O|
@Test
@TestWithoutNeovim(SkipNeovimReason.UNCLEAR)
fun testInsertSingleCommand() {
doTest(
listOf("i", "def", "<C-O>", "d2h", "x"),
@ -1072,6 +1074,7 @@ foobaz
@Test
@TestFor(issues = ["VIM-2074"])
@TestWithoutNeovim(SkipNeovimReason.UNCLEAR)
fun `backspace with replace mode`() {
configureByText("${c}Hello world")
typeText("R1111")

View File

@ -11,6 +11,7 @@ import com.intellij.idea.TestFor
import com.maddyhome.idea.vim.KeyHandler
import com.maddyhome.idea.vim.VimPlugin
import com.maddyhome.idea.vim.api.injector
import com.maddyhome.idea.vim.newapi.vim
import com.maddyhome.idea.vim.state.mode.Mode
import org.jetbrains.plugins.ideavim.SkipNeovimReason
import org.jetbrains.plugins.ideavim.TestWithoutNeovim
@ -250,7 +251,10 @@ class CopyActionTest : VimTestCase() {
enterCommand("set clipboard=unnamed")
kotlin.test.assertEquals('*', VimPlugin.getRegister().defaultRegister)
typeText("yy")
val starRegister = VimPlugin.getRegister().getRegister('*')
val vimEditor = fixture.editor.vim
val context = injector.executionContextManager.getEditorExecutionContext(vimEditor)
val registerService = injector.registerGroup
val starRegister = registerService.getRegister(vimEditor, context, '*')
assertNotNull<Any>(starRegister)
kotlin.test.assertEquals("bar\n", starRegister.text)
}
@ -262,7 +266,10 @@ class CopyActionTest : VimTestCase() {
fun testLineWiseClipboardYankPaste() {
configureByText("<caret>foo\n")
typeText("\"*yy" + "\"*p")
val register = VimPlugin.getRegister().getRegister('*')
val vimEditor = fixture.editor.vim
val context = injector.executionContextManager.getEditorExecutionContext(vimEditor)
val registerService = injector.registerGroup
val register = registerService.getRegister(vimEditor, context, '*')
assertNotNull<Any>(register)
kotlin.test.assertEquals("foo\n", register.text)
val editor = fixture.editor
@ -290,7 +297,10 @@ class CopyActionTest : VimTestCase() {
""".trimIndent(),
)
typeText("<C-V>j" + "\"*y" + "\"*p")
val register = VimPlugin.getRegister().getRegister('*')
val vimEditor = fixture.editor.vim
val context = injector.executionContextManager.getEditorExecutionContext(vimEditor)
val registerService = injector.registerGroup
val register = registerService.getRegister(vimEditor, context, '*')
assertNotNull<Any>(register)
kotlin.test.assertEquals(
"""

View File

@ -10,10 +10,10 @@ package org.jetbrains.plugins.ideavim.action
import com.intellij.idea.TestFor
import com.intellij.testFramework.LoggedErrorProcessor
import com.maddyhome.idea.vim.KeyHandler
import com.maddyhome.idea.vim.VimPlugin
import com.maddyhome.idea.vim.api.injector
import com.maddyhome.idea.vim.api.keys
import com.maddyhome.idea.vim.command.MappingMode
import com.maddyhome.idea.vim.newapi.vim
import org.jetbrains.plugins.ideavim.ExceptionHandler
import org.jetbrains.plugins.ideavim.OnlyThrowLoggedErrorProcessor
import org.jetbrains.plugins.ideavim.SkipNeovimReason
@ -52,15 +52,18 @@ class MacroActionTest : VimTestCase() {
configureByText("")
enterCommand("imap pp hello")
typeText(injector.parser.parseKeys("qa" + "i" + "pp<Esc>" + "q"))
assertRegister('a', "ipp<Esc>")
assertRegister('a', "ipp^[")
}
@Test
fun testRecordMacroWithDigraph() {
typeTextInFile(injector.parser.parseKeys("qa" + "i" + "<C-K>OK<Esc>" + "q"), "")
val register = VimPlugin.getRegister().getRegister('a')
val vimEditor = fixture.editor.vim
val context = injector.executionContextManager.getEditorExecutionContext(vimEditor)
val registerService = injector.registerGroup
val register = registerService.getRegister(vimEditor, context, 'a')
assertNotNull<Any>(register)
assertRegister('a', "i<C-K>OK<Esc>")
assertRegister('a', "i^KOK^[")
}
@Test
@ -96,7 +99,10 @@ class MacroActionTest : VimTestCase() {
configureByText(content)
typeText(injector.parser.parseKeys("qa" + ":map x y<CR>" + "q"))
val register = VimPlugin.getRegister().getRegister('a')
val vimEditor = fixture.editor.vim
val context = injector.executionContextManager.getEditorExecutionContext(vimEditor)
val registerService = injector.registerGroup
val register = registerService.getRegister(vimEditor, context, 'a')
val registerSize = register!!.keys.size
assertEquals(9, registerSize)
}
@ -244,8 +250,10 @@ class MacroActionTest : VimTestCase() {
)
injector.keyGroup.putKeyMapping(MappingMode.NXO, keys("abc"), exceptionMappingOwner, ExceptionHandler(), false)
injector.registerGroup.storeText('k', "abc")
injector.registerGroup.storeText('q', "x@ky")
val vimEditor = fixture.editor.vim
val context = injector.executionContextManager.getEditorExecutionContext(vimEditor)
injector.registerGroup.storeText(vimEditor, context, 'k', "abc")
injector.registerGroup.storeText(vimEditor, context, 'q', "x@ky")
val exception = assertThrows<Throwable> {
LoggedErrorProcessor.executeWith<Throwable>(OnlyThrowLoggedErrorProcessor) {

View File

@ -10,6 +10,7 @@ package org.jetbrains.plugins.ideavim.action
import com.maddyhome.idea.vim.VimPlugin
import com.maddyhome.idea.vim.api.injector
import com.maddyhome.idea.vim.newapi.vim
import org.jetbrains.plugins.ideavim.SkipNeovimReason
import org.jetbrains.plugins.ideavim.TestWithoutNeovim
import org.jetbrains.plugins.ideavim.VimTestCase
@ -28,8 +29,10 @@ class MacroWithEditingTest : VimTestCase() {
@Test
fun `test copy and perform macro`() {
typeTextInFile(injector.parser.parseKeys("^v\$h\"wy"), "iHello<Esc>")
kotlin.test.assertEquals("iHello<Esc>", VimPlugin.getRegister().getRegister('w')?.rawText)
typeTextInFile(injector.parser.parseKeys("^v\$h\"wy"), "iHello")
val vimEditor = fixture.editor.vim
val context = injector.executionContextManager.getEditorExecutionContext(vimEditor)
kotlin.test.assertEquals("iHello", VimPlugin.getRegister().getRegister(vimEditor, context, 'w')?.text)
setText("")
typeText(injector.parser.parseKeys("@w"))
waitAndAssert {
@ -39,8 +42,10 @@ class MacroWithEditingTest : VimTestCase() {
@Test
fun `test copy and perform macro ctrl_a`() {
typeTextInFile(injector.parser.parseKeys("^v\$h\"wy"), "<C-A>")
kotlin.test.assertEquals("<C-A>", VimPlugin.getRegister().getRegister('w')?.rawText)
typeTextInFile(injector.parser.parseKeys("^v\$h\"wy"), "\u0001")
val vimEditor = fixture.editor.vim
val context = injector.executionContextManager.getEditorExecutionContext(vimEditor)
kotlin.test.assertEquals(injector.parser.parseKeys("<C-A>"), injector.registerGroup.getRegister(vimEditor, context, 'w')!!.keys)
setText("1")
typeText(injector.parser.parseKeys("@w"))
waitAndAssert {

View File

@ -17,6 +17,7 @@ import com.maddyhome.idea.vim.newapi.vim
import com.maddyhome.idea.vim.state.mode.SelectionType
import org.jetbrains.plugins.ideavim.VimBehaviorDiffers
import org.jetbrains.plugins.ideavim.VimTestCase
import org.junit.jupiter.api.Disabled
import org.junit.jupiter.api.Test
import kotlin.test.assertNotNull
@ -1816,6 +1817,7 @@ $c five six se${c}ven eight
)
}
@Disabled("Action execution in tests is broken for 2024.2")
@Test
fun testInsertNewLineAboveAction() {
typeTextInFile(
@ -1842,6 +1844,7 @@ $c five six se${c}ven eight
)
}
@Disabled("Action execution in tests is broken for 2024.2")
@VimBehaviorDiffers(originalVimAfter = "${c}\n${c}\nabcde\n${c}\n${c}\nabcde\n")
@Test
fun testInsertNewLineAboveActionWithMultipleCaretsInLine() {
@ -1856,6 +1859,7 @@ $c five six se${c}ven eight
assertState("${c}\nabcde\n${c}\nabcde\n")
}
@Disabled("Action execution in tests is broken for 2024.2")
@Test
fun testInsertNewLineBelowAction() {
typeTextInFile(
@ -2161,7 +2165,9 @@ rtyfg${c}hzxc"""
fun testPutTextBeforeCursor() {
val before = "${c}qwe asd ${c}zxc rty ${c}fgh vbn"
configureByText(before)
injector.registerGroup.storeText('*', "fgh", SelectionType.CHARACTER_WISE)
val vimEditor = fixture.editor.vim
val context = injector.executionContextManager.getEditorExecutionContext(vimEditor)
injector.registerGroup.storeText(vimEditor, context, '*', "fgh", SelectionType.CHARACTER_WISE)
typeText(injector.parser.parseKeys("\"*P" + "3l" + "\"*P"))
val after = "fghqwfg${c}he asd fghzxfg${c}hc rty fghfgfg${c}hh vbn"
assertState(after)
@ -2171,9 +2177,11 @@ rtyfg${c}hzxc"""
fun testPutTextBeforeCursorOverlapRange() {
val before = "${c}q${c}we asd zxc rty ${c}fgh vbn"
val editor = configureByText(before)
injector.registerGroup.storeText('*', "fgh")
val vimEditor = fixture.editor.vim
val context = injector.executionContextManager.getEditorExecutionContext(vimEditor)
injector.registerGroup.storeText(vimEditor, context, '*', "fgh")
VimPlugin.getRegister()
.storeText(IjVimEditor(editor), editor.vim.primaryCaret(), TextRange(16, 19), SelectionType.CHARACTER_WISE, false)
.storeText(IjVimEditor(editor), context, editor.vim.primaryCaret(), TextRange(16, 19), SelectionType.CHARACTER_WISE, false)
typeText(injector.parser.parseKeys("\"*P"))
val after = "fg${c}hqfg${c}hwe asd zxc rty fg${c}hfgh vbn"
assertState(after)
@ -2183,7 +2191,9 @@ rtyfg${c}hzxc"""
fun testPutTextAfterCursor() {
val before = "${c}qwe asd ${c}zxc rty ${c}fgh vbn"
configureByText(before)
injector.registerGroup.storeText('*', "fgh", SelectionType.CHARACTER_WISE)
val vimEditor = fixture.editor.vim
val context = injector.executionContextManager.getEditorExecutionContext(vimEditor)
injector.registerGroup.storeText(vimEditor, context, '*', "fgh", SelectionType.CHARACTER_WISE)
typeText(injector.parser.parseKeys("\"*p" + "3l" + "2\"*p"))
val after = "qfghwe fghfg${c}hasd zfghxc fghfg${c}hrty ffghgh fghfg${c}hvbn"
assertState(after)
@ -2193,7 +2203,9 @@ rtyfg${c}hzxc"""
fun testPutTextAfterCursorOverlapRange() {
val before = "${c}q${c}we asd zxc rty ${c}fgh vbn"
configureByText(before)
injector.registerGroup.storeText('*', "fgh", SelectionType.CHARACTER_WISE)
val vimEditor = fixture.editor.vim
val context = injector.executionContextManager.getEditorExecutionContext(vimEditor)
injector.registerGroup.storeText(vimEditor, context, '*', "fgh", SelectionType.CHARACTER_WISE)
typeText(injector.parser.parseKeys("2\"*p"))
val after = "qfghfg${c}hwfghfg${c}he asd zxc rty ffghfg${c}hgh vbn"
assertState(after)
@ -2208,7 +2220,9 @@ rtyfg${c}hzxc"""
""".trimIndent()
configureByText(before)
injector.registerGroup.storeText('*', "zxcvbn\n", SelectionType.LINE_WISE)
val vimEditor = fixture.editor.vim
val context = injector.executionContextManager.getEditorExecutionContext(vimEditor)
injector.registerGroup.storeText(vimEditor, context, '*', "zxcvbn\n", SelectionType.LINE_WISE)
typeText(injector.parser.parseKeys("\"*P"))
val after = """
${c}zxcvbn
@ -2342,7 +2356,9 @@ rtyfg${c}hzxc"""
private fun testPutOverlapLine(before: String, after: String, beforeCursor: Boolean) {
configureByText(before)
injector.registerGroup.storeText('*', "zxcvbn\n", SelectionType.LINE_WISE)
val vimEditor = fixture.editor.vim
val context = injector.executionContextManager.getEditorExecutionContext(vimEditor)
injector.registerGroup.storeText(vimEditor, context, '*', "zxcvbn\n", SelectionType.LINE_WISE)
typeText(injector.parser.parseKeys("\"*" + if (beforeCursor) "P" else "p"))
assertState(after)
}
@ -2356,7 +2372,9 @@ rtyfg${c}hzxc"""
""".trimIndent()
configureByText(before)
injector.registerGroup.storeText('*', "zxcvbn", SelectionType.LINE_WISE)
val vimEditor = fixture.editor.vim
val context = injector.executionContextManager.getEditorExecutionContext(vimEditor)
injector.registerGroup.storeText(vimEditor, context, '*', "zxcvbn", SelectionType.LINE_WISE)
typeText(injector.parser.parseKeys("\"*p"))
val after = """
qwerty
@ -2374,7 +2392,9 @@ rtyfg${c}hzxc"""
fun testPutTextBeforeCursorMoveCursor() {
val before = "qw${c}e asd z${c}xc rty ${c}fgh vbn"
configureByText(before)
injector.registerGroup.storeText('*', "fgh", SelectionType.CHARACTER_WISE)
val vimEditor = fixture.editor.vim
val context = injector.executionContextManager.getEditorExecutionContext(vimEditor)
injector.registerGroup.storeText(vimEditor, context, '*', "fgh", SelectionType.CHARACTER_WISE)
typeText(injector.parser.parseKeys("l" + "\"*gP" + "b" + "\"*gP"))
val after = "fgh${c}qwefgh asd fgh${c}zxfghc rty fgh${c}ffghgh vbn"
assertState(after)
@ -2384,7 +2404,9 @@ rtyfg${c}hzxc"""
fun testPutTextAfterCursorMoveCursor() {
val before = "qw${c}e asd z${c}xc rty ${c}fgh vbn"
configureByText(before)
injector.registerGroup.storeText('*', "fgh", SelectionType.CHARACTER_WISE)
val vimEditor = fixture.editor.vim
val context = injector.executionContextManager.getEditorExecutionContext(vimEditor)
injector.registerGroup.storeText(vimEditor, context, '*', "fgh", SelectionType.CHARACTER_WISE)
typeText(injector.parser.parseKeys("l" + "\"*gp" + "b" + "\"*gp"))
val after = "qwe ffgh${c}ghasd zfgh${c}xcfgh rty ffgh${c}gfghh vbn"
assertState(after)
@ -2399,7 +2421,9 @@ rtyfg${c}hzxc"""
""".trimIndent()
configureByText(before)
injector.registerGroup.storeText('*', "zxcvbn\n", SelectionType.LINE_WISE)
val vimEditor = fixture.editor.vim
val context = injector.executionContextManager.getEditorExecutionContext(vimEditor)
injector.registerGroup.storeText(vimEditor, context, '*', "zxcvbn\n", SelectionType.LINE_WISE)
typeText(injector.parser.parseKeys("\"*gP"))
val after = """
zxcvbn
@ -2422,7 +2446,9 @@ rtyfg${c}hzxc"""
""".trimIndent()
configureByText(before)
injector.registerGroup.storeText('*', "zxcvbn", SelectionType.LINE_WISE)
val vimEditor = fixture.editor.vim
val context = injector.executionContextManager.getEditorExecutionContext(vimEditor)
injector.registerGroup.storeText(vimEditor, context, '*', "zxcvbn", SelectionType.LINE_WISE)
typeText(injector.parser.parseKeys("\"*gp"))
val after = """
qwerty
@ -2442,7 +2468,9 @@ rtyfg${c}hzxc"""
* two
"""
configureByText(before)
injector.registerGroup.storeText('*', " *\n *\n", SelectionType.BLOCK_WISE)
val vimEditor = fixture.editor.vim
val context = injector.executionContextManager.getEditorExecutionContext(vimEditor)
injector.registerGroup.storeText(vimEditor, context, '*', " *\n *\n", SelectionType.BLOCK_WISE)
typeText(injector.parser.parseKeys("\"*p"))
val after = """ * $c *one$c *
* *two *
@ -2456,7 +2484,9 @@ rtyfg${c}hzxc"""
* two
"""
configureByText(before)
injector.registerGroup.storeText('*', " *\n \n", SelectionType.BLOCK_WISE)
val vimEditor = fixture.editor.vim
val context = injector.executionContextManager.getEditorExecutionContext(vimEditor)
injector.registerGroup.storeText(vimEditor, context, '*', " *\n \n", SelectionType.BLOCK_WISE)
typeText(injector.parser.parseKeys("\"*P"))
val after = """ *$c * on$c *e
* tw o
@ -2475,7 +2505,9 @@ rtyfg${c}hzxc"""
vb${c}n
""".trimIndent()
configureByText(before)
injector.registerGroup.storeText('*', "qwe\n", SelectionType.LINE_WISE)
val vimEditor = fixture.editor.vim
val context = injector.executionContextManager.getEditorExecutionContext(vimEditor)
injector.registerGroup.storeText(vimEditor, context, '*', "qwe\n", SelectionType.LINE_WISE)
typeText(injector.parser.parseKeys("\"*p"))
val after = """
qwe
@ -2497,7 +2529,10 @@ rtyfg${c}hzxc"""
val before = "qwe ${c}asd ${c}zxc"
configureByText(before)
typeText(injector.parser.parseKeys("ye"))
val lastRegister = VimPlugin.getRegister().lastRegister
val vimEditor = fixture.editor.vim
val context = injector.executionContextManager.getEditorExecutionContext(vimEditor)
val registerService = injector.registerGroup
val lastRegister = registerService.getRegister(vimEditor, context, registerService.lastRegisterChar)
assertNotNull<Any>(lastRegister)
val text = lastRegister.text
assertNotNull<Any>(text)
@ -2519,7 +2554,10 @@ rtyfg${c}hzxc"""
""".trimIndent()
configureByText(before)
typeText(injector.parser.parseKeys("yj"))
val lastRegister = VimPlugin.getRegister().lastRegister
val vimEditor = fixture.editor.vim
val context = injector.executionContextManager.getEditorExecutionContext(vimEditor)
val registerService = injector.registerGroup
val lastRegister = registerService.getRegister(vimEditor, context, registerService.lastRegisterChar)
assertNotNull<Any>(lastRegister)
val text = lastRegister.text
assertNotNull<Any>(text)
@ -2553,7 +2591,10 @@ rtyfg${c}hzxc"""
""".trimIndent()
configureByText(before)
typeText(injector.parser.parseKeys("2yy"))
val lastRegister = VimPlugin.getRegister().lastRegister
val vimEditor = fixture.editor.vim
val context = injector.executionContextManager.getEditorExecutionContext(vimEditor)
val registerService = injector.registerGroup
val lastRegister = registerService.getRegister(vimEditor, context, registerService.lastRegisterChar)
assertNotNull<Any>(lastRegister)
val text = lastRegister.text
assertNotNull<Any>(text)

View File

@ -9,6 +9,7 @@ package org.jetbrains.plugins.ideavim.action
import com.maddyhome.idea.vim.VimPlugin
import com.maddyhome.idea.vim.api.injector
import com.maddyhome.idea.vim.newapi.vim
import com.maddyhome.idea.vim.register.RegisterConstants.LAST_INSERTED_TEXT_REGISTER
import com.maddyhome.idea.vim.register.RegisterConstants.LAST_SEARCH_REGISTER
import com.maddyhome.idea.vim.register.RegisterConstants.SMALL_DELETION_REGISTER
@ -210,7 +211,9 @@ class SpecialRegistersTest : VimTestCase() {
private fun getRegisterText(registerName: Char): String? {
val registerGroup = VimPlugin.getRegister()
val register = registerGroup.getRegister(registerName)
val vimEditor = fixture.editor.vim
val context = injector.executionContextManager.getEditorExecutionContext(vimEditor)
val register = registerGroup.getRegister(vimEditor, context, registerName)
assertNotNull<Any>(register)
return register!!.text
}

View File

@ -162,6 +162,16 @@ class UndoActionTest : VimTestCase() {
configureByText("Lorem ${c}ipsum dolor sit amet")
}
@Test
@TestFor(issues = ["VIM-3671"])
fun `test undo scrolls caret to reset scrolloff`() {
configureByLines(200, "lorem ipsum dolor sit amet")
enterCommand("set scrolloff=10")
typeText("50G", "dd", "G", "u")
assertPosition(49, 0)
assertVisibleArea(39, 73)
}
private fun hasSelection(): Boolean {
val editor = fixture.editor
return editor.caretModel.primaryCaret.hasSelection()

View File

@ -194,4 +194,16 @@ Mode.INSERT,
fun testLastSymbolInWord() {
doTest("cw", "fo${c}o", "fo${c}", Mode.INSERT)
}
// VIM-3729
@Test
fun `test change with count applies only to motion when repeated`() {
doTest(listOf("2c3l", "foo<Esc>", "w", "."),
"""
banana banana
""".trimIndent(),
"""
foo foo
""".trimIndent())
}
}

View File

@ -10,7 +10,8 @@
package org.jetbrains.plugins.ideavim.action.change.delete
import com.maddyhome.idea.vim.VimPlugin
import com.maddyhome.idea.vim.api.injector
import com.maddyhome.idea.vim.newapi.vim
import org.jetbrains.plugins.ideavim.SkipNeovimReason
import org.jetbrains.plugins.ideavim.TestWithoutNeovim
import org.jetbrains.plugins.ideavim.VimBehaviorDiffers
@ -102,7 +103,10 @@ class DeleteMotionActionTest : VimTestCase() {
expression${c} two
""".trimIndent(),
)
val savedText = VimPlugin.getRegister().lastRegister?.text ?: ""
val vimEditor = fixture.editor.vim
val context = injector.executionContextManager.getEditorExecutionContext(vimEditor)
val registerService = injector.registerGroup
val savedText = registerService.getRegister(vimEditor, context, registerService.lastRegisterChar)?.text ?: ""
kotlin.test.assertEquals(" expression two\n", savedText)
}

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