qword 1.0.2 → 1.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.
- package/ChangeLog +22 -0
- package/README.md +2 -3
- package/assets/style.css +10 -10
- package/client/_clipboard.js +30 -33
- package/client/_clipboard.spec.js +160 -0
- package/client/content.d.ts +8 -0
- package/client/content.js +14 -0
- package/client/content.spec.js +69 -0
- package/client/create.d.ts +32 -0
- package/client/create.js +95 -0
- package/client/create.spec.js +192 -0
- package/client/decorations.d.ts +40 -0
- package/client/decorations.js +110 -0
- package/client/decorations.spec.js +135 -0
- package/client/dom.d.ts +7 -0
- package/client/dom.js +17 -0
- package/client/dom.spec.js +78 -0
- package/client/editor.d.ts +72 -0
- package/client/editor.js +164 -0
- package/client/editor.spec.js +307 -0
- package/client/events.d.ts +7 -0
- package/client/events.js +8 -0
- package/client/events.spec.js +100 -0
- package/client/highlight.d.ts +3 -0
- package/client/highlight.js +82 -0
- package/client/highlight.spec.js +28 -0
- package/client/history.d.ts +4 -0
- package/client/history.js +16 -0
- package/client/history.spec.js +57 -0
- package/client/index.d.ts +60 -0
- package/client/index.js +37 -0
- package/client/loadremote.js +10 -4
- package/client/options.d.ts +30 -0
- package/client/options.js +59 -0
- package/client/options.spec.js +157 -0
- package/client/position.d.ts +21 -0
- package/client/position.js +33 -0
- package/client/position.spec.js +176 -0
- package/client/qword.js +141 -391
- package/client/save.js +2 -2
- package/client/scroll.d.ts +10 -0
- package/client/scroll.js +9 -0
- package/client/scroll.spec.js +58 -0
- package/client/types.d.ts +24 -0
- package/dist/qword.js +175 -1
- package/dist/qword.js.map +1 -1
- package/dist-dev/qword.js +544 -660
- package/package.json +30 -15
- package/server/edit.js +8 -3
- package/server/edit.spec.js +101 -0
- package/test/errors.ts +21 -0
- package/test/types.ts +134 -0
- package/client/codemirror/show-trailing.js +0 -54
- package/client/codemirror/use-soft-tabs.js +0 -38
- package/client/set-key-map.js +0 -3
- package/dist/2b22a33a0a80bb926bbc.png +0 -1
- package/dist/8e9019bd86255ac9a618.png +0 -1
- package/dist/codemirror.js +0 -2
- package/dist/codemirror.js.map +0 -1
- package/dist/dword.js +0 -2
- package/dist/dword.js.map +0 -1
- package/dist-dev/2b22a33a0a80bb926bbc.png +0 -1
- package/dist-dev/8e9019bd86255ac9a618.png +0 -1
- package/dist-dev/codemirror.js +0 -104
- package/dist-dev/dword.js +0 -960
- package/modules/codemirror-emmet/dist/emmet.js +0 -48661
- package/modules/codemirror-emmet/dist/emmet.min.js +0 -8
- package/modules/codemirror-emmet/dist/emmetPlugin.js +0 -840
- package/modules/codemirror-emmet/dist/emmetPlugin.min.js +0 -1
- package/modules/codemirror-emmet/dist/index.html +0 -46
- package/modules/jshint/README.md +0 -114
- package/modules/jshint/dist/jshint-rhino.js +0 -32143
- package/modules/jshint/dist/jshint.js +0 -32141
- package/modules/jshint/package.json +0 -77
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import {history, historyKeymap} from '@codemirror/commands';
|
|
2
|
+
import {keymap} from '@codemirror/view';
|
|
3
|
+
|
|
4
|
+
export function clearHistory(view, historyCompartment, vimEnabled) {
|
|
5
|
+
if (!view)
|
|
6
|
+
return;
|
|
7
|
+
|
|
8
|
+
view.dispatch({
|
|
9
|
+
effects: historyCompartment.reconfigure(vimEnabled
|
|
10
|
+
? []
|
|
11
|
+
: [
|
|
12
|
+
history(),
|
|
13
|
+
keymap.of(historyKeymap),
|
|
14
|
+
]),
|
|
15
|
+
});
|
|
16
|
+
}
|
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
import {test} from 'supertape';
|
|
2
|
+
import {EditorState, Compartment} from '@codemirror/state';
|
|
3
|
+
import {EditorView, keymap} from '@codemirror/view';
|
|
4
|
+
import {history, historyKeymap} from '@codemirror/commands';
|
|
5
|
+
import {clearHistory} from './history.js';
|
|
6
|
+
|
|
7
|
+
const historyExtensions = [
|
|
8
|
+
history(),
|
|
9
|
+
keymap.of(historyKeymap),
|
|
10
|
+
];
|
|
11
|
+
|
|
12
|
+
function makeView() {
|
|
13
|
+
const element = document.createElement('div');
|
|
14
|
+
document.body.appendChild(element);
|
|
15
|
+
const compartment = new Compartment();
|
|
16
|
+
|
|
17
|
+
const view = new EditorView({
|
|
18
|
+
state: EditorState.create({
|
|
19
|
+
doc: 'hello',
|
|
20
|
+
extensions: [
|
|
21
|
+
compartment.of(historyExtensions),
|
|
22
|
+
],
|
|
23
|
+
}),
|
|
24
|
+
parent: element,
|
|
25
|
+
});
|
|
26
|
+
|
|
27
|
+
return {
|
|
28
|
+
view,
|
|
29
|
+
compartment,
|
|
30
|
+
};
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
test('history: clearHistory does not throw', (t) => {
|
|
34
|
+
const {view, compartment} = makeView();
|
|
35
|
+
clearHistory(view, compartment, false);
|
|
36
|
+
view.destroy();
|
|
37
|
+
|
|
38
|
+
t.ok(true);
|
|
39
|
+
t.end();
|
|
40
|
+
});
|
|
41
|
+
|
|
42
|
+
test('history: clearHistory with vim does not throw', (t) => {
|
|
43
|
+
const {view, compartment} = makeView();
|
|
44
|
+
clearHistory(view, compartment, true);
|
|
45
|
+
view.destroy();
|
|
46
|
+
|
|
47
|
+
t.ok(true);
|
|
48
|
+
t.end();
|
|
49
|
+
});
|
|
50
|
+
|
|
51
|
+
test('history: clearHistory null view does not throw', (t) => {
|
|
52
|
+
const {compartment} = makeView();
|
|
53
|
+
clearHistory(null, compartment, false);
|
|
54
|
+
|
|
55
|
+
t.ok(true);
|
|
56
|
+
t.end();
|
|
57
|
+
});
|
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
export {
|
|
2
|
+
createEditor,
|
|
3
|
+
type CreateEditorOptions,
|
|
4
|
+
type QwordEditorView,
|
|
5
|
+
} from './create.js';
|
|
6
|
+
export {
|
|
7
|
+
getValue,
|
|
8
|
+
setValue,
|
|
9
|
+
getDocValue,
|
|
10
|
+
setDocValue,
|
|
11
|
+
} from './content.js';
|
|
12
|
+
export {
|
|
13
|
+
offsetToPosition,
|
|
14
|
+
positionToOffset,
|
|
15
|
+
posFromIndex,
|
|
16
|
+
indexFromPos,
|
|
17
|
+
getCursorIndex,
|
|
18
|
+
getCursor,
|
|
19
|
+
} from './position.js';
|
|
20
|
+
export {
|
|
21
|
+
markField,
|
|
22
|
+
lineField,
|
|
23
|
+
markText,
|
|
24
|
+
addLineClass,
|
|
25
|
+
removeLineClass,
|
|
26
|
+
type MarkHandle,
|
|
27
|
+
} from './decorations.js';
|
|
28
|
+
export {
|
|
29
|
+
on,
|
|
30
|
+
off,
|
|
31
|
+
type EventBinding,
|
|
32
|
+
} from './events.js';
|
|
33
|
+
export {
|
|
34
|
+
getScrollInfo,
|
|
35
|
+
scrollTo,
|
|
36
|
+
type ScrollInfo,
|
|
37
|
+
} from './scroll.js';
|
|
38
|
+
export {
|
|
39
|
+
getView,
|
|
40
|
+
refresh,
|
|
41
|
+
observeResize,
|
|
42
|
+
} from './dom.js';
|
|
43
|
+
export {
|
|
44
|
+
keymapExtension,
|
|
45
|
+
themeExtension,
|
|
46
|
+
languageExtension,
|
|
47
|
+
setOption,
|
|
48
|
+
type OptionKey,
|
|
49
|
+
type OptionValue,
|
|
50
|
+
} from './options.js';
|
|
51
|
+
export {clearHistory} from './history.js';
|
|
52
|
+
export {highlightStyle} from './highlight.js';
|
|
53
|
+
export type {
|
|
54
|
+
SourceCode,
|
|
55
|
+
SourcePosition,
|
|
56
|
+
CharOffset,
|
|
57
|
+
EditorMode,
|
|
58
|
+
EditorTheme,
|
|
59
|
+
KeyMap,
|
|
60
|
+
} from './types.js';
|
package/client/index.js
ADDED
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
export {createEditor} from './create.js';
|
|
2
|
+
export {
|
|
3
|
+
getValue,
|
|
4
|
+
setValue,
|
|
5
|
+
getDocValue,
|
|
6
|
+
setDocValue,
|
|
7
|
+
} from './content.js';
|
|
8
|
+
export {
|
|
9
|
+
offsetToPosition,
|
|
10
|
+
positionToOffset,
|
|
11
|
+
posFromIndex,
|
|
12
|
+
indexFromPos,
|
|
13
|
+
getCursorIndex,
|
|
14
|
+
getCursor,
|
|
15
|
+
} from './position.js';
|
|
16
|
+
export {
|
|
17
|
+
markField,
|
|
18
|
+
lineField,
|
|
19
|
+
markText,
|
|
20
|
+
addLineClass,
|
|
21
|
+
removeLineClass,
|
|
22
|
+
} from './decorations.js';
|
|
23
|
+
export {on, off} from './events.js';
|
|
24
|
+
export {getScrollInfo, scrollTo} from './scroll.js';
|
|
25
|
+
export {
|
|
26
|
+
getView,
|
|
27
|
+
refresh,
|
|
28
|
+
observeResize,
|
|
29
|
+
} from './dom.js';
|
|
30
|
+
export {
|
|
31
|
+
keymapExtension,
|
|
32
|
+
themeExtension,
|
|
33
|
+
languageExtension,
|
|
34
|
+
setOption,
|
|
35
|
+
} from './options.js';
|
|
36
|
+
export {clearHistory} from './history.js';
|
|
37
|
+
export {highlightStyle} from './highlight.js';
|
package/client/loadremote.js
CHANGED
|
@@ -59,11 +59,17 @@ export default async (name, options = {}) => {
|
|
|
59
59
|
local = [module.local];
|
|
60
60
|
}
|
|
61
61
|
|
|
62
|
-
local =
|
|
62
|
+
local = [];
|
|
63
63
|
|
|
64
|
-
const
|
|
65
|
-
|
|
66
|
-
}
|
|
64
|
+
for (const url of local) {
|
|
65
|
+
local.push(prefix + url);
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
const remote = [];
|
|
69
|
+
|
|
70
|
+
for (const tmpl of remoteTmpls) {
|
|
71
|
+
remote.push(tmpl.replace(/{{\sversion\s}}/g, version));
|
|
72
|
+
}
|
|
67
73
|
|
|
68
74
|
if (!online)
|
|
69
75
|
return await off(local);
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
import {type Compartment, type Extension} from '@codemirror/state';
|
|
2
|
+
import type {QwordEditorView} from './create.js';
|
|
3
|
+
import type {
|
|
4
|
+
KeyMap,
|
|
5
|
+
EditorMode,
|
|
6
|
+
EditorTheme,
|
|
7
|
+
} from './types.js';
|
|
8
|
+
|
|
9
|
+
export function keymapExtension(name: KeyMap): Extension;
|
|
10
|
+
|
|
11
|
+
export function themeExtension(name: EditorTheme): Extension;
|
|
12
|
+
|
|
13
|
+
export function languageExtension(mode: EditorMode | {
|
|
14
|
+
name: EditorMode;
|
|
15
|
+
json?: boolean;
|
|
16
|
+
} | null): Extension;
|
|
17
|
+
|
|
18
|
+
export type OptionKey = 'theme' | 'keyMap' | 'mode';
|
|
19
|
+
|
|
20
|
+
export type OptionValue =
|
|
21
|
+
| EditorTheme
|
|
22
|
+
| KeyMap
|
|
23
|
+
| EditorMode
|
|
24
|
+
| {
|
|
25
|
+
name: EditorMode;
|
|
26
|
+
json?: boolean;
|
|
27
|
+
}
|
|
28
|
+
| null;
|
|
29
|
+
|
|
30
|
+
export function setOption(view: QwordEditorView, key: OptionKey, value: OptionValue): void;
|
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
import {keymap} from '@codemirror/view';
|
|
2
|
+
import {javascript} from '@codemirror/lang-javascript';
|
|
3
|
+
import {json} from '@codemirror/lang-json';
|
|
4
|
+
import {html} from '@codemirror/lang-html';
|
|
5
|
+
import {vim} from '@replit/codemirror-vim';
|
|
6
|
+
import {nord} from '@uiw/codemirror-theme-nord';
|
|
7
|
+
import {
|
|
8
|
+
defaultKeymap,
|
|
9
|
+
emacsStyleKeymap,
|
|
10
|
+
} from '@codemirror/commands';
|
|
11
|
+
|
|
12
|
+
export function keymapExtension(name) {
|
|
13
|
+
if (name === 'vim')
|
|
14
|
+
return vim();
|
|
15
|
+
|
|
16
|
+
if (name === 'emacs')
|
|
17
|
+
return keymap.of(emacsStyleKeymap);
|
|
18
|
+
|
|
19
|
+
return keymap.of(defaultKeymap);
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
export function themeExtension(name) {
|
|
23
|
+
if (name === 'nord' || name === 'dark' || name === 'one-dark')
|
|
24
|
+
return nord;
|
|
25
|
+
|
|
26
|
+
return [];
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
export function languageExtension(mode) {
|
|
30
|
+
const name = typeof mode === 'object' ? mode?.name : mode;
|
|
31
|
+
|
|
32
|
+
if (name === 'javascript' || name === 'js')
|
|
33
|
+
return javascript();
|
|
34
|
+
|
|
35
|
+
if (name === 'json')
|
|
36
|
+
return json();
|
|
37
|
+
|
|
38
|
+
if (name === 'html')
|
|
39
|
+
return html();
|
|
40
|
+
|
|
41
|
+
return [];
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
export function setOption(view, key, value) {
|
|
45
|
+
if (key === 'theme')
|
|
46
|
+
return view.dispatch({
|
|
47
|
+
effects: view._themeCompartment.reconfigure(themeExtension(value)),
|
|
48
|
+
});
|
|
49
|
+
|
|
50
|
+
if (key === 'keyMap')
|
|
51
|
+
return view.dispatch({
|
|
52
|
+
effects: view._keymapCompartment.reconfigure(keymapExtension(value)),
|
|
53
|
+
});
|
|
54
|
+
|
|
55
|
+
if (key === 'mode')
|
|
56
|
+
return view.dispatch({
|
|
57
|
+
effects: view._langCompartment.reconfigure(languageExtension(value)),
|
|
58
|
+
});
|
|
59
|
+
}
|
|
@@ -0,0 +1,157 @@
|
|
|
1
|
+
import {test} from 'supertape';
|
|
2
|
+
import {EditorState, Compartment} from '@codemirror/state';
|
|
3
|
+
import {EditorView} from '@codemirror/view';
|
|
4
|
+
import {
|
|
5
|
+
keymapExtension,
|
|
6
|
+
themeExtension,
|
|
7
|
+
languageExtension,
|
|
8
|
+
setOption,
|
|
9
|
+
} from './options.js';
|
|
10
|
+
|
|
11
|
+
function makeView() {
|
|
12
|
+
const element = document.createElement('div');
|
|
13
|
+
document.body.appendChild(element);
|
|
14
|
+
const view = new EditorView({
|
|
15
|
+
state: EditorState.create({
|
|
16
|
+
doc: 'hello',
|
|
17
|
+
}),
|
|
18
|
+
parent: element,
|
|
19
|
+
});
|
|
20
|
+
|
|
21
|
+
view._themeCompartment = new Compartment();
|
|
22
|
+
view._keymapCompartment = new Compartment();
|
|
23
|
+
view._langCompartment = new Compartment();
|
|
24
|
+
|
|
25
|
+
return view;
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
test('options: keymapExtension default returns extension object', (t) => {
|
|
29
|
+
const result = keymapExtension('default');
|
|
30
|
+
|
|
31
|
+
t.ok(result);
|
|
32
|
+
t.end();
|
|
33
|
+
});
|
|
34
|
+
|
|
35
|
+
test('options: keymapExtension vim returns extension object', (t) => {
|
|
36
|
+
const result = keymapExtension('vim');
|
|
37
|
+
|
|
38
|
+
t.ok(result);
|
|
39
|
+
t.end();
|
|
40
|
+
});
|
|
41
|
+
|
|
42
|
+
test('options: keymapExtension emacs returns extension object', (t) => {
|
|
43
|
+
const result = keymapExtension('emacs');
|
|
44
|
+
|
|
45
|
+
t.ok(result);
|
|
46
|
+
t.end();
|
|
47
|
+
});
|
|
48
|
+
|
|
49
|
+
test('options: themeExtension nord returns nord theme object', (t) => {
|
|
50
|
+
const result = themeExtension('nord');
|
|
51
|
+
|
|
52
|
+
t.ok(result);
|
|
53
|
+
t.end();
|
|
54
|
+
});
|
|
55
|
+
|
|
56
|
+
test('options: themeExtension dark returns nord theme', (t) => {
|
|
57
|
+
const result = themeExtension('dark');
|
|
58
|
+
|
|
59
|
+
t.equal(result, themeExtension('nord'));
|
|
60
|
+
t.end();
|
|
61
|
+
});
|
|
62
|
+
|
|
63
|
+
test('options: themeExtension one-dark returns nord theme', (t) => {
|
|
64
|
+
const result = themeExtension('one-dark');
|
|
65
|
+
|
|
66
|
+
t.equal(result, themeExtension('nord'));
|
|
67
|
+
t.end();
|
|
68
|
+
});
|
|
69
|
+
|
|
70
|
+
test('options: themeExtension default returns empty array', (t) => {
|
|
71
|
+
const result = themeExtension('default');
|
|
72
|
+
const expected = [];
|
|
73
|
+
|
|
74
|
+
t.deepEqual(result, expected);
|
|
75
|
+
t.end();
|
|
76
|
+
});
|
|
77
|
+
|
|
78
|
+
test('options: languageExtension javascript returns extension object', (t) => {
|
|
79
|
+
const result = languageExtension('javascript');
|
|
80
|
+
|
|
81
|
+
t.ok(result);
|
|
82
|
+
t.end();
|
|
83
|
+
});
|
|
84
|
+
|
|
85
|
+
test('options: languageExtension js returns javascript extension', (t) => {
|
|
86
|
+
const result = languageExtension('js');
|
|
87
|
+
|
|
88
|
+
t.ok(result);
|
|
89
|
+
t.end();
|
|
90
|
+
});
|
|
91
|
+
|
|
92
|
+
test('options: languageExtension json returns extension', (t) => {
|
|
93
|
+
const result = languageExtension('json');
|
|
94
|
+
|
|
95
|
+
t.ok(result);
|
|
96
|
+
t.end();
|
|
97
|
+
});
|
|
98
|
+
|
|
99
|
+
test('options: languageExtension html returns extension', (t) => {
|
|
100
|
+
const result = languageExtension('html');
|
|
101
|
+
|
|
102
|
+
t.ok(result);
|
|
103
|
+
t.end();
|
|
104
|
+
});
|
|
105
|
+
|
|
106
|
+
test('options: languageExtension object with javascript name returns extension', (t) => {
|
|
107
|
+
const result = languageExtension({
|
|
108
|
+
name: 'javascript',
|
|
109
|
+
json: true,
|
|
110
|
+
});
|
|
111
|
+
|
|
112
|
+
t.ok(result);
|
|
113
|
+
t.end();
|
|
114
|
+
});
|
|
115
|
+
|
|
116
|
+
test('options: languageExtension unknown string returns empty array', (t) => {
|
|
117
|
+
const result = languageExtension('css');
|
|
118
|
+
const expected = [];
|
|
119
|
+
|
|
120
|
+
t.deepEqual(result, expected);
|
|
121
|
+
t.end();
|
|
122
|
+
});
|
|
123
|
+
|
|
124
|
+
test('options: languageExtension null returns empty array', (t) => {
|
|
125
|
+
const result = languageExtension(null);
|
|
126
|
+
const expected = [];
|
|
127
|
+
|
|
128
|
+
t.deepEqual(result, expected);
|
|
129
|
+
t.end();
|
|
130
|
+
});
|
|
131
|
+
|
|
132
|
+
test('options: setOption theme dispatches without throwing', (t) => {
|
|
133
|
+
const view = makeView();
|
|
134
|
+
setOption(view, 'theme', 'nord');
|
|
135
|
+
view.destroy();
|
|
136
|
+
|
|
137
|
+
t.ok(true);
|
|
138
|
+
t.end();
|
|
139
|
+
});
|
|
140
|
+
|
|
141
|
+
test('options: setOption keyMap dispatches without throwing', (t) => {
|
|
142
|
+
const view = makeView();
|
|
143
|
+
setOption(view, 'keyMap', 'vim');
|
|
144
|
+
view.destroy();
|
|
145
|
+
|
|
146
|
+
t.ok(true);
|
|
147
|
+
t.end();
|
|
148
|
+
});
|
|
149
|
+
|
|
150
|
+
test('options: setOption mode dispatches without throwing', (t) => {
|
|
151
|
+
const view = makeView();
|
|
152
|
+
setOption(view, 'mode', 'javascript');
|
|
153
|
+
view.destroy();
|
|
154
|
+
|
|
155
|
+
t.ok(true);
|
|
156
|
+
t.end();
|
|
157
|
+
});
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
import type {Text} from '@codemirror/state';
|
|
2
|
+
import type {EditorView} from '@codemirror/view';
|
|
3
|
+
import type {
|
|
4
|
+
CharOffset,
|
|
5
|
+
SourcePosition,
|
|
6
|
+
} from './types.js';
|
|
7
|
+
|
|
8
|
+
export function offsetToPosition(document_: Text, offset: CharOffset): SourcePosition;
|
|
9
|
+
|
|
10
|
+
export function positionToOffset(document_: Text, {line, ch}: SourcePosition): CharOffset;
|
|
11
|
+
|
|
12
|
+
export function posFromIndex(view: EditorView, index: CharOffset): SourcePosition;
|
|
13
|
+
|
|
14
|
+
export function indexFromPos(view: EditorView, position: SourcePosition): CharOffset;
|
|
15
|
+
|
|
16
|
+
export function getCursor(view: EditorView): {
|
|
17
|
+
row: number;
|
|
18
|
+
column: number;
|
|
19
|
+
};
|
|
20
|
+
|
|
21
|
+
export function getCursorIndex(view: EditorView): CharOffset;
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
export function offsetToPosition(document_, offset) {
|
|
2
|
+
const index = offset < 0 ? 0 : offset > document_.length ? document_.length : offset;
|
|
3
|
+
const line = document_.lineAt(index);
|
|
4
|
+
|
|
5
|
+
return {
|
|
6
|
+
line: line.number - 1,
|
|
7
|
+
ch: index - line.from,
|
|
8
|
+
};
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
export function positionToOffset(document_, {line, ch}) {
|
|
12
|
+
return document_.line(line + 1).from + ch;
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
export function posFromIndex(view, index) {
|
|
16
|
+
return offsetToPosition(view.state.doc, index);
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
export function indexFromPos(view, position) {
|
|
20
|
+
return positionToOffset(view.state.doc, position);
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
export function getCursor(view) {
|
|
24
|
+
const index = getCursorIndex(view);
|
|
25
|
+
const position = offsetToPosition(view.state.doc, index);
|
|
26
|
+
|
|
27
|
+
return {
|
|
28
|
+
row: position.line,
|
|
29
|
+
column: position.ch,
|
|
30
|
+
};
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
export const getCursorIndex = (view) => view.state.selection.main.head;
|
|
@@ -0,0 +1,176 @@
|
|
|
1
|
+
import {test} from 'supertape';
|
|
2
|
+
import {EditorState} from '@codemirror/state';
|
|
3
|
+
import {EditorView} from '@codemirror/view';
|
|
4
|
+
import {
|
|
5
|
+
offsetToPosition,
|
|
6
|
+
positionToOffset,
|
|
7
|
+
posFromIndex,
|
|
8
|
+
indexFromPos,
|
|
9
|
+
getCursorIndex,
|
|
10
|
+
getCursor,
|
|
11
|
+
} from './position.js';
|
|
12
|
+
|
|
13
|
+
function makeView(document_ = 'hello\nworld') {
|
|
14
|
+
const element = document.createElement('div');
|
|
15
|
+
document.body.appendChild(element);
|
|
16
|
+
|
|
17
|
+
return new EditorView({
|
|
18
|
+
state: EditorState.create({
|
|
19
|
+
doc: document_,
|
|
20
|
+
}),
|
|
21
|
+
parent: element,
|
|
22
|
+
});
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
test('position: offsetToPosition line 0 ch 0 for offset 0', (t) => {
|
|
26
|
+
const view = makeView();
|
|
27
|
+
const result = offsetToPosition(view.state.doc, 0);
|
|
28
|
+
|
|
29
|
+
const expected = {
|
|
30
|
+
line: 0,
|
|
31
|
+
ch: 0,
|
|
32
|
+
};
|
|
33
|
+
|
|
34
|
+
view.destroy();
|
|
35
|
+
|
|
36
|
+
t.deepEqual(result, expected);
|
|
37
|
+
t.end();
|
|
38
|
+
});
|
|
39
|
+
|
|
40
|
+
test('position: offsetToPosition line 1 ch 0 for start of second line', (t) => {
|
|
41
|
+
const view = makeView();
|
|
42
|
+
const result = offsetToPosition(view.state.doc, 6);
|
|
43
|
+
|
|
44
|
+
const expected = {
|
|
45
|
+
line: 1,
|
|
46
|
+
ch: 0,
|
|
47
|
+
};
|
|
48
|
+
|
|
49
|
+
view.destroy();
|
|
50
|
+
|
|
51
|
+
t.deepEqual(result, expected);
|
|
52
|
+
t.end();
|
|
53
|
+
});
|
|
54
|
+
|
|
55
|
+
test('position: positionToOffset 0 for line 0 ch 0', (t) => {
|
|
56
|
+
const view = makeView();
|
|
57
|
+
const result = positionToOffset(view.state.doc, {
|
|
58
|
+
line: 0,
|
|
59
|
+
ch: 0,
|
|
60
|
+
});
|
|
61
|
+
|
|
62
|
+
const expected = 0;
|
|
63
|
+
|
|
64
|
+
view.destroy();
|
|
65
|
+
|
|
66
|
+
t.equal(result, expected);
|
|
67
|
+
t.end();
|
|
68
|
+
});
|
|
69
|
+
|
|
70
|
+
test('position: positionToOffset 6 for start of second line', (t) => {
|
|
71
|
+
const view = makeView();
|
|
72
|
+
const result = positionToOffset(view.state.doc, {
|
|
73
|
+
line: 1,
|
|
74
|
+
ch: 0,
|
|
75
|
+
});
|
|
76
|
+
|
|
77
|
+
const expected = 6;
|
|
78
|
+
|
|
79
|
+
view.destroy();
|
|
80
|
+
|
|
81
|
+
t.equal(result, expected);
|
|
82
|
+
t.end();
|
|
83
|
+
});
|
|
84
|
+
|
|
85
|
+
test('position: posFromIndex returns correct position', (t) => {
|
|
86
|
+
const view = makeView();
|
|
87
|
+
const result = posFromIndex(view, 6);
|
|
88
|
+
|
|
89
|
+
const expected = {
|
|
90
|
+
line: 1,
|
|
91
|
+
ch: 0,
|
|
92
|
+
};
|
|
93
|
+
|
|
94
|
+
view.destroy();
|
|
95
|
+
|
|
96
|
+
t.deepEqual(result, expected);
|
|
97
|
+
t.end();
|
|
98
|
+
});
|
|
99
|
+
|
|
100
|
+
test('position: indexFromPos returns correct offset', (t) => {
|
|
101
|
+
const view = makeView();
|
|
102
|
+
const result = indexFromPos(view, {
|
|
103
|
+
line: 1,
|
|
104
|
+
ch: 0,
|
|
105
|
+
});
|
|
106
|
+
|
|
107
|
+
const expected = 6;
|
|
108
|
+
|
|
109
|
+
view.destroy();
|
|
110
|
+
|
|
111
|
+
t.equal(result, expected);
|
|
112
|
+
t.end();
|
|
113
|
+
});
|
|
114
|
+
|
|
115
|
+
test('position: offsetToPosition clamps negative offset to start', (t) => {
|
|
116
|
+
const view = makeView();
|
|
117
|
+
const result = offsetToPosition(view.state.doc, -1);
|
|
118
|
+
|
|
119
|
+
const expected = {
|
|
120
|
+
line: 0,
|
|
121
|
+
ch: 0,
|
|
122
|
+
};
|
|
123
|
+
|
|
124
|
+
view.destroy();
|
|
125
|
+
|
|
126
|
+
t.deepEqual(result, expected);
|
|
127
|
+
t.end();
|
|
128
|
+
});
|
|
129
|
+
|
|
130
|
+
test('position: offsetToPosition clamps offset beyond end to end', (t) => {
|
|
131
|
+
const view = makeView();
|
|
132
|
+
const result = offsetToPosition(view.state.doc, 999);
|
|
133
|
+
|
|
134
|
+
const expected = {
|
|
135
|
+
line: 1,
|
|
136
|
+
ch: 5,
|
|
137
|
+
};
|
|
138
|
+
|
|
139
|
+
view.destroy();
|
|
140
|
+
|
|
141
|
+
t.deepEqual(result, expected);
|
|
142
|
+
t.end();
|
|
143
|
+
});
|
|
144
|
+
|
|
145
|
+
test('position: getCursorIndex returns a number', (t) => {
|
|
146
|
+
const view = makeView();
|
|
147
|
+
const result = typeof getCursorIndex(view);
|
|
148
|
+
const expected = 'number';
|
|
149
|
+
|
|
150
|
+
view.destroy();
|
|
151
|
+
|
|
152
|
+
t.equal(result, expected);
|
|
153
|
+
t.end();
|
|
154
|
+
});
|
|
155
|
+
|
|
156
|
+
test('position: getCursor returns row and column', (t) => {
|
|
157
|
+
const view = makeView();
|
|
158
|
+
|
|
159
|
+
view.dispatch({
|
|
160
|
+
selection: {
|
|
161
|
+
anchor: 6,
|
|
162
|
+
},
|
|
163
|
+
});
|
|
164
|
+
|
|
165
|
+
const result = getCursor(view);
|
|
166
|
+
|
|
167
|
+
const expected = {
|
|
168
|
+
row: 1,
|
|
169
|
+
column: 0,
|
|
170
|
+
};
|
|
171
|
+
|
|
172
|
+
view.destroy();
|
|
173
|
+
|
|
174
|
+
t.deepEqual(result, expected);
|
|
175
|
+
t.end();
|
|
176
|
+
});
|