@adecore/editor 0.0.1 → 0.17.0-beta.1
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/LICENSE +110 -0
- package/README.md +82 -0
- package/dist/attribution.d.ts +28 -0
- package/dist/attribution.js +55 -0
- package/dist/code-block.d.ts +2 -0
- package/dist/code-block.js +89 -0
- package/dist/column-selection.d.ts +9 -0
- package/dist/column-selection.js +52 -0
- package/dist/controller.d.ts +64 -0
- package/dist/controller.js +772 -0
- package/dist/editor.css +813 -0
- package/dist/engine.d.ts +2 -0
- package/dist/engine.js +604 -0
- package/dist/fake.d.ts +190 -0
- package/dist/fake.js +658 -0
- package/dist/find.d.ts +30 -0
- package/dist/find.js +108 -0
- package/dist/index.d.ts +5 -0
- package/dist/index.js +4 -0
- package/dist/input.d.ts +9 -0
- package/dist/input.js +57 -0
- package/dist/keymap-table.d.ts +102 -0
- package/dist/keymap-table.js +130 -0
- package/dist/keymap.d.ts +36 -0
- package/dist/keymap.js +163 -0
- package/dist/layout.d.ts +167 -0
- package/dist/layout.js +583 -0
- package/dist/lens.d.ts +3 -0
- package/dist/lens.js +29 -0
- package/dist/listeners.d.ts +3 -0
- package/dist/listeners.js +12 -0
- package/dist/metrics.d.ts +8 -0
- package/dist/metrics.js +54 -0
- package/dist/modifier-gesture.d.ts +24 -0
- package/dist/modifier-gesture.js +98 -0
- package/dist/native-input.d.ts +26 -0
- package/dist/native-input.js +147 -0
- package/dist/offsets.d.ts +2 -0
- package/dist/offsets.js +14 -0
- package/dist/outline.d.ts +55 -0
- package/dist/outline.js +181 -0
- package/dist/overview.d.ts +17 -0
- package/dist/overview.js +55 -0
- package/dist/paint.d.ts +86 -0
- package/dist/paint.js +481 -0
- package/dist/php-html.d.ts +9 -0
- package/dist/php-html.js +36 -0
- package/dist/pointer.d.ts +49 -0
- package/dist/pointer.js +243 -0
- package/dist/scroll-animation.d.ts +3 -0
- package/dist/scroll-animation.js +45 -0
- package/dist/scroll.d.ts +28 -0
- package/dist/scroll.js +76 -0
- package/dist/semantic.d.ts +8 -0
- package/dist/semantic.js +47 -0
- package/dist/shiki.d.ts +7 -0
- package/dist/shiki.js +106 -0
- package/dist/smart-keys.d.ts +3 -0
- package/dist/smart-keys.js +14 -0
- package/dist/testing.d.ts +34 -0
- package/dist/testing.js +117 -0
- package/dist/theme-scopes.d.ts +9 -0
- package/dist/theme-scopes.js +61 -0
- package/dist/tokens.d.ts +28 -0
- package/dist/tokens.js +152 -0
- package/dist/tracked-range.d.ts +5 -0
- package/dist/tracked-range.js +20 -0
- package/dist/types.d.ts +342 -0
- package/dist/types.js +1 -0
- package/dist/view.d.ts +382 -0
- package/dist/view.js +2134 -0
- package/package.json +63 -3
- package/src/attribution.ts +85 -0
- package/src/code-block.ts +100 -0
- package/src/column-selection.ts +63 -0
- package/src/controller.ts +838 -0
- package/src/editor.css +813 -0
- package/src/engine.ts +757 -0
- package/src/fake.ts +831 -0
- package/src/find.ts +128 -0
- package/src/index.ts +69 -0
- package/src/input.ts +63 -0
- package/src/keymap-table.ts +163 -0
- package/src/keymap.ts +204 -0
- package/src/layout.ts +754 -0
- package/src/lens.ts +32 -0
- package/src/listeners.ts +15 -0
- package/src/metrics.ts +65 -0
- package/src/modifier-gesture.ts +115 -0
- package/src/native-input.ts +179 -0
- package/src/offsets.ts +16 -0
- package/src/outline.ts +248 -0
- package/src/overview.ts +80 -0
- package/src/paint.ts +608 -0
- package/src/php-html.ts +43 -0
- package/src/pointer.ts +326 -0
- package/src/scroll-animation.ts +48 -0
- package/src/scroll.ts +102 -0
- package/src/semantic.ts +54 -0
- package/src/shiki.ts +118 -0
- package/src/smart-keys.ts +17 -0
- package/src/testing.ts +157 -0
- package/src/theme-scopes.ts +75 -0
- package/src/tokens.ts +175 -0
- package/src/tracked-range.ts +22 -0
- package/src/types.ts +656 -0
- package/src/view.ts +2436 -0
package/dist/find.js
ADDED
|
@@ -0,0 +1,108 @@
|
|
|
1
|
+
import { mapOffset } from './offsets.js';
|
|
2
|
+
/* Past this a count means nothing to a reader, and marking every match would stall the editor. */
|
|
3
|
+
export const FIND_LIMIT = 10000;
|
|
4
|
+
/*
|
|
5
|
+
* The find of the host's find bar: every match marked, one of them current. It searches the model on
|
|
6
|
+
* its own and never touches the selection until the find ends.
|
|
7
|
+
*/
|
|
8
|
+
export class FindController {
|
|
9
|
+
query = null;
|
|
10
|
+
marks = [];
|
|
11
|
+
index = null;
|
|
12
|
+
/* The text a search in the selection is held to, which follows the text through edits; null when nothing was selected. */
|
|
13
|
+
bounds = null;
|
|
14
|
+
/* Where the next refresh looks for the current match from, once: right after what a replace wrote. */
|
|
15
|
+
pendingAnchor;
|
|
16
|
+
model;
|
|
17
|
+
constructor(model) {
|
|
18
|
+
this.model = model;
|
|
19
|
+
}
|
|
20
|
+
get activeQuery() {
|
|
21
|
+
return this.query;
|
|
22
|
+
}
|
|
23
|
+
get active() {
|
|
24
|
+
return this.query !== null;
|
|
25
|
+
}
|
|
26
|
+
get matches() {
|
|
27
|
+
return this.marks;
|
|
28
|
+
}
|
|
29
|
+
get current() {
|
|
30
|
+
return this.index;
|
|
31
|
+
}
|
|
32
|
+
get state() {
|
|
33
|
+
return { count: this.marks.length, current: this.index, ...(this.query?.inSelection === true && this.bounds === null ? { noSelection: true } : {}) };
|
|
34
|
+
}
|
|
35
|
+
/* The range a search in the selection is held to. */
|
|
36
|
+
get scope() {
|
|
37
|
+
return this.query?.inSelection === true ? this.bounds : null;
|
|
38
|
+
}
|
|
39
|
+
get currentMark() {
|
|
40
|
+
return this.index === null ? null : (this.marks[this.index] ?? null);
|
|
41
|
+
}
|
|
42
|
+
/* A new query, or null to end the find. */
|
|
43
|
+
set(query) {
|
|
44
|
+
const wasInSelection = this.query?.inSelection === true;
|
|
45
|
+
this.query = query === null || query.text === '' ? null : query;
|
|
46
|
+
if (this.query?.inSelection !== true) {
|
|
47
|
+
this.bounds = null;
|
|
48
|
+
}
|
|
49
|
+
else if (!wasInSelection) {
|
|
50
|
+
const selection = this.model.getPrimary();
|
|
51
|
+
this.bounds =
|
|
52
|
+
selection.anchor === selection.head
|
|
53
|
+
? null
|
|
54
|
+
: { from: Math.min(selection.anchor, selection.head), to: Math.max(selection.anchor, selection.head) };
|
|
55
|
+
}
|
|
56
|
+
this.refresh();
|
|
57
|
+
}
|
|
58
|
+
/* The text of a search in the selection moved with an edit. */
|
|
59
|
+
mapBounds(changes) {
|
|
60
|
+
if (this.bounds !== null) {
|
|
61
|
+
this.bounds = { from: mapOffset(this.bounds.from, changes), to: mapOffset(this.bounds.to, changes) };
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
/*
|
|
65
|
+
* The matches again, after a new query or an edit. The current one is the first from where the
|
|
66
|
+
* previous one was, or from the cursor, so each letter typed keeps the place instead of starting over.
|
|
67
|
+
*/
|
|
68
|
+
refresh() {
|
|
69
|
+
const previous = this.currentMark;
|
|
70
|
+
const query = this.query;
|
|
71
|
+
if (query === null) {
|
|
72
|
+
this.marks = [];
|
|
73
|
+
this.index = null;
|
|
74
|
+
return;
|
|
75
|
+
}
|
|
76
|
+
let found = [];
|
|
77
|
+
try {
|
|
78
|
+
const inSelection = query.inSelection === true;
|
|
79
|
+
if (!inSelection || this.bounds !== null) {
|
|
80
|
+
found = this.model
|
|
81
|
+
.find(query.text, {
|
|
82
|
+
caseSensitive: query.caseSensitive,
|
|
83
|
+
wholeWord: query.wholeWord,
|
|
84
|
+
regex: query.regex,
|
|
85
|
+
maxResults: FIND_LIMIT,
|
|
86
|
+
...(inSelection ? this.bounds : {})
|
|
87
|
+
})
|
|
88
|
+
.map((match) => ({ from: match.from, to: match.to }));
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
catch {
|
|
92
|
+
// A pattern that does not parse finds nothing; the find bar says it is invalid.
|
|
93
|
+
}
|
|
94
|
+
this.marks = found;
|
|
95
|
+
const selection = this.model.getPrimary();
|
|
96
|
+
const anchor = this.pendingAnchor ?? previous?.from ?? Math.min(selection.anchor, selection.head);
|
|
97
|
+
this.pendingAnchor = undefined;
|
|
98
|
+
const next = found.findIndex((mark) => mark.from >= anchor);
|
|
99
|
+
this.index = found.length === 0 ? null : Math.max(0, next);
|
|
100
|
+
}
|
|
101
|
+
step(direction) {
|
|
102
|
+
const count = this.marks.length;
|
|
103
|
+
if (count === 0) {
|
|
104
|
+
return;
|
|
105
|
+
}
|
|
106
|
+
this.index = this.index === null ? (direction === 1 ? 0 : count - 1) : (this.index + direction + count) % count;
|
|
107
|
+
}
|
|
108
|
+
}
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,5 @@
|
|
|
1
|
+
export { AGENT_COLORS } from './attribution.ts';
|
|
2
|
+
export { createSmartEditorEngine } from './engine.ts';
|
|
3
|
+
export { DEFAULT_SMART_KEYS } from './smart-keys.ts';
|
|
4
|
+
export { shikiScopeColors, shikiTokenizers } from './shiki.ts';
|
|
5
|
+
export type { Editor, EditorAttributionHover, EditorAttributionMark, EditorMarkColor, EditorBlock, EditorChangeKind, EditorChangeMark, EditorCodeVision, EditorCodeVisionEntry, EditorClick, EditorCodeBlockOptions, EditorClickHandler, EditorCommand, EditorContextMenu, EditorEngine, EditorFindQuery, EditorFoldHints, EditorFoldOutline, EditorFoldRange, EditorFoldSymbol, EditorFolds, EditorFindState, EditorReplaceOptions, EditorGutterAction, EditorGutterMarker, EditorIndentation, EditorContentChange, EditorHighlight, EditorHighlightKind, EditorHover, EditorGhostText, EditorInlayHint, EditorKeyHandler, EditorLineAction, EditorLineHighlight, EditorMarker, EditorMessages, EditorMarkerSeverity, EditorReveal, EditorRect, EditorSmartKeys, EditorOptions, EditorPosition, EditorRemoteCursor, EditorRange, EditorTextChange, EditorTrackedRange, EditorRunCommand, EditorSelectionRanges, EditorTheme, EditorViewCommand, EditorWidget, FoldRole, KeyChord, LineToken, ScopeColorSource, ScopeColors, ScopeStyle, EditorSemanticToken, LineTokenizer, SmartEditorEngineOptions, TokenizedLine, TokenizerSource } from './types.ts';
|
package/dist/index.js
ADDED
package/dist/input.d.ts
ADDED
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
import type { Selection, TextEdit } from '@adecore/editor-core';
|
|
2
|
+
export declare function findChange(before: string, after: string): TextEdit;
|
|
3
|
+
export declare function replacementEdits(selections: readonly Selection[], text: string): {
|
|
4
|
+
edits: TextEdit[];
|
|
5
|
+
selections: Selection[];
|
|
6
|
+
};
|
|
7
|
+
export declare function textareaText(text: string): string;
|
|
8
|
+
export declare function modelOffset(text: string, nativeOffset: number): number;
|
|
9
|
+
export declare function textareaOffset(text: string, offset: number): number;
|
package/dist/input.js
ADDED
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
/* The one stretch two texts differ in, found from both ends and never inside a surrogate pair. */
|
|
2
|
+
export function findChange(before, after) {
|
|
3
|
+
let from = 0;
|
|
4
|
+
while (from < before.length && from < after.length && before[from] === after[from]) {
|
|
5
|
+
from++;
|
|
6
|
+
}
|
|
7
|
+
if (from > 0 && /[\uDC00-\uDFFF]/.test(before[from] ?? '') && /[\uD800-\uDBFF]/.test(before[from - 1])) {
|
|
8
|
+
from--;
|
|
9
|
+
}
|
|
10
|
+
let endBefore = before.length;
|
|
11
|
+
let endAfter = after.length;
|
|
12
|
+
while (endBefore > from && endAfter > from && before[endBefore - 1] === after[endAfter - 1]) {
|
|
13
|
+
endBefore--;
|
|
14
|
+
endAfter--;
|
|
15
|
+
}
|
|
16
|
+
if (endBefore < before.length && /[\uDC00-\uDFFF]/.test(before[endBefore] ?? '') && /[\uD800-\uDBFF]/.test(before[endBefore - 1] ?? '')) {
|
|
17
|
+
endBefore++;
|
|
18
|
+
endAfter++;
|
|
19
|
+
}
|
|
20
|
+
return { from, to: endBefore, text: after.slice(from, endAfter) };
|
|
21
|
+
}
|
|
22
|
+
/* One replacement per selection, with the carets after what each inserted. */
|
|
23
|
+
export function replacementEdits(selections, text) {
|
|
24
|
+
const sorted = selections
|
|
25
|
+
.map((selection, index) => ({ from: Math.min(selection.anchor, selection.head), to: Math.max(selection.anchor, selection.head), index }))
|
|
26
|
+
.sort((left, right) => left.from - right.from);
|
|
27
|
+
const edits = [];
|
|
28
|
+
const positions = [];
|
|
29
|
+
let delta = 0;
|
|
30
|
+
for (const range of sorted) {
|
|
31
|
+
edits.push({ from: range.from, to: range.to, text });
|
|
32
|
+
const head = range.from + delta + text.length;
|
|
33
|
+
positions[range.index] = { anchor: head, head };
|
|
34
|
+
delta += text.length - (range.to - range.from);
|
|
35
|
+
}
|
|
36
|
+
return { edits, selections: positions };
|
|
37
|
+
}
|
|
38
|
+
export function textareaText(text) {
|
|
39
|
+
return text.replace(/\r\n?/g, '\n');
|
|
40
|
+
}
|
|
41
|
+
/* A textarea normalizes CRLF; the model keeps its own UTF-16 offsets. */
|
|
42
|
+
export function modelOffset(text, nativeOffset) {
|
|
43
|
+
let native = 0;
|
|
44
|
+
for (let index = 0; index < text.length; index++) {
|
|
45
|
+
if (native === nativeOffset) {
|
|
46
|
+
return index;
|
|
47
|
+
}
|
|
48
|
+
if (text[index] === '\r' && text[index + 1] === '\n') {
|
|
49
|
+
continue;
|
|
50
|
+
}
|
|
51
|
+
native++;
|
|
52
|
+
}
|
|
53
|
+
return text.length;
|
|
54
|
+
}
|
|
55
|
+
export function textareaOffset(text, offset) {
|
|
56
|
+
return textareaText(text.slice(0, offset)).length;
|
|
57
|
+
}
|
|
@@ -0,0 +1,102 @@
|
|
|
1
|
+
import type { KeyChord } from './types.ts';
|
|
2
|
+
export interface TakenKey {
|
|
3
|
+
readonly platform: string;
|
|
4
|
+
readonly by: string;
|
|
5
|
+
}
|
|
6
|
+
export interface KeyBinding {
|
|
7
|
+
readonly mac: string | null;
|
|
8
|
+
readonly other: string | null;
|
|
9
|
+
readonly takenMac?: TakenKey;
|
|
10
|
+
readonly takenOther?: TakenKey;
|
|
11
|
+
}
|
|
12
|
+
export declare const KEYMAP: {
|
|
13
|
+
readonly undo: KeyBinding;
|
|
14
|
+
readonly redo: KeyBinding;
|
|
15
|
+
readonly selectAll: KeyBinding;
|
|
16
|
+
readonly save: KeyBinding;
|
|
17
|
+
readonly duplicateLine: KeyBinding;
|
|
18
|
+
readonly deleteLine: KeyBinding;
|
|
19
|
+
readonly moveLineUp: KeyBinding;
|
|
20
|
+
readonly moveLineDown: KeyBinding;
|
|
21
|
+
readonly toggleLineComment: KeyBinding;
|
|
22
|
+
readonly toggleBlockComment: KeyBinding;
|
|
23
|
+
readonly joinLines: KeyBinding;
|
|
24
|
+
readonly startNewLine: KeyBinding;
|
|
25
|
+
readonly startNewLineBefore: KeyBinding;
|
|
26
|
+
readonly splitLine: KeyBinding;
|
|
27
|
+
readonly toggleCase: KeyBinding;
|
|
28
|
+
readonly autoIndentLines: KeyBinding;
|
|
29
|
+
readonly expandSelection: KeyBinding;
|
|
30
|
+
readonly shrinkSelection: KeyBinding;
|
|
31
|
+
readonly matchBrace: KeyBinding;
|
|
32
|
+
readonly selectNextOccurrence: KeyBinding;
|
|
33
|
+
readonly unselectOccurrence: KeyBinding;
|
|
34
|
+
readonly selectAllOccurrences: KeyBinding;
|
|
35
|
+
readonly addCaretPerSelectedLine: KeyBinding;
|
|
36
|
+
readonly addCaretAbove: KeyBinding;
|
|
37
|
+
readonly addCaretBelow: KeyBinding;
|
|
38
|
+
readonly toggleColumnMode: KeyBinding;
|
|
39
|
+
readonly wordLeft: KeyBinding;
|
|
40
|
+
readonly wordRight: KeyBinding;
|
|
41
|
+
readonly selectWordLeft: KeyBinding;
|
|
42
|
+
readonly selectWordRight: KeyBinding;
|
|
43
|
+
readonly deleteWordLeft: KeyBinding;
|
|
44
|
+
readonly deleteWordRight: KeyBinding;
|
|
45
|
+
readonly smartHome: KeyBinding;
|
|
46
|
+
readonly selectSmartHome: KeyBinding;
|
|
47
|
+
readonly smartEnd: KeyBinding;
|
|
48
|
+
readonly selectSmartEnd: KeyBinding;
|
|
49
|
+
readonly textStart: KeyBinding;
|
|
50
|
+
readonly selectTextStart: KeyBinding;
|
|
51
|
+
readonly textEnd: KeyBinding;
|
|
52
|
+
readonly selectTextEnd: KeyBinding;
|
|
53
|
+
readonly collapse: KeyBinding;
|
|
54
|
+
readonly expand: KeyBinding;
|
|
55
|
+
readonly collapseAll: KeyBinding;
|
|
56
|
+
readonly expandAll: KeyBinding;
|
|
57
|
+
readonly collapseRecursively: KeyBinding;
|
|
58
|
+
readonly expandRecursively: KeyBinding;
|
|
59
|
+
readonly foldSelection: KeyBinding;
|
|
60
|
+
readonly collapseDocComments: KeyBinding;
|
|
61
|
+
readonly expandDocComments: KeyBinding;
|
|
62
|
+
readonly expandAllToLevel1: KeyBinding;
|
|
63
|
+
readonly expandAllToLevel2: KeyBinding;
|
|
64
|
+
readonly expandAllToLevel3: KeyBinding;
|
|
65
|
+
readonly expandAllToLevel4: KeyBinding;
|
|
66
|
+
readonly expandAllToLevel5: KeyBinding;
|
|
67
|
+
readonly triggerCompletion: KeyBinding;
|
|
68
|
+
readonly parameterInfo: KeyBinding;
|
|
69
|
+
readonly quickInfo: KeyBinding;
|
|
70
|
+
readonly codeActions: KeyBinding;
|
|
71
|
+
readonly refactorThis: KeyBinding;
|
|
72
|
+
readonly renameSymbol: KeyBinding;
|
|
73
|
+
readonly organizeImports: KeyBinding;
|
|
74
|
+
readonly formatDocument: KeyBinding;
|
|
75
|
+
readonly selectionToChat: KeyBinding;
|
|
76
|
+
readonly inlineEdit: KeyBinding;
|
|
77
|
+
readonly suggestInline: KeyBinding;
|
|
78
|
+
readonly acceptGhostWord: KeyBinding;
|
|
79
|
+
readonly goToSymbol: KeyBinding;
|
|
80
|
+
readonly goToDefinition: KeyBinding;
|
|
81
|
+
readonly goToTypeDefinition: KeyBinding;
|
|
82
|
+
readonly goToImplementation: KeyBinding;
|
|
83
|
+
readonly peekDefinition: KeyBinding;
|
|
84
|
+
readonly peekReferences: KeyBinding;
|
|
85
|
+
readonly historyBack: KeyBinding;
|
|
86
|
+
readonly historyForward: KeyBinding;
|
|
87
|
+
readonly recentLocations: KeyBinding;
|
|
88
|
+
readonly nextProblem: KeyBinding;
|
|
89
|
+
readonly previousProblem: KeyBinding;
|
|
90
|
+
readonly nextHighlight: KeyBinding;
|
|
91
|
+
readonly previousHighlight: KeyBinding;
|
|
92
|
+
readonly findNext: KeyBinding;
|
|
93
|
+
readonly findPrevious: KeyBinding;
|
|
94
|
+
readonly replace: KeyBinding;
|
|
95
|
+
};
|
|
96
|
+
export type KeymapId = keyof typeof KEYMAP;
|
|
97
|
+
export type Keymap = Readonly<Record<KeymapId, KeyBinding>>;
|
|
98
|
+
export type KeymapOverrides = Partial<Record<KeymapId, Partial<KeyBinding>>>;
|
|
99
|
+
export declare const KEYMAP_IDS: KeymapId[];
|
|
100
|
+
export declare function resolveKeymap(overrides?: KeymapOverrides): Keymap;
|
|
101
|
+
export declare function chordOf(id: KeymapId, apple: boolean, keymap?: Keymap): string | null;
|
|
102
|
+
export declare function parseChord(text: string): KeyChord;
|
|
@@ -0,0 +1,130 @@
|
|
|
1
|
+
function bind(mac, other = mac, taken = {}) {
|
|
2
|
+
return {
|
|
3
|
+
mac,
|
|
4
|
+
other,
|
|
5
|
+
...(taken.mac === undefined ? {} : { takenMac: taken.mac }),
|
|
6
|
+
...(taken.other === undefined ? {} : { takenOther: taken.other })
|
|
7
|
+
};
|
|
8
|
+
}
|
|
9
|
+
export const KEYMAP = {
|
|
10
|
+
undo: bind('Mod+Z'),
|
|
11
|
+
redo: bind('Mod+Shift+Z'),
|
|
12
|
+
selectAll: bind('Mod+A'),
|
|
13
|
+
save: bind('Mod+S'),
|
|
14
|
+
duplicateLine: bind('Mod+D'),
|
|
15
|
+
deleteLine: bind('Mod+Backspace', 'Mod+Y'),
|
|
16
|
+
moveLineUp: bind('Alt+Shift+ArrowUp'),
|
|
17
|
+
moveLineDown: bind('Alt+Shift+ArrowDown'),
|
|
18
|
+
toggleLineComment: bind('Mod+/'),
|
|
19
|
+
toggleBlockComment: bind('Mod+Alt+/', 'Mod+Shift+/'),
|
|
20
|
+
joinLines: bind('Ctrl+Shift+J'),
|
|
21
|
+
startNewLine: bind('Shift+Enter'),
|
|
22
|
+
startNewLineBefore: bind('Mod+Alt+Enter'),
|
|
23
|
+
splitLine: bind('Mod+Enter'),
|
|
24
|
+
toggleCase: bind('Mod+Shift+U'),
|
|
25
|
+
autoIndentLines: bind('Ctrl+Alt+I'),
|
|
26
|
+
expandSelection: bind('Alt+ArrowUp', 'Ctrl+W'),
|
|
27
|
+
shrinkSelection: bind('Alt+ArrowDown'),
|
|
28
|
+
matchBrace: bind('Ctrl+M', 'Ctrl+Shift+M'),
|
|
29
|
+
selectNextOccurrence: bind('Ctrl+G', 'Alt+J'),
|
|
30
|
+
unselectOccurrence: bind('Ctrl+Shift+G', 'Alt+Shift+J'),
|
|
31
|
+
selectAllOccurrences: bind('Meta+Ctrl+G', 'Ctrl+Alt+Shift+J'),
|
|
32
|
+
addCaretPerSelectedLine: bind('Alt+Shift+G'),
|
|
33
|
+
// Tap the modifier twice, hold it and press an arrow: Option on macOS, Ctrl elsewhere (`modifier-gesture.ts`). It has no chord to print.
|
|
34
|
+
addCaretAbove: bind(null),
|
|
35
|
+
addCaretBelow: bind(null),
|
|
36
|
+
toggleColumnMode: bind('Mod+Shift+8'),
|
|
37
|
+
wordLeft: bind('Alt+ArrowLeft', 'Ctrl+ArrowLeft'),
|
|
38
|
+
wordRight: bind('Alt+ArrowRight', 'Ctrl+ArrowRight'),
|
|
39
|
+
selectWordLeft: bind('Alt+Shift+ArrowLeft', 'Ctrl+Shift+ArrowLeft'),
|
|
40
|
+
selectWordRight: bind('Alt+Shift+ArrowRight', 'Ctrl+Shift+ArrowRight'),
|
|
41
|
+
deleteWordLeft: bind('Alt+Backspace', 'Ctrl+Backspace'),
|
|
42
|
+
deleteWordRight: bind('Alt+Delete', 'Ctrl+Delete'),
|
|
43
|
+
smartHome: bind('Meta+ArrowLeft', null),
|
|
44
|
+
selectSmartHome: bind('Meta+Shift+ArrowLeft', null),
|
|
45
|
+
smartEnd: bind('Meta+ArrowRight', null),
|
|
46
|
+
selectSmartEnd: bind('Meta+Shift+ArrowRight', null),
|
|
47
|
+
textStart: bind('Meta+ArrowUp', null),
|
|
48
|
+
selectTextStart: bind('Meta+Shift+ArrowUp', null),
|
|
49
|
+
textEnd: bind('Meta+ArrowDown', null),
|
|
50
|
+
selectTextEnd: bind('Meta+Shift+ArrowDown', null),
|
|
51
|
+
collapse: bind('Mod+-'),
|
|
52
|
+
expand: bind('Mod+='),
|
|
53
|
+
collapseAll: bind('Mod+Shift+-'),
|
|
54
|
+
expandAll: bind('Mod+Shift+='),
|
|
55
|
+
collapseRecursively: bind('Mod+Alt+-'),
|
|
56
|
+
expandRecursively: bind('Mod+Alt+='),
|
|
57
|
+
foldSelection: bind('Mod+.'),
|
|
58
|
+
// The platform binds the levels to a chord of two strokes, which this table cannot write, so they live in the menu and the palette.
|
|
59
|
+
collapseDocComments: bind(null),
|
|
60
|
+
expandDocComments: bind(null),
|
|
61
|
+
expandAllToLevel1: bind(null),
|
|
62
|
+
expandAllToLevel2: bind(null),
|
|
63
|
+
expandAllToLevel3: bind(null),
|
|
64
|
+
expandAllToLevel4: bind(null),
|
|
65
|
+
expandAllToLevel5: bind(null),
|
|
66
|
+
triggerCompletion: bind('Ctrl+Space'),
|
|
67
|
+
parameterInfo: bind('Mod+P'),
|
|
68
|
+
quickInfo: bind('Ctrl+J', 'Ctrl+Q'),
|
|
69
|
+
codeActions: bind('Alt+Enter'),
|
|
70
|
+
refactorThis: bind('Ctrl+T', 'Ctrl+Alt+Shift+T'),
|
|
71
|
+
renameSymbol: bind('Shift+F6'),
|
|
72
|
+
organizeImports: bind('Ctrl+Alt+O'),
|
|
73
|
+
formatDocument: bind('Mod+Alt+L'),
|
|
74
|
+
selectionToChat: bind(null),
|
|
75
|
+
inlineEdit: bind(null),
|
|
76
|
+
suggestInline: bind(null),
|
|
77
|
+
acceptGhostWord: bind(null),
|
|
78
|
+
goToSymbol: bind('Mod+F12'),
|
|
79
|
+
goToDefinition: bind('Mod+B'),
|
|
80
|
+
goToTypeDefinition: bind('Ctrl+Shift+B'),
|
|
81
|
+
goToImplementation: bind('Mod+Alt+B'),
|
|
82
|
+
peekDefinition: bind('Alt+Space', 'Ctrl+Shift+I'),
|
|
83
|
+
peekReferences: bind('Alt+F7'),
|
|
84
|
+
historyBack: bind('Mod+[', 'Ctrl+Alt+ArrowLeft'),
|
|
85
|
+
historyForward: bind('Mod+]', 'Ctrl+Alt+ArrowRight'),
|
|
86
|
+
recentLocations: bind('Mod+Shift+E'),
|
|
87
|
+
nextProblem: bind('F2'),
|
|
88
|
+
previousProblem: bind('Alt+Shift+F2'),
|
|
89
|
+
nextHighlight: bind('Ctrl+Alt+ArrowDown', 'Alt+F3'),
|
|
90
|
+
previousHighlight: bind('Ctrl+Alt+ArrowUp', 'Alt+Shift+F3'),
|
|
91
|
+
findNext: bind('Mod+G', 'Ctrl+L'),
|
|
92
|
+
findPrevious: bind('Mod+Shift+G', 'Ctrl+Shift+L'),
|
|
93
|
+
replace: bind('Mod+R')
|
|
94
|
+
};
|
|
95
|
+
export const KEYMAP_IDS = Object.keys(KEYMAP);
|
|
96
|
+
export function resolveKeymap(overrides = {}) {
|
|
97
|
+
return Object.fromEntries(KEYMAP_IDS.map((id) => [id, { ...KEYMAP[id], ...overrides[id] }]));
|
|
98
|
+
}
|
|
99
|
+
/* The chord of a command on a platform, or null when the platform has none. */
|
|
100
|
+
export function chordOf(id, apple, keymap = KEYMAP) {
|
|
101
|
+
return keymap[id][apple ? 'mac' : 'other'];
|
|
102
|
+
}
|
|
103
|
+
const MODIFIERS = new Map([
|
|
104
|
+
['Mod', 'mod'],
|
|
105
|
+
['Ctrl', 'ctrl'],
|
|
106
|
+
['Meta', 'meta'],
|
|
107
|
+
['Alt', 'alt'],
|
|
108
|
+
['Shift', 'shift']
|
|
109
|
+
]);
|
|
110
|
+
/* Reads `Mod+Shift+K`. Throws on a text it cannot read, so a typo fails before a binding is used. */
|
|
111
|
+
export function parseChord(text) {
|
|
112
|
+
const words = text.endsWith('++') ? [...text.slice(0, -2).split('+'), '+'] : text.split('+');
|
|
113
|
+
const chord = { mod: false, ctrl: false, meta: false, alt: false, shift: false, key: '' };
|
|
114
|
+
for (const word of words) {
|
|
115
|
+
const modifier = MODIFIERS.get(word);
|
|
116
|
+
if (modifier !== undefined) {
|
|
117
|
+
chord[modifier] = true;
|
|
118
|
+
}
|
|
119
|
+
else if (word === '' || chord.key !== '') {
|
|
120
|
+
throw new Error(`Cannot read the chord "${text}"`);
|
|
121
|
+
}
|
|
122
|
+
else {
|
|
123
|
+
chord.key = word.length === 1 ? word.toUpperCase() : word;
|
|
124
|
+
}
|
|
125
|
+
}
|
|
126
|
+
if (chord.key === '') {
|
|
127
|
+
throw new Error(`The chord "${text}" names no key`);
|
|
128
|
+
}
|
|
129
|
+
return chord;
|
|
130
|
+
}
|
package/dist/keymap.d.ts
ADDED
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
import type { EditorCommand } from '@adecore/editor-core';
|
|
2
|
+
import type { EditorViewCommand } from './types.ts';
|
|
3
|
+
import { type Keymap } from './keymap-table.ts';
|
|
4
|
+
import type { KeyChord } from './types.ts';
|
|
5
|
+
export interface KeyLike {
|
|
6
|
+
key: string;
|
|
7
|
+
code?: string;
|
|
8
|
+
ctrlKey: boolean;
|
|
9
|
+
metaKey: boolean;
|
|
10
|
+
altKey: boolean;
|
|
11
|
+
shiftKey: boolean;
|
|
12
|
+
}
|
|
13
|
+
export type MoveKey = 'ArrowLeft' | 'ArrowRight' | 'ArrowUp' | 'ArrowDown' | 'PageUp' | 'PageDown';
|
|
14
|
+
export type KeyAction = {
|
|
15
|
+
readonly type: 'command';
|
|
16
|
+
readonly command: EditorCommand;
|
|
17
|
+
} | {
|
|
18
|
+
readonly type: 'move';
|
|
19
|
+
readonly key: MoveKey;
|
|
20
|
+
readonly extend: boolean;
|
|
21
|
+
} | {
|
|
22
|
+
readonly type: 'edge';
|
|
23
|
+
readonly end: boolean;
|
|
24
|
+
readonly extend: boolean;
|
|
25
|
+
} | {
|
|
26
|
+
readonly type: 'bracket';
|
|
27
|
+
} | {
|
|
28
|
+
readonly type: 'view';
|
|
29
|
+
readonly command: EditorViewCommand;
|
|
30
|
+
} | {
|
|
31
|
+
readonly type: 'save';
|
|
32
|
+
} | {
|
|
33
|
+
readonly type: 'escape';
|
|
34
|
+
};
|
|
35
|
+
export declare function chordMatches(chord: KeyChord, event: KeyLike, apple: boolean): boolean;
|
|
36
|
+
export declare function keyAction(event: KeyLike, apple: boolean, keymap?: Keymap): KeyAction | null;
|
package/dist/keymap.js
ADDED
|
@@ -0,0 +1,163 @@
|
|
|
1
|
+
import { chordOf, KEYMAP, KEYMAP_IDS, parseChord } from './keymap-table.js';
|
|
2
|
+
const MOVES = new Set(['ArrowLeft', 'ArrowRight', 'ArrowUp', 'ArrowDown', 'PageUp', 'PageDown']);
|
|
3
|
+
function command(name) {
|
|
4
|
+
return { type: 'command', command: name };
|
|
5
|
+
}
|
|
6
|
+
/* What each key of the table does in the editor. A key of the table that is not here is a language command, which the host answers. */
|
|
7
|
+
const ACTIONS = {
|
|
8
|
+
undo: command('undo'),
|
|
9
|
+
redo: command('redo'),
|
|
10
|
+
selectAll: command('selectAll'),
|
|
11
|
+
save: { type: 'save' },
|
|
12
|
+
duplicateLine: command('duplicateLine'),
|
|
13
|
+
deleteLine: command('deleteLine'),
|
|
14
|
+
moveLineUp: command('moveLineUp'),
|
|
15
|
+
moveLineDown: command('moveLineDown'),
|
|
16
|
+
toggleLineComment: command('toggleLineComment'),
|
|
17
|
+
toggleBlockComment: command('toggleBlockComment'),
|
|
18
|
+
joinLines: command('joinLines'),
|
|
19
|
+
startNewLine: command('startNewLine'),
|
|
20
|
+
startNewLineBefore: command('startNewLineBefore'),
|
|
21
|
+
splitLine: command('splitLine'),
|
|
22
|
+
toggleCase: command('toggleCase'),
|
|
23
|
+
autoIndentLines: command('autoIndentLines'),
|
|
24
|
+
expandSelection: command('expandSelection'),
|
|
25
|
+
shrinkSelection: command('shrinkSelection'),
|
|
26
|
+
matchBrace: { type: 'bracket' },
|
|
27
|
+
selectNextOccurrence: command('selectNextOccurrence'),
|
|
28
|
+
unselectOccurrence: command('unselectOccurrence'),
|
|
29
|
+
selectAllOccurrences: command('selectAllOccurrences'),
|
|
30
|
+
addCaretPerSelectedLine: command('addCaretPerSelectedLine'),
|
|
31
|
+
wordLeft: command('wordLeft'),
|
|
32
|
+
wordRight: command('wordRight'),
|
|
33
|
+
selectWordLeft: command('selectWordLeft'),
|
|
34
|
+
selectWordRight: command('selectWordRight'),
|
|
35
|
+
deleteWordLeft: command('deleteWordLeft'),
|
|
36
|
+
deleteWordRight: command('deleteWordRight'),
|
|
37
|
+
smartHome: command('smartHome'),
|
|
38
|
+
selectSmartHome: command('selectSmartHome'),
|
|
39
|
+
smartEnd: command('smartEnd'),
|
|
40
|
+
selectSmartEnd: command('selectSmartEnd'),
|
|
41
|
+
textStart: { type: 'edge', end: false, extend: false },
|
|
42
|
+
selectTextStart: { type: 'edge', end: false, extend: true },
|
|
43
|
+
textEnd: { type: 'edge', end: true, extend: false },
|
|
44
|
+
selectTextEnd: { type: 'edge', end: true, extend: true },
|
|
45
|
+
collapse: { type: 'view', command: 'collapseRegion' },
|
|
46
|
+
expand: { type: 'view', command: 'expandRegion' },
|
|
47
|
+
collapseAll: { type: 'view', command: 'collapseAllRegions' },
|
|
48
|
+
expandAll: { type: 'view', command: 'expandAllRegions' },
|
|
49
|
+
collapseRecursively: { type: 'view', command: 'collapseRegionRecursively' },
|
|
50
|
+
expandRecursively: { type: 'view', command: 'expandRegionRecursively' },
|
|
51
|
+
foldSelection: { type: 'view', command: 'foldSelection' },
|
|
52
|
+
collapseDocComments: { type: 'view', command: 'collapseDocComments' },
|
|
53
|
+
expandDocComments: { type: 'view', command: 'expandDocComments' },
|
|
54
|
+
expandAllToLevel1: { type: 'view', command: 'expandAllToLevel1' },
|
|
55
|
+
expandAllToLevel2: { type: 'view', command: 'expandAllToLevel2' },
|
|
56
|
+
expandAllToLevel3: { type: 'view', command: 'expandAllToLevel3' },
|
|
57
|
+
expandAllToLevel4: { type: 'view', command: 'expandAllToLevel4' },
|
|
58
|
+
expandAllToLevel5: { type: 'view', command: 'expandAllToLevel5' },
|
|
59
|
+
toggleColumnMode: { type: 'view', command: 'toggleColumnMode' }
|
|
60
|
+
};
|
|
61
|
+
const PUNCTUATION_CODES = {
|
|
62
|
+
',': 'Comma',
|
|
63
|
+
'.': 'Period',
|
|
64
|
+
'/': 'Slash',
|
|
65
|
+
';': 'Semicolon',
|
|
66
|
+
"'": 'Quote',
|
|
67
|
+
'`': 'Backquote',
|
|
68
|
+
'\\': 'Backslash',
|
|
69
|
+
'[': 'BracketLeft',
|
|
70
|
+
']': 'BracketRight'
|
|
71
|
+
};
|
|
72
|
+
/* The fold keys sit on the minus and the equals key, which Shift and Option turn into other characters. */
|
|
73
|
+
const FOLD_CODES = { '-': ['Minus', 'NumpadSubtract'], '=': ['Equal', 'NumpadAdd'] };
|
|
74
|
+
/* Letters and punctuation by the physical key when the event has one, since Option changes the character on macOS and Shift on every platform. */
|
|
75
|
+
function keyMatches(chordKey, event) {
|
|
76
|
+
const code = event.code;
|
|
77
|
+
if (/^[A-Z]$/.test(chordKey)) {
|
|
78
|
+
return code ? code === `Key${chordKey}` : event.key.toUpperCase() === chordKey;
|
|
79
|
+
}
|
|
80
|
+
if (/^[0-9]$/.test(chordKey)) {
|
|
81
|
+
return code ? code === `Digit${chordKey}` || code === `Numpad${chordKey}` : event.key === chordKey;
|
|
82
|
+
}
|
|
83
|
+
const fold = FOLD_CODES[chordKey];
|
|
84
|
+
if (fold !== undefined) {
|
|
85
|
+
return (code !== undefined && fold.includes(code)) || event.key === chordKey || (chordKey === '=' && event.key === '+');
|
|
86
|
+
}
|
|
87
|
+
const punctuation = PUNCTUATION_CODES[chordKey];
|
|
88
|
+
if (punctuation !== undefined) {
|
|
89
|
+
return code ? code === punctuation : event.key === chordKey;
|
|
90
|
+
}
|
|
91
|
+
if (chordKey === 'Space') {
|
|
92
|
+
return event.key === ' ' || code === 'Space';
|
|
93
|
+
}
|
|
94
|
+
return event.key === chordKey;
|
|
95
|
+
}
|
|
96
|
+
/* Whether an event is a chord, with `mod` as Cmd on macOS and Ctrl elsewhere and every modifier the chord does not name up. */
|
|
97
|
+
export function chordMatches(chord, event, apple) {
|
|
98
|
+
const wantsMeta = chord.meta || (chord.mod && apple);
|
|
99
|
+
const wantsCtrl = chord.ctrl || (chord.mod && !apple);
|
|
100
|
+
return (wantsMeta === event.metaKey &&
|
|
101
|
+
wantsCtrl === event.ctrlKey &&
|
|
102
|
+
chord.alt === event.altKey &&
|
|
103
|
+
chord.shift === event.shiftKey &&
|
|
104
|
+
keyMatches(chord.key, event));
|
|
105
|
+
}
|
|
106
|
+
const bindings = new WeakMap();
|
|
107
|
+
function bound(apple, keymap) {
|
|
108
|
+
return KEYMAP_IDS.flatMap((id) => {
|
|
109
|
+
const text = chordOf(id, apple, keymap);
|
|
110
|
+
const action = ACTIONS[id];
|
|
111
|
+
return text === null || action === undefined ? [] : [{ chord: parseChord(text), action }];
|
|
112
|
+
});
|
|
113
|
+
}
|
|
114
|
+
/*
|
|
115
|
+
* What a key does in the editor, or null when it is not the editor's, which leaves it to the page and
|
|
116
|
+
* to the textarea. Movement of the caret is the view's, since it depends on how the lines are drawn.
|
|
117
|
+
*/
|
|
118
|
+
export function keyAction(event, apple, keymap = KEYMAP) {
|
|
119
|
+
let resolved = bindings.get(keymap);
|
|
120
|
+
if (resolved === undefined) {
|
|
121
|
+
resolved = { mac: bound(true, keymap), other: bound(false, keymap) };
|
|
122
|
+
bindings.set(keymap, resolved);
|
|
123
|
+
}
|
|
124
|
+
for (const { chord, action } of resolved[apple ? 'mac' : 'other']) {
|
|
125
|
+
if (chordMatches(chord, event, apple)) {
|
|
126
|
+
return action;
|
|
127
|
+
}
|
|
128
|
+
}
|
|
129
|
+
const key = event.key.toLowerCase();
|
|
130
|
+
const mod = apple ? event.metaKey : event.ctrlKey;
|
|
131
|
+
const { altKey: alt, shiftKey: shift, ctrlKey: ctrl, metaKey: meta } = event;
|
|
132
|
+
if (mod && !alt && (key === 'home' || key === 'end')) {
|
|
133
|
+
return { type: 'edge', end: key === 'end', extend: shift };
|
|
134
|
+
}
|
|
135
|
+
if (mod || alt || ctrl || meta) {
|
|
136
|
+
return null;
|
|
137
|
+
}
|
|
138
|
+
if (key === 'home') {
|
|
139
|
+
return command(shift ? 'selectSmartHome' : 'smartHome');
|
|
140
|
+
}
|
|
141
|
+
if (key === 'end') {
|
|
142
|
+
return command(shift ? 'selectSmartEnd' : 'smartEnd');
|
|
143
|
+
}
|
|
144
|
+
if (key === 'tab') {
|
|
145
|
+
return command(shift ? 'outdent' : 'insertTab');
|
|
146
|
+
}
|
|
147
|
+
if (key === 'enter') {
|
|
148
|
+
return command('insertNewline');
|
|
149
|
+
}
|
|
150
|
+
if (key === 'backspace') {
|
|
151
|
+
return command('smartBackspace');
|
|
152
|
+
}
|
|
153
|
+
if (key === 'delete') {
|
|
154
|
+
return command('deleteForward');
|
|
155
|
+
}
|
|
156
|
+
if (MOVES.has(event.key)) {
|
|
157
|
+
return { type: 'move', key: event.key, extend: shift };
|
|
158
|
+
}
|
|
159
|
+
if (key === 'escape') {
|
|
160
|
+
return { type: 'escape' };
|
|
161
|
+
}
|
|
162
|
+
return null;
|
|
163
|
+
}
|