mirror of
https://github.com/chylex/IntelliJ-IdeaVim.git
synced 2024-11-25 07:42:59 +01:00
Compare commits
418 Commits
dd8bfac3cf
...
83f52ee9eb
Author | SHA1 | Date | |
---|---|---|---|
83f52ee9eb | |||
d61241a73b | |||
00a4f4d919 | |||
95ffed3586 | |||
3815c756d6 | |||
4cf3680ff4 | |||
1a386520e6 | |||
232c6411fd | |||
0bd4464e4d | |||
f704b55adf | |||
6c3e8a6373 | |||
b22c3ae12f | |||
40da35654c | |||
829232a60c | |||
4f3bf039c9 | |||
1f4b97fa00 | |||
61301fdbf5 | |||
c980250529 | |||
bdb4b02016 | |||
62039a00d3 | |||
95c66fd4a6 | |||
|
fb9bfbaeeb | ||
|
09668f4fcb | ||
|
4c7a6165ed | ||
|
12d0d2613f | ||
|
42ee78cd3d | ||
|
58d308c1ed | ||
|
29e1bcc53d | ||
|
3531574e5e | ||
|
b9721218ab | ||
|
a119ea6a29 | ||
|
95ef5f5f32 | ||
|
b81b18645b | ||
|
ce591f1b43 | ||
|
28afa4b3ce | ||
|
89a24d71a6 | ||
f69630b668 | |||
|
a2d34a883b | ||
|
5c79b887d8 | ||
|
d0475bf659 | ||
|
85c9576699 | ||
|
2483450a1f | ||
|
519d5eed06 | ||
|
d87965775a | ||
|
8c6f81aa00 | ||
|
6ea0ab0968 | ||
|
70ab3ecdbe | ||
|
a24ae616df | ||
|
cc838f614f | ||
|
ae62a9f378 | ||
|
1b5778a58c | ||
|
27a689e7b8 | ||
|
8e6c490c62 | ||
|
ccda70fe53 | ||
|
26c42e4f0d | ||
|
3244dd52eb | ||
|
4c6807a0c2 | ||
|
03a6a2749a | ||
|
82f69456e9 | ||
|
6beda371fb | ||
|
5b9cb2efc5 | ||
|
733968723c | ||
|
63c81d67f2 | ||
|
ad8ba1dd24 | ||
|
04f821e3e1 | ||
|
4937985e2c | ||
|
5fd7d83a70 | ||
|
699a19d202 | ||
|
0b42938197 | ||
|
1e2bfb6216 | ||
|
f755a4b23f | ||
|
4f58e12fca | ||
|
588cf89531 | ||
|
4fe2dd2706 | ||
|
11ad605cd6 | ||
|
fa9f160bd1 | ||
|
dae1fad54e | ||
|
52200188d4 | ||
|
0d74b9ef0b | ||
|
549163d274 | ||
|
755018c783 | ||
|
2a1c4b3a1c | ||
|
aae0d825e7 | ||
|
855dbfab16 | ||
|
f3a357c559 | ||
|
63995e8c61 | ||
|
7062d9b8f8 | ||
|
ede62f5c75 | ||
|
6386770ff3 | ||
|
b4e831a81f | ||
|
9b283360ce | ||
|
fabbd4d156 | ||
|
9bea5bf5f7 | ||
|
9fbc990493 | ||
|
b05fdaaa73 | ||
|
52d5d4d64c | ||
|
6ec712466c | ||
|
6616b8dc07 | ||
|
807457c718 | ||
|
13d2a40903 | ||
|
022b196d6a | ||
|
7a64216830 | ||
|
bf7d2bd465 | ||
|
6e97b591de | ||
|
fc7c470966 | ||
|
51492ca121 | ||
|
ce1df84330 | ||
|
9b43e2a715 | ||
|
732cabd6aa | ||
|
7c14801d5c | ||
|
66df09c065 | ||
|
8fd6985316 | ||
|
feac001499 | ||
|
4c47e3a8eb | ||
|
7773b625a5 | ||
|
abe1abec72 | ||
|
023838a96b | ||
|
f4e743acc5 | ||
|
06d58cbda5 | ||
|
d199dea204 | ||
|
5722060ed9 | ||
|
d4f7e727c1 | ||
|
ba9afc3f8e | ||
|
39897bd012 | ||
|
575d563154 | ||
|
2bf46ce2f3 | ||
|
b49a185efc | ||
|
e305ebd1ed | ||
|
6f5c9826f4 | ||
|
6025eaaca9 | ||
|
b2441c3cca | ||
|
a73599e9ee | ||
|
58398f40fa | ||
|
43f5d5a8e8 | ||
|
b20cbd3558 | ||
|
7f835a407c | ||
|
9859974db7 | ||
|
6c24ddd1a0 | ||
|
bd92ef08ec | ||
|
8de6107a17 | ||
|
e639f03ac7 | ||
|
f9aac442c1 | ||
|
5fdf675168 | ||
|
232f81ff48 | ||
|
1c4a6b2274 | ||
|
deb71f8efc | ||
|
4596596d9f | ||
|
bbb6d42f8d | ||
|
01efd0f9f0 | ||
|
2d7597d206 | ||
|
221741c891 | ||
|
9f69beb450 | ||
|
e843d9e9c3 | ||
|
008b3d94fb | ||
|
6756d83c55 | ||
|
b52072a2e3 | ||
|
3afb00d563 | ||
|
a30c94fd2f | ||
|
f50c29a285 | ||
|
f238b0f138 | ||
|
d0a8c98040 | ||
|
b3d161ad97 | ||
|
fce9cf2077 | ||
|
efd0e56697 | ||
|
b94a9bb9d9 | ||
|
c153cc5a29 | ||
|
a680e9a25a | ||
|
3c18c4ef22 | ||
|
c4e11b5976 | ||
|
65be51dd48 | ||
|
9684103f97 | ||
|
f4c647d430 | ||
|
f1eab3b9c1 | ||
|
545d52bd93 | ||
|
4e42198c09 | ||
|
44736a51b9 | ||
|
e675ffd623 | ||
|
1f14e06bd3 | ||
|
9871078269 | ||
|
5e7a7f4d62 | ||
|
7d690c6809 | ||
|
6edb4266d5 | ||
|
799e82d501 | ||
|
a2370bff68 | ||
|
c72f3bcd12 | ||
|
295964a74d | ||
|
d77cda0fae | ||
|
6da072d47d | ||
|
471a5a1b3e | ||
|
cd5da2d237 | ||
|
62f67cd626 | ||
|
70db96d9e5 | ||
|
98470111fb | ||
|
557a3bb01f | ||
|
dee70acdcb | ||
|
862b16879c | ||
|
ed7249558e | ||
|
4f6c6f4d10 | ||
|
650d02d9b3 | ||
|
e4041a2f69 | ||
|
4c284a6d13 | ||
|
e14fc801bd | ||
|
0478d468e0 | ||
|
4ac98710fb | ||
|
f256f6417e | ||
|
ca94d55b62 | ||
|
c11c061113 | ||
|
c15c3eb802 | ||
|
0ce102b782 | ||
|
cc48207a99 | ||
|
353ea5fc5d | ||
|
64138310cc | ||
|
1c4538af72 | ||
|
755b47ef19 | ||
|
c78a5d3cab | ||
|
b9b8d30f3b | ||
|
9be93212c3 | ||
|
89973809af | ||
|
e324af356d | ||
|
f51fc6ed47 | ||
|
ecce98289a | ||
|
23c14aa2e4 | ||
|
678d04c5db | ||
|
691ba75372 | ||
|
d2d7bbc632 | ||
|
b3b1a6bdb9 | ||
|
310125ea01 | ||
|
208d1cbba2 | ||
|
e94154ba80 | ||
|
582fbdd9e7 | ||
|
dd175912f4 | ||
|
a6a0ae7a51 | ||
|
8cdac91a01 | ||
|
4c89f41daa | ||
|
512e826a42 | ||
|
bc0d277a21 | ||
|
169fe5fc5b | ||
|
30867702a4 | ||
|
6131f92ae6 | ||
|
823a52583c | ||
|
e2c6c0539f | ||
|
f7f1c0e90d | ||
|
eca12607dd | ||
|
006e3e11f9 | ||
|
a9982cbdca | ||
|
0fa9c5a2a2 | ||
|
cdcc9729d3 | ||
|
4acf651aa7 | ||
|
4bba791c65 | ||
|
662688d3b9 | ||
|
21a3e8fdc4 | ||
|
3815a1d538 | ||
|
cbe0c5cfec | ||
|
15db9b30e1 | ||
|
e891294c0f | ||
|
f6b9e7cc26 | ||
|
052fd7162f | ||
|
189acb73f5 | ||
|
ec7c1677b4 | ||
|
a9474c8e67 | ||
|
3a70dfc5f3 | ||
|
669177d803 | ||
|
b1f43b061c | ||
|
7ff3c84deb | ||
|
ee642b63ce | ||
|
17315e5096 | ||
|
4e9d52fc62 | ||
|
d7e87f8fc8 | ||
|
3efe11f393 | ||
|
26c6c464d8 | ||
|
4db654e653 | ||
|
048759d374 | ||
|
db2424057f | ||
|
472a53e3b9 | ||
|
9e15d91900 | ||
|
d5cff281c0 | ||
|
57b6c4dffb | ||
|
908a2d1d8c | ||
|
69bdea9273 | ||
|
5b21a653ee | ||
|
cfddcf1630 | ||
|
f009687ddf | ||
|
6ddfe29465 | ||
|
715c51f673 | ||
|
b443e8f06a | ||
|
0bd0466c9c | ||
|
ad5db3c9e5 | ||
|
fa3182cb5e | ||
|
3f44bed66e | ||
|
2a70530d0f | ||
|
7c542d5fc7 | ||
|
638dfb7777 | ||
|
1323536a63 | ||
|
419212e2d4 | ||
|
5f1c234a7d | ||
|
db1e8301cd | ||
|
bf94a3c68d | ||
|
96baa4ffc6 | ||
|
7d472afe61 | ||
|
f32a4d33a7 | ||
|
0722991955 | ||
|
bcc740cdbc | ||
|
5cf46097f7 | ||
|
61dc189f8b | ||
|
23c2b008c9 | ||
|
db14afdf3a | ||
|
b7927336d1 | ||
|
ee23a3d4cd | ||
|
63c0112ffb | ||
|
db08d7d280 | ||
|
9892525fbc | ||
|
34b87ff6bf | ||
|
241ad68bd5 | ||
|
a0ec18921b | ||
|
45e17eb0b2 | ||
|
59f0e9ae67 | ||
|
af24611c73 | ||
|
d4502dda3f | ||
|
c0efa8af5d | ||
|
1c06a3fc89 | ||
|
c19fb38d1c | ||
|
5dc1de9daf | ||
|
6774301938 | ||
|
4ef6cf0428 | ||
|
ca5f8e4b44 | ||
|
1907f03abe | ||
|
6351a4e4f3 | ||
|
fa34c3937f | ||
|
cdac97ebf5 | ||
|
fe958d28b8 | ||
|
f71982e1d5 | ||
|
cb2bfcea53 | ||
|
4a9d5bbceb | ||
|
10809eade6 | ||
|
43d63527f8 | ||
|
df51eb54ed | ||
|
b47109ab4d | ||
|
15b2b68940 | ||
|
62a239f6fe | ||
|
d89bc95a0a | ||
|
2a76f21b31 | ||
|
f07e22d742 | ||
|
058ab7a1ea | ||
|
fae3baa640 | ||
|
2c4da9c634 | ||
|
8de0313aca | ||
|
143c5b17f9 | ||
|
ec32fde60d | ||
|
f2ac5d4995 | ||
|
716962af03 | ||
|
156efde6b9 | ||
|
a9b7716dfe | ||
|
76a67a6715 | ||
|
c3defdcda4 | ||
|
d8092aa916 | ||
|
8b5a3d31aa | ||
|
11761b66b2 | ||
|
f83f107bd1 | ||
|
f1b90857ff | ||
|
5aea4cdd65 | ||
|
1822a59c70 | ||
|
37c6934802 | ||
|
90c7f747a4 | ||
|
b7efa3dcd6 | ||
|
da80f537ac | ||
|
0119912318 | ||
|
5e0b1d0161 | ||
|
35145d100b | ||
|
584dd0ba89 | ||
|
e5f5dc56c9 | ||
|
880efb012a | ||
|
b95308ac24 | ||
|
3b192ad357 | ||
|
b04938ac5e | ||
|
56410ac1f2 | ||
|
45a2eadc58 | ||
|
0e03151505 | ||
|
3e9706e6ce | ||
|
a1646a7a88 | ||
|
434511658b | ||
|
04230fdd9c | ||
|
2e16ad8a2a | ||
|
7fb59b0fa9 | ||
|
24e044bcda | ||
|
1093656ec5 | ||
|
674e997060 | ||
|
37fd124f56 | ||
|
7df2e67312 | ||
|
8ea1f0796c | ||
|
00fb5bc6cf | ||
|
5e01f726d3 | ||
|
e87290aeea | ||
|
e7236beedd | ||
|
5a6f54c96c | ||
|
7769985439 | ||
|
f4afdb21b2 | ||
|
cc1b9e0a50 | ||
|
2c58740cbb | ||
|
808533b110 | ||
|
e04a15bb99 | ||
|
26d4074a61 | ||
|
0137de5ca2 | ||
|
b0a1b2edba | ||
|
355c560ddc | ||
|
72f286d9c6 | ||
|
db6786414a | ||
|
f8f046f193 | ||
|
6c9ad4ded2 | ||
|
32cae8ca11 | ||
|
0cb65279d9 | ||
|
412da06554 | ||
|
247f8a2778 | ||
|
017c9a6a70 | ||
|
eccb2430b5 | ||
|
5c64ebf1cc | ||
|
1d7796805c | ||
|
3479aaf6f6 | ||
|
d2071cf05c |
1
.gitattributes
vendored
Normal file
1
.gitattributes
vendored
Normal file
@ -0,0 +1 @@
|
|||||||
|
* text=auto eol=lf
|
87
.github/workflows/runUiOctopusTests.yml
vendored
Normal file
87
.github/workflows/runUiOctopusTests.yml
vendored
Normal file
@ -0,0 +1,87 @@
|
|||||||
|
name: Run Non Octopus UI Tests
|
||||||
|
on:
|
||||||
|
workflow_dispatch:
|
||||||
|
schedule:
|
||||||
|
- cron: '0 12 * * *'
|
||||||
|
jobs:
|
||||||
|
build-for-ui-test-mac-os:
|
||||||
|
if: github.repository == 'JetBrains/ideavim'
|
||||||
|
runs-on: macos-latest
|
||||||
|
steps:
|
||||||
|
- uses: actions/checkout@v4
|
||||||
|
- name: Apply Patch
|
||||||
|
run: |
|
||||||
|
git apply tests/ui-ij-tests/src/test/kotlin/ui/octopus.patch
|
||||||
|
- name: Setup Java
|
||||||
|
uses: actions/setup-java@v4
|
||||||
|
with:
|
||||||
|
distribution: zulu
|
||||||
|
java-version: 17
|
||||||
|
- name: Setup FFmpeg
|
||||||
|
uses: FedericoCarboni/setup-ffmpeg@v3
|
||||||
|
with:
|
||||||
|
# Not strictly necessary, but it may prevent rate limit
|
||||||
|
# errors especially on GitHub-hosted macos machines.
|
||||||
|
github-token: ${{ secrets.GITHUB_TOKEN }}
|
||||||
|
- name: Setup Gradle
|
||||||
|
uses: gradle/gradle-build-action@v2.4.2
|
||||||
|
- name: Build Plugin
|
||||||
|
run: gradle :buildPlugin
|
||||||
|
- name: Run Idea
|
||||||
|
run: |
|
||||||
|
mkdir -p build/reports
|
||||||
|
gradle runIdeForUiTests > build/reports/idea.log &
|
||||||
|
- name: Wait for Idea started
|
||||||
|
uses: jtalk/url-health-check-action@v3
|
||||||
|
with:
|
||||||
|
url: http://127.0.0.1:8082
|
||||||
|
max-attempts: 20
|
||||||
|
retry-delay: 10s
|
||||||
|
- name: Tests
|
||||||
|
run: gradle :tests:ui-ij-tests:testUi
|
||||||
|
- name: Move video
|
||||||
|
if: always()
|
||||||
|
run: mv tests/ui-ij-tests/video build/reports
|
||||||
|
- name: Move sandbox logs
|
||||||
|
if: always()
|
||||||
|
run: mv build/idea-sandbox/system/log sandbox-idea-log
|
||||||
|
- name: Save report
|
||||||
|
if: always()
|
||||||
|
uses: actions/upload-artifact@v4
|
||||||
|
with:
|
||||||
|
name: ui-test-fails-report-mac
|
||||||
|
path: |
|
||||||
|
build/reports
|
||||||
|
sandbox-idea-log
|
||||||
|
# build-for-ui-test-linux:
|
||||||
|
# runs-on: ubuntu-latest
|
||||||
|
# steps:
|
||||||
|
# - uses: actions/checkout@v2
|
||||||
|
# - name: Setup Java
|
||||||
|
# uses: actions/setup-java@v2.1.0
|
||||||
|
# with:
|
||||||
|
# distribution: zulu
|
||||||
|
# java-version: 11
|
||||||
|
# - name: Build Plugin
|
||||||
|
# run: gradle :buildPlugin
|
||||||
|
# - name: Run Idea
|
||||||
|
# run: |
|
||||||
|
# export DISPLAY=:99.0
|
||||||
|
# Xvfb -ac :99 -screen 0 1920x1080x16 &
|
||||||
|
# mkdir -p build/reports
|
||||||
|
# gradle :runIdeForUiTests #> build/reports/idea.log
|
||||||
|
# - name: Wait for Idea started
|
||||||
|
# uses: jtalk/url-health-check-action@1.5
|
||||||
|
# with:
|
||||||
|
# url: http://127.0.0.1:8082
|
||||||
|
# max-attempts: 15
|
||||||
|
# retry-delay: 30s
|
||||||
|
# - name: Tests
|
||||||
|
# run: gradle :testUi
|
||||||
|
# - name: Save fails report
|
||||||
|
# if: ${{ failure() }}
|
||||||
|
# uses: actions/upload-artifact@v2
|
||||||
|
# with:
|
||||||
|
# name: ui-test-fails-report-linux
|
||||||
|
# path: |
|
||||||
|
# ui-test-example/build/reports
|
55
.github/workflows/runUiPyTests.yml
vendored
Normal file
55
.github/workflows/runUiPyTests.yml
vendored
Normal file
@ -0,0 +1,55 @@
|
|||||||
|
name: Run UI PyCharm Tests
|
||||||
|
on:
|
||||||
|
workflow_dispatch:
|
||||||
|
schedule:
|
||||||
|
- cron: '0 12 * * *'
|
||||||
|
jobs:
|
||||||
|
build-for-ui-test-mac-os:
|
||||||
|
if: github.repository == 'JetBrains/ideavim'
|
||||||
|
runs-on: macos-latest
|
||||||
|
steps:
|
||||||
|
- uses: actions/checkout@v4
|
||||||
|
- name: Setup Java
|
||||||
|
uses: actions/setup-java@v4
|
||||||
|
with:
|
||||||
|
distribution: zulu
|
||||||
|
java-version: 17
|
||||||
|
- uses: actions/setup-python@v5
|
||||||
|
with:
|
||||||
|
python-version: '3.10'
|
||||||
|
- name: Setup FFmpeg
|
||||||
|
uses: FedericoCarboni/setup-ffmpeg@v3
|
||||||
|
with:
|
||||||
|
# Not strictly necessary, but it may prevent rate limit
|
||||||
|
# errors especially on GitHub-hosted macos machines.
|
||||||
|
github-token: ${{ secrets.GITHUB_TOKEN }}
|
||||||
|
- name: Setup Gradle
|
||||||
|
uses: gradle/gradle-build-action@v2.4.2
|
||||||
|
- name: Build Plugin
|
||||||
|
run: gradle :buildPlugin
|
||||||
|
- name: Run Idea
|
||||||
|
run: |
|
||||||
|
mkdir -p build/reports
|
||||||
|
gradle :runIdeForUiTests -PideaType=PC > build/reports/idea.log &
|
||||||
|
- name: Wait for Idea started
|
||||||
|
uses: jtalk/url-health-check-action@v3
|
||||||
|
with:
|
||||||
|
url: http://127.0.0.1:8082
|
||||||
|
max-attempts: 20
|
||||||
|
retry-delay: 10s
|
||||||
|
- name: Tests
|
||||||
|
run: gradle :tests:ui-py-tests:testUi
|
||||||
|
- name: Move video
|
||||||
|
if: always()
|
||||||
|
run: mv tests/ui-py-tests/video build/reports
|
||||||
|
- name: Move sandbox logs
|
||||||
|
if: always()
|
||||||
|
run: mv build/idea-sandbox/system/log sandbox-idea-log
|
||||||
|
- name: Save report
|
||||||
|
if: always()
|
||||||
|
uses: actions/upload-artifact@v4
|
||||||
|
with:
|
||||||
|
name: ui-test-fails-report-mac
|
||||||
|
path: |
|
||||||
|
build/reports
|
||||||
|
sandbox-idea-log
|
8
.github/workflows/runUiTests.yml
vendored
8
.github/workflows/runUiTests.yml
vendored
@ -13,7 +13,7 @@ jobs:
|
|||||||
uses: actions/setup-java@v4
|
uses: actions/setup-java@v4
|
||||||
with:
|
with:
|
||||||
distribution: zulu
|
distribution: zulu
|
||||||
java-version: 11
|
java-version: 17
|
||||||
- name: Setup FFmpeg
|
- name: Setup FFmpeg
|
||||||
uses: FedericoCarboni/setup-ffmpeg@v3
|
uses: FedericoCarboni/setup-ffmpeg@v3
|
||||||
with:
|
with:
|
||||||
@ -27,7 +27,7 @@ jobs:
|
|||||||
- name: Run Idea
|
- name: Run Idea
|
||||||
run: |
|
run: |
|
||||||
mkdir -p build/reports
|
mkdir -p build/reports
|
||||||
gradle :runIdeForUiTests > build/reports/idea.log &
|
gradle 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:
|
||||||
@ -35,10 +35,10 @@ jobs:
|
|||||||
max-attempts: 20
|
max-attempts: 20
|
||||||
retry-delay: 10s
|
retry-delay: 10s
|
||||||
- name: Tests
|
- name: Tests
|
||||||
run: gradle :testUi
|
run: gradle :tests:ui-ij-tests:testUi
|
||||||
- name: Move video
|
- name: Move video
|
||||||
if: always()
|
if: always()
|
||||||
run: mv 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/system/log sandbox-idea-log
|
||||||
|
1
.gitignore
vendored
1
.gitignore
vendored
@ -23,6 +23,7 @@
|
|||||||
|
|
||||||
# Generated by gradle task "generateGrammarSource"
|
# Generated by gradle task "generateGrammarSource"
|
||||||
src/main/java/com/maddyhome/idea/vim/vimscript/parser/generated
|
src/main/java/com/maddyhome/idea/vim/vimscript/parser/generated
|
||||||
|
vim-engine/src/main/java/com/maddyhome/idea/vim/regexp/parser/generated
|
||||||
# Generated JSONs for lazy classloading
|
# Generated JSONs for lazy classloading
|
||||||
/vim-engine/src/main/resources/ksp-generated
|
/vim-engine/src/main/resources/ksp-generated
|
||||||
/src/main/resources/ksp-generated
|
/src/main/resources/ksp-generated
|
||||||
|
@ -6,6 +6,7 @@
|
|||||||
<option name="CONTINUATION_INDENT_SIZE" value="4" />
|
<option name="CONTINUATION_INDENT_SIZE" value="4" />
|
||||||
</value>
|
</value>
|
||||||
</option>
|
</option>
|
||||||
|
<option name="LINE_SEPARATOR" value=" " />
|
||||||
<JavaCodeStyleSettings>
|
<JavaCodeStyleSettings>
|
||||||
<option name="FIELD_NAME_PREFIX" value="my" />
|
<option name="FIELD_NAME_PREFIX" value="my" />
|
||||||
<option name="STATIC_FIELD_NAME_PREFIX" value="our" />
|
<option name="STATIC_FIELD_NAME_PREFIX" value="our" />
|
||||||
|
2
.teamcity/_Self/Project.kt
vendored
2
.teamcity/_Self/Project.kt
vendored
@ -15,7 +15,7 @@ import jetbrains.buildServer.configs.kotlin.v2019_2.BuildType
|
|||||||
import jetbrains.buildServer.configs.kotlin.v2019_2.Project
|
import jetbrains.buildServer.configs.kotlin.v2019_2.Project
|
||||||
|
|
||||||
object Project : Project({
|
object Project : Project({
|
||||||
description = "Vim engine for IDEs based on the IntelliJ platform"
|
description = "Vim engine for JetBrains IDEs"
|
||||||
|
|
||||||
subProjects(Releases, OldTests, GitHub)
|
subProjects(Releases, OldTests, GitHub)
|
||||||
|
|
||||||
|
1
.teamcity/_Self/buildTypes/Compatibility.kt
vendored
1
.teamcity/_Self/buildTypes/Compatibility.kt
vendored
@ -34,7 +34,6 @@ object Compatibility : IdeaVimBuildType({
|
|||||||
|
|
||||||
java --version
|
java --version
|
||||||
java -jar verifier1/verifier-cli-dev-all-1.jar check-plugin '${'$'}org.jetbrains.IdeaVim-EasyMotion' [latest-IU] -team-city
|
java -jar verifier1/verifier-cli-dev-all-1.jar check-plugin '${'$'}org.jetbrains.IdeaVim-EasyMotion' [latest-IU] -team-city
|
||||||
java -jar verifier1/verifier-cli-dev-all-1.jar check-plugin '${'$'}io.github.mishkun.ideavimsneak' [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 '${'$'}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
|
||||||
|
2
.teamcity/_Self/buildTypes/LongRunning.kt
vendored
2
.teamcity/_Self/buildTypes/LongRunning.kt
vendored
@ -25,7 +25,7 @@ object LongRunning : IdeaVimBuildType({
|
|||||||
|
|
||||||
steps {
|
steps {
|
||||||
gradle {
|
gradle {
|
||||||
tasks = "clean testLongRunning"
|
tasks = "clean :tests:long-running-tests:testLongRunning"
|
||||||
buildFile = ""
|
buildFile = ""
|
||||||
enableStacktrace = true
|
enableStacktrace = true
|
||||||
}
|
}
|
||||||
|
2
.teamcity/_Self/buildTypes/Nvim.kt
vendored
2
.teamcity/_Self/buildTypes/Nvim.kt
vendored
@ -39,7 +39,7 @@ object Nvim : IdeaVimBuildType({
|
|||||||
""".trimIndent()
|
""".trimIndent()
|
||||||
}
|
}
|
||||||
gradle {
|
gradle {
|
||||||
tasks = "clean testWithNeovim"
|
tasks = "clean test -Dnvim"
|
||||||
buildFile = ""
|
buildFile = ""
|
||||||
enableStacktrace = true
|
enableStacktrace = true
|
||||||
}
|
}
|
||||||
|
2
.teamcity/_Self/buildTypes/PropertyBased.kt
vendored
2
.teamcity/_Self/buildTypes/PropertyBased.kt
vendored
@ -24,7 +24,7 @@ object PropertyBased : IdeaVimBuildType({
|
|||||||
|
|
||||||
steps {
|
steps {
|
||||||
gradle {
|
gradle {
|
||||||
tasks = "clean testPropertyBased"
|
tasks = "clean :tests:property-tests:testPropertyBased"
|
||||||
buildFile = ""
|
buildFile = ""
|
||||||
enableStacktrace = true
|
enableStacktrace = true
|
||||||
}
|
}
|
||||||
|
@ -20,8 +20,8 @@ object PublishVimEngine : IdeaVimBuildType({
|
|||||||
params {
|
params {
|
||||||
param("env.ORG_GRADLE_PROJECT_engineVersion", "%build.number%")
|
param("env.ORG_GRADLE_PROJECT_engineVersion", "%build.number%")
|
||||||
param("env.ORG_GRADLE_PROJECT_uploadUrl", "https://packages.jetbrains.team/maven/p/ij/intellij-dependencies")
|
param("env.ORG_GRADLE_PROJECT_uploadUrl", "https://packages.jetbrains.team/maven/p/ij/intellij-dependencies")
|
||||||
password("env.ORG_GRADLE_PROJECT_spacePassword", "credentialsJSON:790b4e43-ee83-4184-b81b-678afab60409", display = ParameterDisplay.HIDDEN)
|
password("env.ORG_GRADLE_PROJECT_spacePassword", "credentialsJSON:5ea56f8c-efe7-4e1e-83de-0c02bcc39d0b", display = ParameterDisplay.HIDDEN)
|
||||||
param("env.ORG_GRADLE_PROJECT_spaceUsername", "Aleksei.Plate")
|
param("env.ORG_GRADLE_PROJECT_spaceUsername", "a121c67e-39ac-40e6-bf82-649855ec27b6")
|
||||||
}
|
}
|
||||||
|
|
||||||
vcs {
|
vcs {
|
||||||
|
20
.teamcity/_Self/buildTypes/ReleaseEap.kt
vendored
20
.teamcity/_Self/buildTypes/ReleaseEap.kt
vendored
@ -5,6 +5,7 @@ import _Self.Constants.RELEASE_EAP
|
|||||||
import _Self.IdeaVimBuildType
|
import _Self.IdeaVimBuildType
|
||||||
import jetbrains.buildServer.configs.kotlin.v2019_2.CheckoutMode
|
import jetbrains.buildServer.configs.kotlin.v2019_2.CheckoutMode
|
||||||
import jetbrains.buildServer.configs.kotlin.v2019_2.DslContext
|
import jetbrains.buildServer.configs.kotlin.v2019_2.DslContext
|
||||||
|
import jetbrains.buildServer.configs.kotlin.v2019_2.ParameterDisplay
|
||||||
import jetbrains.buildServer.configs.kotlin.v2019_2.buildFeatures.sshAgent
|
import jetbrains.buildServer.configs.kotlin.v2019_2.buildFeatures.sshAgent
|
||||||
import jetbrains.buildServer.configs.kotlin.v2019_2.buildSteps.gradle
|
import jetbrains.buildServer.configs.kotlin.v2019_2.buildSteps.gradle
|
||||||
import jetbrains.buildServer.configs.kotlin.v2019_2.buildSteps.script
|
import jetbrains.buildServer.configs.kotlin.v2019_2.buildSteps.script
|
||||||
@ -30,6 +31,11 @@ object ReleaseEap : IdeaVimBuildType({
|
|||||||
"credentialsJSON:a8ab8150-e6f8-4eaf-987c-bcd65eac50b5",
|
"credentialsJSON:a8ab8150-e6f8-4eaf-987c-bcd65eac50b5",
|
||||||
label = "Slack Token"
|
label = "Slack Token"
|
||||||
)
|
)
|
||||||
|
password(
|
||||||
|
"env.YOUTRACK_TOKEN",
|
||||||
|
"credentialsJSON:2479995b-7b60-4fbb-b095-f0bafae7f622",
|
||||||
|
display = ParameterDisplay.HIDDEN
|
||||||
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
vcs {
|
vcs {
|
||||||
@ -61,26 +67,26 @@ object ReleaseEap : IdeaVimBuildType({
|
|||||||
tasks = "scripts:addReleaseTag"
|
tasks = "scripts:addReleaseTag"
|
||||||
}
|
}
|
||||||
gradle {
|
gradle {
|
||||||
|
name = "Publish plugin"
|
||||||
tasks = "publishPlugin"
|
tasks = "publishPlugin"
|
||||||
}
|
}
|
||||||
// Same as the script below. However, jgit can't find ssh keys on TeamCity
|
|
||||||
// gradle {
|
|
||||||
// name = "Push changes to the repo"
|
|
||||||
// tasks = "scripts:pushChanges"
|
|
||||||
// }
|
|
||||||
script {
|
script {
|
||||||
name = "Push changes to the repo"
|
name = "Push changes to the repo"
|
||||||
scriptContent = """
|
scriptContent = """
|
||||||
branch=$(git branch --show-current)
|
branch=$(git branch --show-current)
|
||||||
echo current branch is ${'$'}branch
|
echo current branch is ${'$'}branch
|
||||||
if [ "master" != "${'$'}branch" ];
|
if [ "master" != "${'$'}branch" ];
|
||||||
then
|
then
|
||||||
exit 1
|
exit 1
|
||||||
fi
|
fi
|
||||||
|
|
||||||
git push origin %build.number%
|
git push origin %build.number%
|
||||||
""".trimIndent()
|
""".trimIndent()
|
||||||
}
|
}
|
||||||
|
gradle {
|
||||||
|
name = "YouTrack post release actions"
|
||||||
|
tasks = "scripts:eapReleaseActions"
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
features {
|
features {
|
||||||
|
72
.teamcity/_Self/buildTypes/ReleasePlugin.kt
vendored
72
.teamcity/_Self/buildTypes/ReleasePlugin.kt
vendored
@ -45,7 +45,11 @@ sealed class ReleasePlugin(private val releaseType: String) : IdeaVimBuildType({
|
|||||||
"credentialsJSON:a8ab8150-e6f8-4eaf-987c-bcd65eac50b5",
|
"credentialsJSON:a8ab8150-e6f8-4eaf-987c-bcd65eac50b5",
|
||||||
label = "Slack Token"
|
label = "Slack Token"
|
||||||
)
|
)
|
||||||
password("env.ORG_GRADLE_PROJECT_youtrackToken", "credentialsJSON:3cd3e867-282c-451f-b958-bc95d56a8450", display = ParameterDisplay.HIDDEN)
|
password(
|
||||||
|
"env.ORG_GRADLE_PROJECT_youtrackToken",
|
||||||
|
"credentialsJSON:7bc0eb3a-b86a-4ebd-b622-d4ef12d7e1d3",
|
||||||
|
display = ParameterDisplay.HIDDEN
|
||||||
|
)
|
||||||
param("env.ORG_GRADLE_PROJECT_releaseType", releaseType)
|
param("env.ORG_GRADLE_PROJECT_releaseType", releaseType)
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -65,9 +69,25 @@ sealed class ReleasePlugin(private val releaseType: String) : IdeaVimBuildType({
|
|||||||
name = "Pull git history"
|
name = "Pull git history"
|
||||||
scriptContent = "git fetch --unshallow"
|
scriptContent = "git fetch --unshallow"
|
||||||
}
|
}
|
||||||
gradle {
|
script {
|
||||||
name = "Select branch"
|
name = "Reset release branch"
|
||||||
tasks = "scripts:selectBranch"
|
scriptContent = """
|
||||||
|
if [ "major" = "$releaseType" ] || [ "minor" = "$releaseType" ]
|
||||||
|
then
|
||||||
|
echo Resetting the release branch because the release type is $releaseType
|
||||||
|
git checkout master
|
||||||
|
latest_eap=${'$'}(git describe --tags --match="[0-9].[0-9]*.[0-9]-eap.[0-9]*" --abbrev=0 HEAD)
|
||||||
|
echo Latest EAP: ${'$'}latest_eap
|
||||||
|
commit_of_latest_eap=${'$'}(git rev-list -n 1 ${'$'}latest_eap)
|
||||||
|
echo Commit of latest EAP: ${'$'}commit_of_latest_eap
|
||||||
|
git checkout release
|
||||||
|
git reset --hard ${'$'}commit_of_latest_eap
|
||||||
|
else
|
||||||
|
git checkout release
|
||||||
|
echo Do not reset the release branch because the release type is $releaseType
|
||||||
|
fi
|
||||||
|
echo Checked out release branch
|
||||||
|
""".trimIndent()
|
||||||
}
|
}
|
||||||
gradle {
|
gradle {
|
||||||
name = "Calculate new version"
|
name = "Calculate new version"
|
||||||
@ -89,41 +109,47 @@ sealed class ReleasePlugin(private val releaseType: String) : IdeaVimBuildType({
|
|||||||
name = "Add release tag"
|
name = "Add release tag"
|
||||||
tasks = "scripts:addReleaseTag"
|
tasks = "scripts:addReleaseTag"
|
||||||
}
|
}
|
||||||
gradle {
|
script {
|
||||||
name = "Reset release branch"
|
name = "Run tests"
|
||||||
tasks = "scripts:resetReleaseBranch"
|
scriptContent = "./gradlew test"
|
||||||
}
|
}
|
||||||
gradle {
|
gradle {
|
||||||
name = "Publish release"
|
name = "Publish release"
|
||||||
tasks = "publishPlugin"
|
tasks = "publishPlugin"
|
||||||
}
|
}
|
||||||
// gradle {
|
script {
|
||||||
// name = "Push changes to the repo"
|
name = "Checkout master branch"
|
||||||
// tasks = "scripts:pushChangesWithReleaseBranch"
|
scriptContent = """
|
||||||
// }
|
echo Checkout master
|
||||||
|
git checkout master
|
||||||
|
""".trimIndent()
|
||||||
|
}
|
||||||
|
gradle {
|
||||||
|
name = "Update change log in master"
|
||||||
|
tasks = "scripts:changelogUpdateUnreleased"
|
||||||
|
}
|
||||||
|
gradle {
|
||||||
|
name = "Commit preparation changes in master"
|
||||||
|
tasks = "scripts:commitChanges"
|
||||||
|
}
|
||||||
script {
|
script {
|
||||||
name = "Push changes to the repo"
|
name = "Push changes to the repo"
|
||||||
scriptContent = """
|
scriptContent = """
|
||||||
branch=$(git branch --show-current)
|
branch=$(git branch --show-current)
|
||||||
echo current branch is ${'$'}branch
|
echo Current branch is ${'$'}branch
|
||||||
if [ "master" != "${'$'}branch" ];
|
if [ "master" != "${'$'}branch" ];
|
||||||
then
|
then
|
||||||
git checkout master
|
git checkout master
|
||||||
fi
|
fi
|
||||||
|
|
||||||
git push origin --tags
|
|
||||||
git push origin
|
git push origin
|
||||||
|
|
||||||
if [ "patch" != $releaseType ];
|
git checkout release
|
||||||
then
|
echo checkout release branch
|
||||||
git checkout release
|
git branch --set-upstream-to=origin/release release
|
||||||
echo checkout release branch
|
git push origin --force
|
||||||
git branch --set-upstream-to=origin/release release
|
# Push tag
|
||||||
git push --tags
|
git push origin %build.number%
|
||||||
git push origin --force
|
|
||||||
fi
|
|
||||||
|
|
||||||
git checkout ${'$'}branch
|
|
||||||
""".trimIndent()
|
""".trimIndent()
|
||||||
}
|
}
|
||||||
gradle {
|
gradle {
|
||||||
|
19
.teamcity/patches/buildTypes/PublishVimEngine.kts
vendored
Normal file
19
.teamcity/patches/buildTypes/PublishVimEngine.kts
vendored
Normal file
@ -0,0 +1,19 @@
|
|||||||
|
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 = 'PublishVimEngine'
|
||||||
|
accordingly, and delete the patch script.
|
||||||
|
*/
|
||||||
|
changeBuildType(RelativeId("PublishVimEngine")) {
|
||||||
|
vcs {
|
||||||
|
|
||||||
|
check(branchFilter == "+:<default>") {
|
||||||
|
"Unexpected option value: branchFilter = $branchFilter"
|
||||||
|
}
|
||||||
|
branchFilter = "+:fleet"
|
||||||
|
}
|
||||||
|
}
|
20
.teamcity/patches/buildTypes/ReleaseMinor.kts
vendored
20
.teamcity/patches/buildTypes/ReleaseMinor.kts
vendored
@ -1,20 +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 = 'ReleaseMinor'
|
|
||||||
accordingly, and delete the patch script.
|
|
||||||
*/
|
|
||||||
changeBuildType(RelativeId("ReleaseMinor")) {
|
|
||||||
params {
|
|
||||||
expect {
|
|
||||||
password("env.ORG_GRADLE_PROJECT_youtrackToken", "credentialsJSON:3cd3e867-282c-451f-b958-bc95d56a8450", display = ParameterDisplay.HIDDEN)
|
|
||||||
}
|
|
||||||
update {
|
|
||||||
password("env.ORG_GRADLE_PROJECT_youtrackToken", "credentialsJSON:7bc0eb3a-b86a-4ebd-b622-d4ef12d7e1d3", display = ParameterDisplay.HIDDEN)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
20
.teamcity/patches/buildTypes/ReleasePatch.kts
vendored
20
.teamcity/patches/buildTypes/ReleasePatch.kts
vendored
@ -1,20 +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 = 'ReleasePatch'
|
|
||||||
accordingly, and delete the patch script.
|
|
||||||
*/
|
|
||||||
changeBuildType(RelativeId("ReleasePatch")) {
|
|
||||||
params {
|
|
||||||
expect {
|
|
||||||
password("env.ORG_GRADLE_PROJECT_youtrackToken", "credentialsJSON:3cd3e867-282c-451f-b958-bc95d56a8450", display = ParameterDisplay.HIDDEN)
|
|
||||||
}
|
|
||||||
update {
|
|
||||||
password("env.ORG_GRADLE_PROJECT_youtrackToken", "credentialsJSON:7bc0eb3a-b86a-4ebd-b622-d4ef12d7e1d3", display = ParameterDisplay.HIDDEN)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
17
.teamcity/patches/projects/_Self.kts
vendored
17
.teamcity/patches/projects/_Self.kts
vendored
@ -1,17 +0,0 @@
|
|||||||
package patches.projects
|
|
||||||
|
|
||||||
import jetbrains.buildServer.configs.kotlin.v2019_2.*
|
|
||||||
import jetbrains.buildServer.configs.kotlin.v2019_2.Project
|
|
||||||
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 root project
|
|
||||||
accordingly, and delete the patch script.
|
|
||||||
*/
|
|
||||||
changeProject(DslContext.projectId) {
|
|
||||||
check(description == "Vim engine for IDEs based on the IntelliJ platform") {
|
|
||||||
"Unexpected description: '$description'"
|
|
||||||
}
|
|
||||||
description = "Vim engine for JetBrains IDEs"
|
|
||||||
}
|
|
2
.teamcity/settings.kts
vendored
2
.teamcity/settings.kts
vendored
@ -30,5 +30,5 @@ node (Plugins -> teamcity-configs -> teamcity-configs:generate),
|
|||||||
the 'Debug' option is available in the context menu for the task.
|
the 'Debug' option is available in the context menu for the task.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
version = "2023.05"
|
version = "2023.11"
|
||||||
project(_Self.Project)
|
project(_Self.Project)
|
||||||
|
@ -491,6 +491,10 @@ Contributors:
|
|||||||
[![icon][github]](https://github.com/Infonautica)
|
[![icon][github]](https://github.com/Infonautica)
|
||||||
|
|
||||||
Leonid Danilov
|
Leonid Danilov
|
||||||
|
* [![icon][mail]](mailto:emanuel-367@hotmail.com)
|
||||||
|
[![icon][github]](https://github.com/emanuelgestosa)
|
||||||
|
|
||||||
|
Emanuel Gestosa
|
||||||
|
|
||||||
Previous contributors:
|
Previous contributors:
|
||||||
|
|
||||||
|
11
CHANGES.md
11
CHANGES.md
@ -25,6 +25,15 @@ usual beta standards.
|
|||||||
|
|
||||||
## To Be Released
|
## To Be Released
|
||||||
|
|
||||||
|
### Fixes:
|
||||||
|
* [VIM-3055](https://youtrack.jetbrains.com/issue/VIM-3055) Fix the issue with double deleting after dot
|
||||||
|
|
||||||
|
### Merged PRs:
|
||||||
|
* [725](https://github.com/JetBrains/ideavim/pull/725) by [Emanuel Gestosa](https://github.com/emanuelgestosa): Regex
|
||||||
|
* [805](https://github.com/JetBrains/ideavim/pull/805) by [chylex](https://github.com/chylex): VIM-3238 Fix recording a macro that replays another macro
|
||||||
|
|
||||||
|
## 2.8.0, 2024-01-30
|
||||||
|
|
||||||
### Fixes:
|
### Fixes:
|
||||||
* [VIM-3130](https://youtrack.jetbrains.com/issue/VIM-3130) Change the build version to 2023.1.2
|
* [VIM-3130](https://youtrack.jetbrains.com/issue/VIM-3130) Change the build version to 2023.1.2
|
||||||
* [VIM-3168](https://youtrack.jetbrains.com/issue/VIM-3168) Do not switch to block caret after enter if the IdeaVim is disabled
|
* [VIM-3168](https://youtrack.jetbrains.com/issue/VIM-3168) Do not switch to block caret after enter if the IdeaVim is disabled
|
||||||
@ -44,6 +53,8 @@ usual beta standards.
|
|||||||
* [VIM-3206](https://youtrack.jetbrains.com/issue/VIM-3206) Disable both copilot suggestion and insert mode on a single escape
|
* [VIM-3206](https://youtrack.jetbrains.com/issue/VIM-3206) Disable both copilot suggestion and insert mode on a single escape
|
||||||
* [VIM-3090](https://youtrack.jetbrains.com/issue/VIM-3090) Cmd line mode saves the visual mode
|
* [VIM-3090](https://youtrack.jetbrains.com/issue/VIM-3090) Cmd line mode saves the visual mode
|
||||||
* [VIM-3085](https://youtrack.jetbrains.com/issue/VIM-3085) Open access to VimTypedActionHandler and VimShortcutKeyAction
|
* [VIM-3085](https://youtrack.jetbrains.com/issue/VIM-3085) Open access to VimTypedActionHandler and VimShortcutKeyAction
|
||||||
|
* [VIM-3260](https://youtrack.jetbrains.com/issue/VIM-3260) Processing the offsets at the file end
|
||||||
|
* [VIM-3183](https://youtrack.jetbrains.com/issue/VIM-3183) Execute .ideavimrc on pooled thread
|
||||||
|
|
||||||
### Merged PRs:
|
### Merged PRs:
|
||||||
* [763](https://github.com/JetBrains/ideavim/pull/763) by [Sam Ng](https://github.com/samabcde): Fix(VIM-3176) add test for restore selection after pasting in/below s…
|
* [763](https://github.com/JetBrains/ideavim/pull/763) by [Sam Ng](https://github.com/samabcde): Fix(VIM-3176) add test for restore selection after pasting in/below s…
|
||||||
|
@ -84,3 +84,8 @@ IV) It is not allowed to remove this license from the distribution of the Vim
|
|||||||
license for previous Vim releases instead of the license that they came
|
license for previous Vim releases instead of the license that they came
|
||||||
with, at your option.
|
with, at your option.
|
||||||
```
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
File [sneakIcon.png](doc/images/sneakIcon.svg), which is originally an icon of the ideavim-sneak plugin,
|
||||||
|
is merged icons of IdeaVim plugin and a random sneaker by FreePic from flaticon.com.
|
||||||
|
@ -8,7 +8,7 @@
|
|||||||
|
|
||||||
plugins {
|
plugins {
|
||||||
kotlin("jvm")
|
kotlin("jvm")
|
||||||
kotlin("plugin.serialization") version "1.8.21"
|
kotlin("plugin.serialization") version "1.9.22"
|
||||||
}
|
}
|
||||||
|
|
||||||
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:1.9.22-1.0.16")
|
compileOnly("com.google.devtools.ksp:symbol-processing-api:1.9.22-1.0.17")
|
||||||
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")
|
||||||
|
1354
build.gradle.kts
1354
build.gradle.kts
File diff suppressed because it is too large
Load Diff
@ -44,16 +44,21 @@ All commands with the mappings are supported. See the [full list of supported co
|
|||||||
|
|
||||||
<details>
|
<details>
|
||||||
<summary><h2>sneak</h2></summary>
|
<summary><h2>sneak</h2></summary>
|
||||||
|
|
||||||
|
<img src="images/sneakIcon.svg" width="80" height="80" alt="icon"/>
|
||||||
|
|
||||||
|
By [Mikhail Levchenko](https://github.com/Mishkun)
|
||||||
|
Original repository with the plugin: https://github.com/Mishkun/ideavim-sneak
|
||||||
Original plugin: [vim-sneak](https://github.com/justinmk/vim-sneak).
|
Original plugin: [vim-sneak](https://github.com/justinmk/vim-sneak).
|
||||||
|
|
||||||
### Setup:
|
### Setup:
|
||||||
- Install [IdeaVim-sneak](https://plugins.jetbrains.com/plugin/15348-ideavim-sneak) plugin.
|
- Add the following command to `~/.ideavimrc`: `Plug 'justinmk/vim-sneak'`
|
||||||
- Add the following command to `~/.ideavimrc`: `set sneak`
|
|
||||||
|
|
||||||
### Instructions
|
### Instructions
|
||||||
|
|
||||||
See the [docs](https://github.com/Mishkun/ideavim-sneak#usage)
|
* Type `s` and two chars to start sneaking in forward direction
|
||||||
|
* Type `S` and two chars to start sneaking in backward direction
|
||||||
|
* Type `;` or `,` to proceed with sneaking just as if you were using `f` or `t` commands
|
||||||
|
|
||||||
</details>
|
</details>
|
||||||
|
|
||||||
|
28
doc/images/sneakIcon.svg
Normal file
28
doc/images/sneakIcon.svg
Normal file
@ -0,0 +1,28 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<svg viewBox="386.498 234 32 32" xmlns="http://www.w3.org/2000/svg">
|
||||||
|
<defs>
|
||||||
|
<linearGradient id="ideavim_plugin-a" x1="-6.748%" x2="47.286%" y1="33.61%" y2="85.907%">
|
||||||
|
<stop offset="0" stop-color="#3BEA62"/>
|
||||||
|
<stop offset="1" stop-color="#087CFA"/>
|
||||||
|
</linearGradient>
|
||||||
|
</defs>
|
||||||
|
<g transform="matrix(1.238978, 0.90017, -0.90017, 1.238978, 131.776901, -422.953003)" style="">
|
||||||
|
<path d="M 399.962 247.648 C 399.207 246.894 399.147 246.318 399.692 245.453 C 400.237 244.588 401.955 245.886 401.955 245.886 L 401.955 250.737" style="fill: rgb(248, 245, 231);"/>
|
||||||
|
<path d="M 413.846 253.602 C 413.846 255.077 411.827 256.134 409.392 256.134 L 396.381 256.134 C 395.211 256.134 394.232 256.003 393.433 255.817 C 391.496 255.367 390.613 254.596 390.613 254.596 L 391.478 252.513 L 393.607 252.617 Z M 413.846 253.602" fill="#cce6f6" style=""/>
|
||||||
|
<path d="M 413.846 253.602 C 413.846 253.602 411.475 254.468 408.27 254.468 C 405.94 254.468 398.116 253.433 394.023 252.869 C 392.488 252.658 391.478 252.512 391.478 252.512 C 390.864 251.662 392.078 248.741 392.758 247.263 C 393.118 246.484 393.85 245.929 394.703 245.83 C 394.782 245.82 394.861 245.815 394.939 245.815 C 395.369 245.815 395.645 246.532 396.059 247.225 C 396.446 247.877 396.955 248.507 397.823 248.507 C 399.617 248.507 401.955 245.886 401.955 245.886 C 406.544 249.03 410.097 250.43 410.097 250.43 C 410.097 250.43 413.061 250.446 413.782 251.167 C 414.503 251.888 414.422 253.218 413.846 253.602 Z M 413.846 253.602" style="fill: rgb(8, 124, 250);"/>
|
||||||
|
<path d="M 394.023 252.869 L 393.433 255.817 C 391.496 255.367 390.613 254.596 390.613 254.596 L 391.478 252.513 L 393.607 252.617 Z M 394.023 252.869" fill="#9dcae0" style=""/>
|
||||||
|
<path d="M 396.059 247.225 C 395.073 245.986 393.193 250.255 394.023 252.869 C 392.488 252.658 391.478 252.513 391.478 252.513 C 390.864 251.662 392.078 248.741 392.758 247.263 C 393.118 246.484 393.85 245.929 394.703 245.83 C 394.782 245.82 394.861 245.815 394.939 245.815 C 395.369 245.815 395.645 246.532 396.059 247.225 Z M 396.059 247.225" style="fill: rgb(14, 112, 142);"/>
|
||||||
|
<path d="M 403.527 246.924 L 399.174 251.768 C 397.7 250.892 395.578 250.174 394.016 249.717 C 392.843 249.372 391.985 249.174 391.959 249.168 C 392.108 248.769 392.268 248.379 392.421 248.022 L 394.341 248.65 L 394.884 248.827 C 395.509 249.031 396.192 248.95 396.753 248.608 L 397.153 248.363 C 397.35 248.454 397.572 248.507 397.823 248.507 C 399.617 248.507 401.955 245.886 401.955 245.886 C 402.494 246.256 403.02 246.601 403.527 246.924 Z M 403.527 246.924" style="fill: rgb(59, 234, 98);"/>
|
||||||
|
<path d="M 413.847 253.602 C 413.847 253.602 411.475 254.468 408.27 254.468 C 407.586 254.468 406.426 254.378 405.025 254.238 L 405.025 254.237 C 405.025 253.495 406.924 251.743 408.366 251.616 C 408.623 252.865 410.097 252.512 411.219 252.128 C 412.341 251.743 413.783 251.167 413.783 251.167 C 414.503 251.888 414.422 253.218 413.847 253.602 Z M 413.847 253.602" style="fill: rgb(8, 124, 250);"/>
|
||||||
|
<path d="M 394.341 248.65 C 394.214 248.978 394.103 249.339 394.016 249.717 C 392.843 249.372 391.985 249.174 391.959 249.168 C 392.108 248.769 392.268 248.379 392.421 248.022 Z M 394.341 248.65" style="fill: rgb(37, 187, 163);"/>
|
||||||
|
<path d="M 408.366 251.616 C 406.924 251.743 405.025 253.495 405.025 254.237 L 405.025 254.238 C 403.784 254.113 402.355 253.948 400.899 253.77 C 400.899 253.051 400.191 252.372 399.174 251.768 L 399.174 251.768 L 403.528 246.924 C 407.331 249.34 410.097 250.43 410.097 250.43 C 410.77 251.102 409.809 251.487 408.366 251.616 Z M 408.366 251.616" style="fill: rgb(248, 245, 231);"/>
|
||||||
|
<polygon fill="url(#ideavim_plugin-a)" fill-rule="evenodd" points="406.356 248.463 403.261 252.616 402.183 248.212 400.984 249.899 401.999 254.236 403.927 254.176 407.639 249.062" style="" transform="matrix(0.994522, 0.104529, -0.104529, 0.994522, 28.475005, -40.88594)"/>
|
||||||
|
<g fill="#fb6572" transform="matrix(0.046265, 0, 0, 0.046265, 390.612823, 245.155533)" style="">
|
||||||
|
<path d="m288.839844 72.65625c-2.007813 0-4.011719-.761719-5.542969-2.292969-3.058594-3.0625-3.058594-8.023437 0-11.082031l20.089844-20.089844c3.0625-3.058594 8.023437-3.058594 11.082031 0 3.058594 3.0625 3.0625 8.023438 0 11.082032l-20.089844 20.089843c-1.527344 1.53125-3.535156 2.292969-5.539062 2.292969zm0 0" style="fill: rgb(56, 228, 105);"/>
|
||||||
|
<path d="m314.589844 87.082031c-2.007813 0-4.011719-.765625-5.542969-2.296875-3.0625-3.058594-3.0625-8.019531 0-11.082031l20.089844-20.085937c3.0625-3.058594 8.023437-3.058594 11.082031 0 3.058594 3.0625 3.058594 8.023437 0 11.082031l-20.089844 20.085937c-1.527344 1.53125-3.535156 2.296875-5.539062 2.296875zm0 0" style="fill: rgb(59, 233, 100);"/>
|
||||||
|
<path d="m340.339844 101.507812c-2.007813 0-4.011719-.765624-5.542969-2.296874-3.058594-3.058594-3.058594-8.023438 0-11.082032l20.089844-20.085937c3.0625-3.0625 8.023437-3.0625 11.082031 0 3.058594 3.058593 3.0625 8.023437 0 11.082031l-20.089844 20.085938c-1.527344 1.53125-3.535156 2.296874-5.539062 2.296874zm0 0" style="fill: rgb(59, 233, 100);"/>
|
||||||
|
<path d="m366.089844 115.929688c-2.003906 0-4.011719-.761719-5.539063-2.292969-3.0625-3.0625-3.0625-8.023438 0-11.082031l20.085938-20.089844c3.0625-3.058594 8.023437-3.058594 11.082031 0 3.0625 3.0625 3.0625 8.023437 0 11.082031l-20.085938 20.089844c-1.53125 1.53125-3.535156 2.292969-5.542968 2.292969zm0 0" style="fill: rgb(59, 233, 100);"/>
|
||||||
|
</g>
|
||||||
|
<path d="M 401.925 247.748 C 401.761 247.748 401.611 247.629 401.573 247.469 C 401.536 247.312 401.611 247.144 401.753 247.067 C 402 246.933 402.318 247.147 402.286 247.426 C 402.265 247.606 402.107 247.748 401.925 247.748 Z M 401.925 247.748" fill="#1e2628" style=""/>
|
||||||
|
</g>
|
||||||
|
</svg>
|
After Width: | Height: | Size: 5.6 KiB |
@ -8,18 +8,21 @@
|
|||||||
|
|
||||||
# suppress inspection "UnusedProperty" for whole file
|
# suppress inspection "UnusedProperty" for whole file
|
||||||
|
|
||||||
ideaVersion=2023.3.2
|
ideaVersion=2023.3.3
|
||||||
|
# Values for type: https://plugins.jetbrains.com/docs/intellij/tools-gradle-intellij-plugin.html#intellij-extension-type
|
||||||
|
ideaType=IC
|
||||||
downloadIdeaSources=true
|
downloadIdeaSources=true
|
||||||
instrumentPluginCode=true
|
instrumentPluginCode=true
|
||||||
version=SNAPSHOT
|
version=chylex-27
|
||||||
javaVersion=17
|
javaVersion=17
|
||||||
remoteRobotVersion=0.11.21
|
remoteRobotVersion=0.11.22
|
||||||
antlrVersion=4.10.1
|
antlrVersion=4.10.1
|
||||||
|
|
||||||
|
kotlin.incremental.useClasspathSnapshot=false
|
||||||
|
|
||||||
# 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.8.21
|
kotlinVersion=1.9.22
|
||||||
publishToken=token
|
publishToken=token
|
||||||
publishChannels=eap
|
publishChannels=eap
|
||||||
|
|
||||||
@ -37,4 +40,4 @@ org.gradle.jvmargs='-Dfile.encoding=UTF-8'
|
|||||||
kotlin.stdlib.default.dependency=false
|
kotlin.stdlib.default.dependency=false
|
||||||
|
|
||||||
# Disable incremental annotation processing
|
# Disable incremental annotation processing
|
||||||
ksp.incremental=false
|
ksp.incremental=false
|
||||||
|
BIN
gradle/wrapper/gradle-wrapper.jar
vendored
BIN
gradle/wrapper/gradle-wrapper.jar
vendored
Binary file not shown.
3
gradle/wrapper/gradle-wrapper.properties
vendored
3
gradle/wrapper/gradle-wrapper.properties
vendored
@ -1,6 +1,7 @@
|
|||||||
distributionBase=GRADLE_USER_HOME
|
distributionBase=GRADLE_USER_HOME
|
||||||
distributionPath=wrapper/dists
|
distributionPath=wrapper/dists
|
||||||
distributionUrl=https\://services.gradle.org/distributions/gradle-8.1.1-bin.zip
|
distributionUrl=https\://services.gradle.org/distributions/gradle-8.6-bin.zip
|
||||||
networkTimeout=10000
|
networkTimeout=10000
|
||||||
|
validateDistributionUrl=true
|
||||||
zipStoreBase=GRADLE_USER_HOME
|
zipStoreBase=GRADLE_USER_HOME
|
||||||
zipStorePath=wrapper/dists
|
zipStorePath=wrapper/dists
|
||||||
|
5
gradlew
vendored
5
gradlew
vendored
@ -130,10 +130,13 @@ location of your Java installation."
|
|||||||
fi
|
fi
|
||||||
else
|
else
|
||||||
JAVACMD=java
|
JAVACMD=java
|
||||||
which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
|
if ! command -v java >/dev/null 2>&1
|
||||||
|
then
|
||||||
|
die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
|
||||||
|
|
||||||
Please set the JAVA_HOME variable in your environment to match the
|
Please set the JAVA_HOME variable in your environment to match the
|
||||||
location of your Java installation."
|
location of your Java installation."
|
||||||
|
fi
|
||||||
fi
|
fi
|
||||||
|
|
||||||
# Increase the maximum file descriptors if we can.
|
# Increase the maximum file descriptors if we can.
|
||||||
|
@ -93,28 +93,16 @@ tasks.register("addReleaseTag", JavaExec::class) {
|
|||||||
args = listOf(project.version.toString(), rootProject.rootDir.toString(), releaseType ?: "")
|
args = listOf(project.version.toString(), rootProject.rootDir.toString(), releaseType ?: "")
|
||||||
}
|
}
|
||||||
|
|
||||||
tasks.register("resetReleaseBranch", JavaExec::class) {
|
tasks.register("selectBranch", JavaExec::class) {
|
||||||
group = "release"
|
group = "release"
|
||||||
mainClass.set("scripts.release.ResetReleaseBranchKt")
|
mainClass.set("scripts.release.SelectBranchKt")
|
||||||
classpath = sourceSets["main"].runtimeClasspath
|
classpath = sourceSets["main"].runtimeClasspath
|
||||||
args = listOf(project.version.toString(), rootProject.rootDir.toString(), releaseType ?: "")
|
args = listOf(project.version.toString(), rootProject.rootDir.toString(), releaseType ?: "")
|
||||||
}
|
}
|
||||||
|
|
||||||
tasks.register("pushChanges", JavaExec::class) {
|
tasks.register("eapReleaseActions", JavaExec::class) {
|
||||||
mainClass.set("scripts.PushChangesKt")
|
|
||||||
classpath = sourceSets["main"].runtimeClasspath
|
|
||||||
args = listOf(rootProject.rootDir.toString())
|
|
||||||
}
|
|
||||||
|
|
||||||
tasks.register("pushChangesWithReleaseBranch", JavaExec::class) {
|
|
||||||
mainClass.set("scripts.PushChangesWithReleaseBranchKt")
|
|
||||||
classpath = sourceSets["main"].runtimeClasspath
|
|
||||||
args = listOf(rootProject.rootDir.toString(), releaseType ?: "")
|
|
||||||
}
|
|
||||||
|
|
||||||
tasks.register("selectBranch", JavaExec::class) {
|
|
||||||
group = "release"
|
group = "release"
|
||||||
mainClass.set("scripts.release.SelectBranchKt")
|
mainClass.set("scripts.releaseEap.EapReleaseActionsKt")
|
||||||
classpath = sourceSets["main"].runtimeClasspath
|
classpath = sourceSets["main"].runtimeClasspath
|
||||||
args = listOf(project.version.toString(), rootProject.rootDir.toString(), releaseType ?: "")
|
args = listOf(project.version.toString(), rootProject.rootDir.toString(), releaseType ?: "")
|
||||||
}
|
}
|
||||||
|
@ -32,6 +32,7 @@ val knownPlugins = listOf(
|
|||||||
"com.github.dankinsoid.multicursor",
|
"com.github.dankinsoid.multicursor",
|
||||||
"com.joshestein.ideavim-quickscope",
|
"com.joshestein.ideavim-quickscope",
|
||||||
"ca.alexgirard.HarpoonIJ",
|
"ca.alexgirard.HarpoonIJ",
|
||||||
|
"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
|
||||||
)
|
)
|
||||||
|
@ -32,7 +32,7 @@ fun httpClient(): HttpClient {
|
|||||||
install(Auth) {
|
install(Auth) {
|
||||||
bearer {
|
bearer {
|
||||||
loadTokens {
|
loadTokens {
|
||||||
val accessToken = System.getenv("YOUTRACK_TOKEN")!!
|
val accessToken = System.getenv("YOUTRACK_TOKEN") ?: error("Missing YOUTRACK_TOKEN")
|
||||||
BearerTokens(accessToken, "")
|
BearerTokens(accessToken, "")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,37 +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 scripts
|
|
||||||
|
|
||||||
import scripts.release.checkoutBranch
|
|
||||||
import scripts.release.withGit
|
|
||||||
import scripts.release.withRepo
|
|
||||||
|
|
||||||
fun main(args: Array<String>) {
|
|
||||||
val rootDir = args[0]
|
|
||||||
println("root dir: $rootDir")
|
|
||||||
|
|
||||||
val currentBranch = withRepo(rootDir) { it.branch }
|
|
||||||
println("Current branch is $currentBranch")
|
|
||||||
|
|
||||||
|
|
||||||
withGit(rootDir) { git ->
|
|
||||||
if (currentBranch != "master") {
|
|
||||||
git.checkoutBranch("master")
|
|
||||||
println("Check out master branch")
|
|
||||||
}
|
|
||||||
|
|
||||||
git.push()
|
|
||||||
.setPushTags()
|
|
||||||
.call()
|
|
||||||
println("Master pushed with tags")
|
|
||||||
|
|
||||||
git.checkoutBranch(currentBranch)
|
|
||||||
println("Checked out $currentBranch branch")
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,54 +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 scripts
|
|
||||||
|
|
||||||
import scripts.release.checkoutBranch
|
|
||||||
import scripts.release.withGit
|
|
||||||
import scripts.release.withRepo
|
|
||||||
|
|
||||||
fun main(args: Array<String>) {
|
|
||||||
val rootDir = args[0]
|
|
||||||
val releaseType = args[1]
|
|
||||||
println("root dir: $rootDir")
|
|
||||||
println("releaseType: $releaseType")
|
|
||||||
|
|
||||||
val currentBranch = withRepo(rootDir) { it.branch }
|
|
||||||
println("Current branch is $currentBranch")
|
|
||||||
|
|
||||||
|
|
||||||
withGit(rootDir) { git ->
|
|
||||||
if (currentBranch != "master") {
|
|
||||||
git.checkoutBranch("master")
|
|
||||||
println("Check out master branch")
|
|
||||||
}
|
|
||||||
|
|
||||||
git.push()
|
|
||||||
.setPushTags()
|
|
||||||
.call()
|
|
||||||
println("Master pushed with tags")
|
|
||||||
|
|
||||||
if (releaseType != "patch") {
|
|
||||||
git.checkoutBranch("release")
|
|
||||||
println("Checked out release")
|
|
||||||
|
|
||||||
git
|
|
||||||
.push()
|
|
||||||
.setForce(true)
|
|
||||||
.setPushTags()
|
|
||||||
.call()
|
|
||||||
println("Pushed release branch with tags")
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
println("Do not push release branch because type of release is $releaseType")
|
|
||||||
}
|
|
||||||
|
|
||||||
git.checkoutBranch(currentBranch)
|
|
||||||
println("Checked out $currentBranch branch")
|
|
||||||
}
|
|
||||||
}
|
|
@ -8,28 +8,45 @@
|
|||||||
|
|
||||||
package scripts.release
|
package scripts.release
|
||||||
|
|
||||||
|
import com.vdurmont.semver4j.Semver
|
||||||
import java.time.LocalDate
|
import java.time.LocalDate
|
||||||
import java.time.format.DateTimeFormatter
|
import java.time.format.DateTimeFormatter
|
||||||
import kotlin.io.path.Path
|
import kotlin.io.path.Path
|
||||||
import kotlin.io.path.readText
|
import kotlin.io.path.readText
|
||||||
import kotlin.io.path.writeText
|
import kotlin.io.path.writeText
|
||||||
|
|
||||||
|
private const val toBeReleased = "## To Be Released"
|
||||||
|
|
||||||
fun main(args: Array<String>) {
|
fun main(args: Array<String>) {
|
||||||
println("Start updating unreleased section")
|
println("Start updating unreleased section")
|
||||||
val (newVersion, rootDir, releaseType) = readArgs(args)
|
val (newVersion, rootDir, releaseType) = readArgs(args)
|
||||||
|
|
||||||
checkReleaseType(releaseType)
|
checkReleaseType(releaseType)
|
||||||
|
|
||||||
if (releaseType == "patch") {
|
|
||||||
println("Skip updating the changelog because release type is 'patch'")
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
val currentDate = LocalDate.now().format(DateTimeFormatter.ISO_LOCAL_DATE)
|
val currentDate = LocalDate.now().format(DateTimeFormatter.ISO_LOCAL_DATE)
|
||||||
val newItem = "## $newVersion, $currentDate"
|
val newItem = "## $newVersion, $currentDate"
|
||||||
|
|
||||||
val changelogPath = Path("$rootDir/CHANGES.md")
|
val changelogPath = Path("$rootDir/CHANGES.md")
|
||||||
val changelog = changelogPath.readText()
|
val changelog = changelogPath.readText()
|
||||||
val newChangelog = changelog.replace("## To Be Released", newItem)
|
val newChangelog = if (releaseType == "patch") {
|
||||||
|
val decreasedVersion = Semver(newVersion).withIncPatch(-1)
|
||||||
|
val firstEntry = changelog.indexOf("## $decreasedVersion")
|
||||||
|
if (firstEntry != -1) {
|
||||||
|
val newLog = StringBuilder(changelog)
|
||||||
|
newLog.insert(firstEntry, newItem + "\n")
|
||||||
|
newLog.toString()
|
||||||
|
} else {
|
||||||
|
changelog
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
if (toBeReleased in changelog) {
|
||||||
|
changelog.replace(toBeReleased, newItem)
|
||||||
|
} else {
|
||||||
|
val firstEntry = changelog.indexOf("##")
|
||||||
|
val newLog = StringBuilder(changelog)
|
||||||
|
newLog.insert(firstEntry, newItem + "\n")
|
||||||
|
newLog.toString()
|
||||||
|
}
|
||||||
|
}
|
||||||
changelogPath.writeText(newChangelog)
|
changelogPath.writeText(newChangelog)
|
||||||
}
|
}
|
@ -13,21 +13,20 @@ fun main(args: Array<String>) {
|
|||||||
|
|
||||||
checkReleaseType(releaseType)
|
checkReleaseType(releaseType)
|
||||||
|
|
||||||
if (releaseType == "patch") {
|
|
||||||
println("Skip committing changes because release type is 'patch'")
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
withGit(rootDir) { git ->
|
withGit(rootDir) { git ->
|
||||||
git
|
if (git.diff().call().isNotEmpty()) {
|
||||||
.commit()
|
git
|
||||||
.setAll(true)
|
.commit()
|
||||||
.setAuthor("IdeaVim Bot", "maintainers@ideavim.dev")
|
.setAll(true)
|
||||||
.setMessage("Preparation to $newVersion release")
|
.setAuthor("IdeaVim Bot", "maintainers@ideavim.dev")
|
||||||
.setSign(false)
|
.setMessage("Preparation to $newVersion release")
|
||||||
.call()
|
.setSign(false)
|
||||||
|
.call()
|
||||||
|
|
||||||
val lastGitMessage = git.log().call().first().shortMessage
|
val lastGitMessage = git.log().call().first().shortMessage
|
||||||
println("Changes committed. Last gitlog message: $lastGitMessage")
|
println("Changes committed. Last gitlog message: $lastGitMessage")
|
||||||
|
} else {
|
||||||
|
println("No Changes")
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,38 +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 scripts.release
|
|
||||||
|
|
||||||
fun main(args: Array<String>) {
|
|
||||||
val (_, rootDir, releaseType) = readArgs(args)
|
|
||||||
|
|
||||||
checkReleaseType(releaseType)
|
|
||||||
|
|
||||||
checkBranch(rootDir, releaseType)
|
|
||||||
|
|
||||||
if (releaseType == "patch") {
|
|
||||||
println("Skip release branch reset because release type is 'patch'")
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
withGit(rootDir) { git ->
|
|
||||||
val currentCommit = git.log().setMaxCount(1).call().first()
|
|
||||||
println("Current commit id: ${currentCommit.id.name}")
|
|
||||||
|
|
||||||
git.checkoutBranch("release")
|
|
||||||
println("Checked out release branch")
|
|
||||||
|
|
||||||
git.reset()
|
|
||||||
.setRef(currentCommit.id.name)
|
|
||||||
.call()
|
|
||||||
println("release branch reset")
|
|
||||||
|
|
||||||
git.checkoutBranch("master")
|
|
||||||
println("Checked out master branch")
|
|
||||||
}
|
|
||||||
}
|
|
@ -15,8 +15,7 @@ fun main(args: Array<String>) {
|
|||||||
|
|
||||||
withGit(rootDir) { git ->
|
withGit(rootDir) { git ->
|
||||||
val branchName = when (releaseType) {
|
val branchName = when (releaseType) {
|
||||||
"major", "minor" -> "master"
|
"major", "minor", "patch" -> "release"
|
||||||
"patch" -> "release"
|
|
||||||
else -> error("Unsupported release type: $releaseType")
|
else -> error("Unsupported release type: $releaseType")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -0,0 +1,36 @@
|
|||||||
|
/*
|
||||||
|
* Copyright 2003-2024 The IdeaVim authors
|
||||||
|
*
|
||||||
|
* Use of this source code is governed by an MIT-style
|
||||||
|
* license that can be found in the LICENSE.txt file or at
|
||||||
|
* https://opensource.org/licenses/MIT.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package scripts.releaseEap
|
||||||
|
|
||||||
|
import kotlinx.coroutines.runBlocking
|
||||||
|
import scripts.addComment
|
||||||
|
import scripts.getYoutrackTicketsByQuery
|
||||||
|
import scripts.release.readArgs
|
||||||
|
import scripts.releasedInEapTagId
|
||||||
|
import scripts.setTag
|
||||||
|
|
||||||
|
fun main(args: Array<String>) {
|
||||||
|
runBlocking {
|
||||||
|
val (newVersion, _, _) = readArgs(args)
|
||||||
|
|
||||||
|
// Search for Ready to release, but without "IdeaVim Released In EAP" tag
|
||||||
|
val ticketsToUpdate =
|
||||||
|
getYoutrackTicketsByQuery("%23%7BReady%20To%20Release%7D%20tag:%20-%7BIdeaVim%20Released%20In%20EAP%7D%20")
|
||||||
|
println("Have to update the following tickets: $ticketsToUpdate")
|
||||||
|
|
||||||
|
ticketsToUpdate.forEach { ticketId ->
|
||||||
|
setTag(ticketId, releasedInEapTagId)
|
||||||
|
addComment(
|
||||||
|
ticketId, """
|
||||||
|
The fix is available in the IdeaVim $newVersion. See https://jb.gg/ideavim-eap for the instructions on how to get EAP builds as updates within the IDE. You can also wait till the next stable release with this fix, you’ll get it automatically.
|
||||||
|
""".trimIndent()
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -11,6 +11,8 @@ package scripts
|
|||||||
import io.ktor.client.call.*
|
import io.ktor.client.call.*
|
||||||
import io.ktor.client.request.*
|
import io.ktor.client.request.*
|
||||||
import io.ktor.http.*
|
import io.ktor.http.*
|
||||||
|
import kotlinx.coroutines.runBlocking
|
||||||
|
import kotlinx.serialization.json.JsonArray
|
||||||
import kotlinx.serialization.json.JsonObject
|
import kotlinx.serialization.json.JsonObject
|
||||||
import kotlinx.serialization.json.addJsonObject
|
import kotlinx.serialization.json.addJsonObject
|
||||||
import kotlinx.serialization.json.buildJsonObject
|
import kotlinx.serialization.json.buildJsonObject
|
||||||
@ -21,6 +23,10 @@ import kotlinx.serialization.json.put
|
|||||||
import kotlinx.serialization.json.putJsonArray
|
import kotlinx.serialization.json.putJsonArray
|
||||||
import kotlinx.serialization.json.putJsonObject
|
import kotlinx.serialization.json.putJsonObject
|
||||||
|
|
||||||
|
|
||||||
|
// YouTrack tag "IdeaVim Released In EAP"
|
||||||
|
const val releasedInEapTagId = "68-385032"
|
||||||
|
|
||||||
suspend fun setYoutrackStatus(tickets: Collection<String>, status: String) {
|
suspend fun setYoutrackStatus(tickets: Collection<String>, status: String) {
|
||||||
val client = httpClient()
|
val client = httpClient()
|
||||||
|
|
||||||
@ -59,3 +65,59 @@ suspend fun setYoutrackStatus(tickets: Collection<String>, status: String) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fun getYoutrackTicketsByQuery(query: String): Set<String> {
|
||||||
|
val client = httpClient()
|
||||||
|
|
||||||
|
return runBlocking {
|
||||||
|
val response = client.get("https://youtrack.jetbrains.com/api/issues/?fields=idReadable&query=project:VIM+$query")
|
||||||
|
response.body<JsonArray>().mapTo(HashSet()) { it.jsonObject.getValue("idReadable").jsonPrimitive.content }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 68-385032
|
||||||
|
* [issueHumanId] is like VIM-123
|
||||||
|
* [tagId] is like "145-23"
|
||||||
|
*/
|
||||||
|
suspend fun setTag(issueHumanId: String, tagId: String) {
|
||||||
|
val client = httpClient()
|
||||||
|
|
||||||
|
println("Try to add tag $tagId to $issueHumanId")
|
||||||
|
val response =
|
||||||
|
// I've updated default url in client, so this may be broken now
|
||||||
|
client.post("https://youtrack.jetbrains.com/api/issues/$issueHumanId/tags?fields=customFields(id,name,value(id,name))") {
|
||||||
|
contentType(ContentType.Application.Json)
|
||||||
|
accept(ContentType.Application.Json)
|
||||||
|
val request = buildJsonObject {
|
||||||
|
put("id", tagId)
|
||||||
|
}
|
||||||
|
setBody(request)
|
||||||
|
}
|
||||||
|
println(response)
|
||||||
|
println(response.body<String>())
|
||||||
|
if (!response.status.isSuccess()) {
|
||||||
|
error("Request failed. $issueHumanId, ${response.body<String>()}")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
suspend fun addComment(issueHumanId: String, text: String) {
|
||||||
|
val client = httpClient()
|
||||||
|
|
||||||
|
println("Try to add comment to $issueHumanId")
|
||||||
|
val response =
|
||||||
|
// I've updated default url in client, so this may be broken now
|
||||||
|
client.post("https://youtrack.jetbrains.com/api/issues/$issueHumanId/comments?fields=customFields(id,name,value(id,name))") {
|
||||||
|
contentType(ContentType.Application.Json)
|
||||||
|
accept(ContentType.Application.Json)
|
||||||
|
val request = buildJsonObject {
|
||||||
|
put("text", text)
|
||||||
|
}
|
||||||
|
setBody(request)
|
||||||
|
}
|
||||||
|
println(response)
|
||||||
|
println(response.body<String>())
|
||||||
|
if (!response.status.isSuccess()) {
|
||||||
|
error("Request failed. $issueHumanId, ${response.body<String>()}")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
@ -12,4 +12,9 @@ rootProject.name = 'IdeaVIM'
|
|||||||
include 'vim-engine'
|
include 'vim-engine'
|
||||||
include 'scripts'
|
include 'scripts'
|
||||||
include 'annotation-processors'
|
include 'annotation-processors'
|
||||||
|
include 'tests:java-tests'
|
||||||
|
include 'tests:property-tests'
|
||||||
|
include 'tests:long-running-tests'
|
||||||
|
include 'tests:ui-ij-tests'
|
||||||
|
include 'tests:ui-py-tests'
|
||||||
|
include 'tests:ui-fixtures'
|
||||||
|
@ -7,56 +7,27 @@
|
|||||||
*/
|
*/
|
||||||
package com.maddyhome.idea.vim
|
package com.maddyhome.idea.vim
|
||||||
|
|
||||||
import com.intellij.openapi.application.ApplicationManager
|
|
||||||
import com.intellij.openapi.extensions.ExtensionPointName
|
|
||||||
import com.maddyhome.idea.vim.action.EngineCommandProvider
|
import com.maddyhome.idea.vim.action.EngineCommandProvider
|
||||||
import com.maddyhome.idea.vim.action.IntellijCommandProvider
|
import com.maddyhome.idea.vim.action.IntellijCommandProvider
|
||||||
import com.maddyhome.idea.vim.api.injector
|
|
||||||
import com.maddyhome.idea.vim.handler.ActionBeanClass
|
|
||||||
import com.maddyhome.idea.vim.handler.EditorActionHandlerBase
|
import com.maddyhome.idea.vim.handler.EditorActionHandlerBase
|
||||||
import com.maddyhome.idea.vim.key.MappingOwner
|
import com.maddyhome.idea.vim.key.MappingOwner
|
||||||
import com.maddyhome.idea.vim.newapi.IjVimActionsInitiator
|
|
||||||
import com.maddyhome.idea.vim.newapi.globalIjOptions
|
|
||||||
import java.awt.event.KeyEvent
|
import java.awt.event.KeyEvent
|
||||||
import javax.swing.KeyStroke
|
import javax.swing.KeyStroke
|
||||||
|
|
||||||
public object RegisterActions {
|
public object RegisterActions {
|
||||||
@Deprecated("Please use @CommandOrMotion annotation instead")
|
|
||||||
internal val VIM_ACTIONS_EP: ExtensionPointName<ActionBeanClass> = ExtensionPointName.create("IdeaVIM.vimAction")
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Register all the key/action mappings for the plugin.
|
* Register all the key/action mappings for the plugin.
|
||||||
*/
|
*/
|
||||||
@JvmStatic
|
@JvmStatic
|
||||||
public fun registerActions() {
|
public fun registerActions() {
|
||||||
registerVimCommandActions()
|
registerVimCommandActions()
|
||||||
if (!injector.globalIjOptions().commandOrMotionAnnotation) {
|
registerEmptyShortcuts() // todo most likely it is not needed
|
||||||
registerEmptyShortcuts()
|
|
||||||
registerEpListener()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Deprecated("Moving to annotations approach instead of xml")
|
|
||||||
private fun registerEpListener() {
|
|
||||||
// IdeaVim doesn't support contribution to VIM_ACTIONS_EP extension point, so technically we can skip this update,
|
|
||||||
// but let's support dynamic plugins in a more classic way and reload actions on every EP change.
|
|
||||||
VIM_ACTIONS_EP.addChangeListener({
|
|
||||||
unregisterActions()
|
|
||||||
registerActions()
|
|
||||||
}, VimPlugin.getInstance())
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public fun findAction(id: String): EditorActionHandlerBase? {
|
public fun findAction(id: String): EditorActionHandlerBase? {
|
||||||
if (injector.globalIjOptions().commandOrMotionAnnotation) {
|
val commandBean = EngineCommandProvider.getCommands().firstOrNull { it.actionId == id }
|
||||||
val commandBean = EngineCommandProvider.getCommands().firstOrNull { it.actionId == id }
|
?: IntellijCommandProvider.getCommands().firstOrNull { it.actionId == id } ?: return null
|
||||||
?: IntellijCommandProvider.getCommands().firstOrNull { it.actionId == id } ?: return null
|
return commandBean.instance
|
||||||
return commandBean.instance
|
|
||||||
} else {
|
|
||||||
return VIM_ACTIONS_EP.getExtensionList(ApplicationManager.getApplication()).stream()
|
|
||||||
.filter { vimActionBean: ActionBeanClass -> vimActionBean.actionId == id }
|
|
||||||
.findFirst().map { obj: ActionBeanClass -> obj.instance }
|
|
||||||
.orElse(null)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public fun findActionOrDie(id: String): EditorActionHandlerBase {
|
public fun findActionOrDie(id: String): EditorActionHandlerBase {
|
||||||
@ -71,24 +42,10 @@ public object RegisterActions {
|
|||||||
|
|
||||||
private fun registerVimCommandActions() {
|
private fun registerVimCommandActions() {
|
||||||
val parser = VimPlugin.getKey()
|
val parser = VimPlugin.getKey()
|
||||||
if (injector.globalIjOptions().commandOrMotionAnnotation) {
|
EngineCommandProvider.getCommands().forEach { parser.registerCommandAction(it) }
|
||||||
EngineCommandProvider.getCommands().forEach { parser.registerCommandAction(it) }
|
IntellijCommandProvider.getCommands().forEach { parser.registerCommandAction(it) }
|
||||||
IntellijCommandProvider.getCommands().forEach { parser.registerCommandAction(it) }
|
|
||||||
} else {
|
|
||||||
VIM_ACTIONS_EP.getExtensionList(ApplicationManager.getApplication()).stream().map { bean: ActionBeanClass? ->
|
|
||||||
IjVimActionsInitiator(
|
|
||||||
bean!!
|
|
||||||
)
|
|
||||||
}
|
|
||||||
.forEach { actionHolder: IjVimActionsInitiator? ->
|
|
||||||
parser.registerCommandAction(
|
|
||||||
actionHolder!!
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// todo do we really need this?
|
|
||||||
private fun registerEmptyShortcuts() {
|
private fun registerEmptyShortcuts() {
|
||||||
val parser = VimPlugin.getKey()
|
val parser = VimPlugin.getKey()
|
||||||
|
|
||||||
|
@ -39,11 +39,9 @@ import com.maddyhome.idea.vim.listener.VimListenerManager;
|
|||||||
import com.maddyhome.idea.vim.newapi.IjVimInjector;
|
import com.maddyhome.idea.vim.newapi.IjVimInjector;
|
||||||
import com.maddyhome.idea.vim.ui.StatusBarIconFactory;
|
import com.maddyhome.idea.vim.ui.StatusBarIconFactory;
|
||||||
import com.maddyhome.idea.vim.ui.ex.ExEntryPanel;
|
import com.maddyhome.idea.vim.ui.ex.ExEntryPanel;
|
||||||
import com.maddyhome.idea.vim.vimscript.services.OptionService;
|
|
||||||
import com.maddyhome.idea.vim.vimscript.services.VariableService;
|
import com.maddyhome.idea.vim.vimscript.services.VariableService;
|
||||||
import com.maddyhome.idea.vim.yank.YankGroupBase;
|
import com.maddyhome.idea.vim.yank.YankGroupBase;
|
||||||
import org.jdom.Element;
|
import org.jdom.Element;
|
||||||
import org.jetbrains.annotations.ApiStatus;
|
|
||||||
import org.jetbrains.annotations.Nls;
|
import org.jetbrains.annotations.Nls;
|
||||||
import org.jetbrains.annotations.NotNull;
|
import org.jetbrains.annotations.NotNull;
|
||||||
import org.jetbrains.annotations.Nullable;
|
import org.jetbrains.annotations.Nullable;
|
||||||
@ -117,12 +115,6 @@ public class VimPlugin implements PersistentStateComponent<Element>, Disposable
|
|||||||
return ApplicationManager.getApplication().getService(CommandGroup.class);
|
return ApplicationManager.getApplication().getService(CommandGroup.class);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Deprecated // "Please use `injector.markService` instead"
|
|
||||||
@ApiStatus.ScheduledForRemoval(inVersion = "2.3")
|
|
||||||
public static @NotNull MarkGroup getMark() {
|
|
||||||
return ApplicationManager.getApplication().getService(MarkGroup.class);
|
|
||||||
}
|
|
||||||
|
|
||||||
public static @NotNull RegisterGroup getRegister() {
|
public static @NotNull RegisterGroup getRegister() {
|
||||||
return ((RegisterGroup)VimInjectorKt.getInjector().getRegisterGroup());
|
return ((RegisterGroup)VimInjectorKt.getInjector().getRegisterGroup());
|
||||||
}
|
}
|
||||||
@ -195,13 +187,6 @@ public class VimPlugin implements PersistentStateComponent<Element>, Disposable
|
|||||||
return VimInjectorKt.getInjector().getOptionGroup();
|
return VimInjectorKt.getInjector().getOptionGroup();
|
||||||
}
|
}
|
||||||
|
|
||||||
/** Deprecated: Use getOptionGroup */
|
|
||||||
@Deprecated
|
|
||||||
// Used by which-key 0.8.0, IdeaVimExtension 1.6.5 + 1.6.8
|
|
||||||
public static @NotNull OptionService getOptionService() {
|
|
||||||
return VimInjectorKt.getInjector().getOptionService();
|
|
||||||
}
|
|
||||||
|
|
||||||
private static @NotNull NotificationService getNotifications() {
|
private static @NotNull NotificationService getNotifications() {
|
||||||
return getNotifications(null);
|
return getNotifications(null);
|
||||||
}
|
}
|
||||||
|
@ -28,6 +28,7 @@ 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.group.IjOptionConstants
|
import com.maddyhome.idea.vim.group.IjOptionConstants
|
||||||
import com.maddyhome.idea.vim.group.IjOptions
|
import com.maddyhome.idea.vim.group.IjOptions
|
||||||
|
import com.maddyhome.idea.vim.handler.enableOctopus
|
||||||
import com.maddyhome.idea.vim.handler.isOctopusEnabled
|
import com.maddyhome.idea.vim.handler.isOctopusEnabled
|
||||||
import com.maddyhome.idea.vim.helper.EditorHelper
|
import com.maddyhome.idea.vim.helper.EditorHelper
|
||||||
import com.maddyhome.idea.vim.helper.HandlerInjector
|
import com.maddyhome.idea.vim.helper.HandlerInjector
|
||||||
@ -116,11 +117,13 @@ public class VimShortcutKeyAction : AnAction(), DumbAware/*, LightEditCompatible
|
|||||||
if (VimPlugin.isNotEnabled()) return ActionEnableStatus.no("IdeaVim is disabled", LogLevel.DEBUG)
|
if (VimPlugin.isNotEnabled()) return ActionEnableStatus.no("IdeaVim is disabled", LogLevel.DEBUG)
|
||||||
val editor = getEditor(e)
|
val editor = getEditor(e)
|
||||||
if (editor != null && keyStroke != null) {
|
if (editor != null && keyStroke != null) {
|
||||||
if (isOctopusEnabled(keyStroke, editor)) {
|
if (enableOctopus) {
|
||||||
return ActionEnableStatus.no(
|
if (isOctopusEnabled(keyStroke, editor)) {
|
||||||
"Processing VimShortcutKeyAction for the key that is used in the octopus handler",
|
return ActionEnableStatus.no(
|
||||||
LogLevel.ERROR
|
"Processing VimShortcutKeyAction for the key that is used in the octopus handler",
|
||||||
)
|
LogLevel.ERROR
|
||||||
|
)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
if (editor.isIdeaVimDisabledHere) {
|
if (editor.isIdeaVimDisabledHere) {
|
||||||
return ActionEnableStatus.no("IdeaVim is disabled in this place", LogLevel.INFO)
|
return ActionEnableStatus.no("IdeaVim is disabled in this place", LogLevel.INFO)
|
||||||
|
@ -8,10 +8,9 @@
|
|||||||
|
|
||||||
package com.maddyhome.idea.vim.action.editor
|
package com.maddyhome.idea.vim.action.editor
|
||||||
|
|
||||||
|
import com.intellij.openapi.actionSystem.IdeActions
|
||||||
import com.intellij.vim.annotations.CommandOrMotion
|
import com.intellij.vim.annotations.CommandOrMotion
|
||||||
import com.intellij.vim.annotations.Mode
|
import com.intellij.vim.annotations.Mode
|
||||||
import com.intellij.openapi.actionSystem.IdeActions
|
|
||||||
import com.maddyhome.idea.vim.action.ComplicatedKeysAction
|
|
||||||
import com.maddyhome.idea.vim.api.ExecutionContext
|
import com.maddyhome.idea.vim.api.ExecutionContext
|
||||||
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.api.injector
|
||||||
@ -21,54 +20,32 @@ 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 java.awt.event.KeyEvent
|
|
||||||
import java.util.*
|
import java.util.*
|
||||||
import javax.swing.KeyStroke
|
|
||||||
|
|
||||||
@CommandOrMotion(keys = ["<C-H>", "<BS>"], modes = [Mode.INSERT])
|
@CommandOrMotion(keys = ["<C-H>", "<BS>"], modes = [Mode.INSERT])
|
||||||
internal class VimEditorBackSpace : IdeActionHandler(IdeActions.ACTION_EDITOR_BACKSPACE), ComplicatedKeysAction {
|
internal class VimEditorBackSpace : IdeActionHandler(IdeActions.ACTION_EDITOR_BACKSPACE) {
|
||||||
override val keyStrokesSet: Set<List<KeyStroke>> = setOf(
|
|
||||||
listOf(KeyStroke.getKeyStroke(KeyEvent.VK_H, KeyEvent.CTRL_DOWN_MASK)),
|
|
||||||
listOf(KeyStroke.getKeyStroke(KeyEvent.VK_BACK_SPACE, 0)),
|
|
||||||
)
|
|
||||||
override val type: Command.Type = Command.Type.DELETE
|
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), ComplicatedKeysAction {
|
internal class VimEditorDelete : IdeActionHandler(IdeActions.ACTION_EDITOR_DELETE) {
|
||||||
override val keyStrokesSet: Set<List<KeyStroke>> = setOf(
|
|
||||||
listOf(KeyStroke.getKeyStroke(KeyEvent.VK_DELETE, 0)),
|
|
||||||
)
|
|
||||||
override val type: Command.Type = Command.Type.DELETE
|
override val type: Command.Type = Command.Type.DELETE
|
||||||
override val flags: EnumSet<CommandFlags> = enumSetOf(CommandFlags.FLAG_SAVE_STROKE)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@CommandOrMotion(keys = ["<Down>", "<kDown>"], modes = [Mode.INSERT])
|
@CommandOrMotion(keys = ["<Down>", "<kDown>"], modes = [Mode.INSERT])
|
||||||
internal class VimEditorDown : IdeActionHandler(IdeActions.ACTION_EDITOR_MOVE_CARET_DOWN), ComplicatedKeysAction {
|
internal class VimEditorDown : IdeActionHandler(IdeActions.ACTION_EDITOR_MOVE_CARET_DOWN) {
|
||||||
override val keyStrokesSet: Set<List<KeyStroke>> = setOf(
|
|
||||||
listOf(KeyStroke.getKeyStroke(KeyEvent.VK_DOWN, 0)),
|
|
||||||
listOf(KeyStroke.getKeyStroke(KeyEvent.VK_KP_DOWN, 0)),
|
|
||||||
)
|
|
||||||
override val type: Command.Type = Command.Type.MOTION
|
override val type: Command.Type = Command.Type.MOTION
|
||||||
override val flags: EnumSet<CommandFlags> = enumSetOf(CommandFlags.FLAG_CLEAR_STROKES)
|
override val flags: EnumSet<CommandFlags> = enumSetOf(CommandFlags.FLAG_CLEAR_STROKES)
|
||||||
}
|
}
|
||||||
|
|
||||||
@CommandOrMotion(keys = ["<Tab>", "<C-I>"], modes = [Mode.INSERT])
|
@CommandOrMotion(keys = ["<Tab>", "<C-I>"], modes = [Mode.INSERT])
|
||||||
internal class VimEditorTab : IdeActionHandler(IdeActions.ACTION_EDITOR_TAB), ComplicatedKeysAction {
|
internal class VimEditorTab : IdeActionHandler(IdeActions.ACTION_EDITOR_TAB) {
|
||||||
override val keyStrokesSet: Set<List<KeyStroke>> = setOf(
|
|
||||||
listOf(KeyStroke.getKeyStroke(KeyEvent.VK_I, KeyEvent.CTRL_DOWN_MASK)),
|
|
||||||
listOf(KeyStroke.getKeyStroke(KeyEvent.VK_TAB, 0)),
|
|
||||||
)
|
|
||||||
override val type: Command.Type = Command.Type.INSERT
|
override val type: Command.Type = Command.Type.INSERT
|
||||||
override val flags: EnumSet<CommandFlags> = enumSetOf(CommandFlags.FLAG_SAVE_STROKE)
|
override val flags: EnumSet<CommandFlags> = enumSetOf(CommandFlags.FLAG_SAVE_STROKE)
|
||||||
}
|
}
|
||||||
|
|
||||||
@CommandOrMotion(keys = ["<Up>", "<kUp>"], modes = [Mode.INSERT])
|
@CommandOrMotion(keys = ["<Up>", "<kUp>"], modes = [Mode.INSERT])
|
||||||
internal class VimEditorUp : IdeActionHandler(IdeActions.ACTION_EDITOR_MOVE_CARET_UP), ComplicatedKeysAction {
|
internal class VimEditorUp : IdeActionHandler(IdeActions.ACTION_EDITOR_MOVE_CARET_UP) {
|
||||||
override val keyStrokesSet: Set<List<KeyStroke>> = setOf(
|
|
||||||
listOf(KeyStroke.getKeyStroke(KeyEvent.VK_UP, 0)),
|
|
||||||
listOf(KeyStroke.getKeyStroke(KeyEvent.VK_KP_UP, 0)),
|
|
||||||
)
|
|
||||||
override val type: Command.Type = Command.Type.MOTION
|
override val type: Command.Type = Command.Type.MOTION
|
||||||
override val flags: EnumSet<CommandFlags> = enumSetOf(CommandFlags.FLAG_CLEAR_STROKES)
|
override val flags: EnumSet<CommandFlags> = enumSetOf(CommandFlags.FLAG_CLEAR_STROKES)
|
||||||
}
|
}
|
||||||
|
@ -10,7 +10,6 @@ package com.maddyhome.idea.vim.action.ex
|
|||||||
import com.intellij.vim.annotations.CommandOrMotion
|
import com.intellij.vim.annotations.CommandOrMotion
|
||||||
import com.intellij.vim.annotations.Mode
|
import com.intellij.vim.annotations.Mode
|
||||||
import com.maddyhome.idea.vim.VimPlugin
|
import com.maddyhome.idea.vim.VimPlugin
|
||||||
import com.maddyhome.idea.vim.action.ComplicatedKeysAction
|
|
||||||
import com.maddyhome.idea.vim.api.ExecutionContext
|
import com.maddyhome.idea.vim.api.ExecutionContext
|
||||||
import com.maddyhome.idea.vim.api.VimEditor
|
import com.maddyhome.idea.vim.api.VimEditor
|
||||||
import com.maddyhome.idea.vim.command.Command
|
import com.maddyhome.idea.vim.command.Command
|
||||||
@ -18,7 +17,6 @@ import com.maddyhome.idea.vim.command.CommandFlags
|
|||||||
import com.maddyhome.idea.vim.command.OperatorArguments
|
import com.maddyhome.idea.vim.command.OperatorArguments
|
||||||
import com.maddyhome.idea.vim.handler.VimActionHandler
|
import com.maddyhome.idea.vim.handler.VimActionHandler
|
||||||
import java.util.*
|
import java.util.*
|
||||||
import javax.swing.KeyStroke
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Called by KeyHandler to process the contents of the ex entry panel
|
* Called by KeyHandler to process the contents of the ex entry panel
|
||||||
@ -26,10 +24,7 @@ import javax.swing.KeyStroke
|
|||||||
* The mapping for this action means that the ex command is executed as a write action
|
* The mapping for this action means that the ex command is executed as a write action
|
||||||
*/
|
*/
|
||||||
@CommandOrMotion(keys = ["<CR>", "<C-M>", "<C-J>"], modes = [Mode.CMD_LINE])
|
@CommandOrMotion(keys = ["<CR>", "<C-M>", "<C-J>"], modes = [Mode.CMD_LINE])
|
||||||
public class ProcessExEntryAction : VimActionHandler.SingleExecution(), ComplicatedKeysAction {
|
public class ProcessExEntryAction : VimActionHandler.SingleExecution() {
|
||||||
override val keyStrokesSet: Set<List<KeyStroke>> =
|
|
||||||
parseKeysSet("<CR>", "<C-M>", 0x0a.toChar().toString(), 0x0d.toChar().toString())
|
|
||||||
|
|
||||||
override val type: Command.Type = Command.Type.OTHER_SELF_SYNCHRONIZED
|
override val type: Command.Type = Command.Type.OTHER_SELF_SYNCHRONIZED
|
||||||
|
|
||||||
override val flags: EnumSet<CommandFlags> = EnumSet.of(CommandFlags.FLAG_COMPLETE_EX)
|
override val flags: EnumSet<CommandFlags> = EnumSet.of(CommandFlags.FLAG_COMPLETE_EX)
|
||||||
|
@ -53,6 +53,11 @@ internal object VimExtensionRegistrar : VimExtensionRegistrator {
|
|||||||
@Synchronized
|
@Synchronized
|
||||||
private fun registerExtension(extensionBean: ExtensionBeanClass) {
|
private fun registerExtension(extensionBean: ExtensionBeanClass) {
|
||||||
val name = extensionBean.name ?: extensionBean.instance.name
|
val name = extensionBean.name ?: extensionBean.instance.name
|
||||||
|
if (name == "sneak" && extensionBean.name == null) {
|
||||||
|
// Filter out the old ideavim-sneak extension that used to be a separate plugin
|
||||||
|
// https://github.com/Mishkun/ideavim-sneak
|
||||||
|
return
|
||||||
|
}
|
||||||
if (name in registeredExtensions) return
|
if (name in registeredExtensions) return
|
||||||
|
|
||||||
registeredExtensions.add(name)
|
registeredExtensions.add(name)
|
||||||
|
@ -217,6 +217,8 @@ private object FileTypePatterns {
|
|||||||
|
|
||||||
return if (fileTypeName in htmlLikeFileTypes) {
|
return if (fileTypeName in htmlLikeFileTypes) {
|
||||||
this.htmlPatterns
|
this.htmlPatterns
|
||||||
|
} else if (fileTypeName == "JAVA" || fileExtension == "java") {
|
||||||
|
this.javaPatterns
|
||||||
} else if (fileTypeName == "Ruby" || fileExtension == "rb") {
|
} else if (fileTypeName == "Ruby" || fileExtension == "rb") {
|
||||||
this.rubyPatterns
|
this.rubyPatterns
|
||||||
} else if (fileTypeName == "RHTML" || fileExtension == "erb") {
|
} else if (fileTypeName == "RHTML" || fileExtension == "erb") {
|
||||||
@ -231,7 +233,7 @@ private object FileTypePatterns {
|
|||||||
} else if (fileTypeName == "CMakeLists.txt" || fileName == "CMakeLists") {
|
} else if (fileTypeName == "CMakeLists.txt" || fileName == "CMakeLists") {
|
||||||
this.cMakePatterns
|
this.cMakePatterns
|
||||||
} else {
|
} else {
|
||||||
return null
|
this.htmlPatterns
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -242,6 +244,7 @@ private object FileTypePatterns {
|
|||||||
)
|
)
|
||||||
|
|
||||||
private val htmlPatterns = createHtmlPatterns()
|
private val htmlPatterns = createHtmlPatterns()
|
||||||
|
private val javaPatterns = createJavaPatterns()
|
||||||
private val rubyPatterns = createRubyPatterns()
|
private val rubyPatterns = createRubyPatterns()
|
||||||
private val rubyAndHtmlPatterns = rubyPatterns + htmlPatterns
|
private val rubyAndHtmlPatterns = rubyPatterns + htmlPatterns
|
||||||
private val phpPatterns = createPhpPatterns()
|
private val phpPatterns = createPhpPatterns()
|
||||||
@ -270,6 +273,14 @@ private object FileTypePatterns {
|
|||||||
LanguagePatterns(linkedMapOf(openingTagPattern to htmlSearchPair), linkedMapOf(closingTagPattern to htmlSearchPair))
|
LanguagePatterns(linkedMapOf(openingTagPattern to htmlSearchPair), linkedMapOf(closingTagPattern to htmlSearchPair))
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private fun createJavaPatterns(): LanguagePatterns {
|
||||||
|
return (
|
||||||
|
LanguagePatterns("\\b(?<!else\\s+)if\\b", "\\belse\\s+if\\b", "\\belse(?!\\s+if)\\b") +
|
||||||
|
LanguagePatterns("\\bdo\\b", "\\bwhile\\b") +
|
||||||
|
LanguagePatterns("\\btry\\b", "\\bcatch\\b", "\\bfinally\\b")
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
private fun createRubyPatterns(): LanguagePatterns {
|
private fun createRubyPatterns(): LanguagePatterns {
|
||||||
// Original patterns: https://github.com/vim/vim/blob/master/runtime/ftplugin/ruby.vim
|
// Original patterns: https://github.com/vim/vim/blob/master/runtime/ftplugin/ruby.vim
|
||||||
|
@ -246,7 +246,7 @@ internal class VimMultipleCursorsExtension : VimExtension {
|
|||||||
|
|
||||||
// Note that ignoreCase is not overridden by the `\C` in the pattern
|
// Note that ignoreCase is not overridden by the `\C` in the pattern
|
||||||
val pattern = makePattern(text, whole)
|
val pattern = makePattern(text, whole)
|
||||||
val matches = SearchHelper.findAll(editor, pattern, 0, -1, false)
|
val matches = injector.searchHelper.findAll(IjVimEditor(editor), pattern, 0, -1, false)
|
||||||
for (match in matches) {
|
for (match in matches) {
|
||||||
if (match.contains(primaryCaret.offset)) {
|
if (match.contains(primaryCaret.offset)) {
|
||||||
primaryCaret.vim.moveToOffset(match.startOffset)
|
primaryCaret.vim.moveToOffset(match.startOffset)
|
||||||
@ -322,7 +322,7 @@ internal class VimMultipleCursorsExtension : VimExtension {
|
|||||||
searchOptions.add(SearchOptions.WRAP)
|
searchOptions.add(SearchOptions.WRAP)
|
||||||
}
|
}
|
||||||
|
|
||||||
return SearchHelper.findPattern(editor, makePattern(text, whole), startOffset, 1, searchOptions)?.startOffset ?: -1
|
return injector.searchHelper.findPattern(IjVimEditor(editor), makePattern(text, whole), startOffset, 1, searchOptions)?.startOffset ?: -1
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun makePattern(text: String, whole: Boolean): String {
|
private fun makePattern(text: String, whole: Boolean): String {
|
||||||
|
@ -502,6 +502,13 @@ internal class NerdTree : VimExtension {
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
)
|
)
|
||||||
|
|
||||||
|
for (c in ('a'..'z') + ('A'..'Z')) {
|
||||||
|
val ks = KeyStroke.getKeyStroke(c)
|
||||||
|
if (ks !in actionsRoot) {
|
||||||
|
registerCommand(c.toString(), NerdAction.Code { _, _, _ -> })
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
companion object {
|
companion object {
|
||||||
|
@ -11,8 +11,8 @@ package com.maddyhome.idea.vim.extension.paragraphmotion
|
|||||||
import com.intellij.openapi.editor.Caret
|
import com.intellij.openapi.editor.Caret
|
||||||
import com.maddyhome.idea.vim.api.ExecutionContext
|
import com.maddyhome.idea.vim.api.ExecutionContext
|
||||||
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.injector
|
import com.maddyhome.idea.vim.api.injector
|
||||||
import com.maddyhome.idea.vim.api.normalizeOffset
|
|
||||||
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.extension.ExtensionHandler
|
import com.maddyhome.idea.vim.extension.ExtensionHandler
|
||||||
@ -45,8 +45,7 @@ internal class ParagraphMotion : VimExtension {
|
|||||||
}
|
}
|
||||||
|
|
||||||
fun moveCaretToNextParagraph(editor: VimEditor, caret: Caret, count: Int): Int? {
|
fun moveCaretToNextParagraph(editor: VimEditor, caret: Caret, count: Int): Int? {
|
||||||
return injector.searchHelper.findNextParagraph(editor, caret.vim, count, true)
|
return injector.searchHelper.findNextParagraph(editor, caret.vim, count, true)?.let(editor::getLineEndForOffset)
|
||||||
?.let { editor.normalizeOffset(it, true) }
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -0,0 +1,321 @@
|
|||||||
|
/*
|
||||||
|
* Copyright 2003-2024 The IdeaVim authors
|
||||||
|
*
|
||||||
|
* Use of this source code is governed by an MIT-style
|
||||||
|
* license that can be found in the LICENSE.txt file or at
|
||||||
|
* https://opensource.org/licenses/MIT.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package com.maddyhome.idea.vim.extension.sneak
|
||||||
|
|
||||||
|
import com.intellij.openapi.actionSystem.DataContext
|
||||||
|
import com.intellij.openapi.editor.Editor
|
||||||
|
import com.intellij.openapi.editor.ScrollType
|
||||||
|
import com.intellij.openapi.editor.colors.EditorColors
|
||||||
|
import com.intellij.openapi.editor.markup.EffectType
|
||||||
|
import com.intellij.openapi.editor.markup.HighlighterLayer
|
||||||
|
import com.intellij.openapi.editor.markup.HighlighterTargetArea
|
||||||
|
import com.intellij.openapi.editor.markup.RangeHighlighter
|
||||||
|
import com.intellij.openapi.editor.markup.TextAttributes
|
||||||
|
import com.intellij.openapi.util.Disposer
|
||||||
|
import com.maddyhome.idea.vim.VimPlugin
|
||||||
|
import com.maddyhome.idea.vim.VimProjectService
|
||||||
|
import com.maddyhome.idea.vim.api.ExecutionContext
|
||||||
|
import com.maddyhome.idea.vim.api.VimEditor
|
||||||
|
import com.maddyhome.idea.vim.api.injector
|
||||||
|
import com.maddyhome.idea.vim.api.options
|
||||||
|
import com.maddyhome.idea.vim.command.MappingMode
|
||||||
|
import com.maddyhome.idea.vim.command.OperatorArguments
|
||||||
|
import com.maddyhome.idea.vim.common.TextRange
|
||||||
|
import com.maddyhome.idea.vim.extension.ExtensionHandler
|
||||||
|
import com.maddyhome.idea.vim.extension.VimExtension
|
||||||
|
import com.maddyhome.idea.vim.extension.VimExtensionFacade
|
||||||
|
import com.maddyhome.idea.vim.extension.VimExtensionFacade.putKeyMapping
|
||||||
|
import com.maddyhome.idea.vim.extension.VimExtensionHandler
|
||||||
|
import com.maddyhome.idea.vim.helper.StrictMode
|
||||||
|
import com.maddyhome.idea.vim.newapi.ij
|
||||||
|
import java.awt.Font
|
||||||
|
import java.awt.event.KeyEvent
|
||||||
|
import javax.swing.Timer
|
||||||
|
|
||||||
|
|
||||||
|
private const val DEFAULT_HIGHLIGHT_DURATION_SNEAK = 300
|
||||||
|
|
||||||
|
// By [Mikhail Levchenko](https://github.com/Mishkun)
|
||||||
|
// Original repository with the plugin: https://github.com/Mishkun/ideavim-sneak
|
||||||
|
internal class IdeaVimSneakExtension : VimExtension {
|
||||||
|
override fun getName(): String = "sneak"
|
||||||
|
|
||||||
|
override fun init() {
|
||||||
|
val highlightHandler = HighlightHandler()
|
||||||
|
mapToFunctionAndProvideKeys("s", SneakHandler(highlightHandler, Direction.FORWARD))
|
||||||
|
mapToFunctionAndProvideKeys("S", SneakHandler(highlightHandler, Direction.BACKWARD))
|
||||||
|
|
||||||
|
// workaround to support ; and , commands
|
||||||
|
mapToFunctionAndProvideKeys("f", SneakMemoryHandler("f"))
|
||||||
|
mapToFunctionAndProvideKeys("F", SneakMemoryHandler("F"))
|
||||||
|
mapToFunctionAndProvideKeys("t", SneakMemoryHandler("t"))
|
||||||
|
mapToFunctionAndProvideKeys("T", SneakMemoryHandler("T"))
|
||||||
|
|
||||||
|
mapToFunctionAndProvideKeys(";", SneakRepeatHandler(highlightHandler, RepeatDirection.IDENTICAL))
|
||||||
|
mapToFunctionAndProvideKeys(",", SneakRepeatHandler(highlightHandler, RepeatDirection.REVERSE))
|
||||||
|
}
|
||||||
|
|
||||||
|
private class SneakHandler(
|
||||||
|
private val highlightHandler: HighlightHandler,
|
||||||
|
private val direction: Direction,
|
||||||
|
) : ExtensionHandler {
|
||||||
|
override fun execute(editor: VimEditor, context: ExecutionContext, operatorArguments: OperatorArguments) {
|
||||||
|
val charone = getChar(editor) ?: return
|
||||||
|
val chartwo = getChar(editor) ?: return
|
||||||
|
val range = Util.jumpTo(editor, charone, chartwo, direction)
|
||||||
|
range?.let { highlightHandler.highlightSneakRange(editor.ij, range) }
|
||||||
|
Util.lastSymbols = "${charone}${chartwo}"
|
||||||
|
Util.lastSDirection = direction
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun getChar(editor: VimEditor): Char? {
|
||||||
|
val key = VimExtensionFacade.inputKeyStroke(editor.ij)
|
||||||
|
return when {
|
||||||
|
key.keyChar == KeyEvent.CHAR_UNDEFINED || key.keyCode == KeyEvent.VK_ESCAPE -> null
|
||||||
|
else -> key.keyChar
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This class acts as proxy for normal find commands because we need to update [Util.lastSDirection]
|
||||||
|
*/
|
||||||
|
private class SneakMemoryHandler(private val char: String) : VimExtensionHandler {
|
||||||
|
override fun execute(editor: Editor, context: DataContext) {
|
||||||
|
Util.lastSDirection = null
|
||||||
|
VimExtensionFacade.executeNormalWithoutMapping(injector.parser.parseKeys(char), editor)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private class SneakRepeatHandler(
|
||||||
|
private val highlightHandler: HighlightHandler,
|
||||||
|
private val direction: RepeatDirection,
|
||||||
|
) : ExtensionHandler {
|
||||||
|
override fun execute(editor: VimEditor, context: ExecutionContext, operatorArguments: OperatorArguments) {
|
||||||
|
val lastSDirection = Util.lastSDirection
|
||||||
|
if (lastSDirection != null) {
|
||||||
|
val (charone, chartwo) = Util.lastSymbols.toList()
|
||||||
|
val jumpRange = Util.jumpTo(editor, charone, chartwo, direction.map(lastSDirection))
|
||||||
|
jumpRange?.let { highlightHandler.highlightSneakRange(editor.ij, jumpRange) }
|
||||||
|
} else {
|
||||||
|
VimExtensionFacade.executeNormalWithoutMapping(injector.parser.parseKeys(direction.symb), editor.ij)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private object Util {
|
||||||
|
var lastSDirection: Direction? = null
|
||||||
|
var lastSymbols: String = ""
|
||||||
|
fun jumpTo(editor: VimEditor, charone: Char, chartwo: Char, sneakDirection: Direction): TextRange? {
|
||||||
|
val caret = editor.primaryCaret()
|
||||||
|
val position = caret.offset.point
|
||||||
|
val chars = editor.text()
|
||||||
|
val foundPosition = sneakDirection.findBiChar(editor, chars, position, charone, chartwo)
|
||||||
|
if (foundPosition != null) {
|
||||||
|
editor.primaryCaret().moveToOffset(foundPosition)
|
||||||
|
}
|
||||||
|
editor.ij.scrollingModel.scrollToCaret(ScrollType.MAKE_VISIBLE)
|
||||||
|
return foundPosition?.let { TextRange(foundPosition, foundPosition + 2) }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private enum class Direction(val offset: Int) {
|
||||||
|
FORWARD(1) {
|
||||||
|
override fun findBiChar(
|
||||||
|
editor: VimEditor,
|
||||||
|
charSequence: CharSequence,
|
||||||
|
position: Int,
|
||||||
|
charone: Char,
|
||||||
|
chartwo: Char
|
||||||
|
): Int? {
|
||||||
|
for (i in (position + offset) until charSequence.length - 1) {
|
||||||
|
if (matches(editor, charSequence, i, charone, chartwo)) {
|
||||||
|
return i
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
},
|
||||||
|
BACKWARD(-1) {
|
||||||
|
override fun findBiChar(
|
||||||
|
editor: VimEditor,
|
||||||
|
charSequence: CharSequence,
|
||||||
|
position: Int,
|
||||||
|
charone: Char,
|
||||||
|
chartwo: Char
|
||||||
|
): Int? {
|
||||||
|
for (i in (position + offset) downTo 0) {
|
||||||
|
if (matches(editor, charSequence, i, charone, chartwo)) {
|
||||||
|
return i
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
|
||||||
|
};
|
||||||
|
|
||||||
|
abstract fun findBiChar(
|
||||||
|
editor: VimEditor,
|
||||||
|
charSequence: CharSequence,
|
||||||
|
position: Int,
|
||||||
|
charone: Char,
|
||||||
|
chartwo: Char,
|
||||||
|
): Int?
|
||||||
|
|
||||||
|
fun matches(
|
||||||
|
editor: VimEditor,
|
||||||
|
charSequence: CharSequence,
|
||||||
|
charPosition: Int,
|
||||||
|
charOne: Char,
|
||||||
|
charTwo: Char,
|
||||||
|
): Boolean {
|
||||||
|
var match = charSequence[charPosition].equals(charOne, ignoreCase = injector.options(editor).ignorecase) &&
|
||||||
|
charSequence[charPosition + 1].equals(charTwo, ignoreCase = injector.options(editor).ignorecase)
|
||||||
|
|
||||||
|
if (injector.options(editor).ignorecase && injector.options(editor).smartcase) {
|
||||||
|
if (charOne.isUpperCase() || charTwo.isUpperCase()) {
|
||||||
|
match = charSequence[charPosition].equals(charOne, ignoreCase = false) &&
|
||||||
|
charSequence[charPosition + 1].equals(charTwo, ignoreCase = false)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return match
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private enum class RepeatDirection(val symb: String) {
|
||||||
|
IDENTICAL(";") {
|
||||||
|
override fun map(direction: Direction): Direction = direction
|
||||||
|
},
|
||||||
|
REVERSE(",") {
|
||||||
|
override fun map(direction: Direction): Direction = when (direction) {
|
||||||
|
Direction.FORWARD -> Direction.BACKWARD
|
||||||
|
Direction.BACKWARD -> Direction.FORWARD
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
abstract fun map(direction: Direction): Direction
|
||||||
|
}
|
||||||
|
|
||||||
|
private class HighlightHandler {
|
||||||
|
private var editor: Editor? = null
|
||||||
|
private val sneakHighlighters: MutableSet<RangeHighlighter> = mutableSetOf()
|
||||||
|
|
||||||
|
fun highlightSneakRange(editor: Editor, range: TextRange) {
|
||||||
|
clearAllSneakHighlighters()
|
||||||
|
|
||||||
|
this.editor = editor
|
||||||
|
val project = editor.project
|
||||||
|
if (project != null) {
|
||||||
|
Disposer.register(VimProjectService.getInstance(project)) {
|
||||||
|
this.editor = null
|
||||||
|
sneakHighlighters.clear()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (range.isMultiple) {
|
||||||
|
for (i in 0 until range.size()) {
|
||||||
|
highlightSingleRange(editor, range.startOffsets[i]..range.endOffsets[i])
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
highlightSingleRange(editor, range.startOffset..range.endOffset)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fun clearAllSneakHighlighters() {
|
||||||
|
sneakHighlighters.forEach { highlighter ->
|
||||||
|
editor?.markupModel?.removeHighlighter(highlighter) ?: StrictMode.fail("Highlighters without an editor")
|
||||||
|
}
|
||||||
|
|
||||||
|
sneakHighlighters.clear()
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun highlightSingleRange(editor: Editor, range: ClosedRange<Int>) {
|
||||||
|
val highlighter = editor.markupModel.addRangeHighlighter(
|
||||||
|
range.start,
|
||||||
|
range.endInclusive,
|
||||||
|
HighlighterLayer.SELECTION,
|
||||||
|
getHighlightTextAttributes(),
|
||||||
|
HighlighterTargetArea.EXACT_RANGE
|
||||||
|
)
|
||||||
|
|
||||||
|
sneakHighlighters.add(highlighter)
|
||||||
|
|
||||||
|
setClearHighlightRangeTimer(highlighter)
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun setClearHighlightRangeTimer(highlighter: RangeHighlighter) {
|
||||||
|
val timer = Timer(DEFAULT_HIGHLIGHT_DURATION_SNEAK) {
|
||||||
|
if (editor?.isDisposed != true) {
|
||||||
|
editor?.markupModel?.removeHighlighter(highlighter)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
timer.isRepeats = false
|
||||||
|
timer.start()
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun getHighlightTextAttributes() = TextAttributes(
|
||||||
|
null,
|
||||||
|
EditorColors.TEXT_SEARCH_RESULT_ATTRIBUTES.defaultAttributes.backgroundColor,
|
||||||
|
editor?.colorsScheme?.getColor(EditorColors.CARET_COLOR),
|
||||||
|
EffectType.SEARCH_MATCH,
|
||||||
|
Font.PLAIN
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Map some <Plug>(keys) command to given handler
|
||||||
|
* and create mapping to <Plug>(prefix)[keys]
|
||||||
|
*/
|
||||||
|
private fun VimExtension.mapToFunctionAndProvideKeys(keys: String, handler: ExtensionHandler) {
|
||||||
|
VimExtensionFacade.putExtensionHandlerMapping(
|
||||||
|
MappingMode.NXO,
|
||||||
|
injector.parser.parseKeys(command(keys)),
|
||||||
|
owner,
|
||||||
|
handler,
|
||||||
|
false
|
||||||
|
)
|
||||||
|
VimExtensionFacade.putExtensionHandlerMapping(
|
||||||
|
MappingMode.NXO,
|
||||||
|
injector.parser.parseKeys(commandFromOriginalPlugin(keys)),
|
||||||
|
owner,
|
||||||
|
handler,
|
||||||
|
false
|
||||||
|
)
|
||||||
|
|
||||||
|
// This is a combination to meet the following requirements:
|
||||||
|
// - Now we should support mappings from sneak `Sneak_s` and mappings from the previous version of the plugin `(sneak-s)`
|
||||||
|
// - The shortcut should not be registered if any of these mappings is overridden in .ideavimrc
|
||||||
|
// - The shortcut should not be registered if some other shortcut for this key exists
|
||||||
|
val fromKeys = injector.parser.parseKeys(keys)
|
||||||
|
val filteredModes = MappingMode.NXO.filterNotTo(HashSet()) {
|
||||||
|
VimPlugin.getKey().hasmapto(it, injector.parser.parseKeys(command(keys)))
|
||||||
|
}
|
||||||
|
val filteredModes2 = MappingMode.NXO.filterNotTo(HashSet()) {
|
||||||
|
VimPlugin.getKey().hasmapto(it, injector.parser.parseKeys(commandFromOriginalPlugin(keys)))
|
||||||
|
}
|
||||||
|
val filteredFromModes = MappingMode.NXO.filterNotTo(HashSet()) {
|
||||||
|
injector.keyGroup.hasmapfrom(it, fromKeys)
|
||||||
|
}
|
||||||
|
|
||||||
|
val doubleFiltered = MappingMode.NXO
|
||||||
|
.filter { it in filteredModes2 && it in filteredModes && it in filteredFromModes }
|
||||||
|
.toSet()
|
||||||
|
putKeyMapping(doubleFiltered, fromKeys, owner, injector.parser.parseKeys(command(keys)), true)
|
||||||
|
putKeyMapping(
|
||||||
|
doubleFiltered,
|
||||||
|
fromKeys,
|
||||||
|
owner,
|
||||||
|
injector.parser.parseKeys(commandFromOriginalPlugin(keys)),
|
||||||
|
true
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun command(keys: String) = "<Plug>(sneak-$keys)"
|
||||||
|
private fun commandFromOriginalPlugin(keys: String) = "<Plug>Sneak_$keys"
|
@ -0,0 +1,30 @@
|
|||||||
|
package com.maddyhome.idea.vim.extension.surround
|
||||||
|
|
||||||
|
import com.intellij.util.text.CharSequenceSubSequence
|
||||||
|
|
||||||
|
internal data class RepeatedCharSequence(val text: CharSequence, val count: Int) : CharSequence {
|
||||||
|
override val length = text.length * count
|
||||||
|
|
||||||
|
override fun get(index: Int): Char {
|
||||||
|
if (index < 0 || index >= length) throw IndexOutOfBoundsException()
|
||||||
|
return text[index % text.length]
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun subSequence(startIndex: Int, endIndex: Int): CharSequence {
|
||||||
|
return CharSequenceSubSequence(this, startIndex, endIndex)
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun toString(): String {
|
||||||
|
return text.repeat(count)
|
||||||
|
}
|
||||||
|
|
||||||
|
companion object {
|
||||||
|
fun of(text: CharSequence, count: Int): CharSequence {
|
||||||
|
return when (count) {
|
||||||
|
0 -> ""
|
||||||
|
1 -> text
|
||||||
|
else -> RepeatedCharSequence(text, count)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -13,6 +13,7 @@ import com.intellij.openapi.editor.Editor
|
|||||||
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.VimCaret
|
import com.maddyhome.idea.vim.api.VimCaret
|
||||||
|
import com.maddyhome.idea.vim.api.VimChangeGroup
|
||||||
import com.maddyhome.idea.vim.api.VimEditor
|
import com.maddyhome.idea.vim.api.VimEditor
|
||||||
import com.maddyhome.idea.vim.api.endsWithNewLine
|
import com.maddyhome.idea.vim.api.endsWithNewLine
|
||||||
import com.maddyhome.idea.vim.api.getLeadingCharacterOffset
|
import com.maddyhome.idea.vim.api.getLeadingCharacterOffset
|
||||||
@ -31,7 +32,10 @@ import com.maddyhome.idea.vim.extension.VimExtensionFacade.putExtensionHandlerMa
|
|||||||
import com.maddyhome.idea.vim.extension.VimExtensionFacade.putKeyMappingIfMissing
|
import com.maddyhome.idea.vim.extension.VimExtensionFacade.putKeyMappingIfMissing
|
||||||
import com.maddyhome.idea.vim.extension.VimExtensionFacade.setOperatorFunction
|
import com.maddyhome.idea.vim.extension.VimExtensionFacade.setOperatorFunction
|
||||||
import com.maddyhome.idea.vim.extension.VimExtensionFacade.setRegisterForCaret
|
import com.maddyhome.idea.vim.extension.VimExtensionFacade.setRegisterForCaret
|
||||||
|
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.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.options.helpers.ClipboardOptionHelper
|
import com.maddyhome.idea.vim.options.helpers.ClipboardOptionHelper
|
||||||
@ -80,7 +84,7 @@ internal class VimSurroundExtension : 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) {
|
||||||
setOperatorFunction(Operator())
|
setOperatorFunction(Operator(supportsMultipleCursors = false, count = 1)) // TODO
|
||||||
executeNormalWithoutMapping(injector.parser.parseKeys("g@"), editor.ij)
|
executeNormalWithoutMapping(injector.parser.parseKeys("g@"), editor.ij)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -101,7 +105,7 @@ internal class VimSurroundExtension : VimExtension {
|
|||||||
val lastNonWhiteSpaceOffset = getLastNonWhitespaceCharacterOffset(editor.text(), lineStartOffset, lineEndOffset)
|
val lastNonWhiteSpaceOffset = getLastNonWhitespaceCharacterOffset(editor.text(), lineStartOffset, lineEndOffset)
|
||||||
if (lastNonWhiteSpaceOffset != null) {
|
if (lastNonWhiteSpaceOffset != null) {
|
||||||
val range = TextRange(lineStartOffset, lastNonWhiteSpaceOffset + 1)
|
val range = TextRange(lineStartOffset, lastNonWhiteSpaceOffset + 1)
|
||||||
performSurround(pair, range, it)
|
performSurround(pair, range, it, count = operatorArguments.count1)
|
||||||
}
|
}
|
||||||
// it.moveToOffset(lineStartOffset)
|
// it.moveToOffset(lineStartOffset)
|
||||||
}
|
}
|
||||||
@ -121,15 +125,13 @@ internal class VimSurroundExtension : VimExtension {
|
|||||||
|
|
||||||
private class VSurroundHandler : ExtensionHandler {
|
private class VSurroundHandler : ExtensionHandler {
|
||||||
override fun execute(editor: VimEditor, context: ExecutionContext, operatorArguments: OperatorArguments) {
|
override fun execute(editor: VimEditor, context: ExecutionContext, operatorArguments: OperatorArguments) {
|
||||||
val selectionStart = editor.ij.caretModel.primaryCaret.selectionStart
|
|
||||||
// NB: Operator ignores SelectionType anyway
|
// NB: Operator ignores SelectionType anyway
|
||||||
if (!Operator().apply(editor, context, editor.mode.selectionType)) {
|
if (!Operator(supportsMultipleCursors = true, count = operatorArguments.count1).apply(editor, context, editor.mode.selectionType)) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
runWriteAction {
|
runWriteAction {
|
||||||
// Leave visual mode
|
// Leave visual mode
|
||||||
executeNormalWithoutMapping(injector.parser.parseKeys("<Esc>"), editor.ij)
|
executeNormalWithoutMapping(injector.parser.parseKeys("<Esc>"), editor.ij)
|
||||||
editor.ij.caretModel.moveToOffset(selectionStart)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -150,6 +152,10 @@ 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: Pair<String, String>?) {
|
||||||
|
editor.ij.runWithEveryCaretAndRestore { changeAtCaret(editor, context, charFrom, newSurround) }
|
||||||
|
}
|
||||||
|
|
||||||
|
fun changeAtCaret(editor: VimEditor, context: ExecutionContext, charFrom: Char, newSurround: Pair<String, String>?) {
|
||||||
// Save old register values for carets
|
// Save old register values for carets
|
||||||
val surroundings = editor.sortedCarets()
|
val surroundings = editor.sortedCarets()
|
||||||
.map {
|
.map {
|
||||||
@ -257,20 +263,41 @@ internal class VimSurroundExtension : VimExtension {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private class Operator : OperatorFunction {
|
private class Operator(private val supportsMultipleCursors: Boolean, private val count: Int) : OperatorFunction {
|
||||||
override fun apply(editor: VimEditor, context: ExecutionContext, selectionType: SelectionType?): Boolean {
|
override fun apply(vimEditor: VimEditor, context: ExecutionContext, selectionType: SelectionType?): Boolean {
|
||||||
val ijEditor = editor.ij
|
val editor = vimEditor.ij
|
||||||
val c = getChar(ijEditor)
|
val c = getChar(editor)
|
||||||
if (c.code == 0) return true
|
if (c.code == 0) return true
|
||||||
|
|
||||||
val pair = getOrInputPair(c, ijEditor) ?: return false
|
val pair = getOrInputPair(c, editor) ?: return false
|
||||||
// XXX: Will it work with line-wise or block-wise selections?
|
|
||||||
val range = getSurroundRange(editor.currentCaret()) ?: return false
|
runWriteAction {
|
||||||
performSurround(pair, range, editor.currentCaret(), selectionType == SelectionType.LINE_WISE)
|
val change = VimPlugin.getChange()
|
||||||
// Jump back to start
|
if (supportsMultipleCursors) {
|
||||||
executeNormalWithoutMapping(injector.parser.parseKeys("`["), ijEditor)
|
editor.runWithEveryCaretAndRestore {
|
||||||
|
applyOnce(editor, change, pair, count)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
applyOnce(editor, change, pair, count)
|
||||||
|
// Jump back to start
|
||||||
|
executeNormalWithoutMapping(injector.parser.parseKeys("`["), editor)
|
||||||
|
}
|
||||||
|
}
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private fun applyOnce(editor: Editor, change: VimChangeGroup, pair: Pair<String, String>, count: Int) {
|
||||||
|
// XXX: Will it work with line-wise or block-wise selections?
|
||||||
|
val primaryCaret = editor.caretModel.primaryCaret
|
||||||
|
val range = getSurroundRange(primaryCaret.vim)
|
||||||
|
if (range != null) {
|
||||||
|
val start = RepeatedCharSequence.of(pair.first, count)
|
||||||
|
val end = RepeatedCharSequence.of(pair.second, count)
|
||||||
|
change.insertText(IjVimEditor(editor), IjVimCaret(primaryCaret), range.startOffset, start)
|
||||||
|
change.insertText(IjVimEditor(editor), IjVimCaret(primaryCaret), range.endOffset + start.length, end)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
private fun getSurroundRange(caret: VimCaret): TextRange? {
|
private fun getSurroundRange(caret: VimCaret): TextRange? {
|
||||||
val editor = caret.editor
|
val editor = caret.editor
|
||||||
@ -354,15 +381,15 @@ private fun getChar(editor: Editor): Char {
|
|||||||
return res
|
return res
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun performSurround(pair: Pair<String, String>, range: TextRange, caret: VimCaret, tagsOnNewLines: Boolean = false) {
|
private fun performSurround(pair: Pair<String, String>, range: TextRange, caret: VimCaret, count: Int, tagsOnNewLines: Boolean = false) {
|
||||||
runWriteAction {
|
runWriteAction {
|
||||||
val editor = caret.editor
|
val editor = caret.editor
|
||||||
val change = VimPlugin.getChange()
|
val change = VimPlugin.getChange()
|
||||||
val leftSurround = pair.first + if (tagsOnNewLines) "\n" else ""
|
val leftSurround = RepeatedCharSequence.of(pair.first + if (tagsOnNewLines) "\n" else "", count)
|
||||||
|
|
||||||
val isEOF = range.endOffset == editor.text().length
|
val isEOF = range.endOffset == editor.text().length
|
||||||
val hasNewLine = editor.endsWithNewLine()
|
val hasNewLine = editor.endsWithNewLine()
|
||||||
val rightSurround = if (tagsOnNewLines) {
|
val rightSurround = (if (tagsOnNewLines) {
|
||||||
if (isEOF && !hasNewLine) {
|
if (isEOF && !hasNewLine) {
|
||||||
"\n" + pair.second
|
"\n" + pair.second
|
||||||
} else {
|
} else {
|
||||||
@ -370,7 +397,7 @@ private fun performSurround(pair: Pair<String, String>, range: TextRange, caret:
|
|||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
pair.second
|
pair.second
|
||||||
}
|
}).let { RepeatedCharSequence.of(it, count) }
|
||||||
|
|
||||||
change.insertText(editor, caret, range.startOffset, leftSurround)
|
change.insertText(editor, caret, range.startOffset, leftSurround)
|
||||||
change.insertText(editor, caret, range.endOffset + leftSurround.length, rightSurround)
|
change.insertText(editor, caret, range.endOffset + leftSurround.length, rightSurround)
|
||||||
|
@ -78,7 +78,6 @@ import java.math.BigInteger
|
|||||||
import java.util.*
|
import java.util.*
|
||||||
import java.util.function.Consumer
|
import java.util.function.Consumer
|
||||||
import kotlin.math.max
|
import kotlin.math.max
|
||||||
import kotlin.math.min
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Provides all the insert/replace related functionality
|
* Provides all the insert/replace related functionality
|
||||||
@ -395,6 +394,7 @@ public class ChangeGroup : VimChangeGroupBase() {
|
|||||||
context: ExecutionContext,
|
context: ExecutionContext,
|
||||||
range: TextRange,
|
range: TextRange,
|
||||||
) {
|
) {
|
||||||
|
val startPos = editor.offsetToBufferPosition(caret.offset.point)
|
||||||
val startOffset = editor.getLineStartForOffset(range.startOffset)
|
val startOffset = editor.getLineStartForOffset(range.startOffset)
|
||||||
val endOffset = editor.getLineEndForOffset(range.endOffset)
|
val endOffset = editor.getLineEndForOffset(range.endOffset)
|
||||||
val ijEditor = (editor as IjVimEditor).editor
|
val ijEditor = (editor as IjVimEditor).editor
|
||||||
@ -419,11 +419,7 @@ public class ChangeGroup : VimChangeGroupBase() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
val afterAction = {
|
val afterAction = {
|
||||||
val firstLine = editor.offsetToBufferPosition(
|
caret.moveToOffset(injector.motion.moveCaretToLineStartSkipLeading(editor, startPos.line))
|
||||||
min(startOffset.toDouble(), endOffset.toDouble()).toInt()
|
|
||||||
).line
|
|
||||||
val newOffset = injector.motion.moveCaretToLineStartSkipLeading(editor, firstLine)
|
|
||||||
caret.moveToOffset(newOffset)
|
|
||||||
restoreCursor(editor, caret, (caret as IjVimCaret).caret.logicalPosition.line)
|
restoreCursor(editor, caret, (caret as IjVimCaret).caret.logicalPosition.line)
|
||||||
}
|
}
|
||||||
if (project != null) {
|
if (project != null) {
|
||||||
|
@ -30,7 +30,6 @@ public open class GlobalIjOptions(scope: OptionAccessScope) : OptionsPropertiesB
|
|||||||
public var trackactionids: Boolean by optionProperty(IjOptions.trackactionids)
|
public var trackactionids: Boolean by optionProperty(IjOptions.trackactionids)
|
||||||
public var visualdelay: Int by optionProperty(IjOptions.visualdelay)
|
public var visualdelay: Int by optionProperty(IjOptions.visualdelay)
|
||||||
public var showmodewidget: Boolean by optionProperty(IjOptions.showmodewidget)
|
public var showmodewidget: Boolean by optionProperty(IjOptions.showmodewidget)
|
||||||
public var colorfulmodewidget: Boolean by optionProperty(IjOptions.colorfulmodewidget)
|
|
||||||
|
|
||||||
// Temporary options to control work-in-progress behaviour
|
// Temporary options to control work-in-progress behaviour
|
||||||
public var oldundo: Boolean by optionProperty(IjOptions.oldundo)
|
public var oldundo: Boolean by optionProperty(IjOptions.oldundo)
|
||||||
@ -38,6 +37,7 @@ public open class GlobalIjOptions(scope: OptionAccessScope) : OptionsPropertiesB
|
|||||||
public var exCommandAnnotation: Boolean by optionProperty(IjOptions.exCommandAnnotation)
|
public var exCommandAnnotation: Boolean by optionProperty(IjOptions.exCommandAnnotation)
|
||||||
public var vimscriptFunctionAnnotation: Boolean by optionProperty(IjOptions.vimscriptFunctionAnnotation)
|
public var vimscriptFunctionAnnotation: Boolean by optionProperty(IjOptions.vimscriptFunctionAnnotation)
|
||||||
public var commandOrMotionAnnotation: Boolean by optionProperty(IjOptions.commandOrMotionAnnotation)
|
public var commandOrMotionAnnotation: Boolean by optionProperty(IjOptions.commandOrMotionAnnotation)
|
||||||
|
public var useNewRegex: Boolean by optionProperty(IjOptions.useNewRegex)
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -83,13 +83,13 @@ public object IjOptions {
|
|||||||
public val trackactionids: ToggleOption = addOption(ToggleOption("trackactionids", GLOBAL, "tai", false))
|
public val trackactionids: ToggleOption = addOption(ToggleOption("trackactionids", GLOBAL, "tai", false))
|
||||||
public val unifyjumps: ToggleOption = addOption(ToggleOption("unifyjumps", GLOBAL, "unifyjumps", true))
|
public val unifyjumps: ToggleOption = addOption(ToggleOption("unifyjumps", GLOBAL, "unifyjumps", true))
|
||||||
public val visualdelay: UnsignedNumberOption = addOption(UnsignedNumberOption("visualdelay", GLOBAL, "visualdelay", 100))
|
public val visualdelay: UnsignedNumberOption = addOption(UnsignedNumberOption("visualdelay", GLOBAL, "visualdelay", 100))
|
||||||
public val oldundo: ToggleOption = addOption(ToggleOption("oldundo", GLOBAL, "oldundo", false, isTemporary = true))
|
public val oldundo: ToggleOption = addOption(ToggleOption("oldundo", GLOBAL, "oldundo", true, isTemporary = true))
|
||||||
public val vimscriptFunctionAnnotation: ToggleOption = addOption(ToggleOption("vimscriptfunctionannotation", GLOBAL, "vimscriptfunctionannotation", true, isTemporary = true))
|
public val vimscriptFunctionAnnotation: ToggleOption = addOption(ToggleOption("vimscriptfunctionannotation", GLOBAL, "vimscriptfunctionannotation", true, isTemporary = true))
|
||||||
public val commandOrMotionAnnotation: ToggleOption = addOption(ToggleOption("commandormotionannotation", GLOBAL, "commandormotionannotation", true, isTemporary = true))
|
public val commandOrMotionAnnotation: ToggleOption = addOption(ToggleOption("commandormotionannotation", GLOBAL, "commandormotionannotation", true, isTemporary = true))
|
||||||
public val showmodewidget: ToggleOption = addOption(ToggleOption("showmodewidget", GLOBAL, "showmodewidget", false, isTemporary = true))
|
public val showmodewidget: ToggleOption = addOption(ToggleOption("showmodewidget", GLOBAL, "showmodewidget", false, isTemporary = true))
|
||||||
public val colorfulmodewidget: ToggleOption = addOption(ToggleOption("colorfulmodewidget", GLOBAL, "colorfulmodewidget", false, isTemporary = true))
|
public val useNewRegex: ToggleOption = addOption(ToggleOption("usenewregex", GLOBAL, "usenewregex", true, isTemporary = true))
|
||||||
|
|
||||||
// This needs to be Option<out VimDataType> so that it can work with derived option types, such as NumberOption, which
|
// This needs to be Option<out VimDataType> so that it can work with derived option types, such as NumberOption, which
|
||||||
// derives from Option<VimInt>
|
// derives from Option<VimInt>
|
||||||
private fun <T : Option<out VimDataType>> addOption(option: T) = option.also { Options.addOption(option) }
|
private fun <T : Option<out VimDataType>> addOption(option: T) = option.also { Options.addOption(option) }
|
||||||
}
|
}
|
||||||
|
@ -24,17 +24,14 @@ import com.intellij.openapi.keymap.KeymapManager;
|
|||||||
import com.intellij.openapi.keymap.ex.KeymapManagerEx;
|
import com.intellij.openapi.keymap.ex.KeymapManagerEx;
|
||||||
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.ComplicatedKeysAction;
|
|
||||||
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.*;
|
import com.maddyhome.idea.vim.api.*;
|
||||||
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.ex.ExOutputModel;
|
||||||
import com.maddyhome.idea.vim.handler.EditorActionHandlerBase;
|
|
||||||
import com.maddyhome.idea.vim.helper.HelperKt;
|
import com.maddyhome.idea.vim.helper.HelperKt;
|
||||||
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.IjVimActionsInitiator;
|
|
||||||
import com.maddyhome.idea.vim.newapi.IjVimEditor;
|
import com.maddyhome.idea.vim.newapi.IjVimEditor;
|
||||||
import kotlin.Pair;
|
import kotlin.Pair;
|
||||||
import kotlin.text.StringsKt;
|
import kotlin.text.StringsKt;
|
||||||
@ -49,6 +46,7 @@ import java.awt.event.KeyEvent;
|
|||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.*;
|
import java.util.*;
|
||||||
|
|
||||||
|
import static com.maddyhome.idea.vim.api.VimInjectorKt.injector;
|
||||||
import static java.util.stream.Collectors.toList;
|
import static java.util.stream.Collectors.toList;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -221,63 +219,20 @@ 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<VimActionsInitiator> node = getKeyRoot(mappingMode);
|
Node<LazyVimCommand> node = getKeyRoot(mappingMode);
|
||||||
NodesKt.addLeafs(node, keyStrokes, command);
|
NodesKt.addLeafs(node, keyStrokes, command);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@Deprecated
|
|
||||||
public void registerCommandAction(@NotNull VimActionsInitiator actionHolder) {
|
|
||||||
IjVimActionsInitiator holder = (IjVimActionsInitiator)actionHolder;
|
|
||||||
|
|
||||||
if (!VimPlugin.getPluginId().equals(holder.getBean().getPluginDescriptor().getPluginId())) {
|
|
||||||
logger.error("IdeaVim doesn't accept contributions to `vimActions` extension points. " +
|
|
||||||
"Please create a plugin using `VimExtension`. " +
|
|
||||||
"Plugin to blame: " +
|
|
||||||
holder.getBean().getPluginDescriptor().getPluginId());
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
Set<List<KeyStroke>> actionKeys = holder.getBean().getParsedKeys();
|
|
||||||
if (actionKeys == null) {
|
|
||||||
final EditorActionHandlerBase action = actionHolder.getInstance();
|
|
||||||
if (action instanceof ComplicatedKeysAction) {
|
|
||||||
actionKeys = ((ComplicatedKeysAction)action).getKeyStrokesSet();
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
throw new RuntimeException("Cannot register action: " + action.getClass().getName());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
Set<MappingMode> actionModes = holder.getBean().getParsedModes();
|
|
||||||
if (actionModes == null) {
|
|
||||||
throw new RuntimeException("Cannot register action: " + holder.getBean().getImplementation());
|
|
||||||
}
|
|
||||||
|
|
||||||
if (ApplicationManager.getApplication().isUnitTestMode()) {
|
|
||||||
initIdentityChecker();
|
|
||||||
for (List<KeyStroke> keys : actionKeys) {
|
|
||||||
checkCommand(actionModes, actionHolder.getInstance(), keys);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
for (List<KeyStroke> keyStrokes : actionKeys) {
|
|
||||||
registerRequiredShortcut(keyStrokes, MappingOwner.IdeaVim.System.INSTANCE);
|
|
||||||
|
|
||||||
for (MappingMode mappingMode : actionModes) {
|
|
||||||
Node<VimActionsInitiator> node = getKeyRoot(mappingMode);
|
|
||||||
NodesKt.addLeafs(node, keyStrokes, actionHolder);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private void registerRequiredShortcut(@NotNull List<KeyStroke> keys, MappingOwner owner) {
|
private void registerRequiredShortcut(@NotNull List<KeyStroke> keys, MappingOwner owner) {
|
||||||
for (KeyStroke key : keys) {
|
for (KeyStroke key : keys) {
|
||||||
if (key.getKeyChar() == KeyEvent.CHAR_UNDEFINED &&
|
if (key.getKeyChar() == KeyEvent.CHAR_UNDEFINED) {
|
||||||
!(key.getKeyCode() == KeyEvent.VK_ESCAPE && key.getModifiers() == 0) &&
|
if (!injector.getOptionGroup().getGlobalOptions().getOctopushandler() ||
|
||||||
!(key.getKeyCode() == KeyEvent.VK_ENTER && key.getModifiers() == 0)) {
|
!(key.getKeyCode() == KeyEvent.VK_ESCAPE && key.getModifiers() == 0) &&
|
||||||
getRequiredShortcutKeys().add(new RequiredShortcut(key, owner));
|
!(key.getKeyCode() == KeyEvent.VK_ENTER && key.getModifiers() == 0)) {
|
||||||
|
getRequiredShortcutKeys().add(new RequiredShortcut(key, owner));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -0,0 +1,68 @@
|
|||||||
|
package com.maddyhome.idea.vim.group
|
||||||
|
|
||||||
|
import com.intellij.codeInsight.daemon.ReferenceImporter
|
||||||
|
import com.intellij.openapi.actionSystem.CommonDataKeys
|
||||||
|
import com.intellij.openapi.actionSystem.DataContext
|
||||||
|
import com.intellij.openapi.application.ApplicationManager
|
||||||
|
import com.intellij.openapi.application.ReadAction
|
||||||
|
import com.intellij.openapi.command.WriteCommandAction
|
||||||
|
import com.intellij.openapi.editor.Editor
|
||||||
|
import com.intellij.openapi.fileEditor.FileDocumentManager
|
||||||
|
import com.intellij.openapi.progress.ProgressIndicator
|
||||||
|
import com.intellij.openapi.progress.ProgressManager
|
||||||
|
import com.intellij.openapi.progress.Task
|
||||||
|
import com.intellij.psi.PsiDocumentManager
|
||||||
|
import com.intellij.psi.PsiElement
|
||||||
|
import com.intellij.psi.PsiRecursiveElementWalkingVisitor
|
||||||
|
import java.util.function.BooleanSupplier
|
||||||
|
|
||||||
|
internal object MacroAutoImport {
|
||||||
|
fun run(editor: Editor, dataContext: DataContext) {
|
||||||
|
val project = CommonDataKeys.PROJECT.getData(dataContext) ?: return
|
||||||
|
val file = PsiDocumentManager.getInstance(project).getPsiFile(editor.document) ?: return
|
||||||
|
|
||||||
|
if (!FileDocumentManager.getInstance().requestWriting(editor.document, project)) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
val importers = ReferenceImporter.EP_NAME.extensionList
|
||||||
|
if (importers.isEmpty()) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
ProgressManager.getInstance().run(object : Task.Backgroundable(project, "Auto import", true) {
|
||||||
|
override fun run(indicator: ProgressIndicator) {
|
||||||
|
val fixes = ReadAction.nonBlocking<List<BooleanSupplier>> {
|
||||||
|
val fixes = mutableListOf<BooleanSupplier>()
|
||||||
|
|
||||||
|
file.accept(object : PsiRecursiveElementWalkingVisitor() {
|
||||||
|
override fun visitElement(element: PsiElement) {
|
||||||
|
for (reference in element.references) {
|
||||||
|
if (reference.resolve() != null) {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
for (importer in importers) {
|
||||||
|
importer.computeAutoImportAtOffset(editor, file, element.textRange.startOffset, true)
|
||||||
|
?.let(fixes::add)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
super.visitElement(element)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
return@nonBlocking fixes
|
||||||
|
}.executeSynchronously()
|
||||||
|
|
||||||
|
ApplicationManager.getApplication().invokeAndWait {
|
||||||
|
WriteCommandAction.writeCommandAction(project)
|
||||||
|
.withName("Auto Import")
|
||||||
|
.withGroupId("IdeaVimAutoImportAfterMacro")
|
||||||
|
.shouldRecordActionForActiveDocument(true)
|
||||||
|
.run<RuntimeException> {
|
||||||
|
fixes.forEach { it.asBoolean }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
@ -7,8 +7,6 @@
|
|||||||
*/
|
*/
|
||||||
package com.maddyhome.idea.vim.group
|
package com.maddyhome.idea.vim.group
|
||||||
|
|
||||||
import com.intellij.codeInsight.completion.CompletionPhase
|
|
||||||
import com.intellij.codeInsight.completion.impl.CompletionServiceImpl
|
|
||||||
import com.intellij.openapi.application.ApplicationManager
|
import com.intellij.openapi.application.ApplicationManager
|
||||||
import com.intellij.openapi.diagnostic.logger
|
import com.intellij.openapi.diagnostic.logger
|
||||||
import com.intellij.openapi.progress.ProcessCanceledException
|
import com.intellij.openapi.progress.ProcessCanceledException
|
||||||
@ -21,6 +19,7 @@ import com.maddyhome.idea.vim.api.injector
|
|||||||
import com.maddyhome.idea.vim.helper.MessageHelper.message
|
import com.maddyhome.idea.vim.helper.MessageHelper.message
|
||||||
import com.maddyhome.idea.vim.macro.VimMacroBase
|
import com.maddyhome.idea.vim.macro.VimMacroBase
|
||||||
import com.maddyhome.idea.vim.newapi.IjVimEditor
|
import com.maddyhome.idea.vim.newapi.IjVimEditor
|
||||||
|
import com.maddyhome.idea.vim.newapi.ij
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Used to handle playback of macros
|
* Used to handle playback of macros
|
||||||
@ -78,7 +77,7 @@ internal class MacroGroup : VimMacroBase() {
|
|||||||
ProgressManager.getInstance().executeNonCancelableSection {
|
ProgressManager.getInstance().executeNonCancelableSection {
|
||||||
// Prevent autocompletion during macros.
|
// Prevent autocompletion during macros.
|
||||||
// See https://github.com/JetBrains/ideavim/pull/772 for details
|
// See https://github.com/JetBrains/ideavim/pull/772 for details
|
||||||
CompletionServiceImpl.setCompletionPhase(CompletionPhase.NoCompletion)
|
// CompletionServiceImpl.setCompletionPhase(CompletionPhase.NoCompletion)
|
||||||
getInstance().handleKey(editor, key, context)
|
getInstance().handleKey(editor, key, context)
|
||||||
}
|
}
|
||||||
if (injector.messages.isError()) return@runnable
|
if (injector.messages.isError()) return@runnable
|
||||||
@ -90,6 +89,9 @@ internal class MacroGroup : VimMacroBase() {
|
|||||||
} finally {
|
} finally {
|
||||||
keyStack.removeFirst()
|
keyStack.removeFirst()
|
||||||
}
|
}
|
||||||
|
if (!isInternalMacro) {
|
||||||
|
MacroAutoImport.run(editor.ij, context.ij)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (isInternalMacro) {
|
if (isInternalMacro) {
|
||||||
|
@ -1,138 +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.ide.bookmark.LineBookmark;
|
|
||||||
import com.intellij.openapi.editor.Editor;
|
|
||||||
import com.maddyhome.idea.vim.api.*;
|
|
||||||
import com.maddyhome.idea.vim.common.TextRange;
|
|
||||||
import com.maddyhome.idea.vim.mark.IntellijMark;
|
|
||||||
import com.maddyhome.idea.vim.mark.Jump;
|
|
||||||
import com.maddyhome.idea.vim.mark.Mark;
|
|
||||||
import com.maddyhome.idea.vim.newapi.IjVimEditor;
|
|
||||||
import org.jetbrains.annotations.ApiStatus;
|
|
||||||
import org.jetbrains.annotations.NotNull;
|
|
||||||
import org.jetbrains.annotations.Nullable;
|
|
||||||
|
|
||||||
import java.util.ArrayList;
|
|
||||||
import java.util.HashMap;
|
|
||||||
import java.util.List;
|
|
||||||
import java.util.Set;
|
|
||||||
|
|
||||||
@Deprecated
|
|
||||||
@ApiStatus.ScheduledForRemoval(inVersion = "2.3")
|
|
||||||
public class MarkGroup {
|
|
||||||
public List<Jump> jumps = VimInjectorKt.injector.getJumpService().getJumps("");
|
|
||||||
|
|
||||||
public void saveJumpLocation(@NotNull Editor editor) {
|
|
||||||
VimInjectorKt.injector.getJumpService().saveJumpLocation(new IjVimEditor(editor));
|
|
||||||
}
|
|
||||||
|
|
||||||
public void saveJumpLocation(@NotNull VimEditor editor) {
|
|
||||||
VimInjectorKt.injector.getJumpService().saveJumpLocation(editor);
|
|
||||||
}
|
|
||||||
|
|
||||||
public void setChangeMarks(@NotNull VimEditor vimEditor, @NotNull TextRange range) {
|
|
||||||
VimMarkService markService = VimInjectorKt.injector.getMarkService();
|
|
||||||
VimMarkServiceKt.setChangeMarks(markService, vimEditor.primaryCaret(), range);
|
|
||||||
}
|
|
||||||
|
|
||||||
public void addJump(@NotNull VimEditor editor, boolean reset) {
|
|
||||||
VimJumpServiceKt.addJump(VimInjectorKt.injector.getJumpService(), editor, reset);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Nullable
|
|
||||||
public Mark getMark(@NotNull VimEditor editor, char ch) {
|
|
||||||
return VimInjectorKt.injector.getMarkService().getMark(editor.primaryCaret(), ch);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Nullable
|
|
||||||
public Jump getJump(int count) {
|
|
||||||
return VimInjectorKt.injector.getJumpService().getJump("", count);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Nullable
|
|
||||||
public Mark createSystemMark(char ch, int line, int col, @NotNull VimEditor editor) {
|
|
||||||
Editor ijEditor = ((IjVimEditor)editor).getEditor();
|
|
||||||
@Nullable LineBookmark systemMark = SystemMarks.createOrGetSystemMark(ch, line, ijEditor);
|
|
||||||
if (systemMark == null) {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
return new IntellijMark(systemMark, col, ijEditor.getProject());
|
|
||||||
}
|
|
||||||
|
|
||||||
public boolean setMark(@NotNull VimEditor editor, char ch, int offset) {
|
|
||||||
return VimInjectorKt.injector.getMarkService().setMark(editor.primaryCaret(), ch, offset);
|
|
||||||
}
|
|
||||||
|
|
||||||
public boolean setMark(@NotNull VimEditor editor, char ch) {
|
|
||||||
return VimInjectorKt.injector.getMarkService().setMark(editor, ch);
|
|
||||||
}
|
|
||||||
|
|
||||||
public void includeCurrentCommandAsNavigation(@NotNull VimEditor editor) {
|
|
||||||
VimInjectorKt.injector.getJumpService().includeCurrentCommandAsNavigation(editor);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Nullable
|
|
||||||
public Mark getFileMark(@NotNull VimEditor editor, char ch) {
|
|
||||||
return VimInjectorKt.injector.getMarkService().getMark(editor.primaryCaret(), ch);
|
|
||||||
}
|
|
||||||
|
|
||||||
public void setVisualSelectionMarks(@NotNull VimEditor editor, @NotNull TextRange range) {
|
|
||||||
VimMarkService markService = VimInjectorKt.injector.getMarkService();
|
|
||||||
VimMarkServiceKt.setVisualSelectionMarks(markService, editor.primaryCaret(), range);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Nullable
|
|
||||||
public TextRange getChangeMarks(@NotNull VimEditor editor) {
|
|
||||||
return VimInjectorKt.injector.getMarkService().getChangeMarks(editor.primaryCaret());
|
|
||||||
}
|
|
||||||
|
|
||||||
@Nullable
|
|
||||||
public TextRange getVisualSelectionMarks(@NotNull VimEditor editor) {
|
|
||||||
return VimInjectorKt.injector.getMarkService().getVisualSelectionMarks(editor.primaryCaret());
|
|
||||||
}
|
|
||||||
|
|
||||||
public void resetAllMarks() {
|
|
||||||
VimInjectorKt.injector.getMarkService().resetAllMarks();
|
|
||||||
}
|
|
||||||
|
|
||||||
public void removeMark(char ch, @NotNull Mark mark) {
|
|
||||||
VimInjectorKt.injector.getMarkService().removeMark(ch, mark);
|
|
||||||
}
|
|
||||||
|
|
||||||
@NotNull
|
|
||||||
public List<Mark> getMarks(@NotNull VimEditor editor) {
|
|
||||||
Set<Mark> marks = VimInjectorKt.injector.getMarkService().getAllLocalMarks(editor.primaryCaret());
|
|
||||||
marks.addAll(VimInjectorKt.injector.getMarkService().getGlobalMarks(editor));
|
|
||||||
return new ArrayList<>(marks);
|
|
||||||
}
|
|
||||||
|
|
||||||
public int getJumpSpot() {
|
|
||||||
return VimInjectorKt.injector.getJumpService().getJumpSpot("");
|
|
||||||
}
|
|
||||||
|
|
||||||
public void updateMarkFromDelete(@Nullable VimEditor editor,
|
|
||||||
@Nullable HashMap<Character, Mark> marks,
|
|
||||||
int delStartOff,
|
|
||||||
int delLength) {
|
|
||||||
VimInjectorKt.injector.getMarkService().updateMarksFromDelete(editor, delStartOff, delLength);
|
|
||||||
}
|
|
||||||
|
|
||||||
public void updateMarkFromInsert(@Nullable VimEditor editor,
|
|
||||||
@Nullable HashMap<Character, Mark> marks,
|
|
||||||
int insStartOff,
|
|
||||||
int insLength) {
|
|
||||||
VimInjectorKt.injector.getMarkService().updateMarksFromInsert(editor, insStartOff, insLength);
|
|
||||||
}
|
|
||||||
|
|
||||||
public void dropLastJump() {
|
|
||||||
VimInjectorKt.injector.getJumpService().dropLastJump("");
|
|
||||||
}
|
|
||||||
}
|
|
@ -65,28 +65,28 @@ internal class OptionGroup : VimOptionGroupBase(), IjVimOptionGroup {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
internal class IjOptionConstants {
|
public class IjOptionConstants {
|
||||||
@Suppress("SpellCheckingInspection", "MemberVisibilityCanBePrivate", "ConstPropertyName")
|
@Suppress("SpellCheckingInspection", "MemberVisibilityCanBePrivate", "ConstPropertyName")
|
||||||
companion object {
|
public companion object {
|
||||||
|
|
||||||
const val idearefactormode_keep = "keep"
|
public const val idearefactormode_keep: String = "keep"
|
||||||
const val idearefactormode_select = "select"
|
public const val idearefactormode_select: String = "select"
|
||||||
const val idearefactormode_visual = "visual"
|
public const val idearefactormode_visual: String = "visual"
|
||||||
|
|
||||||
const val ideastatusicon_enabled = "enabled"
|
public const val ideastatusicon_enabled: String = "enabled"
|
||||||
const val ideastatusicon_gray = "gray"
|
public const val ideastatusicon_gray: String = "gray"
|
||||||
const val ideastatusicon_disabled = "disabled"
|
public const val ideastatusicon_disabled: String = "disabled"
|
||||||
|
|
||||||
const val ideavimsupport_dialog = "dialog"
|
public const val ideavimsupport_dialog: String = "dialog"
|
||||||
const val ideavimsupport_singleline = "singleline"
|
public const val ideavimsupport_singleline: String = "singleline"
|
||||||
const val ideavimsupport_dialoglegacy = "dialoglegacy"
|
public const val ideavimsupport_dialoglegacy: String = "dialoglegacy"
|
||||||
|
|
||||||
const val ideawrite_all = "all"
|
public const val ideawrite_all: String = "all"
|
||||||
const val ideawrite_file = "file"
|
public const val ideawrite_file: String = "file"
|
||||||
|
|
||||||
val ideaStatusIconValues = setOf(ideastatusicon_enabled, ideastatusicon_gray, ideastatusicon_disabled)
|
public val ideaStatusIconValues: Set<String> = setOf(ideastatusicon_enabled, ideastatusicon_gray, ideastatusicon_disabled)
|
||||||
val ideaRefactorModeValues = setOf(idearefactormode_keep, idearefactormode_select, idearefactormode_visual)
|
public val ideaRefactorModeValues: Set<String> = setOf(idearefactormode_keep, idearefactormode_select, idearefactormode_visual)
|
||||||
val ideaWriteValues = setOf(ideawrite_all, ideawrite_file)
|
public val ideaWriteValues: Set<String> = setOf(ideawrite_all, ideawrite_file)
|
||||||
val ideavimsupportValues = setOf(ideavimsupport_dialog, ideavimsupport_singleline, ideavimsupport_dialoglegacy)
|
public val ideavimsupportValues: Set<String> = setOf(ideavimsupport_dialog, ideavimsupport_singleline, ideavimsupport_dialoglegacy)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -51,6 +51,8 @@ import javax.swing.SwingUtilities
|
|||||||
public class ProcessGroup : VimProcessGroupBase() {
|
public class ProcessGroup : VimProcessGroupBase() {
|
||||||
override var lastCommand: String? = null
|
override var lastCommand: String? = null
|
||||||
private set
|
private set
|
||||||
|
override var isCommandProcessing: Boolean = false
|
||||||
|
override var modeBeforeCommandProcessing: Mode? = null
|
||||||
|
|
||||||
public override fun startSearchCommand(editor: VimEditor, context: ExecutionContext, count: Int, leader: Char) {
|
public override fun startSearchCommand(editor: VimEditor, context: ExecutionContext, count: Int, leader: Char) {
|
||||||
// Don't allow searching in one line editors
|
// Don't allow searching in one line editors
|
||||||
@ -79,6 +81,8 @@ public class ProcessGroup : VimProcessGroupBase() {
|
|||||||
"Cannot enable cmd mode from current mode $currentMode"
|
"Cannot enable cmd mode from current mode $currentMode"
|
||||||
}
|
}
|
||||||
|
|
||||||
|
isCommandProcessing = true
|
||||||
|
modeBeforeCommandProcessing = currentMode
|
||||||
val initText = getRange(editor, cmd)
|
val initText = getRange(editor, cmd)
|
||||||
injector.markService.setVisualSelectionMarks(editor)
|
injector.markService.setVisualSelectionMarks(editor)
|
||||||
editor.vimStateMachine.mode = Mode.CMD_LINE(currentMode)
|
editor.vimStateMachine.mode = Mode.CMD_LINE(currentMode)
|
||||||
@ -134,6 +138,9 @@ public class ProcessGroup : VimProcessGroupBase() {
|
|||||||
logger.error(bad)
|
logger.error(bad)
|
||||||
VimPlugin.indicateError()
|
VimPlugin.indicateError()
|
||||||
res = false
|
res = false
|
||||||
|
} finally {
|
||||||
|
isCommandProcessing = false
|
||||||
|
modeBeforeCommandProcessing = null
|
||||||
}
|
}
|
||||||
|
|
||||||
return res
|
return res
|
||||||
|
@ -36,10 +36,9 @@ import com.maddyhome.idea.vim.history.HistoryConstants;
|
|||||||
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;
|
||||||
import com.maddyhome.idea.vim.newapi.IjVimEditor;
|
import com.maddyhome.idea.vim.newapi.IjVimEditor;
|
||||||
|
import com.maddyhome.idea.vim.newapi.IjVimSearchGroup;
|
||||||
import com.maddyhome.idea.vim.options.GlobalOptionChangeListener;
|
import com.maddyhome.idea.vim.options.GlobalOptionChangeListener;
|
||||||
import com.maddyhome.idea.vim.regexp.CharPointer;
|
import com.maddyhome.idea.vim.regexp.*;
|
||||||
import com.maddyhome.idea.vim.regexp.CharacterClasses;
|
|
||||||
import com.maddyhome.idea.vim.regexp.RegExp;
|
|
||||||
import com.maddyhome.idea.vim.ui.ModalEntry;
|
import com.maddyhome.idea.vim.ui.ModalEntry;
|
||||||
import com.maddyhome.idea.vim.ui.ex.ExEntryPanel;
|
import com.maddyhome.idea.vim.ui.ex.ExEntryPanel;
|
||||||
import com.maddyhome.idea.vim.vimscript.model.VimLContext;
|
import com.maddyhome.idea.vim.vimscript.model.VimLContext;
|
||||||
@ -62,34 +61,46 @@ import java.util.*;
|
|||||||
import static com.maddyhome.idea.vim.api.VimInjectorKt.*;
|
import static com.maddyhome.idea.vim.api.VimInjectorKt.*;
|
||||||
import static com.maddyhome.idea.vim.helper.HelperKt.localEditors;
|
import static com.maddyhome.idea.vim.helper.HelperKt.localEditors;
|
||||||
import static com.maddyhome.idea.vim.helper.SearchHelperKtKt.shouldIgnoreCase;
|
import static com.maddyhome.idea.vim.helper.SearchHelperKtKt.shouldIgnoreCase;
|
||||||
|
import static com.maddyhome.idea.vim.newapi.IjVimInjectorKt.globalIjOptions;
|
||||||
import static com.maddyhome.idea.vim.register.RegisterConstants.LAST_SEARCH_REGISTER;
|
import static com.maddyhome.idea.vim.register.RegisterConstants.LAST_SEARCH_REGISTER;
|
||||||
|
|
||||||
@State(name = "VimSearchSettings", storages = {
|
@State(name = "VimSearchSettings", storages = {
|
||||||
@Storage(value = "$APP_CONFIG$/vim_settings_local.xml", roamingType = RoamingType.DISABLED)
|
@Storage(value = "$APP_CONFIG$/vim_settings_local.xml", roamingType = RoamingType.DISABLED)
|
||||||
})
|
})
|
||||||
public class SearchGroup extends VimSearchGroupBase implements PersistentStateComponent<Element> {
|
@Deprecated
|
||||||
|
/**
|
||||||
|
* @deprecated Replace with IjVimSearchGroup
|
||||||
|
*/
|
||||||
|
public class SearchGroup extends IjVimSearchGroup implements PersistentStateComponent<Element> {
|
||||||
public SearchGroup() {
|
public SearchGroup() {
|
||||||
// TODO: Investigate migrating these listeners to use the effective value change listener
|
super();
|
||||||
// This would allow us to update the editor we're told to update, rather than looping over all projects and updating
|
if (!globalIjOptions(injector).getUseNewRegex()) {
|
||||||
// the highlights in that project's current document's open editors (see VIM-2779).
|
// TODO: Investigate migrating these listeners to use the effective value change listener
|
||||||
// However, we probably only want to update the editors associated with the current document, so maybe the whole
|
// This would allow us to update the editor we're told to update, rather than looping over all projects and updating
|
||||||
// code needs to be reworked. We're currently using the same update code for changes in the search term as well as
|
// the highlights in that project's current document's open editors (see VIM-2779).
|
||||||
// changes in the search options.
|
// However, we probably only want to update the editors associated with the current document, so maybe the whole
|
||||||
VimPlugin.getOptionGroup().addGlobalOptionChangeListener(Options.hlsearch, () -> {
|
// code needs to be reworked. We're currently using the same update code for changes in the search term as well as
|
||||||
resetShowSearchHighlight();
|
// changes in the search options.
|
||||||
forceUpdateSearchHighlights();
|
VimPlugin.getOptionGroup().addGlobalOptionChangeListener(Options.hlsearch, () -> {
|
||||||
});
|
resetShowSearchHighlight();
|
||||||
|
|
||||||
final GlobalOptionChangeListener updateHighlightsIfVisible = () -> {
|
|
||||||
if (showSearchHighlight) {
|
|
||||||
forceUpdateSearchHighlights();
|
forceUpdateSearchHighlights();
|
||||||
}
|
});
|
||||||
};
|
|
||||||
VimPlugin.getOptionGroup().addGlobalOptionChangeListener(Options.ignorecase, updateHighlightsIfVisible);
|
final GlobalOptionChangeListener updateHighlightsIfVisible = () -> {
|
||||||
VimPlugin.getOptionGroup().addGlobalOptionChangeListener(Options.smartcase, updateHighlightsIfVisible);
|
if (showSearchHighlight) {
|
||||||
|
forceUpdateSearchHighlights();
|
||||||
|
}
|
||||||
|
};
|
||||||
|
VimPlugin.getOptionGroup().addGlobalOptionChangeListener(Options.ignorecase, updateHighlightsIfVisible);
|
||||||
|
VimPlugin.getOptionGroup().addGlobalOptionChangeListener(Options.smartcase, updateHighlightsIfVisible);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public void turnOn() {
|
public void turnOn() {
|
||||||
|
if (globalIjOptions(injector).getUseNewRegex()) {
|
||||||
|
super.updateSearchHighlights(false);
|
||||||
|
return;
|
||||||
|
}
|
||||||
updateSearchHighlights();
|
updateSearchHighlights();
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -100,7 +111,12 @@ public class SearchGroup extends VimSearchGroupBase implements PersistentStateCo
|
|||||||
}
|
}
|
||||||
|
|
||||||
@TestOnly
|
@TestOnly
|
||||||
|
@Override
|
||||||
public void resetState() {
|
public void resetState() {
|
||||||
|
if (globalIjOptions(injector).getUseNewRegex()) {
|
||||||
|
super.resetState();
|
||||||
|
return;
|
||||||
|
}
|
||||||
lastPatternIdx = RE_SEARCH;
|
lastPatternIdx = RE_SEARCH;
|
||||||
lastSearch = lastSubstitute = lastReplace = null;
|
lastSearch = lastSubstitute = lastReplace = null;
|
||||||
lastPatternOffset = "";
|
lastPatternOffset = "";
|
||||||
@ -114,7 +130,9 @@ public class SearchGroup extends VimSearchGroupBase implements PersistentStateCo
|
|||||||
*
|
*
|
||||||
* @return The pattern used for last search. Can be null
|
* @return The pattern used for last search. Can be null
|
||||||
*/
|
*/
|
||||||
|
@Override
|
||||||
public @Nullable String getLastSearchPattern() {
|
public @Nullable String getLastSearchPattern() {
|
||||||
|
if (globalIjOptions(injector).getUseNewRegex()) return super.getLastSearchPattern();
|
||||||
return lastSearch;
|
return lastSearch;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -122,7 +140,9 @@ public class SearchGroup extends VimSearchGroupBase implements PersistentStateCo
|
|||||||
* Get the last pattern used in substitution.
|
* Get the last pattern used in substitution.
|
||||||
* @return The pattern used for the last substitute command. Can be null
|
* @return The pattern used for the last substitute command. Can be null
|
||||||
*/
|
*/
|
||||||
|
@Override
|
||||||
public @Nullable String getLastSubstitutePattern() {
|
public @Nullable String getLastSubstitutePattern() {
|
||||||
|
if (globalIjOptions(injector).getUseNewRegex()) return super.getLastSubstitutePattern();
|
||||||
return lastSubstitute;
|
return lastSubstitute;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -131,7 +151,9 @@ public class SearchGroup extends VimSearchGroupBase implements PersistentStateCo
|
|||||||
*
|
*
|
||||||
* @return The pattern last used for either searching or substitution. Can be null
|
* @return The pattern last used for either searching or substitution. Can be null
|
||||||
*/
|
*/
|
||||||
public @Nullable String getLastUsedPattern() {
|
@Override
|
||||||
|
protected @Nullable String getLastUsedPattern() {
|
||||||
|
if (globalIjOptions(injector).getUseNewRegex()) return super.getLastUsedPattern();
|
||||||
switch (lastPatternIdx) {
|
switch (lastPatternIdx) {
|
||||||
case RE_SEARCH: return lastSearch;
|
case RE_SEARCH: return lastSearch;
|
||||||
case RE_SUBST: return lastSubstitute;
|
case RE_SUBST: return lastSubstitute;
|
||||||
@ -192,9 +214,13 @@ public class SearchGroup extends VimSearchGroupBase implements PersistentStateCo
|
|||||||
* @param patternOffset The pattern offset, e.g. `/{pattern}/{offset}`
|
* @param patternOffset The pattern offset, e.g. `/{pattern}/{offset}`
|
||||||
* @param direction The direction to search
|
* @param direction The direction to search
|
||||||
*/
|
*/
|
||||||
@TestOnly
|
@Override
|
||||||
public void setLastSearchState(@SuppressWarnings("unused") @NotNull Editor editor, @NotNull String pattern,
|
public void setLastSearchState(@SuppressWarnings("unused") @NotNull VimEditor editor, @NotNull String pattern,
|
||||||
@NotNull String patternOffset, Direction direction) {
|
@NotNull String patternOffset, Direction direction) {
|
||||||
|
if (globalIjOptions(injector).getUseNewRegex()) {
|
||||||
|
super.setLastSearchState(pattern, patternOffset, direction);
|
||||||
|
return;
|
||||||
|
}
|
||||||
setLastUsedPattern(pattern, RE_SEARCH, true);
|
setLastUsedPattern(pattern, RE_SEARCH, true);
|
||||||
lastIgnoreSmartCase = false;
|
lastIgnoreSmartCase = false;
|
||||||
lastPatternOffset = patternOffset;
|
lastPatternOffset = patternOffset;
|
||||||
@ -226,7 +252,7 @@ public class SearchGroup extends VimSearchGroupBase implements PersistentStateCo
|
|||||||
int startLine,
|
int startLine,
|
||||||
int endLine,
|
int endLine,
|
||||||
boolean ignoreCase) {
|
boolean ignoreCase) {
|
||||||
return SearchHelper.findAll(editor, pattern, startLine, endLine, ignoreCase);
|
return injector.getSearchHelper().findAll(new IjVimEditor(editor), pattern, startLine, endLine, ignoreCase);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -254,6 +280,8 @@ public class SearchGroup extends VimSearchGroupBase implements PersistentStateCo
|
|||||||
*/
|
*/
|
||||||
@Override
|
@Override
|
||||||
public int processSearchCommand(@NotNull VimEditor editor, @NotNull String command, int startOffset, @NotNull Direction dir) {
|
public int processSearchCommand(@NotNull VimEditor editor, @NotNull String command, int startOffset, @NotNull Direction dir) {
|
||||||
|
if (globalIjOptions(injector).getUseNewRegex()) return super.processSearchCommand(editor, command, startOffset, dir);
|
||||||
|
|
||||||
boolean isNewPattern = false;
|
boolean isNewPattern = false;
|
||||||
String pattern = null;
|
String pattern = null;
|
||||||
String patternOffset = null;
|
String patternOffset = null;
|
||||||
@ -414,6 +442,7 @@ public class SearchGroup extends VimSearchGroupBase implements PersistentStateCo
|
|||||||
*/
|
*/
|
||||||
@Override
|
@Override
|
||||||
public int searchWord(@NotNull VimEditor editor, @NotNull ImmutableVimCaret caret, int count, boolean whole, @NotNull Direction dir) {
|
public int searchWord(@NotNull VimEditor editor, @NotNull ImmutableVimCaret caret, int count, boolean whole, @NotNull Direction dir) {
|
||||||
|
if (globalIjOptions(injector).getUseNewRegex()) return super.searchWord(editor, caret, count, whole, dir);
|
||||||
TextRange range = SearchHelper.findWordUnderCursor(((IjVimEditor)editor).getEditor(), ((IjVimCaret)caret).getCaret());
|
TextRange range = SearchHelper.findWordUnderCursor(((IjVimEditor)editor).getEditor(), ((IjVimCaret)caret).getCaret());
|
||||||
if (range == null) {
|
if (range == null) {
|
||||||
logger.warn("No range was found");
|
logger.warn("No range was found");
|
||||||
@ -455,6 +484,7 @@ public class SearchGroup extends VimSearchGroupBase implements PersistentStateCo
|
|||||||
*/
|
*/
|
||||||
@Override
|
@Override
|
||||||
public int searchNext(@NotNull VimEditor editor, @NotNull ImmutableVimCaret caret, int count) {
|
public int searchNext(@NotNull VimEditor editor, @NotNull ImmutableVimCaret caret, int count) {
|
||||||
|
if (globalIjOptions(injector).getUseNewRegex()) return super.searchNext(editor, caret, count);
|
||||||
return searchNextWithDirection(((IjVimEditor)editor).getEditor(), ((IjVimCaret)caret).getCaret(), count, lastDir);
|
return searchNextWithDirection(((IjVimEditor)editor).getEditor(), ((IjVimCaret)caret).getCaret(), count, lastDir);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -471,6 +501,7 @@ public class SearchGroup extends VimSearchGroupBase implements PersistentStateCo
|
|||||||
*/
|
*/
|
||||||
@Override
|
@Override
|
||||||
public int searchPrevious(@NotNull VimEditor editor, @NotNull ImmutableVimCaret caret, int count) {
|
public int searchPrevious(@NotNull VimEditor editor, @NotNull ImmutableVimCaret caret, int count) {
|
||||||
|
if (globalIjOptions(injector).getUseNewRegex()) return super.searchPrevious(editor, caret, count);
|
||||||
return searchNextWithDirection(((IjVimEditor)editor).getEditor(), ((IjVimCaret)caret).getCaret(), count,
|
return searchNextWithDirection(((IjVimEditor)editor).getEditor(), ((IjVimCaret)caret).getCaret(), count,
|
||||||
lastDir.reverse());
|
lastDir.reverse());
|
||||||
}
|
}
|
||||||
@ -524,6 +555,8 @@ public class SearchGroup extends VimSearchGroupBase implements PersistentStateCo
|
|||||||
@NotNull @NonNls String excmd,
|
@NotNull @NonNls String excmd,
|
||||||
@NotNull @NonNls String exarg,
|
@NotNull @NonNls String exarg,
|
||||||
@NotNull VimLContext parent) {
|
@NotNull VimLContext parent) {
|
||||||
|
if (globalIjOptions(injector).getUseNewRegex()) return super.processSubstituteCommand(editor, caret, range, excmd, exarg, parent);
|
||||||
|
|
||||||
// Explicitly exit visual mode here, so that visual mode marks don't change when we move the cursor to a match.
|
// Explicitly exit visual mode here, so that visual mode marks don't change when we move the cursor to a match.
|
||||||
List<ExException> exceptions = new ArrayList<>();
|
List<ExException> exceptions = new ArrayList<>();
|
||||||
if (CommandStateHelper.inVisualMode(((IjVimEditor) editor).getEditor())) {
|
if (CommandStateHelper.inVisualMode(((IjVimEditor) editor).getEditor())) {
|
||||||
@ -687,8 +720,7 @@ public class SearchGroup extends VimSearchGroupBase implements PersistentStateCo
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
Pair<Boolean, Triple<Object, String, Object>> booleanregmmatch_tPair = search_regcomp(pat, which_pat,
|
Pair<Boolean, Triple<Object, String, Object>> booleanregmmatch_tPair = search_regcomp(pat, which_pat, RE_SUBST);
|
||||||
RE_SUBST);
|
|
||||||
if (!booleanregmmatch_tPair.getFirst()) {
|
if (!booleanregmmatch_tPair.getFirst()) {
|
||||||
if (do_error) {
|
if (do_error) {
|
||||||
VimPlugin.showMessage(MessageHelper.message(Msg.e_invcmd));
|
VimPlugin.showMessage(MessageHelper.message(Msg.e_invcmd));
|
||||||
@ -696,9 +728,9 @@ public class SearchGroup extends VimSearchGroupBase implements PersistentStateCo
|
|||||||
}
|
}
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
RegExp.regmmatch_T regmatch = (RegExp.regmmatch_T) booleanregmmatch_tPair.getSecond().getFirst();
|
RegExp.regmmatch_T regmatch = (RegExp.regmmatch_T)booleanregmmatch_tPair.getSecond().getFirst();
|
||||||
String pattern = booleanregmmatch_tPair.getSecond().getSecond();
|
String pattern = booleanregmmatch_tPair.getSecond().getSecond();
|
||||||
RegExp sp = (RegExp) booleanregmmatch_tPair.getSecond().getThird();
|
RegExp sp = (RegExp)booleanregmmatch_tPair.getSecond().getThird();
|
||||||
|
|
||||||
/* the 'i' or 'I' flag overrules 'ignorecase' and 'smartcase' */
|
/* the 'i' or 'I' flag overrules 'ignorecase' and 'smartcase' */
|
||||||
if (do_ic == 'i') {
|
if (do_ic == 'i') {
|
||||||
@ -731,8 +763,8 @@ public class SearchGroup extends VimSearchGroupBase implements PersistentStateCo
|
|||||||
resetShowSearchHighlight();
|
resetShowSearchHighlight();
|
||||||
forceUpdateSearchHighlights();
|
forceUpdateSearchHighlights();
|
||||||
|
|
||||||
int start = ((IjVimEditor) editor).getEditor().getDocument().getLineStartOffset(line1);
|
int start = ((IjVimEditor)editor).getEditor().getDocument().getLineStartOffset(line1);
|
||||||
int end = ((IjVimEditor) editor).getEditor().getDocument().getLineEndOffset(line2);
|
int end = ((IjVimEditor)editor).getEditor().getDocument().getLineEndOffset(line2);
|
||||||
|
|
||||||
if (logger.isDebugEnabled()) {
|
if (logger.isDebugEnabled()) {
|
||||||
logger.debug("search range=[" + start + "," + end + "]");
|
logger.debug("search range=[" + start + "," + end + "]");
|
||||||
@ -755,7 +787,6 @@ public class SearchGroup extends VimSearchGroupBase implements PersistentStateCo
|
|||||||
firstMatch = false;
|
firstMatch = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
String match = sp.vim_regsub_multi(regmatch, lnum, sub, 1, false);
|
String match = sp.vim_regsub_multi(regmatch, lnum, sub, 1, false);
|
||||||
if (sub.charAt(0) == '\\' && sub.charAt(1) == '=') {
|
if (sub.charAt(0) == '\\' && sub.charAt(1) == '=') {
|
||||||
String exprString = sub.toString().substring(2);
|
String exprString = sub.toString().substring(2);
|
||||||
@ -764,22 +795,25 @@ public class SearchGroup extends VimSearchGroupBase implements PersistentStateCo
|
|||||||
exceptions.add(new ExException("E15: Invalid expression: " + exprString));
|
exceptions.add(new ExException("E15: Invalid expression: " + exprString));
|
||||||
expression = new SimpleExpression(new VimString(""));
|
expression = new SimpleExpression(new VimString(""));
|
||||||
}
|
}
|
||||||
} else if (match == null) {
|
}
|
||||||
|
else if (match == null) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
int line = lnum + regmatch.startpos[0].lnum;
|
int line = lnum + regmatch.startpos[0].lnum;
|
||||||
CharacterPosition startpos = new CharacterPosition(lnum + regmatch.startpos[0].lnum, regmatch.startpos[0].col);
|
CharacterPosition startpos = new CharacterPosition(lnum + regmatch.startpos[0].lnum, regmatch.startpos[0].col);
|
||||||
CharacterPosition endpos = new CharacterPosition(lnum + regmatch.endpos[0].lnum, regmatch.endpos[0].col);
|
CharacterPosition endpos = new CharacterPosition(lnum + regmatch.endpos[0].lnum, regmatch.endpos[0].col);
|
||||||
int startoff = startpos.toOffset(((IjVimEditor) editor).getEditor());
|
int startoff = startpos.toOffset(((IjVimEditor)editor).getEditor());
|
||||||
int endoff = endpos.toOffset(((IjVimEditor) editor).getEditor());
|
int endoff = endpos.toOffset(((IjVimEditor)editor).getEditor());
|
||||||
|
|
||||||
if (do_all || line != lastLine) {
|
if (do_all || line != lastLine) {
|
||||||
boolean doReplace = true;
|
boolean doReplace = true;
|
||||||
if (do_ask) {
|
if (do_ask) {
|
||||||
RangeHighlighter hl = SearchHighlightsHelper.addSubstitutionConfirmationHighlight(((IjVimEditor) editor).getEditor(), startoff, endoff);
|
RangeHighlighter hl =
|
||||||
final ReplaceConfirmationChoice choice = confirmChoice(((IjVimEditor) editor).getEditor(), match, ((IjVimCaret) caret).getCaret(), startoff);
|
SearchHighlightsHelper.addSubstitutionConfirmationHighlight(((IjVimEditor)editor).getEditor(), startoff,
|
||||||
((IjVimEditor) editor).getEditor().getMarkupModel().removeHighlighter(hl);
|
endoff);
|
||||||
|
final ReplaceConfirmationChoice choice = confirmChoice(((IjVimEditor)editor).getEditor(), match, ((IjVimCaret)caret).getCaret(), startoff);
|
||||||
|
((IjVimEditor)editor).getEditor().getMarkupModel().removeHighlighter(hl);
|
||||||
switch (choice) {
|
switch (choice) {
|
||||||
case SUBSTITUTE_THIS:
|
case SUBSTITUTE_THIS:
|
||||||
doReplace = true;
|
doReplace = true;
|
||||||
@ -802,25 +836,26 @@ public class SearchGroup extends VimSearchGroupBase implements PersistentStateCo
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (doReplace) {
|
if (doReplace) {
|
||||||
SubmatchFunctionHandler.Companion.getInstance().setLatestMatch(((IjVimEditor) editor).getEditor().getDocument().getText(new com.intellij.openapi.util.TextRange(startoff, endoff)));
|
SubmatchFunctionHandler.Companion.getInstance().setLatestMatch(
|
||||||
|
((IjVimEditor)editor).getEditor().getDocument().getText(new com.intellij.openapi.util.TextRange(startoff, endoff)));
|
||||||
caret.moveToOffset(startoff);
|
caret.moveToOffset(startoff);
|
||||||
if (expression != null) {
|
if (expression != null) {
|
||||||
try {
|
try {
|
||||||
match = expression
|
match =
|
||||||
.evaluate(editor, injector.getExecutionContextManager().onEditor(editor, null), parent)
|
expression.evaluate(editor, injector.getExecutionContextManager().onEditor(editor, null), parent).toInsertableString();
|
||||||
.toInsertableString();
|
}
|
||||||
} catch (Exception e) {
|
catch (Exception e) {
|
||||||
exceptions.add((ExException) e);
|
exceptions.add((ExException)e);
|
||||||
match = "";
|
match = "";
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
String finalMatch = match;
|
String finalMatch = match;
|
||||||
ApplicationManager.getApplication().runWriteAction(() -> ((IjVimEditor) editor).getEditor().getDocument().replaceString(startoff, endoff,
|
ApplicationManager.getApplication().runWriteAction(
|
||||||
finalMatch));
|
() -> ((IjVimEditor)editor).getEditor().getDocument().replaceString(startoff, endoff, finalMatch));
|
||||||
lastMatch = startoff;
|
lastMatch = startoff;
|
||||||
int newend = startoff + match.length();
|
int newend = startoff + match.length();
|
||||||
newpos = CharacterPosition.Companion.fromOffset(((IjVimEditor) editor).getEditor(), newend);
|
newpos = CharacterPosition.Companion.fromOffset(((IjVimEditor)editor).getEditor(), newend);
|
||||||
|
|
||||||
lnum += newpos.line - endpos.line;
|
lnum += newpos.line - endpos.line;
|
||||||
line2 += newpos.line - endpos.line;
|
line2 += newpos.line - endpos.line;
|
||||||
@ -875,6 +910,10 @@ public class SearchGroup extends VimSearchGroupBase implements PersistentStateCo
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void setLastSearchPattern(@Nullable String lastSearchPattern) {
|
public void setLastSearchPattern(@Nullable String lastSearchPattern) {
|
||||||
|
if (globalIjOptions(injector).getUseNewRegex()) {
|
||||||
|
super.setLastSearchPattern(lastSearchPattern);
|
||||||
|
return;
|
||||||
|
}
|
||||||
this.lastSearch = lastSearchPattern;
|
this.lastSearch = lastSearchPattern;
|
||||||
if (showSearchHighlight) {
|
if (showSearchHighlight) {
|
||||||
resetIncsearchHighlights();
|
resetIncsearchHighlights();
|
||||||
@ -884,6 +923,10 @@ public class SearchGroup extends VimSearchGroupBase implements PersistentStateCo
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void setLastSubstitutePattern(@Nullable String lastSubstitutePattern) {
|
public void setLastSubstitutePattern(@Nullable String lastSubstitutePattern) {
|
||||||
|
if (globalIjOptions(injector).getUseNewRegex()) {
|
||||||
|
super.setLastSubstitutePattern(lastSubstitutePattern);
|
||||||
|
return;
|
||||||
|
}
|
||||||
this.lastSubstitute = lastSubstitutePattern;
|
this.lastSubstitute = lastSubstitutePattern;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -893,6 +936,7 @@ public class SearchGroup extends VimSearchGroupBase implements PersistentStateCo
|
|||||||
int patternOffset,
|
int patternOffset,
|
||||||
int startOffset,
|
int startOffset,
|
||||||
@NotNull Direction direction) {
|
@NotNull Direction direction) {
|
||||||
|
if (globalIjOptions(injector).getUseNewRegex()) return super.processSearchRange(editor, pattern, patternOffset, startOffset, direction);
|
||||||
return processSearchRange(((IjVimEditor) editor).getEditor(), pattern, patternOffset, startOffset, direction);
|
return processSearchRange(((IjVimEditor) editor).getEditor(), pattern, patternOffset, startOffset, direction);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -1015,6 +1059,7 @@ public class SearchGroup extends VimSearchGroupBase implements PersistentStateCo
|
|||||||
*/
|
*/
|
||||||
@Override
|
@Override
|
||||||
public @Nullable TextRange getNextSearchRange(@NotNull VimEditor editor, int count, boolean forwards) {
|
public @Nullable TextRange getNextSearchRange(@NotNull VimEditor editor, int count, boolean forwards) {
|
||||||
|
if (globalIjOptions(injector).getUseNewRegex()) return super.getNextSearchRange(editor, count, forwards);
|
||||||
editor.removeSecondaryCarets();
|
editor.removeSecondaryCarets();
|
||||||
TextRange current = findUnderCaret(editor);
|
TextRange current = findUnderCaret(editor);
|
||||||
|
|
||||||
@ -1046,17 +1091,10 @@ public class SearchGroup extends VimSearchGroupBase implements PersistentStateCo
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
|
||||||
@Nullable
|
|
||||||
public TextRange findUnderCaret(@NotNull VimEditor editor) {
|
|
||||||
final TextRange backSearch = searchBackward(editor, editor.primaryCaret().getOffset().getPoint() + 1, 1);
|
|
||||||
if (backSearch == null) return null;
|
|
||||||
return backSearch.contains(editor.primaryCaret().getOffset().getPoint()) ? backSearch : null;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@Nullable
|
@Nullable
|
||||||
public TextRange searchBackward(@NotNull VimEditor editor, int offset, int count) {
|
public TextRange searchBackward(@NotNull VimEditor editor, int offset, int count) {
|
||||||
|
if (globalIjOptions(injector).getUseNewRegex()) return super.searchBackward(editor, offset, count);
|
||||||
// Backward search returns wrongs end offset for some cases. That's why we should perform additional forward search
|
// Backward search returns wrongs end offset for some cases. That's why we should perform additional forward search
|
||||||
final EnumSet<SearchOptions> searchOptions = EnumSet.of(SearchOptions.WRAP, SearchOptions.WHOLE_FILE, SearchOptions.BACKWARDS);
|
final EnumSet<SearchOptions> searchOptions = EnumSet.of(SearchOptions.WRAP, SearchOptions.WHOLE_FILE, SearchOptions.BACKWARDS);
|
||||||
final TextRange foundBackward = VimInjectorKt.getInjector().getSearchHelper().findPattern(editor, getLastUsedPattern(), offset, count, searchOptions);
|
final TextRange foundBackward = VimInjectorKt.getInjector().getSearchHelper().findPattern(editor, getLastUsedPattern(), offset, count, searchOptions);
|
||||||
@ -1074,7 +1112,12 @@ public class SearchGroup extends VimSearchGroupBase implements PersistentStateCo
|
|||||||
//
|
//
|
||||||
// *******************************************************************************************************************
|
// *******************************************************************************************************************
|
||||||
//region Search highlights
|
//region Search highlights
|
||||||
|
@Override
|
||||||
public void clearSearchHighlight() {
|
public void clearSearchHighlight() {
|
||||||
|
if (globalIjOptions(injector).getUseNewRegex()) {
|
||||||
|
super.clearSearchHighlight();
|
||||||
|
return;
|
||||||
|
}
|
||||||
showSearchHighlight = false;
|
showSearchHighlight = false;
|
||||||
updateSearchHighlights();
|
updateSearchHighlights();
|
||||||
}
|
}
|
||||||
@ -1094,7 +1137,12 @@ public class SearchGroup extends VimSearchGroupBase implements PersistentStateCo
|
|||||||
/**
|
/**
|
||||||
* Reset the search highlights to the last used pattern after highlighting incsearch results.
|
* Reset the search highlights to the last used pattern after highlighting incsearch results.
|
||||||
*/
|
*/
|
||||||
|
@Override
|
||||||
public void resetIncsearchHighlights() {
|
public void resetIncsearchHighlights() {
|
||||||
|
if (globalIjOptions(injector).getUseNewRegex()) {
|
||||||
|
super.resetIncsearchHighlights();
|
||||||
|
return;
|
||||||
|
}
|
||||||
SearchHighlightsHelper.updateSearchHighlights(getLastUsedPattern(), lastIgnoreSmartCase, showSearchHighlight, true);
|
SearchHighlightsHelper.updateSearchHighlights(getLastUsedPattern(), lastIgnoreSmartCase, showSearchHighlight, true);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -1103,9 +1151,13 @@ public class SearchGroup extends VimSearchGroupBase implements PersistentStateCo
|
|||||||
}
|
}
|
||||||
|
|
||||||
private void highlightSearchLines(@NotNull Editor editor, int startLine, int endLine) {
|
private void highlightSearchLines(@NotNull Editor editor, int startLine, int endLine) {
|
||||||
|
if (globalIjOptions(injector).getUseNewRegex()) {
|
||||||
|
super.highlightSearchLines(new IjVimEditor(editor), startLine, endLine);
|
||||||
|
return;
|
||||||
|
}
|
||||||
final String pattern = getLastUsedPattern();
|
final String pattern = getLastUsedPattern();
|
||||||
if (pattern != null) {
|
if (pattern != null) {
|
||||||
final List<TextRange> results = SearchHelper.findAll(editor, pattern, startLine, endLine,
|
final List<TextRange> results = injector.getSearchHelper().findAll(new IjVimEditor(editor), pattern, startLine, endLine,
|
||||||
shouldIgnoreCase(pattern, lastIgnoreSmartCase));
|
shouldIgnoreCase(pattern, lastIgnoreSmartCase));
|
||||||
SearchHighlightsHelper.highlightSearchResults(editor, pattern, results, -1);
|
SearchHighlightsHelper.highlightSearchResults(editor, pattern, results, -1);
|
||||||
}
|
}
|
||||||
@ -1114,12 +1166,17 @@ public class SearchGroup extends VimSearchGroupBase implements PersistentStateCo
|
|||||||
/**
|
/**
|
||||||
* Updates search highlights when the selected editor changes
|
* Updates search highlights when the selected editor changes
|
||||||
*/
|
*/
|
||||||
public static void fileEditorManagerSelectionChangedCallback(@SuppressWarnings("unused") @NotNull FileEditorManagerEvent event) {
|
public void fileEditorManagerSelectionChangedCallback(@SuppressWarnings("unused") @NotNull FileEditorManagerEvent event) {
|
||||||
|
if (globalIjOptions(injector).getUseNewRegex()) {
|
||||||
|
super.updateSearchHighlights(false);
|
||||||
|
return;
|
||||||
|
}
|
||||||
VimPlugin.getSearch().updateSearchHighlights();
|
VimPlugin.getSearch().updateSearchHighlights();
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public Integer findDecimalNumber(@NotNull String line) {
|
public Integer findDecimalNumber(@NotNull String line) {
|
||||||
|
if (globalIjOptions(injector).getUseNewRegex()) return super.findDecimalNumber(line);
|
||||||
Pair<TextRange, NumberType> searchResult = SearchHelper.findNumberInText(line, 0, false, false, false);
|
Pair<TextRange, NumberType> searchResult = SearchHelper.findNumberInText(line, 0, false, false, false);
|
||||||
if (searchResult != null) {
|
if (searchResult != null) {
|
||||||
TextRange range = searchResult.component1();
|
TextRange range = searchResult.component1();
|
||||||
@ -1131,6 +1188,7 @@ public class SearchGroup extends VimSearchGroupBase implements PersistentStateCo
|
|||||||
@NotNull
|
@NotNull
|
||||||
@Override
|
@Override
|
||||||
public Direction getLastSearchDirection() {
|
public Direction getLastSearchDirection() {
|
||||||
|
if (globalIjOptions(injector).getUseNewRegex()) return super.getLastSearchDirection();
|
||||||
return lastDir;
|
return lastDir;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -1274,7 +1332,7 @@ public class SearchGroup extends VimSearchGroupBase implements PersistentStateCo
|
|||||||
if (hasEndOffset) searchOptions.add(SearchOptions.WANT_ENDPOS);
|
if (hasEndOffset) searchOptions.add(SearchOptions.WANT_ENDPOS);
|
||||||
|
|
||||||
// Uses RE_LAST. We know this is always set before being called
|
// Uses RE_LAST. We know this is always set before being called
|
||||||
TextRange range = SearchHelper.findPattern(editor, getLastUsedPattern(), startOffset, count, searchOptions);
|
TextRange range = injector.getSearchHelper().findPattern(new IjVimEditor(editor), getLastUsedPattern(), startOffset, count, searchOptions);
|
||||||
if (range == null) {
|
if (range == null) {
|
||||||
logger.warn("No range is found");
|
logger.warn("No range is found");
|
||||||
return -1;
|
return -1;
|
||||||
|
@ -177,15 +177,6 @@ internal class VimMarkServiceImpl : VimMarkServiceBase(), PersistentStateCompone
|
|||||||
return createOrGetSystemMark(char, line, col, editor)
|
return createOrGetSystemMark(char, line, col, editor)
|
||||||
}
|
}
|
||||||
|
|
||||||
@Deprecated("Please use removeMark with other signature")
|
|
||||||
override fun removeMark(ch: Char, mark: Mark) {
|
|
||||||
if (ch.isGlobalMark()) {
|
|
||||||
removeGlobalMark(ch)
|
|
||||||
} else if (ch.isLocalMark()) {
|
|
||||||
getLocalMarks(mark.filepath).remove(ch)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun removeGlobalMark(char: Char) {
|
override fun removeGlobalMark(char: Char) {
|
||||||
val mark = getGlobalMark(char)
|
val mark = getGlobalMark(char)
|
||||||
if (mark is IntellijMark) {
|
if (mark is IntellijMark) {
|
||||||
@ -279,16 +270,6 @@ internal class VimMarkServiceImpl : VimMarkServiceBase(), PersistentStateCompone
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* COMPATIBILITY-LAYER: Method added
|
|
||||||
* Please see: [doc](https://jb.gg/zo8n0r)
|
|
||||||
*
|
|
||||||
*/
|
|
||||||
@Deprecated("Please use method with VimEditor")
|
|
||||||
fun saveJumpLocation(editor: Editor?) {
|
|
||||||
injector.jumpService.saveJumpLocation(IjVimEditor(editor!!))
|
|
||||||
}
|
|
||||||
|
|
||||||
companion object {
|
companion object {
|
||||||
private const val SAVE_MARK_COUNT = 20
|
private const val SAVE_MARK_COUNT = 20
|
||||||
private val logger = Logger.getInstance(
|
private val logger = Logger.getInstance(
|
||||||
|
@ -52,12 +52,13 @@ import javax.swing.Timer
|
|||||||
* editorHasSelection is false. Insert mode ([mode]) has no selection and editor also has no selection, so
|
* editorHasSelection is false. Insert mode ([mode]) has no selection and editor also has no selection, so
|
||||||
* no adjustment gets performed and IdeaVim stays in insert mode.
|
* no adjustment gets performed and IdeaVim stays in insert mode.
|
||||||
*/
|
*/
|
||||||
internal object VimVisualTimer {
|
// Do not remove until it's used in EasyMotion plugin in tests
|
||||||
|
public object VimVisualTimer {
|
||||||
|
|
||||||
var swingTimer: Timer? = null
|
public var swingTimer: Timer? = null
|
||||||
var mode: Mode? = null
|
public var mode: Mode? = null
|
||||||
|
|
||||||
inline fun singleTask(currentMode: Mode, crossinline task: (initialMode: Mode?) -> Unit) {
|
public inline fun singleTask(currentMode: Mode, crossinline task: (initialMode: Mode?) -> Unit) {
|
||||||
swingTimer?.stop()
|
swingTimer?.stop()
|
||||||
|
|
||||||
if (mode == null) mode = currentMode
|
if (mode == null) mode = currentMode
|
||||||
@ -69,7 +70,7 @@ internal object VimVisualTimer {
|
|||||||
swingTimer = timer
|
swingTimer = timer
|
||||||
}
|
}
|
||||||
|
|
||||||
fun doNow() {
|
public fun doNow() {
|
||||||
val swingTimer1 = swingTimer
|
val swingTimer1 = swingTimer
|
||||||
if (swingTimer1 != null) {
|
if (swingTimer1 != null) {
|
||||||
swingTimer1.stop()
|
swingTimer1.stop()
|
||||||
@ -79,12 +80,12 @@ internal object VimVisualTimer {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fun drop() {
|
public fun drop() {
|
||||||
swingTimer?.stop()
|
swingTimer?.stop()
|
||||||
swingTimer = null
|
swingTimer = null
|
||||||
}
|
}
|
||||||
|
|
||||||
inline fun timerAction(task: (initialMode: Mode?) -> Unit) {
|
public inline fun timerAction(task: (initialMode: Mode?) -> Unit) {
|
||||||
task(mode)
|
task(mode)
|
||||||
swingTimer = null
|
swingTimer = null
|
||||||
mode = null
|
mode = null
|
||||||
|
@ -1,102 +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.handler
|
|
||||||
|
|
||||||
import com.intellij.serviceContainer.BaseKeyedLazyInstance
|
|
||||||
import com.intellij.util.SmartList
|
|
||||||
import com.intellij.util.xmlb.annotations.Attribute
|
|
||||||
import com.maddyhome.idea.vim.command.MappingMode
|
|
||||||
import org.jetbrains.annotations.ApiStatus.ScheduledForRemoval
|
|
||||||
import javax.swing.KeyStroke
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Action holder for IdeaVim actions.
|
|
||||||
*
|
|
||||||
* [implementation] should be subclass of [EditorActionHandlerBase]
|
|
||||||
*
|
|
||||||
* [modes] ("mappingModes") defines the action modes. E.g. "NO" - action works in normal and op-pending modes.
|
|
||||||
* Warning: V - Visual and Select mode. X - Visual mode. (like vmap and xmap).
|
|
||||||
* Use "ALL" to enable action for all modes.
|
|
||||||
*
|
|
||||||
* [keys] comma-separated list of keys for the action. E.g. `gt,gT` - action gets executed on `gt` or `gT`
|
|
||||||
* Since xml doesn't allow using raw `<` character, use « and » symbols for mappings with modifiers.
|
|
||||||
* E.g. `«C-U»` - CTRL-U (<C-U> in vim notation)
|
|
||||||
* If you want to use exactly `<` character, replace it with `<`. E.g. `i<` - i<
|
|
||||||
* If you want to use comma in mapping, use `«COMMA»`
|
|
||||||
* Do not place a whitespace around the comma!
|
|
||||||
*
|
|
||||||
*
|
|
||||||
* !! IMPORTANT !!
|
|
||||||
* You may wonder why the extension points are used instead of any other approach to register actions.
|
|
||||||
* The reason is startup performance. Using the extension points you don't even have to load classes of actions.
|
|
||||||
* So, all actions are loaded on demand, including classes in classloader.
|
|
||||||
*/
|
|
||||||
@Deprecated(message = "Please use CommandOrMotion annotation")
|
|
||||||
@ScheduledForRemoval(inVersion = "2.9.0")
|
|
||||||
internal class ActionBeanClass : BaseKeyedLazyInstance<EditorActionHandlerBase>() {
|
|
||||||
@Attribute("implementation")
|
|
||||||
var implementation: String? = null
|
|
||||||
|
|
||||||
@Attribute("mappingModes")
|
|
||||||
var modes: String? = null
|
|
||||||
|
|
||||||
@Attribute("keys")
|
|
||||||
var keys: String? = null
|
|
||||||
|
|
||||||
val actionId: String get() = implementation?.let { EditorActionHandlerBase.getActionId(it) } ?: ""
|
|
||||||
|
|
||||||
fun getParsedKeys(): Set<List<KeyStroke>>? {
|
|
||||||
val myKeys = keys ?: return null
|
|
||||||
val escapedKeys = myKeys.splitByComma()
|
|
||||||
return EditorActionHandlerBase.parseKeysSet(escapedKeys)
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun getImplementationClassName(): String? = implementation
|
|
||||||
|
|
||||||
fun getParsedModes(): Set<MappingMode>? {
|
|
||||||
val myModes = modes ?: return null
|
|
||||||
|
|
||||||
if ("ALL" == myModes) return MappingMode.ALL
|
|
||||||
|
|
||||||
val res = mutableListOf<MappingMode>()
|
|
||||||
for (c in myModes) {
|
|
||||||
when (c) {
|
|
||||||
'N' -> res += MappingMode.NORMAL
|
|
||||||
'X' -> res += MappingMode.VISUAL
|
|
||||||
'V' -> {
|
|
||||||
res += MappingMode.VISUAL
|
|
||||||
res += MappingMode.SELECT
|
|
||||||
}
|
|
||||||
'S' -> res += MappingMode.SELECT
|
|
||||||
'O' -> res += MappingMode.OP_PENDING
|
|
||||||
'I' -> res += MappingMode.INSERT
|
|
||||||
'C' -> res += MappingMode.CMD_LINE
|
|
||||||
else -> error("Wrong mapping mode: $c")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return res.toSet()
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun String.splitByComma(): List<String> {
|
|
||||||
if (this.isEmpty()) return ArrayList()
|
|
||||||
val res = SmartList<String>()
|
|
||||||
var start = 0
|
|
||||||
var current = 0
|
|
||||||
while (current < this.length) {
|
|
||||||
if (this[current] == ',') {
|
|
||||||
res += this.substring(start, current)
|
|
||||||
current++
|
|
||||||
start = current
|
|
||||||
}
|
|
||||||
current++
|
|
||||||
}
|
|
||||||
res += this.substring(start, current)
|
|
||||||
return res
|
|
||||||
}
|
|
||||||
}
|
|
@ -9,42 +9,70 @@
|
|||||||
package com.maddyhome.idea.vim.handler
|
package com.maddyhome.idea.vim.handler
|
||||||
|
|
||||||
import com.intellij.openapi.actionSystem.KeyboardShortcut
|
import com.intellij.openapi.actionSystem.KeyboardShortcut
|
||||||
|
import com.intellij.openapi.components.Service
|
||||||
|
import com.intellij.openapi.components.service
|
||||||
import com.intellij.openapi.diagnostic.logger
|
import com.intellij.openapi.diagnostic.logger
|
||||||
import com.intellij.openapi.keymap.Keymap
|
import com.intellij.openapi.keymap.Keymap
|
||||||
import com.intellij.openapi.keymap.KeymapManagerListener
|
import com.intellij.openapi.keymap.KeymapManagerListener
|
||||||
import com.intellij.openapi.keymap.ex.KeymapManagerEx
|
import com.intellij.openapi.keymap.ex.KeymapManagerEx
|
||||||
import com.intellij.openapi.project.Project
|
import com.intellij.openapi.project.Project
|
||||||
import com.intellij.openapi.startup.StartupActivity
|
import com.intellij.openapi.startup.ProjectActivity
|
||||||
import com.intellij.util.SingleAlarm
|
|
||||||
import com.jetbrains.rd.util.ConcurrentHashMap
|
import com.jetbrains.rd.util.ConcurrentHashMap
|
||||||
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.key
|
import com.maddyhome.idea.vim.api.key
|
||||||
|
import kotlinx.coroutines.CoroutineScope
|
||||||
|
import kotlinx.coroutines.FlowPreview
|
||||||
|
import kotlinx.coroutines.channels.BufferOverflow
|
||||||
|
import kotlinx.coroutines.flow.MutableSharedFlow
|
||||||
|
import kotlinx.coroutines.flow.collectLatest
|
||||||
|
import kotlinx.coroutines.flow.debounce
|
||||||
|
import kotlinx.coroutines.launch
|
||||||
|
|
||||||
|
|
||||||
// We use alarm with delay to avoid many actions in case many events are fired at the same time
|
// We use alarm with delay to avoid many actions in case many events are fired at the same time
|
||||||
// [VERSION UPDATE] 2023.3+ Replace SingleAlarm with coroutine flows https://youtrack.jetbrains.com/articles/IJPL-A-8/Alarm-Alternative
|
internal val correctorRequester = MutableSharedFlow<Unit>(replay=1, onBufferOverflow = BufferOverflow.DROP_OLDEST)
|
||||||
internal val correctorRequester = SingleAlarm({ correctCopilotKeymap() }, 1_000)
|
|
||||||
|
|
||||||
private val LOG = logger<CopilotKeymapCorrector>()
|
private val LOG = logger<CopilotKeymapCorrector>()
|
||||||
|
|
||||||
internal class CopilotKeymapCorrector : StartupActivity {
|
internal class CopilotKeymapCorrector : ProjectActivity {
|
||||||
override fun runActivity(project: Project) {
|
override suspend fun execute(project: Project) {
|
||||||
correctorRequester.request()
|
project.service<CopilotKeymapCorrectorService>().start()
|
||||||
|
correctorRequester.emit(Unit)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* At the moment of release 2023.3 there is a problem that starting a coroutine like this
|
||||||
|
* right in the project activity will block this project activity in tests.
|
||||||
|
* To avoid that, there is an intermediate service that will allow to avoid this issue.
|
||||||
|
*
|
||||||
|
* However, in general we should start this coroutine right in the [CopilotKeymapCorrector]
|
||||||
|
*/
|
||||||
|
@OptIn(FlowPreview::class)
|
||||||
|
@Service(Service.Level.PROJECT)
|
||||||
|
internal class CopilotKeymapCorrectorService(private val cs: CoroutineScope) {
|
||||||
|
fun start() {
|
||||||
|
cs.launch {
|
||||||
|
correctorRequester
|
||||||
|
.debounce(5_000)
|
||||||
|
.collectLatest { correctCopilotKeymap() }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
internal class IdeaVimCorrectorKeymapChangedListener : KeymapManagerListener {
|
internal class IdeaVimCorrectorKeymapChangedListener : KeymapManagerListener {
|
||||||
override fun activeKeymapChanged(keymap: Keymap?) {
|
override fun activeKeymapChanged(keymap: Keymap?) {
|
||||||
correctorRequester.request()
|
check(correctorRequester.tryEmit(Unit))
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun shortcutChanged(keymap: Keymap, actionId: String) {
|
override fun shortcutChanged(keymap: Keymap, actionId: String) {
|
||||||
correctorRequester.request()
|
check(correctorRequester.tryEmit(Unit))
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun shortcutChanged(keymap: Keymap, actionId: String, fromSettings: Boolean) {
|
override fun shortcutChanged(keymap: Keymap, actionId: String, fromSettings: Boolean) {
|
||||||
correctorRequester.request()
|
check(correctorRequester.tryEmit(Unit))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -67,6 +95,7 @@ private fun correctCopilotKeymap() {
|
|||||||
// This is needed to initialize the injector in case this verification is called to fast
|
// This is needed to initialize the injector in case this verification is called to fast
|
||||||
VimPlugin.getInstance()
|
VimPlugin.getInstance()
|
||||||
|
|
||||||
|
if (!enableOctopus) return
|
||||||
if (injector.enabler.isEnabled()) {
|
if (injector.enabler.isEnabled()) {
|
||||||
val keymap = KeymapManagerEx.getInstanceEx().activeKeymap
|
val keymap = KeymapManagerEx.getInstanceEx().activeKeymap
|
||||||
val res = keymap.getShortcuts("copilot.disposeInlays")
|
val res = keymap.getShortcuts("copilot.disposeInlays")
|
||||||
|
@ -34,6 +34,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) {
|
||||||
|
if (!enableOctopus) return
|
||||||
|
|
||||||
val escHandlers = editorHandlers.extensionList
|
val escHandlers = editorHandlers.extensionList
|
||||||
.filter { it.action == "EditorEscape" }
|
.filter { it.action == "EditorEscape" }
|
||||||
.joinToString("\n") { it.implementationClass }
|
.joinToString("\n") { it.implementationClass }
|
||||||
|
@ -87,6 +87,7 @@ private fun verifyKeymap() {
|
|||||||
// This is needed to initialize the injector in case this verification is called to fast
|
// This is needed to initialize the injector in case this verification is called to fast
|
||||||
VimPlugin.getInstance()
|
VimPlugin.getInstance()
|
||||||
|
|
||||||
|
if (!enableOctopus) return
|
||||||
if (!injector.enabler.isEnabled()) return
|
if (!injector.enabler.isEnabled()) return
|
||||||
|
|
||||||
val keymap = KeymapManagerEx.getInstanceEx().activeKeymap
|
val keymap = KeymapManagerEx.getInstanceEx().activeKeymap
|
||||||
|
@ -27,6 +27,7 @@ import com.intellij.openapi.util.UserDataHolder
|
|||||||
import com.intellij.openapi.util.removeUserData
|
import com.intellij.openapi.util.removeUserData
|
||||||
import com.maddyhome.idea.vim.KeyHandler
|
import com.maddyhome.idea.vim.KeyHandler
|
||||||
import com.maddyhome.idea.vim.VimPlugin
|
import com.maddyhome.idea.vim.VimPlugin
|
||||||
|
import com.maddyhome.idea.vim.api.globalOptions
|
||||||
import com.maddyhome.idea.vim.api.injector
|
import com.maddyhome.idea.vim.api.injector
|
||||||
import com.maddyhome.idea.vim.api.key
|
import com.maddyhome.idea.vim.api.key
|
||||||
import com.maddyhome.idea.vim.group.IjOptionConstants
|
import com.maddyhome.idea.vim.group.IjOptionConstants
|
||||||
@ -52,7 +53,7 @@ internal val commandContinuation = Key.create<EditorActionHandler>("commandConti
|
|||||||
*/
|
*/
|
||||||
internal class CaretShapeEnterEditorHandler(private val nextHandler: EditorActionHandler) : EditorActionHandler() {
|
internal class CaretShapeEnterEditorHandler(private val nextHandler: EditorActionHandler) : EditorActionHandler() {
|
||||||
override fun doExecute(editor: Editor, caret: Caret?, dataContext: DataContext?) {
|
override fun doExecute(editor: Editor, caret: Caret?, dataContext: DataContext?) {
|
||||||
if (VimPlugin.isEnabled()) {
|
if (VimPlugin.isEnabled() && enableOctopus) {
|
||||||
invokeLater {
|
invokeLater {
|
||||||
editor.updateCaretsVisualAttributes()
|
editor.updateCaretsVisualAttributes()
|
||||||
}
|
}
|
||||||
@ -128,6 +129,7 @@ internal abstract class OctopusHandler(private val nextHandler: EditorActionHand
|
|||||||
if (VimPlugin.isNotEnabled()) return false
|
if (VimPlugin.isNotEnabled()) return false
|
||||||
if (!isHandlerEnabled(editor, dataContext)) return false
|
if (!isHandlerEnabled(editor, dataContext)) return false
|
||||||
if (isNotActualKeyPress(dataContext)) return false
|
if (isNotActualKeyPress(dataContext)) return false
|
||||||
|
if (!enableOctopus) return false
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -242,6 +244,7 @@ internal class VimEscForRiderHandler(nextHandler: EditorActionHandler) : VimKeyH
|
|||||||
override val key: String = "<Esc>"
|
override val key: String = "<Esc>"
|
||||||
|
|
||||||
override fun isHandlerEnabled(editor: Editor, dataContext: DataContext?): Boolean {
|
override fun isHandlerEnabled(editor: Editor, dataContext: DataContext?): Boolean {
|
||||||
|
if (!enableOctopus) return false
|
||||||
return LookupManager.getActiveLookup(editor) != null
|
return LookupManager.getActiveLookup(editor) != null
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -257,7 +260,9 @@ internal class VimEscForRiderHandler(nextHandler: EditorActionHandler) : VimKeyH
|
|||||||
*/
|
*/
|
||||||
internal class VimEscLoggerHandler(private val nextHandler: EditorActionHandler) : EditorActionHandler() {
|
internal class VimEscLoggerHandler(private val nextHandler: EditorActionHandler) : EditorActionHandler() {
|
||||||
override fun doExecute(editor: Editor, caret: Caret?, dataContext: DataContext?) {
|
override fun doExecute(editor: Editor, caret: Caret?, dataContext: DataContext?) {
|
||||||
LOG.info("Esc pressed")
|
if (enableOctopus) {
|
||||||
|
LOG.info("Esc pressed")
|
||||||
|
}
|
||||||
nextHandler.execute(editor, caret, dataContext)
|
nextHandler.execute(editor, caret, dataContext)
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -283,7 +288,9 @@ internal class StartNewLineBeforeCurrentDetector(nextHandler: EditorActionHandle
|
|||||||
|
|
||||||
internal open class StartNewLineDetectorBase(private val nextHandler: EditorActionHandler) : EditorActionHandler() {
|
internal open class StartNewLineDetectorBase(private val nextHandler: EditorActionHandler) : EditorActionHandler() {
|
||||||
override fun doExecute(editor: Editor, caret: Caret?, dataContext: DataContext?) {
|
override fun doExecute(editor: Editor, caret: Caret?, dataContext: DataContext?) {
|
||||||
DataManager.getInstance().saveInDataContext(dataContext, Util.key, true)
|
if (enableOctopus) {
|
||||||
|
DataManager.getInstance().saveInDataContext(dataContext, Util.key, true)
|
||||||
|
}
|
||||||
nextHandler.execute(editor, caret, dataContext)
|
nextHandler.execute(editor, caret, dataContext)
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -311,7 +318,9 @@ internal open class StartNewLineDetectorBase(private val nextHandler: EditorActi
|
|||||||
*/
|
*/
|
||||||
internal class VimEnterLoggerHandler(private val nextHandler: EditorActionHandler) : EditorActionHandler() {
|
internal class VimEnterLoggerHandler(private val nextHandler: EditorActionHandler) : EditorActionHandler() {
|
||||||
override fun doExecute(editor: Editor, caret: Caret?, dataContext: DataContext?) {
|
override fun doExecute(editor: Editor, caret: Caret?, dataContext: DataContext?) {
|
||||||
LOG.info("Enter pressed")
|
if (enableOctopus) {
|
||||||
|
LOG.info("Enter pressed")
|
||||||
|
}
|
||||||
nextHandler.execute(editor, caret, dataContext)
|
nextHandler.execute(editor, caret, dataContext)
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -341,6 +350,7 @@ internal abstract class VimKeyHandler(nextHandler: EditorActionHandler?) : Octop
|
|||||||
}
|
}
|
||||||
|
|
||||||
internal fun isOctopusEnabled(s: KeyStroke, editor: Editor): Boolean {
|
internal fun isOctopusEnabled(s: KeyStroke, editor: Editor): Boolean {
|
||||||
|
if (!enableOctopus) return false
|
||||||
// CMD line has a different processing mechanizm: the processing actions are registered
|
// CMD line has a different processing mechanizm: the processing actions are registered
|
||||||
// for the input field component. These keys are not dispatched via the octopus handler.
|
// for the input field component. These keys are not dispatched via the octopus handler.
|
||||||
if (editor.vim.mode is Mode.CMD_LINE) return false
|
if (editor.vim.mode is Mode.CMD_LINE) return false
|
||||||
@ -350,3 +360,6 @@ internal fun isOctopusEnabled(s: KeyStroke, editor: Editor): Boolean {
|
|||||||
}
|
}
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
|
||||||
|
internal val enableOctopus: Boolean
|
||||||
|
get() = injector.globalOptions().octopushandler
|
||||||
|
@ -46,12 +46,6 @@ public class EditorHelper {
|
|||||||
return editor.getScrollingModel().getVisibleAreaOnScrollingFinished();
|
return editor.getScrollingModel().getVisibleAreaOnScrollingFinished();
|
||||||
}
|
}
|
||||||
|
|
||||||
//("Use extension function with the same name on VimEditor")
|
|
||||||
@Deprecated
|
|
||||||
public static boolean isLineEmpty(final @NotNull Editor editor, final int line, final boolean allowBlanks) {
|
|
||||||
return EngineEditorHelperKt.isLineEmpty(new IjVimEditor(editor), line, allowBlanks);
|
|
||||||
}
|
|
||||||
|
|
||||||
public static boolean scrollVertically(@NotNull Editor editor, int verticalOffset) {
|
public static boolean scrollVertically(@NotNull Editor editor, int verticalOffset) {
|
||||||
final ScrollingModel scrollingModel = editor.getScrollingModel();
|
final ScrollingModel scrollingModel = editor.getScrollingModel();
|
||||||
final Rectangle area = scrollingModel.getVisibleAreaOnScrollingFinished();
|
final Rectangle area = scrollingModel.getVisibleAreaOnScrollingFinished();
|
||||||
@ -335,7 +329,7 @@ public class EditorHelper {
|
|||||||
|
|
||||||
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);
|
@NotNull final VimEditor editor1 = new IjVimEditor(editor);
|
||||||
final int lastVisualLine = EngineEditorHelperKt.getVisualLineCount(editor1) - 1;
|
final int lastVisualLine = EngineEditorHelperKt.getVisualLineCount(editor1) + editor.getSettings().getAdditionalLinesCount();
|
||||||
final int offsetForLastLineAtBottom = getOffsetToScrollVisualLineToBottomOfScreen(editor, lastVisualLine);
|
final int offsetForLastLineAtBottom = getOffsetToScrollVisualLineToBottomOfScreen(editor, lastVisualLine);
|
||||||
|
|
||||||
// For `zz`, we want to use virtual space and move any line, including the last one, to the middle of the screen.
|
// For `zz`, we want to use virtual space and move any line, including the last one, to the middle of the screen.
|
||||||
|
@ -12,6 +12,7 @@ package com.maddyhome.idea.vim.helper
|
|||||||
|
|
||||||
import com.intellij.codeWithMe.ClientId
|
import com.intellij.codeWithMe.ClientId
|
||||||
import com.intellij.openapi.editor.Caret
|
import com.intellij.openapi.editor.Caret
|
||||||
|
import com.intellij.openapi.editor.CaretState
|
||||||
import com.intellij.openapi.editor.Editor
|
import com.intellij.openapi.editor.Editor
|
||||||
import com.intellij.openapi.editor.ex.util.EditorUtil
|
import com.intellij.openapi.editor.ex.util.EditorUtil
|
||||||
import com.intellij.openapi.fileEditor.ex.FileEditorManagerEx
|
import com.intellij.openapi.fileEditor.ex.FileEditorManagerEx
|
||||||
@ -19,6 +20,8 @@ import com.intellij.util.ui.table.JBTableRowEditor
|
|||||||
import com.maddyhome.idea.vim.api.injector
|
import com.maddyhome.idea.vim.api.injector
|
||||||
import com.maddyhome.idea.vim.group.IjOptionConstants
|
import com.maddyhome.idea.vim.group.IjOptionConstants
|
||||||
import com.maddyhome.idea.vim.newapi.globalIjOptions
|
import com.maddyhome.idea.vim.newapi.globalIjOptions
|
||||||
|
import com.maddyhome.idea.vim.newapi.vim
|
||||||
|
import com.maddyhome.idea.vim.state.mode.inBlockSelection
|
||||||
import java.awt.Component
|
import java.awt.Component
|
||||||
import javax.swing.JComponent
|
import javax.swing.JComponent
|
||||||
import javax.swing.JTable
|
import javax.swing.JTable
|
||||||
@ -93,3 +96,41 @@ internal val Caret.vimLine: Int
|
|||||||
*/
|
*/
|
||||||
internal val Editor.vimLine: Int
|
internal val Editor.vimLine: Int
|
||||||
get() = this.caretModel.currentCaret.vimLine
|
get() = this.caretModel.currentCaret.vimLine
|
||||||
|
|
||||||
|
internal inline fun Editor.runWithEveryCaretAndRestore(action: () -> Unit) {
|
||||||
|
val caretModel = this.caretModel
|
||||||
|
val carets = if (this.vim.inBlockSelection) null else caretModel.allCarets
|
||||||
|
if (carets == null || carets.size == 1) {
|
||||||
|
action()
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
var initialDocumentSize = this.document.textLength
|
||||||
|
var documentSizeDifference = 0
|
||||||
|
|
||||||
|
val caretOffsets = carets.map { it.selectionStart to it.selectionEnd }
|
||||||
|
val restoredCarets = mutableListOf<CaretState>()
|
||||||
|
|
||||||
|
caretModel.removeSecondaryCarets()
|
||||||
|
|
||||||
|
for ((selectionStart, selectionEnd) in caretOffsets) {
|
||||||
|
if (selectionStart == selectionEnd) {
|
||||||
|
caretModel.primaryCaret.moveToOffset(selectionStart + documentSizeDifference)
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
caretModel.primaryCaret.setSelection(
|
||||||
|
selectionStart + documentSizeDifference,
|
||||||
|
selectionEnd + documentSizeDifference
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
action()
|
||||||
|
restoredCarets.add(caretModel.caretsAndSelections.single())
|
||||||
|
|
||||||
|
val documentLength = this.document.textLength
|
||||||
|
documentSizeDifference += documentLength - initialDocumentSize
|
||||||
|
initialDocumentSize = documentLength
|
||||||
|
}
|
||||||
|
|
||||||
|
caretModel.caretsAndSelections = restoredCarets
|
||||||
|
}
|
||||||
|
}
|
||||||
|
@ -23,43 +23,6 @@ import com.maddyhome.idea.vim.newapi.vim
|
|||||||
import com.maddyhome.idea.vim.state.mode.inBlockSelection
|
import com.maddyhome.idea.vim.state.mode.inBlockSelection
|
||||||
import java.util.stream.Collectors
|
import java.util.stream.Collectors
|
||||||
|
|
||||||
/**
|
|
||||||
* This annotation is created for test functions (methods).
|
|
||||||
* It means that the original vim behavior has small differences from behavior of IdeaVim.
|
|
||||||
* [shouldBeFixed] flag indicates whether the given functionality should be fixed
|
|
||||||
* or the given behavior is normal for IdeaVim and should be leaved as is.
|
|
||||||
*
|
|
||||||
* E.g. after execution of some commands original vim has the following text:
|
|
||||||
* Hello1
|
|
||||||
* Hello2
|
|
||||||
* Hello3
|
|
||||||
*
|
|
||||||
* But IdeaVim gives you:
|
|
||||||
* Hello1
|
|
||||||
*
|
|
||||||
* Hello2
|
|
||||||
* Hello3
|
|
||||||
*
|
|
||||||
* In this case you should still create the test function and mark this function with [VimBehaviorDiffers] annotation.
|
|
||||||
*
|
|
||||||
* Why does this annotation exist?
|
|
||||||
* After creating some functionality you can understand that IdeaVim has a bit different behavior, but you
|
|
||||||
* cannot fix it right now because of any reason (bugs in IDE,
|
|
||||||
* the impossibility of this functionality in IDEA (*[shouldBeFixed] == false*), leak of time for fixing).
|
|
||||||
* In that case, you should NOT remove the corresponding test or leave it without any marks that this test
|
|
||||||
* not fully convenient with vim, but leave the test with IdeaVim's behavior and put this annotation
|
|
||||||
* with description of how original vim works.
|
|
||||||
*
|
|
||||||
* Note that using this annotation should be avoided as much as possible and behavior of IdeaVim should be as close
|
|
||||||
* to vim as possible.
|
|
||||||
*/
|
|
||||||
@Target(AnnotationTarget.FUNCTION)
|
|
||||||
internal annotation class VimBehaviorDiffers(
|
|
||||||
val originalVimAfter: String = "",
|
|
||||||
val description: String = "",
|
|
||||||
val shouldBeFixed: Boolean = true,
|
|
||||||
)
|
|
||||||
|
|
||||||
internal fun <T : Comparable<T>> sort(a: T, b: T) = if (a > b) b to a else a to b
|
internal fun <T : Comparable<T>> sort(a: T, b: T) = if (a > b) b to a else a to b
|
||||||
|
|
||||||
// TODO Should be replaced with VimEditor.carets()
|
// TODO Should be replaced with VimEditor.carets()
|
||||||
|
@ -14,7 +14,6 @@ import com.maddyhome.idea.vim.api.injector
|
|||||||
import com.maddyhome.idea.vim.api.normalizeVisualColumn
|
import com.maddyhome.idea.vim.api.normalizeVisualColumn
|
||||||
import com.maddyhome.idea.vim.api.options
|
import com.maddyhome.idea.vim.api.options
|
||||||
import com.maddyhome.idea.vim.command.CommandFlags
|
import com.maddyhome.idea.vim.command.CommandFlags
|
||||||
import com.maddyhome.idea.vim.state.VimStateMachine
|
|
||||||
import com.maddyhome.idea.vim.helper.EditorHelper.getApproximateScreenHeight
|
import com.maddyhome.idea.vim.helper.EditorHelper.getApproximateScreenHeight
|
||||||
import com.maddyhome.idea.vim.helper.EditorHelper.getApproximateScreenWidth
|
import com.maddyhome.idea.vim.helper.EditorHelper.getApproximateScreenWidth
|
||||||
import com.maddyhome.idea.vim.helper.EditorHelper.getNonNormalizedVisualLineAtBottomOfScreen
|
import com.maddyhome.idea.vim.helper.EditorHelper.getNonNormalizedVisualLineAtBottomOfScreen
|
||||||
@ -29,6 +28,7 @@ import com.maddyhome.idea.vim.helper.EditorHelper.scrollVisualLineToBottomOfScre
|
|||||||
import com.maddyhome.idea.vim.helper.EditorHelper.scrollVisualLineToMiddleOfScreen
|
import com.maddyhome.idea.vim.helper.EditorHelper.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
|
||||||
@ -56,7 +56,7 @@ internal object ScrollViewHelper {
|
|||||||
// that this needs to be replaced as a more or less dumb line for line rewrite.
|
// that this needs to be replaced as a more or less dumb line for line rewrite.
|
||||||
val topLine = getVisualLineAtTopOfScreen(editor)
|
val topLine = getVisualLineAtTopOfScreen(editor)
|
||||||
val bottomLine = getVisualLineAtBottomOfScreen(editor)
|
val bottomLine = getVisualLineAtBottomOfScreen(editor)
|
||||||
val lastLine = vimEditor.getVisualLineCount() - 1
|
val lastLine = vimEditor.getVisualLineCount() + editor.settings.additionalLinesCount
|
||||||
|
|
||||||
// We need the non-normalised value here, so we can handle cases such as so=999 to keep the current line centred
|
// We need the non-normalised value here, so we can handle cases such as so=999 to keep the current line centred
|
||||||
val scrollOffset = injector.options(vimEditor).scrolloff
|
val scrollOffset = injector.options(vimEditor).scrolloff
|
||||||
|
@ -9,6 +9,7 @@
|
|||||||
package com.maddyhome.idea.vim.helper;
|
package com.maddyhome.idea.vim.helper;
|
||||||
|
|
||||||
import com.google.common.collect.Lists;
|
import com.google.common.collect.Lists;
|
||||||
|
import com.intellij.codeInsight.daemon.impl.DaemonCodeAnalyzerEx;
|
||||||
import com.intellij.lang.CodeDocumentationAwareCommenter;
|
import com.intellij.lang.CodeDocumentationAwareCommenter;
|
||||||
import com.intellij.lang.Commenter;
|
import com.intellij.lang.Commenter;
|
||||||
import com.intellij.lang.Language;
|
import com.intellij.lang.Language;
|
||||||
@ -16,15 +17,17 @@ import com.intellij.lang.LanguageCommenters;
|
|||||||
import com.intellij.openapi.diagnostic.Logger;
|
import com.intellij.openapi.diagnostic.Logger;
|
||||||
import com.intellij.openapi.editor.Caret;
|
import com.intellij.openapi.editor.Caret;
|
||||||
import com.intellij.openapi.editor.Editor;
|
import com.intellij.openapi.editor.Editor;
|
||||||
|
import com.intellij.openapi.project.Project;
|
||||||
import com.intellij.psi.PsiComment;
|
import com.intellij.psi.PsiComment;
|
||||||
import com.intellij.psi.PsiElement;
|
import com.intellij.psi.PsiElement;
|
||||||
import com.intellij.psi.PsiFile;
|
import com.intellij.psi.PsiFile;
|
||||||
import com.intellij.psi.util.PsiTreeUtil;
|
import com.intellij.psi.util.PsiTreeUtil;
|
||||||
|
import com.intellij.spellchecker.SpellCheckerSeveritiesProvider;
|
||||||
import com.maddyhome.idea.vim.VimPlugin;
|
import com.maddyhome.idea.vim.VimPlugin;
|
||||||
import com.maddyhome.idea.vim.api.EngineEditorHelperKt;
|
import com.maddyhome.idea.vim.api.EngineEditorHelperKt;
|
||||||
import com.maddyhome.idea.vim.api.VimEditor;
|
import com.maddyhome.idea.vim.api.VimEditor;
|
||||||
import com.maddyhome.idea.vim.state.mode.Mode;
|
import com.maddyhome.idea.vim.regexp.*;
|
||||||
import com.maddyhome.idea.vim.state.VimStateMachine;
|
import com.maddyhome.idea.vim.regexp.match.VimMatchResult;
|
||||||
import com.maddyhome.idea.vim.common.CharacterPosition;
|
import com.maddyhome.idea.vim.common.CharacterPosition;
|
||||||
import com.maddyhome.idea.vim.common.Direction;
|
import com.maddyhome.idea.vim.common.Direction;
|
||||||
import com.maddyhome.idea.vim.common.TextRange;
|
import com.maddyhome.idea.vim.common.TextRange;
|
||||||
@ -32,6 +35,12 @@ import com.maddyhome.idea.vim.newapi.IjVimCaret;
|
|||||||
import com.maddyhome.idea.vim.newapi.IjVimEditor;
|
import com.maddyhome.idea.vim.newapi.IjVimEditor;
|
||||||
import com.maddyhome.idea.vim.regexp.CharPointer;
|
import com.maddyhome.idea.vim.regexp.CharPointer;
|
||||||
import com.maddyhome.idea.vim.regexp.RegExp;
|
import com.maddyhome.idea.vim.regexp.RegExp;
|
||||||
|
import com.maddyhome.idea.vim.state.VimStateMachine;
|
||||||
|
import com.maddyhome.idea.vim.state.mode.Mode;
|
||||||
|
import it.unimi.dsi.fastutil.ints.IntComparator;
|
||||||
|
import it.unimi.dsi.fastutil.ints.IntIterator;
|
||||||
|
import it.unimi.dsi.fastutil.ints.IntRBTreeSet;
|
||||||
|
import it.unimi.dsi.fastutil.ints.IntSortedSet;
|
||||||
import kotlin.Pair;
|
import kotlin.Pair;
|
||||||
import org.jetbrains.annotations.Contract;
|
import org.jetbrains.annotations.Contract;
|
||||||
import org.jetbrains.annotations.NotNull;
|
import org.jetbrains.annotations.NotNull;
|
||||||
@ -42,10 +51,10 @@ import java.util.function.Function;
|
|||||||
import java.util.regex.Matcher;
|
import java.util.regex.Matcher;
|
||||||
import java.util.regex.Pattern;
|
import java.util.regex.Pattern;
|
||||||
|
|
||||||
import static com.maddyhome.idea.vim.api.VimInjectorKt.injector;
|
import static com.maddyhome.idea.vim.api.VimInjectorKt.*;
|
||||||
import static com.maddyhome.idea.vim.api.VimInjectorKt.options;
|
|
||||||
import static com.maddyhome.idea.vim.helper.SearchHelperKtKt.checkInString;
|
import static com.maddyhome.idea.vim.helper.SearchHelperKtKt.checkInString;
|
||||||
import static com.maddyhome.idea.vim.helper.SearchHelperKtKt.shouldIgnoreCase;
|
import static com.maddyhome.idea.vim.helper.SearchHelperKtKt.shouldIgnoreCase;
|
||||||
|
import static com.maddyhome.idea.vim.newapi.IjVimInjectorKt.globalIjOptions;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Helper methods for searching text
|
* Helper methods for searching text
|
||||||
@ -59,6 +68,8 @@ public class SearchHelper {
|
|||||||
/**
|
/**
|
||||||
* Find text matching the given pattern.
|
* Find text matching the given pattern.
|
||||||
*
|
*
|
||||||
|
* @deprecated Use IjVimSearchHelper.findPattern instead
|
||||||
|
*
|
||||||
* <p>See search.c:searchit</p>
|
* <p>See search.c:searchit</p>
|
||||||
*
|
*
|
||||||
* @param editor The editor to search in
|
* @param editor The editor to search in
|
||||||
@ -69,12 +80,13 @@ public class SearchHelper {
|
|||||||
* @return A TextRange representing the result, or null
|
* @return A TextRange representing the result, or null
|
||||||
*/
|
*/
|
||||||
@Nullable
|
@Nullable
|
||||||
|
@Deprecated
|
||||||
public static TextRange findPattern(@NotNull Editor editor,
|
public static TextRange findPattern(@NotNull Editor editor,
|
||||||
@Nullable String pattern,
|
@Nullable String pattern,
|
||||||
int startOffset,
|
int startOffset,
|
||||||
int count,
|
int count,
|
||||||
EnumSet<SearchOptions> searchOptions) {
|
EnumSet<SearchOptions> searchOptions) {
|
||||||
if (pattern == null || pattern.length() == 0) {
|
if (pattern == null || pattern.isEmpty()) {
|
||||||
logger.warn("Pattern is null or empty. Cannot perform search");
|
logger.warn("Pattern is null or empty. Cannot perform search");
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
@ -347,6 +359,8 @@ public class SearchHelper {
|
|||||||
/**
|
/**
|
||||||
* Find all occurrences of the pattern.
|
* Find all occurrences of the pattern.
|
||||||
*
|
*
|
||||||
|
* @deprecated Use IjVimSearchHelper.findall instead
|
||||||
|
*
|
||||||
* @param editor The editor to search in
|
* @param editor The editor to search in
|
||||||
* @param pattern The pattern to search for
|
* @param pattern The pattern to search for
|
||||||
* @param startLine The start line of the range to search for
|
* @param startLine The start line of the range to search for
|
||||||
@ -354,12 +368,33 @@ public class SearchHelper {
|
|||||||
* @param ignoreCase Case sensitive or insensitive searching
|
* @param ignoreCase Case sensitive or insensitive searching
|
||||||
* @return A list of TextRange objects representing the results
|
* @return A list of TextRange objects representing the results
|
||||||
*/
|
*/
|
||||||
|
@Deprecated
|
||||||
public static @NotNull List<TextRange> findAll(@NotNull Editor editor,
|
public static @NotNull List<TextRange> findAll(@NotNull Editor editor,
|
||||||
@NotNull String pattern,
|
@NotNull String pattern,
|
||||||
int startLine,
|
int startLine,
|
||||||
int endLine,
|
int endLine,
|
||||||
boolean ignoreCase) {
|
boolean ignoreCase) {
|
||||||
final List<TextRange> results = Lists.newArrayList();
|
final List<TextRange> results = Lists.newArrayList();
|
||||||
|
|
||||||
|
if (globalIjOptions(injector).getUseNewRegex()) {
|
||||||
|
final List<VimRegexOptions> options = new ArrayList<>();
|
||||||
|
if (globalOptions(injector).getSmartcase()) options.add(VimRegexOptions.SMART_CASE);
|
||||||
|
if (globalOptions(injector).getIgnorecase()) options.add(VimRegexOptions.IGNORE_CASE);
|
||||||
|
VimEditor vimEditor = new IjVimEditor(editor);
|
||||||
|
try {
|
||||||
|
// TODO: we shouldn't care about the ignoreCase argument, and instead just look into the editor options.
|
||||||
|
// It would require a refactor, so for now prepend \c or \C to "force" ignoreCase
|
||||||
|
String newPattern = (ignoreCase ? "\\c" : "\\C") + pattern;
|
||||||
|
VimRegex regex = new VimRegex(newPattern);
|
||||||
|
List<VimMatchResult.Success> foundMatches = regex.findAll(vimEditor, vimEditor.getLineStartOffset(startLine), vimEditor.getLineEndOffset(endLine == -1 ? vimEditor.lineCount() - 1 : endLine) + 1, options);
|
||||||
|
for (VimMatchResult.Success match : foundMatches) results.add(match.getRange());
|
||||||
|
return results;
|
||||||
|
} catch (VimRegexException e) {
|
||||||
|
injector.getMessages().showStatusBarMessage(vimEditor, e.getMessage());
|
||||||
|
return results;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
final int lineCount = new IjVimEditor(editor).lineCount();
|
final int lineCount = new IjVimEditor(editor).lineCount();
|
||||||
final int actualEndLine = endLine == -1 ? lineCount - 1 : endLine;
|
final int actualEndLine = endLine == -1 ? lineCount - 1 : endLine;
|
||||||
|
|
||||||
@ -402,6 +437,10 @@ public class SearchHelper {
|
|||||||
return results;
|
return results;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @deprecated Use IjVimSearchHelper.findSection instead
|
||||||
|
*/
|
||||||
|
@Deprecated
|
||||||
public static int findSection(@NotNull Editor editor, @NotNull Caret caret, char type, int dir, int count) {
|
public static int findSection(@NotNull Editor editor, @NotNull Caret caret, char type, int dir, int count) {
|
||||||
CharSequence chars = editor.getDocument().getCharsSequence();
|
CharSequence chars = editor.getDocument().getCharsSequence();
|
||||||
int line = caret.getLogicalPosition().line + dir;
|
int line = caret.getLogicalPosition().line + dir;
|
||||||
@ -428,6 +467,10 @@ public class SearchHelper {
|
|||||||
return res;
|
return res;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @deprecated Use IjVimSearchHelper.findUnmatchedBlock instead
|
||||||
|
*/
|
||||||
|
@Deprecated
|
||||||
public static int findUnmatchedBlock(@NotNull Editor editor, @NotNull Caret caret, char type, int count) {
|
public static int findUnmatchedBlock(@NotNull Editor editor, @NotNull Caret caret, char type, int count) {
|
||||||
CharSequence chars = editor.getDocument().getCharsSequence();
|
CharSequence chars = editor.getDocument().getCharsSequence();
|
||||||
int pos = caret.getOffset();
|
int pos = caret.getOffset();
|
||||||
@ -447,6 +490,8 @@ public class SearchHelper {
|
|||||||
/**
|
/**
|
||||||
* Find block enclosing the caret
|
* Find block enclosing the caret
|
||||||
*
|
*
|
||||||
|
* @deprecated Use IjVimSearchHelper.findBlockRange instead
|
||||||
|
*
|
||||||
* @param editor The editor to search in
|
* @param editor The editor to search in
|
||||||
* @param caret The caret currently at
|
* @param caret The caret currently at
|
||||||
* @param type The type of block, e.g. (, [, {, <
|
* @param type The type of block, e.g. (, [, {, <
|
||||||
@ -798,6 +843,10 @@ public class SearchHelper {
|
|||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @deprecated Use IjVimSearchHelper.findBlockTagRange instead
|
||||||
|
*/
|
||||||
|
@Deprecated
|
||||||
public static @Nullable TextRange findBlockTagRange(@NotNull Editor editor,
|
public static @Nullable TextRange findBlockTagRange(@NotNull Editor editor,
|
||||||
@NotNull Caret caret,
|
@NotNull Caret caret,
|
||||||
int count,
|
int count,
|
||||||
@ -1330,6 +1379,10 @@ public class SearchHelper {
|
|||||||
return new TextRange(start, end);
|
return new TextRange(start, end);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @deprecated Use IjVimSearchHelper.findWordUnderCursor instead
|
||||||
|
*/
|
||||||
|
@Deprecated
|
||||||
@Contract("_, _, _, _, _, _, _ -> new")
|
@Contract("_, _, _, _, _, _, _ -> new")
|
||||||
public static @NotNull TextRange findWordUnderCursor(@NotNull Editor editor,
|
public static @NotNull TextRange findWordUnderCursor(@NotNull Editor editor,
|
||||||
@NotNull Caret caret,
|
@NotNull Caret caret,
|
||||||
@ -1515,14 +1568,56 @@ public class SearchHelper {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @deprecated Use IjVimSearchHelper.findMethodStart instead
|
||||||
|
*/
|
||||||
public static int findMethodStart(@NotNull Editor editor, @NotNull Caret caret, int count) {
|
public static int findMethodStart(@NotNull Editor editor, @NotNull Caret caret, int count) {
|
||||||
return PsiHelper.findMethodStart(editor, caret.getOffset(), count);
|
return PsiHelper.findMethodStart(editor, caret.getOffset(), count);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @deprecated Use IjVimSearchHelper.findMethodEnd instead
|
||||||
|
*/
|
||||||
public static int findMethodEnd(@NotNull Editor editor, @NotNull Caret caret, int count) {
|
public static int findMethodEnd(@NotNull Editor editor, @NotNull Caret caret, int count) {
|
||||||
return PsiHelper.findMethodEnd(editor, caret.getOffset(), count);
|
return PsiHelper.findMethodEnd(editor, caret.getOffset(), count);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public static int findMisspelledWords(@NotNull Editor editor,
|
||||||
|
int startOffset,
|
||||||
|
int endOffset,
|
||||||
|
int skipCount,
|
||||||
|
IntComparator offsetOrdering) {
|
||||||
|
Project project = editor.getProject();
|
||||||
|
if (project == null) {
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
IntSortedSet offsets = new IntRBTreeSet(offsetOrdering);
|
||||||
|
DaemonCodeAnalyzerEx.processHighlights(editor.getDocument(), project, SpellCheckerSeveritiesProvider.TYPO,
|
||||||
|
startOffset, endOffset, highlight -> {
|
||||||
|
if (highlight.getSeverity() == SpellCheckerSeveritiesProvider.TYPO) {
|
||||||
|
int offset = highlight.getStartOffset();
|
||||||
|
if (offset >= startOffset && offset <= endOffset) {
|
||||||
|
offsets.add(offset);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
});
|
||||||
|
|
||||||
|
if (offsets.isEmpty()) {
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (skipCount >= offsets.size()) {
|
||||||
|
return offsets.lastInt();
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
IntIterator offsetIterator = offsets.iterator();
|
||||||
|
offsetIterator.skip(skipCount);
|
||||||
|
return offsetIterator.nextInt();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
private static @NotNull String parseMatchPairsOption(final VimEditor vimEditor) {
|
private static @NotNull String parseMatchPairsOption(final VimEditor vimEditor) {
|
||||||
List<String> pairs = options(injector, vimEditor).getMatchpairs();
|
List<String> pairs = options(injector, vimEditor).getMatchpairs();
|
||||||
StringBuilder res = new StringBuilder();
|
StringBuilder res = new StringBuilder();
|
||||||
|
@ -26,6 +26,7 @@ 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.common.TextRange
|
import com.maddyhome.idea.vim.common.TextRange
|
||||||
import com.maddyhome.idea.vim.ex.ranges.LineRange
|
import com.maddyhome.idea.vim.ex.ranges.LineRange
|
||||||
|
import com.maddyhome.idea.vim.newapi.IjVimEditor
|
||||||
import com.maddyhome.idea.vim.newapi.vim
|
import com.maddyhome.idea.vim.newapi.vim
|
||||||
import org.jetbrains.annotations.Contract
|
import org.jetbrains.annotations.Contract
|
||||||
import java.awt.Color
|
import java.awt.Color
|
||||||
@ -105,7 +106,7 @@ private fun updateSearchHighlights(
|
|||||||
val startLine = searchRange?.startLine ?: 0
|
val startLine = searchRange?.startLine ?: 0
|
||||||
val endLine = searchRange?.endLine ?: -1
|
val endLine = searchRange?.endLine ?: -1
|
||||||
val results =
|
val results =
|
||||||
SearchHelper.findAll(editor, pattern, startLine, endLine, shouldIgnoreCase(pattern, shouldIgnoreSmartCase))
|
injector.searchHelper.findAll(IjVimEditor(editor), pattern, startLine, endLine, shouldIgnoreCase(pattern, shouldIgnoreSmartCase))
|
||||||
if (results.isNotEmpty()) {
|
if (results.isNotEmpty()) {
|
||||||
currentMatchOffset = findClosestMatch(editor, results, initialOffset, forwards)
|
currentMatchOffset = findClosestMatch(editor, results, initialOffset, forwards)
|
||||||
highlightSearchResults(editor, pattern, results, currentMatchOffset)
|
highlightSearchResults(editor, pattern, results, currentMatchOffset)
|
||||||
@ -119,7 +120,7 @@ private fun updateSearchHighlights(
|
|||||||
}
|
}
|
||||||
if (shouldIgnoreSmartCase) searchOptions.add(SearchOptions.IGNORE_SMARTCASE)
|
if (shouldIgnoreSmartCase) searchOptions.add(SearchOptions.IGNORE_SMARTCASE)
|
||||||
if (!forwards) searchOptions.add(SearchOptions.BACKWARDS)
|
if (!forwards) searchOptions.add(SearchOptions.BACKWARDS)
|
||||||
val result = SearchHelper.findPattern(editor, pattern, initialOffset, 1, searchOptions)
|
val result = injector.searchHelper.findPattern(IjVimEditor(editor), pattern, initialOffset, 1, searchOptions)
|
||||||
if (result != null) {
|
if (result != null) {
|
||||||
currentMatchOffset = result.startOffset
|
currentMatchOffset = result.startOffset
|
||||||
val results = listOf(result)
|
val results = listOf(result)
|
||||||
|
@ -31,10 +31,4 @@ public object StringHelper {
|
|||||||
return Arrays.stream(string).flatMap { o: String -> injector.parser.parseKeys(o).stream() }
|
return Arrays.stream(string).flatMap { o: String -> injector.parser.parseKeys(o).stream() }
|
||||||
.collect(Collectors.toList())
|
.collect(Collectors.toList())
|
||||||
}
|
}
|
||||||
|
|
||||||
@JvmStatic
|
|
||||||
@Deprecated("Use KeyStroke.isCloseKeyStroke", ReplaceWith("stroke.isCloseKeyStroke()"))
|
|
||||||
public fun isCloseKeyStroke(stroke: KeyStroke): Boolean {
|
|
||||||
return stroke.isCloseKeyStroke()
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
@ -11,14 +11,15 @@ import com.google.common.collect.Lists
|
|||||||
import com.intellij.openapi.editor.Editor
|
import com.intellij.openapi.editor.Editor
|
||||||
import javax.swing.KeyStroke
|
import javax.swing.KeyStroke
|
||||||
|
|
||||||
internal class TestInputModel private constructor() {
|
// Do not remove until it's used in EasyMotion plugin in tests
|
||||||
|
public class TestInputModel private constructor() {
|
||||||
private val myKeyStrokes: MutableList<KeyStroke> = Lists.newArrayList()
|
private val myKeyStrokes: MutableList<KeyStroke> = Lists.newArrayList()
|
||||||
fun setKeyStrokes(keyStrokes: List<KeyStroke>) {
|
public fun setKeyStrokes(keyStrokes: List<KeyStroke>) {
|
||||||
myKeyStrokes.clear()
|
myKeyStrokes.clear()
|
||||||
myKeyStrokes.addAll(keyStrokes)
|
myKeyStrokes.addAll(keyStrokes)
|
||||||
}
|
}
|
||||||
|
|
||||||
fun nextKeyStroke(): KeyStroke? {
|
public fun nextKeyStroke(): KeyStroke? {
|
||||||
// Return key from the unfinished mapping
|
// Return key from the unfinished mapping
|
||||||
/*
|
/*
|
||||||
MappingStack mappingStack = KeyHandler.getInstance().getMappingStack();
|
MappingStack mappingStack = KeyHandler.getInstance().getMappingStack();
|
||||||
@ -33,9 +34,9 @@ if (mappingStack.hasStroke()) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
companion object {
|
public companion object {
|
||||||
@JvmStatic
|
@JvmStatic
|
||||||
fun getInstance(editor: Editor): TestInputModel {
|
public fun getInstance(editor: Editor): TestInputModel {
|
||||||
var model = editor.vimTestInputModel
|
var model = editor.vimTestInputModel
|
||||||
if (model == null) {
|
if (model == null) {
|
||||||
model = TestInputModel()
|
model = TestInputModel()
|
||||||
|
@ -14,6 +14,7 @@ import com.intellij.openapi.command.CommandProcessor
|
|||||||
import com.intellij.openapi.command.undo.UndoManager
|
import com.intellij.openapi.command.undo.UndoManager
|
||||||
import com.intellij.openapi.components.Service
|
import com.intellij.openapi.components.Service
|
||||||
import com.intellij.openapi.fileEditor.impl.text.TextEditorProvider
|
import com.intellij.openapi.fileEditor.impl.text.TextEditorProvider
|
||||||
|
import com.maddyhome.idea.vim.VimPlugin
|
||||||
import com.maddyhome.idea.vim.api.ExecutionContext
|
import com.maddyhome.idea.vim.api.ExecutionContext
|
||||||
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.api.injector
|
||||||
@ -21,6 +22,8 @@ import com.maddyhome.idea.vim.common.ChangesListener
|
|||||||
import com.maddyhome.idea.vim.listener.SelectionVimListenerSuppressor
|
import com.maddyhome.idea.vim.listener.SelectionVimListenerSuppressor
|
||||||
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.state.mode.SelectionType
|
||||||
|
import com.maddyhome.idea.vim.state.mode.inVisualMode
|
||||||
import com.maddyhome.idea.vim.undo.UndoRedoBase
|
import com.maddyhome.idea.vim.undo.UndoRedoBase
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -39,6 +42,7 @@ internal class UndoRedoHelper : UndoRedoBase() {
|
|||||||
|
|
||||||
if (injector.globalIjOptions().oldundo) {
|
if (injector.globalIjOptions().oldundo) {
|
||||||
SelectionVimListenerSuppressor.lock().use { undoManager.undo(fileEditor) }
|
SelectionVimListenerSuppressor.lock().use { undoManager.undo(fileEditor) }
|
||||||
|
restoreVisualMode(editor)
|
||||||
} else {
|
} else {
|
||||||
// TODO refactor me after VIM-308 when restoring selection and caret movement will be ignored by undo
|
// TODO refactor me after VIM-308 when restoring selection and caret movement will be ignored by undo
|
||||||
editor.runWithChangeTracking {
|
editor.runWithChangeTracking {
|
||||||
@ -74,6 +78,7 @@ internal class UndoRedoHelper : UndoRedoBase() {
|
|||||||
if (undoManager.isRedoAvailable(fileEditor)) {
|
if (undoManager.isRedoAvailable(fileEditor)) {
|
||||||
if (injector.globalIjOptions().oldundo) {
|
if (injector.globalIjOptions().oldundo) {
|
||||||
SelectionVimListenerSuppressor.lock().use { undoManager.redo(fileEditor) }
|
SelectionVimListenerSuppressor.lock().use { undoManager.redo(fileEditor) }
|
||||||
|
restoreVisualMode(editor)
|
||||||
} else {
|
} else {
|
||||||
undoManager.redo(fileEditor)
|
undoManager.redo(fileEditor)
|
||||||
CommandProcessor.getInstance().runUndoTransparentAction {
|
CommandProcessor.getInstance().runUndoTransparentAction {
|
||||||
@ -131,4 +136,21 @@ internal class UndoRedoHelper : UndoRedoBase() {
|
|||||||
val hasChanges: Boolean
|
val hasChanges: Boolean
|
||||||
get() = changeListener.hasChanged || initialPath != editor.getPath()
|
get() = changeListener.hasChanged || initialPath != editor.getPath()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private fun restoreVisualMode(editor: VimEditor) {
|
||||||
|
if (!editor.inVisualMode && editor.getSelectionModel().hasSelection()) {
|
||||||
|
val detectedMode = VimPlugin.getVisualMotion().autodetectVisualSubmode(editor)
|
||||||
|
|
||||||
|
// Visual block selection is restored into multiple carets, so multi-carets that form a block are always
|
||||||
|
// identified as visual block mode, leading to false positives.
|
||||||
|
// Since I use visual block mode much less often than multi-carets, this is a judgment call to never restore
|
||||||
|
// visual block mode.
|
||||||
|
val wantedMode = if (detectedMode == SelectionType.BLOCK_WISE)
|
||||||
|
SelectionType.CHARACTER_WISE
|
||||||
|
else
|
||||||
|
detectedMode
|
||||||
|
|
||||||
|
VimPlugin.getVisualMotion().enterVisualMode(editor, wantedMode)
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,32 +0,0 @@
|
|||||||
/*
|
|
||||||
* Copyright 2003-2023 The IdeaVim authors
|
|
||||||
*
|
|
||||||
* Use of this source code is governed by an MIT-style
|
|
||||||
* license that can be found in the LICENSE.txt file or at
|
|
||||||
* https://opensource.org/licenses/MIT.
|
|
||||||
*/
|
|
||||||
|
|
||||||
package com.maddyhome.idea.vim.helper
|
|
||||||
|
|
||||||
import com.intellij.ide.plugins.StandalonePluginUpdateChecker
|
|
||||||
import com.intellij.openapi.components.Service
|
|
||||||
import com.intellij.openapi.components.service
|
|
||||||
import com.maddyhome.idea.vim.VimPlugin
|
|
||||||
import com.maddyhome.idea.vim.group.NotificationService
|
|
||||||
import com.maddyhome.idea.vim.icons.VimIcons
|
|
||||||
|
|
||||||
@Service(Service.Level.APP)
|
|
||||||
internal class VimStandalonePluginUpdateChecker : StandalonePluginUpdateChecker(
|
|
||||||
VimPlugin.getPluginId(),
|
|
||||||
updateTimestampProperty = PROPERTY_NAME,
|
|
||||||
NotificationService.IDEAVIM_STICKY_GROUP,
|
|
||||||
VimIcons.IDEAVIM,
|
|
||||||
) {
|
|
||||||
|
|
||||||
override fun skipUpdateCheck(): Boolean = VimPlugin.isNotEnabled() || "dev" in VimPlugin.getVersion()
|
|
||||||
|
|
||||||
companion object {
|
|
||||||
private const val PROPERTY_NAME = "ideavim.statistics.timestamp"
|
|
||||||
val instance: VimStandalonePluginUpdateChecker = service()
|
|
||||||
}
|
|
||||||
}
|
|
@ -27,6 +27,7 @@ import com.intellij.openapi.actionSystem.CommonDataKeys
|
|||||||
import com.intellij.openapi.actionSystem.ex.AnActionListener
|
import com.intellij.openapi.actionSystem.ex.AnActionListener
|
||||||
import com.intellij.openapi.actionSystem.impl.ProxyShortcutSet
|
import com.intellij.openapi.actionSystem.impl.ProxyShortcutSet
|
||||||
import com.intellij.openapi.editor.Editor
|
import com.intellij.openapi.editor.Editor
|
||||||
|
import com.intellij.openapi.editor.impl.ScrollingModelImpl
|
||||||
import com.intellij.openapi.project.DumbAwareToggleAction
|
import com.intellij.openapi.project.DumbAwareToggleAction
|
||||||
import com.intellij.openapi.util.TextRange
|
import com.intellij.openapi.util.TextRange
|
||||||
import com.maddyhome.idea.vim.KeyHandler
|
import com.maddyhome.idea.vim.KeyHandler
|
||||||
@ -56,6 +57,7 @@ internal object IdeaSpecifics {
|
|||||||
private val surrounderAction =
|
private val surrounderAction =
|
||||||
"com.intellij.codeInsight.generation.surroundWith.SurroundWithHandler\$InvokeSurrounderAction"
|
"com.intellij.codeInsight.generation.surroundWith.SurroundWithHandler\$InvokeSurrounderAction"
|
||||||
private var editor: Editor? = null
|
private var editor: Editor? = null
|
||||||
|
private var caretOffset = -1
|
||||||
private var completionPrevDocumentLength: Int? = null
|
private var completionPrevDocumentLength: Int? = null
|
||||||
private var completionPrevDocumentOffset: Int? = null
|
private var completionPrevDocumentOffset: Int? = null
|
||||||
override fun beforeActionPerformed(action: AnAction, event: AnActionEvent) {
|
override fun beforeActionPerformed(action: AnAction, event: AnActionEvent) {
|
||||||
@ -64,6 +66,7 @@ internal object IdeaSpecifics {
|
|||||||
val hostEditor = event.dataContext.getData(CommonDataKeys.HOST_EDITOR)
|
val hostEditor = event.dataContext.getData(CommonDataKeys.HOST_EDITOR)
|
||||||
if (hostEditor != null) {
|
if (hostEditor != null) {
|
||||||
editor = hostEditor
|
editor = hostEditor
|
||||||
|
caretOffset = hostEditor.caretModel.offset
|
||||||
}
|
}
|
||||||
|
|
||||||
val isVimAction = (action as? AnActionWrapper)?.delegate is VimShortcutKeyAction
|
val isVimAction = (action as? AnActionWrapper)?.delegate is VimShortcutKeyAction
|
||||||
@ -95,43 +98,56 @@ internal object IdeaSpecifics {
|
|||||||
if (VimPlugin.isNotEnabled()) return
|
if (VimPlugin.isNotEnabled()) return
|
||||||
|
|
||||||
val editor = editor
|
val editor = editor
|
||||||
if (editor != null && action is ChooseItemAction && editor.vimStateMachine?.isRecording == true) {
|
if (editor != null) {
|
||||||
val prevDocumentLength = completionPrevDocumentLength
|
if (action is ChooseItemAction && editor.vimStateMachine?.isRecording == true) {
|
||||||
val prevDocumentOffset = completionPrevDocumentOffset
|
val prevDocumentLength = completionPrevDocumentLength
|
||||||
|
val prevDocumentOffset = completionPrevDocumentOffset
|
||||||
|
|
||||||
if (prevDocumentLength != null && prevDocumentOffset != null) {
|
if (prevDocumentLength != null && prevDocumentOffset != null) {
|
||||||
val register = VimPlugin.getRegister()
|
val register = VimPlugin.getRegister()
|
||||||
val addedTextLength = editor.document.textLength - prevDocumentLength
|
val addedTextLength = editor.document.textLength - prevDocumentLength
|
||||||
val caretShift = addedTextLength - (editor.caretModel.primaryCaret.offset - prevDocumentOffset)
|
val caretShift = addedTextLength - (editor.caretModel.primaryCaret.offset - prevDocumentOffset)
|
||||||
val leftArrow = KeyStroke.getKeyStroke(KeyEvent.VK_LEFT, 0)
|
val leftArrow = KeyStroke.getKeyStroke(KeyEvent.VK_LEFT, 0)
|
||||||
|
|
||||||
register.recordText(editor.document.getText(TextRange(prevDocumentOffset, prevDocumentOffset + addedTextLength)))
|
register.recordText(editor.document.getText(TextRange(prevDocumentOffset, prevDocumentOffset + addedTextLength)))
|
||||||
repeat(caretShift.coerceAtLeast(0)) {
|
repeat(caretShift.coerceAtLeast(0)) {
|
||||||
register.recordKeyStroke(leftArrow)
|
register.recordKeyStroke(leftArrow)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
this.completionPrevDocumentLength = null
|
this.completionPrevDocumentLength = null
|
||||||
this.completionPrevDocumentOffset = null
|
this.completionPrevDocumentOffset = null
|
||||||
}
|
|
||||||
|
|
||||||
//region Enter insert mode after surround with if
|
|
||||||
if (surrounderAction == action.javaClass.name && surrounderItems.any {
|
|
||||||
action.templatePresentation.text.endsWith(
|
|
||||||
it,
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
) {
|
|
||||||
editor?.let {
|
//region Enter insert mode after surround with if
|
||||||
val commandState = it.vim.vimStateMachine
|
if (surrounderAction == action.javaClass.name && surrounderItems.any {
|
||||||
|
action.templatePresentation.text.endsWith(
|
||||||
|
it,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
) {
|
||||||
|
val commandState = editor.vim.vimStateMachine
|
||||||
commandState.mode = Mode.NORMAL()
|
commandState.mode = Mode.NORMAL()
|
||||||
VimPlugin.getChange().insertBeforeCursor(it.vim, event.dataContext.vim)
|
VimPlugin.getChange().insertBeforeCursor(editor.vim, event.dataContext.vim)
|
||||||
KeyHandler.getInstance().reset(it.vim)
|
KeyHandler.getInstance().reset(editor.vim)
|
||||||
|
}
|
||||||
|
//endregion
|
||||||
|
|
||||||
|
if (caretOffset != -1 && caretOffset != editor.caretModel.offset) {
|
||||||
|
val scrollModel = editor.scrollingModel as ScrollingModelImpl
|
||||||
|
if (scrollModel.isScrollingNow) {
|
||||||
|
val v = scrollModel.verticalScrollOffset
|
||||||
|
val h = scrollModel.horizontalScrollOffset
|
||||||
|
scrollModel.finishAnimation()
|
||||||
|
scrollModel.scroll(h, v)
|
||||||
|
scrollModel.finishAnimation()
|
||||||
|
}
|
||||||
|
injector.scroll.scrollCaretIntoView(editor.vim)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
//endregion
|
|
||||||
|
|
||||||
this.editor = null
|
this.editor = null
|
||||||
|
this.caretOffset = -1
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -55,6 +55,7 @@ import com.maddyhome.idea.vim.VimTypedActionHandler
|
|||||||
import com.maddyhome.idea.vim.api.LocalOptionInitialisationScenario
|
import com.maddyhome.idea.vim.api.LocalOptionInitialisationScenario
|
||||||
import com.maddyhome.idea.vim.api.Options
|
import com.maddyhome.idea.vim.api.Options
|
||||||
import com.maddyhome.idea.vim.api.VimEditor
|
import com.maddyhome.idea.vim.api.VimEditor
|
||||||
|
import com.maddyhome.idea.vim.api.coerceOffset
|
||||||
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.api.injector
|
import com.maddyhome.idea.vim.api.injector
|
||||||
@ -74,7 +75,6 @@ import com.maddyhome.idea.vim.handler.correctorRequester
|
|||||||
import com.maddyhome.idea.vim.handler.keyCheckRequests
|
import com.maddyhome.idea.vim.handler.keyCheckRequests
|
||||||
import com.maddyhome.idea.vim.helper.GuicursorChangeListener
|
import com.maddyhome.idea.vim.helper.GuicursorChangeListener
|
||||||
import com.maddyhome.idea.vim.helper.StrictMode
|
import com.maddyhome.idea.vim.helper.StrictMode
|
||||||
import com.maddyhome.idea.vim.helper.VimStandalonePluginUpdateChecker
|
|
||||||
import com.maddyhome.idea.vim.helper.exitSelectMode
|
import com.maddyhome.idea.vim.helper.exitSelectMode
|
||||||
import com.maddyhome.idea.vim.helper.exitVisualMode
|
import com.maddyhome.idea.vim.helper.exitVisualMode
|
||||||
import com.maddyhome.idea.vim.helper.forceBarCursor
|
import com.maddyhome.idea.vim.helper.forceBarCursor
|
||||||
@ -91,6 +91,8 @@ import com.maddyhome.idea.vim.listener.MouseEventsDataHolder.skipNDragEvents
|
|||||||
import com.maddyhome.idea.vim.listener.VimListenerManager.EditorListeners.add
|
import com.maddyhome.idea.vim.listener.VimListenerManager.EditorListeners.add
|
||||||
import com.maddyhome.idea.vim.newapi.IjVimEditor
|
import com.maddyhome.idea.vim.newapi.IjVimEditor
|
||||||
import com.maddyhome.idea.vim.newapi.vim
|
import com.maddyhome.idea.vim.newapi.vim
|
||||||
|
import com.maddyhome.idea.vim.state.VimStateMachine
|
||||||
|
import com.maddyhome.idea.vim.state.mode.Mode
|
||||||
import com.maddyhome.idea.vim.state.mode.inSelectMode
|
import com.maddyhome.idea.vim.state.mode.inSelectMode
|
||||||
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
|
||||||
@ -133,14 +135,14 @@ internal object VimListenerManager {
|
|||||||
fun turnOn() {
|
fun turnOn() {
|
||||||
GlobalListeners.enable()
|
GlobalListeners.enable()
|
||||||
EditorListeners.addAll()
|
EditorListeners.addAll()
|
||||||
correctorRequester.request()
|
check(correctorRequester.tryEmit(Unit))
|
||||||
check(keyCheckRequests.tryEmit(Unit))
|
check(keyCheckRequests.tryEmit(Unit))
|
||||||
}
|
}
|
||||||
|
|
||||||
fun turnOff() {
|
fun turnOff() {
|
||||||
GlobalListeners.disable()
|
GlobalListeners.disable()
|
||||||
EditorListeners.removeAll()
|
EditorListeners.removeAll()
|
||||||
correctorRequester.request()
|
check(correctorRequester.tryEmit(Unit))
|
||||||
}
|
}
|
||||||
|
|
||||||
object GlobalListeners {
|
object GlobalListeners {
|
||||||
@ -304,9 +306,19 @@ internal object VimListenerManager {
|
|||||||
class VimFileEditorManagerListener : FileEditorManagerListener {
|
class VimFileEditorManagerListener : FileEditorManagerListener {
|
||||||
override fun selectionChanged(event: FileEditorManagerEvent) {
|
override fun selectionChanged(event: FileEditorManagerEvent) {
|
||||||
if (VimPlugin.isNotEnabled()) return
|
if (VimPlugin.isNotEnabled()) return
|
||||||
|
|
||||||
|
val newEditor = event.newEditor
|
||||||
|
if (newEditor is TextEditor) {
|
||||||
|
val editor = newEditor.editor
|
||||||
|
if (editor.isInsertMode) {
|
||||||
|
VimStateMachine.getInstance(editor).mode = Mode.NORMAL()
|
||||||
|
KeyHandler.getInstance().reset(editor.vim)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
MotionGroup.fileEditorManagerSelectionChangedCallback(event)
|
MotionGroup.fileEditorManagerSelectionChangedCallback(event)
|
||||||
FileGroup.fileEditorManagerSelectionChangedCallback(event)
|
FileGroup.fileEditorManagerSelectionChangedCallback(event)
|
||||||
SearchGroup.fileEditorManagerSelectionChangedCallback(event)
|
VimPlugin.getSearch().fileEditorManagerSelectionChangedCallback(event)
|
||||||
OptionGroup.fileEditorManagerSelectionChangedCallback(event)
|
OptionGroup.fileEditorManagerSelectionChangedCallback(event)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -368,8 +380,6 @@ internal object VimListenerManager {
|
|||||||
|
|
||||||
event.editor.putUserData(openingEditorKey, OpeningEditor(openingEditor, owningEditorWindow, isPreview, canBeReused))
|
event.editor.putUserData(openingEditorKey, OpeningEditor(openingEditor, owningEditorWindow, isPreview, canBeReused))
|
||||||
}
|
}
|
||||||
|
|
||||||
VimStandalonePluginUpdateChecker.instance.pluginUsed()
|
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun editorReleased(event: EditorFactoryEvent) {
|
override fun editorReleased(event: EditorFactoryEvent) {
|
||||||
@ -462,11 +472,17 @@ internal object VimListenerManager {
|
|||||||
if (lineEnd == endOffset - 1) {
|
if (lineEnd == endOffset - 1) {
|
||||||
// When starting on an empty line and dragging vertically upwards onto
|
// When starting on an empty line and dragging vertically upwards onto
|
||||||
// another line, the selection should include the entirety of the empty line
|
// another line, the selection should include the entirety of the empty line
|
||||||
caret.setSelection(endOffset + 1, startOffset)
|
caret.setSelection(
|
||||||
|
ijVimEditor.coerceOffset(endOffset + 1).point,
|
||||||
|
ijVimEditor.coerceOffset(startOffset).point,
|
||||||
|
)
|
||||||
} else if (lineEnd == startOffset + 1 && startOffset == endOffset) {
|
} else if (lineEnd == startOffset + 1 && startOffset == endOffset) {
|
||||||
// When dragging left from EOL on a non-empty line, the selection
|
// When dragging left from EOL on a non-empty line, the selection
|
||||||
// should include the last character on the line
|
// should include the last character on the line
|
||||||
caret.setSelection(lineEnd, lineEnd - 1)
|
caret.setSelection(
|
||||||
|
ijVimEditor.coerceOffset(lineEnd).point,
|
||||||
|
ijVimEditor.coerceOffset(lineEnd - 1).point,
|
||||||
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
//endregion
|
//endregion
|
||||||
|
@ -1,23 +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.newapi
|
|
||||||
|
|
||||||
import com.maddyhome.idea.vim.api.VimActionsInitiator
|
|
||||||
import com.maddyhome.idea.vim.handler.ActionBeanClass
|
|
||||||
import com.maddyhome.idea.vim.handler.EditorActionHandlerBase
|
|
||||||
import org.jetbrains.annotations.ApiStatus
|
|
||||||
|
|
||||||
@Deprecated(message = "Please use CommandOrMotion annotation")
|
|
||||||
@ApiStatus.ScheduledForRemoval(inVersion = "2.9.0")
|
|
||||||
internal class IjVimActionsInitiator(val bean: ActionBeanClass) : VimActionsInitiator {
|
|
||||||
override fun getInstance(): EditorActionHandlerBase = bean.instance
|
|
||||||
}
|
|
||||||
|
|
||||||
internal val VimActionsInitiator.ij: ActionBeanClass
|
|
||||||
get() = (this as IjVimActionsInitiator).bean
|
|
181
src/main/java/com/maddyhome/idea/vim/newapi/IjVimSearchGroup.kt
Normal file
181
src/main/java/com/maddyhome/idea/vim/newapi/IjVimSearchGroup.kt
Normal file
@ -0,0 +1,181 @@
|
|||||||
|
/*
|
||||||
|
* 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.newapi
|
||||||
|
|
||||||
|
import com.intellij.openapi.application.ApplicationManager
|
||||||
|
import com.intellij.openapi.util.Ref
|
||||||
|
import com.maddyhome.idea.vim.VimPlugin
|
||||||
|
import com.maddyhome.idea.vim.api.Options
|
||||||
|
import com.maddyhome.idea.vim.api.VimCaret
|
||||||
|
import com.maddyhome.idea.vim.api.VimEditor
|
||||||
|
import com.maddyhome.idea.vim.api.VimSearchGroupBase
|
||||||
|
import com.maddyhome.idea.vim.api.globalOptions
|
||||||
|
import com.maddyhome.idea.vim.api.injector
|
||||||
|
import com.maddyhome.idea.vim.helper.MessageHelper
|
||||||
|
import com.maddyhome.idea.vim.helper.TestInputModel.Companion.getInstance
|
||||||
|
import com.maddyhome.idea.vim.helper.addSubstitutionConfirmationHighlight
|
||||||
|
import com.maddyhome.idea.vim.helper.highlightSearchResults
|
||||||
|
import com.maddyhome.idea.vim.helper.isCloseKeyStroke
|
||||||
|
import com.maddyhome.idea.vim.helper.shouldIgnoreCase
|
||||||
|
import com.maddyhome.idea.vim.helper.updateSearchHighlights
|
||||||
|
import com.maddyhome.idea.vim.options.GlobalOptionChangeListener
|
||||||
|
import com.maddyhome.idea.vim.ui.ModalEntry
|
||||||
|
import com.maddyhome.idea.vim.vimscript.model.expressions.Expression
|
||||||
|
import com.maddyhome.idea.vim.vimscript.model.functions.handlers.SubmatchFunctionHandler
|
||||||
|
import com.maddyhome.idea.vim.vimscript.parser.VimscriptParser.parseExpression
|
||||||
|
import org.jetbrains.annotations.TestOnly
|
||||||
|
import javax.swing.KeyStroke
|
||||||
|
|
||||||
|
public abstract class IjVimSearchGroup : VimSearchGroupBase() {
|
||||||
|
|
||||||
|
init {
|
||||||
|
// TODO: Investigate migrating these listeners to use the effective value change listener
|
||||||
|
// This would allow us to update the editor we're told to update, rather than looping over all projects and updating
|
||||||
|
// the highlights in that project's current document's open editors (see VIM-2779).
|
||||||
|
// However, we probably only want to update the editors associated with the current document, so maybe the whole
|
||||||
|
// code needs to be reworked. We're currently using the same update code for changes in the search term as well as
|
||||||
|
// changes in the search options.
|
||||||
|
VimPlugin.getOptionGroup().addGlobalOptionChangeListener(Options.hlsearch) {
|
||||||
|
resetSearchHighlight()
|
||||||
|
updateSearchHighlights(true)
|
||||||
|
}
|
||||||
|
|
||||||
|
val updateHighlightsIfVisible = GlobalOptionChangeListener {
|
||||||
|
if (showSearchHighlight) {
|
||||||
|
updateSearchHighlights(true)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
VimPlugin.getOptionGroup().addGlobalOptionChangeListener(Options.ignorecase, updateHighlightsIfVisible)
|
||||||
|
VimPlugin.getOptionGroup().addGlobalOptionChangeListener(Options.smartcase, updateHighlightsIfVisible)
|
||||||
|
}
|
||||||
|
|
||||||
|
private var showSearchHighlight: Boolean = injector.globalOptions().hlsearch
|
||||||
|
|
||||||
|
override fun highlightSearchLines(
|
||||||
|
editor: VimEditor,
|
||||||
|
startLine: Int,
|
||||||
|
endLine: Int,
|
||||||
|
) {
|
||||||
|
val pattern = getLastUsedPattern()
|
||||||
|
if (pattern != null) {
|
||||||
|
val results = injector.searchHelper.findAll(
|
||||||
|
editor, pattern, startLine, endLine,
|
||||||
|
shouldIgnoreCase(pattern, lastIgnoreSmartCase)
|
||||||
|
)
|
||||||
|
highlightSearchResults(editor.ij, pattern, results, -1)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun updateSearchHighlights(force: Boolean) {
|
||||||
|
updateSearchHighlights(getLastUsedPattern(), lastIgnoreSmartCase, showSearchHighlight, force)
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun resetIncsearchHighlights() {
|
||||||
|
updateSearchHighlights(getLastUsedPattern(), lastIgnoreSmartCase, showSearchHighlight, true)
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun confirmChoice(
|
||||||
|
editor: VimEditor,
|
||||||
|
match: String,
|
||||||
|
caret: VimCaret,
|
||||||
|
startOffset: Int,
|
||||||
|
): ReplaceConfirmationChoice {
|
||||||
|
val result: Ref<ReplaceConfirmationChoice> = Ref.create(ReplaceConfirmationChoice.QUIT)
|
||||||
|
val keyStrokeProcessor: Function1<KeyStroke, Boolean> = label@{ key: KeyStroke ->
|
||||||
|
val choice: ReplaceConfirmationChoice
|
||||||
|
val c = key.keyChar
|
||||||
|
choice = if (key.isCloseKeyStroke() || c == 'q') {
|
||||||
|
ReplaceConfirmationChoice.QUIT
|
||||||
|
} else if (c == 'y') {
|
||||||
|
ReplaceConfirmationChoice.SUBSTITUTE_THIS
|
||||||
|
} else if (c == 'l') {
|
||||||
|
ReplaceConfirmationChoice.SUBSTITUTE_LAST
|
||||||
|
} else if (c == 'n') {
|
||||||
|
ReplaceConfirmationChoice.SKIP
|
||||||
|
} else if (c == 'a') {
|
||||||
|
ReplaceConfirmationChoice.SUBSTITUTE_ALL
|
||||||
|
} else {
|
||||||
|
return@label true
|
||||||
|
}
|
||||||
|
// TODO: Handle <C-E> and <C-Y>
|
||||||
|
result.set(choice)
|
||||||
|
false
|
||||||
|
}
|
||||||
|
if (ApplicationManager.getApplication().isUnitTestMode) {
|
||||||
|
caret.moveToOffset(startOffset)
|
||||||
|
val inputModel = getInstance(editor.ij)
|
||||||
|
var key = inputModel.nextKeyStroke()
|
||||||
|
while (key != null) {
|
||||||
|
if (!keyStrokeProcessor.invoke(key)) {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
key = inputModel.nextKeyStroke()
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// XXX: The Ex entry panel is used only for UI here, its logic might be inappropriate for this method
|
||||||
|
val exEntryPanel: com.maddyhome.idea.vim.ui.ex.ExEntryPanel =
|
||||||
|
com.maddyhome.idea.vim.ui.ex.ExEntryPanel.getInstanceWithoutShortcuts()
|
||||||
|
val context = injector.executionContextManager.onEditor(editor, null)
|
||||||
|
exEntryPanel.activate(
|
||||||
|
editor.ij,
|
||||||
|
(context as IjEditorExecutionContext).context,
|
||||||
|
MessageHelper.message("replace.with.0", match),
|
||||||
|
"",
|
||||||
|
1
|
||||||
|
)
|
||||||
|
caret.moveToOffset(startOffset)
|
||||||
|
ModalEntry.activate(editor, keyStrokeProcessor)
|
||||||
|
exEntryPanel.deactivate(true, false)
|
||||||
|
}
|
||||||
|
return result.get()
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun parseVimScriptExpression(expressionString: String): Expression? {
|
||||||
|
return parseExpression(expressionString)
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun addSubstitutionConfirmationHighlight(editor: VimEditor, startOffset: Int, endOffset: Int) {
|
||||||
|
val hl = addSubstitutionConfirmationHighlight(
|
||||||
|
(editor as IjVimEditor).editor,
|
||||||
|
startOffset,
|
||||||
|
endOffset
|
||||||
|
)
|
||||||
|
editor.editor.markupModel.removeHighlighter(hl)
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun setLatestMatch(match: String) {
|
||||||
|
SubmatchFunctionHandler.getInstance().latestMatch = match
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun replaceString(
|
||||||
|
editor: VimEditor,
|
||||||
|
startOffset: Int,
|
||||||
|
endOffset: Int,
|
||||||
|
newString: String,
|
||||||
|
) {
|
||||||
|
ApplicationManager.getApplication().runWriteAction {
|
||||||
|
(editor as IjVimEditor).editor.document.replaceString(startOffset, endOffset, newString)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@TestOnly
|
||||||
|
override fun resetState() {
|
||||||
|
super.resetState()
|
||||||
|
showSearchHighlight = injector.globalOptions().hlsearch
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun resetSearchHighlight() {
|
||||||
|
showSearchHighlight = injector.globalOptions().hlsearch
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun clearSearchHighlight() {
|
||||||
|
showSearchHighlight = false
|
||||||
|
updateSearchHighlights(false)
|
||||||
|
}
|
||||||
|
}
|
@ -9,49 +9,146 @@
|
|||||||
package com.maddyhome.idea.vim.newapi
|
package com.maddyhome.idea.vim.newapi
|
||||||
|
|
||||||
import com.intellij.openapi.components.Service
|
import com.intellij.openapi.components.Service
|
||||||
|
import com.intellij.openapi.diagnostic.Logger
|
||||||
import com.maddyhome.idea.vim.api.ImmutableVimCaret
|
import com.maddyhome.idea.vim.api.ImmutableVimCaret
|
||||||
import com.maddyhome.idea.vim.api.VimEditor
|
import com.maddyhome.idea.vim.api.VimEditor
|
||||||
import com.maddyhome.idea.vim.api.VimSearchHelperBase
|
import com.maddyhome.idea.vim.api.VimSearchHelperBase
|
||||||
|
import com.maddyhome.idea.vim.api.anyNonWhitespace
|
||||||
|
import com.maddyhome.idea.vim.api.getLineEndOffset
|
||||||
|
import com.maddyhome.idea.vim.api.getLineStartForOffset
|
||||||
|
import com.maddyhome.idea.vim.api.injector
|
||||||
|
import com.maddyhome.idea.vim.api.normalizeOffset
|
||||||
|
import com.maddyhome.idea.vim.common.Direction
|
||||||
import com.maddyhome.idea.vim.common.TextRange
|
import com.maddyhome.idea.vim.common.TextRange
|
||||||
|
import com.maddyhome.idea.vim.helper.CharacterHelper
|
||||||
|
import com.maddyhome.idea.vim.helper.CharacterHelper.charType
|
||||||
|
import com.maddyhome.idea.vim.helper.PsiHelper
|
||||||
import com.maddyhome.idea.vim.helper.SearchHelper
|
import com.maddyhome.idea.vim.helper.SearchHelper
|
||||||
import com.maddyhome.idea.vim.helper.SearchOptions
|
import com.maddyhome.idea.vim.helper.SearchOptions
|
||||||
|
import com.maddyhome.idea.vim.helper.checkInString
|
||||||
|
import com.maddyhome.idea.vim.helper.fileSize
|
||||||
|
import com.maddyhome.idea.vim.state.VimStateMachine.Companion.getInstance
|
||||||
|
import com.maddyhome.idea.vim.state.mode.Mode.VISUAL
|
||||||
|
import it.unimi.dsi.fastutil.ints.IntComparator
|
||||||
|
import it.unimi.dsi.fastutil.ints.IntComparators
|
||||||
import java.util.*
|
import java.util.*
|
||||||
|
import java.util.function.Function
|
||||||
|
import java.util.regex.Pattern
|
||||||
|
import kotlin.math.abs
|
||||||
|
import kotlin.math.max
|
||||||
|
|
||||||
@Service
|
@Service
|
||||||
internal class IjVimSearchHelper : VimSearchHelperBase() {
|
internal class IjVimSearchHelper : VimSearchHelperBase() {
|
||||||
override fun findSection(editor: VimEditor, caret: ImmutableVimCaret, type: Char, dir: Int, count: Int): Int {
|
|
||||||
return SearchHelper.findSection(
|
companion object {
|
||||||
(editor as IjVimEditor).editor,
|
private const val BLOCK_CHARS = "{}()[]<>"
|
||||||
(caret as IjVimCaret).caret,
|
private val logger = Logger.getInstance(IjVimSearchHelper::class.java.name)
|
||||||
type,
|
}
|
||||||
dir,
|
override fun findSection(
|
||||||
count,
|
editor: VimEditor,
|
||||||
)
|
caret: ImmutableVimCaret,
|
||||||
|
type: Char,
|
||||||
|
direction: Int,
|
||||||
|
count: Int,
|
||||||
|
)
|
||||||
|
: Int {
|
||||||
|
val documentText: CharSequence = editor.ij.document.charsSequence
|
||||||
|
var currentLine: Int = caret.ij.logicalPosition.line + direction
|
||||||
|
var resultOffset = -1
|
||||||
|
var remainingTargets = count
|
||||||
|
|
||||||
|
while (currentLine in 1 until editor.lineCount() && remainingTargets > 0) {
|
||||||
|
val lineStartOffset = editor.getLineStartOffset(currentLine)
|
||||||
|
if (lineStartOffset < documentText.length) {
|
||||||
|
val currentChar = documentText[lineStartOffset]
|
||||||
|
if (currentChar == type || currentChar == '\u000C') {
|
||||||
|
resultOffset = lineStartOffset
|
||||||
|
remainingTargets--
|
||||||
|
}
|
||||||
|
}
|
||||||
|
currentLine += direction
|
||||||
|
}
|
||||||
|
|
||||||
|
if (resultOffset == -1) {
|
||||||
|
resultOffset = if (direction < 0) 0 else documentText.length - 1
|
||||||
|
}
|
||||||
|
|
||||||
|
return resultOffset
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun findMethodEnd(editor: VimEditor, caret: ImmutableVimCaret, count: Int): Int {
|
override fun findMethodEnd(editor: VimEditor, caret: ImmutableVimCaret, count: Int): Int {
|
||||||
return SearchHelper.findMethodEnd(
|
return PsiHelper.findMethodEnd(editor.ij, caret.ij.offset, count)
|
||||||
(editor as IjVimEditor).editor,
|
|
||||||
(caret as IjVimCaret).caret,
|
|
||||||
count,
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun findMethodStart(editor: VimEditor, caret: ImmutableVimCaret, count: Int): Int {
|
override fun findMethodStart(editor: VimEditor, caret: ImmutableVimCaret, count: Int): Int {
|
||||||
return SearchHelper.findMethodStart(
|
return PsiHelper.findMethodStart(editor.ij, caret.ij.offset, count)
|
||||||
(editor as IjVimEditor).editor,
|
|
||||||
(caret as IjVimCaret).caret,
|
|
||||||
count,
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun findUnmatchedBlock(editor: VimEditor, caret: ImmutableVimCaret, type: Char, count: Int): Int {
|
override fun findUnmatchedBlock(editor: VimEditor, caret: ImmutableVimCaret, type: Char, count: Int): Int {
|
||||||
return SearchHelper.findUnmatchedBlock(
|
val chars: CharSequence = editor.ij.document.charsSequence
|
||||||
(editor as IjVimEditor).editor,
|
var pos: Int = caret.ij.offset
|
||||||
(caret as IjVimCaret).caret,
|
val loc = BLOCK_CHARS.indexOf(type)
|
||||||
type,
|
// What direction should we go now (-1 is backward, 1 is forward)
|
||||||
count,
|
val dir = if (loc % 2 == 0) Direction.BACKWARDS else Direction.FORWARDS
|
||||||
)
|
// Which character did we find and which should we now search for
|
||||||
|
val match = BLOCK_CHARS[loc]
|
||||||
|
val found = BLOCK_CHARS[loc - dir.toInt()]
|
||||||
|
|
||||||
|
if (pos < chars.length && chars[pos] == type) {
|
||||||
|
pos += dir.toInt()
|
||||||
|
}
|
||||||
|
return findBlockLocation(chars, found, match, dir, pos, count)
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun findBlockLocation(
|
||||||
|
chars: CharSequence,
|
||||||
|
found: Char,
|
||||||
|
match: Char,
|
||||||
|
dir: Direction,
|
||||||
|
pos: Int,
|
||||||
|
cnt: Int,
|
||||||
|
): Int {
|
||||||
|
var position = pos
|
||||||
|
var count = cnt
|
||||||
|
var res = -1
|
||||||
|
val initialPos = position
|
||||||
|
val initialInString = checkInString(chars, position, true)
|
||||||
|
val inCheckPosF =
|
||||||
|
Function { x: Int -> if (dir === Direction.BACKWARDS && x > 0) x - 1 else x + 1 }
|
||||||
|
val inCheckPos = inCheckPosF.apply(position)
|
||||||
|
var inString = checkInString(chars, inCheckPos, true)
|
||||||
|
var inChar = checkInString(chars, inCheckPos, false)
|
||||||
|
var stack = 0
|
||||||
|
// Search to start or end of file, as appropriate
|
||||||
|
val charsToSearch: Set<Char> = HashSet(listOf('\'', '"', '\n', match, found))
|
||||||
|
while (position >= 0 && position < chars.length && count > 0) {
|
||||||
|
val (c, second) = SearchHelper.findPositionOfFirstCharacter(chars, position, charsToSearch, true, dir) ?: return -1
|
||||||
|
position = second
|
||||||
|
// If we found a match and we're not in a string...
|
||||||
|
if (c == match && (!inString) && !inChar) {
|
||||||
|
// We found our match
|
||||||
|
if (stack == 0) {
|
||||||
|
res = position
|
||||||
|
count--
|
||||||
|
} else {
|
||||||
|
stack--
|
||||||
|
}
|
||||||
|
} else if (c == '\n') {
|
||||||
|
inString = false
|
||||||
|
inChar = false
|
||||||
|
} else if (position != initialPos) {
|
||||||
|
// We found another character like our original - belongs to another pair
|
||||||
|
if (!inString && !inChar && c == found) {
|
||||||
|
stack++
|
||||||
|
} else if (!inChar) {
|
||||||
|
inString = checkInString(chars, inCheckPosF.apply(position), true)
|
||||||
|
} else if (!inString) {
|
||||||
|
inChar = checkInString(chars, inCheckPosF.apply(position), false)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
position += dir.toInt()
|
||||||
|
}
|
||||||
|
return res
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun findPattern(
|
override fun findPattern(
|
||||||
@ -61,11 +158,44 @@ internal class IjVimSearchHelper : VimSearchHelperBase() {
|
|||||||
count: Int,
|
count: Int,
|
||||||
searchOptions: EnumSet<SearchOptions>?,
|
searchOptions: EnumSet<SearchOptions>?,
|
||||||
): TextRange? {
|
): TextRange? {
|
||||||
return SearchHelper.findPattern(editor.ij, pattern, startOffset, count, searchOptions)
|
return if (injector.globalIjOptions().useNewRegex) super.findPattern(editor, pattern, startOffset, count, searchOptions)
|
||||||
|
else SearchHelper.findPattern(editor.ij, pattern, startOffset, count, searchOptions)
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun findAll(
|
||||||
|
editor: VimEditor,
|
||||||
|
pattern: String,
|
||||||
|
startLine: Int,
|
||||||
|
endLine: Int,
|
||||||
|
ignoreCase: Boolean,
|
||||||
|
): List<TextRange> {
|
||||||
|
return if (injector.globalIjOptions().useNewRegex) super.findAll(editor, pattern, startLine, endLine, ignoreCase)
|
||||||
|
else SearchHelper.findAll(editor.ij, pattern, startLine, endLine, ignoreCase)
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun findNextCharacterOnLine(editor: VimEditor, caret: ImmutableVimCaret, count: Int, ch: Char): Int {
|
override fun findNextCharacterOnLine(editor: VimEditor, caret: ImmutableVimCaret, count: Int, ch: Char): Int {
|
||||||
return SearchHelper.findNextCharacterOnLine(editor.ij, caret.ij, count, ch)
|
val line: Int = caret.ij.logicalPosition.line
|
||||||
|
val start = editor.getLineStartOffset(line)
|
||||||
|
val end = editor.getLineEndOffset(line, true)
|
||||||
|
val chars: CharSequence = editor.ij.document.charsSequence
|
||||||
|
var found = 0
|
||||||
|
val step = if (count >= 0) 1 else -1
|
||||||
|
var pos: Int = caret.ij.offset + step
|
||||||
|
while (pos in start until end && pos < chars.length) {
|
||||||
|
if (chars[pos] == ch) {
|
||||||
|
found++
|
||||||
|
if (found == abs(count)) {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
pos += step
|
||||||
|
}
|
||||||
|
|
||||||
|
return if (found == abs(count)) {
|
||||||
|
pos
|
||||||
|
} else {
|
||||||
|
-1
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun findWordUnderCursor(
|
override fun findWordUnderCursor(
|
||||||
@ -77,11 +207,347 @@ internal class IjVimSearchHelper : VimSearchHelperBase() {
|
|||||||
isBig: Boolean,
|
isBig: Boolean,
|
||||||
hasSelection: Boolean,
|
hasSelection: Boolean,
|
||||||
): TextRange {
|
): TextRange {
|
||||||
return SearchHelper.findWordUnderCursor(editor.ij, caret.ij, count, dir, isOuter, isBig, hasSelection)
|
if (logger.isDebugEnabled) {
|
||||||
|
logger.debug("count=$count")
|
||||||
|
logger.debug("dir=$dir")
|
||||||
|
logger.debug("isOuter=$isOuter")
|
||||||
|
logger.debug("isBig=$isBig")
|
||||||
|
logger.debug("hasSelection=$hasSelection")
|
||||||
|
}
|
||||||
|
|
||||||
|
val chars: CharSequence = editor.ij.document.charsSequence
|
||||||
|
//int min = EditorHelper.getLineStartOffset(editor, EditorHelper.getCurrentLogicalLine(editor));
|
||||||
|
//int max = EditorHelper.getLineEndOffset(editor, EditorHelper.getCurrentLogicalLine(editor), true);
|
||||||
|
val min = 0
|
||||||
|
val max: Int = editor.ij.fileSize
|
||||||
|
if (max == 0) return TextRange(0, 0)
|
||||||
|
|
||||||
|
if (logger.isDebugEnabled) {
|
||||||
|
logger.debug("min=$min")
|
||||||
|
logger.debug("max=$max")
|
||||||
|
}
|
||||||
|
|
||||||
|
val pos: Int = caret.ij.offset
|
||||||
|
if (chars.length <= pos) return TextRange(chars.length - 1, chars.length - 1)
|
||||||
|
|
||||||
|
val startSpace = charType(editor, chars[pos], isBig) === CharacterHelper.CharacterType.WHITESPACE
|
||||||
|
// Find word start
|
||||||
|
val onWordStart = pos == min ||
|
||||||
|
charType(editor, chars[pos - 1], isBig) !==
|
||||||
|
charType(editor, chars[pos], isBig)
|
||||||
|
var start = pos
|
||||||
|
|
||||||
|
if (logger.isDebugEnabled) {
|
||||||
|
logger.debug("pos=$pos")
|
||||||
|
logger.debug("onWordStart=$onWordStart")
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!onWordStart && !(startSpace && isOuter) || hasSelection || count > 1 && dir == -1) {
|
||||||
|
start = if (dir == 1) {
|
||||||
|
findNextWord(editor, pos, -1, isBig, !isOuter)
|
||||||
|
} else {
|
||||||
|
findNextWord(
|
||||||
|
editor,
|
||||||
|
pos,
|
||||||
|
-(count - if (onWordStart && !hasSelection) 1 else 0),
|
||||||
|
isBig,
|
||||||
|
!isOuter
|
||||||
|
)
|
||||||
|
}
|
||||||
|
start = editor.normalizeOffset(start, false)
|
||||||
|
}
|
||||||
|
|
||||||
|
if (logger.isDebugEnabled) logger.debug("start=$start")
|
||||||
|
|
||||||
|
// Find word end
|
||||||
|
|
||||||
|
// Find word end
|
||||||
|
val onWordEnd = pos >= max - 1 ||
|
||||||
|
charType(editor, chars[pos + 1], isBig) !==
|
||||||
|
charType(editor, chars[pos], isBig)
|
||||||
|
|
||||||
|
if (logger.isDebugEnabled) logger.debug("onWordEnd=$onWordEnd")
|
||||||
|
|
||||||
|
var end = pos
|
||||||
|
if (!onWordEnd || hasSelection || count > 1 && dir == 1 || startSpace && isOuter) {
|
||||||
|
end = if (dir == 1) {
|
||||||
|
val c = count - if (onWordEnd && !hasSelection && (!(startSpace && isOuter) || startSpace && !isOuter)) 1 else 0
|
||||||
|
findNextWordEnd(editor, pos, c, isBig, !isOuter)
|
||||||
|
} else {
|
||||||
|
findNextWordEnd(editor, pos, 1, isBig, !isOuter)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (logger.isDebugEnabled) logger.debug("end=$end")
|
||||||
|
|
||||||
|
var goBack = startSpace && !hasSelection || !startSpace && hasSelection && !onWordStart
|
||||||
|
if (dir == 1 && isOuter) {
|
||||||
|
var firstEnd = end
|
||||||
|
if (count > 1) {
|
||||||
|
firstEnd = findNextWordEnd(editor, pos, 1, isBig, false)
|
||||||
|
}
|
||||||
|
if (firstEnd < max - 1) {
|
||||||
|
if (charType(editor, chars[firstEnd + 1], false) !== CharacterHelper.CharacterType.WHITESPACE) {
|
||||||
|
goBack = true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (dir == -1 && isOuter && startSpace) {
|
||||||
|
if (pos > min) {
|
||||||
|
if (charType(editor, chars[pos - 1], false) !== CharacterHelper.CharacterType.WHITESPACE) {
|
||||||
|
goBack = true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
var goForward = dir == 1 && isOuter && (!startSpace && !onWordEnd || startSpace && onWordEnd && hasSelection)
|
||||||
|
if (!goForward && dir == 1 && isOuter) {
|
||||||
|
var firstEnd = end
|
||||||
|
if (count > 1) {
|
||||||
|
firstEnd = findNextWordEnd(editor, pos, 1, isBig, false)
|
||||||
|
}
|
||||||
|
if (firstEnd < max - 1) {
|
||||||
|
if (charType(editor, chars[firstEnd + 1], false) !== CharacterHelper.CharacterType.WHITESPACE) {
|
||||||
|
goForward = true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (!goForward && dir == 1 && isOuter && !startSpace && !hasSelection) {
|
||||||
|
if (end < max - 1) {
|
||||||
|
if (charType(editor, chars[end + 1], !isBig) !==
|
||||||
|
charType(editor, chars[end], !isBig)
|
||||||
|
) {
|
||||||
|
goForward = true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (logger.isDebugEnabled) {
|
||||||
|
logger.debug("goBack=$goBack")
|
||||||
|
logger.debug("goForward=$goForward")
|
||||||
|
}
|
||||||
|
|
||||||
|
if (goForward) {
|
||||||
|
if (editor.anyNonWhitespace(end, 1)) {
|
||||||
|
while (end + 1 < max &&
|
||||||
|
charType(editor, chars[end + 1], false) === CharacterHelper.CharacterType.WHITESPACE
|
||||||
|
) {
|
||||||
|
end++
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (goBack) {
|
||||||
|
if (editor.anyNonWhitespace(start, -1)) {
|
||||||
|
while (start > min &&
|
||||||
|
charType(editor, chars[start - 1], false) === CharacterHelper.CharacterType.WHITESPACE
|
||||||
|
) {
|
||||||
|
start--
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (logger.isDebugEnabled) {
|
||||||
|
logger.debug("start=$start")
|
||||||
|
logger.debug("end=$end")
|
||||||
|
}
|
||||||
|
|
||||||
|
// End offset is exclusive
|
||||||
|
return TextRange(start, end + 1)
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun findBlockTagRange(editor: VimEditor, caret: ImmutableVimCaret, count: Int, isOuter: Boolean): TextRange? {
|
override fun findBlockTagRange(editor: VimEditor, caret: ImmutableVimCaret, count: Int, isOuter: Boolean): TextRange? {
|
||||||
return SearchHelper.findBlockTagRange(editor.ij, caret.ij, count, isOuter)
|
var counter = count
|
||||||
|
var isOuterVariable = isOuter
|
||||||
|
val position: Int = caret.ij.offset
|
||||||
|
val sequence: CharSequence = editor.ij.document.charsSequence
|
||||||
|
|
||||||
|
val selectionStart: Int = caret.ij.selectionStart
|
||||||
|
val selectionEnd: Int = caret.ij.selectionEnd
|
||||||
|
|
||||||
|
val isRangeSelection = selectionEnd - selectionStart > 1
|
||||||
|
|
||||||
|
var searchStartPosition: Int
|
||||||
|
searchStartPosition = if (!isRangeSelection) {
|
||||||
|
val line: Int = caret.ij.logicalPosition.line
|
||||||
|
val lineBegin: Int = editor.ij.document.getLineStartOffset(line)
|
||||||
|
ignoreWhitespaceAtLineStart(sequence, lineBegin, position)
|
||||||
|
} else {
|
||||||
|
selectionEnd
|
||||||
|
}
|
||||||
|
|
||||||
|
if (isInHTMLTag(sequence, searchStartPosition, false)) {
|
||||||
|
// caret is inside opening tag. Move to closing '>'.
|
||||||
|
while (searchStartPosition < sequence.length && sequence[searchStartPosition] != '>') {
|
||||||
|
searchStartPosition++
|
||||||
|
}
|
||||||
|
} else if (isInHTMLTag(sequence, searchStartPosition, true)) {
|
||||||
|
// caret is inside closing tag. Move to starting '<'.
|
||||||
|
while (searchStartPosition > 0 && sequence[searchStartPosition] != '<') {
|
||||||
|
searchStartPosition--
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
while (true) {
|
||||||
|
val (closingTagTextRange, tagName) = findUnmatchedClosingTag(sequence, searchStartPosition, counter)
|
||||||
|
?: return null
|
||||||
|
val openingTag = findUnmatchedOpeningTag(sequence, closingTagTextRange.startOffset, tagName)
|
||||||
|
?: return null
|
||||||
|
if (isRangeSelection && openingTag.endOffset - 1 >= selectionStart) {
|
||||||
|
// If there was already some text selected and the new selection would not extend further, we try again
|
||||||
|
searchStartPosition = closingTagTextRange.endOffset
|
||||||
|
counter = 1
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
var selectionEndWithoutNewline = selectionEnd
|
||||||
|
while (selectionEndWithoutNewline < sequence.length && sequence[selectionEndWithoutNewline] == '\n') {
|
||||||
|
selectionEndWithoutNewline++
|
||||||
|
}
|
||||||
|
val mode = getInstance(editor).mode
|
||||||
|
if (mode is VISUAL) {
|
||||||
|
if (closingTagTextRange.startOffset == selectionEndWithoutNewline &&
|
||||||
|
openingTag.endOffset == selectionStart
|
||||||
|
) {
|
||||||
|
// Special case: if the inner tag is already selected we should like isOuter is active
|
||||||
|
// Note that we need to ignore newlines, because their selection is lost between multiple "it" invocations
|
||||||
|
isOuterVariable = true
|
||||||
|
} else if (openingTag.endOffset == closingTagTextRange.startOffset &&
|
||||||
|
selectionStart == openingTag.endOffset
|
||||||
|
) {
|
||||||
|
// Special case: for an empty tag pair (e.g. <a></a>) the whole tag is selected if the caret is in the middle.
|
||||||
|
isOuterVariable = true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return if (isOuterVariable) {
|
||||||
|
TextRange(openingTag.startOffset, closingTagTextRange.endOffset)
|
||||||
|
} else {
|
||||||
|
TextRange(openingTag.endOffset, closingTagTextRange.startOffset)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* returns new position which ignore whitespaces at beginning of the line
|
||||||
|
*/
|
||||||
|
private fun ignoreWhitespaceAtLineStart(seq: CharSequence, lineStart: Int, pos: Int): Int {
|
||||||
|
var position = pos
|
||||||
|
if (seq.subSequence(lineStart, position).chars().allMatch { codePoint: Int ->
|
||||||
|
Character.isWhitespace(
|
||||||
|
codePoint
|
||||||
|
)
|
||||||
|
}) {
|
||||||
|
while (position < seq.length && seq[position] != '\n' && Character.isWhitespace(seq[position])) {
|
||||||
|
position++
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return position
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns true if there is a html at the given position. Ignores tags with a trailing slash like <aaa></aaa>.
|
||||||
|
*/
|
||||||
|
private fun isInHTMLTag(sequence: CharSequence, position: Int, isEndtag: Boolean): Boolean {
|
||||||
|
var openingBracket = -1
|
||||||
|
run {
|
||||||
|
var i = position
|
||||||
|
while (i >= 0 && i < sequence.length) {
|
||||||
|
if (sequence[i] == '<') {
|
||||||
|
openingBracket = i
|
||||||
|
break
|
||||||
|
}
|
||||||
|
if (sequence[i] == '>' && i != position) {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
i--
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (openingBracket == -1) {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
val hasSlashAfterOpening = openingBracket + 1 < sequence.length && sequence[openingBracket + 1] == '/'
|
||||||
|
if (isEndtag && !hasSlashAfterOpening || !isEndtag && hasSlashAfterOpening) {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
var closingBracket = -1
|
||||||
|
for (i in openingBracket until sequence.length) {
|
||||||
|
if (sequence[i] == '>') {
|
||||||
|
closingBracket = i
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return closingBracket != -1 && sequence[closingBracket - 1] != '/'
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun findUnmatchedOpeningTag(
|
||||||
|
sequence: CharSequence,
|
||||||
|
position: Int,
|
||||||
|
tagName: String,
|
||||||
|
): TextRange? {
|
||||||
|
val quotedTagName = Pattern.quote(tagName)
|
||||||
|
val patternString = ("(</%s>)" // match closing tags
|
||||||
|
+
|
||||||
|
"|(<%s" // or opening tags starting with tagName
|
||||||
|
+
|
||||||
|
"(\\s([^>]*" // After at least one whitespace there might be additional text in the tag. E.g. <html lang="en">
|
||||||
|
+
|
||||||
|
"[^/])?)?>)") // Slash is not allowed as last character (this would be a self closing tag).
|
||||||
|
val tagPattern =
|
||||||
|
Pattern.compile(String.format(patternString, quotedTagName, quotedTagName), Pattern.CASE_INSENSITIVE)
|
||||||
|
val matcher = tagPattern.matcher(sequence.subSequence(0, position + 1))
|
||||||
|
val openTags: Deque<TextRange> = ArrayDeque()
|
||||||
|
while (matcher.find()) {
|
||||||
|
val match = TextRange(matcher.start(), matcher.end())
|
||||||
|
if (sequence[matcher.start() + 1] == '/') {
|
||||||
|
if (!openTags.isEmpty()) {
|
||||||
|
openTags.pop()
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
openTags.push(match)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return if (openTags.isEmpty()) {
|
||||||
|
null
|
||||||
|
} else {
|
||||||
|
openTags.pop()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun findUnmatchedClosingTag(
|
||||||
|
sequence: CharSequence,
|
||||||
|
position: Int,
|
||||||
|
count: Int,
|
||||||
|
): Pair<TextRange, String>? {
|
||||||
|
// The tag name may contain any characters except slashes, whitespace and '>'
|
||||||
|
var counter = count
|
||||||
|
val tagNamePattern = "([^/\\s>]+)"
|
||||||
|
// An opening tag consists of '<' followed by a tag name, optionally some additional text after whitespace and a '>'
|
||||||
|
val openingTagPattern = String.format("<%s(?:\\s[^>]*)?>", tagNamePattern)
|
||||||
|
val closingTagPattern = String.format("</%s>", tagNamePattern)
|
||||||
|
val tagPattern = Pattern.compile(String.format("(?:%s)|(?:%s)", openingTagPattern, closingTagPattern))
|
||||||
|
val matcher = tagPattern.matcher(sequence.subSequence(position, sequence.length))
|
||||||
|
val openTags: Deque<String> = ArrayDeque()
|
||||||
|
while (matcher.find()) {
|
||||||
|
val isClosingTag = matcher.group(1) == null
|
||||||
|
if (isClosingTag) {
|
||||||
|
val tagName = matcher.group(2)
|
||||||
|
// Ignore unmatched open tags. Either the file is malformed or it might be a tag like <br> that does not need to be closed.
|
||||||
|
while (!openTags.isEmpty() && !openTags.peek().equals(tagName, ignoreCase = true)) {
|
||||||
|
openTags.pop()
|
||||||
|
}
|
||||||
|
if (openTags.isEmpty()) {
|
||||||
|
if (counter <= 1) {
|
||||||
|
return Pair(TextRange(position + matcher.start(), position + matcher.end()), tagName)
|
||||||
|
} else {
|
||||||
|
counter--
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
openTags.pop()
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
val tagName = matcher.group(1)
|
||||||
|
openTags.push(tagName)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return null
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun findBlockRange(
|
override fun findBlockRange(
|
||||||
@ -91,6 +557,160 @@ internal class IjVimSearchHelper : VimSearchHelperBase() {
|
|||||||
count: Int,
|
count: Int,
|
||||||
isOuter: Boolean,
|
isOuter: Boolean,
|
||||||
): TextRange? {
|
): TextRange? {
|
||||||
return SearchHelper.findBlockRange(editor.ij, caret.ij, type, count, isOuter)
|
val chars: CharSequence = editor.ij.document.charsSequence
|
||||||
|
var pos: Int = caret.ij.offset
|
||||||
|
var start: Int = caret.ij.selectionStart
|
||||||
|
var end: Int = caret.ij.selectionEnd
|
||||||
|
|
||||||
|
val loc = BLOCK_CHARS.indexOf(type)
|
||||||
|
val close = BLOCK_CHARS[loc + 1]
|
||||||
|
|
||||||
|
// extend the range for blank line after type and before close, as they are excluded when inner match
|
||||||
|
if (!isOuter) {
|
||||||
|
if (start > 1 && chars[start - 2] == type && chars[start - 1] == '\n') {
|
||||||
|
start--
|
||||||
|
}
|
||||||
|
if (end < chars.length && chars[end] == '\n') {
|
||||||
|
var isSingleLineAllWhiteSpaceUntilClose = false
|
||||||
|
var countWhiteSpaceCharacter = 1
|
||||||
|
while (end + countWhiteSpaceCharacter < chars.length) {
|
||||||
|
if (Character.isWhitespace(chars[end + countWhiteSpaceCharacter]) &&
|
||||||
|
chars[end + countWhiteSpaceCharacter] != '\n'
|
||||||
|
) {
|
||||||
|
countWhiteSpaceCharacter++
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
if (chars[end + countWhiteSpaceCharacter] == close) {
|
||||||
|
isSingleLineAllWhiteSpaceUntilClose = true
|
||||||
|
}
|
||||||
|
break
|
||||||
|
}
|
||||||
|
if (isSingleLineAllWhiteSpaceUntilClose) {
|
||||||
|
end += countWhiteSpaceCharacter
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
var rangeSelection = end - start > 1
|
||||||
|
if (rangeSelection && start == 0) // early return not only for optimization
|
||||||
|
{
|
||||||
|
return null // but also not to break the interval semantic on this edge case (see below)
|
||||||
|
}
|
||||||
|
|
||||||
|
/* In case of successive inner selection. We want to break out of
|
||||||
|
* the block delimiter of the current inner selection.
|
||||||
|
* In other terms, for the rest of the algorithm, a previous inner selection of a block
|
||||||
|
* if equivalent to an outer one. */
|
||||||
|
|
||||||
|
/* In case of successive inner selection. We want to break out of
|
||||||
|
* the block delimiter of the current inner selection.
|
||||||
|
* In other terms, for the rest of the algorithm, a previous inner selection of a block
|
||||||
|
* if equivalent to an outer one. */if (!isOuter && start - 1 >= 0 && type == chars[start - 1] && end < chars.length && close == chars[end]) {
|
||||||
|
start -= 1
|
||||||
|
pos = start
|
||||||
|
rangeSelection = true
|
||||||
|
}
|
||||||
|
|
||||||
|
/* when one char is selected, we want to find the enclosing block of (start,end]
|
||||||
|
* although when a range of characters is selected, we want the enclosing block of [start, end]
|
||||||
|
* shifting the position allow to express which kind of interval we work on */
|
||||||
|
|
||||||
|
/* when one char is selected, we want to find the enclosing block of (start,end]
|
||||||
|
* although when a range of characters is selected, we want the enclosing block of [start, end]
|
||||||
|
* shifting the position allow to express which kind of interval we work on */if (rangeSelection) pos =
|
||||||
|
max(0.0, (start - 1).toDouble()).toInt()
|
||||||
|
|
||||||
|
val initialPosIsInString = checkInString(chars, pos, true)
|
||||||
|
|
||||||
|
var bstart = -1
|
||||||
|
var bend = -1
|
||||||
|
|
||||||
|
var startPosInStringFound = false
|
||||||
|
|
||||||
|
if (initialPosIsInString) {
|
||||||
|
val quoteRange = injector.searchHelper
|
||||||
|
.findBlockQuoteInLineRange(editor, caret, '"', false)
|
||||||
|
if (quoteRange != null) {
|
||||||
|
val startOffset = quoteRange.startOffset
|
||||||
|
val endOffset = quoteRange.endOffset
|
||||||
|
val subSequence = chars.subSequence(startOffset, endOffset)
|
||||||
|
val inQuotePos = pos - startOffset
|
||||||
|
var inQuoteStart =
|
||||||
|
findBlockLocation(subSequence, close, type, Direction.BACKWARDS, inQuotePos, count)
|
||||||
|
if (inQuoteStart == -1) {
|
||||||
|
inQuoteStart =
|
||||||
|
findBlockLocation(subSequence, close, type, Direction.FORWARDS, inQuotePos, count)
|
||||||
|
}
|
||||||
|
if (inQuoteStart != -1) {
|
||||||
|
startPosInStringFound = true
|
||||||
|
val inQuoteEnd =
|
||||||
|
findBlockLocation(subSequence, type, close, Direction.FORWARDS, inQuoteStart, 1)
|
||||||
|
if (inQuoteEnd != -1) {
|
||||||
|
bstart = inQuoteStart + startOffset
|
||||||
|
bend = inQuoteEnd + startOffset
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!startPosInStringFound) {
|
||||||
|
bstart = findBlockLocation(chars, close, type, Direction.BACKWARDS, pos, count)
|
||||||
|
if (bstart == -1) {
|
||||||
|
bstart = findBlockLocation(chars, close, type, Direction.FORWARDS, pos, count)
|
||||||
|
}
|
||||||
|
if (bstart != -1) {
|
||||||
|
bend = findBlockLocation(chars, type, close, Direction.FORWARDS, bstart, 1)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (bstart == -1 || bend == -1) {
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!isOuter) {
|
||||||
|
bstart++
|
||||||
|
// exclude first line break after start for inner match
|
||||||
|
if (chars[bstart] == '\n') {
|
||||||
|
bstart++
|
||||||
|
}
|
||||||
|
val o = editor.getLineStartForOffset(bend)
|
||||||
|
var allWhite = true
|
||||||
|
for (i in o until bend) {
|
||||||
|
if (!Character.isWhitespace(chars[i])) {
|
||||||
|
allWhite = false
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (allWhite) {
|
||||||
|
bend = o - 2
|
||||||
|
} else {
|
||||||
|
bend--
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// End offset exclusive
|
||||||
|
return TextRange(bstart, bend + 1)
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun findMisspelledWord(editor: VimEditor, caret: ImmutableVimCaret, count: Int): Int {
|
||||||
|
val startOffset: Int
|
||||||
|
val endOffset: Int
|
||||||
|
val skipCount: Int
|
||||||
|
val offsetOrdering: IntComparator
|
||||||
|
|
||||||
|
if (count < 0) {
|
||||||
|
startOffset = 0
|
||||||
|
endOffset = caret.offset.point - 1
|
||||||
|
skipCount = -count - 1
|
||||||
|
offsetOrdering = IntComparators.OPPOSITE_COMPARATOR
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
startOffset = caret.offset.point + 1
|
||||||
|
endOffset = editor.ij.document.textLength
|
||||||
|
skipCount = count - 1
|
||||||
|
offsetOrdering = IntComparators.NATURAL_COMPARATOR
|
||||||
|
}
|
||||||
|
|
||||||
|
return SearchHelper.findMisspelledWords(editor.ij, startOffset, endOffset, skipCount, offsetOrdering)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,39 +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.
|
|
||||||
*/
|
|
||||||
|
|
||||||
@file:Suppress("DEPRECATION", "unused")
|
|
||||||
|
|
||||||
package com.maddyhome.idea.vim.option
|
|
||||||
|
|
||||||
import com.maddyhome.idea.vim.api.Options
|
|
||||||
import com.maddyhome.idea.vim.options.helpers.KeywordOptionHelper
|
|
||||||
|
|
||||||
/**
|
|
||||||
* COMPATIBILITY-LAYER: Added a class and package
|
|
||||||
* Please see: https://jb.gg/zo8n0r
|
|
||||||
*/
|
|
||||||
@Deprecated("Use VimInjector.optionService")
|
|
||||||
// Used by:
|
|
||||||
// ideavim-sneak 1.2.0 which is current - ignorecase + smartcase, both just access isSet property
|
|
||||||
// IdeaVim-EasyMotion 1.9 + 1.10 - OptionsManger.iskeyword.toRegex() (plus more in tests, which are already broken)
|
|
||||||
// (which-key 0.6.2 uses timeout + timeoutlen, now removed. That plugin version is broken due to other changes)
|
|
||||||
public object OptionsManager {
|
|
||||||
public val ignorecase: ToggleOption
|
|
||||||
get() = ToggleOption(Options.ignorecase)
|
|
||||||
public val smartcase: ToggleOption
|
|
||||||
get() = ToggleOption(Options.smartcase)
|
|
||||||
public val iskeyword: KeywordOption
|
|
||||||
get() = KeywordOption(KeywordOptionHelper)
|
|
||||||
}
|
|
||||||
|
|
||||||
@Deprecated("No longer used. Use StringOption or KeywordOptionHelper")
|
|
||||||
public class KeywordOption(public val helper: KeywordOptionHelper) {
|
|
||||||
public fun toRegex(): List<String> {
|
|
||||||
return helper.toRegex()
|
|
||||||
}
|
|
||||||
}
|
|
@ -333,7 +333,7 @@
|
|||||||
* |[m| {@link com.maddyhome.idea.vim.action.motion.text.MotionMethodPreviousStartAction}
|
* |[m| {@link com.maddyhome.idea.vim.action.motion.text.MotionMethodPreviousStartAction}
|
||||||
* |[p| {@link com.maddyhome.idea.vim.action.copy.PutVisualTextAfterCursorNoIndentAction}
|
* |[p| {@link com.maddyhome.idea.vim.action.copy.PutVisualTextAfterCursorNoIndentAction}
|
||||||
* |[p| {@link com.maddyhome.idea.vim.action.copy.PutTextAfterCursorNoIndentAction}
|
* |[p| {@link com.maddyhome.idea.vim.action.copy.PutTextAfterCursorNoIndentAction}
|
||||||
* |[s| TO BE IMPLEMENTED
|
* |[s| {@link com.maddyhome.idea.vim.action.motion.text.MotionMisspelledWordPreviousAction}
|
||||||
* |[z| TO BE IMPLEMENTED
|
* |[z| TO BE IMPLEMENTED
|
||||||
* |[{| {@link com.maddyhome.idea.vim.action.motion.text.MotionUnmatchedBraceOpenAction}
|
* |[{| {@link com.maddyhome.idea.vim.action.motion.text.MotionUnmatchedBraceOpenAction}
|
||||||
* |]_CTRL-D| TO BE IMPLEMENTED
|
* |]_CTRL-D| TO BE IMPLEMENTED
|
||||||
@ -358,7 +358,7 @@
|
|||||||
* |]m| {@link com.maddyhome.idea.vim.action.motion.text.MotionMethodNextStartAction}
|
* |]m| {@link com.maddyhome.idea.vim.action.motion.text.MotionMethodNextStartAction}
|
||||||
* |]p| {@link com.maddyhome.idea.vim.action.copy.PutVisualTextAfterCursorNoIndentAction}
|
* |]p| {@link com.maddyhome.idea.vim.action.copy.PutVisualTextAfterCursorNoIndentAction}
|
||||||
* |]p| {@link com.maddyhome.idea.vim.action.copy.PutTextAfterCursorNoIndentAction}
|
* |]p| {@link com.maddyhome.idea.vim.action.copy.PutTextAfterCursorNoIndentAction}
|
||||||
* |]s| TO BE IMPLEMENTED
|
* |]s| {@link com.maddyhome.idea.vim.action.motion.text.MotionMisspelledWordNextAction}
|
||||||
* |]z| TO BE IMPLEMENTED
|
* |]z| TO BE IMPLEMENTED
|
||||||
* |]}| {@link com.maddyhome.idea.vim.action.motion.text.MotionUnmatchedBraceCloseAction}
|
* |]}| {@link com.maddyhome.idea.vim.action.motion.text.MotionUnmatchedBraceCloseAction}
|
||||||
*
|
*
|
||||||
|
@ -8,6 +8,7 @@ import com.maddyhome.idea.vim.helper.Msg
|
|||||||
import org.jetbrains.annotations.NonNls
|
import org.jetbrains.annotations.NonNls
|
||||||
import java.util.*
|
import java.util.*
|
||||||
|
|
||||||
|
@Deprecated("Please use VimRegex class instead")
|
||||||
internal class RegExp {
|
internal class RegExp {
|
||||||
/*
|
/*
|
||||||
* The first byte of the regexp internal "program" is actually this magic
|
* The first byte of the regexp internal "program" is actually this magic
|
||||||
|
@ -37,7 +37,7 @@ internal object ShowCmd {
|
|||||||
private const val SHOWCMD_COLS = 10
|
private const val SHOWCMD_COLS = 10
|
||||||
|
|
||||||
@NonNls
|
@NonNls
|
||||||
internal const val ID = "IdeaVim::ShowCmd"
|
internal const val ID = "IdeaVimShowCmd"
|
||||||
|
|
||||||
@NlsSafe
|
@NlsSafe
|
||||||
internal const val displayName = "IdeaVim showcmd"
|
internal const val displayName = "IdeaVim showcmd"
|
||||||
|
@ -24,7 +24,7 @@ import com.maddyhome.idea.vim.ui.widgets.VimWidgetListener
|
|||||||
import com.maddyhome.idea.vim.ui.widgets.mode.VimStatusBarWidget
|
import com.maddyhome.idea.vim.ui.widgets.mode.VimStatusBarWidget
|
||||||
import java.awt.Component
|
import java.awt.Component
|
||||||
|
|
||||||
private const val ID = "IdeaVim::Macro"
|
private const val ID = "IdeaVimMacro"
|
||||||
|
|
||||||
internal class MacroWidgetFactory : StatusBarWidgetFactory, VimStatusBarWidget {
|
internal class MacroWidgetFactory : StatusBarWidgetFactory, VimStatusBarWidget {
|
||||||
private var content: String = ""
|
private var content: String = ""
|
||||||
|
@ -19,7 +19,7 @@ import com.maddyhome.idea.vim.ui.widgets.VimWidgetListener
|
|||||||
|
|
||||||
public class ModeWidgetFactory : StatusBarWidgetFactory {
|
public class ModeWidgetFactory : StatusBarWidgetFactory {
|
||||||
public companion object {
|
public companion object {
|
||||||
public const val ID: String = "IdeaVim::Mode"
|
public const val ID: String = "IdeaVimMode"
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun getId(): String {
|
override fun getId(): String {
|
||||||
|
@ -351,7 +351,7 @@ public class ModeWidgetPopup : AnAction() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public enum class ModeWidgetTheme(private var value: String) {
|
public enum class ModeWidgetTheme(private var value: String) {
|
||||||
TEST("Nord-Aurora (testing, will be removed)"),
|
TERM("Term"),
|
||||||
COLORLESS("Colorless");
|
COLORLESS("Colorless");
|
||||||
|
|
||||||
override fun toString(): String {
|
override fun toString(): String {
|
||||||
@ -363,6 +363,6 @@ public enum class ModeWidgetTheme(private var value: String) {
|
|||||||
return ModeWidgetTheme.values().firstOrNull { it.value == string }
|
return ModeWidgetTheme.values().firstOrNull { it.value == string }
|
||||||
}
|
}
|
||||||
|
|
||||||
public fun getDefaultTheme(): ModeWidgetTheme = TEST
|
public fun getDefaultTheme(): ModeWidgetTheme = TERM
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -22,14 +22,12 @@ public fun getModeBackground(mode: Mode?): Color {
|
|||||||
val themeString = injector.variableService.getVimVariable("widget_mode_theme$keyPostfix")?.asString() ?: ""
|
val themeString = injector.variableService.getVimVariable("widget_mode_theme$keyPostfix")?.asString() ?: ""
|
||||||
val theme = ModeWidgetTheme.parseString(themeString) ?: ModeWidgetTheme.getDefaultTheme()
|
val theme = ModeWidgetTheme.parseString(themeString) ?: ModeWidgetTheme.getDefaultTheme()
|
||||||
when (theme) {
|
when (theme) {
|
||||||
ModeWidgetTheme.TEST -> {
|
ModeWidgetTheme.TERM -> {
|
||||||
return when (mode) {
|
return when (mode) {
|
||||||
Mode.INSERT -> Color.decode("#D08770")
|
Mode.INSERT -> Color.decode("#F4BF75")
|
||||||
Mode.REPLACE -> Color.decode("#EBCB8B")
|
Mode.REPLACE -> Color.decode("#AC4242")
|
||||||
is Mode.NORMAL -> Color.decode("#BF616A")
|
is Mode.NORMAL, is Mode.CMD_LINE -> Color.decode("#90A959")
|
||||||
is Mode.CMD_LINE -> Color.decode("#A3BE8C")
|
is Mode.VISUAL, is Mode.SELECT -> Color.decode("#6A9FB5")
|
||||||
is Mode.VISUAL -> Color.decode("#B48EAD")
|
|
||||||
is Mode.SELECT -> Color.decode("#B48EAD")
|
|
||||||
is Mode.OP_PENDING, null -> UIUtil.getPanelBackground()
|
is Mode.OP_PENDING, null -> UIUtil.getPanelBackground()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -84,7 +82,7 @@ public fun getModeForeground(mode: Mode?): Color {
|
|||||||
val themeString = injector.variableService.getVimVariable("widget_mode_theme$keyPostfix")?.asString() ?: ""
|
val themeString = injector.variableService.getVimVariable("widget_mode_theme$keyPostfix")?.asString() ?: ""
|
||||||
val theme = ModeWidgetTheme.parseString(themeString) ?: ModeWidgetTheme.getDefaultTheme()
|
val theme = ModeWidgetTheme.parseString(themeString) ?: ModeWidgetTheme.getDefaultTheme()
|
||||||
return when (theme) {
|
return when (theme) {
|
||||||
ModeWidgetTheme.TEST -> Color.decode("#2E3440")
|
ModeWidgetTheme.TERM -> UIUtil.getPanelBackground()
|
||||||
ModeWidgetTheme.COLORLESS -> UIUtil.getLabelForeground()
|
ModeWidgetTheme.COLORLESS -> UIUtil.getLabelForeground()
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
|
@ -48,10 +48,7 @@ public class VimModeWidget(public val project: Project) : CustomStatusBarWidget,
|
|||||||
private const val SELECT_LINE = "S-LINE"
|
private const val SELECT_LINE = "S-LINE"
|
||||||
private const val SELECT_BLOCK = "S-BLOCK"
|
private const val SELECT_BLOCK = "S-BLOCK"
|
||||||
}
|
}
|
||||||
private val useColors = injector.globalIjOptions().colorfulmodewidget
|
private val label = JBLabelWiderThan(setOf(REPLACE)).apply { isOpaque = true }
|
||||||
private val label = JBLabelWiderThan(setOf(REPLACE)).apply {
|
|
||||||
isOpaque = useColors
|
|
||||||
}
|
|
||||||
|
|
||||||
init {
|
init {
|
||||||
val mode = getFocusedEditor(project)?.vim?.mode
|
val mode = getFocusedEditor(project)?.vim?.mode
|
||||||
@ -96,10 +93,8 @@ public class VimModeWidget(public val project: Project) : CustomStatusBarWidget,
|
|||||||
|
|
||||||
private fun updateLabel(mode: Mode?) {
|
private fun updateLabel(mode: Mode?) {
|
||||||
label.text = getModeText(mode)
|
label.text = getModeText(mode)
|
||||||
if (useColors) {
|
label.foreground = getModeForeground(mode)
|
||||||
label.foreground = getModeForeground(mode)
|
label.background = getModeBackground(mode)
|
||||||
label.background = getModeBackground(mode)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun getFocusedEditor(project: Project): Editor? {
|
private fun getFocusedEditor(project: Project): Editor? {
|
||||||
|
@ -11,6 +11,7 @@ package com.maddyhome.idea.vim.vimscript.model.commands
|
|||||||
import com.intellij.openapi.editor.RangeMarker
|
import com.intellij.openapi.editor.RangeMarker
|
||||||
import com.intellij.vim.annotations.ExCommand
|
import com.intellij.vim.annotations.ExCommand
|
||||||
import com.maddyhome.idea.vim.VimPlugin
|
import com.maddyhome.idea.vim.VimPlugin
|
||||||
|
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.VimEditor
|
import com.maddyhome.idea.vim.api.VimEditor
|
||||||
import com.maddyhome.idea.vim.api.getLineStartForOffset
|
import com.maddyhome.idea.vim.api.getLineStartForOffset
|
||||||
@ -18,15 +19,20 @@ import com.maddyhome.idea.vim.api.injector
|
|||||||
import com.maddyhome.idea.vim.command.OperatorArguments
|
import com.maddyhome.idea.vim.command.OperatorArguments
|
||||||
import com.maddyhome.idea.vim.ex.ranges.LineRange
|
import com.maddyhome.idea.vim.ex.ranges.LineRange
|
||||||
import com.maddyhome.idea.vim.ex.ranges.Ranges
|
import com.maddyhome.idea.vim.ex.ranges.Ranges
|
||||||
|
import com.maddyhome.idea.vim.group.SearchGroup
|
||||||
import com.maddyhome.idea.vim.group.SearchGroup.RE_BOTH
|
import com.maddyhome.idea.vim.group.SearchGroup.RE_BOTH
|
||||||
import com.maddyhome.idea.vim.group.SearchGroup.RE_LAST
|
import com.maddyhome.idea.vim.group.SearchGroup.RE_LAST
|
||||||
import com.maddyhome.idea.vim.group.SearchGroup.RE_SEARCH
|
import com.maddyhome.idea.vim.group.SearchGroup.RE_SEARCH
|
||||||
import com.maddyhome.idea.vim.group.SearchGroup.RE_SUBST
|
import com.maddyhome.idea.vim.group.SearchGroup.RE_SUBST
|
||||||
import com.maddyhome.idea.vim.helper.MessageHelper.message
|
import com.maddyhome.idea.vim.helper.MessageHelper.message
|
||||||
import com.maddyhome.idea.vim.helper.Msg
|
import com.maddyhome.idea.vim.helper.Msg
|
||||||
|
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.regexp.CharPointer
|
import com.maddyhome.idea.vim.regexp.CharPointer
|
||||||
import com.maddyhome.idea.vim.regexp.RegExp
|
import com.maddyhome.idea.vim.regexp.RegExp
|
||||||
|
import com.maddyhome.idea.vim.regexp.VimRegex
|
||||||
|
import com.maddyhome.idea.vim.regexp.VimRegexException
|
||||||
|
import com.maddyhome.idea.vim.regexp.match.VimMatchResult
|
||||||
import com.maddyhome.idea.vim.vimscript.model.ExecutionResult
|
import com.maddyhome.idea.vim.vimscript.model.ExecutionResult
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -99,63 +105,110 @@ internal data class GlobalCommand(val ranges: Ranges, val argument: String, val
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
val (first, second) = injector.searchGroup.search_regcomp(pat, whichPat, RE_BOTH)
|
if (injector.globalIjOptions().useNewRegex) {
|
||||||
if (!first) {
|
val regex = try {
|
||||||
VimPlugin.showMessage(message(Msg.e_invcmd))
|
VimRegex(pat.toString())
|
||||||
VimPlugin.indicateError()
|
} catch (e: VimRegexException) {
|
||||||
return false
|
injector.messages.showStatusBarMessage(editor, e.message)
|
||||||
}
|
|
||||||
val regmatch = second.first as RegExp.regmmatch_T
|
|
||||||
val sp = second.third as RegExp
|
|
||||||
|
|
||||||
var match: Int
|
|
||||||
val lcount = editor.lineCount()
|
|
||||||
val searchcol = 0
|
|
||||||
if (globalBusy) {
|
|
||||||
val offset = editor.currentCaret().offset
|
|
||||||
val lineStartOffset = editor.getLineStartForOffset(offset.point)
|
|
||||||
match = sp.vim_regexec_multi(regmatch, editor, lcount, editor.currentCaret().getLine().line, searchcol)
|
|
||||||
if ((!invert && match > 0) || (invert && match <= 0)) {
|
|
||||||
globalExecuteOne(editor, context, lineStartOffset, cmd.toString())
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
// pass 1: set marks for each (not) matching line
|
|
||||||
val line1 = range.startLine
|
|
||||||
val line2 = range.endLine
|
|
||||||
//region search_regcomp implementation
|
|
||||||
// We don't need to worry about lastIgnoreSmartCase, it's always false. Vim resets after checking, and it only sets
|
|
||||||
// it to true when searching for a word with `*`, `#`, `g*`, etc.
|
|
||||||
|
|
||||||
if (line1 < 0 || line2 < 0) {
|
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
|
||||||
var ndone = 0
|
if (globalBusy) {
|
||||||
val marks = mutableListOf<RangeMarker>()
|
val match = regex.findInLine(editor, editor.currentCaret().getLine().line)
|
||||||
for (lnum in line1..line2) {
|
if (match is VimMatchResult.Success == !invert) {
|
||||||
if (gotInt) break
|
globalExecuteOne(editor, context, editor.getLineStartOffset(editor.currentCaret().getLine().line), cmd.toString())
|
||||||
|
|
||||||
// a match on this line?
|
|
||||||
match = sp.vim_regexec_multi(regmatch, editor, lcount, lnum, searchcol)
|
|
||||||
if ((!invert && match > 0) || (invert && match <= 0)) {
|
|
||||||
val lineStartOffset = editor.getLineStartOffset(lnum)
|
|
||||||
marks += editor.ij.document.createRangeMarker(lineStartOffset, lineStartOffset)
|
|
||||||
ndone += 1
|
|
||||||
}
|
|
||||||
// TODO: 25.05.2021 Check break
|
|
||||||
}
|
|
||||||
|
|
||||||
// pass 2: execute the command for each line that has been marked
|
|
||||||
if (gotInt) {
|
|
||||||
VimPlugin.showMessage(message("e_interr"))
|
|
||||||
} else if (ndone == 0) {
|
|
||||||
if (invert) {
|
|
||||||
VimPlugin.showMessage(message("global.command.not.found.v", pat.toString()))
|
|
||||||
} else {
|
|
||||||
VimPlugin.showMessage(message("global.command.not.found.g", pat.toString()))
|
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
globalExe(editor, context, marks, cmd.toString())
|
val line1 = range.startLine
|
||||||
|
val line2 = range.endLine
|
||||||
|
if (line1 < 0 || line2 < 0) {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
val matches = regex.findAll(
|
||||||
|
editor,
|
||||||
|
editor.getLineStartOffset(line1),
|
||||||
|
editor.getLineEndOffset(line2),
|
||||||
|
)
|
||||||
|
val marks = if (!invert) matches.map {
|
||||||
|
editor.ij.document.createRangeMarker(editor.getLineStartForOffset(it.range.startOffset), editor.getLineStartForOffset(it.range.startOffset))
|
||||||
|
// filter out lines that contain a match
|
||||||
|
} else (line1..line2).filterNot { line ->
|
||||||
|
matches.map { match ->
|
||||||
|
editor.offsetToBufferPosition(match.range.startOffset).line
|
||||||
|
}.contains(line)
|
||||||
|
}.map { editor.ij.document.createRangeMarker(editor.getLineStartOffset(it), editor.getLineStartOffset(it)) }
|
||||||
|
|
||||||
|
if (gotInt) {
|
||||||
|
VimPlugin.showMessage(message("e_interr"))
|
||||||
|
} else if (marks.isEmpty()) {
|
||||||
|
if (invert) {
|
||||||
|
VimPlugin.showMessage(message("global.command.not.found.v", pat.toString()))
|
||||||
|
} else {
|
||||||
|
VimPlugin.showMessage(message("global.command.not.found.g", pat.toString()))
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
globalExe(editor, context, marks, cmd.toString())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
val (first, second) = (injector.searchGroup as SearchGroup).search_regcomp(pat, whichPat, RE_BOTH)
|
||||||
|
if (!first) {
|
||||||
|
VimPlugin.showMessage(message(Msg.e_invcmd))
|
||||||
|
VimPlugin.indicateError()
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
val regmatch = second.first as RegExp.regmmatch_T
|
||||||
|
val sp = second.third as RegExp
|
||||||
|
|
||||||
|
var match: Int
|
||||||
|
val lcount = editor.lineCount()
|
||||||
|
val searchcol = 0
|
||||||
|
if (globalBusy) {
|
||||||
|
val offset = editor.currentCaret().offset
|
||||||
|
val lineStartOffset = editor.getLineStartForOffset(offset.point)
|
||||||
|
match = sp.vim_regexec_multi(regmatch, editor, lcount, editor.currentCaret().getLine().line, searchcol)
|
||||||
|
if ((!invert && match > 0) || (invert && match <= 0)) {
|
||||||
|
globalExecuteOne(editor, context, lineStartOffset, cmd.toString())
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// pass 1: set marks for each (not) matching line
|
||||||
|
val line1 = range.startLine
|
||||||
|
val line2 = range.endLine
|
||||||
|
//region search_regcomp implementation
|
||||||
|
// We don't need to worry about lastIgnoreSmartCase, it's always false. Vim resets after checking, and it only sets
|
||||||
|
// it to true when searching for a word with `*`, `#`, `g*`, etc.
|
||||||
|
|
||||||
|
if (line1 < 0 || line2 < 0) {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
var ndone = 0
|
||||||
|
val marks = mutableListOf<RangeMarker>()
|
||||||
|
for (lnum in line1..line2) {
|
||||||
|
if (gotInt) break
|
||||||
|
|
||||||
|
// a match on this line?
|
||||||
|
match = sp.vim_regexec_multi(regmatch, editor, lcount, lnum, searchcol)
|
||||||
|
if ((!invert && match > 0) || (invert && match <= 0)) {
|
||||||
|
val lineStartOffset = editor.getLineStartOffset(lnum)
|
||||||
|
marks += editor.ij.document.createRangeMarker(lineStartOffset, lineStartOffset)
|
||||||
|
ndone += 1
|
||||||
|
}
|
||||||
|
// TODO: 25.05.2021 Check break
|
||||||
|
}
|
||||||
|
|
||||||
|
// pass 2: execute the command for each line that has been marked
|
||||||
|
if (gotInt) {
|
||||||
|
VimPlugin.showMessage(message("e_interr"))
|
||||||
|
} else if (ndone == 0) {
|
||||||
|
if (invert) {
|
||||||
|
VimPlugin.showMessage(message("global.command.not.found.v", pat.toString()))
|
||||||
|
} else {
|
||||||
|
VimPlugin.showMessage(message("global.command.not.found.g", pat.toString()))
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
globalExe(editor, context, marks, cmd.toString())
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return true
|
return true
|
||||||
|
@ -1,35 +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.vimscript.model.functions
|
|
||||||
|
|
||||||
import com.intellij.openapi.diagnostic.logger
|
|
||||||
import com.intellij.serviceContainer.BaseKeyedLazyInstance
|
|
||||||
import com.intellij.util.xmlb.annotations.Attribute
|
|
||||||
import com.maddyhome.idea.vim.VimPlugin
|
|
||||||
import com.maddyhome.idea.vim.api.injector
|
|
||||||
|
|
||||||
@Deprecated("Moved to annotation approach and lazy initialization")
|
|
||||||
internal class FunctionBeanClass : BaseKeyedLazyInstance<FunctionHandler>() {
|
|
||||||
|
|
||||||
@Attribute("implementation")
|
|
||||||
lateinit var implementation: String
|
|
||||||
|
|
||||||
@Attribute("name")
|
|
||||||
lateinit var name: String
|
|
||||||
|
|
||||||
override fun getImplementationClassName(): String = implementation
|
|
||||||
|
|
||||||
fun register() {
|
|
||||||
if (this.pluginDescriptor.pluginId != VimPlugin.getPluginId()) {
|
|
||||||
logger<FunctionHandler>().error("IdeaVim doesn't accept contributions to `vimActions` extension points. Please create a plugin using `VimExtension`. Plugin to blame: ${this.pluginDescriptor.pluginId}")
|
|
||||||
return
|
|
||||||
}
|
|
||||||
injector.functionService.addOldHandler(this)
|
|
||||||
}
|
|
||||||
}
|
|
@ -8,20 +8,15 @@
|
|||||||
|
|
||||||
package com.maddyhome.idea.vim.vimscript.services
|
package com.maddyhome.idea.vim.vimscript.services
|
||||||
|
|
||||||
import com.intellij.openapi.application.ApplicationManager
|
|
||||||
import com.intellij.openapi.diagnostic.logger
|
import com.intellij.openapi.diagnostic.logger
|
||||||
import com.intellij.openapi.extensions.ExtensionPointName
|
|
||||||
import com.maddyhome.idea.vim.api.VimscriptFunctionService
|
import com.maddyhome.idea.vim.api.VimscriptFunctionService
|
||||||
import com.maddyhome.idea.vim.api.injector
|
|
||||||
import com.maddyhome.idea.vim.ex.ExException
|
import com.maddyhome.idea.vim.ex.ExException
|
||||||
import com.maddyhome.idea.vim.newapi.globalIjOptions
|
|
||||||
import com.maddyhome.idea.vim.vimscript.model.CommandLineVimLContext
|
import com.maddyhome.idea.vim.vimscript.model.CommandLineVimLContext
|
||||||
import com.maddyhome.idea.vim.vimscript.model.Script
|
import com.maddyhome.idea.vim.vimscript.model.Script
|
||||||
import com.maddyhome.idea.vim.vimscript.model.VimLContext
|
import com.maddyhome.idea.vim.vimscript.model.VimLContext
|
||||||
import com.maddyhome.idea.vim.vimscript.model.expressions.Scope
|
import com.maddyhome.idea.vim.vimscript.model.expressions.Scope
|
||||||
import com.maddyhome.idea.vim.vimscript.model.functions.DefinedFunctionHandler
|
import com.maddyhome.idea.vim.vimscript.model.functions.DefinedFunctionHandler
|
||||||
import com.maddyhome.idea.vim.vimscript.model.functions.EngineFunctionProvider
|
import com.maddyhome.idea.vim.vimscript.model.functions.EngineFunctionProvider
|
||||||
import com.maddyhome.idea.vim.vimscript.model.functions.FunctionBeanClass
|
|
||||||
import com.maddyhome.idea.vim.vimscript.model.functions.FunctionHandler
|
import com.maddyhome.idea.vim.vimscript.model.functions.FunctionHandler
|
||||||
import com.maddyhome.idea.vim.vimscript.model.functions.IntellijFunctionProvider
|
import com.maddyhome.idea.vim.vimscript.model.functions.IntellijFunctionProvider
|
||||||
import com.maddyhome.idea.vim.vimscript.model.functions.LazyVimscriptFunction
|
import com.maddyhome.idea.vim.vimscript.model.functions.LazyVimscriptFunction
|
||||||
@ -34,8 +29,6 @@ internal class FunctionStorage : VimscriptFunctionService {
|
|||||||
private val globalFunctions: MutableMap<String, FunctionDeclaration> = mutableMapOf()
|
private val globalFunctions: MutableMap<String, FunctionDeclaration> = mutableMapOf()
|
||||||
|
|
||||||
private val builtInFunctions: MutableMap<String, LazyVimscriptFunction> = mutableMapOf()
|
private val builtInFunctions: MutableMap<String, LazyVimscriptFunction> = mutableMapOf()
|
||||||
@Deprecated("Moved to annotation approach and lazy initialization")
|
|
||||||
private val oldBuiltInFunctions: MutableMap<String, FunctionHandler> = mutableMapOf()
|
|
||||||
|
|
||||||
override fun deleteFunction(name: String, scope: Scope?, vimContext: VimLContext) {
|
override fun deleteFunction(name: String, scope: Scope?, vimContext: VimLContext) {
|
||||||
if (name[0].isLowerCase() && scope != Scope.SCRIPT_VARIABLE) {
|
if (name[0].isLowerCase() && scope != Scope.SCRIPT_VARIABLE) {
|
||||||
@ -145,11 +138,7 @@ internal class FunctionStorage : VimscriptFunctionService {
|
|||||||
}
|
}
|
||||||
|
|
||||||
override fun getBuiltInFunction(name: String): FunctionHandler? {
|
override fun getBuiltInFunction(name: String): FunctionHandler? {
|
||||||
return if (injector.globalIjOptions().vimscriptFunctionAnnotation) {
|
return builtInFunctions[name]?.instance
|
||||||
builtInFunctions[name]?.instance
|
|
||||||
} else {
|
|
||||||
oldBuiltInFunctions[name]
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun storeScriptFunction(functionDeclaration: FunctionDeclaration) {
|
private fun storeScriptFunction(functionDeclaration: FunctionDeclaration) {
|
||||||
@ -180,27 +169,9 @@ internal class FunctionStorage : VimscriptFunctionService {
|
|||||||
|
|
||||||
val intellijFunctions = IntellijFunctionProvider.getFunctions()
|
val intellijFunctions = IntellijFunctionProvider.getFunctions()
|
||||||
intellijFunctions.forEach { addHandler(it) }
|
intellijFunctions.forEach { addHandler(it) }
|
||||||
|
|
||||||
extensionPoint.getExtensionList(ApplicationManager.getApplication()).forEach(FunctionBeanClass::register)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun addHandler(handler: LazyVimscriptFunction) {
|
override fun addHandler(handler: LazyVimscriptFunction) {
|
||||||
builtInFunctions[handler.name] = handler
|
builtInFunctions[handler.name] = handler
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun addOldHandler(handler: Any) {
|
|
||||||
handler as FunctionBeanClass
|
|
||||||
oldBuiltInFunctions[handler.name] = handler.instance
|
|
||||||
}
|
|
||||||
|
|
||||||
@Deprecated("Moved to annotation approach and lazy initialization")
|
|
||||||
companion object {
|
|
||||||
private val extensionPoint = ExtensionPointName.create<FunctionBeanClass>("IdeaVIM.vimLibraryFunction")
|
|
||||||
|
|
||||||
inline fun <reified T : FunctionHandler> getFunctionOfType(): T {
|
|
||||||
val point = extensionPoint.getExtensionList(ApplicationManager.getApplication())
|
|
||||||
.single { it.implementation == T::class.java.name }
|
|
||||||
return point.instance as T
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
@ -16,7 +16,6 @@
|
|||||||
<applicationService serviceImplementation="com.maddyhome.idea.vim.group.CommandGroup"/>
|
<applicationService serviceImplementation="com.maddyhome.idea.vim.group.CommandGroup"/>
|
||||||
<applicationService serviceImplementation="com.maddyhome.idea.vim.group.VimMarkServiceImpl"
|
<applicationService serviceImplementation="com.maddyhome.idea.vim.group.VimMarkServiceImpl"
|
||||||
serviceInterface="com.maddyhome.idea.vim.api.VimMarkService"/>
|
serviceInterface="com.maddyhome.idea.vim.api.VimMarkService"/>
|
||||||
<applicationService serviceImplementation="com.maddyhome.idea.vim.group.MarkGroup"/>
|
|
||||||
<applicationService serviceImplementation="com.maddyhome.idea.vim.group.VimJumpServiceImpl"
|
<applicationService serviceImplementation="com.maddyhome.idea.vim.group.VimJumpServiceImpl"
|
||||||
serviceInterface="com.maddyhome.idea.vim.api.VimJumpService"/>
|
serviceInterface="com.maddyhome.idea.vim.api.VimJumpService"/>
|
||||||
<applicationService serviceImplementation="com.maddyhome.idea.vim.group.RegisterGroup"
|
<applicationService serviceImplementation="com.maddyhome.idea.vim.group.RegisterGroup"
|
||||||
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue
Block a user