1
0
mirror of https://github.com/chylex/IntelliJ-IdeaVim.git synced 2025-08-21 16:54:06 +02:00

Compare commits

..

409 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
IdeaVim Bot
4c09ab4766 Add Felix Wiemuth to contributors list 2024-08-31 09:01:59 +00:00
Alex Plate
ee447bce07 Add a note to replace the raw string after the changes will be available
https://github.com/JetBrains/intellij-community/pull/2825
2024-08-30 18:57:00 +03:00
Alex Plate
5fb4c10f88 Add 2024.2 IJ for testing on TC 2024-08-30 18:42:02 +03:00
Alex Plate
ed2fcb08b0 [VIM-3620] Add a link to the usage survey 2024-08-30 18:34:10 +03:00
dedd90ce13 Fix(VIM-3615): Escape closes dialog while waiting for more keys 2024-08-30 16:46:53 +03:00
Alex Plate
73326e623e Use the behavior form in docs 2024-08-30 16:42:03 +03:00
Alex Plate
a2bc34d6ec [VIM-3620] Show the uninstall survey only when uninstalling IdeaVim 2024-08-30 16:41:36 +03:00
Felix Wiemuth
b652c7726a Fix typo 2024-08-30 16:41:05 +03:00
Matt Ellis
fb7a2de07b Encapsulate the command builder's state flag
This also gets rid of BAD_COMMAND, which was set but never checked - the function that set the flag would immediately reset the command builder
2024-08-30 16:36:24 +03:00
Matt Ellis
def9ca479b Ensure builder resets to a root command trie node
Also refactors command nodes a bit for better debug/trace output
2024-08-30 16:36:24 +03:00
Matt Ellis
0936e0761f Reorder CommandBuilder methods
Try to keep related functions together: awaiting arguments, count, registers, adding action/argument, processing keystrokes, build, reset.
2024-08-30 16:36:24 +03:00
Matt Ellis
09a335bcfe Start to encapsulate setting command builder state
Also rename `pushCommandPart` and `completeCommandPart`
2024-08-30 16:36:24 +03:00
Matt Ellis
37b8d69bac Remove unused EMPTY_COMMAND 2024-08-30 16:36:24 +03:00
Matt Ellis
13308050a8 Remove unnecessary OperatorArguments parameters 2024-08-30 16:36:24 +03:00
Matt Ellis
a1a553ebc9 Deprecate OperatorArguments.mode 2024-08-30 16:36:24 +03:00
Matt Ellis
5bb0c4f7cb Use editor.mode instead of OperatorArguments.mode
`OperatorArguments.mode` is the mode *before* the command was completed, rather than the current mode, which is non-obvious. E.g. for a command in Insert mode, it will still be Insert, and for a (simple) command in Normal, it will still be Normal. The only difference is that a command such as `dw` would be in Operator-pending before the command is completed. That logic is not required for this method, so it's safe to use the current mode.

This allows us to start to deprecate `OperatorArguments.mode`.
2024-08-30 16:36:24 +03:00
Matt Ellis
da6736f24a Simplify the logic when yanking during delete 2024-08-30 16:36:24 +03:00
Matt Ellis
4df1ce2ae8 Remove OperatorArguments.mode usage in block insert
`OperatorArguments.mode` is the mode *before* the command is completed, so might be Visual, Operator-pending, Insert, etc. It's not immediately obvious this is the case, so we're going to deprecate `OperatorArguments.mode` to avoid confusion with `editor.mode`.

It's not required for this method because it's only called for Visual-block mode.
2024-08-30 16:36:24 +03:00
Matt Ellis
00fd4cd491 Handle op-pending for space and backspace 2024-08-30 16:36:24 +03:00
Matt Ellis
d185672e2f Deprecate OperatorArguments.isOperatorPending
Register specific handlers for Operator-pending mode instead of relying on a runtime flag for behaviour. Also refactors and renames some arrow motion handlers.
2024-08-30 16:36:24 +03:00
Matt Ellis
55fef03a4a Add 'whichwrap' to dictionary 2024-08-30 16:36:24 +03:00
Matt Ellis
69b3e4f782 Start to refactor OperatorArguments 2024-08-30 16:36:24 +03:00
Matt Ellis
6be29b0378 Remove KeyHandler.isOperatorPending
It's easier to just look at mode. We don't need the additional check on command builder, because we can't be in OP_PENDING without pushing an operator action to the command builder
2024-08-30 16:36:24 +03:00
Matt Ellis
9965c863a6 Encapsulate command node state in builder 2024-08-30 16:36:24 +03:00
Matt Ellis
3f11ae512c Move register pending state to command builder 2024-08-30 16:36:24 +03:00
Matt Ellis
1c842eb3d8 Avoid exposing misleading count on command builder 2024-08-30 16:36:24 +03:00
Matt Ellis
c7fbce675b Update comment 2024-08-30 16:36:24 +03:00
Matt Ellis
d7e68488c8 Make Command.rawCount immutable 2024-08-30 16:36:24 +03:00
Matt Ellis
69d13a74e6 Remove unnecessary copy method 2024-08-30 16:36:24 +03:00
Matt Ellis
5a83df34a7 Replace var properties with read only 2024-08-30 16:36:24 +03:00
Matt Ellis
0a18c388e0 Simplify CommandBuilder
Fixes selecting last register if multiple registers are used in a command
2024-08-30 16:36:24 +03:00
Matt Ellis
1a3409e7df Remove count from motion argument
Only Command has a count. The motion argument is now a sealed class hierarchy, and consists only of the motion action and optional argument. This is to reduce confusion over which count to use, and potential incorrect calculation of the count
2024-08-30 16:36:24 +03:00
Matt Ellis
e93db961a0 Wrap offsets argument as an external action 2024-08-30 16:36:24 +03:00
Matt Ellis
8fd76bd08f Refactor properties for sealed Argument classes 2024-08-30 16:36:24 +03:00
Matt Ellis
0eea4a5b2c Introduce sealed classes to represent an argument 2024-08-30 16:36:24 +03:00
Matt Ellis
18a0c533e2 Remove unused OperatedRange type 2024-08-30 16:36:24 +03:00
Matt Ellis
0bd8d8f4d2 Remove unused digraph command flag 2024-08-30 16:36:24 +03:00
Alex Plate
64a89c8863 [VIM-3620] Add the uninstall survey
We're actively working on understanding the users and what we can improve in the plugin
2024-08-28 18:57:29 +03:00
Filipp Vakhitov
5b17d7740e Update generated files after merging PRs 2024-08-25 21:51:14 +03:00
IdeaVim Bot
e406885ec6 Add Igor Babko to contributors list 2024-08-24 09:01:39 +00:00
filipp
9bbeab8062 VIM-2074 Backspace behaviour is incorrect in Replace mode 2024-08-23 14:22:48 +03:00
filipp
373bfc4eab Add endOffset to LiveRange 2024-08-23 14:22:48 +03:00
filipp
050f2f7b97 Respect emojis and don't assume that every character is a single offset 2024-08-23 14:18:03 +03:00
Filipp Vakhitov
e30bc14843 Make closing command line safer 2024-08-23 14:18:03 +03:00
Filipp Vakhitov
76d590be11 Support more flexible command line restrictions for Fleet 2024-08-23 14:18:03 +03:00
Filipp Vakhitov
b005328b4a Exclude generated files from .gitignore
I've also added sorting to be more confident that the generated file will be persistent across different machines
2024-08-23 14:18:03 +03:00
Filipp Vakhitov
ad20021cee Remove currentAction and some unused code 2024-08-23 14:18:03 +03:00
Filipp Vakhitov
126de5c218 Remove package-info.java 2024-08-23 14:18:03 +03:00
Filipp Vakhitov
0f7aef3f15 Fix tests 2024-08-23 14:18:03 +03:00
Filipp Vakhitov
f352b84922 Move some Executor logic to its base class 2024-08-23 14:18:03 +03:00
Filipp Vakhitov
789faa7cb2 Add entry to history on <Esc> in COMMAND mode 2024-08-23 14:18:03 +03:00
Filipp Vakhitov
a338f5768a Better reset 2024-08-23 14:18:03 +03:00
Filipp Vakhitov
8205c74571 Remove some old bindings 2024-08-23 14:18:03 +03:00
Filipp Vakhitov
def40eb409 Save last entry on every command line text update 2024-08-23 14:18:03 +03:00
Filipp Vakhitov
010e8a7541 Support <C-U> in command mode 2024-08-23 14:18:03 +03:00
Filipp Vakhitov
46c6778b3a Support <Down>, <S-Down>, <C-N>, <PageDown> in command mode 2024-08-23 14:18:03 +03:00
Filipp Vakhitov
0977bd4400 Support <Up>, <S-Up>, <C-P>, <PageUp> in command mode 2024-08-23 14:18:03 +03:00
Filipp Vakhitov
db092e9b0a Support history in VimCommandLine 2024-08-23 14:18:03 +03:00
Filipp Vakhitov
5cfb98e188 Remove some old bindings 2024-08-23 14:18:03 +03:00
Filipp Vakhitov
3a95b62885 Support <Insert> in command mode 2024-08-23 14:18:03 +03:00
Filipp Vakhitov
93e6adf5a9 Support <C-W> in command mode 2024-08-23 14:18:03 +03:00
Filipp Vakhitov
37204398ff Support <C-Right>, <S-Right> in command mode 2024-08-23 14:18:03 +03:00
Filipp Vakhitov
b2f450d14d Support <C-Left>, <S-Left> in command mode 2024-08-23 14:18:03 +03:00
Filipp Vakhitov
61da888571 Fix failing tests 2024-08-23 14:18:03 +03:00
Filipp Vakhitov
fcda97cfb8 Update methods to support searching in any text, not just Editor content 2024-08-23 14:18:03 +03:00
Filipp Vakhitov
1dc7ea6363 Register all shortcuts without references on ExKeyBindings
All the shortcuts will be removed from ExKeyBindings, but they still need to be registered
2024-08-23 14:18:03 +03:00
Filipp Vakhitov
bb507db884 Remove swing bindings that are already implemented 2024-08-23 14:18:03 +03:00
Filipp Vakhitov
7b0482ed94 Fix unwanted beep when can't perform an action 2024-08-23 14:18:03 +03:00
Filipp Vakhitov
1c79b0d59a Support <Right> in command mode 2024-08-23 14:18:03 +03:00
Filipp Vakhitov
ff4eb31418 Support <Left> in command mode 2024-08-23 14:18:03 +03:00
Filipp Vakhitov
cb1078cf70 Support <C-H> in command mode 2024-08-23 14:18:03 +03:00
Filipp Vakhitov
da3e40eaf6 Support <C-E>, <End> in command mode 2024-08-23 14:18:03 +03:00
Filipp Vakhitov
17f77a9639 Support <C-B>, <Home> in command mode 2024-08-23 14:18:03 +03:00
Filipp Vakhitov
3d03494354 Support <DEL> in command mode 2024-08-23 14:18:03 +03:00
Filipp Vakhitov
642caddda7 Support <BS> in command mode 2024-08-23 14:18:03 +03:00
Filipp Vakhitov
1d97c43e30 Make ExEntryAction public
Fleet registers commands manually, so commands must be accessible
2024-08-23 14:18:03 +03:00
Igor Babko
7ed3e3b53c Update support-guide.md 2024-08-23 12:14:41 +03:00
IdeaVim Bot
aa39ca2006 Add shaun to contributors list 2024-08-19 09:01:43 +00:00
shaun
bb122903de remove Latest Fixes link 2024-08-19 10:23:23 +03:00
shaun
c84a3cf64d [changelog] add YouTrack link for version fixes 2024-08-19 10:23:23 +03:00
IdeaVim Bot
f4c9464b8a Add The1xDeveloper to contributors list 2024-08-17 09:01:38 +00:00
The1xDeveloper
3ba14d05b4 VIM-566: Add za motion support for toggling folds 2024-08-16 12:28:18 +03:00
Filipp Vakhitov
2189b70b87 Fix(VIM-3601): The escape characters in IdeaVim's configuration file are invalid 2024-08-14 18:18:55 +03:00
dependabot[bot]
ea2222f9d5 Bump org.junit.jupiter:junit-jupiter from 5.10.3 to 5.11.0
Bumps [org.junit.jupiter:junit-jupiter](https://github.com/junit-team/junit5) from 5.10.3 to 5.11.0.
- [Release notes](https://github.com/junit-team/junit5/releases)
- [Commits](https://github.com/junit-team/junit5/compare/r5.10.3...r5.11.0)

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

Signed-off-by: dependabot[bot] <support@github.com>
2024-08-14 18:53:54 +03:00
Filipp Vakhitov
7d68d41888 Fix(VIM-3580): New terminal switches editor to insert mode 2024-08-12 13:44:24 +03:00
Alex Pláte
44749b6d8c Update IdeaVim Plugins.md 2024-08-12 12:16:19 +03:00
Alex Pláte
1ce4dbc569 Update Home.md 2024-08-12 12:14:44 +03:00
Alex Pláte
db59513505 Update Home.md 2024-08-12 12:13:36 +03:00
Filipp Vakhitov
8c9ff9465f Fix(VIM-3584): com.maddyhome.idea.vim.KeyHandler requests com.maddyhome.idea.vim.api.VimKeyGroup instance. Class initialization must not depend on services 2024-08-11 01:12:59 +03:00
Filipp Vakhitov
1a2322ddec OutputPanel no longer ignores key events 2024-08-10 23:55:57 +03:00
dependabot[bot]
fbbd1ebc0d Bump org.antlr:antlr4 from 4.13.1 to 4.13.2
Bumps [org.antlr:antlr4](https://github.com/antlr/antlr4) from 4.13.1 to 4.13.2.
- [Release notes](https://github.com/antlr/antlr4/releases)
- [Changelog](https://github.com/antlr/antlr4/blob/dev/CHANGES.txt)
- [Commits](https://github.com/antlr/antlr4/compare/4.13.1...4.13.2)

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

Signed-off-by: dependabot[bot] <support@github.com>
2024-08-07 18:46:09 +03:00
Alex Plate
8d5df11372 Add a warning when the action is executed from the write action
Related: VIM-3376
2024-08-06 11:11:40 +03:00
Matt Ellis
dfebe542d8 Fix incsearch highlight in multiple Rider instances
Unlike other IDEs, Rider has multiple client sessions. The IDE itself is the "local" session, while the external ReSharper process is treated as a "frontend" process. The code to get local editors was erroneously getting `ALL` sessions, rather than just `LOCAL` sessions, and assuming that the first was the local session. In Rider, opening three instances would add three additional clients, and that would change the order.

I don't know why I changed `LOCAL` to `ALL` when previously changing this bit of code. AIUI, using `LOCAL` should work fine. If it turns out that CWM or remote dev require `ALL`, please document why.

Fixes VIM-3549
2024-08-05 18:58:45 +03:00
Matt Ellis
7fde47c08b Update README.md
Added links to POSIX spec
2024-08-01 14:04:58 +03:00
dependabot[bot]
629919f634 Bump org.jetbrains.intellij.platform from 2.0.0-rc1 to 2.0.0-rc2
Bumps org.jetbrains.intellij.platform from 2.0.0-rc1 to 2.0.0-rc2.

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

Signed-off-by: dependabot[bot] <support@github.com>
2024-07-31 17:27:09 +02:00
dependabot[bot]
43fff1c73e 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.23 to 2.0.0-1.0.24.
- [Release notes](https://github.com/google/ksp/releases)
- [Commits](https://github.com/google/ksp/compare/2.0.0-1.0.23...2.0.0-1.0.24)

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

Signed-off-by: dependabot[bot] <support@github.com>
2024-07-31 17:26:53 +02:00
Alex Plate
d02f0e17ca Revert "Fix(VIM-3376): Refactor the way IdeaVim executes actions"
This reverts commit 24514039
2024-07-31 13:14:25 +03:00
Filipp Vakhitov
5dc860f61e Fix(VIM-3569): Pipe-symbol can no longer be used in remaps 2024-07-30 18:02:12 +03:00
Alex Plate
956e726c31 Add test 2024-07-29 17:48:07 +02:00
Alex Plate
446067e2fe Update formatting 2024-07-29 16:57:29 +02:00
Alex Plate
bd53a895c0 Return nullability of editor in action 2024-07-29 16:54:02 +02:00
Alex Plate
e61fed2467 Revert "Use installer in case of the snapshot dependency"
This reverts commit f5e125759f.
2024-07-29 16:51:25 +02:00
Alex Plate
f5e125759f Use installer in case of the snapshot dependency 2024-07-29 16:47:31 +02:00
Alex Plate
622eb887c8 Make style refactoring 2024-07-29 16:37:57 +02:00
Alex Plate
9fb614e16c Pass editor to the "execute" method of the NativeAction 2024-07-29 16:36:01 +02:00
Alex Plate
1e0fa07768 Fix problem with unsatisfied condition 2024-07-29 16:15:58 +02:00
Alex Plate
bfb1d5b7f5 Remove accidential return 2024-07-29 14:08:15 +02:00
Alex Plate
2fad8790a9 Reset key handler in property tests 2024-07-29 13:57:32 +02:00
Alex Plate
9719106a14 Fix incorrect assertions after migration to the new version of gradle plugin 2024-07-29 13:52:14 +02:00
Alex Plate
870a0da2a2 Update UI tests without octopus handler 2024-07-29 13:41:24 +02:00
Alex Plate
d3c315d299 Update logs location in UI tests 2024-07-29 13:39:53 +02:00
Alex Plate
2583b6e792 Print the list of available keys for debugging 2024-07-29 13:27:15 +02:00
Alex Plate
89417eb4f6 Comment out compatibility check for copilot because it's broken for the moment 2024-07-29 13:16:50 +02:00
Alex Plate
560700c9aa Pass editor to the execution of the action
Related: VIM-3376
2024-07-29 12:49:16 +02:00
Alex Plate
24514039e1 Fix(VIM-3376): Refactor the way IdeaVim executes actions
Now instead of a few hacks, we use a special function from the platform
2024-07-29 12:49:16 +02:00
Alex Plate
ff44596c1a Migrate to gradle plugin 2.0.0-rc1 2024-07-26 19:00:31 +03:00
Filipp Vakhitov
b001d63fd9 Do not fire VimInsertListener on every mode change 2024-07-26 17:30:08 +03:00
Filipp Vakhitov
5db96bef28 Fix compilation with IdeaVimExtension 2024-07-26 17:30:08 +03:00
Filipp Vakhitov
39c615cddd Do not cast VimStateMachine classes in vim-engine
It will break if we change implementation in Fleet
2024-07-26 17:30:08 +03:00
Filipp Vakhitov
961173a93b Fix tests 2024-07-26 17:30:08 +03:00
Filipp Vakhitov
92741c6356 Fix compilation 2024-07-26 17:30:08 +03:00
Filipp Vakhitov
643eb2a85f Add context to MappingProcessor 2024-07-26 17:30:08 +03:00
filipp
883744e4ee Move blockInsert to vim-engine 2024-07-26 17:30:08 +03:00
filipp
66173e03be Move reformatCodeRange to vim-engine 2024-07-26 17:30:08 +03:00
filipp
e455722758 Move more methods to vim-engine 2024-07-26 17:30:08 +03:00
filipp
823bdc1561 Replace VimYankGroup with implementation that works with VimListenersNotifier 2024-07-26 17:30:08 +03:00
filipp
f91fda2ca5 Remove VimInsertListener
It can be replaced by ModeChangeListener
2024-07-26 17:30:08 +03:00
filipp
92abd76615 Move more methods to engine 2024-07-26 17:30:08 +03:00
filipp
57c45ca153 Move more methods to engine 2024-07-26 17:30:08 +03:00
filipp
7c623ae4b5 Move case change methods to engine 2024-07-26 17:30:08 +03:00
filipp
f2ef92cdef Remove unused method 2024-07-26 17:30:08 +03:00
Filipp Vakhitov
e8e6eabe97 Fix tests
See https://youtrack.jetbrains.com/issue/VIM-3566/Unit-Tests-Test-UI-Caret-Attributes for more details.
2024-07-26 16:31:47 +03:00
Filipp Vakhitov
ef1c915264 Fix visual mode not being removed.
Steps to reproduce:
1. Select some text inside diff editor
2. Open 'Find in Files' window and search for file
3. Open the found file

Result: current mode is VISUAL
2024-07-26 14:24:01 +03:00
Filipp Vakhitov
a5e2168f7f Fix(VIM-3540): Caret icon is stuck in incorrect mode when having two or more simultaneous instances running
Some of the text input fields where Vim should not work at all had block carets.
It did not happen before, because previously we had a unique VimStateMachine for each editor and for newly created editors it was in INSERT mode. And we did call the updateSecondaryCaretsVisualAttributes method for editors that have nothing to do with Vim, but because of the INSERT mode it was looking OK.
However, now the VimStateMachine is global, and we can't rely on local INSERT anymore.
This commit forbids updating caret visual attributes for editors that do not support Vim.

NOTE: `isIdeaVimDisabledHere` is broken during editor creation handling, it always returns true. However, we do not trigger carets redraws on editor creation and do it on focus events, so it should work.
2024-07-26 00:44:35 +03:00
Filipp Vakhitov
83e5470b3a Fix(VIM-3563): Can't exit insert mode in Readonly File 2024-07-25 22:54:39 +03:00
dependabot[bot]
c446de8979 Bump org.jetbrains.kotlin:kotlin-stdlib from 1.9.24 to 1.9.25
Bumps [org.jetbrains.kotlin:kotlin-stdlib](https://github.com/JetBrains/kotlin) from 1.9.24 to 1.9.25.
- [Release notes](https://github.com/JetBrains/kotlin/releases)
- [Changelog](https://github.com/JetBrains/kotlin/blob/v1.9.25/ChangeLog.md)
- [Commits](https://github.com/JetBrains/kotlin/compare/v1.9.24...v1.9.25)

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

Signed-off-by: dependabot[bot] <support@github.com>
2024-07-24 19:02:14 +03:00
Matt Ellis
13426915f4 Remove special case handling for macro recording 2024-07-23 23:08:24 +03:00
Matt Ellis
d766c3b8ee Add test for nested insert literal action 2024-07-23 23:08:24 +03:00
Matt Ellis
844bc01537 Make functions protected 2024-07-23 23:08:24 +03:00
Matt Ellis
3d2d32b022 Move special case code to actions 2024-07-23 23:08:24 +03:00
Matt Ellis
a8677d3dd7 Refactor expected argument type
We no longer need to track a previous fallback argument type, since we don't support nested commands inside a command builder. We can just return the current argument type, or its fallback
2024-07-23 23:08:24 +03:00
Matt Ellis
7217fdf734 Remove unused special case check
This was to handle nested commands, e.g. inserting a digraph inside a search `d/foo<C-K>OK<CR>`. The command line now has its own command builder, so this check is no longer needed
2024-07-23 23:08:24 +03:00
Matt Ellis
0c867b3869 Make some CommandBuilder properties immutable 2024-07-23 23:08:24 +03:00
Matt Ellis
b3bcab4336 Refactor DigraphResult to sealed classes 2024-07-23 23:08:24 +03:00
Matt Ellis
fe1b48a9b3 Refactor digraph prompt handling 2024-07-23 23:08:24 +03:00
Matt Ellis
b5a0862520 The last command part is the current one 2024-07-23 23:08:24 +03:00
Matt Ellis
babc1f54e5 Return the last valid register if it exists
The last command part is not guaranteed to be a "select register" part. The user might have selected a register then typed an operator, and we might be waiting for a motion.
2024-07-23 23:08:24 +03:00
Matt Ellis
32b910a65b Remove unused functions and properties 2024-07-23 23:08:24 +03:00
Filipp Vakhitov
2aa71a0008 Add "Dracula" theme to mode widget 2024-07-23 19:36:07 +03:00
Filipp Vakhitov
c2c0c2aba2 Fix(VIM-3552): Undo undoes paste and insert in one keypress 2024-07-22 23:08:56 +03:00
Alex Plate
6a10cf5e0d Update the configuration for PY UI tests 2024-07-22 10:08:56 +03:00
Alex Plate
90474a3a4f Fix the mouse button issue 2024-07-19 20:25:45 +03:00
Alex Plate
be43f74bc6 Get rid of deprecated execute method in UI tests 2024-07-19 19:36:11 +03:00
Alex Plate
5916c42cd1 Disable cache for gradle UI tests 2024-07-19 18:52:58 +03:00
Alex Plate
a43c7ece32 Remove deprecated execute function in UI tests 2024-07-19 18:37:14 +03:00
Alex Plate
40c1070b1a Update path to logs in UI tests one more time 2024-07-19 17:52:54 +03:00
Alex Plate
75ccdb2a4d Update qodana after moving the vimscript to the engine 2024-07-19 17:47:49 +03:00
Alex Plate
3de7b0ca78 Disable configuration cache for gradle release task 2024-07-19 17:43:26 +03:00
Alex Plate
448e32a6cc Update AceJump dependency to the latest one 2024-07-19 17:27:02 +03:00
Alex Plate
4a85058ba2 Start using kotlin 2.0
Originally this is needed to update the dependency on AceJump, which uses the kotlin 2.0 compiler, and its classes are not compatible with the old compiler
2024-07-19 17:13:59 +03:00
filipp
7e28deb328 Better logic
In case that user will unsubscribe via setting UI and won't call IdeaVim's action
2024-07-19 16:29:15 +03:00
filipp
f3767b53b7 Subscribe IDE EAP users to IdeaVim EAP 2024-07-19 16:12:33 +03:00
Alex Plate
1026e27e64 Bring back the old version of AceJump dependency because the new one breaks the compilation 2024-07-19 14:58:20 +03:00
Alex Plate
18d653a9ae Update AceJump dependency to the latest one 2024-07-19 14:32:11 +03:00
Alex Plate
fcf4b44443 Save the whole sandbox after the UI test 2024-07-19 09:35:39 +03:00
Alex Plate
907e44b1d7 Change the gradle task for the UI tests 2024-07-19 09:05:11 +03:00
Alex Plate
6c9b39a623 Update gradle plugin and remote robot versions 2024-07-18 17:08:16 +03:00
dependabot[bot]
a3cb093b42 Bump org.jetbrains.intellij.platform from 2.0.0-beta8 to 2.0.0-beta9
Bumps org.jetbrains.intellij.platform from 2.0.0-beta8 to 2.0.0-beta9.

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

Signed-off-by: dependabot[bot] <support@github.com>
2024-07-17 15:50:17 +00:00
dependabot[bot]
524e854c61 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.22 to 2.0.0-1.0.23.
- [Release notes](https://github.com/google/ksp/releases)
- [Commits](https://github.com/google/ksp/compare/2.0.0-1.0.22...2.0.0-1.0.23)

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

Signed-off-by: dependabot[bot] <support@github.com>
2024-07-17 18:49:49 +03:00
Filipp Vakhitov
5588c27037 Attempt to fix VIM-3540
I'm not sure what causes the issue, but everything was working when we were updating visual attributes per each caret and... let update them per each caret
2024-07-17 14:45:50 +03:00
Filipp Vakhitov
90d36eea98 Make myInputInterceptor private 2024-07-17 13:30:54 +03:00
Filipp Vakhitov
f4414de86c Safer getCurrentModalInput() 2024-07-17 13:30:54 +03:00
Filipp Vakhitov
d46102ccaf Remove deprecated method from vim-engine 2024-07-17 13:30:54 +03:00
Filipp Vakhitov
82347f5f0d Fix surround 2024-07-17 13:30:54 +03:00
Filipp Vakhitov
c594f28acb Fix compilation 2024-07-17 13:30:54 +03:00
Filipp Vakhitov
bf6517e58f Add comment 2024-07-17 13:30:54 +03:00
Filipp Vakhitov
aec2f4c435 Remove isCancel argument
It was not used anywhere
2024-07-17 13:30:54 +03:00
Filipp Vakhitov
8f905758d5 Remove typing in ModalInput
Typing is more suitable for command lines than a modal input, and most likely it should be used instead
It is still possible to support typing by properly implementing the handleKey method
2024-07-17 13:30:54 +03:00
Filipp Vakhitov
80cc236f48 Remove modal input on click 2024-07-17 13:30:54 +03:00
Filipp Vakhitov
e432a02a45 Remove confirmChoice method
It used secondary loop
2024-07-17 13:30:54 +03:00
Filipp Vakhitov
d7894fa7f4 ProcessSubstituteCommand refactoring part 9
Finally, moved confirmation dialog to modal input
2024-07-17 13:30:54 +03:00
Filipp Vakhitov
853d7032f0 ProcessSubstituteCommand refactoring part 8 2024-07-17 13:30:54 +03:00
Filipp Vakhitov
5f9f57e1c0 ProcessSubstituteCommand refactoring part 7 2024-07-17 13:30:54 +03:00
Filipp Vakhitov
f4381c8216 ProcessSubstituteCommand refactoring part 6 2024-07-17 13:30:54 +03:00
Filipp Vakhitov
20eee7cae7 ProcessSubstituteCommand refactoring part 5 2024-07-17 13:30:54 +03:00
Filipp Vakhitov
33392c2148 ProcessSubstituteCommand refactoring part 4 2024-07-17 13:30:54 +03:00
Filipp Vakhitov
bb67564fbe ProcessSubstituteCommand refactoring part 3 2024-07-17 13:30:54 +03:00
Filipp Vakhitov
61ccbcd788 ProcessSubstituteCommand refactoring part 2
We do not need the `doAll` condition (because the next code block with `doAll` adjusts line and column for the next match to be correct),
And the line2 can't be >= editor.lineCount()
2024-07-17 13:30:54 +03:00
Filipp Vakhitov
1dbaa3be6d ProcessSubstituteCommand refactoring part 1 2024-07-17 13:30:54 +03:00
Filipp Vakhitov
872bc22830 Remove shouldRecord from KeyConsumer
shouldRecord value was only updated in ModeInputConsumer when the key was not handled. But when the key is not handled, it is not passed to finishedCommandPreparation and the shouldRecord value is not used
2024-07-17 13:30:54 +03:00
Filipp Vakhitov
ce23ed814c Move command line key handling logic to KeyConsumer 2024-07-17 13:30:54 +03:00
Filipp Vakhitov
82cd534756 Remove startExEntry method
We may create a command line via the VimCommandLineService and forget (or do not know) about calling startExEntry necessary. So we move its logic inside the creation of the command line
2024-07-17 13:30:54 +03:00
Filipp Vakhitov
673809d6b9 Move lastCommand to CmdFilterCommand 2024-07-17 13:30:54 +03:00
Filipp Vakhitov
cdbaf73b1e Remove unused fields from VimProcessGroup 2024-07-17 13:30:54 +03:00
Filipp Vakhitov
7f911b7e72 Fix VisualAreaMatcher
It looks for the last selection info in all conditions, not the ones that were specified
2024-07-17 13:30:54 +03:00
Filipp Vakhitov
c03a2dfe7e Add support for finishOn in readInputAndProcess 2024-07-17 13:30:54 +03:00
Filipp Vakhitov
75935ce4d1 Add readInputAndProcess to replace inputString and save us from using secondary loop 2024-07-17 13:30:54 +03:00
Filipp Vakhitov
f0b203409e Replace cancelExEntry with close method in VimCommandLine 2024-07-17 13:30:54 +03:00
Filipp Vakhitov
b3b369eb59 Remove unnecessary write action
`insertRegister` is self synchronized
2024-07-17 13:30:54 +03:00
Filipp Vakhitov
96072982cf Replace deprecated inputString() with ModalInput 2024-07-17 13:30:54 +03:00
Filipp Vakhitov
de016fc445 Always register shortcuts
No matter what kind of panel we have, we want VimShortcutKeyAction to be triggered and pass the shortcut keys (e.g. CTRL-C to close panel)
2024-07-17 13:30:54 +03:00
Filipp Vakhitov
554d9b5f7b Create a Wrapper for ExEntryPanel
ExEntryPanel already extends JPanel, so we need a wrapper to make it extend VimModalInputBase
2024-07-17 13:30:54 +03:00
Filipp Vakhitov
149edefad5 Delegate handling closing keystrokes to implementations
Sometimes it may mean aborting interception (CTRL-C), sometimes finishing it (CR)
2024-07-17 13:30:54 +03:00
Filipp Vakhitov
52d3840c83 Rename argument 2024-07-17 13:30:54 +03:00
Filipp Vakhitov
793677d4fd Add typeText() method to VimModalInput 2024-07-17 13:30:54 +03:00
Filipp Vakhitov
2376ee4877 Fix tests 2024-07-17 13:30:54 +03:00
Filipp Vakhitov
68dcab6262 Introduce ModalInput to get rid of Swing's secondary loop 2024-07-17 13:30:54 +03:00
Filipp Vakhitov
f38fd3512c Fix passing key to editor when the output panel is scrolled to its end 2024-07-17 13:30:54 +03:00
Filipp Vakhitov
e515278ba3 Add label field to VimOutputPanel 2024-07-17 13:30:54 +03:00
Filipp Vakhitov
9cc69e41ee Add KeyHandling logic to VimOutputPanel 2024-07-17 13:30:54 +03:00
Filipp Vakhitov
2109ff235c Fix tests 2024-07-16 20:32:11 +03:00
Filipp Vakhitov
d6910aa81d Fix(VIM-3538): Can't select a block in {} when the cursor is on commented Line 2024-07-16 19:23:43 +03:00
Filipp Vakhitov
8369391902 Fix(VIM-3530): "Yank inner block"/"Change inner block" uses wrong block 2024-07-16 18:47:58 +03:00
Alex Plate
f336807498 Bring back IU for copilot compatibility testing 2024-07-16 09:09:35 +03:00
Alex Plate
14ba5d7126 Convert Troubleshooter field to function to avoid early initialization 2024-07-15 13:54:20 +03:00
Alex Plate
288394d25f Update the notification text for undo 2024-07-15 13:19:03 +03:00
Alex Plate
fb08b5fd65 Prepare to changing the default value of the undo
A notification added to give the information about what happened

Related: VIM-547
2024-07-15 13:16:01 +03:00
Alex Plate
3465e11c3a Initialize the injector in multiple entry points of IntelliJ
Initially, injector was initialized in VimPlugin, assuming that almost every interaction with the plugin goes through it. However, with the plugin evolution, this class starts to be less used.
As IJ doesn't have any single entry point for the plugins, we initialize it in multiple places.
However, the architecture where the plugin might be not initizlied is considered as a bad acrhitecture and should be reviewed.

Related ticket: VIM-3369
2024-07-12 15:09:18 +03:00
Matt Ellis
e07a16863e Show possible IDs based on the action's shortcuts
Fixes VIM-3499
2024-07-12 15:09:02 +03:00
kun-codes
64f7532510 added rename functionality 2024-07-12 09:53:07 +03:00
kun-codes
dd892e77fb added paste functionality 2024-07-12 09:53:07 +03:00
kun-codes
65aeeba521 added copy functionality 2024-07-12 09:53:07 +03:00
dependabot[bot]
ca3e56d0d6 Bump org.mockito.kotlin:mockito-kotlin from 5.3.1 to 5.4.0
Bumps [org.mockito.kotlin:mockito-kotlin](https://github.com/mockito/mockito-kotlin) from 5.3.1 to 5.4.0.
- [Release notes](https://github.com/mockito/mockito-kotlin/releases)
- [Commits](https://github.com/mockito/mockito-kotlin/compare/5.3.1...5.4.0)

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

Signed-off-by: dependabot[bot] <support@github.com>
2024-07-10 18:21:34 +03:00
dependabot[bot]
bcbfb0dc32 Bump org.jetbrains.changelog from 2.2.0 to 2.2.1
Bumps org.jetbrains.changelog from 2.2.0 to 2.2.1.

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

Signed-off-by: dependabot[bot] <support@github.com>
2024-07-10 18:21:20 +03:00
Alex Plate
46a4a10e63 Reformat code in tests 2024-07-10 10:06:27 +03:00
Alex Plate
d5bddd077f Disable Gradle configuration cache in CI tests
With gradle plugin 2.0 this cache fails on GitHub Actions. This solution is temporlly and generally the `integrationsTest` should be migrated from `doLast` to some other task approach
2024-07-10 09:02:26 +03:00
Alex Plate
e22fd263cc Test compatibility of copilot on PyCharm
The current execution reports the absence of python plugin. Probably there is a dependency on PyCharm there
2024-07-09 16:35:32 +03:00
Alex Plate
f3902d0ae0 Convert property to function to avoid extra service creation 2024-07-09 16:07:57 +03:00
Alex Plate
f9213ee45d Mute some tests that depend on oldundo option 2024-07-09 15:46:41 +03:00
Alex Plate
281bc2573e Bring back some methods to preserve the compatibility with other plugins 2024-07-09 15:38:50 +03:00
Alex Plate
ae8c7f6bfa Revert "Remove old compatibility layers"
This reverts commit 6cee04a4be.
2024-07-09 15:32:39 +03:00
509 changed files with 15709 additions and 7944 deletions

View File

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

View File

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

View File

@@ -27,7 +27,7 @@ jobs:
settings-path: ${{ github.workspace }} # location for the settings.xml file settings-path: ${{ github.workspace }} # location for the settings.xml file
- name: Run tests - name: Run tests
run: ./gradlew integrationsTest run: ./gradlew --no-configuration-cache integrationsTest
env: env:
YOUTRACK_TOKEN: ${{ secrets.YOUTRACK_TOKEN }} YOUTRACK_TOKEN: ${{ secrets.YOUTRACK_TOKEN }}
GITHUB_OAUTH: ${{ secrets.GITHUB_TOKEN }} GITHUB_OAUTH: ${{ secrets.GITHUB_TOKEN }}

View File

@@ -16,14 +16,14 @@ jobs:
java-version: 17 java-version: 17
- name: Setup FFmpeg - name: Setup FFmpeg
run: brew install ffmpeg run: brew install ffmpeg
- name: Setup Gradle # - name: Setup Gradle
uses: gradle/gradle-build-action@v2.4.2 # uses: gradle/gradle-build-action@v2.4.2
- name: Build Plugin - name: Build Plugin
run: gradle :buildPlugin run: gradle :buildPlugin
- name: Run Idea - name: Run Idea
run: | run: |
mkdir -p build/reports mkdir -p build/reports
gradle runIdeForUiTests -Doctopus.handler=false > build/reports/idea.log & gradle --no-configuration-cache runIdeForUiTests -Doctopus.handler=false > build/reports/idea.log &
- name: Wait for Idea started - name: Wait for Idea started
uses: jtalk/url-health-check-action@v3 uses: jtalk/url-health-check-action@v3
with: with:
@@ -37,7 +37,7 @@ jobs:
run: mv tests/ui-ij-tests/video build/reports run: mv tests/ui-ij-tests/video build/reports
- name: Move sandbox logs - name: Move sandbox logs
if: always() if: always()
run: mv build/idea-sandbox/system/log sandbox-idea-log run: mv build/idea-sandbox/IC-2024.1.2/log_runIdeForUiTests idea-sandbox-log
- name: Save report - name: Save report
if: always() if: always()
uses: actions/upload-artifact@v4 uses: actions/upload-artifact@v4
@@ -46,7 +46,7 @@ jobs:
path: | path: |
build/reports build/reports
tests/ui-ij-tests/build/reports tests/ui-ij-tests/build/reports
sandbox-idea-log idea-sandbox-log
# build-for-ui-test-linux: # build-for-ui-test-linux:
# runs-on: ubuntu-latest # runs-on: ubuntu-latest
# steps: # steps:
@@ -78,4 +78,4 @@ jobs:
# with: # with:
# name: ui-test-fails-report-linux # name: ui-test-fails-report-linux
# path: | # path: |
# ui-test-example/build/reports # ui-test-example/build/reports

View File

@@ -19,14 +19,14 @@ jobs:
python-version: '3.10' python-version: '3.10'
- name: Setup FFmpeg - name: Setup FFmpeg
run: brew install ffmpeg run: brew install ffmpeg
- name: Setup Gradle # - name: Setup Gradle
uses: gradle/gradle-build-action@v2.4.2 # uses: gradle/gradle-build-action@v2.4.2
- name: Build Plugin - name: Build Plugin
run: gradle :buildPlugin run: gradle :buildPlugin
- name: Run Idea - name: Run Idea
run: | run: |
mkdir -p build/reports mkdir -p build/reports
gradle :runIdeForUiTests -PideaType=PC > build/reports/idea.log & gradle --no-configuration-cache :runIdeForUiTests -PideaType=PC > build/reports/idea.log &
- name: Wait for Idea started - name: Wait for Idea started
uses: jtalk/url-health-check-action@v3 uses: jtalk/url-health-check-action@v3
with: with:
@@ -40,7 +40,7 @@ jobs:
run: mv tests/ui-py-tests/video build/reports run: mv tests/ui-py-tests/video build/reports
- name: Move sandbox logs - name: Move sandbox logs
if: always() if: always()
run: mv build/idea-sandbox/system/log sandbox-idea-log run: mv build/idea-sandbox/PC-2024.1.2/log_runIdeForUiTests idea-sandbox-log
- name: Save report - name: Save report
if: always() if: always()
uses: actions/upload-artifact@v4 uses: actions/upload-artifact@v4
@@ -49,4 +49,4 @@ jobs:
path: | path: |
build/reports build/reports
tests/ui-py-tests/build/reports tests/ui-py-tests/build/reports
sandbox-idea-log idea-sandbox-log

View File

@@ -16,14 +16,14 @@ jobs:
java-version: 17 java-version: 17
- name: Setup FFmpeg - name: Setup FFmpeg
run: brew install ffmpeg run: brew install ffmpeg
- name: Setup Gradle # - name: Setup Gradle
uses: gradle/gradle-build-action@v2.4.2 # uses: gradle/gradle-build-action@v2.4.2
- name: Build Plugin - name: Build Plugin
run: gradle :buildPlugin run: gradle :buildPlugin
- name: Run Idea - name: Run Idea
run: | run: |
mkdir -p build/reports mkdir -p build/reports
gradle runIdeForUiTests > build/reports/idea.log & gradle --no-configuration-cache runIdeForUiTests > build/reports/idea.log &
- name: Wait for Idea started - name: Wait for Idea started
uses: jtalk/url-health-check-action@v3 uses: jtalk/url-health-check-action@v3
with: with:
@@ -37,7 +37,7 @@ jobs:
run: mv tests/ui-ij-tests/video build/reports run: mv tests/ui-ij-tests/video build/reports
- name: Move sandbox logs - name: Move sandbox logs
if: always() if: always()
run: mv build/idea-sandbox/system/log sandbox-idea-log run: mv build/idea-sandbox/IC-2024.1.2/log_runIdeForUiTests idea-sandbox-log
- name: Save report - name: Save report
if: always() if: always()
uses: actions/upload-artifact@v4 uses: actions/upload-artifact@v4
@@ -46,7 +46,7 @@ jobs:
path: | path: |
build/reports build/reports
tests/ui-ij-tests/build/reports tests/ui-ij-tests/build/reports
sandbox-idea-log idea-sandbox-log
# build-for-ui-test-linux: # build-for-ui-test-linux:
# runs-on: ubuntu-latest # runs-on: ubuntu-latest
# steps: # steps:
@@ -78,4 +78,4 @@ jobs:
# with: # with:
# name: ui-test-fails-report-linux # name: ui-test-fails-report-linux
# path: | # path: |
# ui-test-example/build/reports # ui-test-example/build/reports

View File

@@ -10,6 +10,9 @@ on:
push: push:
branches: [ master ] branches: [ master ]
permissions:
contents: write
jobs: jobs:
build: build:
@@ -34,6 +37,17 @@ jobs:
id: update_authors id: update_authors
run: cp -a origin/doc/. docs 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 - name: Commit changes
uses: stefanzweifel/git-auto-commit-action@v4 uses: stefanzweifel/git-auto-commit-action@v4
with: with:

6
.gitignore vendored
View File

@@ -1,5 +1,6 @@
*.swp *.swp
/.gradle/ /.gradle/
/.intellijPlatform/
/.idea/ /.idea/
!/.idea/scopes !/.idea/scopes
@@ -26,9 +27,8 @@
# Generated by gradle task "generateGrammarSource" # Generated by gradle task "generateGrammarSource"
vim-engine/src/main/java/com/maddyhome/idea/vim/parser/generated vim-engine/src/main/java/com/maddyhome/idea/vim/parser/generated
vim-engine/src/main/java/com/maddyhome/idea/vim/regexp/parser/generated vim-engine/src/main/java/com/maddyhome/idea/vim/regexp/parser/generated
# Generated JSONs for lazy classloading
/vim-engine/src/main/resources/ksp-generated
/src/main/resources/ksp-generated
# Created by github automation # Created by github automation
settings.xml settings.xml
.kotlin

View File

@@ -12,7 +12,7 @@
<option name="taskNames"> <option name="taskNames">
<list> <list>
<option value="check" /> <option value="check" />
<option value="runPluginVerifier" /> <option value="verifyPlugin" />
</list> </list>
</option> </option>
<option name="vmOptions" value="" /> <option name="vmOptions" value="" />
@@ -20,6 +20,7 @@
<ExternalSystemDebugServerProcess>true</ExternalSystemDebugServerProcess> <ExternalSystemDebugServerProcess>true</ExternalSystemDebugServerProcess>
<ExternalSystemReattachDebugProcess>true</ExternalSystemReattachDebugProcess> <ExternalSystemReattachDebugProcess>true</ExternalSystemReattachDebugProcess>
<DebugAllEnabled>false</DebugAllEnabled> <DebugAllEnabled>false</DebugAllEnabled>
<RunAsTest>false</RunAsTest>
<method v="2" /> <method v="2" />
</configuration> </configuration>
</component> </component>

View File

@@ -5,13 +5,12 @@ object Constants {
const val EAP_CHANNEL = "eap" const val EAP_CHANNEL = "eap"
const val DEV_CHANNEL = "Dev" const val DEV_CHANNEL = "Dev"
const val GITHUB_TESTS = "2024.1.1" const val NVIM_TESTS = "2024.2.1"
const val NVIM_TESTS = "2024.1.1" const val PROPERTY_TESTS = "2024.2.1"
const val PROPERTY_TESTS = "2024.1.1" const val LONG_RUNNING_TESTS = "2024.2.1"
const val LONG_RUNNING_TESTS = "2024.1.1" const val QODANA_TESTS = "2024.2.1"
const val QODANA_TESTS = "2024.1.1" const val RELEASE = "2024.2.1"
const val RELEASE = "2024.1.1"
const val RELEASE_DEV = "2024.1.1" const val RELEASE_DEV = "2024.2.1"
const val RELEASE_EAP = "2024.1.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.Qodana
import _Self.buildTypes.TestingBuildType import _Self.buildTypes.TestingBuildType
import _Self.subprojects.GitHub import _Self.subprojects.GitHub
import _Self.subprojects.OldTests
import _Self.subprojects.Releases import _Self.subprojects.Releases
import _Self.vcsRoots.GitHubPullRequest import _Self.vcsRoots.GitHubPullRequest
import _Self.vcsRoots.ReleasesVcsRoot import _Self.vcsRoots.ReleasesVcsRoot
@@ -18,7 +17,7 @@ import jetbrains.buildServer.configs.kotlin.v2019_2.Project
object Project : Project({ object Project : Project({
description = "Vim engine for JetBrains IDEs" description = "Vim engine for JetBrains IDEs"
subProjects(Releases, OldTests, GitHub) subProjects(Releases, GitHub)
// VCS roots // VCS roots
vcsRoot(GitHubPullRequest) vcsRoot(GitHubPullRequest)
@@ -26,7 +25,7 @@ object Project : Project({
// Active tests // Active tests
buildType(TestingBuildType("Latest EAP", "<default>", version = "LATEST-EAP-SNAPSHOT")) buildType(TestingBuildType("Latest EAP", "<default>", version = "LATEST-EAP-SNAPSHOT"))
buildType(TestingBuildType("2024.1.1", "<default>")) buildType(TestingBuildType("2024.2.1", "<default>"))
buildType(TestingBuildType("Latest EAP With Xorg", "<default>", version = "LATEST-EAP-SNAPSHOT")) buildType(TestingBuildType("Latest EAP With Xorg", "<default>", version = "LATEST-EAP-SNAPSHOT"))
buildType(PropertyBased) buildType(PropertyBased)
@@ -43,6 +42,9 @@ object Project : Project({
abstract class IdeaVimBuildType(init: BuildType.() -> Unit) : BuildType({ abstract class IdeaVimBuildType(init: BuildType.() -> Unit) : BuildType({
artifactRules = """ artifactRules = """
+:build/reports => build/reports +: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/ +:/mnt/agent/temp/buildTmp/ => /mnt/agent/temp/buildTmp/
""".trimIndent() """.trimIndent()
@@ -52,7 +54,7 @@ abstract class IdeaVimBuildType(init: BuildType.() -> Unit) : BuildType({
// These requirements define Linux-Medium configuration. // These requirements define Linux-Medium configuration.
// Unfortunately, requirement by name (teamcity.agent.name) doesn't work // Unfortunately, requirement by name (teamcity.agent.name) doesn't work
// IDK the reason for it, but on our agents this property is empty // 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") equals("teamcity.agent.os.family", "Linux")
} }

View File

@@ -37,9 +37,12 @@ object Compatibility : IdeaVimBuildType({
java -jar verifier1/verifier-cli-dev-all-1.jar check-plugin '${'$'}eu.theblob42.idea.whichkey' [latest-IU] -team-city java -jar verifier1/verifier-cli-dev-all-1.jar check-plugin '${'$'}eu.theblob42.idea.whichkey' [latest-IU] -team-city
java -jar verifier1/verifier-cli-dev-all-1.jar check-plugin '${'$'}IdeaVimExtension' [latest-IU] -team-city java -jar verifier1/verifier-cli-dev-all-1.jar check-plugin '${'$'}IdeaVimExtension' [latest-IU] -team-city
# Outdated java -jar verifier/verifier-cli-dev-all.jar check-plugin '${'$'}github.zgqq.intellij-enhance' [latest-IU] -team-city # Outdated java -jar verifier/verifier-cli-dev-all.jar check-plugin '${'$'}github.zgqq.intellij-enhance' [latest-IU] -team-city
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.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.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.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() """.trimIndent()
} }
} }

View File

@@ -22,7 +22,7 @@ object PluginVerifier : IdeaVimBuildType({
steps { steps {
gradle { gradle {
tasks = "clean runPluginVerifier" tasks = "clean verifyPlugin"
buildFile = "" buildFile = ""
enableStacktrace = true enableStacktrace = true
} }

View File

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

View File

@@ -144,6 +144,7 @@ sealed class ReleasePlugin(private val releaseType: String) : IdeaVimBuildType({
gradle { gradle {
name = "Run Integrations" name = "Run Integrations"
tasks = "releaseActions" tasks = "releaseActions"
gradleParams = "--no-configuration-cache"
} }
// gradle { // gradle {
// name = "Slack Notification" // name = "Slack Notification"

View File

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

View File

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

@@ -523,6 +523,46 @@ Contributors:
[![icon][github]](https://github.com/LazyScaper) [![icon][github]](https://github.com/LazyScaper)
&nbsp; &nbsp;
Jake Jake
* [![icon][mail]](mailto:the1xdeveloper@gmail.com)
[![icon][github]](https://github.com/The1xDeveloper)
&nbsp;
The1xDeveloper
* [![icon][mail]](mailto:shaunewilliams@gmail.com)
[![icon][github]](https://github.com/shaunlebron)
&nbsp;
shaun
* [![icon][mail]](mailto:i.i.babko@gmail.com)
[![icon][github]](https://github.com/igorbabko)
&nbsp;
Igor Babko
* [![icon][mail]](mailto:533601+felixwiemuth@users.noreply.github.com)
[![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: Previous contributors:
@@ -534,6 +574,10 @@ Previous contributors:
[![icon][github]](https://github.com/kevin70) [![icon][github]](https://github.com/kevin70)
&nbsp; &nbsp;
kk 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 If you are a contributor and your name is not listed here, feel free to

View File

@@ -27,8 +27,8 @@ usual beta standards.
Since version 2.9.0, the changelog can be found on YouTrack Since version 2.9.0, the changelog can be found on YouTrack
To Be Released: https://youtrack.jetbrains.com/issues/VIM?q=%23%7BReady%20To%20Release%7D%20 * [To Be Released](https://youtrack.jetbrains.com/issues/VIM?q=%23%7BReady%20To%20Release%7D%20)
Latest Fixes: https://youtrack.jetbrains.com/issues/VIM?q=State:%20Fixed%20sort%20by:%20updated%20 * [Version Fixes](https://youtrack.jetbrains.com/issues/VIM?q=State:%20Fixed%20sort%20by:%20%7BFix%20versions%7D%20asc)
## 2.9.0, 2024-02-20 ## 2.9.0, 2024-02-20

View File

@@ -62,12 +62,16 @@ for a few days or send it to a friend for testing.
If you are looking for: If you are looking for:
- Vim commands (`w`, `<C-O>`, `p`, etc.): - Vim commands (`w`, `<C-O>`, `p`, etc.):
- Any particular command: `package-info.java`. - Any particular command:
- [Commands common for Fleet and IdeaVim](vim-engine/src/main/resources/ksp-generated/engine_commands.json)
- [IdeaVim only commands](src/main/resources/ksp-generated/intellij_commands.json)
- How commands are executed in common: `EditorActionHandlerBase`. - How commands are executed in common: `EditorActionHandlerBase`.
- Key mapping: `KeyHandler.handleKey()`. - Key mapping: `KeyHandler.handleKey()`.
- Ex commands (`:set`, `:s`, `:nohlsearch`): - Ex commands (`:set`, `:s`, `:nohlsearch`):
- Any particular ex command: package `com.maddyhome.idea.vim.ex.handler`. - Any particular command:
- [Commands common for Fleet and IdeaVim](vim-engine/src/main/resources/ksp-generated/engine_ex_commands.json)
- [IdeaVim only commands](src/main/resources/ksp-generated/intellij_ex_commands.json)
- Vim script grammar: `Vimscript.g4`. - Vim script grammar: `Vimscript.g4`.
- Vim script parsing: package `com.maddyhome.idea.vim.vimscript.parser`. - Vim script parsing: package `com.maddyhome.idea.vim.vimscript.parser`.
- Vim script executor: `Executor`. - Vim script executor: `Executor`.

View File

@@ -109,7 +109,6 @@ etc
See also: See also:
* [The list of all supported commands](src/main/java/com/maddyhome/idea/vim/package-info.java)
* [Top feature requests and bugs](https://youtrack.jetbrains.com/issues/VIM?q=%23Unresolved+sort+by%3A+votes) * [Top feature requests and bugs](https://youtrack.jetbrains.com/issues/VIM?q=%23Unresolved+sort+by%3A+votes)
* [Vimscript support roadmap](vimscript-info/VIMSCRIPT_ROADMAP.md) * [Vimscript support roadmap](vimscript-info/VIMSCRIPT_ROADMAP.md)
* [List of supported in-build functions](vimscript-info/FUNCTIONS_INFO.MD) * [List of supported in-build functions](vimscript-info/FUNCTIONS_INFO.MD)
@@ -222,13 +221,13 @@ Ex commands or via `:map` command mappings:
* Execute an action by `{action_id}`. Works from Ex command line. * Execute an action by `{action_id}`. Works from Ex command line.
* Please don't use `:action` in mappings. Use `<Action>` instead. * Please don't use `:action` in mappings. Use `<Action>` instead.
### Finding action ids: ### Finding action IDs:
* IJ provides `IdeaVim: track action Ids` command to show the id of the executed actions. * IJ provides `IdeaVim: track action IDs` command to show the id of the executed actions.
This command can be found in "Search everywhere" (double `shift`). This command can be found in "Search everywhere" (double `shift`).
<details> <details>
<summary><strong>"Track action Ids" Details</strong> (click to see)</summary> <summary><strong>"Track action IDs" Details</strong> (click to see)</summary>
<picture> <picture>
<source media="(prefers-color-scheme: dark)" srcset="assets/readme/track_action_dark.gif"> <source media="(prefers-color-scheme: dark)" srcset="assets/readme/track_action_dark.gif">
<img src="assets/readme/track_action_light.gif" alt="track action ids"/> <img src="assets/readme/track_action_light.gif" alt="track action ids"/>
@@ -369,6 +368,8 @@ is the full list of synonyms.
- Fancy constants for [undolevels](https://vimhelp.org/options.txt.html#%27undolevels%27): - Fancy constants for [undolevels](https://vimhelp.org/options.txt.html#%27undolevels%27):
> The local value is set to -123456 when the global value is to be used. > The local value is set to -123456 when the global value is to be used.
- Vi (not Vim) is a POSIX standard, and [has a spec](https://pubs.opengroup.org/onlinepubs/9699919799/utilities/vi.html)! Vim is mostly POSIX compliant when Vi compatibility is selected with the `'compatible'` option, but there are still some differences that can be changed with `'copoptions'`. The spec is interesting because it documents the behaviour of different commands in a stricter style than the user documentation, describing the current line and column after the command, for example. [More details can be found by reading `:help posix`](https://vimhelp.org/vi_diff.txt.html#posix).
License License
------- -------

View File

@@ -8,7 +8,7 @@
plugins { plugins {
kotlin("jvm") kotlin("jvm")
kotlin("plugin.serialization") version "1.9.22" kotlin("plugin.serialization") version "2.0.0"
} }
val kotlinxSerializationVersion: String by project val kotlinxSerializationVersion: String by project
@@ -21,7 +21,7 @@ repositories {
} }
dependencies { dependencies {
compileOnly("com.google.devtools.ksp:symbol-processing-api:2.0.0-1.0.22") compileOnly("com.google.devtools.ksp:symbol-processing-api:2.1.0-1.0.29")
implementation("org.jetbrains.kotlinx:kotlinx-serialization-json-jvm:$kotlinxSerializationVersion") { 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 // kotlin stdlib is provided by IJ, so there is no need to include it into the distribution
exclude("org.jetbrains.kotlin", "kotlin-stdlib") exclude("org.jetbrains.kotlin", "kotlin-stdlib")

View File

@@ -37,7 +37,8 @@ class CommandOrMotionProcessor(private val environment: SymbolProcessorEnvironme
Files.createDirectories(generatedDirPath) Files.createDirectories(generatedDirPath)
val filePath = generatedDirPath.resolve(environment.options["commands_file"]!!) val filePath = generatedDirPath.resolve(environment.options["commands_file"]!!)
val fileContent = json.encodeToString(commands) val sortedCommands = commands.sortedWith(compareBy({ it.keys }, { it.`class` }))
val fileContent = json.encodeToString(sortedCommands)
filePath.writeText(fileContent) filePath.writeText(fileContent)
return emptyList() return emptyList()

View File

@@ -37,7 +37,8 @@ class ExCommandProcessor(private val environment: SymbolProcessorEnvironment): S
Files.createDirectories(generatedDirPath) Files.createDirectories(generatedDirPath)
val filePath = generatedDirPath.resolve(environment.options["ex_commands_file"]!!) val filePath = generatedDirPath.resolve(environment.options["ex_commands_file"]!!)
val fileContent = json.encodeToString(commandToClass) val sortedCommandToClass = commandToClass.toList().sortedWith(compareBy({ it.first }, { it.second })).toMap()
val fileContent = json.encodeToString(sortedCommandToClass)
filePath.writeText(fileContent) filePath.writeText(fileContent)
return emptyList() return emptyList()

View File

@@ -37,7 +37,8 @@ class VimscriptFunctionProcessor(private val environment: SymbolProcessorEnviron
Files.createDirectories(generatedDirPath) Files.createDirectories(generatedDirPath)
val filePath = generatedDirPath.resolve(environment.options["vimscript_functions_file"]!!) val filePath = generatedDirPath.resolve(environment.options["vimscript_functions_file"]!!)
val fileContent = json.encodeToString(nameToClass) val sortedNameToClass = nameToClass.toList().sortedWith(compareBy({ it.first }, { it.second })).toMap()
val fileContent = json.encodeToString(sortedNameToClass)
filePath.writeText(fileContent) filePath.writeText(fileContent)
return emptyList() return emptyList()

View File

@@ -32,6 +32,8 @@ import org.eclipse.jgit.api.Git
import org.eclipse.jgit.lib.RepositoryBuilder import org.eclipse.jgit.lib.RepositoryBuilder
import org.intellij.markdown.ast.getTextInNode import org.intellij.markdown.ast.getTextInNode
import org.jetbrains.changelog.Changelog import org.jetbrains.changelog.Changelog
import org.jetbrains.intellij.platform.gradle.TestFrameworkType
import org.jetbrains.intellij.platform.gradle.tasks.aware.SplitModeAware
import org.kohsuke.github.GHUser import org.kohsuke.github.GHUser
import java.net.HttpURLConnection import java.net.HttpURLConnection
import java.net.URL import java.net.URL
@@ -43,19 +45,19 @@ buildscript {
} }
dependencies { dependencies {
classpath("org.jetbrains.kotlin:kotlin-gradle-plugin:1.9.22") classpath("org.jetbrains.kotlin:kotlin-gradle-plugin:2.0.0")
classpath("com.github.AlexPl292:mark-down-to-slack:1.1.2") classpath("com.github.AlexPl292:mark-down-to-slack:1.1.2")
classpath("org.eclipse.jgit:org.eclipse.jgit:6.6.0.202305301015-r") classpath("org.eclipse.jgit:org.eclipse.jgit:6.6.0.202305301015-r")
// This is needed for jgit to connect to ssh // 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("org.kohsuke:github-api:1.305")
classpath("io.ktor:ktor-client-core:2.3.12") classpath("io.ktor:ktor-client-core:3.0.2")
classpath("io.ktor:ktor-client-cio:2.3.10") classpath("io.ktor:ktor-client-cio:3.0.2")
classpath("io.ktor:ktor-client-auth:2.3.12") classpath("io.ktor:ktor-client-auth:3.0.2")
classpath("io.ktor:ktor-client-content-negotiation:2.3.10") classpath("io.ktor:ktor-client-content-negotiation:3.0.2")
classpath("io.ktor:ktor-serialization-kotlinx-json:2.3.12") classpath("io.ktor:ktor-serialization-kotlinx-json:3.0.2")
// This comes from the changelog plugin // This comes from the changelog plugin
// classpath("org.jetbrains:markdown:0.3.1") // classpath("org.jetbrains:markdown:0.3.1")
@@ -64,28 +66,25 @@ buildscript {
plugins { plugins {
java java
kotlin("jvm") version "1.9.22" kotlin("jvm") version "2.0.0"
application application
id("java-test-fixtures") id("java-test-fixtures")
id("org.jetbrains.intellij.platform") version "2.2.0"
id("org.jetbrains.intellij") version "1.17.3" id("org.jetbrains.changelog") version "2.2.1"
id("org.jetbrains.changelog") version "2.2.0"
id("org.jetbrains.kotlinx.kover") version "0.6.1" id("org.jetbrains.kotlinx.kover") version "0.6.1"
id("com.dorongold.task-tree") version "4.0.0" id("com.dorongold.task-tree") version "4.0.0"
id("com.google.devtools.ksp") version "2.0.0-1.0.23"
id("com.google.devtools.ksp") version "1.9.22-1.0.17"
} }
val moduleSources by configurations.registering
// Import variables from gradle.properties file // Import variables from gradle.properties file
val javaVersion: String by project val javaVersion: String by project
val kotlinVersion: String by project val kotlinVersion: String by project
val ideaVersion: String by project val ideaVersion: String by project
val ideaType: String by project val ideaType: String by project
val downloadIdeaSources: String by project
val instrumentPluginCode: String by project val instrumentPluginCode: String by project
val remoteRobotVersion: String by project val remoteRobotVersion: String by project
val splitModeVersion: String by project
val publishChannels: String by project val publishChannels: String by project
val publishToken: String by project val publishToken: String by project
@@ -97,7 +96,9 @@ val releaseType: String? by project
repositories { repositories {
mavenCentral() mavenCentral()
maven { url = uri("https://cache-redirector.jetbrains.com/intellij-dependencies") } intellijPlatform {
defaultRepositories()
}
} }
dependencies { dependencies {
@@ -106,12 +107,34 @@ dependencies {
compileOnly(project(":annotation-processors")) compileOnly(project(":annotation-processors"))
compileOnly("org.jetbrains.kotlin:kotlin-stdlib:$kotlinVersion") 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, useInstaller)
pluginVerifier()
zipSigner()
instrumentationTools()
testFramework(TestFrameworkType.Platform)
testFramework(TestFrameworkType.JUnit5)
// 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"))
// --------- Test dependencies ---------- // --------- Test dependencies ----------
testImplementation(testFixtures(project(":")))
testApi("com.squareup.okhttp3:okhttp:4.12.0") testApi("com.squareup.okhttp3:okhttp:4.12.0")
// https://mvnrepository.com/artifact/com.ensarsarajcic.neovim.java/neovim-api // https://mvnrepository.com/artifact/com.ensarsarajcic.neovim.java/neovim-api
@@ -125,14 +148,20 @@ dependencies {
testFixturesImplementation("org.jetbrains.kotlin:kotlin-test:$kotlinVersion") testFixturesImplementation("org.jetbrains.kotlin:kotlin-test:$kotlinVersion")
// https://mvnrepository.com/artifact/org.mockito.kotlin/mockito-kotlin // https://mvnrepository.com/artifact/org.mockito.kotlin/mockito-kotlin
testImplementation("org.mockito.kotlin:mockito-kotlin:5.3.1") 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-api:5.10.5")
testImplementation("org.junit.jupiter:junit-jupiter-engine:5.10.3") testImplementation("org.junit.jupiter:junit-jupiter-engine:5.10.5")
testImplementation("org.junit.jupiter:junit-jupiter-params:5.10.3") testImplementation("org.junit.jupiter:junit-jupiter-params:5.10.5")
testFixturesImplementation("org.junit.jupiter:junit-jupiter-api:5.10.3") testFixturesImplementation("org.junit.jupiter:junit-jupiter-api:5.10.5")
testFixturesImplementation("org.junit.jupiter:junit-jupiter-engine:5.10.3") testFixturesImplementation("org.junit.jupiter:junit-jupiter-engine:5.10.5")
testFixturesImplementation("org.junit.jupiter:junit-jupiter-params:5.10.3") 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.5")
// testFixturesImplementation("org.junit.vintage:junit-vintage-engine:5.10.3")
} }
configurations { configurations {
@@ -172,60 +201,91 @@ tasks {
// See https://plugins.jetbrains.com/docs/intellij/using-kotlin.html#kotlin-standard-library // See https://plugins.jetbrains.com/docs/intellij/using-kotlin.html#kotlin-standard-library
// For the list of bundled versions // For the list of bundled versions
apiVersion = "1.9" apiVersion = "1.9"
freeCompilerArgs = listOf("-Xjvm-default=all-compatibility") freeCompilerArgs = listOf(
"-Xjvm-default=all-compatibility",
// Needed to compile the AceJump which uses kotlin beta
// Without these two option compilation fails
"-Xskip-prerelease-check",
"-Xallow-unstable-dependencies",
)
// allWarningsAsErrors = true // allWarningsAsErrors = true
} }
} }
compileTestKotlin { compileTestKotlin {
enabled = false
kotlinOptions { kotlinOptions {
jvmTarget = javaVersion jvmTarget = javaVersion
apiVersion = "1.9" apiVersion = "1.9"
// Needed to compile the AceJump which uses kotlin beta
// Without these two option compilation fails
freeCompilerArgs += listOf("-Xskip-prerelease-check", "-Xallow-unstable-dependencies")
// allWarningsAsErrors = true // allWarningsAsErrors = true
} }
} }
// Note that this will run the plugin installed in the IDE specified in dependencies. To run in a different IDE, use
// a custom task (see below)
runIde { runIde {
systemProperty("octopus.handler", System.getProperty("octopus.handler") ?: true) systemProperty("octopus.handler", System.getProperty("octopus.handler") ?: true)
} }
downloadRobotServerPlugin { // Uncomment to run the plugin in a custom IDE, rather than the IDE specified as a compile target in dependencies
version.set(remoteRobotVersion) // 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"
// }
// Uncomment to run the plugin in a locally installed IDE
// val runIdeLocal by intellijPlatformTesting.runIde.registering {
// localPath = file("/Users/{user}/Applications/WebStorm.app")
// }
val runIdeForUiTests by intellijPlatformTesting.runIde.registering {
task {
jvmArgumentProviders += CommandLineArgumentProvider {
listOf(
"-Drobot-server.port=8082",
"-Dide.mac.message.dialogs.as.sheets=false",
"-Djb.privacy.policy.text=<!--999.999-->",
"-Djb.consents.confirmation.enabled=false",
"-Dide.show.tips.on.startup.default.value=false",
"-Doctopus.handler=" + (System.getProperty("octopus.handler") ?: true),
)
}
}
plugins {
robotServerPlugin(remoteRobotVersion)
}
} }
runIdeForUiTests { val runIdeSplitMode by intellijPlatformTesting.runIde.registering {
systemProperty("robot-server.port", "8082") splitMode = true
systemProperty("ide.mac.message.dialogs.as.sheets", "false") splitModeTarget = SplitModeAware.SplitModeTarget.FRONTEND
systemProperty("jb.privacy.policy.text", "<!--999.999-->")
systemProperty("jb.consents.confirmation.enabled", "false")
systemProperty("ide.show.tips.on.startup.default.value", "false")
systemProperty("octopus.handler", System.getProperty("octopus.handler") ?: true)
} }
// Add plugin open API sources to the plugin ZIP // Add plugin open API sources to the plugin ZIP
val createOpenApiSourceJar by registering(Jar::class) { val sourcesJar by registering(Jar::class) {
// Java sources dependsOn(moduleSources)
from(sourceSets.main.get().java) {
include("**/com/maddyhome/idea/vim/**/*.java")
}
from(project(":vim-engine").sourceSets.main.get().java) {
include("**/com/maddyhome/idea/vim/**/*.java")
}
// Kotlin sources
from(kotlin.sourceSets.main.get().kotlin) {
include("**/com/maddyhome/idea/vim/**/*.kt")
}
from(project(":vim-engine").kotlin.sourceSets.main.get().kotlin) {
include("**/com/maddyhome/idea/vim/**/*.kt")
}
destinationDirectory.set(layout.buildDirectory.dir("libs")) destinationDirectory.set(layout.buildDirectory.dir("libs"))
archiveClassifier.set("src") archiveClassifier.set(DocsType.SOURCES)
from(sourceSets.main.map { it.kotlin })
from(provider {
moduleSources.map {
it.map { jarFile -> zipTree(jarFile) }
}
})
} }
buildPlugin { buildPlugin {
dependsOn(createOpenApiSourceJar) dependsOn(sourcesJar)
from(createOpenApiSourceJar) { into("lib/src") } from(sourcesJar) { into("lib/src") }
} }
} }
@@ -250,44 +310,51 @@ gradle.projectsEvaluated {
// --- Intellij plugin // --- Intellij plugin
intellij { intellijPlatform {
version.set(ideaVersion) pluginConfiguration {
type.set(ideaType) name = "IdeaVim"
pluginName.set("IdeaVim") changeNotes.set(
"""
Undo in IdeaVim now works like in Vim<br/>
Caret movement is no longer a separate undo step, and full insert is undoable in one step.<br/>
<a href="https://youtrack.jetbrains.com/issue/VIM-547/Undo-splits-Insert-mode-edits-into-separate-undo-chunks">Share Feedback</a>
<br/>
<br/>
<a href="https://youtrack.jetbrains.com/issues/VIM?q=State:%20Fixed%20Fix%20versions:%20${version.get()}">Changelog</a>
""".trimIndent()
)
updateSinceUntilBuild.set(false) ideaVersion {
// Let the Gradle plugin set the since-build version. It defaults to the version of the IDE we're building against
// specified as two components, `{branch}.{build}` (e.g., "241.15989"). There is no third component specified.
// The until-build version defaults to `{branch}.*`, but we want to support _all_ future versions, so we set it
// with a null provider (the provider is important).
// By letting the Gradle plugin handle this, the Plugin DevKit IntelliJ plugin cannot help us with the "Usage of
// IntelliJ API not available in older IDEs" inspection. However, since our since-build is the version we compile
// against, we can never get an API that's newer - it would be an unresolved symbol.
untilBuild.set(provider { null })
}
}
downloadSources.set(downloadIdeaSources.toBoolean()) publishing {
instrumentCode.set(instrumentPluginCode.toBoolean())
intellijRepository.set("https://www.jetbrains.com/intellij-repository")
plugins.set(listOf("AceJump:3.8.11"))
}
tasks {
publishPlugin {
channels.set(publishChannels.split(",")) channels.set(publishChannels.split(","))
token.set(publishToken) token.set(publishToken)
} }
signPlugin { signing {
certificateChain.set(providers.environmentVariable("CERTIFICATE_CHAIN")) certificateChain.set(providers.environmentVariable("CERTIFICATE_CHAIN"))
privateKey.set(providers.environmentVariable("PRIVATE_KEY")) privateKey.set(providers.environmentVariable("PRIVATE_KEY"))
password.set(providers.environmentVariable("PRIVATE_KEY_PASSWORD")) password.set(providers.environmentVariable("PRIVATE_KEY_PASSWORD"))
} }
runPluginVerifier { verifyPlugin {
downloadDir.set("${project.buildDir}/pluginVerifier/ides") teamCityOutputFormat = true
teamCityOutputFormat.set(true) ides {
recommended()
}
} }
patchPluginXml { instrumentCode.set(instrumentPluginCode.toBoolean())
// Don't forget to update plugin.xml
sinceBuild.set("241.15989.150")
changeNotes.set(
"""<a href="https://youtrack.jetbrains.com/issues/VIM?q=State:%20Fixed%20Fix%20versions:%20${version.get()}">Changelog</a>"""
)
}
} }
ksp { ksp {
@@ -891,12 +958,12 @@ fun changes(): List<Change> {
println("Start changes processing") println("Start changes processing")
for (message in messages) { for (message in messages) {
println("Processing '$message'...") println("Processing '$message'...")
val lowercaseMessage = message.toLowerCase() val lowercaseMessage = message.lowercase()
val regex = "^fix\\((vim-\\d+)\\):".toRegex() val regex = "^fix\\((vim-\\d+)\\):".toRegex()
val findResult = regex.find(lowercaseMessage) val findResult = regex.find(lowercaseMessage)
if (findResult != null) { if (findResult != null) {
println("Message matches") println("Message matches")
val value = findResult.groups[1]!!.value.toUpperCase() val value = findResult.groups[1]!!.value.uppercase()
val shortMessage = message.drop(findResult.range.last + 1).trim() val shortMessage = message.drop(findResult.range.last + 1).trim()
newFixes += Change(value, shortMessage) newFixes += Change(value, shortMessage)
} else { } else {

View File

@@ -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. 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)"` `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 https://github.com/machakann/vim-highlightedyank/blob/master/doc/highlightedyank.txt
</details> </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 https://github.com/TheBlob42/idea-which-key?tab=readme-ov-file#installation
</details> </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

@@ -5,9 +5,9 @@ Using actions from external plugins is the same, as tracking and reusing any IDE
1. Install the plugin via Marketplace 1. Install the plugin via Marketplace
2. Enable action tracking. You can enable it by one of the following ways: 2. Enable action tracking. You can enable it by one of the following ways:
* Execute `:set trackactionids` ex command or just `:set tai` * Execute `:set trackactionids` ex command or just `:set tai`
* Open the "Find actions" window by pressing `Ctrl-Shift-A` and search for "Track Action Ids" to find the toggle that enables and disables action tracking * Open the "Find actions" window by pressing `Ctrl-Shift-A` and search for "Track Action IDs" to find the toggle that enables and disables action tracking
3. Execute the plugin action the way intended by plugin author "See Edit menu or use ⇧ + ⌥ + U / Shift + Alt + U" or just find the `Toggle Camel Case` action in the "Find actions" window (`Ctrl-Shift-A`). If you action tracking is on, you will see this notification in your right bottom corner: 3. Execute the plugin action the way intended by plugin author "See Edit menu or use ⇧ + ⌥ + U / Shift + Alt + U" or just find the `Toggle Camel Case` action in the "Find actions" window (`Ctrl-Shift-A`). If you action tracking is on, you will see this notification in your right bottom corner:
<img alt="Notification" src="images/action-id-notification.png"/> <img alt="Notification" src="images/action-id-notification.png"/>
4. Copy the action id from the notification to create the following mapping in your .ideavimrc 4. Copy the action id from the notification to create the following mapping in your .ideavimrc
```map <leader>t <Action>(de.netnexus.CamelCasePlugin.ToggleCamelCase)``` ```map <leader>t <Action>(de.netnexus.CamelCasePlugin.ToggleCamelCase)```

View File

@@ -8,7 +8,7 @@ Every effort is made to make these options compatible with Vim behaviour.
However, some differences are inevitable. However, some differences are inevitable.
``` ```
'clipboard' 'cb' Defines clipboard behavioue 'clipboard' 'cb' Defines clipboard behavior
A comma-separated list of words to control clipboard behaviour: A comma-separated list of words to control clipboard behaviour:
unnamed The clipboard register '*' is used instead of the unnamed The clipboard register '*' is used instead of the
unnamed register unnamed register

View File

@@ -1,7 +1,7 @@
# Support Guide # Support Guide
This document is created to help our support team. This document is created to help our support team.
It's not intended to be read by the users as it brings to value to them. It's not intended to be read by the users as it brings no value to them.
## Support channels ## Support channels
@@ -25,7 +25,7 @@ It's not intended to be read by the users as it brings to value to them.
IdeaVim has multiple YouTrack statuses, main are: 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. - 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 - Waiting For Reply: Waiting for further information from the user. These issues are automatically closed if the
user doesn't reply in 30 days. user doesn't reply in 30 days.
- Ready To Release: Bug is fixed, but not yet released - Ready To Release: Bug is fixed, but not yet released
@@ -35,4 +35,4 @@ IdeaVim has multiple YouTrack statuses, main are:
# ~.ideavimrc file # ~.ideavimrc file
`~/.ideavimrc` is the file that is used for IdeaVim configuration. It may affect behaviour of the program, `~/.ideavimrc` is the file that is used for IdeaVim configuration. It may affect behaviour of the program,
so it makes sense to additionally request this file in case the issues can't be reproduced. so it makes sense to additionally request this file in case the issues can't be reproduced.

View File

@@ -16,26 +16,19 @@
# https://data.services.jetbrains.com/products?code=IC # https://data.services.jetbrains.com/products?code=IC
# Maven releases are here: https://www.jetbrains.com/intellij-repository/releases # Maven releases are here: https://www.jetbrains.com/intellij-repository/releases
# And snapshots: https://www.jetbrains.com/intellij-repository/snapshots # 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 # Values for type: https://plugins.jetbrains.com/docs/intellij/tools-gradle-intellij-plugin.html#intellij-extension-type
ideaType=IC ideaType=IC
downloadIdeaSources=true
instrumentPluginCode=true instrumentPluginCode=true
version=chylex-37 version=chylex-42
javaVersion=17 javaVersion=17
remoteRobotVersion=0.11.22 remoteRobotVersion=0.11.23
antlrVersion=4.10.1 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 # Please don't forget to update kotlin version in buildscript section
# Also update kotlinxSerializationVersion version # Also update kotlinxSerializationVersion version
kotlinVersion=1.9.22 kotlinVersion=2.0.0
publishToken=token publishToken=token
publishChannels=eap publishChannels=eap

Binary file not shown.

View File

@@ -1,6 +1,6 @@
distributionBase=GRADLE_USER_HOME distributionBase=GRADLE_USER_HOME
distributionPath=wrapper/dists 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 networkTimeout=10000
validateDistributionUrl=true validateDistributionUrl=true
zipStoreBase=GRADLE_USER_HOME zipStoreBase=GRADLE_USER_HOME

22
gradlew vendored
View File

@@ -15,6 +15,8 @@
# See the License for the specific language governing permissions and # See the License for the specific language governing permissions and
# limitations under the License. # limitations under the License.
# #
# SPDX-License-Identifier: Apache-2.0
#
############################################################################## ##############################################################################
# #
@@ -55,7 +57,7 @@
# Darwin, MinGW, and NonStop. # Darwin, MinGW, and NonStop.
# #
# (3) This script is generated from the Groovy template # (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. # within the Gradle project.
# #
# You can find Gradle at https://github.com/gradle/gradle/. # You can find Gradle at https://github.com/gradle/gradle/.
@@ -83,7 +85,9 @@ done
# This is normally unused # This is normally unused
# shellcheck disable=SC2034 # shellcheck disable=SC2034
APP_BASE_NAME=${0##*/} 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. # Use the maximum available, or set MAX_FD != -1 to use that value.
MAX_FD=maximum MAX_FD=maximum
@@ -144,7 +148,7 @@ if ! "$cygwin" && ! "$darwin" && ! "$nonstop" ; then
case $MAX_FD in #( case $MAX_FD in #(
max*) max*)
# In POSIX sh, ulimit -H is undefined. That's why the result is checked to see if it worked. # 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 ) || MAX_FD=$( ulimit -H -n ) ||
warn "Could not query maximum file descriptor limit" warn "Could not query maximum file descriptor limit"
esac esac
@@ -152,7 +156,7 @@ if ! "$cygwin" && ! "$darwin" && ! "$nonstop" ; then
'' | soft) :;; #( '' | soft) :;; #(
*) *)
# In POSIX sh, ulimit -n is undefined. That's why the result is checked to see if it worked. # 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" || ulimit -n "$MAX_FD" ||
warn "Could not set maximum file descriptor limit to $MAX_FD" warn "Could not set maximum file descriptor limit to $MAX_FD"
esac 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. # 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"' DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"'
# Collect all arguments for the java command; # Collect all arguments for the java command:
# * $DEFAULT_JVM_OPTS, $JAVA_OPTS, and $GRADLE_OPTS can contain fragments of # * DEFAULT_JVM_OPTS, JAVA_OPTS, JAVA_OPTS, and optsEnvironmentVar are not allowed to contain shell fragments,
# shell script including quotes and variable substitutions, so put them in # and any embedded shellness will be escaped.
# double quotes to make sure that they get re-expanded; and # * For example: A user cannot expect ${Hostname} to be expanded, as it is an environment variable and will be
# * put everything else in single quotes, so that it's not re-expanded. # treated as '${Hostname}' itself on the command line.
set -- \ set -- \
"-Dorg.gradle.appname=$APP_BASE_NAME" \ "-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 See the License for the specific language governing permissions and
@rem limitations under the License. @rem limitations under the License.
@rem @rem
@rem SPDX-License-Identifier: Apache-2.0
@rem
@if "%DEBUG%"=="" @echo off @if "%DEBUG%"=="" @echo off
@rem ########################################################################## @rem ##########################################################################
@@ -43,11 +45,11 @@ set JAVA_EXE=java.exe
%JAVA_EXE% -version >NUL 2>&1 %JAVA_EXE% -version >NUL 2>&1
if %ERRORLEVEL% equ 0 goto execute if %ERRORLEVEL% equ 0 goto execute
echo. echo. 1>&2
echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 1>&2
echo. echo. 1>&2
echo Please set the JAVA_HOME variable in your environment to match the echo Please set the JAVA_HOME variable in your environment to match the 1>&2
echo location of your Java installation. echo location of your Java installation. 1>&2
goto fail goto fail
@@ -57,11 +59,11 @@ set JAVA_EXE=%JAVA_HOME%/bin/java.exe
if exist "%JAVA_EXE%" goto execute if exist "%JAVA_EXE%" goto execute
echo. echo. 1>&2
echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% 1>&2
echo. echo. 1>&2
echo Please set the JAVA_HOME variable in your environment to match the echo Please set the JAVA_HOME variable in your environment to match the 1>&2
echo location of your Java installation. echo location of your Java installation. 1>&2
goto fail goto fail

View File

@@ -19,9 +19,8 @@ exclude:
- src/test/java/org/jetbrains/plugins/ideavim/propertybased/samples/JavaText.kt - src/test/java/org/jetbrains/plugins/ideavim/propertybased/samples/JavaText.kt
- src/test/java/org/jetbrains/plugins/ideavim/propertybased/samples/LoremText.kt - src/test/java/org/jetbrains/plugins/ideavim/propertybased/samples/LoremText.kt
- src/test/java/org/jetbrains/plugins/ideavim/propertybased/samples/SimpleText.kt - src/test/java/org/jetbrains/plugins/ideavim/propertybased/samples/SimpleText.kt
- src/main/java/com/maddyhome/idea/vim/vimscript/parser/generated
- src/main/java/com/maddyhome/idea/vim/package-info.java
- vim-engine/src/main/java/com/maddyhome/idea/vim/regexp/parser/generated - vim-engine/src/main/java/com/maddyhome/idea/vim/regexp/parser/generated
- vim-engine/src/main/java/com/maddyhome/idea/vim/parser/generated
- src/main/java/com/maddyhome/idea/vim/group/SearchGroup.java - src/main/java/com/maddyhome/idea/vim/group/SearchGroup.java
- tests/ui-fixtures - tests/ui-fixtures
dependencyIgnores: dependencyIgnores:

View File

@@ -20,17 +20,17 @@ repositories {
} }
dependencies { dependencies {
compileOnly("org.jetbrains.kotlin:kotlin-stdlib:1.9.24") compileOnly("org.jetbrains.kotlin:kotlin-stdlib:2.1.0")
implementation("io.ktor:ktor-client-core:2.3.12") implementation("io.ktor:ktor-client-core:3.0.2")
implementation("io.ktor:ktor-client-cio:2.3.10") implementation("io.ktor:ktor-client-cio:3.0.2")
implementation("io.ktor:ktor-client-content-negotiation:2.3.10") implementation("io.ktor:ktor-client-content-negotiation:3.0.2")
implementation("io.ktor:ktor-serialization-kotlinx-json:2.3.12") implementation("io.ktor:ktor-serialization-kotlinx-json:3.0.2")
implementation("io.ktor:ktor-client-auth:2.3.12") implementation("io.ktor:ktor-client-auth:3.0.2")
implementation("org.eclipse.jgit:org.eclipse.jgit:6.6.0.202305301015-r") implementation("org.eclipse.jgit:org.eclipse.jgit:6.6.0.202305301015-r")
// This is needed for jgit to connect to ssh // 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") implementation("com.vdurmont:semver4j:3.1.0")
} }

View File

@@ -40,6 +40,9 @@ val knownPlugins = setOf(
"com.protoseo.input-source-auto-converter", "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 // "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() { suspend fun main() {

View File

@@ -8,14 +8,23 @@
package com.maddyhome.idea.vim package com.maddyhome.idea.vim
import com.intellij.ide.BrowserUtil
import com.intellij.ide.plugins.IdeaPluginDescriptor
import com.intellij.ide.plugins.PluginStateListener
import com.intellij.ide.plugins.PluginStateManager
import com.intellij.openapi.application.ApplicationManager
import com.intellij.openapi.fileEditor.ex.FileEditorManagerEx import com.intellij.openapi.fileEditor.ex.FileEditorManagerEx
import com.intellij.openapi.project.Project import com.intellij.openapi.project.Project
import com.intellij.openapi.project.ProjectManagerListener import com.intellij.openapi.project.ProjectManagerListener
import com.intellij.openapi.startup.ProjectActivity import com.intellij.openapi.startup.ProjectActivity
import com.intellij.openapi.updateSettings.impl.UpdateSettings
import com.maddyhome.idea.vim.api.injector import com.maddyhome.idea.vim.api.injector
import com.maddyhome.idea.vim.helper.EditorHelper import com.maddyhome.idea.vim.helper.EditorHelper
import com.maddyhome.idea.vim.newapi.IjVimEditor import com.maddyhome.idea.vim.newapi.IjVimEditor
import com.maddyhome.idea.vim.newapi.globalIjOptions import com.maddyhome.idea.vim.newapi.globalIjOptions
import com.maddyhome.idea.vim.newapi.initInjector
import com.maddyhome.idea.vim.ui.JoinEap
import com.maddyhome.idea.vim.ui.JoinEap.EAP_LINK
/** /**
* @author Alex Plate * @author Alex Plate
@@ -24,18 +33,39 @@ internal class PluginStartup : ProjectActivity/*, LightEditCompatible*/ {
private var firstInitializationOccurred = false 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) { override suspend fun execute(project: Project) {
if (firstInitializationOccurred) return if (firstInitializationOccurred) return
firstInitializationOccurred = true firstInitializationOccurred = true
if (!VimPlugin.getVimState().wasSubscibedToEAPAutomatically && ApplicationManager.getApplication().isEAP && !JoinEap.eapActive()) {
VimPlugin.getVimState().wasSubscibedToEAPAutomatically = true
UpdateSettings.getInstance().storedPluginHosts += EAP_LINK
}
// This code should be executed once // This code should be executed once
VimPlugin.getInstance().initialize() VimPlugin.getInstance().initialize()
// Uninstall survey. Should be registered once for all projects
PluginStateManager.addStateListener(object : PluginStateListener {
override fun install(p0: IdeaPluginDescriptor) {/*Nothing*/
}
override fun uninstall(descriptor: IdeaPluginDescriptor) {
if (descriptor.pluginId == VimPlugin.getPluginId()) {
BrowserUtil.open("https://surveys.jetbrains.com/s3/ideavim-uninstall-feedback")
}
}
})
} }
} }
// This is a temporal workaround for VIM-2487 // This is a temporal workaround for VIM-2487
internal class PyNotebooksCloseWorkaround : ProjectManagerListener { internal class PyNotebooksCloseWorkaround : ProjectManagerListener {
override fun projectClosingBeforeSave(project: Project) { override fun projectClosingBeforeSave(project: Project) {
initInjector()
// TODO: Confirm context in CWM scenario // TODO: Confirm context in CWM scenario
if (injector.globalIjOptions().closenotebooks) { if (injector.globalIjOptions().closenotebooks) {
injector.editorGroup.getEditors().forEach { vimEditor -> injector.editorGroup.getEditors().forEach { vimEditor ->

View File

@@ -10,6 +10,7 @@ package com.maddyhome.idea.vim;
import com.intellij.ide.plugins.IdeaPluginDescriptor; import com.intellij.ide.plugins.IdeaPluginDescriptor;
import com.intellij.ide.plugins.PluginManagerCore; import com.intellij.ide.plugins.PluginManagerCore;
import com.intellij.openapi.Disposable; import com.intellij.openapi.Disposable;
import com.intellij.openapi.application.AccessToken;
import com.intellij.openapi.application.Application; import com.intellij.openapi.application.Application;
import com.intellij.openapi.application.ApplicationManager; import com.intellij.openapi.application.ApplicationManager;
import com.intellij.openapi.components.PersistentStateComponent; 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.ui.Messages;
import com.intellij.openapi.util.Disposer; import com.intellij.openapi.util.Disposer;
import com.intellij.openapi.util.SystemInfo; import com.intellij.openapi.util.SystemInfo;
import com.maddyhome.idea.vim.api.VimEditor; import com.intellij.util.SlowOperations;
import com.maddyhome.idea.vim.api.VimInjectorKt; import com.maddyhome.idea.vim.api.*;
import com.maddyhome.idea.vim.api.VimKeyGroup;
import com.maddyhome.idea.vim.api.VimOptionGroup;
import com.maddyhome.idea.vim.config.VimState; import com.maddyhome.idea.vim.config.VimState;
import com.maddyhome.idea.vim.config.migration.ApplicationConfigurationMigrator; import com.maddyhome.idea.vim.config.migration.ApplicationConfigurationMigrator;
import com.maddyhome.idea.vim.extension.VimExtensionRegistrar; import com.maddyhome.idea.vim.extension.VimExtensionRegistrar;
@@ -36,7 +35,7 @@ import com.maddyhome.idea.vim.group.copy.PutGroup;
import com.maddyhome.idea.vim.group.visual.VisualMotionGroup; import com.maddyhome.idea.vim.group.visual.VisualMotionGroup;
import com.maddyhome.idea.vim.helper.MacKeyRepeat; import com.maddyhome.idea.vim.helper.MacKeyRepeat;
import com.maddyhome.idea.vim.listener.VimListenerManager; 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.newapi.IjVimSearchGroup;
import com.maddyhome.idea.vim.ui.StatusBarIconFactory; import com.maddyhome.idea.vim.ui.StatusBarIconFactory;
import com.maddyhome.idea.vim.vimscript.services.VariableService; import com.maddyhome.idea.vim.vimscript.services.VariableService;
@@ -67,7 +66,7 @@ public class VimPlugin implements PersistentStateComponent<Element>, Disposable
private static final Logger LOG = Logger.getInstance(VimPlugin.class); private static final Logger LOG = Logger.getInstance(VimPlugin.class);
static { static {
VimInjectorKt.setInjector(new IjVimInjector()); IjVimInjectorKt.initInjector();
} }
private final @NotNull VimState state = new VimState(); private final @NotNull VimState state = new VimState();
@@ -140,8 +139,8 @@ public class VimPlugin implements PersistentStateComponent<Element>, Disposable
return (MacroGroup)VimInjectorKt.getInjector().getMacro(); return (MacroGroup)VimInjectorKt.getInjector().getMacro();
} }
public static @NotNull DigraphGroup getDigraph() { public static @NotNull VimDigraphGroup getDigraph() {
return (DigraphGroup)VimInjectorKt.getInjector().getDigraphGroup(); return VimInjectorKt.getInjector().getDigraphGroup();
} }
public static @NotNull HistoryGroup getHistory() { public static @NotNull HistoryGroup getHistory() {
@@ -338,7 +337,9 @@ public class VimPlugin implements PersistentStateComponent<Element>, Disposable
// 4) ~/.ideavimrc execution // 4) ~/.ideavimrc execution
// Evaluate in the context of the fallback window, to capture local option state, to copy to the first editor window // 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 // Turing on should be performed after all commands registration
getSearch().turnOn(); getSearch().turnOn();

View File

@@ -18,7 +18,7 @@ import com.maddyhome.idea.vim.ui.ex.ExEntryPanel
internal class VimProjectService(val project: Project) : Disposable { internal class VimProjectService(val project: Project) : Disposable {
override fun dispose() { override fun dispose() {
// Not sure if this is a best solution // Not sure if this is a best solution
ExEntryPanel.getInstance().editor = null ExEntryPanel.getInstance().setEditor(null)
} }
companion object { companion object {

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

@@ -43,6 +43,7 @@ import com.maddyhome.idea.vim.key.ShortcutOwnerInfo
import com.maddyhome.idea.vim.listener.AceJumpService import com.maddyhome.idea.vim.listener.AceJumpService
import com.maddyhome.idea.vim.listener.AppCodeTemplates.appCodeTemplateCaptured import com.maddyhome.idea.vim.listener.AppCodeTemplates.appCodeTemplateCaptured
import com.maddyhome.idea.vim.newapi.globalIjOptions import com.maddyhome.idea.vim.newapi.globalIjOptions
import com.maddyhome.idea.vim.newapi.initInjector
import com.maddyhome.idea.vim.newapi.vim import com.maddyhome.idea.vim.newapi.vim
import com.maddyhome.idea.vim.ui.ex.ExEntryPanel import com.maddyhome.idea.vim.ui.ex.ExEntryPanel
import com.maddyhome.idea.vim.ui.ex.ExTextField import com.maddyhome.idea.vim.ui.ex.ExTextField
@@ -61,10 +62,13 @@ import javax.swing.KeyStroke
* way to get ideavim keys for this plugin. See VIM-3085 * way to get ideavim keys for this plugin. See VIM-3085
*/ */
class VimShortcutKeyAction : AnAction(), DumbAware/*, LightEditCompatible*/ { class VimShortcutKeyAction : AnAction(), DumbAware/*, LightEditCompatible*/ {
init {
initInjector()
}
private val traceTime: Boolean private val traceTime: Boolean
get() { get() {
// Make sure the injector is initialized
VimPlugin.getInstance()
return injector.globalOptions().ideatracetime return injector.globalOptions().ideatracetime
} }
@@ -257,7 +261,7 @@ class VimShortcutKeyAction : AnAction(), DumbAware/*, LightEditCompatible*/ {
private fun getEditor(e: AnActionEvent): Editor? { private fun getEditor(e: AnActionEvent): Editor? {
return e.getData(PlatformDataKeys.EDITOR) return e.getData(PlatformDataKeys.EDITOR)
?: if (e.getData(PlatformDataKeys.CONTEXT_COMPONENT) is ExTextField) { ?: if (e.getData(PlatformDataKeys.CONTEXT_COMPONENT) is ExTextField) {
ExEntryPanel.getInstance().editor ExEntryPanel.getInstance().ijEditor
} else { } else {
null null
} }
@@ -294,26 +298,10 @@ class VimShortcutKeyAction : AnAction(), DumbAware/*, LightEditCompatible*/ {
.addAll(getKeyStrokes(KeyEvent.VK_BACK_SPACE, 0, InputEvent.CTRL_DOWN_MASK)) .addAll(getKeyStrokes(KeyEvent.VK_BACK_SPACE, 0, InputEvent.CTRL_DOWN_MASK))
.addAll(getKeyStrokes(KeyEvent.VK_INSERT, 0)) .addAll(getKeyStrokes(KeyEvent.VK_INSERT, 0))
.addAll(getKeyStrokes(KeyEvent.VK_DELETE, 0, InputEvent.CTRL_DOWN_MASK)) .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_UP, 0))
.addAll(getKeyStrokes(KeyEvent.VK_DOWN, 0, InputEvent.CTRL_DOWN_MASK, InputEvent.SHIFT_DOWN_MASK)) .addAll(getKeyStrokes(KeyEvent.VK_DOWN, 0))
.addAll( .addAll(getKeyStrokes(KeyEvent.VK_LEFT, 0))
getKeyStrokes( .addAll(getKeyStrokes(KeyEvent.VK_RIGHT, 0))
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( .addAll(
getKeyStrokes( getKeyStrokes(
KeyEvent.VK_HOME, KeyEvent.VK_HOME,

View File

@@ -37,7 +37,7 @@ import com.maddyhome.idea.vim.vimscript.model.expressions.FunctionCallExpression
import com.maddyhome.idea.vim.vimscript.model.expressions.SimpleExpression import com.maddyhome.idea.vim.vimscript.model.expressions.SimpleExpression
// todo make it multicaret // todo make it multicaret
private fun doOperatorAction(editor: VimEditor, context: ExecutionContext, textRange: TextRange, selectionType: SelectionType): Boolean { private fun doOperatorAction(editor: VimEditor, context: ExecutionContext, textRange: TextRange, motionType: SelectionType): Boolean {
val func = injector.globalOptions().operatorfunc val func = injector.globalOptions().operatorfunc
if (func.isEmpty()) { if (func.isEmpty()) {
VimPlugin.showMessage(MessageHelper.message("E774")) VimPlugin.showMessage(MessageHelper.message("E774"))
@@ -57,9 +57,9 @@ private fun doOperatorAction(editor: VimEditor, context: ExecutionContext, textR
if (value is VimFuncref) { if (value is VimFuncref) {
handler = value.handler handler = value.handler
} }
} catch (ex: ExException) { } catch (_: ExException) {
// Get the argument for function('...') or funcref('...') for the error message // Get the argument for function('...') or funcref('...') for the error message
val functionName = if (expression is FunctionCallExpression && expression.arguments.size > 0) { val functionName = if (expression is FunctionCallExpression && expression.arguments.isNotEmpty()) {
expression.arguments[0].evaluate(editor, context, scriptContext).toString() expression.arguments[0].evaluate(editor, context, scriptContext).toString()
} }
else { else {
@@ -77,7 +77,7 @@ private fun doOperatorAction(editor: VimEditor, context: ExecutionContext, textR
return false return false
} }
val arg = when (selectionType) { val arg = when (motionType) {
SelectionType.LINE_WISE -> "line" SelectionType.LINE_WISE -> "line"
SelectionType.CHARACTER_WISE -> "char" SelectionType.CHARACTER_WISE -> "char"
SelectionType.BLOCK_WISE -> "block" SelectionType.BLOCK_WISE -> "block"
@@ -101,19 +101,13 @@ internal class OperatorAction : VimActionHandler.SingleExecution() {
override val argumentType: Argument.Type = Argument.Type.MOTION override val argumentType: Argument.Type = Argument.Type.MOTION
override fun execute(editor: VimEditor, context: ExecutionContext, cmd: Command, operatorArguments: OperatorArguments): Boolean { override fun execute(editor: VimEditor, context: ExecutionContext, cmd: Command, operatorArguments: OperatorArguments): Boolean {
val argument = cmd.argument ?: return false val argument = cmd.argument as? Argument.Motion ?: return false
if (!editor.inRepeatMode) { if (!editor.inRepeatMode) {
argumentCaptured = argument argumentCaptured = argument
} }
val range = getMotionRange(editor, context, argument, operatorArguments) val range = getMotionRange(editor, context, argument, operatorArguments)
if (range != null) { if (range != null) {
val selectionType = if (argument.motion.isLinewiseMotion()) { return doOperatorAction(editor, context, range, argument.getMotionType())
SelectionType.LINE_WISE
} else {
SelectionType.CHARACTER_WISE
}
return doOperatorAction(editor, context, range, selectionType)
} }
return false return false
} }
@@ -121,7 +115,7 @@ internal class OperatorAction : VimActionHandler.SingleExecution() {
private fun getMotionRange( private fun getMotionRange(
editor: VimEditor, editor: VimEditor,
context: ExecutionContext, context: ExecutionContext,
argument: Argument, argument: Argument.Motion,
operatorArguments: OperatorArguments, operatorArguments: OperatorArguments,
): TextRange? { ): TextRange? {
// Note that we're using getMotionRange2 in order to avoid normalising the linewise range into line start // Note that we're using getMotionRange2 in order to avoid normalising the linewise range into line start
@@ -136,7 +130,7 @@ internal class OperatorAction : VimActionHandler.SingleExecution() {
operatorArguments, operatorArguments,
)?.normalize()?.let { )?.normalize()?.let {
// If we're linewise, make sure the end offset isn't just the EOL char // If we're linewise, make sure the end offset isn't just the EOL char
if (argument.motion.isLinewiseMotion() && it.endOffset < editor.fileSize()) { if (argument.getMotionType() == SelectionType.LINE_WISE && it.endOffset < editor.fileSize()) {
TextRange(it.startOffset, it.endOffset + 1) TextRange(it.startOffset, it.endOffset + 1)
} else { } else {
it it

View File

@@ -25,7 +25,7 @@ internal class RepeatChangeAction : VimActionHandler.SingleExecution() {
override fun execute(editor: VimEditor, context: ExecutionContext, cmd: Command, operatorArguments: OperatorArguments): Boolean { override fun execute(editor: VimEditor, context: ExecutionContext, cmd: Command, operatorArguments: OperatorArguments): Boolean {
val state = injector.vimState val state = injector.vimState
val lastCommand = VimRepeater.lastChangeCommand var lastCommand = VimRepeater.lastChangeCommand
if (lastCommand == null && Extension.lastExtensionHandler == null) return false if (lastCommand == null && Extension.lastExtensionHandler == null) return false
@@ -57,12 +57,7 @@ internal class RepeatChangeAction : VimActionHandler.SingleExecution() {
) )
} else if (!repeatHandler && lastCommand != null) { } else if (!repeatHandler && lastCommand != null) {
if (cmd.rawCount > 0) { if (cmd.rawCount > 0) {
lastCommand.rawCount = cmd.count lastCommand = lastCommand.copy(rawCount = cmd.rawCount)
val arg = lastCommand.argument
if (arg != null) {
val mot = arg.motion
mot.rawCount = 0
}
} }
state.executingCommand = lastCommand state.executingCommand = lastCommand

View File

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

View File

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

View File

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

View File

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

View File

@@ -20,13 +20,10 @@ import com.maddyhome.idea.vim.command.OperatorArguments
import com.maddyhome.idea.vim.handler.IdeActionHandler import com.maddyhome.idea.vim.handler.IdeActionHandler
import com.maddyhome.idea.vim.handler.VimActionHandler import com.maddyhome.idea.vim.handler.VimActionHandler
import com.maddyhome.idea.vim.helper.enumSetOf import com.maddyhome.idea.vim.helper.enumSetOf
import com.maddyhome.idea.vim.undo.VimKeyBasedUndoService
import com.maddyhome.idea.vim.undo.VimTimestampBasedUndoService
import java.util.* import java.util.*
@CommandOrMotion(keys = ["<C-H>", "<BS>"], modes = [Mode.INSERT])
internal class VimEditorBackSpace : IdeActionHandler(IdeActions.ACTION_EDITOR_BACKSPACE) {
override val type: Command.Type = Command.Type.DELETE
}
@CommandOrMotion(keys = ["<Del>"], modes = [Mode.INSERT]) @CommandOrMotion(keys = ["<Del>"], modes = [Mode.INSERT])
internal class VimEditorDelete : IdeActionHandler(IdeActions.ACTION_EDITOR_DELETE) { internal class VimEditorDelete : IdeActionHandler(IdeActions.ACTION_EDITOR_DELETE) {
override val type: Command.Type = Command.Type.DELETE override val type: Command.Type = Command.Type.DELETE
@@ -44,8 +41,13 @@ internal class VimEditorDown : IdeActionHandler(IdeActions.ACTION_EDITOR_MOVE_CA
operatorArguments: OperatorArguments operatorArguments: OperatorArguments
): Boolean { ): Boolean {
val undo = injector.undo val undo = injector.undo
val nanoTime = System.nanoTime() when (undo) {
editor.forEachCaret { undo.endInsertSequence(it, it.offset, nanoTime) } 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) return super.execute(editor, context, cmd, operatorArguments)
} }
} }
@@ -68,8 +70,13 @@ internal class VimEditorUp : IdeActionHandler(IdeActions.ACTION_EDITOR_MOVE_CARE
operatorArguments: OperatorArguments operatorArguments: OperatorArguments
): Boolean { ): Boolean {
val undo = injector.undo val undo = injector.undo
val nanoTime = System.nanoTime() when (undo) {
editor.forEachCaret { undo.endInsertSequence(it, it.offset, nanoTime) } 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) return super.execute(editor, context, cmd, operatorArguments)
} }
} }
@@ -79,7 +86,7 @@ internal class VimQuickJavaDoc : VimActionHandler.SingleExecution() {
override val type: Command.Type = Command.Type.OTHER_READONLY override val type: Command.Type = Command.Type.OTHER_READONLY
override fun execute(editor: VimEditor, context: ExecutionContext, cmd: Command, operatorArguments: OperatorArguments): Boolean { override fun execute(editor: VimEditor, context: ExecutionContext, cmd: Command, operatorArguments: OperatorArguments): Boolean {
injector.actionExecutor.executeAction(IdeActions.ACTION_QUICK_JAVADOC, context) injector.actionExecutor.executeAction(editor, IdeActions.ACTION_QUICK_JAVADOC, context)
return true return true
} }
} }

View File

@@ -0,0 +1,72 @@
/*
* 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.command
import com.intellij.openapi.editor.Editor
import com.maddyhome.idea.vim.KeyHandler
import com.maddyhome.idea.vim.api.injector
import com.maddyhome.idea.vim.state.VimStateMachine
/**
* COMPATIBILITY-LAYER: Additional class
* Please see: https://jb.gg/zo8n0r
*/
@Deprecated("Use `injector.vimState`")
class CommandState(private val machine: VimStateMachine) {
val mode: Mode
get() {
val myMode = machine.mode
return when (myMode) {
is com.maddyhome.idea.vim.state.mode.Mode.CMD_LINE -> Mode.CMD_LINE
com.maddyhome.idea.vim.state.mode.Mode.INSERT -> Mode.INSERT
is com.maddyhome.idea.vim.state.mode.Mode.NORMAL -> Mode.COMMAND
is com.maddyhome.idea.vim.state.mode.Mode.OP_PENDING -> Mode.OP_PENDING
com.maddyhome.idea.vim.state.mode.Mode.REPLACE -> Mode.REPLACE
is com.maddyhome.idea.vim.state.mode.Mode.SELECT -> Mode.SELECT
is com.maddyhome.idea.vim.state.mode.Mode.VISUAL -> Mode.VISUAL
}
}
@Deprecated("Use `KeyHandler.keyHandlerState.commandBuilder", ReplaceWith(
"KeyHandler.getInstance().keyHandlerState.commandBuilder",
"com.maddyhome.idea.vim.KeyHandler"
)
)
val commandBuilder: CommandBuilder
get() = KeyHandler.getInstance().keyHandlerState.commandBuilder
@Deprecated("Use `KeyHandler.keyHandlerState.mappingState", ReplaceWith(
"KeyHandler.getInstance().keyHandlerState.mappingState",
"com.maddyhome.idea.vim.KeyHandler"
)
)
val mappingState: MappingState
get() = KeyHandler.getInstance().keyHandlerState.mappingState
enum class Mode {
// Basic modes
COMMAND, VISUAL, SELECT, INSERT, CMD_LINE, /*EX*/
// Additional modes
OP_PENDING, REPLACE /*, VISUAL_REPLACE*/, INSERT_NORMAL, INSERT_VISUAL, INSERT_SELECT
}
enum class SubMode {
NONE, VISUAL_CHARACTER, VISUAL_LINE, VISUAL_BLOCK
}
companion object {
@JvmStatic
@Deprecated("Use `injector.vimState`")
fun getInstance(editor: Editor): CommandState {
return CommandState(injector.vimState)
}
}
}

View File

@@ -12,17 +12,17 @@ import com.intellij.application.options.CodeStyle
import com.intellij.openapi.editor.Editor import com.intellij.openapi.editor.Editor
import com.intellij.openapi.project.Project import com.intellij.openapi.project.Project
import com.intellij.psi.codeStyle.CommonCodeStyleSettings.IndentOptions import com.intellij.psi.codeStyle.CommonCodeStyleSettings.IndentOptions
import com.maddyhome.idea.vim.api.VimIndentConfig
internal class IndentConfig private constructor(indentOptions: IndentOptions) { internal class IndentConfig private constructor(indentOptions: IndentOptions): VimIndentConfig {
private val indentSize = indentOptions.INDENT_SIZE private val indentSize = indentOptions.INDENT_SIZE
private val tabSize = indentOptions.TAB_SIZE private val tabSize = indentOptions.TAB_SIZE
private val isUseTabs = indentOptions.USE_TAB_CHARACTER private val isUseTabs = indentOptions.USE_TAB_CHARACTER
fun getTotalIndent(count: Int): Int = indentSize * count override fun getIndentSize(depth: Int): Int = indentSize * depth
override fun createIndentByDepth(depth: Int): String = createIndentBySize(getIndentSize(depth))
fun createIndentByCount(count: Int): String = createIndentBySize(getTotalIndent(count)) override fun createIndentBySize(size: Int): String {
fun createIndentBySize(size: Int): String {
val tabCount: Int val tabCount: Int
val spaceCount: Int val spaceCount: Int
if (isUseTabs) { if (isUseTabs) {

View File

@@ -18,6 +18,8 @@ import kotlin.reflect.KProperty
internal class VimState { internal class VimState {
var isIdeaJoinNotified by StateProperty("idea-join") var isIdeaJoinNotified by StateProperty("idea-join")
var isIdeaPutNotified by StateProperty("idea-put") var isIdeaPutNotified by StateProperty("idea-put")
var isNewUndoNotified by StateProperty("idea-undo")
var wasSubscibedToEAPAutomatically by StateProperty("was-automatically-subscribed-to-eap")
fun readData(element: Element) { fun readData(element: Element) {
val notifications = element.getChild("notifications") val notifications = element.getChild("notifications")

View File

@@ -9,14 +9,15 @@ package com.maddyhome.idea.vim.ex
import com.intellij.openapi.application.ApplicationManager import com.intellij.openapi.application.ApplicationManager
import com.intellij.openapi.editor.Editor import com.intellij.openapi.editor.Editor
import com.maddyhome.idea.vim.api.VimOutputPanel import com.maddyhome.idea.vim.api.VimOutputPanelBase
import com.maddyhome.idea.vim.api.injector import com.maddyhome.idea.vim.api.injector
import com.maddyhome.idea.vim.helper.vimExOutput import com.maddyhome.idea.vim.helper.vimExOutput
import com.maddyhome.idea.vim.ui.ExOutputPanel import com.maddyhome.idea.vim.ui.ExOutputPanel
import java.lang.ref.WeakReference import java.lang.ref.WeakReference
import javax.swing.KeyStroke
// TODO: We need a nicer way to handle output, especially wrt testing, appending + clearing // TODO: We need a nicer way to handle output, especially wrt testing, appending + clearing
class ExOutputModel(private val myEditor: WeakReference<Editor>) : VimOutputPanel { class ExOutputModel(private val myEditor: WeakReference<Editor>) : VimOutputPanelBase() {
private var isActiveInTestMode = false private var isActiveInTestMode = false
val editor get() = myEditor.get() val editor get() = myEditor.get()
@@ -48,6 +49,24 @@ class ExOutputModel(private val myEditor: WeakReference<Editor>) : VimOutputPane
} }
} }
override fun scrollPage() {
val notNullEditor = editor ?: return
val panel = ExOutputPanel.getNullablePanel(notNullEditor) ?: return
panel.scrollPage()
}
override fun scrollHalfPage() {
val notNullEditor = editor ?: return
val panel = ExOutputPanel.getNullablePanel(notNullEditor) ?: return
panel.scrollHalfPage()
}
override fun scrollLine() {
val notNullEditor = editor ?: return
val panel = ExOutputPanel.getNullablePanel(notNullEditor) ?: return
panel.scrollLine()
}
override var text: String = "" override var text: String = ""
get() = if (!ApplicationManager.getApplication().isUnitTestMode) { get() = if (!ApplicationManager.getApplication().isUnitTestMode) {
editor?.let { ExOutputPanel.getInstance(it).text } ?: "" editor?.let { ExOutputPanel.getInstance(it).text } ?: ""
@@ -66,6 +85,17 @@ class ExOutputModel(private val myEditor: WeakReference<Editor>) : VimOutputPane
isActiveInTestMode = newValue.isNotEmpty() isActiveInTestMode = newValue.isNotEmpty()
} }
} }
override var label: String
get() {
val notNullEditor = editor ?: return ""
val panel = ExOutputPanel.getNullablePanel(notNullEditor) ?: return ""
return panel.myLabel.text
}
set(value) {
val notNullEditor = editor ?: return
val panel = ExOutputPanel.getNullablePanel(notNullEditor) ?: return
panel.myLabel.text = value
}
fun output(text: String) { fun output(text: String) {
this.text = text this.text = text
@@ -75,6 +105,25 @@ class ExOutputModel(private val myEditor: WeakReference<Editor>) : VimOutputPane
text = "" text = ""
} }
override val atEnd: Boolean
get() {
val notNullEditor = editor ?: return false
val panel = ExOutputPanel.getNullablePanel(notNullEditor) ?: return false
return panel.isAtEnd()
}
override fun onBadKey() {
val notNullEditor = editor ?: return
val panel = ExOutputPanel.getNullablePanel(notNullEditor) ?: return
panel.onBadKey()
}
override fun close(key: KeyStroke?) {
val notNullEditor = editor ?: return
val panel = ExOutputPanel.getNullablePanel(notNullEditor) ?: return
panel.close(key)
}
override fun close() { override fun close() {
if (!ApplicationManager.getApplication().isUnitTestMode) { if (!ApplicationManager.getApplication().isUnitTestMode) {
editor?.let { ExOutputPanel.getInstance(it).close() } editor?.let { ExOutputPanel.getInstance(it).close() }

View File

@@ -27,6 +27,30 @@ public interface VimExtension {
return MappingOwner.Plugin.Companion.get(getName()); 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(); void init();
default void dispose() { default void dispose() {

View File

@@ -31,6 +31,7 @@ import com.maddyhome.idea.vim.key.OperatorFunction
import com.maddyhome.idea.vim.newapi.vim import com.maddyhome.idea.vim.newapi.vim
import com.maddyhome.idea.vim.state.mode.SelectionType import com.maddyhome.idea.vim.state.mode.SelectionType
import com.maddyhome.idea.vim.ui.ModalEntry import com.maddyhome.idea.vim.ui.ModalEntry
import com.maddyhome.idea.vim.ui.ex.ExEntryPanelService
import com.maddyhome.idea.vim.vimscript.model.Executable import com.maddyhome.idea.vim.vimscript.model.Executable
import com.maddyhome.idea.vim.vimscript.model.ExecutionResult import com.maddyhome.idea.vim.vimscript.model.ExecutionResult
import com.maddyhome.idea.vim.vimscript.model.VimLContext import com.maddyhome.idea.vim.vimscript.model.VimLContext
@@ -68,8 +69,7 @@ object VimExtensionFacade {
@JvmStatic @JvmStatic
@Deprecated( @Deprecated("Use VimPlugin.getKey().putKeyMapping(modes, fromKeys, pluginOwner, extensionHandler, recursive)",
"Use VimPlugin.getKey().putKeyMapping(modes, fromKeys, pluginOwner, extensionHandler, recursive)",
ReplaceWith( ReplaceWith(
"VimPlugin.getKey().putKeyMapping(modes, fromKeys, pluginOwner, extensionHandler, recursive)", "VimPlugin.getKey().putKeyMapping(modes, fromKeys, pluginOwner, extensionHandler, recursive)",
"com.maddyhome.idea.vim.VimPlugin" "com.maddyhome.idea.vim.VimPlugin"
@@ -183,19 +183,27 @@ object VimExtensionFacade {
/** Returns a string typed in the input box similar to 'input()'. */ /** Returns a string typed in the input box similar to 'input()'. */
@JvmStatic @JvmStatic
fun inputString(editor: Editor, context: DataContext, prompt: String, finishOn: Char?): String { fun inputString(editor: Editor, context: DataContext, prompt: String, finishOn: Char?): String {
return injector.commandLine.inputString(editor.vim, context.vim, prompt, finishOn) ?: "" return (injector.commandLine as ExEntryPanelService).inputString(editor.vim, context.vim, prompt, finishOn) ?: ""
} }
/** Get the current contents of the given register similar to 'getreg()'. */ /** Get the current contents of the given register similar to 'getreg()'. */
@JvmStatic @JvmStatic
@Deprecated("Please use com.maddyhome.idea.vim.extension.VimExtensionFacade.getRegister(com.maddyhome.idea.vim.api.VimEditor, char)")
fun getRegister(register: Char): List<KeyStroke>? { fun getRegister(register: Char): List<KeyStroke>? {
val reg = VimPlugin.getRegister().getRegister(register) ?: return null val reg = VimPlugin.getRegister().getRegister(register) ?: return null
return reg.keys 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 @JvmStatic
fun getRegisterForCaret(register: Char, caret: VimCaret): List<KeyStroke>? { fun getRegisterForCaret(register: Char, caret: VimCaret): List<KeyStroke>? {
val reg = injector.registerGroup.getRegister(register) ?: return null val reg = caret.registerStorage.getRegister(register) ?: return null
return reg.keys return reg.keys
} }
@@ -208,7 +216,7 @@ object VimExtensionFacade {
/** Set the current contents of the given register */ /** Set the current contents of the given register */
@JvmStatic @JvmStatic
fun setRegisterForCaret(register: Char, caret: ImmutableVimCaret, keys: List<KeyStroke?>?) { fun setRegisterForCaret(register: Char, caret: ImmutableVimCaret, keys: List<KeyStroke?>?) {
injector.registerGroup.setKeys(register, keys?.filterNotNull() ?: emptyList()) caret.registerStorage.setKeys(register, keys?.filterNotNull() ?: emptyList())
} }
/** Set the current contents of the given register */ /** Set the current contents of the given register */

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. * See the docs for [VimExtension.init]
* This practically means that the .vimrc file is initialized first, then the plugins are loaded.
* See `:h initialization`
* *
* In IdeaVim we don't have a separate plugins folder to load it after .ideavimrc load. However, we can collect * 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. * 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 @JvmStatic
fun enableDelayedExtensions() { fun enableDelayedExtensions() {

View File

@@ -33,7 +33,6 @@ import org.jetbrains.annotations.Nullable;
import java.util.ArrayDeque; import java.util.ArrayDeque;
import java.util.Deque; import java.util.Deque;
import java.util.EnumSet;
import static com.maddyhome.idea.vim.extension.VimExtensionFacade.putExtensionHandlerMapping; import static com.maddyhome.idea.vim.extension.VimExtensionFacade.putExtensionHandlerMapping;
import static com.maddyhome.idea.vim.extension.VimExtensionFacade.putKeyMappingIfMissing; import static com.maddyhome.idea.vim.extension.VimExtensionFacade.putKeyMappingIfMissing;
@@ -64,8 +63,8 @@ public class VimArgTextObjExtension implements VimExtension {
*/ */
private static class BracketPairs { private static class BracketPairs {
// NOTE: brackets must match by the position, and ordered by rank (highest to lowest). // NOTE: brackets must match by the position, and ordered by rank (highest to lowest).
@NotNull private final String openBrackets; private final @NotNull String openBrackets;
@NotNull private final String closeBrackets; private final @NotNull String closeBrackets;
static class ParseException extends Exception { static class ParseException extends Exception {
public ParseException(@NotNull String message) { public ParseException(@NotNull String message) {
@@ -87,8 +86,7 @@ public class VimArgTextObjExtension implements VimExtension {
* @param bracketPairs comma-separated list of colon-separated bracket pairs. * @param bracketPairs comma-separated list of colon-separated bracket pairs.
* @throws ParseException if a syntax error is detected. * @throws ParseException if a syntax error is detected.
*/ */
@NotNull static @NotNull BracketPairs fromBracketPairList(final @NotNull String bracketPairs) throws ParseException {
static BracketPairs fromBracketPairList(@NotNull final String bracketPairs) throws ParseException {
StringBuilder openBrackets = new StringBuilder(); StringBuilder openBrackets = new StringBuilder();
StringBuilder closeBrackets = new StringBuilder(); StringBuilder closeBrackets = new StringBuilder();
ParseState state = ParseState.OPEN; ParseState state = ParseState.OPEN;
@@ -128,7 +126,7 @@ public class VimArgTextObjExtension implements VimExtension {
return new BracketPairs(openBrackets.toString(), closeBrackets.toString()); return new BracketPairs(openBrackets.toString(), closeBrackets.toString());
} }
BracketPairs(@NotNull final String openBrackets, @NotNull final String closeBrackets) { BracketPairs(final @NotNull String openBrackets, final @NotNull String closeBrackets) {
assert openBrackets.length() == closeBrackets.length(); assert openBrackets.length() == closeBrackets.length();
this.openBrackets = openBrackets; this.openBrackets = openBrackets;
this.closeBrackets = closeBrackets; this.closeBrackets = closeBrackets;
@@ -158,10 +156,9 @@ public class VimArgTextObjExtension implements VimExtension {
} }
} }
public static final BracketPairs DEFAULT_BRACKET_PAIRS = new BracketPairs("(", ")"); private static final BracketPairs DEFAULT_BRACKET_PAIRS = new BracketPairs("(", ")");
@Nullable private static @Nullable String bracketPairsVariable() {
private static String bracketPairsVariable() {
final Object value = VimPlugin.getVariableService().getGlobalVariableValue("argtextobj_pairs"); final Object value = VimPlugin.getVariableService().getGlobalVariableValue("argtextobj_pairs");
if (value instanceof VimString vimValue) { if (value instanceof VimString vimValue) {
return vimValue.getValue(); return vimValue.getValue();
@@ -192,13 +189,12 @@ public class VimArgTextObjExtension implements VimExtension {
this.isInner = isInner; this.isInner = isInner;
} }
@Nullable
@Override @Override
public TextRange getRange(@NotNull VimEditor editor, public @Nullable TextRange getRange(@NotNull VimEditor editor,
@NotNull ImmutableVimCaret caret, @NotNull ImmutableVimCaret caret,
@NotNull ExecutionContext context, @NotNull ExecutionContext context,
int count, int count,
int rawCount) { int rawCount) {
BracketPairs bracketPairs = DEFAULT_BRACKET_PAIRS; BracketPairs bracketPairs = DEFAULT_BRACKET_PAIRS;
final String bracketPairsVar = bracketPairsVariable(); final String bracketPairsVar = bracketPairsVariable();
if (bracketPairsVar != null) { if (bracketPairsVar != null) {
@@ -236,24 +232,22 @@ public class VimArgTextObjExtension implements VimExtension {
return new TextRange(finder.getLeftBound(), finder.getRightBound()); return new TextRange(finder.getLeftBound(), finder.getRightBound());
} }
@NotNull
@Override @Override
public TextObjectVisualType getVisualType() { public @NotNull TextObjectVisualType getVisualType() {
return TextObjectVisualType.CHARACTER_WISE; return TextObjectVisualType.CHARACTER_WISE;
} }
} }
@Override @Override
public void execute(@NotNull VimEditor editor, @NotNull ExecutionContext context, @NotNull OperatorArguments operatorArguments) { public void execute(@NotNull VimEditor editor, @NotNull ExecutionContext context, @NotNull OperatorArguments operatorArguments) {
@NotNull KeyHandler keyHandler = KeyHandler.getInstance();
@NotNull KeyHandlerState keyHandlerState = KeyHandler.getInstance().getKeyHandlerState(); @NotNull KeyHandlerState keyHandlerState = KeyHandler.getInstance().getKeyHandlerState();
int count = Math.max(1, keyHandlerState.getCommandBuilder().getCount());
final ArgumentTextObjectHandler textObjectHandler = new ArgumentTextObjectHandler(isInner); final ArgumentTextObjectHandler textObjectHandler = new ArgumentTextObjectHandler(isInner);
//noinspection DuplicatedCode //noinspection DuplicatedCode
if (!keyHandler.isOperatorPending(editor.getMode(), keyHandlerState)) { if (!(editor.getMode() instanceof Mode.OP_PENDING)) {
int count0 = operatorArguments.getCount0();
editor.nativeCarets().forEach((VimCaret caret) -> { editor.nativeCarets().forEach((VimCaret caret) -> {
final TextRange range = textObjectHandler.getRange(editor, caret, context, count, 0); final TextRange range = textObjectHandler.getRange(editor, caret, context, Math.max(1, count0), count0);
if (range != null) { if (range != null) {
try (VimListenerSuppressor.Locked ignored = SelectionVimListenerSuppressor.INSTANCE.lock()) { try (VimListenerSuppressor.Locked ignored = SelectionVimListenerSuppressor.INSTANCE.lock()) {
if (editor.getMode() instanceof Mode.VISUAL) { if (editor.getMode() instanceof Mode.VISUAL) {
@@ -265,8 +259,7 @@ public class VimArgTextObjExtension implements VimExtension {
} }
}); });
} else { } else {
keyHandlerState.getCommandBuilder().completeCommandPart(new Argument(new Command(count, keyHandlerState.getCommandBuilder().addAction(textObjectHandler);
textObjectHandler, Command.Type.MOTION, EnumSet.noneOf(CommandFlags.class))));
} }
} }
} }
@@ -276,9 +269,9 @@ public class VimArgTextObjExtension implements VimExtension {
* position * position
*/ */
private static class ArgBoundsFinder { private static class ArgBoundsFinder {
@NotNull private final CharSequence text; private final @NotNull CharSequence text;
@NotNull private final Document document; private final @NotNull Document document;
@NotNull private final BracketPairs brackets; private final @NotNull BracketPairs brackets;
private int leftBound = Integer.MAX_VALUE; private int leftBound = Integer.MAX_VALUE;
private int rightBound = Integer.MIN_VALUE; private int rightBound = Integer.MIN_VALUE;
private int leftBracket; private int leftBracket;
@@ -305,7 +298,7 @@ public class VimArgTextObjExtension implements VimExtension {
* @param position starting position. * @param position starting position.
*/ */
boolean findBoundsAt(int position) throws IllegalStateException { boolean findBoundsAt(int position) throws IllegalStateException {
if (text.length() == 0) { if (text.isEmpty()) {
error = "empty document"; error = "empty document";
return false; return false;
} }

View File

@@ -25,9 +25,6 @@ import com.maddyhome.idea.vim.api.VimEditor
import com.maddyhome.idea.vim.api.getLineEndOffset import com.maddyhome.idea.vim.api.getLineEndOffset
import com.maddyhome.idea.vim.api.globalOptions import com.maddyhome.idea.vim.api.globalOptions
import com.maddyhome.idea.vim.api.injector import com.maddyhome.idea.vim.api.injector
import com.maddyhome.idea.vim.command.Argument
import com.maddyhome.idea.vim.command.Command
import com.maddyhome.idea.vim.command.CommandFlags
import com.maddyhome.idea.vim.command.MappingMode import com.maddyhome.idea.vim.command.MappingMode
import com.maddyhome.idea.vim.command.OperatorArguments import com.maddyhome.idea.vim.command.OperatorArguments
import com.maddyhome.idea.vim.command.TextObjectVisualType import com.maddyhome.idea.vim.command.TextObjectVisualType
@@ -52,7 +49,6 @@ import com.maddyhome.idea.vim.newapi.ij
import com.maddyhome.idea.vim.newapi.vim import com.maddyhome.idea.vim.newapi.vim
import com.maddyhome.idea.vim.state.mode.Mode import com.maddyhome.idea.vim.state.mode.Mode
import com.maddyhome.idea.vim.state.mode.SelectionType import com.maddyhome.idea.vim.state.mode.SelectionType
import java.util.*
internal class CommentaryExtension : VimExtension { internal class CommentaryExtension : VimExtension {
@@ -79,11 +75,12 @@ internal class CommentaryExtension : VimExtension {
val project = editor.ij.project!! val project = editor.ij.project!!
val callback = { afterCommenting(mode, editor, resetCaret, range) } val callback = { afterCommenting(mode, editor, resetCaret, range) }
actions.any { executeActionWithCallbackOnSuccess(it, project, context, callback) } actions.any { executeActionWithCallbackOnSuccess(editor, it, project, context, callback) }
} }
} }
private fun executeActionWithCallbackOnSuccess( private fun executeActionWithCallbackOnSuccess(
editor: VimEditor,
action: String, action: String,
project: Project, project: Project,
context: ExecutionContext, context: ExecutionContext,
@@ -92,7 +89,7 @@ internal class CommentaryExtension : VimExtension {
val res = Ref.create<Boolean>(false) val res = Ref.create<Boolean>(false)
AsyncActionExecutionService.getInstance(project).withExecutionAfterAction( AsyncActionExecutionService.getInstance(project).withExecutionAfterAction(
action, action,
{ res.set(injector.actionExecutor.executeAction(action, context)) }, { res.set(injector.actionExecutor.executeAction(editor, name = action, context = context)) },
{ if (res.get()) callback() }) { if (res.get()) callback() })
return res.get() return res.get()
} }
@@ -183,10 +180,8 @@ internal class CommentaryExtension : VimExtension {
override val isRepeatable = true override val isRepeatable = true
override fun execute(editor: VimEditor, context: ExecutionContext, operatorArguments: OperatorArguments) { override fun execute(editor: VimEditor, context: ExecutionContext, operatorArguments: OperatorArguments) {
val command = Command(operatorArguments.count1, CommentaryTextObjectMotionHandler, Command.Type.MOTION, EnumSet.noneOf(CommandFlags::class.java))
val keyState = KeyHandler.getInstance().keyHandlerState val keyState = KeyHandler.getInstance().keyHandlerState
keyState.commandBuilder.completeCommandPart(Argument(command)) keyState.commandBuilder.addAction(CommentaryTextObjectMotionHandler)
} }
} }

View File

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

View File

@@ -23,13 +23,15 @@ import com.intellij.util.Alarm
import com.intellij.util.Alarm.ThreadToUse import com.intellij.util.Alarm.ThreadToUse
import com.maddyhome.idea.vim.VimPlugin import com.maddyhome.idea.vim.VimPlugin
import com.maddyhome.idea.vim.api.VimEditor import com.maddyhome.idea.vim.api.VimEditor
import com.maddyhome.idea.vim.api.injector
import com.maddyhome.idea.vim.common.ModeChangeListener
import com.maddyhome.idea.vim.common.TextRange import com.maddyhome.idea.vim.common.TextRange
import com.maddyhome.idea.vim.common.VimYankListener
import com.maddyhome.idea.vim.extension.VimExtension import com.maddyhome.idea.vim.extension.VimExtension
import com.maddyhome.idea.vim.helper.MessageHelper import com.maddyhome.idea.vim.helper.MessageHelper
import com.maddyhome.idea.vim.helper.VimNlsSafe import com.maddyhome.idea.vim.helper.VimNlsSafe
import com.maddyhome.idea.vim.listener.VimInsertListener
import com.maddyhome.idea.vim.listener.VimYankListener
import com.maddyhome.idea.vim.newapi.ij import com.maddyhome.idea.vim.newapi.ij
import com.maddyhome.idea.vim.state.mode.Mode
import com.maddyhome.idea.vim.vimscript.model.datatypes.VimDataType import com.maddyhome.idea.vim.vimscript.model.datatypes.VimDataType
import com.maddyhome.idea.vim.vimscript.model.datatypes.VimString import com.maddyhome.idea.vim.vimscript.model.datatypes.VimString
import org.jetbrains.annotations.NonNls import org.jetbrains.annotations.NonNls
@@ -43,6 +45,10 @@ private val HIGHLIGHT_DURATION_VARIABLE_NAME = "highlightedyank_highlight_durati
@NonNls @NonNls
private val HIGHLIGHT_COLOR_VARIABLE_NAME = "highlightedyank_highlight_color" 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 var defaultHighlightTextColor: Color? = null
private fun getDefaultHighlightTextColor(): Color { private fun getDefaultHighlightTextColor(): Color {
@@ -73,9 +79,12 @@ 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. * 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)" * 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. * When a new text is yanked or user starts editing, the old highlighting would be deleted.
*/ */
internal class VimHighlightedYank : VimExtension, VimYankListener, VimInsertListener { internal class VimHighlightedYank : VimExtension, VimYankListener, ModeChangeListener {
private val highlightHandler = HighlightHandler() private val highlightHandler = HighlightHandler()
private var initialised = false private var initialised = false
@@ -83,8 +92,8 @@ internal class VimHighlightedYank : VimExtension, VimYankListener, VimInsertList
override fun init() { override fun init() {
// Note that these listeners will still be registered when IdeaVim is disabled. However, they'll never get called // Note that these listeners will still be registered when IdeaVim is disabled. However, they'll never get called
VimPlugin.getYank().addListener(this) injector.listenersNotifier.modeChangeListeners.add(this)
VimPlugin.getChange().addInsertListener(this) injector.listenersNotifier.yankListeners.add(this)
// Register our own disposable to remove highlights when IdeaVim is disabled. Somehow, we need to re-register when // Register our own disposable to remove highlights when IdeaVim is disabled. Somehow, we need to re-register when
// IdeaVim is re-enabled. We don't get a call back for that, but because the listeners are active until the // IdeaVim is re-enabled. We don't get a call back for that, but because the listeners are active until the
@@ -105,8 +114,8 @@ internal class VimHighlightedYank : VimExtension, VimYankListener, VimInsertList
override fun dispose() { override fun dispose() {
// Called when the extension is disabled with `:set no{extension}` or if the plugin owning the extension unloads // Called when the extension is disabled with `:set no{extension}` or if the plugin owning the extension unloads
VimPlugin.getYank().removeListener(this) injector.listenersNotifier.modeChangeListeners.remove(this)
VimPlugin.getChange().removeInsertListener(this) injector.listenersNotifier.yankListeners.remove(this)
highlightHandler.clearYankHighlighters() highlightHandler.clearYankHighlighters()
initialised = false initialised = false
@@ -117,7 +126,8 @@ internal class VimHighlightedYank : VimExtension, VimYankListener, VimInsertList
highlightHandler.highlightYankRange(editor.ij, range) highlightHandler.highlightYankRange(editor.ij, range)
} }
override fun insertModeStarted(editor: Editor) { override fun modeChanged(editor: VimEditor, oldMode: Mode) {
if (editor.mode !is Mode.INSERT) return
ensureInitialised() ensureInitialised()
highlightHandler.clearYankHighlighters() highlightHandler.clearYankHighlighters()
} }
@@ -178,13 +188,15 @@ internal class VimHighlightedYank : VimExtension, VimYankListener, VimInsertList
highlighters.clear() highlighters.clear()
} }
private fun getHighlightTextAttributes(editor: Editor) = TextAttributes( private fun getHighlightTextAttributes(editor: Editor): TextAttributes {
null, return TextAttributes(
extractUsersHighlightColor(), extractUserHighlightForegroundColor(),
editor.colorsScheme.getColor(EditorColors.CARET_COLOR), extractUsersHighlightColor(),
EffectType.SEARCH_MATCH, editor.colorsScheme.getColor(EditorColors.CARET_COLOR),
Font.PLAIN, EffectType.SEARCH_MATCH,
) Font.PLAIN,
)
}
private fun extractUsersHighlightDuration(): Int { private fun extractUsersHighlightDuration(): Int {
return extractVariable(HIGHLIGHT_DURATION_VARIABLE_NAME, DEFAULT_HIGHLIGHT_DURATION) { return extractVariable(HIGHLIGHT_DURATION_VARIABLE_NAME, DEFAULT_HIGHLIGHT_DURATION) {
@@ -197,15 +209,52 @@ internal class VimHighlightedYank : VimExtension, VimYankListener, VimInsertList
} }
private fun extractUsersHighlightColor(): Color { private fun extractUsersHighlightColor(): Color {
return extractVariable(HIGHLIGHT_COLOR_VARIABLE_NAME, getDefaultHighlightTextColor()) { value -> val value = VimPlugin.getVariableService().getGlobalVariableValue(HIGHLIGHT_COLOR_VARIABLE_NAME)
val rgba = value.asString() if (value != null) {
.substring(4) return try {
.filter { it != '(' && it != ')' && !it.isWhitespace() } parseRgbaColor(value.asString())
.split(',') } catch (e: Exception) {
.map { it.toInt() } @VimNlsSafe val message = MessageHelper.message(
"highlightedyank.invalid.value.of.0.1",
Color(rgba[0], rgba[1], rgba[2], rgba[3]) "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 { private fun <T> extractVariable(variable: String, default: T, extractFun: (value: VimDataType) -> T): T {

View File

@@ -44,6 +44,7 @@ import com.maddyhome.idea.vim.helper.enumSetOf
import com.maddyhome.idea.vim.newapi.IjVimEditor import com.maddyhome.idea.vim.newapi.IjVimEditor
import com.maddyhome.idea.vim.newapi.ij import com.maddyhome.idea.vim.newapi.ij
import com.maddyhome.idea.vim.newapi.vim import com.maddyhome.idea.vim.newapi.vim
import com.maddyhome.idea.vim.state.mode.Mode
import java.util.* import java.util.*
import java.util.regex.Pattern import java.util.regex.Pattern
@@ -93,34 +94,29 @@ internal class Matchit : VimExtension {
override fun execute(editor: VimEditor, context: ExecutionContext, operatorArguments: OperatorArguments) { override fun execute(editor: VimEditor, context: ExecutionContext, operatorArguments: OperatorArguments) {
val keyHandler = KeyHandler.getInstance() val keyHandler = KeyHandler.getInstance()
val keyState = keyHandler.keyHandlerState val keyState = keyHandler.keyHandlerState
val count = keyState.commandBuilder.count
// Reset the command count so it doesn't transfer onto subsequent commands. // Reset the command count so it doesn't transfer onto subsequent commands.
keyState.commandBuilder.resetCount() keyState.commandBuilder.resetCount()
// Normally we want to jump to the start of the matching pair. But when moving forward in operator // Normally we want to jump to the start of the matching pair. But when moving forward in operator
// pending mode, we want to include the entire match. isInOpPending makes that distinction. // pending mode, we want to include the entire match. isInOpPending makes that distinction.
val isInOpPending = keyHandler.isOperatorPending(editor.mode, keyState) if (editor.mode is Mode.OP_PENDING) {
if (isInOpPending) {
val matchitAction = MatchitAction() val matchitAction = MatchitAction()
matchitAction.reverse = reverse matchitAction.reverse = reverse
matchitAction.isInOpPending = true matchitAction.isInOpPending = true
keyState.commandBuilder.completeCommandPart( keyState.commandBuilder.addAction(matchitAction)
Argument(
Command(
count,
matchitAction,
Command.Type.MOTION,
EnumSet.noneOf(CommandFlags::class.java),
),
),
)
} else { } else {
editor.sortedCarets().forEach { caret -> editor.sortedCarets().forEach { caret ->
injector.jumpService.saveJumpLocation(editor) injector.jumpService.saveJumpLocation(editor)
caret.moveToOffset(getMatchitOffset(editor.ij, caret.ij, count, isInOpPending, reverse)) caret.moveToOffset(
getMatchitOffset(
editor.ij,
caret.ij,
operatorArguments.count0,
isInOpPending = false,
reverse
))
} }
} }
} }
@@ -354,7 +350,7 @@ private object FileTypePatterns {
private val DEFAULT_PAIRS = setOf('(', ')', '[', ']', '{', '}') private val DEFAULT_PAIRS = setOf('(', ')', '[', ']', '{', '}')
private fun getMatchitOffset(editor: Editor, caret: Caret, count: Int, isInOpPending: Boolean, reverse: Boolean): Int { private fun getMatchitOffset(editor: Editor, caret: Caret, count0: Int, isInOpPending: Boolean, reverse: Boolean): Int {
val virtualFile = EditorHelper.getVirtualFile(editor) val virtualFile = EditorHelper.getVirtualFile(editor)
var caretOffset = caret.offset var caretOffset = caret.offset
@@ -367,9 +363,9 @@ private fun getMatchitOffset(editor: Editor, caret: Caret, count: Int, isInOpPen
val currentChar = editor.document.charsSequence[caretOffset] val currentChar = editor.document.charsSequence[caretOffset]
var motionOffset: Int? = null var motionOffset: Int? = null
if (count > 0) { if (count0 > 0) {
// Matchit doesn't affect the percent motion, so we fall back to the default behavior. // Matchit doesn't affect the percent motion, so we fall back to the default behavior.
motionOffset = VimPlugin.getMotion().moveCaretToLinePercent(editor.vim, caret.vim, count) motionOffset = VimPlugin.getMotion().moveCaretToLinePercent(editor.vim, caret.vim, count0)
} else { } else {
// Check the simplest case first. // Check the simplest case first.
if (DEFAULT_PAIRS.contains(currentChar)) { if (DEFAULT_PAIRS.contains(currentChar)) {
@@ -400,8 +396,7 @@ private fun getMatchitOffset(editor: Editor, caret: Caret, count: Int, isInOpPen
private fun getMotionOffset(motion: Motion): Int? { private fun getMotionOffset(motion: Motion): Int? {
return when (motion) { return when (motion) {
is Motion.AbsoluteOffset -> motion.offset is Motion.AdjustedOffset, is Motion.AbsoluteOffset -> motion.offset
is Motion.AdjustedOffset -> motion.offset
is Motion.Error, is Motion.NoMotion -> null is Motion.Error, is Motion.NoMotion -> null
} }
} }

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.group.KeyGroup
import com.maddyhome.idea.vim.helper.MessageHelper import com.maddyhome.idea.vim.helper.MessageHelper
import com.maddyhome.idea.vim.helper.runAfterGotFocus import com.maddyhome.idea.vim.helper.runAfterGotFocus
import com.maddyhome.idea.vim.key.CommandNode import com.maddyhome.idea.vim.key.KeyStrokeTrie
import com.maddyhome.idea.vim.key.CommandPartNode
import com.maddyhome.idea.vim.key.MappingOwner 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.RequiredShortcut
import com.maddyhome.idea.vim.key.RootNode import com.maddyhome.idea.vim.key.add
import com.maddyhome.idea.vim.key.addLeafs
import com.maddyhome.idea.vim.newapi.ij import com.maddyhome.idea.vim.newapi.ij
import com.maddyhome.idea.vim.newapi.vim import com.maddyhome.idea.vim.newapi.vim
import com.maddyhome.idea.vim.vimscript.model.datatypes.VimString import com.maddyhome.idea.vim.vimscript.model.datatypes.VimString
@@ -198,6 +195,8 @@ internal class NerdTree : VimExtension {
internal var waitForSearch = false internal var waitForSearch = false
internal var speedSearchListenerInstalled = false internal var speedSearchListenerInstalled = false
private val keys = mutableListOf<KeyStroke>()
override fun actionPerformed(e: AnActionEvent) { override fun actionPerformed(e: AnActionEvent) {
var keyStroke = getKeyStroke(e) ?: return var keyStroke = getKeyStroke(e) ?: return
val keyChar = keyStroke.keyChar val keyChar = keyStroke.keyChar
@@ -205,20 +204,14 @@ internal class NerdTree : VimExtension {
keyStroke = KeyStroke.getKeyStroke(keyChar) keyStroke = KeyStroke.getKeyStroke(keyChar)
} }
val nextNode = currentNode[keyStroke] keys.add(keyStroke)
actionsRoot.getData(keys)?.let { action ->
when (nextNode) { when (action) {
null -> currentNode = actionsRoot is NerdAction.ToIj -> Util.callAction(null, action.name, e.dataContext.vim)
is CommandNode<NerdAction> -> { is NerdAction.Code -> e.project?.let { action.action(it, e.dataContext, e) }
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) }
}
} }
is CommandPartNode<NerdAction> -> currentNode = nextNode
keys.clear()
} }
} }
@@ -481,6 +474,9 @@ internal class NerdTree : VimExtension {
registerCommand("NERDTreeMapNewFile", "n", NerdAction.ToIj("NewFile")) registerCommand("NERDTreeMapNewFile", "n", NerdAction.ToIj("NewFile"))
registerCommand("NERDTreeMapNewDir", "N", NerdAction.ToIj("NewDir")) registerCommand("NERDTreeMapNewDir", "N", NerdAction.ToIj("NewDir"))
registerCommand("NERDTreeMapDelete", "d", NerdAction.ToIj("\$Delete")) registerCommand("NERDTreeMapDelete", "d", NerdAction.ToIj("\$Delete"))
registerCommand("NERDTreeMapCopy", "y", NerdAction.ToIj("\$Copy"))
registerCommand("NERDTreeMapPaste", "v", NerdAction.ToIj("\$Paste"))
registerCommand("NERDTreeMapRename", "<C-r>", NerdAction.ToIj("RenameElement"))
registerCommand("NERDTreeMapRefreshRoot", "R", NerdAction.ToIj("Synchronize")) registerCommand("NERDTreeMapRefreshRoot", "R", NerdAction.ToIj("Synchronize"))
registerCommand("NERDTreeMapMenu", "m", NerdAction.ToIj("ShowPopupMenu")) registerCommand("NERDTreeMapMenu", "m", NerdAction.ToIj("ShowPopupMenu"))
registerCommand("NERDTreeMapQuit", "q", NerdAction.ToIj("HideActiveWindow")) registerCommand("NERDTreeMapQuit", "q", NerdAction.ToIj("HideActiveWindow"))
@@ -537,37 +533,29 @@ private fun addCommand(alias: String, handler: CommandAliasHandler) {
VimPlugin.getCommand().setAlias(alias, CommandAlias.Call(0, -1, alias, handler)) 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 variableValue = VimPlugin.getVariableService().getGlobalVariableValue(variable)
val mappings = if (variableValue is VimString) { val mapping = if (variableValue is VimString) {
variableValue.value variableValue.value
} else { } else {
default defaultMapping
} }
actionsRoot.addLeafs(mappings, action) registerCommand(mapping, action)
} }
private fun registerCommand(default: String, action: NerdAction) { private fun registerCommand(mapping: String, action: NerdAction) {
actionsRoot.addLeafs(default, action) actionsRoot.add(mapping, action)
} injector.parser.parseKeys(mapping).forEach {
distinctShortcuts.add(it)
private val actionsRoot: RootNode<NerdAction> = RootNode()
private var currentNode: CommandPartNode<NerdAction> = actionsRoot
private fun collectShortcuts(node: Node<NerdAction>): Set<KeyStroke> {
return if (node is CommandPartNode<NerdAction>) {
val res = node.keys.toMutableSet()
res += node.values.map { collectShortcuts(it) }.flatten()
res
} else {
emptySet()
} }
} }
private val actionsRoot: KeyStrokeTrie<NerdAction> = KeyStrokeTrie<NerdAction>("NERDTree")
private val distinctShortcuts = mutableSetOf<KeyStroke>()
private fun installDispatcher(project: Project) { private fun installDispatcher(project: Project) {
val dispatcher = NerdTree.NerdDispatcher.getInstance(project) val dispatcher = NerdTree.NerdDispatcher.getInstance(project)
val shortcuts = val shortcuts = distinctShortcuts.map { RequiredShortcut(it, MappingOwner.Plugin.get(NerdTree.pluginName)) }
collectShortcuts(actionsRoot).map { RequiredShortcut(it, MappingOwner.Plugin.get(NerdTree.pluginName)) }
dispatcher.registerCustomShortcutSet( dispatcher.registerCustomShortcutSet(
KeyGroup.toShortcutSet(shortcuts), KeyGroup.toShortcutSet(shortcuts),
(ProjectView.getInstance(project) as ProjectViewImpl).component, (ProjectView.getInstance(project) as ProjectViewImpl).component,

View File

@@ -62,7 +62,7 @@ internal class ParagraphMotion : VimExtension {
toKeys: List<KeyStroke>, toKeys: List<KeyStroke>,
recursive: Boolean, 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) putKeyMappingIfMissing(filteredModes, fromKeys, pluginOwner, toKeys, recursive)
} }
} }

View File

@@ -10,7 +10,6 @@ package com.maddyhome.idea.vim.extension.replacewithregister
import com.intellij.openapi.actionSystem.DataContext import com.intellij.openapi.actionSystem.DataContext
import com.intellij.openapi.editor.Editor import com.intellij.openapi.editor.Editor
import com.maddyhome.idea.vim.KeyHandler
import com.maddyhome.idea.vim.VimPlugin import com.maddyhome.idea.vim.VimPlugin
import com.maddyhome.idea.vim.api.ExecutionContext import com.maddyhome.idea.vim.api.ExecutionContext
import com.maddyhome.idea.vim.api.ImmutableVimCaret import com.maddyhome.idea.vim.api.ImmutableVimCaret
@@ -144,7 +143,7 @@ internal class ReplaceWithRegister : VimExtension {
private fun doReplace(editor: Editor, context: DataContext, caret: ImmutableVimCaret, visualSelection: PutData.VisualSelection) { private fun doReplace(editor: Editor, context: DataContext, caret: ImmutableVimCaret, visualSelection: PutData.VisualSelection) {
val registerGroup = injector.registerGroup val registerGroup = injector.registerGroup
val lastRegisterChar = if (editor.caretModel.caretCount == 1) registerGroup.currentRegister else registerGroup.getCurrentRegisterForMulticaret() val lastRegisterChar = if (editor.caretModel.caretCount == 1) registerGroup.currentRegister else registerGroup.getCurrentRegisterForMulticaret()
val savedRegister = registerGroup.getRegister(lastRegisterChar) ?: return val savedRegister = caret.registerStorage.getRegister(lastRegisterChar) ?: return
var usedType = savedRegister.type var usedType = savedRegister.type
var usedText = savedRegister.text var usedText = savedRegister.text
@@ -166,17 +165,11 @@ private fun doReplace(editor: Editor, context: DataContext, caret: ImmutableVimC
putToLine = -1, putToLine = -1,
) )
val vimEditor = editor.vim val vimEditor = editor.vim
val keyHandler = KeyHandler.getInstance()
ClipboardOptionHelper.IdeaputDisabler().use { ClipboardOptionHelper.IdeaputDisabler().use {
VimPlugin.getPut().putText( VimPlugin.getPut().putText(
vimEditor, vimEditor,
context.vim, context.vim,
putData, putData,
operatorArguments = OperatorArguments(
keyHandler.isOperatorPending(vimEditor.mode, keyHandler.keyHandlerState),
0,
editor.vim.mode,
),
saveToRegister = false saveToRegister = false
) )
} }

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

View File

@@ -36,6 +36,7 @@ import com.maddyhome.idea.vim.extension.VimExtensionFacade.putKeyMappingIfMissin
import com.maddyhome.idea.vim.extension.VimExtensionFacade.setRegisterForCaret import com.maddyhome.idea.vim.extension.VimExtensionFacade.setRegisterForCaret
import com.maddyhome.idea.vim.extension.exportOperatorFunction import com.maddyhome.idea.vim.extension.exportOperatorFunction
import com.maddyhome.idea.vim.group.findBlockRange 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.helper.runWithEveryCaretAndRestore
import com.maddyhome.idea.vim.key.OperatorFunction import com.maddyhome.idea.vim.key.OperatorFunction
import com.maddyhome.idea.vim.newapi.IjVimCaret import com.maddyhome.idea.vim.newapi.IjVimCaret
@@ -46,6 +47,7 @@ import com.maddyhome.idea.vim.options.helpers.ClipboardOptionHelper
import com.maddyhome.idea.vim.put.PutData import com.maddyhome.idea.vim.put.PutData
import com.maddyhome.idea.vim.state.mode.Mode import com.maddyhome.idea.vim.state.mode.Mode
import com.maddyhome.idea.vim.state.mode.SelectionType 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 com.maddyhome.idea.vim.state.mode.selectionType
import org.jetbrains.annotations.NonNls import org.jetbrains.annotations.NonNls
import java.awt.event.KeyEvent import java.awt.event.KeyEvent
@@ -115,6 +117,9 @@ internal class VimSurroundExtension : VimExtension {
// it.moveToOffset(lineStartOffset) // it.moveToOffset(lineStartOffset)
} }
// Jump back to start // Jump back to start
if (editor.mode !is Mode.NORMAL) {
editor.mode = Mode.NORMAL()
}
executeNormalWithoutMapping(injector.parser.parseKeys("`["), ijEditor) executeNormalWithoutMapping(injector.parser.parseKeys("`["), ijEditor)
} }
@@ -136,7 +141,7 @@ internal class VimSurroundExtension : VimExtension {
} }
runWriteAction { runWriteAction {
// Leave visual mode // Leave visual mode
executeNormalWithoutMapping(injector.parser.parseKeys("<Esc>"), editor.ij) editor.exitVisualMode()
} }
} }
} }
@@ -156,17 +161,17 @@ internal class VimSurroundExtension : VimExtension {
} }
companion object { 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) } editor.ij.runWithEveryCaretAndRestore { changeAtCaret(editor, context, charFrom, newSurround) }
} }
fun changeAtCaret(editor: VimEditor, context: ExecutionContext, charFrom: Char, newSurround: Pair<String, String>?) { fun changeAtCaret(editor: VimEditor, context: ExecutionContext, charFrom: Char, newSurround: SurroundPair?) {
// Save old register values for carets // Save old register values for carets
val surroundings = editor.sortedCarets() val surroundings = editor.sortedCarets()
.map { .map {
val oldValue: List<KeyStroke>? = getRegisterForCaret(REGISTER, it) val oldValue: List<KeyStroke>? = getRegisterForCaret(REGISTER, it)
setRegisterForCaret(REGISTER, it, null) setRegisterForCaret(REGISTER, it, null)
SurroundingInfo(it, null, oldValue) SurroundingInfo(it, null, oldValue, false)
} }
// Delete surrounding's content // Delete surrounding's content
@@ -182,21 +187,25 @@ internal class VimSurroundExtension : VimExtension {
} }
val registerValue = getRegisterForCaret(REGISTER, it.caret) 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 it.innerText = innerValue
}
surroundings.forEach { // Valid surroundings are only those that:
if (it.innerText == null && getRegisterForCaret(REGISTER, it.caret)?.isNotEmpty() == true) { // - are validly wrapping with surround characters (i.e. parenthesis, brackets, tags, quotes, etc.);
it.innerText = emptyList() // - or have non-empty inner text (e.g. when we are surrounding words: `csw"`)
if (currentSurrounding != null || innerValue.isNotEmpty()) {
it.isValidSurrounding = true
} }
} }
surroundings 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 -> .map { surrounding ->
val innerValue = injector.parser.toPrintableString(surrounding.innerText!!) 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 textData = PutData.TextData(text, SelectionType.CHARACTER_WISE, emptyList(), null)
val putData = PutData(textData, null, 1, insertTextBeforeCaret = true, rawIndent = true, caretAfterInsertedText = false) val putData = PutData(textData, null, 1, insertTextBeforeCaret = true, rawIndent = true, caretAfterInsertedText = false)
@@ -249,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() { fun restoreRegister() {
setRegisterForCaret(REGISTER, caret, oldRegisterContent) setRegisterForCaret(REGISTER, caret, oldRegisterContent)
} }
@@ -292,7 +301,7 @@ internal class VimSurroundExtension : VimExtension {
return true return true
} }
private fun applyOnce(editor: Editor, change: VimChangeGroup, pair: Pair<String, String>, count: Int) { private fun applyOnce(editor: Editor, change: VimChangeGroup, pair: SurroundPair, count: Int) {
// XXX: Will it work with line-wise or block-wise selections? // XXX: Will it work with line-wise or block-wise selections?
val primaryCaret = editor.caretModel.primaryCaret val primaryCaret = editor.caretModel.primaryCaret
val range = getSurroundRange(primaryCaret.vim) val range = getSurroundRange(primaryCaret.vim)
@@ -306,8 +315,10 @@ internal class VimSurroundExtension : VimExtension {
private fun getSurroundRange(caret: VimCaret): TextRange? { private fun getSurroundRange(caret: VimCaret): TextRange? {
val editor = caret.editor val editor = caret.editor
val ijEditor = editor.ij if (editor.mode is Mode.CMD_LINE) {
return when (ijEditor.vim.mode) { editor.mode = (editor.mode as Mode.CMD_LINE).returnTo()
}
return when (editor.mode) {
is Mode.NORMAL -> injector.markService.getChangeMarks(caret) is Mode.NORMAL -> injector.markService.getChangeMarks(caret)
is Mode.VISUAL -> caret.run { TextRange(selectionStart, selectionEnd) } is Mode.VISUAL -> caret.run { TextRange(selectionStart, selectionEnd) }
else -> null else -> null
@@ -322,39 +333,44 @@ private const val REGISTER = '"'
private const val OPERATOR_FUNC = "SurroundOperatorFunc" 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( private val SURROUND_PAIRS = mapOf(
'b' to ("(" to ")"), 'b' to SurroundPair("(", ")", false),
'(' to ("( " to " )"), '(' to SurroundPair("( ", " )", false),
')' to ("(" to ")"), ')' to SurroundPair("(", ")", true),
'B' to ("{" to "}"), 'B' to SurroundPair("{", "}", false),
'{' to ("{ " to " }"), '{' to SurroundPair("{ ", " }", false),
'}' to ("{" to "}"), '}' to SurroundPair("{", "}", true),
'r' to ("[" to "]"), 'r' to SurroundPair("[", "]", false),
'[' to ("[ " to " ]"), '[' to SurroundPair("[ ", " ]", false),
']' to ("[" to "]"), ']' to SurroundPair("[", "]", true),
'a' to ("<" to ">"), 'a' to SurroundPair("<", ">", false),
'>' to ("<" to ">"), '>' to SurroundPair("<", ">", false),
's' to (" " to ""), '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] SURROUND_PAIRS[c]
} else if (!c.isLetter()) { } else if (!c.isLetter()) {
val s = c.toString() val s = c.toString()
s to s SurroundPair(s, s, false)
} else { } else {
null null
} }
private fun inputTagPair(editor: Editor, context: DataContext): Pair<String, String>? { private fun inputTagPair(editor: Editor, context: DataContext): SurroundPair? {
val tagInput = inputString(editor, context, "<", '>') val tagInput = inputString(editor, context, "<", '>')
if (editor.vim.mode is Mode.CMD_LINE) {
editor.vim.mode = editor.vim.mode.returnTo()
}
val matcher = tagNameAndAttributesCapturePattern.matcher(tagInput) val matcher = tagNameAndAttributesCapturePattern.matcher(tagInput)
return if (matcher.find()) { return if (matcher.find()) {
val tagName = matcher.group(1) val tagName = matcher.group(1)
val tagAttributes = matcher.group(2) val tagAttributes = matcher.group(2)
"<$tagName$tagAttributes>" to "</$tagName>" SurroundPair("<$tagName$tagAttributes>", "</$tagName>", false)
} else { } else {
null null
} }
@@ -364,13 +380,20 @@ private fun inputFunctionName(
editor: Editor, editor: Editor,
context: DataContext, context: DataContext,
withInternalSpaces: Boolean, withInternalSpaces: Boolean,
): Pair<String, String>? { ): SurroundPair? {
val functionNameInput = inputString(editor, context, "function: ", null) 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 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) '<', 't' -> inputTagPair(editor, context)
'f' -> inputFunctionName(editor, context, false) 'f' -> inputFunctionName(editor, context, false)
'F' -> inputFunctionName(editor, context, true) 'F' -> inputFunctionName(editor, context, true)
@@ -389,7 +412,7 @@ private fun getChar(editor: Editor): Char {
return res return res
} }
private fun performSurround(pair: Pair<String, String>, range: TextRange, caret: VimCaret, count: Int, tagsOnNewLines: Boolean = false) { private fun performSurround(pair: SurroundPair, range: TextRange, caret: VimCaret, count: Int, tagsOnNewLines: Boolean = false) {
runWriteAction { runWriteAction {
val editor = caret.editor val editor = caret.editor
val change = VimPlugin.getChange() val change = VimPlugin.getChange()

View File

@@ -29,14 +29,12 @@ import com.maddyhome.idea.vim.state.mode.Mode;
import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable; import org.jetbrains.annotations.Nullable;
import java.util.EnumSet;
import static com.maddyhome.idea.vim.extension.VimExtensionFacade.putExtensionHandlerMapping; import static com.maddyhome.idea.vim.extension.VimExtensionFacade.putExtensionHandlerMapping;
import static com.maddyhome.idea.vim.extension.VimExtensionFacade.putKeyMappingIfMissing; import static com.maddyhome.idea.vim.extension.VimExtensionFacade.putKeyMappingIfMissing;
/** /**
* Port of vim-entire: * Port of vim-entire:
* https://github.com/kana/vim-textobj-entire * <a href="https://github.com/kana/vim-textobj-entire">vim-textobj-entire</a>
* *
* <p> * <p>
* vim-textobj-entire provides two text objects: * vim-textobj-entire provides two text objects:
@@ -51,7 +49,7 @@ import static com.maddyhome.idea.vim.extension.VimExtensionFacade.putKeyMappingI
* </ul> * </ul>
* *
* See also the reference manual for more details at: * See also the reference manual for more details at:
* https://github.com/kana/vim-textobj-entire/blob/master/doc/textobj-entire.txt * <a href="https://github.com/kana/vim-textobj-entire/blob/master/doc/textobj-entire.txt">text-obj-entire.txt</a>
* *
* @author Alexandre Grison (@agrison) * @author Alexandre Grison (@agrison)
*/ */
@@ -94,13 +92,12 @@ public class VimTextObjEntireExtension implements VimExtension {
this.ignoreLeadingAndTrailing = ignoreLeadingAndTrailing; this.ignoreLeadingAndTrailing = ignoreLeadingAndTrailing;
} }
@Nullable
@Override @Override
public TextRange getRange(@NotNull VimEditor editor, public @Nullable TextRange getRange(@NotNull VimEditor editor,
@NotNull ImmutableVimCaret caret, @NotNull ImmutableVimCaret caret,
@NotNull ExecutionContext context, @NotNull ExecutionContext context,
int count, int count,
int rawCount) { int rawCount) {
int start = 0, end = ((IjVimEditor)editor).getEditor().getDocument().getTextLength(); int start = 0, end = ((IjVimEditor)editor).getEditor().getDocument().getTextLength();
// for the `ie` text object we don't want leading an trailing spaces // for the `ie` text object we don't want leading an trailing spaces
@@ -125,24 +122,22 @@ public class VimTextObjEntireExtension implements VimExtension {
return new TextRange(start, end); return new TextRange(start, end);
} }
@NotNull
@Override @Override
public TextObjectVisualType getVisualType() { public @NotNull TextObjectVisualType getVisualType() {
return TextObjectVisualType.CHARACTER_WISE; return TextObjectVisualType.CHARACTER_WISE;
} }
} }
@Override @Override
public void execute(@NotNull VimEditor editor, @NotNull ExecutionContext context, @NotNull OperatorArguments operatorArguments) { public void execute(@NotNull VimEditor editor, @NotNull ExecutionContext context, @NotNull OperatorArguments operatorArguments) {
@NotNull KeyHandler keyHandler = KeyHandler.getInstance();
@NotNull KeyHandlerState keyHandlerState = KeyHandler.getInstance().getKeyHandlerState(); @NotNull KeyHandlerState keyHandlerState = KeyHandler.getInstance().getKeyHandlerState();
int count = Math.max(1, keyHandlerState.getCommandBuilder().getCount());
final EntireTextObjectHandler textObjectHandler = new EntireTextObjectHandler(ignoreLeadingAndTrailing); final EntireTextObjectHandler textObjectHandler = new EntireTextObjectHandler(ignoreLeadingAndTrailing);
//noinspection DuplicatedCode //noinspection DuplicatedCode
if (!keyHandler.isOperatorPending(editor.getMode(), keyHandlerState)) { if (!(editor.getMode() instanceof Mode.OP_PENDING)) {
int count0 = operatorArguments.getCount0();
((IjVimEditor) editor).getEditor().getCaretModel().runForEachCaret((Caret caret) -> { ((IjVimEditor) editor).getEditor().getCaretModel().runForEachCaret((Caret caret) -> {
final TextRange range = textObjectHandler.getRange(editor, new IjVimCaret(caret), context, count, 0); final TextRange range = textObjectHandler.getRange(editor, new IjVimCaret(caret), context, Math.max(1, count0), count0);
if (range != null) { if (range != null) {
try (VimListenerSuppressor.Locked ignored = SelectionVimListenerSuppressor.INSTANCE.lock()) { try (VimListenerSuppressor.Locked ignored = SelectionVimListenerSuppressor.INSTANCE.lock()) {
if (editor.getMode() instanceof Mode.VISUAL) { if (editor.getMode() instanceof Mode.VISUAL) {
@@ -155,9 +150,7 @@ public class VimTextObjEntireExtension implements VimExtension {
}); });
} else { } else {
keyHandlerState.getCommandBuilder().completeCommandPart(new Argument(new Command(count, keyHandlerState.getCommandBuilder().addAction(textObjectHandler);
textObjectHandler, Command.Type.MOTION,
EnumSet.noneOf(CommandFlags.class))));
} }
} }
} }

View File

@@ -30,14 +30,12 @@ import com.maddyhome.idea.vim.state.mode.Mode;
import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable; import org.jetbrains.annotations.Nullable;
import java.util.EnumSet;
import static com.maddyhome.idea.vim.extension.VimExtensionFacade.putExtensionHandlerMapping; import static com.maddyhome.idea.vim.extension.VimExtensionFacade.putExtensionHandlerMapping;
import static com.maddyhome.idea.vim.extension.VimExtensionFacade.putKeyMapping; import static com.maddyhome.idea.vim.extension.VimExtensionFacade.putKeyMapping;
/** /**
* Port of vim-indent-object: * Port of vim-indent-object:
* https://github.com/michaeljsmith/vim-indent-object * <a href="https://github.com/michaeljsmith/vim-indent-object">vim-indent-object</a>
* *
* <p> * <p>
* vim-indent-object provides these text objects based on the cursor line's indentation: * vim-indent-object provides these text objects based on the cursor line's indentation:
@@ -49,7 +47,7 @@ import static com.maddyhome.idea.vim.extension.VimExtensionFacade.putKeyMapping;
* </ul> * </ul>
* *
* See also the reference manual for more details at: * See also the reference manual for more details at:
* https://github.com/michaeljsmith/vim-indent-object/blob/master/doc/indent-object.txt * <a href="https://github.com/michaeljsmith/vim-indent-object/blob/master/doc/indent-object.txt">indent-object.txt</a>
* *
* @author Shrikant Kandula (@sharat87) * @author Shrikant Kandula (@sharat87)
*/ */
@@ -98,13 +96,12 @@ public class VimIndentObject implements VimExtension {
this.includeBelow = includeBelow; this.includeBelow = includeBelow;
} }
@Nullable
@Override @Override
public TextRange getRange(@NotNull VimEditor editor, public @Nullable TextRange getRange(@NotNull VimEditor editor,
@NotNull ImmutableVimCaret caret, @NotNull ImmutableVimCaret caret,
@NotNull ExecutionContext context, @NotNull ExecutionContext context,
int count, int count,
int rawCount) { int rawCount) {
final CharSequence charSequence = ((IjVimEditor)editor).getEditor().getDocument().getCharsSequence(); final CharSequence charSequence = ((IjVimEditor)editor).getEditor().getDocument().getCharsSequence();
final int caretOffset = ((IjVimCaret)caret).getCaret().getOffset(); final int caretOffset = ((IjVimCaret)caret).getCaret().getOffset();
@@ -249,9 +246,8 @@ public class VimIndentObject implements VimExtension {
return new TextRange(upperBoundaryOffset, lowerBoundaryOffset); return new TextRange(upperBoundaryOffset, lowerBoundaryOffset);
} }
@NotNull
@Override @Override
public TextObjectVisualType getVisualType() { public @NotNull TextObjectVisualType getVisualType() {
return TextObjectVisualType.LINE_WISE; return TextObjectVisualType.LINE_WISE;
} }
@@ -264,15 +260,14 @@ public class VimIndentObject implements VimExtension {
@Override @Override
public void execute(@NotNull VimEditor editor, @NotNull ExecutionContext context, @NotNull OperatorArguments operatorArguments) { public void execute(@NotNull VimEditor editor, @NotNull ExecutionContext context, @NotNull OperatorArguments operatorArguments) {
IjVimEditor vimEditor = (IjVimEditor)editor; IjVimEditor vimEditor = (IjVimEditor)editor;
@NotNull KeyHandler keyHandler = KeyHandler.getInstance();
@NotNull KeyHandlerState keyHandlerState = KeyHandler.getInstance().getKeyHandlerState(); @NotNull KeyHandlerState keyHandlerState = KeyHandler.getInstance().getKeyHandlerState();
int count = Math.max(1, keyHandlerState.getCommandBuilder().getCount());
final IndentObjectHandler textObjectHandler = new IndentObjectHandler(includeAbove, includeBelow); final IndentObjectHandler textObjectHandler = new IndentObjectHandler(includeAbove, includeBelow);
if (!keyHandler.isOperatorPending(editor.getMode(), keyHandlerState)) { if (!(editor.getMode() instanceof Mode.OP_PENDING)) {
int count0 = operatorArguments.getCount0();
((IjVimEditor)editor).getEditor().getCaretModel().runForEachCaret((Caret caret) -> { ((IjVimEditor)editor).getEditor().getCaretModel().runForEachCaret((Caret caret) -> {
final TextRange range = textObjectHandler.getRange(vimEditor, new IjVimCaret(caret), context, count, 0); final TextRange range = textObjectHandler.getRange(vimEditor, new IjVimCaret(caret), context, Math.max(1, count0), count0);
if (range != null) { if (range != null) {
try (VimListenerSuppressor.Locked ignored = SelectionVimListenerSuppressor.INSTANCE.lock()) { try (VimListenerSuppressor.Locked ignored = SelectionVimListenerSuppressor.INSTANCE.lock()) {
if (editor.getMode() instanceof Mode.VISUAL) { if (editor.getMode() instanceof Mode.VISUAL) {
@@ -285,9 +280,7 @@ public class VimIndentObject implements VimExtension {
}); });
} else { } else {
keyHandlerState.getCommandBuilder().completeCommandPart(new Argument(new Command(count, keyHandlerState.getCommandBuilder().addAction(textObjectHandler);
textObjectHandler, Command.Type.MOTION,
EnumSet.noneOf(CommandFlags.class))));
} }
} }
} }

View File

@@ -15,77 +15,38 @@ import com.intellij.openapi.command.CommandProcessor
import com.intellij.openapi.command.UndoConfirmationPolicy import com.intellij.openapi.command.UndoConfirmationPolicy
import com.intellij.openapi.diagnostic.logger import com.intellij.openapi.diagnostic.logger
import com.intellij.openapi.editor.Editor import com.intellij.openapi.editor.Editor
import com.intellij.openapi.editor.LogicalPosition
import com.intellij.openapi.editor.actions.EnterAction import com.intellij.openapi.editor.actions.EnterAction
import com.intellij.openapi.editor.event.EditorMouseEvent import com.intellij.openapi.editor.event.EditorMouseEvent
import com.intellij.openapi.editor.event.EditorMouseListener import com.intellij.openapi.editor.event.EditorMouseListener
import com.intellij.openapi.editor.impl.TextRangeInterval
import com.intellij.openapi.util.UserDataHolder import com.intellij.openapi.util.UserDataHolder
import com.intellij.openapi.util.text.StringUtil
import com.intellij.psi.codeStyle.CodeStyleManager import com.intellij.psi.codeStyle.CodeStyleManager
import com.intellij.psi.util.PsiUtilBase import com.intellij.psi.util.PsiUtilBase
import com.intellij.util.containers.ContainerUtil
import com.maddyhome.idea.vim.EventFacade import com.maddyhome.idea.vim.EventFacade
import com.maddyhome.idea.vim.api.BufferPosition
import com.maddyhome.idea.vim.api.ExecutionContext import com.maddyhome.idea.vim.api.ExecutionContext
import com.maddyhome.idea.vim.api.VimCaret import com.maddyhome.idea.vim.api.VimCaret
import com.maddyhome.idea.vim.api.VimChangeGroupBase import com.maddyhome.idea.vim.api.VimChangeGroupBase
import com.maddyhome.idea.vim.api.VimEditor import com.maddyhome.idea.vim.api.VimEditor
import com.maddyhome.idea.vim.api.VimMotionGroupBase
import com.maddyhome.idea.vim.api.anyNonWhitespace
import com.maddyhome.idea.vim.api.getLineEndForOffset import com.maddyhome.idea.vim.api.getLineEndForOffset
import com.maddyhome.idea.vim.api.getLineEndOffset
import com.maddyhome.idea.vim.api.getLineStartForOffset import com.maddyhome.idea.vim.api.getLineStartForOffset
import com.maddyhome.idea.vim.api.getText
import com.maddyhome.idea.vim.api.injector import com.maddyhome.idea.vim.api.injector
import com.maddyhome.idea.vim.api.lineLength
import com.maddyhome.idea.vim.api.normalizeOffset
import com.maddyhome.idea.vim.api.options
import com.maddyhome.idea.vim.command.Argument
import com.maddyhome.idea.vim.command.OperatorArguments
import com.maddyhome.idea.vim.common.IndentConfig.Companion.create
import com.maddyhome.idea.vim.common.TextRange import com.maddyhome.idea.vim.common.TextRange
import com.maddyhome.idea.vim.ex.ranges.LineRange
import com.maddyhome.idea.vim.group.MotionGroup.Companion.getMotionRange2
import com.maddyhome.idea.vim.group.visual.VimSelection
import com.maddyhome.idea.vim.group.visual.vimSetSystemSelectionSilently import com.maddyhome.idea.vim.group.visual.vimSetSystemSelectionSilently
import com.maddyhome.idea.vim.handler.Motion
import com.maddyhome.idea.vim.handler.Motion.AbsoluteOffset
import com.maddyhome.idea.vim.handler.commandContinuation import com.maddyhome.idea.vim.handler.commandContinuation
import com.maddyhome.idea.vim.helper.CharacterHelper
import com.maddyhome.idea.vim.helper.CharacterHelper.changeCase
import com.maddyhome.idea.vim.helper.CharacterHelper.charType
import com.maddyhome.idea.vim.helper.EditorHelper
import com.maddyhome.idea.vim.helper.NumberType
import com.maddyhome.idea.vim.helper.endOffsetInclusive
import com.maddyhome.idea.vim.helper.findNumberUnderCursor
import com.maddyhome.idea.vim.helper.findNumbersInRange
import com.maddyhome.idea.vim.helper.inInsertMode import com.maddyhome.idea.vim.helper.inInsertMode
import com.maddyhome.idea.vim.helper.moveToInlayAwareLogicalPosition
import com.maddyhome.idea.vim.helper.moveToInlayAwareOffset
import com.maddyhome.idea.vim.key.KeyHandlerKeeper.Companion.getInstance import com.maddyhome.idea.vim.key.KeyHandlerKeeper.Companion.getInstance
import com.maddyhome.idea.vim.listener.VimInsertListener import com.maddyhome.idea.vim.listener.VimInsertListener
import com.maddyhome.idea.vim.newapi.IjEditorExecutionContext
import com.maddyhome.idea.vim.newapi.IjVimCaret 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.IjVimEditor
import com.maddyhome.idea.vim.newapi.ij import com.maddyhome.idea.vim.newapi.ij
import com.maddyhome.idea.vim.regexp.VimRegex
import com.maddyhome.idea.vim.regexp.match.VimMatchResult
import com.maddyhome.idea.vim.state.mode.Mode import com.maddyhome.idea.vim.state.mode.Mode
import com.maddyhome.idea.vim.state.mode.Mode.VISUAL import com.maddyhome.idea.vim.undo.VimKeyBasedUndoService
import com.maddyhome.idea.vim.state.mode.SelectionType import com.maddyhome.idea.vim.undo.VimTimestampBasedUndoService
import com.maddyhome.idea.vim.vimscript.model.commands.SortOption
import org.jetbrains.annotations.TestOnly
import java.math.BigInteger
import java.util.*
import java.util.function.Consumer
import kotlin.math.max
/** /**
* Provides all the insert/replace related functionality * Provides all the insert/replace related functionality
*/ */
class ChangeGroup : VimChangeGroupBase() { class ChangeGroup : VimChangeGroupBase() {
private val insertListeners = ContainerUtil.createLockFreeCopyOnWriteList<VimInsertListener>()
private val listener: EditorMouseListener = object : EditorMouseListener { private val listener: EditorMouseListener = object : EditorMouseListener {
override fun mouseClicked(event: EditorMouseEvent) { override fun mouseClicked(event: EditorMouseEvent) {
val editor = event.editor val editor = event.editor
@@ -103,9 +64,15 @@ class ChangeGroup : VimChangeGroupBase() {
val editor = (vimEditor as IjVimEditor).editor val editor = (vimEditor as IjVimEditor).editor
val ijContext = context.ij val ijContext = context.ij
val doc = vimEditor.editor.document val doc = vimEditor.editor.document
val undo = injector.undo val undo = injector.undo
val nanoTime = System.nanoTime() when (undo) {
vimEditor.forEachCaret { undo.startInsertSequence(it, it.offset, nanoTime) } is VimKeyBasedUndoService -> undo.setInsertNonMergeUndoKey()
is VimTimestampBasedUndoService -> {
val nanoTime = System.nanoTime()
vimEditor.forEachCaret { undo.startInsertSequence(it, it.offset, nanoTime) }
}
}
CommandProcessor.getInstance().executeCommand( CommandProcessor.getInstance().executeCommand(
editor.project, { editor.project, {
ApplicationManager.getApplication() ApplicationManager.getApplication()
@@ -145,161 +112,9 @@ class ChangeGroup : VimChangeGroupBase() {
} }
} }
override fun getDeleteRangeAndType2( override fun processBackspace(editor: VimEditor, context: ExecutionContext) {
editor: VimEditor, injector.actionExecutor.executeAction(editor, name = IdeActions.ACTION_EDITOR_BACKSPACE, context = context)
caret: VimCaret, injector.scroll.scrollCaretIntoView(editor)
context: ExecutionContext,
argument: Argument,
isChange: Boolean,
operatorArguments: OperatorArguments,
): Pair<TextRange, SelectionType>? {
val range = getMotionRange2(
(editor as IjVimEditor).editor,
(caret as IjVimCaret).caret,
(context as IjEditorExecutionContext).context,
argument,
operatorArguments
)
?: return null
// Delete motion commands that are not linewise become linewise if all the following are true:
// 1) The range is across multiple lines
// 2) There is only whitespace before the start of the range
// 3) There is only whitespace after the end of the range
var type: SelectionType
type = if (argument.motion.isLinewiseMotion()) {
SelectionType.LINE_WISE
} else {
SelectionType.CHARACTER_WISE
}
val motion = argument.motion
if (!isChange && !motion.isLinewiseMotion()) {
val start = editor.offsetToBufferPosition(range.startOffset)
val end = editor.offsetToBufferPosition(range.endOffset)
if (start.line != end.line) {
val offset1 = range.startOffset
if (!editor.anyNonWhitespace(offset1, -1)) {
val offset = range.endOffset
if (!editor.anyNonWhitespace(offset, 1)) {
type = SelectionType.LINE_WISE
}
}
}
}
return Pair(range, type)
}
/**
* Toggles the case of count characters
*
* @param editor The editor to change
* @param caret The caret on which the operation is performed
* @param count The number of characters to change
* @return true if able to change count characters
*/
override fun changeCaseToggleCharacter(editor: VimEditor, caret: VimCaret, count: Int): Boolean {
val allowWrap = injector.options(editor).whichwrap.contains("~")
var motion = injector.motion.getHorizontalMotion(editor, caret, count, true, allowWrap)
if (motion is Motion.Error) return false
changeCase(editor, caret, caret.offset, (motion as AbsoluteOffset).offset, CharacterHelper.CASE_TOGGLE)
motion = injector.motion.getHorizontalMotion(
editor,
caret,
count,
false,
allowWrap
) // same but without allow end because we can change till end, but can't move caret there
if (motion is AbsoluteOffset) {
caret.moveToOffset(editor.normalizeOffset(motion.offset, false))
}
return true
}
override fun blockInsert(
editor: VimEditor,
context: ExecutionContext,
range: TextRange,
append: Boolean,
operatorArguments: OperatorArguments,
): Boolean {
val lines = getLinesCountInVisualBlock(editor, range)
val startPosition = editor.offsetToBufferPosition(range.startOffset)
val mode = operatorArguments.mode
val visualBlockMode = mode is VISUAL && mode.selectionType === SelectionType.BLOCK_WISE
for (caret in editor.carets()) {
val line = startPosition.line
var column = startPosition.column
if (!visualBlockMode) {
column = 0
} else if (append) {
column += range.maxLength
if (caret.vimLastColumn == VimMotionGroupBase.LAST_COLUMN) {
column = VimMotionGroupBase.LAST_COLUMN
}
}
val lineLength = editor.lineLength(line)
if (column < VimMotionGroupBase.LAST_COLUMN && lineLength < column) {
val pad = EditorHelper.pad((editor as IjVimEditor).editor, line, column)
val offset = editor.getLineEndOffset(line)
insertText(editor, caret, offset, pad)
}
if (visualBlockMode || !append) {
(caret as IjVimCaret).caret.moveToInlayAwareLogicalPosition(LogicalPosition(line, column))
}
if (visualBlockMode) {
setInsertRepeat(lines, column, append)
}
}
if (visualBlockMode || !append) {
insertBeforeCursor(editor, context)
} else {
insertAfterCursor(editor, context)
}
return true
}
/**
* Changes the case of all the characters in the range
*
* @param editor The editor to change
* @param caret The caret to be moved
* @param range The range to change
* @param type The case change type (TOGGLE, UPPER, LOWER)
* @return true if able to delete the text, false if not
*/
override fun changeCaseRange(editor: VimEditor, caret: VimCaret, range: TextRange, type: Char): Boolean {
val starts = range.startOffsets
val ends = range.endOffsets
for (i in ends.indices.reversed()) {
changeCase(editor, caret, starts[i], ends[i], type)
}
caret.moveToOffset(range.startOffset)
return true
}
/**
* This performs the actual case change.
*
* @param editor The editor to change
* @param start The start offset to change
* @param end The end offset to change
* @param type The type of change (TOGGLE, UPPER, LOWER)
*/
private fun changeCase(editor: VimEditor, caret: VimCaret, start: Int, end: Int, type: Char) {
var start = start
var end = end
if (start > end) {
val t = end
end = start
start = t
}
end = editor.normalizeOffset(end, true)
val chars = editor.text()
val sb = StringBuilder()
for (i in start until end) {
sb.append(changeCase(chars[i], type))
}
replaceText(editor, caret, start, end, sb.toString())
} }
private fun restoreCursor(editor: VimEditor, caret: VimCaret, startLine: Int) { private fun restoreCursor(editor: VimEditor, caret: VimCaret, startLine: Int) {
@@ -310,88 +125,13 @@ class ChangeGroup : VimChangeGroupBase() {
} }
} }
/** override fun reformatCode(editor: VimEditor, start: Int, end: Int) {
* Changes the case of all the character moved over by the motion argument.
*
* @param editor The editor to change
* @param caret The caret on which motion pretends to be performed
* @param context The data context
* @param type The case change type (TOGGLE, UPPER, LOWER)
* @param argument The motion command
* @return true if able to delete the text, false if not
*/
override fun changeCaseMotion(
editor: VimEditor,
caret: VimCaret,
context: ExecutionContext?,
type: Char,
argument: Argument,
operatorArguments: OperatorArguments,
): Boolean {
val range = injector.motion.getMotionRange(
editor, caret, context!!, argument,
operatorArguments
)
return range != null && changeCaseRange(editor, caret, range, type)
}
override fun reformatCodeMotion(
editor: VimEditor,
caret: VimCaret,
context: ExecutionContext,
argument: Argument,
operatorArguments: OperatorArguments,
): Boolean {
val range = injector.motion.getMotionRange(
editor, caret, context, argument,
operatorArguments
)
return range != null && reformatCodeRange(editor, caret, range)
}
override fun reformatCodeSelection(editor: VimEditor, caret: VimCaret, range: VimSelection) {
val textRange = range.toVimTextRange(true)
reformatCodeRange(editor, caret, textRange)
}
private fun reformatCodeRange(editor: VimEditor, caret: VimCaret, range: TextRange): Boolean {
val starts = range.startOffsets
val ends = range.endOffsets
val firstLine = editor.offsetToBufferPosition(range.startOffset).line
for (i in ends.indices.reversed()) {
val startOffset = editor.getLineStartForOffset(starts[i])
val offset = ends[i] - if (startOffset == ends[i]) 0 else 1
val endOffset = editor.getLineEndForOffset(offset)
reformatCode(editor, startOffset, endOffset)
}
val newOffset = injector.motion.moveCaretToLineStartSkipLeading(editor, firstLine)
caret.moveToOffset(newOffset)
return true
}
private fun reformatCode(editor: VimEditor, start: Int, end: Int) {
val project = (editor as IjVimEditor).editor.project ?: return val project = (editor as IjVimEditor).editor.project ?: return
val file = PsiUtilBase.getPsiFileInEditor(editor.editor, project) ?: return val file = PsiUtilBase.getPsiFileInEditor(editor.editor, project) ?: return
val textRange = com.intellij.openapi.util.TextRange.create(start, end) val textRange = com.intellij.openapi.util.TextRange.create(start, end)
CodeStyleManager.getInstance(project).reformatText(file, listOf(textRange)) CodeStyleManager.getInstance(project).reformatText(file, listOf(textRange))
} }
override fun autoIndentMotion(
editor: VimEditor,
caret: VimCaret,
context: ExecutionContext,
argument: Argument,
operatorArguments: OperatorArguments,
) {
val range = injector.motion.getMotionRange(editor, caret, context, argument, operatorArguments)
if (range != null) {
autoIndentRange(
editor, caret, context,
TextRange(range.startOffset, range.endOffsetInclusive)
)
}
}
override fun autoIndentRange( override fun autoIndentRange(
editor: VimEditor, editor: VimEditor,
caret: VimCaret, caret: VimCaret,
@@ -405,10 +145,10 @@ class ChangeGroup : VimChangeGroupBase() {
// FIXME: Here we do selection, and it is not a good idea, because it updates primary selection in Linux // 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 // 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 { try {
if (injector.registerGroup.isPrimaryRegisterSupported()) { if (injector.registerGroup.isPrimaryRegisterSupported()) {
primaryTextAndTransferableData = injector.clipboardManager.getPrimaryTextAndTransferableData() copiedText = injector.clipboardManager.getPrimaryContent(editor, context) as IjVimCopiedText
} }
} catch (e: Exception) { } catch (e: Exception) {
// FIXME: [isPrimaryRegisterSupported()] is not implemented perfectly, so there might be thrown an exception after trying to access the primary selection // FIXME: [isPrimaryRegisterSupported()] is not implemented perfectly, so there might be thrown an exception after trying to access the primary selection
@@ -434,373 +174,22 @@ class ChangeGroup : VimChangeGroupBase() {
afterAction.invoke() afterAction.invoke()
} }
try { try {
if (primaryTextAndTransferableData != null) { if (copiedText != null) {
injector.clipboardManager.setPrimaryText( injector.clipboardManager.setPrimaryContent(editor, context, copiedText)
primaryTextAndTransferableData.first,
primaryTextAndTransferableData.first,
primaryTextAndTransferableData.second ?: emptyList()
)
} }
} catch (e: Exception) { } catch (e: Exception) {
// FIXME: [isPrimaryRegisterSupported()] is not implemented perfectly, so there might be thrown an exception after trying to access the primary selection // FIXME: [isPrimaryRegisterSupported()] is not implemented perfectly, so there might be thrown an exception after trying to access the primary selection
} }
} }
override fun indentLines( @Deprecated(message = "Please use listenersNotifier", replaceWith = ReplaceWith("injector.listenersNotifier.modeChangeListeners.add", imports = ["import com.maddyhome.idea.vim.api.injector"]))
editor: VimEditor,
caret: VimCaret,
context: ExecutionContext,
lines: Int,
dir: Int,
operatorArguments: OperatorArguments,
) {
val start = caret.offset
val end = injector.motion.moveCaretToRelativeLineEnd(editor, caret, lines - 1, true)
indentRange(editor, caret, context, TextRange(start, end), 1, dir, operatorArguments)
}
override fun indentMotion(
editor: VimEditor,
caret: VimCaret,
context: ExecutionContext,
argument: Argument,
dir: Int,
operatorArguments: OperatorArguments,
) {
val range = injector.motion.getMotionRange(editor, caret, context, argument, operatorArguments)
if (range != null) {
indentRange(editor, caret, context, range, 1, dir, operatorArguments)
}
}
override fun indentRange(
editor: VimEditor,
caret: VimCaret,
context: ExecutionContext,
range: TextRange,
count: Int,
dir: Int,
operatorArguments: OperatorArguments,
) {
if (logger.isDebugEnabled) {
logger.debug("count=$count")
}
// Remember the current caret column
val intendedColumn = caret.vimLastColumn
val indentConfig = create((editor as IjVimEditor).editor)
val sline = editor.offsetToBufferPosition(range.startOffset).line
val endLogicalPosition = editor.offsetToBufferPosition(range.endOffset)
val eline = if (endLogicalPosition.column == 0) max((endLogicalPosition.line - 1).toDouble(), 0.0)
.toInt() else endLogicalPosition.line
if (range.isMultiple) {
val from = editor.offsetToBufferPosition(range.startOffset).column
if (dir == 1) {
// Right shift blockwise selection
val indent = indentConfig.createIndentByCount(count)
for (l in sline..eline) {
val len = editor.lineLength(l)
if (len > from) {
val spos = BufferPosition(l, from, false)
insertText(editor, caret, spos, indent)
}
}
} else {
// Left shift blockwise selection
val chars = editor.text()
for (l in sline..eline) {
val len = editor.lineLength(l)
if (len > from) {
val spos = BufferPosition(l, from, false)
val epos = BufferPosition(l, from + indentConfig.getTotalIndent(count) - 1, false)
val wsoff = editor.bufferPositionToOffset(spos)
val weoff = editor.bufferPositionToOffset(epos)
var pos: Int
pos = wsoff
while (pos <= weoff) {
if (charType(editor, chars[pos], false) !== CharacterHelper.CharacterType.WHITESPACE) {
break
}
pos++
}
if (pos > wsoff) {
deleteText(editor, TextRange(wsoff, pos), null, caret, operatorArguments, true)
}
}
}
}
} else {
// Shift non-blockwise selection
for (l in sline..eline) {
val soff = editor.getLineStartOffset(l)
val eoff = editor.getLineEndOffset(l, true)
val woff = injector.motion.moveCaretToLineStartSkipLeading(editor, l)
val col = editor.offsetToBufferPosition(woff).column
val limit = max(0.0, (col + dir * indentConfig.getTotalIndent(count)).toDouble())
.toInt()
if (col > 0 || soff != eoff) {
val indent = indentConfig.createIndentBySize(limit)
replaceText(editor, caret, soff, woff, indent)
}
}
}
if (!editor.editor.inInsertMode) {
if (!range.isMultiple) {
// The caret has moved, so reset the intended column before trying to get the expected offset
val newCaret = caret.setVimLastColumnAndGetCaret(intendedColumn)
val offset = injector.motion.moveCaretToLineWithStartOfLineOption(editor, sline, caret)
newCaret.moveToOffset(offset)
} else {
caret.moveToOffset(range.startOffset)
}
}
}
/**
* Sort range of text with a given comparator
*
* @param editor The editor to replace text in
* @param range The range to sort
* @param lineComparator The comparator to use to sort
* @param sortOptions The option to sort the range
* @return true if able to sort the text, false if not
*/
override fun sortRange(
editor: VimEditor, caret: VimCaret, range: LineRange, lineComparator: Comparator<String>,
sortOptions: SortOption,
): Boolean {
val startLine = range.startLine
val endLine = range.endLine
val count = range.size
if (count < 2) {
return false
}
val startOffset = editor.getLineStartOffset(startLine)
val endOffset = editor.getLineEndOffset(endLine)
val selectedText = (editor as IjVimEditor).editor.document.getText(TextRangeInterval(startOffset, endOffset))
val lines = selectedText.split("\n")
val modifiedLines = sortOptions.pattern?.let {
if (sortOptions.sortOnPattern) {
extractPatternFromLines(editor, lines, startLine, it)
} else {
deletePatternFromLines(editor, lines, startLine, it)
}
} ?: lines
val sortedLines = lines.zip(modifiedLines)
.sortedWith { l1, l2 -> lineComparator.compare(l1.second, l2.second) }
.map {it.first}
.toMutableList()
if (sortOptions.unique) {
val iterator = sortedLines.iterator()
var previous: String? = null
while (iterator.hasNext()) {
val current = iterator.next()
if (current == previous || sortOptions.ignoreCase && current.equals(previous, ignoreCase = true)) {
iterator.remove()
} else {
previous = current
}
}
}
if (sortedLines.isEmpty()) {
return false
}
replaceText(editor, caret, startOffset, endOffset, StringUtil.join(sortedLines, "\n"))
return true
}
private fun extractPatternFromLines(editor: VimEditor, lines: List<String>, startLine: Int, pattern: String): List<String> {
val regex = VimRegex(pattern)
return lines.mapIndexed { i: Int, line: String ->
val result = regex.findInLine(editor, startLine + i, 0)
when (result) {
is VimMatchResult.Success -> result.value
is VimMatchResult.Failure -> line
}
}
}
private fun deletePatternFromLines(editor: VimEditor, lines: List<String>, startLine: Int, pattern: String): List<String> {
val regex = VimRegex(pattern)
return lines.mapIndexed { i: Int, line: String ->
val result = regex.findInLine(editor, startLine + i, 0)
when (result) {
is VimMatchResult.Success -> line.substring(result.value.length, line.length)
is VimMatchResult.Failure -> line
}
}
}
/**
* Perform increment and decrement for numbers in visual mode
*
*
* Flag [avalanche] marks if increment (or decrement) should be performed in avalanche mode
* (for v_g_Ctrl-A and v_g_Ctrl-X commands)
*
* @return true
*/
override fun changeNumberVisualMode(
editor: VimEditor,
caret: VimCaret,
selectedRange: TextRange,
count: Int,
avalanche: Boolean,
): Boolean {
val nf: List<String> = injector.options(editor).nrformats
val alpha = nf.contains("alpha")
val hex = nf.contains("hex")
val octal = nf.contains("octal")
val numberRanges = findNumbersInRange((editor as IjVimEditor).editor, selectedRange, alpha, hex, octal)
val newNumbers: MutableList<String?> = ArrayList()
for (i in numberRanges.indices) {
val numberRange = numberRanges[i]
val iCount = if (avalanche) (i + 1) * count else count
val newNumber = changeNumberInRange(editor, numberRange, iCount, alpha, hex, octal)
newNumbers.add(newNumber)
}
for (i in newNumbers.indices.reversed()) {
// Replace text bottom up. In other direction ranges will be desynchronized after inc numbers like 99
val (first) = numberRanges[i]
val newNumber = newNumbers[i]
replaceText(editor, caret, first.startOffset, first.endOffset, newNumber!!)
}
(caret as IjVimCaret).caret.moveToInlayAwareOffset(selectedRange.startOffset)
return true
}
override fun changeNumber(editor: VimEditor, caret: VimCaret, count: Int): Boolean {
val nf: List<String> = injector.options(editor).nrformats
val alpha = nf.contains("alpha")
val hex = nf.contains("hex")
val octal = nf.contains("octal")
val range = findNumberUnderCursor((editor as IjVimEditor).editor, (caret as IjVimCaret).caret, alpha, hex, octal)
if (range == null) {
logger.debug("no number on line")
return false
}
val newNumber = changeNumberInRange(editor, range, count, alpha, hex, octal)
return if (newNumber == null) {
false
} else {
replaceText(editor, caret, range.first.startOffset, range.first.endOffset, newNumber)
caret.caret.moveToInlayAwareOffset(range.first.startOffset + newNumber.length - 1)
true
}
}
override fun reset() {
strokes.clear()
repeatCharsCount = 0
if (lastStrokes != null) {
lastStrokes!!.clear()
}
}
override fun saveStrokes(newStrokes: String?) {
val chars = newStrokes!!.toCharArray()
strokes.add(chars)
}
private fun changeNumberInRange(
editor: VimEditor,
range: Pair<TextRange, NumberType>,
count: Int,
alpha: Boolean,
hex: Boolean,
octal: Boolean,
): String? {
val text = editor.getText(range.first)
val numberType = range.second
if (logger.isDebugEnabled) {
logger.debug("found range $range")
logger.debug("text=$text")
}
var number = text
if (text.isEmpty()) {
return null
}
var ch = text[0]
if (hex && NumberType.HEX == numberType) {
if (!text.lowercase(Locale.getDefault()).startsWith(HEX_START)) {
throw RuntimeException("Hex number should start with 0x: $text")
}
for (i in text.length - 1 downTo 2) {
val index = "abcdefABCDEF".indexOf(text[i])
if (index >= 0) {
lastLower = index < 6
break
}
}
var num = BigInteger(text.substring(2), 16)
num = num.add(BigInteger.valueOf(count.toLong()))
if (num.compareTo(BigInteger.ZERO) < 0) {
num = BigInteger(MAX_HEX_INTEGER, 16).add(BigInteger.ONE).add(num)
}
number = num.toString(16)
number = number.padStart(text.length - 2, '0')
if (!lastLower) {
number = number.uppercase(Locale.getDefault())
}
number = text.substring(0, 2) + number
} else if (octal && NumberType.OCT == numberType && text.length > 1) {
if (!text.startsWith("0")) throw RuntimeException("Oct number should start with 0: $text")
var num = BigInteger(text, 8).add(BigInteger.valueOf(count.toLong()))
if (num.compareTo(BigInteger.ZERO) < 0) {
num = BigInteger("1777777777777777777777", 8).add(BigInteger.ONE).add(num)
}
number = num.toString(8)
number = "0" + number.padStart(text.length - 1, '0')
} else if (alpha && NumberType.ALPHA == numberType) {
if (!Character.isLetter(ch)) throw RuntimeException("Not alpha number : $text")
ch += count.toChar().code
if (Character.isLetter(ch)) {
number = ch.toString()
}
} else if (NumberType.DEC == numberType) {
if (ch != '-' && !Character.isDigit(ch)) throw RuntimeException("Not dec number : $text")
var pad = ch == '0'
var len = text.length
if (ch == '-' && text[1] == '0') {
pad = true
len--
}
var num = BigInteger(text)
num = num.add(BigInteger.valueOf(count.toLong()))
number = num.toString()
if (!octal && pad) {
var neg = false
if (number[0] == '-') {
neg = true
number = number.substring(1)
}
number = number.padStart(len, '0')
if (neg) {
number = "-$number"
}
}
}
return number
}
fun addInsertListener(listener: VimInsertListener) { fun addInsertListener(listener: VimInsertListener) {
insertListeners.add(listener) injector.listenersNotifier.modeChangeListeners.add(listener)
} }
@Deprecated(message = "Please use listenersNotifier", replaceWith = ReplaceWith("injector.listenersNotifier.modeChangeListeners.remove", imports = ["import com.maddyhome.idea.vim.api.injector"]))
fun removeInsertListener(listener: VimInsertListener) { fun removeInsertListener(listener: VimInsertListener) {
insertListeners.remove(listener) injector.listenersNotifier.modeChangeListeners.remove(listener)
}
override fun notifyListeners(editor: VimEditor) {
insertListeners.forEach(Consumer { listener: VimInsertListener -> listener.insertModeStarted((editor as IjVimEditor).editor) })
}
@TestOnly
override fun resetRepeat() {
setInsertRepeat(0, 0, false)
} }
private companion object { private companion object {

View File

@@ -1,78 +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.ex.ExOutputModel;
import com.maddyhome.idea.vim.helper.EditorHelper;
import com.maddyhome.idea.vim.newapi.IjVimEditor;
import org.jetbrains.annotations.NotNull;
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;
}
}
ExOutputModel.getInstance(((IjVimEditor) editor).getEditor()).output(res.toString());
}
private static final Logger logger = Logger.getInstance(DigraphGroup.class.getName());
}

View File

@@ -8,9 +8,7 @@
package com.maddyhome.idea.vim.group; package com.maddyhome.idea.vim.group;
import com.intellij.execution.impl.ConsoleViewImpl;
import com.intellij.find.EditorSearchSession; import com.intellij.find.EditorSearchSession;
import com.intellij.openapi.application.ApplicationManager;
import com.intellij.openapi.client.ClientAppSession; import com.intellij.openapi.client.ClientAppSession;
import com.intellij.openapi.client.ClientKind; import com.intellij.openapi.client.ClientKind;
import com.intellij.openapi.client.ClientSessionsManager; import com.intellij.openapi.client.ClientSessionsManager;
@@ -23,12 +21,10 @@ import com.intellij.openapi.editor.event.CaretListener;
import com.intellij.openapi.editor.ex.EditorEx; import com.intellij.openapi.editor.ex.EditorEx;
import com.intellij.openapi.editor.ex.EditorGutterComponentEx; import com.intellij.openapi.editor.ex.EditorGutterComponentEx;
import com.intellij.openapi.project.Project; import com.intellij.openapi.project.Project;
import com.maddyhome.idea.vim.KeyHandler;
import com.maddyhome.idea.vim.VimPlugin; import com.maddyhome.idea.vim.VimPlugin;
import com.maddyhome.idea.vim.api.*; import com.maddyhome.idea.vim.api.*;
import com.maddyhome.idea.vim.ex.ExOutputModel; import com.maddyhome.idea.vim.ex.ExOutputModel;
import com.maddyhome.idea.vim.helper.CaretVisualAttributesHelperKt; import com.maddyhome.idea.vim.helper.CaretVisualAttributesHelperKt;
import com.maddyhome.idea.vim.helper.CommandStateHelper;
import com.maddyhome.idea.vim.helper.EditorHelper; import com.maddyhome.idea.vim.helper.EditorHelper;
import com.maddyhome.idea.vim.helper.UserDataManager; import com.maddyhome.idea.vim.helper.UserDataManager;
import com.maddyhome.idea.vim.newapi.IjVimDocument; import com.maddyhome.idea.vim.newapi.IjVimDocument;
@@ -218,7 +214,9 @@ public class EditorGroup implements PersistentStateComponent<Element>, VimEditor
editorEx.addPropertyChangeListener(FontSizeChangeListener.INSTANCE); editorEx.addPropertyChangeListener(FontSizeChangeListener.INSTANCE);
} }
updateCaretsVisualAttributes(new IjVimEditor(editor)); if (injector.getApplication().isUnitTest()) {
updateCaretsVisualAttributes(new IjVimEditor(editor));
}
} }
public void editorDeinit(@NotNull Editor editor) { public void editorDeinit(@NotNull Editor editor) {
@@ -240,9 +238,8 @@ public class EditorGroup implements PersistentStateComponent<Element>, VimEditor
VimPlugin.getNotifications(project).notifyAboutIdeaJoin(editor); VimPlugin.getNotifications(project).notifyAboutIdeaJoin(editor);
} }
@Nullable
@Override @Override
public Element getState() { public @Nullable Element getState() {
Element element = new Element("editor"); Element element = new Element("editor");
saveData(element); saveData(element);
return element; return element;
@@ -318,18 +315,16 @@ public class EditorGroup implements PersistentStateComponent<Element>, VimEditor
.collect(Collectors.toList()); .collect(Collectors.toList());
} }
@NotNull
@Override @Override
public Collection<VimEditor> getEditors() { public @NotNull Collection<VimEditor> getEditors() {
return getLocalEditors() return getLocalEditors()
.filter(UserDataManager::getVimInitialised) .filter(UserDataManager::getVimInitialised)
.map(IjVimEditor::new) .map(IjVimEditor::new)
.collect(Collectors.toList()); .collect(Collectors.toList());
} }
@NotNull
@Override @Override
public Collection<VimEditor> getEditors(@NotNull VimDocument buffer) { public @NotNull Collection<VimEditor> getEditors(@NotNull VimDocument buffer) {
final Document document = ((IjVimDocument)buffer).getDocument(); final Document document = ((IjVimDocument)buffer).getDocument();
return getLocalEditors() return getLocalEditors()
.filter(editor -> UserDataManager.getVimInitialised(editor) && editor.getDocument().equals(document)) .filter(editor -> UserDataManager.getVimInitialised(editor) && editor.getDocument().equals(document))
@@ -349,7 +344,7 @@ public class EditorGroup implements PersistentStateComponent<Element>, VimEditor
// events such as document change (to update search highlights), and these can come from CWM guests, and we'd get // events such as document change (to update search highlights), and these can come from CWM guests, and we'd get
// the remote editors. // the remote editors.
// This invocation will always get local editors, regardless of the current context. // This invocation will always get local editors, regardless of the current context.
List<ClientAppSession> appSessions = ClientSessionsManager.getAppSessions(ClientKind.ALL); List<ClientAppSession> appSessions = ClientSessionsManager.getAppSessions(ClientKind.LOCAL);
if (!appSessions.isEmpty()) { if (!appSessions.isEmpty()) {
ClientAppSession localSession = appSessions.get(0); ClientAppSession localSession = appSessions.get(0);
return localSession.getService(ClientEditorManager.class).editors(); return localSession.getService(ClientEditorManager.class).editors();
@@ -377,11 +372,15 @@ public class EditorGroup implements PersistentStateComponent<Element>, VimEditor
// Note that IDE scale is handled by LafManager.lookAndFeelChanged // Note that IDE scale is handled by LafManager.lookAndFeelChanged
VimCommandLine activeCommandLine = injector.getCommandLine().getActiveCommandLine(); VimCommandLine activeCommandLine = injector.getCommandLine().getActiveCommandLine();
if (activeCommandLine != null) { if (activeCommandLine != null) {
injector.getProcessGroup().cancelExEntry(new IjVimEditor(editor), true, false); activeCommandLine.close(true, false);
} }
ExOutputModel exOutputModel = ExOutputModel.tryGetInstance(editor); VimOutputPanel outputPanel = injector.getOutputPanel().getCurrentOutputPanel();
if (exOutputModel != null) { if (outputPanel != null) {
exOutputModel.close(); outputPanel.close();
}
VimModalInput modalInput = injector.getModalInput().getCurrentModalInput();
if (modalInput != null) {
modalInput.deactivate(true, false);
} }
} }
} }

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.IjVimEditor
import com.maddyhome.idea.vim.newapi.execute import com.maddyhome.idea.vim.newapi.execute
import com.maddyhome.idea.vim.newapi.globalIjOptions 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 com.maddyhome.idea.vim.state.mode.Mode.VISUAL
import java.io.File import java.io.File
import java.util.* import java.util.*
@@ -173,20 +172,20 @@ class FileGroup : VimFileBase() {
/** /**
* Saves specific file in the project. * Saves specific file in the project.
*/ */
override fun saveFile(context: ExecutionContext) { override fun saveFile(editor: VimEditor, context: ExecutionContext) {
val action = if (injector.globalIjOptions().ideawrite.contains(IjOptionConstants.ideawrite_all)) { val action = if (injector.globalIjOptions().ideawrite.contains(IjOptionConstants.ideawrite_all)) {
injector.nativeActionManager.saveAll injector.nativeActionManager.saveAll
} else { } else {
injector.nativeActionManager.saveCurrent injector.nativeActionManager.saveCurrent
} }
action.execute(context) action.execute(editor, context)
} }
/** /**
* Saves all files in the project. * Saves all files in the project.
*/ */
override fun saveFiles(context: ExecutionContext) { override fun saveFiles(editor: VimEditor, context: ExecutionContext) {
injector.nativeActionManager.saveAll.execute(context) injector.nativeActionManager.saveAll.execute(editor, context)
} }
/** /**

View File

@@ -14,9 +14,7 @@ import com.intellij.openapi.components.State;
import com.intellij.openapi.components.Storage; import com.intellij.openapi.components.Storage;
import com.intellij.openapi.diagnostic.Logger; import com.intellij.openapi.diagnostic.Logger;
import com.maddyhome.idea.vim.VimPlugin; import com.maddyhome.idea.vim.VimPlugin;
import com.maddyhome.idea.vim.history.HistoryBlock; import com.maddyhome.idea.vim.history.*;
import com.maddyhome.idea.vim.history.HistoryEntry;
import com.maddyhome.idea.vim.history.VimHistoryBase;
import org.jdom.Element; import org.jdom.Element;
import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable; import org.jetbrains.annotations.Nullable;
@@ -35,21 +33,20 @@ public class HistoryGroup extends VimHistoryBase implements PersistentStateCompo
logger.debug("saveData"); logger.debug("saveData");
Element hist = new Element("history"); Element hist = new Element("history");
saveData(hist, SEARCH); for (Type type : getHistories().keySet()) {
saveData(hist, COMMAND); saveData(hist, type);
saveData(hist, EXPRESSION); }
saveData(hist, INPUT);
element.addContent(hist); element.addContent(hist);
} }
private void saveData(@NotNull Element element, String key) { private void saveData(@NotNull Element element, VimHistory.Type type) {
final HistoryBlock block = getHistories().get(key); final HistoryBlock block = getHistories().get(type);
if (block == null) { if (block == null) {
return; return;
} }
final Element root = new Element("history-" + key); final Element root = new Element("history-" + typeToKey(type));
for (HistoryEntry entry : block.getEntries()) { for (HistoryEntry entry : block.getEntries()) {
final Element entryElement = new Element("entry"); final Element entryElement = new Element("entry");
@@ -67,10 +64,10 @@ public class HistoryGroup extends VimHistoryBase implements PersistentStateCompo
return; return;
} }
readData(hist, SEARCH); for (Element child : hist.getChildren()) {
readData(hist, COMMAND); String key = child.getName().replace("history-", "");
readData(hist, EXPRESSION); readData(hist, key);
readData(hist, INPUT); }
} }
private void readData(@NotNull Element element, String key) { private void readData(@NotNull Element element, String key) {
@@ -80,7 +77,7 @@ public class HistoryGroup extends VimHistoryBase implements PersistentStateCompo
} }
block = new HistoryBlock(); block = new HistoryBlock();
getHistories().put(key, block); getHistories().put(getTypeForString(key), block);
final Element root = element.getChild("history-" + key); final Element root = element.getChild("history-" + key);
if (root != null) { if (root != null) {
@@ -94,6 +91,25 @@ public class HistoryGroup extends VimHistoryBase implements PersistentStateCompo
} }
} }
private String typeToKey(VimHistory.Type type) {
if (type instanceof VimHistory.Type.Search) {
return SEARCH;
}
if (type instanceof VimHistory.Type.Command) {
return COMMAND;
}
if (type instanceof VimHistory.Type.Expression) {
return EXPRESSION;
}
if (type instanceof VimHistory.Type.Input) {
return INPUT;
}
if (type instanceof VimHistory.Type.Custom) {
return ((Type.Custom) type).getId();
}
return "unreachable";
}
@Nullable @Nullable
@Override @Override
public Element getState() { public Element getState() {

View File

@@ -26,7 +26,7 @@ class IjVimPsiService: VimPsiService {
val psiFile = PsiHelper.getFile(editor.ij) ?: return null val psiFile = PsiHelper.getFile(editor.ij) ?: return null
val psiElement = psiFile.findElementAt(pos) ?: return null val psiElement = psiFile.findElementAt(pos) ?: return null
val language = psiElement.language val language = psiElement.language
val commenter = LanguageCommenters.INSTANCE.forLanguage(language) val commenter = LanguageCommenters.INSTANCE.forLanguage(language) ?: return null
val psiComment = PsiTreeUtil.getParentOfType(psiElement, PsiComment::class.java, false) ?: return null val psiComment = PsiTreeUtil.getParentOfType(psiElement, PsiComment::class.java, false) ?: return null
val commentText = psiComment.text val commentText = psiComment.text

View File

@@ -8,7 +8,6 @@
package com.maddyhome.idea.vim.group; package com.maddyhome.idea.vim.group;
import com.google.common.collect.ImmutableList;
import com.intellij.codeInsight.lookup.impl.LookupImpl; import com.intellij.codeInsight.lookup.impl.LookupImpl;
import com.intellij.openapi.actionSystem.*; import com.intellij.openapi.actionSystem.*;
import com.intellij.openapi.actionSystem.ex.ActionManagerEx; import com.intellij.openapi.actionSystem.ex.ActionManagerEx;
@@ -17,21 +16,16 @@ import com.intellij.openapi.application.ApplicationManager;
import com.intellij.openapi.components.PersistentStateComponent; import com.intellij.openapi.components.PersistentStateComponent;
import com.intellij.openapi.components.State; import com.intellij.openapi.components.State;
import com.intellij.openapi.components.Storage; 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.Keymap;
import com.intellij.openapi.keymap.KeymapManager; import com.intellij.openapi.keymap.KeymapManager;
import com.intellij.openapi.keymap.ex.KeymapManagerEx; import com.intellij.openapi.keymap.ex.KeymapManagerEx;
import com.intellij.util.containers.MultiMap;
import com.maddyhome.idea.vim.EventFacade; import com.maddyhome.idea.vim.EventFacade;
import com.maddyhome.idea.vim.VimPlugin; import com.maddyhome.idea.vim.VimPlugin;
import com.maddyhome.idea.vim.action.VimShortcutKeyAction; import com.maddyhome.idea.vim.action.VimShortcutKeyAction;
import com.maddyhome.idea.vim.action.change.LazyVimCommand; import com.maddyhome.idea.vim.action.change.LazyVimCommand;
import com.maddyhome.idea.vim.api.NativeAction; import com.maddyhome.idea.vim.api.*;
import com.maddyhome.idea.vim.api.VimEditor;
import com.maddyhome.idea.vim.api.VimInjectorKt;
import com.maddyhome.idea.vim.api.VimKeyGroupBase;
import com.maddyhome.idea.vim.command.MappingMode; import com.maddyhome.idea.vim.command.MappingMode;
import com.maddyhome.idea.vim.ex.ExOutputModel;
import com.maddyhome.idea.vim.key.*; import com.maddyhome.idea.vim.key.*;
import com.maddyhome.idea.vim.newapi.IjNativeAction; import com.maddyhome.idea.vim.newapi.IjNativeAction;
import com.maddyhome.idea.vim.newapi.IjVimEditor; import com.maddyhome.idea.vim.newapi.IjVimEditor;
@@ -61,8 +55,6 @@ public class KeyGroup extends VimKeyGroupBase implements PersistentStateComponen
private static final @NonNls String OWNER_ATTRIBUTE = "owner"; private static final @NonNls String OWNER_ATTRIBUTE = "owner";
private static final String TEXT_ELEMENT = "text"; private static final String TEXT_ELEMENT = "text";
private static final Logger logger = Logger.getInstance(KeyGroup.class);
public void registerRequiredShortcutKeys(@NotNull VimEditor editor) { public void registerRequiredShortcutKeys(@NotNull VimEditor editor) {
EventFacade.getInstance() EventFacade.getInstance()
.registerCustomShortcutSet(VimShortcutKeyAction.getInstance(), toShortcutSet(getRequiredShortcutKeys()), .registerCustomShortcutSet(VimShortcutKeyAction.getInstance(), toShortcutSet(getRequiredShortcutKeys()),
@@ -80,25 +72,6 @@ public class KeyGroup extends VimKeyGroupBase implements PersistentStateComponen
((IjVimEditor)editor).getEditor().getComponent()); ((IjVimEditor)editor).getEditor().getComponent());
} }
public boolean showKeyMappings(@NotNull Set<? extends MappingMode> modes, @NotNull Editor editor) {
List<Pair<EnumSet<MappingMode>, MappingInfo>> rows = getKeyMappingRows(modes);
final StringBuilder builder = new StringBuilder();
for (Pair<EnumSet<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(mappingInfo.getPresentableString());
builder.append("\n");
}
ExOutputModel.getInstance(editor).output(builder.toString());
return true;
}
@Override @Override
public void updateShortcutKeysRegistration() { public void updateShortcutKeysRegistration() {
for (VimEditor editor : injector.getEditorGroup().getEditors()) { for (VimEditor editor : injector.getEditorGroup().getEditors()) {
@@ -221,8 +194,7 @@ public class KeyGroup extends VimKeyGroupBase implements PersistentStateComponen
registerRequiredShortcut(keyStrokes, MappingOwner.IdeaVim.System.INSTANCE); registerRequiredShortcut(keyStrokes, MappingOwner.IdeaVim.System.INSTANCE);
for (MappingMode mappingMode : command.getModes()) { for (MappingMode mappingMode : command.getModes()) {
Node<LazyVimCommand> node = getKeyRoot(mappingMode); getBuiltinCommandsTrie(mappingMode).add(keyStrokes, command);
NodesKt.addLeafs(node, keyStrokes, command);
} }
} }
} }
@@ -247,53 +219,79 @@ public class KeyGroup extends VimKeyGroupBase implements PersistentStateComponen
return new CustomShortcutSet(shortcuts.toArray(new Shortcut[0])); return new CustomShortcutSet(shortcuts.toArray(new Shortcut[0]));
} }
private static @NotNull List<Pair<EnumSet<MappingMode>, MappingInfo>> getKeyMappingRows(@NotNull Set<? extends MappingMode> modes) { private static @NotNull List<Pair<Set<MappingMode>, MappingInfo>> getKeyMappingRows(@NotNull Set<? extends MappingMode> modes,
final Map<ImmutableList<KeyStroke>, EnumSet<MappingMode>> actualModes = new HashMap<>(); @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) { for (MappingMode mode : modes) {
final KeyMapping mapping = VimPlugin.getKey().getKeyMapping(mode); final KeyMapping mapping = VimPlugin.getKey().getKeyMapping(mode);
for (List<? extends KeyStroke> fromKeys : mapping) {
final ImmutableList<KeyStroke> key = ImmutableList.copyOf(fromKeys); final Iterator<KeyMappingEntry> iterator = mapping.getAll(prefix).iterator();
final EnumSet<MappingMode> value = actualModes.get(key); while (iterator.hasNext()) {
final EnumSet<MappingMode> newValue; final KeyMappingEntry entry = iterator.next();
if (value != null) { final MappingInfo mappingInfo = entry.getMappingInfo();
newValue = value.clone();
newValue.add(mode); final Set<@NotNull MappingMode> originalModes = mappingInfo.getOriginalModes();
if (originalModes.size() == 1) {
rows.add(new Pair<>(originalModes, mappingInfo));
} }
else { else {
newValue = EnumSet.of(mode); entry.collectPath(fromKeys);
} if (!multiModeMappings.get(fromKeys).contains(originalModes)) {
actualModes.put(key, newValue); multiModeMappings.putValue(new ArrayList<>(fromKeys), originalModes);
} rows.add(new Pair<>(getModesForMapping(fromKeys, originalModes), mappingInfo));
} }
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));
} }
} }
} }
rows.sort(Comparator.comparing(Pair<EnumSet<MappingMode>, MappingInfo>::getSecond)); rows.sort(Comparator.comparing(Pair<Set<MappingMode>, MappingInfo>::getSecond));
return rows; 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) { private static @NotNull @NonNls String getModesStringCode(@NotNull Set<MappingMode> modes) {
if (modes.equals(MappingMode.NVO)) { if (modes.equals(MappingMode.IC)) return "!";
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)) { else {
return "i"; if (modes.containsAll(MappingMode.X)) mode += "x";
if (modes.containsAll(MappingMode.S)) mode += "s";
} }
else if (modes.contains(MappingMode.NORMAL)) { return mode;
return "n";
}
// TODO: Add more codes
return "";
} }
private @NotNull List<AnAction> getActions(@NotNull Component component, @NotNull KeyStroke keyStroke) { private @NotNull List<AnAction> getActions(@NotNull Component component, @NotNull KeyStroke keyStroke) {
@@ -357,7 +355,23 @@ public class KeyGroup extends VimKeyGroupBase implements PersistentStateComponen
} }
@Override @Override
public boolean showKeyMappings(@NotNull Set<? extends MappingMode> modes, @NotNull VimEditor editor) { public boolean showKeyMappings(@NotNull Set<? extends MappingMode> modes, @NotNull List<? extends KeyStroke> prefix, @NotNull VimEditor editor) {
return showKeyMappings(modes, ((IjVimEditor) editor).getEditor()); List<Pair<Set<MappingMode>, MappingInfo>> rows = getKeyMappingRows(modes, prefix);
final StringBuilder builder = new StringBuilder();
for (Pair<Set<MappingMode>, MappingInfo> row : rows) {
MappingInfo mappingInfo = row.getSecond();
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();
return true;
} }
} }

View File

@@ -35,7 +35,7 @@ import com.maddyhome.idea.vim.command.Argument
import com.maddyhome.idea.vim.command.MotionType import com.maddyhome.idea.vim.command.MotionType
import com.maddyhome.idea.vim.command.OperatorArguments import com.maddyhome.idea.vim.command.OperatorArguments
import com.maddyhome.idea.vim.common.TextRange import com.maddyhome.idea.vim.common.TextRange
import com.maddyhome.idea.vim.ex.ExOutputModel import com.maddyhome.idea.vim.handler.ExternalActionHandler
import com.maddyhome.idea.vim.handler.Motion import com.maddyhome.idea.vim.handler.Motion
import com.maddyhome.idea.vim.handler.Motion.AbsoluteOffset import com.maddyhome.idea.vim.handler.Motion.AbsoluteOffset
import com.maddyhome.idea.vim.handler.MotionActionHandler import com.maddyhome.idea.vim.handler.MotionActionHandler
@@ -47,11 +47,14 @@ import com.maddyhome.idea.vim.helper.getNormalizedScrollOffset
import com.maddyhome.idea.vim.helper.getNormalizedSideScrollOffset import com.maddyhome.idea.vim.helper.getNormalizedSideScrollOffset
import com.maddyhome.idea.vim.helper.isEndAllowed import com.maddyhome.idea.vim.helper.isEndAllowed
import com.maddyhome.idea.vim.helper.vimLastColumn import com.maddyhome.idea.vim.helper.vimLastColumn
import com.maddyhome.idea.vim.impl.state.VimStateMachineImpl
import com.maddyhome.idea.vim.listener.AppCodeTemplates import com.maddyhome.idea.vim.listener.AppCodeTemplates
import com.maddyhome.idea.vim.newapi.IjEditorExecutionContext import com.maddyhome.idea.vim.newapi.IjEditorExecutionContext
import com.maddyhome.idea.vim.newapi.ij import com.maddyhome.idea.vim.newapi.ij
import com.maddyhome.idea.vim.newapi.vim import com.maddyhome.idea.vim.newapi.vim
import com.maddyhome.idea.vim.state.mode.Mode import com.maddyhome.idea.vim.state.mode.Mode
import com.maddyhome.idea.vim.state.mode.ReturnTo
import com.maddyhome.idea.vim.state.mode.returnTo
import org.jetbrains.annotations.Range import org.jetbrains.annotations.Range
import kotlin.math.max import kotlin.math.max
import kotlin.math.min import kotlin.math.min
@@ -190,21 +193,16 @@ internal class MotionGroup : VimMotionGroupBase() {
argument: Argument, argument: Argument,
operatorArguments: OperatorArguments, operatorArguments: OperatorArguments,
): TextRange? { ): TextRange? {
if (argument !is Argument.Motion) {
throw RuntimeException("Unexpected argument passed to getMotionRange2: $argument")
}
var start: Int var start: Int
var end: Int var end: Int
if (argument.type === Argument.Type.OFFSETS) {
val offsets = argument.offsets[caret.vim] ?: return null
val (first, second) = offsets.getNativeStartAndEnd()
start = first
end = second
} else {
val cmd = argument.motion
// Normalize the counts between the command and the motion argument
val cnt = cmd.count * operatorArguments.count1
val raw = if (operatorArguments.count0 == 0 && cmd.rawCount == 0) 0 else cnt
if (cmd.action is MotionActionHandler) {
val action = cmd.action as MotionActionHandler
val action = argument.motion
when (action) {
is MotionActionHandler -> {
// This is where we are now // This is where we are now
start = caret.offset start = caret.offset
@@ -213,8 +211,8 @@ internal class MotionGroup : VimMotionGroupBase() {
editor.vim, editor.vim,
caret.vim, caret.vim,
IjEditorExecutionContext(context!!), IjEditorExecutionContext(context!!),
cmd.argument, argument.argument,
operatorArguments.withCount0(raw), operatorArguments
) )
// Invalid motion // Invalid motion
@@ -230,22 +228,32 @@ internal class MotionGroup : VimMotionGroupBase() {
end++ end++
} }
} }
} else if (cmd.action is TextObjectActionHandler) { }
val action = cmd.action as TextObjectActionHandler
val range = is TextObjectActionHandler -> {
action.getRange(editor.vim, caret.vim, IjEditorExecutionContext(context!!), cnt, raw) ?: return null val range = action.getRange(
editor.vim,
caret.vim,
IjEditorExecutionContext(context!!),
operatorArguments.count1,
operatorArguments.count0
) ?: return null
start = range.startOffset start = range.startOffset
end = range.endOffset end = range.endOffset
if (cmd.isLinewiseMotion()) end-- if (argument.isLinewiseMotion()) end--
} else {
throw RuntimeException(
"Commands doesn't take " + cmd.action.javaClass.simpleName + " as an operator",
)
} }
is ExternalActionHandler -> {
val range = action.getRange(caret.vim) ?: return null
start = range.startOffset
end = range.endOffset
}
else -> throw RuntimeException("Commands doesn't take " + action.javaClass.simpleName + " as an operator")
} }
// This is a kludge for dw, dW, and d[w. Without this kludge, an extra newline is operated when it shouldn't be. // This is a kludge for dw, dW, and d[w. Without this kludge, an extra newline is operated when it shouldn't be.
val id = argument.motion.action.id val id = argument.motion.id
if (id == VimChangeGroupBase.VIM_MOTION_WORD_RIGHT || id == VimChangeGroupBase.VIM_MOTION_BIG_WORD_RIGHT || id == VimChangeGroupBase.VIM_MOTION_CAMEL_RIGHT) { if (id == VimChangeGroupBase.VIM_MOTION_WORD_RIGHT || id == VimChangeGroupBase.VIM_MOTION_BIG_WORD_RIGHT || id == VimChangeGroupBase.VIM_MOTION_CAMEL_RIGHT) {
val text = editor.document.charsSequence.subSequence(start, end).toString() val text = editor.document.charsSequence.subSequence(start, end).toString()
val lastNewLine = text.lastIndexOf('\n') val lastNewLine = text.lastIndexOf('\n')
@@ -255,6 +263,7 @@ internal class MotionGroup : VimMotionGroupBase() {
} }
} }
} }
return TextRange(start, end) return TextRange(start, end)
} }
@@ -312,13 +321,26 @@ internal class MotionGroup : VimMotionGroupBase() {
KeyHandler.getInstance().reset(vimEditor) KeyHandler.getInstance().reset(vimEditor)
} }
is Mode.CMD_LINE -> { is Mode.CMD_LINE -> {
injector.processGroup.cancelExEntry(vimEditor, refocusOwningEditor = false, resetCaret = false) val commandLine = injector.commandLine.getActiveCommandLine() ?: return
ExOutputModel.tryGetInstance(editor)?.close() commandLine.close(refocusOwningEditor = false, resetCaret = false)
injector.outputPanel.getCurrentOutputPanel()?.close()
} }
else -> {} else -> {}
} }
} }
} }
} else {
val state = injector.vimState as VimStateMachineImpl
if (state.mode is Mode.VISUAL) {
val returnTo = state.mode.returnTo
when (returnTo) {
ReturnTo.INSERT -> state.mode = Mode.INSERT
ReturnTo.REPLACE -> state.mode = Mode.REPLACE
null -> state.mode = Mode.NORMAL()
}
}
val keyHandler = KeyHandler.getInstance()
KeyHandler.getInstance().reset(keyHandler.keyHandlerState, state.mode)
} }
} }
} }

View File

@@ -62,6 +62,25 @@ internal class NotificationService(private val project: Project?) {
@Suppress("unused") @Suppress("unused")
constructor() : this(null) constructor() : this(null)
fun notifyAboutNewUndo() {
val notification = Notification(
IDEAVIM_NOTIFICATION_ID,
"Undo in IdeaVim now works like in Vim",
"""
Caret movement is no longer a separate undo step, and full insert is undoable in one step.
""".trimIndent(),
NotificationType.INFORMATION,
)
notification.addAction(object : DumbAwareAction("Share Feedback") {
override fun actionPerformed(p0: AnActionEvent) {
BrowserUtil.browse("https://youtrack.jetbrains.com/issue/VIM-547/Undo-splits-Insert-mode-edits-into-separate-undo-chunks")
}
})
notification.notify(project)
}
fun notifyAboutIdeaPut() { fun notifyAboutIdeaPut() {
val notification = Notification( val notification = Notification(
IDEAVIM_NOTIFICATION_ID, IDEAVIM_NOTIFICATION_ID,
@@ -182,8 +201,8 @@ internal class NotificationService(private val project: Project?) {
).notify(project) ).notify(project)
} }
fun notifyActionId(id: String?) { fun notifyActionId(id: String?, candidates: List<String>? = null) {
ActionIdNotifier.notifyActionId(id, project) ActionIdNotifier.notifyActionId(id, project, candidates)
} }
fun notifyKeymapIssues(issues: ArrayList<KeyMapIssue>) { fun notifyKeymapIssues(issues: ArrayList<KeyMapIssue>) {
@@ -259,20 +278,31 @@ internal class NotificationService(private val project: Project?) {
object ActionIdNotifier { object ActionIdNotifier {
private var notification: Notification? = null private var notification: Notification? = null
private const val NO_ID = "<i>Cannot detect action id</i>"
fun notifyActionId(id: String?, project: Project?) { fun notifyActionId(id: String?, project: Project?, candidates: List<String>? = null) {
notification?.expire() notification?.expire()
val content = if (id != null) "Action id: $id" else NO_ID val possibleIDs = candidates?.distinct()?.sorted()
Notification(IDEAVIM_NOTIFICATION_ID, IDEAVIM_NOTIFICATION_TITLE, content, NotificationType.INFORMATION).let { val content = when {
notification = it id != null -> "Action ID: <code>$id</code><br><br>"
it.whenExpired { notification = null } possibleIDs.isNullOrEmpty() -> "<i>Cannot detect action ID</i><br><br>"
it.setContent(it.content + "<br><br><small>Use ${ActionCenter.getToolwindowName()} to see previous ids</small>") possibleIDs.size == 1 -> "Possible action ID: <code>${possibleIDs[0]}</code><br><br>"
else -> {
buildString {
append("<p>Multiple possible action IDs. Candidates include:<ul>")
possibleIDs.forEach { append("<li><code>$it</code></li>") }
append("</ul></p>")
}
}
} + "<small>See the ${ActionCenter.getToolwindowName()} tool window for previous IDs</small>"
notification = Notification(IDEAVIM_NOTIFICATION_ID, IDEAVIM_NOTIFICATION_TITLE, content, NotificationType.INFORMATION).also {
it.whenExpired { notification = null }
it.addAction(StopTracking()) it.addAction(StopTracking())
if (id != null) it.addAction(CopyActionId(id, project)) if (id != null || possibleIDs?.size == 1) {
it.addAction(CopyActionId(id ?: possibleIDs?.get(0), project))
}
it.notify(project) it.notify(project)
} }

View File

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

View File

@@ -95,8 +95,6 @@ class ProcessGroup : VimProcessGroupBase() {
val progressIndicator = ProgressIndicatorProvider.getInstance().progressIndicator val progressIndicator = ProgressIndicatorProvider.getInstance().progressIndicator
val output = handler.runProcessWithProgressIndicator(progressIndicator) val output = handler.runProcessWithProgressIndicator(progressIndicator)
lastCommand = command
if (output.isCancelled) { if (output.isCancelled) {
// TODO: Vim will use whatever text has already been written to stdout // TODO: Vim will use whatever text has already been written to stdout
// For whatever reason, we're not getting any here, so just throw an exception // For whatever reason, we're not getting any here, so just throw an exception

View File

@@ -14,6 +14,7 @@ import com.intellij.openapi.components.State;
import com.intellij.openapi.components.Storage; import com.intellij.openapi.components.Storage;
import com.intellij.openapi.diagnostic.Logger; import com.intellij.openapi.diagnostic.Logger;
import com.maddyhome.idea.vim.VimPlugin; import com.maddyhome.idea.vim.VimPlugin;
import com.maddyhome.idea.vim.newapi.IjVimInjectorKt;
import com.maddyhome.idea.vim.register.Register; import com.maddyhome.idea.vim.register.Register;
import com.maddyhome.idea.vim.register.VimRegisterGroupBase; import com.maddyhome.idea.vim.register.VimRegisterGroupBase;
import com.maddyhome.idea.vim.state.mode.SelectionType; import com.maddyhome.idea.vim.state.mode.SelectionType;
@@ -35,6 +36,10 @@ import java.util.List;
}) })
public class RegisterGroup extends VimRegisterGroupBase implements PersistentStateComponent<Element> { public class RegisterGroup extends VimRegisterGroupBase implements PersistentStateComponent<Element> {
static {
IjVimInjectorKt.initInjector();
}
private static final Logger logger = Logger.getInstance(RegisterGroup.class); private static final Logger logger = Logger.getInstance(RegisterGroup.class);
public RegisterGroup() { public RegisterGroup() {

View File

@@ -25,6 +25,7 @@ import com.maddyhome.idea.vim.mark.Jump
import com.maddyhome.idea.vim.newapi.IjVimEditor import com.maddyhome.idea.vim.newapi.IjVimEditor
import com.maddyhome.idea.vim.newapi.globalIjOptions import com.maddyhome.idea.vim.newapi.globalIjOptions
import com.maddyhome.idea.vim.newapi.ij import com.maddyhome.idea.vim.newapi.ij
import com.maddyhome.idea.vim.newapi.initInjector
import org.jdom.Element import org.jdom.Element
@State(name = "VimJumpsSettings", storages = [Storage(value = "\$APP_CONFIG$/vim_settings_local.xml", roamingType = RoamingType.DISABLED)]) @State(name = "VimJumpsSettings", storages = [Storage(value = "\$APP_CONFIG$/vim_settings_local.xml", roamingType = RoamingType.DISABLED)])
@@ -65,6 +66,7 @@ internal class VimJumpServiceImpl : VimJumpServiceBase(), PersistentStateCompone
} }
override fun loadState(state: Element) { override fun loadState(state: Element) {
initInjector()
val projectElements = state.getChildren("project") val projectElements = state.getChildren("project")
for (projectElement in projectElements) { for (projectElement in projectElements) {
val jumps = mutableListOf<Jump>() val jumps = mutableListOf<Jump>()
@@ -89,6 +91,7 @@ internal class VimJumpServiceImpl : VimJumpServiceBase(), PersistentStateCompone
internal class JumpsListener(val project: Project) : RecentPlacesListener { internal class JumpsListener(val project: Project) : RecentPlacesListener {
override fun recentPlaceAdded(changePlace: PlaceInfo, isChanged: Boolean) { override fun recentPlaceAdded(changePlace: PlaceInfo, isChanged: Boolean) {
initInjector()
if (!injector.globalIjOptions().unifyjumps) return if (!injector.globalIjOptions().unifyjumps) return
val jumpService = injector.jumpService val jumpService = injector.jumpService

View File

@@ -7,7 +7,6 @@
*/ */
package com.maddyhome.idea.vim.group package com.maddyhome.idea.vim.group
import com.intellij.codeWithMe.ClientId
import com.intellij.ide.bookmark.Bookmark import com.intellij.ide.bookmark.Bookmark
import com.intellij.ide.bookmark.BookmarkGroup import com.intellij.ide.bookmark.BookmarkGroup
import com.intellij.ide.bookmark.BookmarksListener import com.intellij.ide.bookmark.BookmarksListener

View File

@@ -46,10 +46,13 @@ import com.maddyhome.idea.vim.put.PutData
import com.maddyhome.idea.vim.put.VimPasteProvider import com.maddyhome.idea.vim.put.VimPasteProvider
import com.maddyhome.idea.vim.put.VimPutBase import com.maddyhome.idea.vim.put.VimPutBase
import com.maddyhome.idea.vim.register.RegisterConstants import com.maddyhome.idea.vim.register.RegisterConstants
import com.maddyhome.idea.vim.state.mode.Mode
import com.maddyhome.idea.vim.state.mode.SelectionType import com.maddyhome.idea.vim.state.mode.SelectionType
import com.maddyhome.idea.vim.state.mode.isBlock import com.maddyhome.idea.vim.state.mode.isBlock
import com.maddyhome.idea.vim.state.mode.isChar import com.maddyhome.idea.vim.state.mode.isChar
import com.maddyhome.idea.vim.state.mode.isLine 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 import java.awt.datatransfer.DataFlavor
@Service @Service
@@ -83,10 +86,16 @@ internal class PutGroup : VimPutBase() {
val editor = (vimEditor as IjVimEditor).editor val editor = (vimEditor as IjVimEditor).editor
val context = vimContext.context as DataContext val context = vimContext.context as DataContext
val carets: MutableMap<Caret, RangeMarker> = mutableMapOf() val carets: MutableMap<Caret, RangeMarker> = mutableMapOf()
if (editor.isInsertMode) { if (injector.vimState.mode is Mode.INSERT) {
val undo = injector.undo
val nanoTime = System.nanoTime() 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 -> EditorHelper.getOrderedCaretsList(editor).forEach { caret ->
val startOffset = val startOffset =

View File

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

View File

@@ -9,8 +9,6 @@
package com.maddyhome.idea.vim.group.visual package com.maddyhome.idea.vim.group.visual
import com.maddyhome.idea.vim.api.injector 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.newapi.globalIjOptions
import com.maddyhome.idea.vim.state.mode.Mode import com.maddyhome.idea.vim.state.mode.Mode
import java.awt.event.ActionEvent import java.awt.event.ActionEvent

View File

@@ -16,6 +16,7 @@ import com.intellij.openapi.keymap.ex.KeymapManagerEx
import com.intellij.openapi.project.Project import com.intellij.openapi.project.Project
import com.intellij.openapi.startup.ProjectActivity import com.intellij.openapi.startup.ProjectActivity
import com.maddyhome.idea.vim.api.key import com.maddyhome.idea.vim.api.key
import com.maddyhome.idea.vim.newapi.initInjector
/** /**
* Logs the chain of handlers for esc and enter * Logs the chain of handlers for esc and enter
@@ -34,6 +35,8 @@ internal class EditorHandlersChainLogger : ProjectActivity {
private val editorHandlers = ExtensionPointName<EditorActionHandlerBean>("com.intellij.editorActionHandler") private val editorHandlers = ExtensionPointName<EditorActionHandlerBean>("com.intellij.editorActionHandler")
override suspend fun execute(project: Project) { override suspend fun execute(project: Project) {
initInjector()
if (!enableOctopus) return if (!enableOctopus) return
val escHandlers = editorHandlers.extensionList val escHandlers = editorHandlers.extensionList

View File

@@ -19,7 +19,7 @@ import com.maddyhome.idea.vim.command.OperatorArguments
*/ */
internal abstract class IdeActionHandler(private val actionName: String) : VimActionHandler.SingleExecution() { internal abstract class IdeActionHandler(private val actionName: String) : VimActionHandler.SingleExecution() {
override fun execute(editor: VimEditor, context: ExecutionContext, cmd: Command, operatorArguments: OperatorArguments): Boolean { override fun execute(editor: VimEditor, context: ExecutionContext, cmd: Command, operatorArguments: OperatorArguments): Boolean {
injector.actionExecutor.executeAction(actionName, context) injector.actionExecutor.executeAction(editor, name = actionName, context = context)
injector.scroll.scrollCaretIntoView(editor) injector.scroll.scrollCaretIntoView(editor)
return true return true
} }

View File

@@ -183,6 +183,7 @@ internal abstract class OctopusHandler(private val nextHandler: EditorActionHand
* - App code - set handler after * - App code - set handler after
* - Template - doesn't intersect with enter anymore * - 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 * - 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 * 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 * - before terminalEnter - not necessary, but terminalEnter causes "file is read-only" tooltip for readonly files VIM-3122
@@ -218,13 +219,17 @@ internal class VimEnterHandler(nextHandler: EditorActionHandler?) : VimKeyHandle
internal class VimEscHandler(nextHandler: EditorActionHandler) : VimKeyHandler(nextHandler) { internal class VimEscHandler(nextHandler: EditorActionHandler) : VimKeyHandler(nextHandler) {
override val key: String = "<Esc>" override val key: String = "<Esc>"
private val ideaVimSupportDialog
get() = injector.globalIjOptions().ideavimsupport.contains(IjOptionConstants.ideavimsupport_dialog)
override fun isHandlerEnabled(editor: Editor, dataContext: DataContext?): Boolean { override fun isHandlerEnabled(editor: Editor, dataContext: DataContext?): Boolean {
val ideaVimSupportDialog =
injector.globalIjOptions().ideavimsupport.contains(IjOptionConstants.ideavimsupport_dialog)
return editor.isPrimaryEditor() || return editor.isPrimaryEditor() ||
EditorHelper.isFileEditor(editor) && !editor.vim.mode.inNormalMode || EditorHelper.isFileEditor(editor) && vimStateNeedsToHandleEscape(editor) ||
ideaVimSupportDialog && !editor.vim.mode.inNormalMode ideaVimSupportDialog && vimStateNeedsToHandleEscape(editor)
}
private fun vimStateNeedsToHandleEscape(editor: Editor): Boolean {
return !editor.vim.mode.inNormalMode || KeyHandler.getInstance().keyHandlerState.mappingState.hasKeys
} }
} }

View File

@@ -19,6 +19,7 @@ import com.maddyhome.idea.vim.VimPlugin
import com.maddyhome.idea.vim.api.VimEditor import com.maddyhome.idea.vim.api.VimEditor
import com.maddyhome.idea.vim.api.globalOptions import com.maddyhome.idea.vim.api.globalOptions
import com.maddyhome.idea.vim.api.injector import com.maddyhome.idea.vim.api.injector
import com.maddyhome.idea.vim.common.EditorListener
import com.maddyhome.idea.vim.common.IsReplaceCharListener import com.maddyhome.idea.vim.common.IsReplaceCharListener
import com.maddyhome.idea.vim.common.ModeChangeListener import com.maddyhome.idea.vim.common.ModeChangeListener
import com.maddyhome.idea.vim.newapi.IjVimEditor import com.maddyhome.idea.vim.newapi.IjVimEditor
@@ -86,6 +87,7 @@ private fun isBlockCursorOverride() = EditorSettingsExternalizable.getInstance()
private fun Editor.updatePrimaryCaretVisualAttributes() { private fun Editor.updatePrimaryCaretVisualAttributes() {
if (VimPlugin.isNotEnabled()) thisLogger().error("The caret attributes should not be updated if the IdeaVim is disabled") if (VimPlugin.isNotEnabled()) thisLogger().error("The caret attributes should not be updated if the IdeaVim is disabled")
if (isIdeaVimDisabledHere) return
caretModel.primaryCaret.visualAttributes = AttributesCache.getCaretVisualAttributes(this) caretModel.primaryCaret.visualAttributes = AttributesCache.getCaretVisualAttributes(this)
// Make sure the caret is visible as soon as it's set. It might be invisible while blinking // Make sure the caret is visible as soon as it's set. It might be invisible while blinking
@@ -99,6 +101,7 @@ private fun Editor.updatePrimaryCaretVisualAttributes() {
private fun Editor.updateSecondaryCaretsVisualAttributes() { private fun Editor.updateSecondaryCaretsVisualAttributes() {
if (VimPlugin.isNotEnabled()) thisLogger().error("The caret attributes should not be updated if the IdeaVim is disabled") if (VimPlugin.isNotEnabled()) thisLogger().error("The caret attributes should not be updated if the IdeaVim is disabled")
if (isIdeaVimDisabledHere) return
// IntelliJ simulates visual block with multiple carets with selections. Do our best to hide them // IntelliJ simulates visual block with multiple carets with selections. Do our best to hide them
val attributes = if (this.vim.inBlockSelection) HIDDEN else AttributesCache.getCaretVisualAttributes(this) val attributes = if (this.vim.inBlockSelection) HIDDEN else AttributesCache.getCaretVisualAttributes(this)
this.caretModel.allCarets.forEach { this.caretModel.allCarets.forEach {
@@ -144,24 +147,28 @@ private object AttributesCache {
@TestOnly @TestOnly
internal fun getGuiCursorMode(editor: Editor) = editor.guicursorMode() internal fun getGuiCursorMode(editor: Editor) = editor.guicursorMode()
class CaretVisualAttributesListener : IsReplaceCharListener, ModeChangeListener { class CaretVisualAttributesListener : IsReplaceCharListener, ModeChangeListener, EditorListener {
override fun isReplaceCharChanged(editor: VimEditor) { override fun isReplaceCharChanged(editor: VimEditor) {
updateCaretsVisual() updateCaretsVisual(editor)
} }
override fun modeChanged(editor: VimEditor, oldMode: Mode) { override fun modeChanged(editor: VimEditor, oldMode: Mode) {
updateCaretsVisual() updateCaretsVisual(editor)
} }
private fun updateCaretsVisual() { override fun focusGained(editor: VimEditor) {
updateAllEditorsCaretsVisual() updateCaretsVisual(editor)
}
private fun updateCaretsVisual(editor: VimEditor) {
val ijEditor = (editor as IjVimEditor).editor
ijEditor.updateCaretsVisualAttributes()
ijEditor.updateCaretsVisualPosition()
} }
fun updateAllEditorsCaretsVisual() { fun updateAllEditorsCaretsVisual() {
injector.editorGroup.getEditors().forEach { editor -> injector.editorGroup.getEditors().forEach { editor ->
val ijEditor = (editor as IjVimEditor).editor updateCaretsVisual(editor)
ijEditor.updateCaretsVisualAttributes()
ijEditor.updateCaretsVisualPosition()
} }
} }
} }

View File

@@ -157,6 +157,19 @@ public class EditorHelper {
return (int)(getVisibleArea(editor).width / getPlainSpaceWidthFloat(editor)); 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. * Gets the width of the space character in the editor's plain font as a float.
* <p> * <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 // 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 // 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 lastVisualLine = EngineEditorHelperKt.getVisualLineCount(editor1) - 1;
final int yBottomLineOffset = max(getOffsetToScrollVisualLineToBottomOfScreen(editor, lastVisualLine), visibleArea.y); final int yBottomLineOffset = max(getOffsetToScrollVisualLineToBottomOfScreen(editor, lastVisualLine), visibleArea.y);
scrollVertically(editor, min(yVisualLine - caretScreenOffset - inlayOffset, yBottomLineOffset)); scrollVertically(editor, min(yVisualLine - caretScreenOffset - inlayOffset, yBottomLineOffset));
@@ -325,7 +338,7 @@ public class EditorHelper {
final int lineHeight = editor.getLineHeight(); final int lineHeight = editor.getLineHeight();
final int offset = y - ((screenHeight - lineHeight) / lineHeight / 2 * lineHeight); final int offset = y - ((screenHeight - lineHeight) / lineHeight / 2 * lineHeight);
@NotNull final VimEditor editor1 = new IjVimEditor(editor); final @NotNull VimEditor editor1 = new IjVimEditor(editor);
final int lastVisualLine = EngineEditorHelperKt.getVisualLineCount(editor1) + editor.getSettings().getAdditionalLinesCount(); final int lastVisualLine = EngineEditorHelperKt.getVisualLineCount(editor1) + editor.getSettings().getAdditionalLinesCount();
final int offsetForLastLineAtBottom = getOffsetToScrollVisualLineToBottomOfScreen(editor, lastVisualLine); final int offsetForLastLineAtBottom = getOffsetToScrollVisualLineToBottomOfScreen(editor, lastVisualLine);
@@ -379,7 +392,7 @@ public class EditorHelper {
return 0; 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 // 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 // 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 // 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) { public static Pair<Boolean, Integer> scrollFullPageDown(final @NotNull Editor editor, int pages) {
final Rectangle visibleArea = getVisibleArea(editor); 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; final int lastVisualLine = EngineEditorHelperKt.getVisualLineCount(editor2) - 1;
int y = visibleArea.y + visibleArea.height; int y = visibleArea.y + visibleArea.height;
@@ -480,7 +493,7 @@ public class EditorHelper {
caretVisualLine = lastVisualLine; caretVisualLine = lastVisualLine;
} }
else { else {
@NotNull final VimEditor editor1 = new IjVimEditor(editor); final @NotNull VimEditor editor1 = new IjVimEditor(editor);
caretVisualLine = EngineEditorHelperKt.getVisualLineCount(editor1) - 1; caretVisualLine = EngineEditorHelperKt.getVisualLineCount(editor1) - 1;
completed = false; completed = false;
} }
@@ -515,7 +528,7 @@ public class EditorHelper {
public static Pair<Boolean, Integer> scrollFullPageUp(final @NotNull Editor editor, int pages) { public static Pair<Boolean, Integer> scrollFullPageUp(final @NotNull Editor editor, int pages) {
final Rectangle visibleArea = getVisibleArea(editor); final Rectangle visibleArea = getVisibleArea(editor);
final int lineHeight = editor.getLineHeight(); 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; final int lastVisualLine = EngineEditorHelperKt.getVisualLineCount(editor1) - 1;
int y = visibleArea.y; int y = visibleArea.y;

View File

@@ -13,23 +13,23 @@ import com.intellij.openapi.actionSystem.ActionManager
import com.intellij.openapi.actionSystem.ActionPlaces import com.intellij.openapi.actionSystem.ActionPlaces
import com.intellij.openapi.actionSystem.AnAction import com.intellij.openapi.actionSystem.AnAction
import com.intellij.openapi.actionSystem.AnActionEvent import com.intellij.openapi.actionSystem.AnActionEvent
import com.intellij.openapi.actionSystem.AnActionResult
import com.intellij.openapi.actionSystem.DataContextWrapper import com.intellij.openapi.actionSystem.DataContextWrapper
import com.intellij.openapi.actionSystem.EmptyAction import com.intellij.openapi.actionSystem.EmptyAction
import com.intellij.openapi.actionSystem.IdeActions import com.intellij.openapi.actionSystem.IdeActions
import com.intellij.openapi.actionSystem.PlatformDataKeys import com.intellij.openapi.actionSystem.PlatformDataKeys
import com.intellij.openapi.actionSystem.ex.ActionManagerEx
import com.intellij.openapi.actionSystem.ex.ActionUtil 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.ProxyShortcutSet
import com.intellij.openapi.actionSystem.impl.Utils 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.CommandProcessor
import com.intellij.openapi.command.UndoConfirmationPolicy import com.intellij.openapi.command.UndoConfirmationPolicy
import com.intellij.openapi.components.Service import com.intellij.openapi.components.Service
import com.intellij.openapi.diagnostic.thisLogger
import com.intellij.openapi.editor.actionSystem.DocCommandGroupId import com.intellij.openapi.editor.actionSystem.DocCommandGroupId
import com.intellij.openapi.project.IndexNotReadyException import com.intellij.openapi.progress.util.ProgressIndicatorUtils
import com.intellij.openapi.ui.popup.JBPopupFactory import com.intellij.openapi.ui.popup.JBPopupFactory
import com.intellij.openapi.util.NlsContexts import com.intellij.openapi.util.NlsContexts
import com.intellij.util.SlowOperations
import com.maddyhome.idea.vim.RegisterActions import com.maddyhome.idea.vim.RegisterActions
import com.maddyhome.idea.vim.api.ExecutionContext import com.maddyhome.idea.vim.api.ExecutionContext
import com.maddyhome.idea.vim.api.NativeAction import com.maddyhome.idea.vim.api.NativeAction
@@ -61,6 +61,9 @@ internal class IjActionExecutor : VimActionExecutor {
get() = IdeActions.ACTION_EXPAND_REGION get() = IdeActions.ACTION_EXPAND_REGION
override val ACTION_EXPAND_REGION_RECURSIVELY: String override val ACTION_EXPAND_REGION_RECURSIVELY: String
get() = IdeActions.ACTION_EXPAND_REGION_RECURSIVELY get() = IdeActions.ACTION_EXPAND_REGION_RECURSIVELY
override val ACTION_EXPAND_COLLAPSE_TOGGLE: String
// [VERSION UPDATE] 2024.3+ Replace raw "ExpandCollapseToggleAction" with IdeActions.ACTION_EXPAND_COLLAPSE_TOGGLE_REGION from the platform.
get() = "ExpandCollapseToggleAction"
/** /**
* Execute an action * Execute an action
@@ -69,6 +72,12 @@ internal class IjActionExecutor : VimActionExecutor {
* @param context The context to run it in * @param context The context to run it in
*/ */
override fun executeAction(editor: VimEditor?, action: NativeAction, context: ExecutionContext): Boolean { override fun executeAction(editor: VimEditor?, action: NativeAction, context: ExecutionContext): Boolean {
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("Actions cannot be updated when write-action is running or pending")
}
val ijAction = (action as IjNativeAction).action val ijAction = (action as IjNativeAction).action
/** /**
@@ -121,51 +130,15 @@ internal class IjActionExecutor : VimActionExecutor {
} }
} }
// This is taken directly from ActionUtil.performActionDumbAwareWithCallbacks
// But with one check removed. With this check some actions (like `:w` doesn't work)
// https://youtrack.jetbrains.com/issue/VIM-2691/File-is-not-saved-on-w
private fun performDumbAwareWithCallbacks(
action: AnAction,
event: AnActionEvent,
performRunnable: Runnable,
) {
val project = event.project
var indexError: IndexNotReadyException? = null
val manager = ActionManagerEx.getInstanceEx()
manager.fireBeforeActionPerformed(action, event)
var result: AnActionResult? = null
try {
SlowOperations.allowSlowOperations(SlowOperations.ACTION_PERFORM).use {
performRunnable.run()
result = AnActionResult.PERFORMED
}
} catch (ex: IndexNotReadyException) {
indexError = ex
result = AnActionResult.failed(ex)
} catch (ex: RuntimeException) {
result = AnActionResult.failed(ex)
throw ex
} catch (ex: Error) {
result = AnActionResult.failed(ex)
throw ex
} finally {
if (result == null) result = AnActionResult.failed(Throwable())
manager.fireAfterActionPerformed(action, event, result!!)
}
if (indexError != null) {
ActionUtil.showDumbModeWarning(project, action, event)
}
}
/** /**
* Execute an action by name * Execute an action by name
* *
* @param name The name of the action to execute * @param name The name of the action to execute
* @param context The context to run it in * @param context The context to run it in
*/ */
override fun executeAction(name: @NonNls String, context: ExecutionContext): Boolean { override fun executeAction(editor: VimEditor, name: @NonNls String, context: ExecutionContext): Boolean {
val action = getAction(name, context) val action = getAction(name, context)
return action != null && executeAction(null, IjNativeAction(action), context) return action != null && executeAction(editor, IjNativeAction(action), context)
} }
private fun getAction(name: String, context: ExecutionContext): AnAction? { private fun getAction(name: String, context: ExecutionContext): AnAction? {
@@ -211,8 +184,8 @@ internal class IjActionExecutor : VimActionExecutor {
CommandProcessor.getInstance().executeCommand(editor?.ij?.project, runnable, name, groupId) CommandProcessor.getInstance().executeCommand(editor?.ij?.project, runnable, name, groupId)
} }
override fun executeEsc(context: ExecutionContext): Boolean { override fun executeEsc(editor: VimEditor, context: ExecutionContext): Boolean {
return executeAction(IdeActions.ACTION_EDITOR_ESCAPE, context) return executeAction(editor, IdeActions.ACTION_EDITOR_ESCAPE, context)
} }
override fun executeVimAction( override fun executeVimAction(

View File

@@ -13,7 +13,7 @@ import com.intellij.openapi.editor.ReadOnlyFragmentModificationException
import com.intellij.openapi.editor.VisualPosition import com.intellij.openapi.editor.VisualPosition
import com.intellij.openapi.editor.actionSystem.EditorActionManager import com.intellij.openapi.editor.actionSystem.EditorActionManager
import com.intellij.openapi.editor.ex.util.EditorUtil 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.VimEditor
import com.maddyhome.idea.vim.api.VimRangeMarker import com.maddyhome.idea.vim.api.VimRangeMarker
import com.maddyhome.idea.vim.api.VimVisualPosition import com.maddyhome.idea.vim.api.VimVisualPosition
@@ -22,7 +22,7 @@ import com.maddyhome.idea.vim.newapi.ij
import com.maddyhome.idea.vim.newapi.vim import com.maddyhome.idea.vim.newapi.vim
@Service @Service
internal class IjEditorHelper : EngineEditorHelper { internal class IjEditorHelper : EngineEditorHelperBase() {
override fun amountOfInlaysBeforeVisualPosition(editor: VimEditor, pos: VimVisualPosition): Int { override fun amountOfInlaysBeforeVisualPosition(editor: VimEditor, pos: VimVisualPosition): Int {
return (editor as IjVimEditor).editor.amountOfInlaysBeforeVisualPosition( return (editor as IjVimEditor).editor.amountOfInlaysBeforeVisualPosition(
VisualPosition( VisualPosition(
@@ -41,6 +41,10 @@ internal class IjEditorHelper : EngineEditorHelper {
return EditorHelper.getApproximateScreenWidth(editor.ij) return EditorHelper.getApproximateScreenWidth(editor.ij)
} }
override fun getApproximateOutputPanelWidth(editor: VimEditor): Int {
return EditorHelper.getApproximateOutputPanelWidth(editor.ij)
}
override fun handleWithReadonlyFragmentModificationHandler(editor: VimEditor, exception: Exception) { override fun handleWithReadonlyFragmentModificationHandler(editor: VimEditor, exception: Exception) {
return EditorActionManager.getInstance() return EditorActionManager.getInstance()
.getReadonlyFragmentModificationHandler(editor.ij.document) .getReadonlyFragmentModificationHandler(editor.ij.document)
@@ -51,10 +55,6 @@ internal class IjEditorHelper : EngineEditorHelper {
return EditorHelper.getVisualLineAtBottomOfScreen(editor.ij) return EditorHelper.getVisualLineAtBottomOfScreen(editor.ij)
} }
override fun pad(editor: VimEditor, line: Int, to: Int): String {
return EditorHelper.pad(editor.ij, line, to)
}
override fun inlayAwareOffsetToVisualPosition(editor: VimEditor, offset: Int): VimVisualPosition { override fun inlayAwareOffsetToVisualPosition(editor: VimEditor, offset: Int): VimVisualPosition {
return EditorUtil.inlayAwareOffsetToVisualPosition(editor.ij, offset).vim return EditorUtil.inlayAwareOffsetToVisualPosition(editor.ij, offset).vim
} }

View File

@@ -16,7 +16,6 @@ import com.maddyhome.idea.vim.VimPlugin
import com.maddyhome.idea.vim.api.VimEditor import com.maddyhome.idea.vim.api.VimEditor
import com.maddyhome.idea.vim.api.getLineEndForOffset import com.maddyhome.idea.vim.api.getLineEndForOffset
import com.maddyhome.idea.vim.api.getLineStartForOffset import com.maddyhome.idea.vim.api.getLineStartForOffset
import com.maddyhome.idea.vim.command.OperatorArguments
import com.maddyhome.idea.vim.listener.SelectionVimListenerSuppressor import com.maddyhome.idea.vim.listener.SelectionVimListenerSuppressor
import com.maddyhome.idea.vim.newapi.IjEditorExecutionContext import com.maddyhome.idea.vim.newapi.IjEditorExecutionContext
import com.maddyhome.idea.vim.newapi.IjVimCaret import com.maddyhome.idea.vim.newapi.IjVimCaret
@@ -94,6 +93,6 @@ internal fun VimEditor.exitSelectMode(adjustCaretPosition: Boolean) {
} }
} }
internal fun Editor.exitInsertMode(context: DataContext, operatorArguments: OperatorArguments) { internal fun Editor.exitInsertMode(context: DataContext) {
VimPlugin.getChange().processEscape(IjVimEditor(this), IjEditorExecutionContext(context), operatorArguments) VimPlugin.getChange().processEscape(IjVimEditor(this), IjEditorExecutionContext(context))
} }

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.scrollVisualLineToMiddleOfScreen
import com.maddyhome.idea.vim.helper.EditorHelper.scrollVisualLineToTopOfScreen import com.maddyhome.idea.vim.helper.EditorHelper.scrollVisualLineToTopOfScreen
import com.maddyhome.idea.vim.newapi.vim import com.maddyhome.idea.vim.newapi.vim
import com.maddyhome.idea.vim.state.VimStateMachine
import kotlin.math.max import kotlin.math.max
import kotlin.math.min import kotlin.math.min
import kotlin.math.roundToInt import kotlin.math.roundToInt

View File

@@ -16,18 +16,15 @@ import com.intellij.openapi.editor.Editor
import com.intellij.spellchecker.SpellCheckerSeveritiesProvider import com.intellij.spellchecker.SpellCheckerSeveritiesProvider
import com.maddyhome.idea.vim.api.VimEditor import com.maddyhome.idea.vim.api.VimEditor
import com.maddyhome.idea.vim.api.getLineEndOffset import com.maddyhome.idea.vim.api.getLineEndOffset
import com.maddyhome.idea.vim.api.getText
import com.maddyhome.idea.vim.api.globalOptions import com.maddyhome.idea.vim.api.globalOptions
import com.maddyhome.idea.vim.api.injector import com.maddyhome.idea.vim.api.injector
import com.maddyhome.idea.vim.common.TextRange import com.maddyhome.idea.vim.common.TextRange
import com.maddyhome.idea.vim.helper.CharacterHelper.charType import com.maddyhome.idea.vim.helper.CharacterHelper.charType
import com.maddyhome.idea.vim.newapi.IjVimEditor import com.maddyhome.idea.vim.newapi.IjVimEditor
import com.maddyhome.idea.vim.newapi.vim
import it.unimi.dsi.fastutil.ints.IntComparator import it.unimi.dsi.fastutil.ints.IntComparator
import it.unimi.dsi.fastutil.ints.IntIterator import it.unimi.dsi.fastutil.ints.IntIterator
import it.unimi.dsi.fastutil.ints.IntRBTreeSet import it.unimi.dsi.fastutil.ints.IntRBTreeSet
import it.unimi.dsi.fastutil.ints.IntSortedSet import it.unimi.dsi.fastutil.ints.IntSortedSet
import java.util.*
/** /**
* Check ignorecase and smartcase options to see if a case insensitive search should be performed with the given pattern. * Check ignorecase and smartcase options to see if a case insensitive search should be performed with the given pattern.
@@ -97,210 +94,6 @@ fun countWords(
return CountPosition(count, position) return CountPosition(count, position)
} }
fun findNumbersInRange(
editor: Editor,
textRange: TextRange,
alpha: Boolean,
hex: Boolean,
octal: Boolean,
): List<Pair<TextRange, NumberType>> {
val result: MutableList<Pair<TextRange, NumberType>> = ArrayList()
for (i in 0 until textRange.size()) {
val startOffset = textRange.startOffsets[i]
val end = textRange.endOffsets[i]
val text: String = editor.vim.getText(startOffset, end)
val textChunks = text.split("\\n".toRegex()).dropLastWhile { it.isEmpty() }.toTypedArray()
var chunkStart = 0
for (chunk in textChunks) {
val number = findNumberInText(chunk, 0, alpha, hex, octal)
if (number != null) {
result.add(
Pair(
TextRange(
number.first.startOffset + startOffset + chunkStart,
number.first.endOffset + startOffset + chunkStart
),
number.second
)
)
}
chunkStart += 1 + chunk.length
}
}
return result
}
fun findNumberUnderCursor(
editor: Editor,
caret: Caret,
alpha: Boolean,
hex: Boolean,
octal: Boolean,
): Pair<TextRange, NumberType>? {
val lline = caret.logicalPosition.line
val text = IjVimEditor(editor).getLineText(lline).lowercase(Locale.getDefault())
val startLineOffset = IjVimEditor(editor).getLineStartOffset(lline)
val posOnLine = caret.offset - startLineOffset
val numberTextRange = findNumberInText(text, posOnLine, alpha, hex, octal) ?: return null
return Pair(
TextRange(
numberTextRange.first.startOffset + startLineOffset,
numberTextRange.first.endOffset + startLineOffset
),
numberTextRange.second
)
}
/**
* Search for number in given text from start position
*
* @param textInRange - text to search in
* @param startPosOnLine - start offset to search
* @return - text range with number
*/
fun findNumberInText(
textInRange: String,
startPosOnLine: Int,
alpha: Boolean,
hex: Boolean,
octal: Boolean,
): Pair<TextRange, NumberType>? {
if (logger.isDebugEnabled) {
logger.debug("text=$textInRange")
}
var pos = startPosOnLine
val lineEndOffset = textInRange.length
while (true) {
// Skip over current whitespace if any
while (pos < lineEndOffset && !isNumberChar(textInRange[pos], alpha, hex, octal, true)) {
pos++
}
if (logger.isDebugEnabled) logger.debug("pos=$pos")
if (pos >= lineEndOffset) {
logger.debug("no number char on line")
return null
}
val isHexChar = "abcdefABCDEF".indexOf(textInRange[pos]) >= 0
if (hex) {
// Ox and OX handling
if (textInRange[pos] == '0' && pos < lineEndOffset - 1 && "xX".indexOf(textInRange[pos + 1]) >= 0) {
pos += 2
} else if ("xX".indexOf(textInRange[pos]) >= 0 && pos > 0 && textInRange[pos - 1] == '0') {
pos++
}
logger.debug("checking hex")
val range = findRange(textInRange, pos, false, true, false, false)
val start = range.first
val end = range.second
// Ox and OX
if (start >= 2 && textInRange.substring(start - 2, start).equals("0x", ignoreCase = true)) {
logger.debug("found hex")
return Pair(TextRange(start - 2, end), NumberType.HEX)
}
if (!isHexChar || alpha) {
break
} else {
pos++
}
} else {
break
}
}
if (octal) {
logger.debug("checking octal")
val range = findRange(textInRange, pos, false, false, true, false)
val start = range.first
val end = range.second
if (end - start == 1 && textInRange[start] == '0') {
return Pair(TextRange(start, end), NumberType.DEC)
}
if (textInRange[start] == '0' && end > start &&
!(start > 0 && isNumberChar(textInRange[start - 1], false, false, false, true))
) {
logger.debug("found octal")
return Pair(TextRange(start, end), NumberType.OCT)
}
}
if (alpha) {
if (logger.isDebugEnabled) logger.debug("checking alpha for " + textInRange[pos])
if (isNumberChar(textInRange[pos], true, false, false, false)) {
if (logger.isDebugEnabled) logger.debug("found alpha at $pos")
return Pair(TextRange(pos, pos + 1), NumberType.ALPHA)
}
}
val range = findRange(textInRange, pos, false, false, false, true)
var start = range.first
val end = range.second
if (start > 0 && textInRange[start - 1] == '-') {
start--
}
return Pair(TextRange(start, end), NumberType.DEC)
}
/**
* Searches for digits block that matches parameters
*/
private fun findRange(
text: String,
pos: Int,
alpha: Boolean,
hex: Boolean,
octal: Boolean,
decimal: Boolean,
): Pair<Int, Int> {
var end = pos
while (end < text.length && isNumberChar(text[end], alpha, hex, octal, decimal || octal)) {
end++
}
var start = pos
while (start >= 0 && isNumberChar(text[start], alpha, hex, octal, decimal || octal)) {
start--
}
if (start < end &&
(start == -1 ||
0 <= start && start < text.length &&
!isNumberChar(text[start], alpha, hex, octal, decimal || octal))
) {
start++
}
if (octal) {
for (i in start until end) {
if (!isNumberChar(text[i], false, false, true, false)) return Pair(0, 0)
}
}
return Pair(start, end)
}
private fun isNumberChar(ch: Char, alpha: Boolean, hex: Boolean, octal: Boolean, decimal: Boolean): Boolean {
return if (alpha && ((ch >= 'a' && ch <= 'z') || (ch >= 'A' && ch <= 'Z'))) {
true
} else if (octal && (ch >= '0' && ch <= '7')) {
true
} else if (hex && ((ch >= '0' && ch <= '9') || "abcdefABCDEF".indexOf(ch) >= 0)) {
true
} else {
decimal && (ch >= '0' && ch <= '9')
}
}
/** /**
* Find the word under the cursor or the next word to the right of the cursor on the current line. * Find the word under the cursor or the next word to the right of the cursor on the current line.
* *

View File

@@ -102,7 +102,8 @@ private fun updateSearchHighlights(
// Update highlights in all visible editors. We update non-visible editors when they get focus. // 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 // Note that this now includes all editors - main, diff windows, even toolwindows like the Commit editor and consoles
val editors = injector.editorGroup.getEditors().filter { 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 { editors.forEach {

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