vim-prose 0.1.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.
Files changed (33) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +264 -0
  3. package/dist/extensions/vim/commands.d.ts +26 -0
  4. package/dist/extensions/vim/commands.js +231 -0
  5. package/dist/extensions/vim/commands.js.map +1 -0
  6. package/dist/extensions/vim/index.d.ts +3 -0
  7. package/dist/extensions/vim/index.js +2 -0
  8. package/dist/extensions/vim/index.js.map +1 -0
  9. package/dist/extensions/vim/keyHandler.d.ts +6 -0
  10. package/dist/extensions/vim/keyHandler.js +1400 -0
  11. package/dist/extensions/vim/keyHandler.js.map +1 -0
  12. package/dist/extensions/vim/motions.d.ts +80 -0
  13. package/dist/extensions/vim/motions.js +437 -0
  14. package/dist/extensions/vim/motions.js.map +1 -0
  15. package/dist/extensions/vim/operators.d.ts +36 -0
  16. package/dist/extensions/vim/operators.js +350 -0
  17. package/dist/extensions/vim/operators.js.map +1 -0
  18. package/dist/extensions/vim/state.d.ts +8 -0
  19. package/dist/extensions/vim/state.js +174 -0
  20. package/dist/extensions/vim/state.js.map +1 -0
  21. package/dist/extensions/vim/tiptap.d.ts +17 -0
  22. package/dist/extensions/vim/tiptap.js +49 -0
  23. package/dist/extensions/vim/types.d.ts +55 -0
  24. package/dist/extensions/vim/types.js +29 -0
  25. package/dist/extensions/vim/types.js.map +1 -0
  26. package/dist/extensions/vim/utils.d.ts +76 -0
  27. package/dist/extensions/vim/utils.js +224 -0
  28. package/dist/extensions/vim/utils.js.map +1 -0
  29. package/dist/extensions/vim/vim-mode.css +81 -0
  30. package/dist/extensions/vim/visual.d.ts +15 -0
  31. package/dist/extensions/vim/visual.js +58 -0
  32. package/dist/extensions/vim/visual.js.map +1 -0
  33. package/package.json +64 -0
@@ -0,0 +1,350 @@
1
+ import { TextSelection, Selection, } from 'prosemirror-state';
2
+ import { charAt, isWordChar, isWhitespace, lineBounds, } from './utils';
3
+ /**
4
+ * Extract complete top-level nodes that overlap the given range.
5
+ * For partially-overlapping nodes (e.g. a list where only one item is selected),
6
+ * we create a copy of the parent with only the selected children.
7
+ */
8
+ function extractTopLevelNodes(state, from, to) {
9
+ const nodes = [];
10
+ state.doc.nodesBetween(from, to, (node, pos, parent) => {
11
+ if (parent === state.doc) {
12
+ const nodeEnd = pos + node.nodeSize;
13
+ if (pos >= from && nodeEnd <= to) {
14
+ // Fully contained
15
+ nodes.push(node);
16
+ }
17
+ else {
18
+ // Partially contained — slice the content
19
+ const contentStart = pos + 1;
20
+ const relFrom = Math.max(from - contentStart, 0);
21
+ const relTo = Math.min(to - contentStart, node.content.size);
22
+ if (relFrom < relTo) {
23
+ nodes.push(node.copy(node.content.cut(relFrom, relTo)));
24
+ }
25
+ }
26
+ return false; // Don't descend into children
27
+ }
28
+ });
29
+ return nodes;
30
+ }
31
+ /**
32
+ * Resolve a text object, returning { from, to } positions.
33
+ */
34
+ export function resolveTextObject(state, pos, type, object) {
35
+ if (object === 'w') {
36
+ return resolveWordObject(state, pos);
37
+ }
38
+ const pairs = {
39
+ '(': ['(', ')'],
40
+ ')': ['(', ')'],
41
+ '[': ['[', ']'],
42
+ ']': ['[', ']'],
43
+ '{': ['{', '}'],
44
+ '}': ['{', '}'],
45
+ '<': ['<', '>'],
46
+ '>': ['<', '>'],
47
+ "'": ["'", "'"],
48
+ '"': ['"', '"'],
49
+ '`': ['`', '`'],
50
+ };
51
+ const pair = pairs[object];
52
+ if (!pair)
53
+ return null;
54
+ return resolveDelimiterObject(state, pos, type, pair[0], pair[1]);
55
+ }
56
+ function resolveWordObject(state, pos) {
57
+ const $pos = state.doc.resolve(pos);
58
+ const lineS = $pos.start($pos.depth);
59
+ const lineE = $pos.end($pos.depth);
60
+ const ch = charAt(state, pos);
61
+ if (!ch)
62
+ return null;
63
+ let from = pos;
64
+ let to = pos;
65
+ if (isWordChar(ch)) {
66
+ // Expand to cover full word
67
+ while (from > lineS && isWordChar(charAt(state, from - 1)))
68
+ from--;
69
+ while (to < lineE && isWordChar(charAt(state, to)))
70
+ to++;
71
+ }
72
+ else if (isWhitespace(ch)) {
73
+ while (from > lineS && isWhitespace(charAt(state, from - 1)))
74
+ from--;
75
+ while (to < lineE && isWhitespace(charAt(state, to)))
76
+ to++;
77
+ }
78
+ else {
79
+ // Punctuation
80
+ while (from > lineS &&
81
+ !isWordChar(charAt(state, from - 1)) &&
82
+ !isWhitespace(charAt(state, from - 1)))
83
+ from--;
84
+ while (to < lineE &&
85
+ !isWordChar(charAt(state, to)) &&
86
+ !isWhitespace(charAt(state, to)))
87
+ to++;
88
+ }
89
+ return { from, to };
90
+ }
91
+ function resolveDelimiterObject(state, pos, type, open, close) {
92
+ // For quotes, simple scan within current paragraph
93
+ const $pos = state.doc.resolve(pos);
94
+ const lineS = $pos.start($pos.depth);
95
+ const lineE = $pos.end($pos.depth);
96
+ const text = state.doc.textBetween(lineS, lineE, '\n', '\n');
97
+ const offset = pos - lineS;
98
+ if (open === close) {
99
+ // Quote-like delimiters: find surrounding pair
100
+ let openIdx = -1;
101
+ let closeIdx = -1;
102
+ // Find the quote pair that contains the cursor
103
+ const indices = [];
104
+ for (let i = 0; i < text.length; i++) {
105
+ if (text[i] === open)
106
+ indices.push(i);
107
+ }
108
+ // Find a pair that contains the offset
109
+ for (let i = 0; i < indices.length - 1; i += 2) {
110
+ if (indices[i] <= offset && offset <= indices[i + 1]) {
111
+ openIdx = indices[i];
112
+ closeIdx = indices[i + 1];
113
+ break;
114
+ }
115
+ }
116
+ // If cursor is on a delimiter, try to find pair starting from it
117
+ if (openIdx === -1 && text[offset] === open) {
118
+ for (let i = 0; i < indices.length - 1; i++) {
119
+ if (indices[i] === offset) {
120
+ openIdx = indices[i];
121
+ closeIdx = indices[i + 1];
122
+ break;
123
+ }
124
+ }
125
+ }
126
+ if (openIdx === -1 || closeIdx === -1)
127
+ return null;
128
+ if (type === 'i') {
129
+ return { from: lineS + openIdx + 1, to: lineS + closeIdx };
130
+ }
131
+ else {
132
+ return { from: lineS + openIdx, to: lineS + closeIdx + 1 };
133
+ }
134
+ }
135
+ else {
136
+ // Bracket-like delimiters: handle nesting
137
+ let depth = 0;
138
+ let openIdx = -1;
139
+ // Search backward for opening bracket
140
+ for (let i = offset; i >= 0; i--) {
141
+ if (text[i] === close && i !== offset)
142
+ depth++;
143
+ if (text[i] === open) {
144
+ if (depth === 0) {
145
+ openIdx = i;
146
+ break;
147
+ }
148
+ depth--;
149
+ }
150
+ }
151
+ if (openIdx === -1)
152
+ return null;
153
+ // Search forward for closing bracket
154
+ depth = 0;
155
+ let closeIdx = -1;
156
+ for (let i = openIdx + 1; i < text.length; i++) {
157
+ if (text[i] === open)
158
+ depth++;
159
+ if (text[i] === close) {
160
+ if (depth === 0) {
161
+ closeIdx = i;
162
+ break;
163
+ }
164
+ depth--;
165
+ }
166
+ }
167
+ if (closeIdx === -1)
168
+ return null;
169
+ if (type === 'i') {
170
+ return { from: lineS + openIdx + 1, to: lineS + closeIdx };
171
+ }
172
+ else {
173
+ return { from: lineS + openIdx, to: lineS + closeIdx + 1 };
174
+ }
175
+ }
176
+ }
177
+ /**
178
+ * Execute a delete operation on the given range.
179
+ * Returns the transaction and the deleted text.
180
+ */
181
+ export function executeDelete(state, from, to, vimState, linewise = false) {
182
+ const text = state.doc.textBetween(from, to, '\n', '\n');
183
+ const content = linewise ? extractTopLevelNodes(state, from, to) : null;
184
+ vimState.register = { text, linewise, content };
185
+ let tr;
186
+ if (linewise) {
187
+ tr = state.tr.delete(from, to);
188
+ const newPos = Math.min(from, tr.doc.content.size);
189
+ try {
190
+ const $pos = tr.doc.resolve(newPos);
191
+ const sel = Selection.findFrom($pos, 1, true) || Selection.findFrom($pos, -1, true);
192
+ if (sel)
193
+ tr.setSelection(sel);
194
+ }
195
+ catch {
196
+ // leave as-is
197
+ }
198
+ }
199
+ else {
200
+ tr = state.tr.delete(from, to);
201
+ const newPos = Math.min(from, tr.doc.content.size);
202
+ try {
203
+ tr.setSelection(TextSelection.create(tr.doc, newPos));
204
+ }
205
+ catch {
206
+ // leave selection as-is
207
+ }
208
+ }
209
+ return tr;
210
+ }
211
+ /**
212
+ * Execute a yank operation on the given range.
213
+ * Does not modify the document.
214
+ */
215
+ export function executeYank(state, from, to, vimState, linewise = false) {
216
+ const text = state.doc.textBetween(from, to, '\n', '\n');
217
+ const content = linewise ? extractTopLevelNodes(state, from, to) : null;
218
+ vimState.register = { text, linewise, content };
219
+ }
220
+ /**
221
+ * Execute a change operation: delete range, then enter insert mode.
222
+ * Returns the transaction.
223
+ */
224
+ export function executeChange(state, from, to, vimState, linewise = false) {
225
+ const text = state.doc.textBetween(from, to, '\n', '\n');
226
+ const content = linewise ? extractTopLevelNodes(state, from, to) : null;
227
+ vimState.register = { text, linewise, content };
228
+ let tr;
229
+ if (linewise) {
230
+ // Delete the selected lines and replace with a single empty paragraph
231
+ tr = state.tr.delete(from, to);
232
+ const paragraphType = state.schema.nodes.paragraph;
233
+ if (paragraphType) {
234
+ const insertAt = Math.min(from, tr.doc.content.size);
235
+ tr.insert(insertAt, paragraphType.create());
236
+ try {
237
+ tr.setSelection(TextSelection.create(tr.doc, insertAt + 1));
238
+ }
239
+ catch {
240
+ // leave as-is
241
+ }
242
+ }
243
+ }
244
+ else {
245
+ tr = state.tr.delete(from, to);
246
+ const newPos = Math.min(from, tr.doc.content.size);
247
+ try {
248
+ tr.setSelection(TextSelection.create(tr.doc, newPos));
249
+ }
250
+ catch {
251
+ // leave selection as-is
252
+ }
253
+ }
254
+ vimState.mode = 'insert';
255
+ return tr;
256
+ }
257
+ /**
258
+ * Delete a number of lines starting from the current cursor position.
259
+ */
260
+ export function deleteLines(state, pos, count, vimState) {
261
+ let from = lineBounds(state, pos).from;
262
+ let to = from;
263
+ for (let i = 0; i < count; i++) {
264
+ const bounds = lineBounds(state, Math.min(to + 1, state.doc.content.size - 1));
265
+ to = bounds.to;
266
+ if (to >= state.doc.content.size)
267
+ break;
268
+ }
269
+ to = Math.min(to, state.doc.content.size);
270
+ const text = state.doc.textBetween(from, to, '\n', '\n');
271
+ const content = extractTopLevelNodes(state, from, to);
272
+ vimState.register = { text, linewise: true, content };
273
+ const tr = state.tr.delete(from, to);
274
+ // Position cursor at start of next (or previous) line
275
+ const newPos = Math.min(from, tr.doc.content.size);
276
+ try {
277
+ const $pos = tr.doc.resolve(newPos);
278
+ const sel = Selection.findFrom($pos, 1, true) || Selection.findFrom($pos, -1, true);
279
+ if (sel)
280
+ tr.setSelection(sel);
281
+ }
282
+ catch {
283
+ // leave as-is
284
+ }
285
+ return tr;
286
+ }
287
+ /**
288
+ * Yank a number of lines starting from the current cursor position.
289
+ */
290
+ export function yankLines(state, pos, count, vimState) {
291
+ let from = lineBounds(state, pos).from;
292
+ let to = from;
293
+ for (let i = 0; i < count; i++) {
294
+ const bounds = lineBounds(state, Math.min(to + 1, state.doc.content.size - 1));
295
+ to = bounds.to;
296
+ if (to >= state.doc.content.size)
297
+ break;
298
+ }
299
+ to = Math.min(to, state.doc.content.size);
300
+ const text = state.doc.textBetween(from, to, '\n', '\n');
301
+ const content = extractTopLevelNodes(state, from, to);
302
+ vimState.register = { text, linewise: true, content };
303
+ }
304
+ /**
305
+ * Change a number of lines (clear content, enter insert mode).
306
+ */
307
+ export function changeLines(state, pos, count, vimState) {
308
+ // For `cc` with count, delete extra lines and clear the first one
309
+ const $pos = state.doc.resolve(pos);
310
+ const firstLineStart = $pos.start($pos.depth);
311
+ const firstLineEnd = $pos.end($pos.depth);
312
+ if (count <= 1) {
313
+ // Just clear the content of the current line
314
+ const text = state.doc.textBetween(firstLineStart, firstLineEnd, '\n', '\n');
315
+ vimState.register = { text, linewise: true, content: null };
316
+ const tr = state.tr.delete(firstLineStart, firstLineEnd);
317
+ tr.setSelection(TextSelection.create(tr.doc, firstLineStart));
318
+ vimState.mode = 'insert';
319
+ return tr;
320
+ }
321
+ // Multiple lines: collect text, delete all lines, keep one empty paragraph
322
+ let from = lineBounds(state, pos).from;
323
+ let to = from;
324
+ for (let i = 0; i < count; i++) {
325
+ const bounds = lineBounds(state, Math.min(to + 1, state.doc.content.size - 1));
326
+ to = bounds.to;
327
+ if (to >= state.doc.content.size)
328
+ break;
329
+ }
330
+ to = Math.min(to, state.doc.content.size);
331
+ const text = state.doc.textBetween(from, to, '\n', '\n');
332
+ const changeContent = extractTopLevelNodes(state, from, to);
333
+ vimState.register = { text, linewise: true, content: changeContent };
334
+ // Delete all the lines
335
+ const tr = state.tr.delete(from, to);
336
+ // Insert an empty paragraph
337
+ const paragraphType = state.schema.nodes.paragraph;
338
+ if (paragraphType) {
339
+ tr.insert(from, paragraphType.create());
340
+ }
341
+ const newPos = Math.min(from + 1, tr.doc.content.size);
342
+ try {
343
+ tr.setSelection(TextSelection.create(tr.doc, newPos));
344
+ }
345
+ catch {
346
+ // leave as-is
347
+ }
348
+ vimState.mode = 'insert';
349
+ return tr;
350
+ }
@@ -0,0 +1 @@
1
+ {"version":3,"file":"operators.js","sourceRoot":"","sources":["../../../src/extensions/vim/operators.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,8 @@
1
+ import { Plugin, PluginKey, EditorState } from 'prosemirror-state';
2
+ import { VimState, VimEditorCommands } from './types';
3
+ export declare const vimPluginKey: PluginKey<VimState>;
4
+ export declare function createVimPlugin(commands: VimEditorCommands): Plugin<VimState>;
5
+ /**
6
+ * Get the current vim state from an EditorState.
7
+ */
8
+ export declare function getVimStateFromEditorState(state: EditorState): VimState | null;
@@ -0,0 +1,174 @@
1
+ import { Plugin, PluginKey, Selection, } from 'prosemirror-state';
2
+ import { Decoration, DecorationSet } from 'prosemirror-view';
3
+ import { defaultVimState } from './types';
4
+ import { handleKeyDown } from './keyHandler';
5
+ import { findAllMatches } from './utils';
6
+ export const vimPluginKey = new PluginKey('vimMode');
7
+ export function createVimPlugin(commands) {
8
+ const vimState = defaultVimState();
9
+ return new Plugin({
10
+ key: vimPluginKey,
11
+ state: {
12
+ init() {
13
+ return vimState;
14
+ },
15
+ apply(tr, value) {
16
+ // Map mark positions through document changes
17
+ if (tr.docChanged && Object.keys(value.marks).length > 0) {
18
+ const newMarks = {};
19
+ for (const [key, pos] of Object.entries(value.marks)) {
20
+ newMarks[key] = tr.mapping.map(pos);
21
+ }
22
+ value.marks = newMarks;
23
+ }
24
+ return value;
25
+ },
26
+ },
27
+ view(editorView) {
28
+ // Create search bar element
29
+ const searchBar = document.createElement('div');
30
+ searchBar.className = 'vim-search-bar';
31
+ searchBar.style.display = 'none';
32
+ const searchPrefix = document.createElement('span');
33
+ searchPrefix.className = 'vim-search-prefix';
34
+ searchPrefix.textContent = '/';
35
+ searchBar.appendChild(searchPrefix);
36
+ const searchInput = document.createElement('span');
37
+ searchInput.className = 'vim-search-input';
38
+ searchBar.appendChild(searchInput);
39
+ const searchCursor = document.createElement('span');
40
+ searchCursor.className = 'vim-search-cursor';
41
+ searchCursor.textContent = '\u2588';
42
+ searchBar.appendChild(searchCursor);
43
+ // Insert after the editor
44
+ editorView.dom.parentNode?.insertBefore(searchBar, editorView.dom.nextSibling);
45
+ return {
46
+ update() {
47
+ if (vimState.searchActive) {
48
+ searchBar.style.display = 'flex';
49
+ searchInput.textContent = vimState.searchQuery;
50
+ }
51
+ else {
52
+ searchBar.style.display = 'none';
53
+ }
54
+ },
55
+ destroy() {
56
+ searchBar.remove();
57
+ },
58
+ };
59
+ },
60
+ props: {
61
+ handleDOMEvents: {
62
+ mouseup: (view) => {
63
+ setTimeout(() => {
64
+ const { from, to } = view.state.selection;
65
+ if (from !== to && vimState.mode === 'normal') {
66
+ // Native selection in normal mode → enter visual mode
67
+ vimState.mode = 'visual';
68
+ vimState.visualAnchor = from;
69
+ vimState.visualHead = to > from ? to - 1 : from;
70
+ view.dispatch(view.state.tr);
71
+ }
72
+ else if (from === to &&
73
+ (vimState.mode === 'visual' || vimState.mode === 'visual-line')) {
74
+ // Click (empty selection) in visual mode → exit to normal
75
+ vimState.mode = 'normal';
76
+ vimState.visualAnchor = null;
77
+ vimState.visualHead = null;
78
+ view.dispatch(view.state.tr);
79
+ }
80
+ }, 0);
81
+ return false;
82
+ },
83
+ },
84
+ handleKeyDown(view, event) {
85
+ return handleKeyDown(view, event, vimState, commands);
86
+ },
87
+ handleTextInput(_view, _from, _to, text) {
88
+ if (vimState.isTrackingInsert) {
89
+ vimState.insertTextBuffer += text;
90
+ }
91
+ return false;
92
+ },
93
+ attributes(_state) {
94
+ return {
95
+ 'data-vim-mode': vimState.mode,
96
+ class: `vim-mode vim-mode-${vimState.mode}`,
97
+ };
98
+ },
99
+ decorations(state) {
100
+ const decorations = [];
101
+ const cursorPos = vimState.visualHead ?? state.selection.$head.pos;
102
+ if (vimState.mode !== 'insert') {
103
+ // Block cursor
104
+ try {
105
+ let $pos = state.doc.resolve(cursorPos);
106
+ // If at document root (depth 0), find nearest textblock
107
+ if ($pos.depth === 0) {
108
+ const sel = Selection.findFrom($pos, 1, true) ||
109
+ Selection.findFrom($pos, -1, true);
110
+ if (sel)
111
+ $pos = sel.$from;
112
+ }
113
+ if ($pos.depth > 0) {
114
+ const lineEnd = $pos.end($pos.depth);
115
+ if (cursorPos < lineEnd) {
116
+ decorations.push(Decoration.inline(cursorPos, cursorPos + 1, {
117
+ class: 'vim-block-cursor',
118
+ }));
119
+ }
120
+ else {
121
+ decorations.push(Decoration.widget(cursorPos, () => {
122
+ const span = document.createElement('span');
123
+ span.className = 'vim-block-cursor-eol';
124
+ span.textContent = '\u00a0';
125
+ return span;
126
+ }, { side: 0 }));
127
+ }
128
+ }
129
+ }
130
+ catch {
131
+ // Position invalid, skip cursor decoration
132
+ }
133
+ // Visual selection highlight via decoration (more reliable than ::selection)
134
+ if ((vimState.mode === 'visual' || vimState.mode === 'visual-line') &&
135
+ state.selection.from < state.selection.to) {
136
+ decorations.push(Decoration.inline(state.selection.from, state.selection.to, {
137
+ class: 'vim-visual-selection',
138
+ }));
139
+ }
140
+ }
141
+ // Search match highlights (visible in all modes)
142
+ const activeSearchTerm = vimState.searchActive
143
+ ? vimState.searchQuery
144
+ : vimState.searchHighlightsVisible
145
+ ? vimState.searchTerm
146
+ : '';
147
+ if (activeSearchTerm) {
148
+ const wholeWord = !vimState.searchActive && vimState.searchWholeWord;
149
+ const matches = findAllMatches(state, activeSearchTerm, wholeWord);
150
+ for (const matchPos of matches) {
151
+ const end = matchPos + activeSearchTerm.length;
152
+ if (end <= state.doc.content.size) {
153
+ const isCurrent = matchPos <= cursorPos && cursorPos < end;
154
+ decorations.push(Decoration.inline(matchPos, end, {
155
+ class: isCurrent
156
+ ? 'vim-search-match-current'
157
+ : 'vim-search-match',
158
+ }));
159
+ }
160
+ }
161
+ }
162
+ return decorations.length > 0
163
+ ? DecorationSet.create(state.doc, decorations)
164
+ : DecorationSet.empty;
165
+ },
166
+ },
167
+ });
168
+ }
169
+ /**
170
+ * Get the current vim state from an EditorState.
171
+ */
172
+ export function getVimStateFromEditorState(state) {
173
+ return vimPluginKey.getState(state) ?? null;
174
+ }
@@ -0,0 +1 @@
1
+ {"version":3,"file":"state.js","sourceRoot":"","sources":["../../../src/extensions/vim/state.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,SAAS,EAAE,MAAM,kBAAkB,CAAA;AAC5C,OAAO,EAAY,eAAe,EAAE,MAAM,SAAS,CAAA;AAEnD,MAAM,CAAC,MAAM,YAAY,GAAG,IAAI,SAAS,CAAW,SAAS,CAAC,CAAA;AAE9D,MAAM,UAAU,WAAW,CAAC,WAAgB;IAC1C,OAAO,YAAY,CAAC,QAAQ,CAAC,WAAW,CAAC,IAAI,eAAe,EAAE,CAAA;AAChE,CAAC;AAED,MAAM,UAAU,iBAAiB,CAAC,EAAY;IAC5C,EAAE,CAAC,KAAK,GAAG,IAAI,CAAA;IACf,EAAE,CAAC,aAAa,GAAG,IAAI,CAAA;IACvB,EAAE,CAAC,QAAQ,GAAG,IAAI,CAAA;IAClB,EAAE,CAAC,WAAW,GAAG,KAAK,CAAA;IACtB,EAAE,CAAC,UAAU,GAAG,IAAI,CAAA;IACpB,EAAE,CAAC,SAAS,GAAG,KAAK,CAAA;AACtB,CAAC;AAED;;;GAGG;AACH,MAAM,UAAU,cAAc,CAAC,EAAY;IACzC,MAAM,OAAO,GAAG,EAAE,CAAC,aAAa,IAAI,CAAC,CAAA;IACrC,MAAM,WAAW,GAAG,EAAE,CAAC,KAAK,IAAI,CAAC,CAAA;IACjC,OAAO,OAAO,GAAG,WAAW,CAAA;AAC9B,CAAC"}
@@ -0,0 +1,17 @@
1
+ /**
2
+ * Tiptap v3 extension wrapper for VimMode.
3
+ *
4
+ * Usage:
5
+ * import { VimMode } from './extensions/vim/tiptap'
6
+ * const editor = new Editor({
7
+ * extensions: [VimMode],
8
+ * })
9
+ *
10
+ * Requires @tiptap/core to be installed by the consumer.
11
+ */
12
+ import { Extension } from '@tiptap/core';
13
+ import type { VimState, Mode } from './types';
14
+ export declare const VimMode: Extension<any, any>;
15
+ export declare function getVimMode(editor: any): Mode;
16
+ export declare function getVimStatus(editor: any): string;
17
+ export type { VimState, Mode };
@@ -0,0 +1,49 @@
1
+ /**
2
+ * Tiptap v3 extension wrapper for VimMode.
3
+ *
4
+ * Usage:
5
+ * import { VimMode } from './extensions/vim/tiptap'
6
+ * const editor = new Editor({
7
+ * extensions: [VimMode],
8
+ * })
9
+ *
10
+ * Requires @tiptap/core to be installed by the consumer.
11
+ */
12
+ import { Extension } from '@tiptap/core';
13
+ import { createVimPlugin, vimPluginKey } from './state';
14
+ export const VimMode = Extension.create({
15
+ name: 'vimMode',
16
+ addProseMirrorPlugins() {
17
+ const editor = this.editor;
18
+ return [
19
+ createVimPlugin({
20
+ undo: () => editor.commands.undo(),
21
+ redo: () => editor.commands.redo(),
22
+ indent: () => {
23
+ try {
24
+ return editor.commands.sinkListItem('listItem');
25
+ }
26
+ catch {
27
+ return false;
28
+ }
29
+ },
30
+ outdent: () => {
31
+ try {
32
+ return editor.commands.liftListItem('listItem');
33
+ }
34
+ catch {
35
+ return false;
36
+ }
37
+ },
38
+ }),
39
+ ];
40
+ },
41
+ });
42
+ export function getVimMode(editor) {
43
+ const state = vimPluginKey.getState(editor.state);
44
+ return state?.mode ?? 'normal';
45
+ }
46
+ export function getVimStatus(editor) {
47
+ const state = vimPluginKey.getState(editor.state);
48
+ return state?.statusMessage ?? '';
49
+ }