ep_vim 0.11.0 → 0.12.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +6 -4
- package/package.json +6 -6
- package/static/js/index.js +65 -0
- package/static/js/index.test.js +291 -0
package/README.md
CHANGED
|
@@ -16,16 +16,18 @@ A vim-mode plugin for [Etherpad](https://etherpad.org/). Adds modal editing with
|
|
|
16
16
|
- **Bracket matching** — `%` jump to matching bracket
|
|
17
17
|
- **Text objects** — `iw`/`aw` (word), `i"`/`a"` and `i'`/`a'` (quotes), `i{`/`a{` etc. (brackets), `ip`/`ap` (paragraph), `is`/`as` (sentence)
|
|
18
18
|
- **Operators** — `d`, `c`, `y` with motion and text object combinations (`dw`, `ce`, `y$`, `ciw`, `da"`, `yi(`, etc.)
|
|
19
|
-
- **Line operations** — `dd`, `cc`, `yy`, `J` (join), `Y` (yank line)
|
|
19
|
+
- **Line operations** — `dd`, `cc`, `yy`, `D`, `J` (join), `Y` (yank line)
|
|
20
20
|
- **Registers** — `"a`–`"z` named registers for yank/delete/put, `"_` blackhole register
|
|
21
21
|
- **Put** — `p` / `P` with linewise and characterwise register handling
|
|
22
22
|
- **Editing** — `i` `a` `A` `I` (insert/append), `x`, `r`, `s`, `S`, `C`, `o`, `O`, `~` (toggle case)
|
|
23
23
|
- **Marks** — `m{a-z}` to set, `'{a-z}` / `` `{a-z} `` to jump
|
|
24
|
-
- **Search** — `/` and `?` forward/backward, `n`/`N` repeat
|
|
24
|
+
- **Search** — `/` and `?` forward/backward, `n`/`N` repeat, `*`/`#` search word under cursor
|
|
25
|
+
- **Scrolling** — `zz`/`zt`/`zb` center/top/bottom, `Ctrl+d`/`Ctrl+u` half-page, `Ctrl+f`/`Ctrl+b` full-page (requires ctrl keys enabled)
|
|
26
|
+
- **Visual** — `v` char, `V` line, `gv` reselect last selection; `~` toggle case in visual
|
|
25
27
|
- **Repeat** — `.` repeat last command
|
|
26
28
|
- **Counts** — numeric prefixes work with motions and operators
|
|
27
|
-
- **Undo** — `u`
|
|
28
|
-
- **Toggle** — toolbar button to enable/disable vim mode, persisted in localStorage
|
|
29
|
+
- **Undo/redo** — `u` undo, `Ctrl+r` redo (requires ctrl keys enabled)
|
|
30
|
+
- **Toggle** — toolbar button to enable/disable vim mode, persisted in localStorage; settings panel for system clipboard and ctrl key behavior
|
|
29
31
|
|
|
30
32
|
## Differences from vi
|
|
31
33
|
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "ep_vim",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.12.0",
|
|
4
4
|
"description": "Vim-mode plugin for Etherpad with modal editing, motions, and operators",
|
|
5
5
|
"author": {
|
|
6
6
|
"name": "Seth Rothschild",
|
|
@@ -24,14 +24,14 @@
|
|
|
24
24
|
"peerDependencies": {
|
|
25
25
|
"ep_etherpad-lite": ">=1.8.6"
|
|
26
26
|
},
|
|
27
|
+
"scripts": {
|
|
28
|
+
"test": "npm run format && node --test static/js/vim-core.test.js static/js/index.test.js",
|
|
29
|
+
"format": "prettier -w static/js/*"
|
|
30
|
+
},
|
|
27
31
|
"engines": {
|
|
28
32
|
"node": ">=20.0.0"
|
|
29
33
|
},
|
|
30
34
|
"dependencies": {
|
|
31
35
|
"prettier": "^3.8.1"
|
|
32
|
-
},
|
|
33
|
-
"scripts": {
|
|
34
|
-
"test": "npm run format && node --test static/js/vim-core.test.js static/js/index.test.js",
|
|
35
|
-
"format": "prettier -w static/js/*"
|
|
36
36
|
}
|
|
37
|
-
}
|
|
37
|
+
}
|
package/static/js/index.js
CHANGED
|
@@ -54,6 +54,7 @@ const state = {
|
|
|
54
54
|
searchBuffer: "",
|
|
55
55
|
searchDirection: null,
|
|
56
56
|
lastSearch: null,
|
|
57
|
+
lastVisualSelection: null,
|
|
57
58
|
};
|
|
58
59
|
|
|
59
60
|
// --- Editor operations ---
|
|
@@ -767,6 +768,15 @@ commands["visual-line"]["~"] = (ctx) => {
|
|
|
767
768
|
moveBlockCursor(ctx.editorInfo, start[0], start[1]);
|
|
768
769
|
};
|
|
769
770
|
|
|
771
|
+
commands.normal["gv"] = ({ editorInfo, rep }) => {
|
|
772
|
+
if (!state.lastVisualSelection) return;
|
|
773
|
+
const { anchor, cursor, mode } = state.lastVisualSelection;
|
|
774
|
+
state.visualAnchor = anchor;
|
|
775
|
+
state.visualCursor = cursor;
|
|
776
|
+
state.mode = mode;
|
|
777
|
+
updateVisualSelection(editorInfo, rep);
|
|
778
|
+
};
|
|
779
|
+
|
|
770
780
|
// --- Miscellaneous ---
|
|
771
781
|
|
|
772
782
|
commands.normal["u"] = ({ editorInfo }) => {
|
|
@@ -1056,6 +1066,18 @@ commands.normal["zz"] = ({ line }) => {
|
|
|
1056
1066
|
if (lineDiv) lineDiv.scrollIntoView({ block: "center" });
|
|
1057
1067
|
};
|
|
1058
1068
|
|
|
1069
|
+
commands.normal["zt"] = ({ line }) => {
|
|
1070
|
+
if (!state.editorDoc) return;
|
|
1071
|
+
const lineDiv = state.editorDoc.body.querySelectorAll("div")[line];
|
|
1072
|
+
if (lineDiv) lineDiv.scrollIntoView({ block: "start" });
|
|
1073
|
+
};
|
|
1074
|
+
|
|
1075
|
+
commands.normal["zb"] = ({ line }) => {
|
|
1076
|
+
if (!state.editorDoc) return;
|
|
1077
|
+
const lineDiv = state.editorDoc.body.querySelectorAll("div")[line];
|
|
1078
|
+
if (lineDiv) lineDiv.scrollIntoView({ block: "end" });
|
|
1079
|
+
};
|
|
1080
|
+
|
|
1059
1081
|
// --- Dispatch ---
|
|
1060
1082
|
|
|
1061
1083
|
const handleKey = (key, ctx) => {
|
|
@@ -1212,10 +1234,20 @@ exports.aceKeyEvent = (_hookName, { evt, rep, editorInfo }) => {
|
|
|
1212
1234
|
if (evt.key === "Escape") {
|
|
1213
1235
|
state.desiredColumn = null;
|
|
1214
1236
|
if (state.mode === "visual-line") {
|
|
1237
|
+
state.lastVisualSelection = {
|
|
1238
|
+
anchor: state.visualAnchor,
|
|
1239
|
+
cursor: state.visualCursor,
|
|
1240
|
+
mode: "visual-line",
|
|
1241
|
+
};
|
|
1215
1242
|
const line = Math.min(state.visualAnchor[0], state.visualCursor[0]);
|
|
1216
1243
|
state.mode = "normal";
|
|
1217
1244
|
moveBlockCursor(editorInfo, line, 0);
|
|
1218
1245
|
} else if (state.mode === "visual-char") {
|
|
1246
|
+
state.lastVisualSelection = {
|
|
1247
|
+
anchor: state.visualAnchor,
|
|
1248
|
+
cursor: state.visualCursor,
|
|
1249
|
+
mode: "visual-char",
|
|
1250
|
+
};
|
|
1219
1251
|
const [vLine, vChar] = state.visualCursor;
|
|
1220
1252
|
state.mode = "normal";
|
|
1221
1253
|
moveBlockCursor(editorInfo, vLine, vChar);
|
|
@@ -1312,6 +1344,33 @@ exports.aceKeyEvent = (_hookName, { evt, rep, editorInfo }) => {
|
|
|
1312
1344
|
evt.preventDefault();
|
|
1313
1345
|
return true;
|
|
1314
1346
|
}
|
|
1347
|
+
const fullPage = halfPage * 2;
|
|
1348
|
+
if (evt.key === "f") {
|
|
1349
|
+
const target = Math.min(line + fullPage * count, rep.lines.length() - 1);
|
|
1350
|
+
const targetLen = rep.lines.atIndex(target).text.length;
|
|
1351
|
+
moveBlockCursor(
|
|
1352
|
+
editorInfo,
|
|
1353
|
+
target,
|
|
1354
|
+
Math.min(char, Math.max(0, targetLen - 1)),
|
|
1355
|
+
);
|
|
1356
|
+
state.pendingCount = null;
|
|
1357
|
+
state.pendingRegister = null;
|
|
1358
|
+
evt.preventDefault();
|
|
1359
|
+
return true;
|
|
1360
|
+
}
|
|
1361
|
+
if (evt.key === "b") {
|
|
1362
|
+
const target = Math.max(line - fullPage * count, 0);
|
|
1363
|
+
const targetLen = rep.lines.atIndex(target).text.length;
|
|
1364
|
+
moveBlockCursor(
|
|
1365
|
+
editorInfo,
|
|
1366
|
+
target,
|
|
1367
|
+
Math.min(char, Math.max(0, targetLen - 1)),
|
|
1368
|
+
);
|
|
1369
|
+
state.pendingCount = null;
|
|
1370
|
+
state.pendingRegister = null;
|
|
1371
|
+
evt.preventDefault();
|
|
1372
|
+
return true;
|
|
1373
|
+
}
|
|
1315
1374
|
}
|
|
1316
1375
|
|
|
1317
1376
|
const handled = handleKey(evt.key, ctx);
|
|
@@ -1324,3 +1383,9 @@ exports._state = state;
|
|
|
1324
1383
|
exports._handleKey = handleKey;
|
|
1325
1384
|
exports._commands = commands;
|
|
1326
1385
|
exports._parameterized = parameterized;
|
|
1386
|
+
exports._setVimEnabled = (v) => {
|
|
1387
|
+
vimEnabled = v;
|
|
1388
|
+
};
|
|
1389
|
+
exports._setUseCtrlKeys = (v) => {
|
|
1390
|
+
useCtrlKeys = v;
|
|
1391
|
+
};
|
package/static/js/index.test.js
CHANGED
|
@@ -15,6 +15,9 @@ const {
|
|
|
15
15
|
_handleKey: handleKey,
|
|
16
16
|
_commands: commands,
|
|
17
17
|
_parameterized: parameterized,
|
|
18
|
+
_setVimEnabled: setVimEnabled,
|
|
19
|
+
_setUseCtrlKeys: setUseCtrlKeys,
|
|
20
|
+
aceKeyEvent,
|
|
18
21
|
} = require("./index.js");
|
|
19
22
|
|
|
20
23
|
const makeRep = (lines) => ({
|
|
@@ -2022,6 +2025,7 @@ const resetState = () => {
|
|
|
2022
2025
|
state.searchBuffer = "";
|
|
2023
2026
|
state.searchDirection = null;
|
|
2024
2027
|
state.lastSearch = null;
|
|
2028
|
+
state.lastVisualSelection = null;
|
|
2025
2029
|
};
|
|
2026
2030
|
|
|
2027
2031
|
describe("edge cases: cc with count", () => {
|
|
@@ -3730,3 +3734,290 @@ describe("missing feature: zz center screen", () => {
|
|
|
3730
3734
|
assert.doesNotThrow(() => commands.normal["zz"](ctx));
|
|
3731
3735
|
});
|
|
3732
3736
|
});
|
|
3737
|
+
|
|
3738
|
+
describe("zt and zb scroll commands", () => {
|
|
3739
|
+
beforeEach(resetState);
|
|
3740
|
+
|
|
3741
|
+
it("zt does nothing when editorDoc is null", () => {
|
|
3742
|
+
state.editorDoc = null;
|
|
3743
|
+
const rep = makeRep(["hello"]);
|
|
3744
|
+
const { editorInfo } = makeMockEditorInfo();
|
|
3745
|
+
const ctx = {
|
|
3746
|
+
rep,
|
|
3747
|
+
editorInfo,
|
|
3748
|
+
line: 0,
|
|
3749
|
+
char: 0,
|
|
3750
|
+
lineText: "hello",
|
|
3751
|
+
count: 1,
|
|
3752
|
+
hasCount: false,
|
|
3753
|
+
};
|
|
3754
|
+
assert.doesNotThrow(() => commands.normal["zt"](ctx));
|
|
3755
|
+
});
|
|
3756
|
+
|
|
3757
|
+
it("zb does nothing when editorDoc is null", () => {
|
|
3758
|
+
state.editorDoc = null;
|
|
3759
|
+
const rep = makeRep(["hello"]);
|
|
3760
|
+
const { editorInfo } = makeMockEditorInfo();
|
|
3761
|
+
const ctx = {
|
|
3762
|
+
rep,
|
|
3763
|
+
editorInfo,
|
|
3764
|
+
line: 0,
|
|
3765
|
+
char: 0,
|
|
3766
|
+
lineText: "hello",
|
|
3767
|
+
count: 1,
|
|
3768
|
+
hasCount: false,
|
|
3769
|
+
};
|
|
3770
|
+
assert.doesNotThrow(() => commands.normal["zb"](ctx));
|
|
3771
|
+
});
|
|
3772
|
+
|
|
3773
|
+
it("zt calls scrollIntoView with block: start", () => {
|
|
3774
|
+
const scrollCalls = [];
|
|
3775
|
+
const mockLineDiv = {
|
|
3776
|
+
scrollIntoView: (opts) => scrollCalls.push(opts),
|
|
3777
|
+
};
|
|
3778
|
+
state.editorDoc = {
|
|
3779
|
+
body: {
|
|
3780
|
+
querySelectorAll: () => [mockLineDiv],
|
|
3781
|
+
},
|
|
3782
|
+
};
|
|
3783
|
+
const rep = makeRep(["hello"]);
|
|
3784
|
+
const { editorInfo } = makeMockEditorInfo();
|
|
3785
|
+
const ctx = {
|
|
3786
|
+
rep,
|
|
3787
|
+
editorInfo,
|
|
3788
|
+
line: 0,
|
|
3789
|
+
char: 0,
|
|
3790
|
+
lineText: "hello",
|
|
3791
|
+
count: 1,
|
|
3792
|
+
hasCount: false,
|
|
3793
|
+
};
|
|
3794
|
+
commands.normal["zt"](ctx);
|
|
3795
|
+
assert.equal(scrollCalls.length, 1);
|
|
3796
|
+
assert.deepEqual(scrollCalls[0], { block: "start" });
|
|
3797
|
+
});
|
|
3798
|
+
|
|
3799
|
+
it("zb calls scrollIntoView with block: end", () => {
|
|
3800
|
+
const scrollCalls = [];
|
|
3801
|
+
const mockLineDiv = {
|
|
3802
|
+
scrollIntoView: (opts) => scrollCalls.push(opts),
|
|
3803
|
+
};
|
|
3804
|
+
state.editorDoc = {
|
|
3805
|
+
body: {
|
|
3806
|
+
querySelectorAll: () => [mockLineDiv],
|
|
3807
|
+
},
|
|
3808
|
+
};
|
|
3809
|
+
const rep = makeRep(["hello"]);
|
|
3810
|
+
const { editorInfo } = makeMockEditorInfo();
|
|
3811
|
+
const ctx = {
|
|
3812
|
+
rep,
|
|
3813
|
+
editorInfo,
|
|
3814
|
+
line: 0,
|
|
3815
|
+
char: 0,
|
|
3816
|
+
lineText: "hello",
|
|
3817
|
+
count: 1,
|
|
3818
|
+
hasCount: false,
|
|
3819
|
+
};
|
|
3820
|
+
commands.normal["zb"](ctx);
|
|
3821
|
+
assert.equal(scrollCalls.length, 1);
|
|
3822
|
+
assert.deepEqual(scrollCalls[0], { block: "end" });
|
|
3823
|
+
});
|
|
3824
|
+
});
|
|
3825
|
+
|
|
3826
|
+
describe("gv reselect last visual", () => {
|
|
3827
|
+
beforeEach(resetState);
|
|
3828
|
+
|
|
3829
|
+
it("does nothing when lastVisualSelection is null", () => {
|
|
3830
|
+
state.lastVisualSelection = null;
|
|
3831
|
+
const rep = makeRep(["hello world"]);
|
|
3832
|
+
const { editorInfo, calls } = makeMockEditorInfo();
|
|
3833
|
+
const ctx = {
|
|
3834
|
+
rep,
|
|
3835
|
+
editorInfo,
|
|
3836
|
+
line: 0,
|
|
3837
|
+
char: 0,
|
|
3838
|
+
lineText: "hello world",
|
|
3839
|
+
count: 1,
|
|
3840
|
+
hasCount: false,
|
|
3841
|
+
};
|
|
3842
|
+
commands.normal["gv"](ctx);
|
|
3843
|
+
assert.equal(calls.length, 0);
|
|
3844
|
+
assert.equal(state.mode, "normal");
|
|
3845
|
+
});
|
|
3846
|
+
|
|
3847
|
+
it("restores visual-char selection", () => {
|
|
3848
|
+
state.lastVisualSelection = {
|
|
3849
|
+
anchor: [0, 2],
|
|
3850
|
+
cursor: [0, 5],
|
|
3851
|
+
mode: "visual-char",
|
|
3852
|
+
};
|
|
3853
|
+
const rep = makeRep(["hello world"]);
|
|
3854
|
+
const { editorInfo, calls } = makeMockEditorInfo();
|
|
3855
|
+
const ctx = {
|
|
3856
|
+
rep,
|
|
3857
|
+
editorInfo,
|
|
3858
|
+
line: 0,
|
|
3859
|
+
char: 0,
|
|
3860
|
+
lineText: "hello world",
|
|
3861
|
+
count: 1,
|
|
3862
|
+
hasCount: false,
|
|
3863
|
+
};
|
|
3864
|
+
commands.normal["gv"](ctx);
|
|
3865
|
+
assert.equal(state.mode, "visual-char");
|
|
3866
|
+
assert.deepEqual(state.visualAnchor, [0, 2]);
|
|
3867
|
+
assert.deepEqual(state.visualCursor, [0, 5]);
|
|
3868
|
+
assert.ok(calls.length > 0, "expected a selection call");
|
|
3869
|
+
});
|
|
3870
|
+
|
|
3871
|
+
it("restores visual-line selection", () => {
|
|
3872
|
+
state.lastVisualSelection = {
|
|
3873
|
+
anchor: [1, 0],
|
|
3874
|
+
cursor: [2, 0],
|
|
3875
|
+
mode: "visual-line",
|
|
3876
|
+
};
|
|
3877
|
+
const rep = makeRep(["aaa", "bbb", "ccc"]);
|
|
3878
|
+
const { editorInfo } = makeMockEditorInfo();
|
|
3879
|
+
const ctx = {
|
|
3880
|
+
rep,
|
|
3881
|
+
editorInfo,
|
|
3882
|
+
line: 0,
|
|
3883
|
+
char: 0,
|
|
3884
|
+
lineText: "aaa",
|
|
3885
|
+
count: 1,
|
|
3886
|
+
hasCount: false,
|
|
3887
|
+
};
|
|
3888
|
+
commands.normal["gv"](ctx);
|
|
3889
|
+
assert.equal(state.mode, "visual-line");
|
|
3890
|
+
assert.deepEqual(state.visualAnchor, [1, 0]);
|
|
3891
|
+
assert.deepEqual(state.visualCursor, [2, 0]);
|
|
3892
|
+
});
|
|
3893
|
+
|
|
3894
|
+
it("escape from visual-char saves lastVisualSelection", () => {
|
|
3895
|
+
state.mode = "visual-char";
|
|
3896
|
+
state.visualAnchor = [0, 1];
|
|
3897
|
+
state.visualCursor = [0, 4];
|
|
3898
|
+
state.editorDoc = null;
|
|
3899
|
+
const rep = makeRep(["hello world"]);
|
|
3900
|
+
rep.selStart = [0, 4];
|
|
3901
|
+
const { editorInfo } = makeMockEditorInfo();
|
|
3902
|
+
const mockEvt = {
|
|
3903
|
+
type: "keydown",
|
|
3904
|
+
key: "Escape",
|
|
3905
|
+
ctrlKey: false,
|
|
3906
|
+
metaKey: false,
|
|
3907
|
+
target: { ownerDocument: null },
|
|
3908
|
+
preventDefault: () => {},
|
|
3909
|
+
};
|
|
3910
|
+
setVimEnabled(true);
|
|
3911
|
+
aceKeyEvent("aceKeyEvent", { evt: mockEvt, rep, editorInfo });
|
|
3912
|
+
setVimEnabled(false);
|
|
3913
|
+
assert.deepEqual(state.lastVisualSelection, {
|
|
3914
|
+
anchor: [0, 1],
|
|
3915
|
+
cursor: [0, 4],
|
|
3916
|
+
mode: "visual-char",
|
|
3917
|
+
});
|
|
3918
|
+
});
|
|
3919
|
+
|
|
3920
|
+
it("escape from visual-line saves lastVisualSelection", () => {
|
|
3921
|
+
state.mode = "visual-line";
|
|
3922
|
+
state.visualAnchor = [0, 0];
|
|
3923
|
+
state.visualCursor = [2, 0];
|
|
3924
|
+
state.editorDoc = null;
|
|
3925
|
+
const rep = makeRep(["aaa", "bbb", "ccc"]);
|
|
3926
|
+
rep.selStart = [2, 0];
|
|
3927
|
+
const { editorInfo } = makeMockEditorInfo();
|
|
3928
|
+
const mockEvt = {
|
|
3929
|
+
type: "keydown",
|
|
3930
|
+
key: "Escape",
|
|
3931
|
+
ctrlKey: false,
|
|
3932
|
+
metaKey: false,
|
|
3933
|
+
target: { ownerDocument: null },
|
|
3934
|
+
preventDefault: () => {},
|
|
3935
|
+
};
|
|
3936
|
+
setVimEnabled(true);
|
|
3937
|
+
aceKeyEvent("aceKeyEvent", { evt: mockEvt, rep, editorInfo });
|
|
3938
|
+
setVimEnabled(false);
|
|
3939
|
+
assert.deepEqual(state.lastVisualSelection, {
|
|
3940
|
+
anchor: [0, 0],
|
|
3941
|
+
cursor: [2, 0],
|
|
3942
|
+
mode: "visual-line",
|
|
3943
|
+
});
|
|
3944
|
+
});
|
|
3945
|
+
});
|
|
3946
|
+
|
|
3947
|
+
describe("Ctrl+f and Ctrl+b page scroll", () => {
|
|
3948
|
+
beforeEach(() => {
|
|
3949
|
+
resetState();
|
|
3950
|
+
setVimEnabled(true);
|
|
3951
|
+
setUseCtrlKeys(true);
|
|
3952
|
+
});
|
|
3953
|
+
|
|
3954
|
+
const makeCtrlEvt = (key) => ({
|
|
3955
|
+
type: "keydown",
|
|
3956
|
+
key,
|
|
3957
|
+
ctrlKey: true,
|
|
3958
|
+
metaKey: false,
|
|
3959
|
+
target: { ownerDocument: null },
|
|
3960
|
+
preventDefault: () => {},
|
|
3961
|
+
});
|
|
3962
|
+
|
|
3963
|
+
it("Ctrl+f moves forward one full page", () => {
|
|
3964
|
+
const lines = Array.from({ length: 50 }, (_, i) => `line${i}`);
|
|
3965
|
+
const rep = makeRep(lines);
|
|
3966
|
+
rep.selStart = [0, 0];
|
|
3967
|
+
const { editorInfo, calls } = makeMockEditorInfo();
|
|
3968
|
+
aceKeyEvent("aceKeyEvent", {
|
|
3969
|
+
evt: makeCtrlEvt("f"),
|
|
3970
|
+
rep,
|
|
3971
|
+
editorInfo,
|
|
3972
|
+
});
|
|
3973
|
+
const selectCall = calls.find((c) => c.type === "select");
|
|
3974
|
+
assert.ok(selectCall, "expected a select call");
|
|
3975
|
+
assert.equal(selectCall.start[0], 30);
|
|
3976
|
+
});
|
|
3977
|
+
|
|
3978
|
+
it("Ctrl+b moves backward one full page", () => {
|
|
3979
|
+
const lines = Array.from({ length: 50 }, (_, i) => `line${i}`);
|
|
3980
|
+
const rep = makeRep(lines);
|
|
3981
|
+
rep.selStart = [40, 0];
|
|
3982
|
+
state.mode = "normal";
|
|
3983
|
+
const { editorInfo, calls } = makeMockEditorInfo();
|
|
3984
|
+
aceKeyEvent("aceKeyEvent", {
|
|
3985
|
+
evt: makeCtrlEvt("b"),
|
|
3986
|
+
rep,
|
|
3987
|
+
editorInfo,
|
|
3988
|
+
});
|
|
3989
|
+
const selectCall = calls.find((c) => c.type === "select");
|
|
3990
|
+
assert.ok(selectCall, "expected a select call");
|
|
3991
|
+
assert.equal(selectCall.start[0], 10);
|
|
3992
|
+
});
|
|
3993
|
+
|
|
3994
|
+
it("Ctrl+f clamps at end of document", () => {
|
|
3995
|
+
const lines = Array.from({ length: 10 }, (_, i) => `line${i}`);
|
|
3996
|
+
const rep = makeRep(lines);
|
|
3997
|
+
rep.selStart = [5, 0];
|
|
3998
|
+
const { editorInfo, calls } = makeMockEditorInfo();
|
|
3999
|
+
aceKeyEvent("aceKeyEvent", {
|
|
4000
|
+
evt: makeCtrlEvt("f"),
|
|
4001
|
+
rep,
|
|
4002
|
+
editorInfo,
|
|
4003
|
+
});
|
|
4004
|
+
const selectCall = calls.find((c) => c.type === "select");
|
|
4005
|
+
assert.ok(selectCall, "expected a select call");
|
|
4006
|
+
assert.equal(selectCall.start[0], 9);
|
|
4007
|
+
});
|
|
4008
|
+
|
|
4009
|
+
it("Ctrl+b clamps at start of document", () => {
|
|
4010
|
+
const lines = Array.from({ length: 10 }, (_, i) => `line${i}`);
|
|
4011
|
+
const rep = makeRep(lines);
|
|
4012
|
+
rep.selStart = [3, 0];
|
|
4013
|
+
const { editorInfo, calls } = makeMockEditorInfo();
|
|
4014
|
+
aceKeyEvent("aceKeyEvent", {
|
|
4015
|
+
evt: makeCtrlEvt("b"),
|
|
4016
|
+
rep,
|
|
4017
|
+
editorInfo,
|
|
4018
|
+
});
|
|
4019
|
+
const selectCall = calls.find((c) => c.type === "select");
|
|
4020
|
+
assert.ok(selectCall, "expected a select call");
|
|
4021
|
+
assert.equal(selectCall.start[0], 0);
|
|
4022
|
+
});
|
|
4023
|
+
});
|