@adecore/editor 0.42.0 → 0.44.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/dist/controller.d.ts +9 -2
- package/dist/controller.js +82 -8
- package/dist/editor.css +13 -0
- package/dist/engine.js +44 -3
- package/dist/fake.d.ts +23 -2
- package/dist/fake.js +80 -4
- package/dist/index.d.ts +3 -1
- package/dist/index.js +2 -0
- package/dist/keymap-table.d.ts +25 -1
- package/dist/keymap-table.js +30 -3
- package/dist/keymap.js +14 -0
- package/dist/layout.d.ts +2 -0
- package/dist/layout.js +6 -1
- package/dist/outline.d.ts +1 -0
- package/dist/outline.js +10 -0
- package/dist/paint.js +12 -2
- package/dist/text-queries.d.ts +10 -0
- package/dist/text-queries.js +24 -0
- package/dist/types.d.ts +41 -3
- package/dist/view.d.ts +11 -0
- package/dist/view.js +73 -8
- package/package.json +2 -2
- package/src/controller.ts +110 -9
- package/src/editor.css +13 -0
- package/src/engine.ts +60 -3
- package/src/fake.ts +109 -4
- package/src/index.ts +8 -0
- package/src/keymap-table.ts +55 -4
- package/src/keymap.ts +14 -0
- package/src/layout.ts +9 -1
- package/src/outline.ts +13 -0
- package/src/paint.ts +11 -2
- package/src/text-queries.ts +45 -0
- package/src/types.ts +77 -3
- package/src/view.ts +95 -9
package/src/keymap.ts
CHANGED
|
@@ -44,10 +44,15 @@ const ACTIONS: Partial<Record<KeymapId, KeyAction>> = {
|
|
|
44
44
|
deleteLine: command('deleteLine'),
|
|
45
45
|
moveLineUp: command('moveLineUp'),
|
|
46
46
|
moveLineDown: command('moveLineDown'),
|
|
47
|
+
moveStatementUp: command('moveStatementUp'),
|
|
48
|
+
moveStatementDown: command('moveStatementDown'),
|
|
47
49
|
toggleLineComment: command('toggleLineComment'),
|
|
48
50
|
toggleBlockComment: command('toggleBlockComment'),
|
|
49
51
|
joinLines: command('joinLines'),
|
|
50
52
|
startNewLine: command('startNewLine'),
|
|
53
|
+
completeCurrentStatement: command('completeCurrentStatement'),
|
|
54
|
+
hippieCompletion: command('hippieCompletion'),
|
|
55
|
+
hippieBackwardCompletion: command('hippieBackwardCompletion'),
|
|
51
56
|
startNewLineBefore: command('startNewLineBefore'),
|
|
52
57
|
splitLine: command('splitLine'),
|
|
53
58
|
toggleCase: command('toggleCase'),
|
|
@@ -60,6 +65,15 @@ const ACTIONS: Partial<Record<KeymapId, KeyAction>> = {
|
|
|
60
65
|
expandSelection: command('expandSelection'),
|
|
61
66
|
shrinkSelection: command('shrinkSelection'),
|
|
62
67
|
matchBrace: { type: 'bracket' },
|
|
68
|
+
methodUp: view('methodUp'),
|
|
69
|
+
methodDown: view('methodDown'),
|
|
70
|
+
pasteAsPlainText: view('pasteAsPlainText'),
|
|
71
|
+
nextChange: view('nextChange'),
|
|
72
|
+
previousChange: view('previousChange'),
|
|
73
|
+
codeBlockStart: command('codeBlockStart'),
|
|
74
|
+
codeBlockEnd: command('codeBlockEnd'),
|
|
75
|
+
codeBlockStartWithSelection: command('codeBlockStartWithSelection'),
|
|
76
|
+
codeBlockEndWithSelection: command('codeBlockEndWithSelection'),
|
|
63
77
|
selectNextOccurrence: command('selectNextOccurrence'),
|
|
64
78
|
unselectOccurrence: command('unselectOccurrence'),
|
|
65
79
|
selectAllOccurrences: command('selectAllOccurrences'),
|
package/src/layout.ts
CHANGED
|
@@ -57,8 +57,13 @@ export interface Inlay {
|
|
|
57
57
|
composition?: boolean;
|
|
58
58
|
/* A suggestion the host drew after the caret: text in the code face with no padding, which the caret stays in front of. */
|
|
59
59
|
ghost?: boolean;
|
|
60
|
+
/* A square of this CSS color in place of the text, such as the color a literal in the code stands for. */
|
|
61
|
+
swatch?: string;
|
|
60
62
|
}
|
|
61
63
|
|
|
64
|
+
/* The side of a swatch in pixels, without its gaps. */
|
|
65
|
+
export const SWATCH_SIZE = 12;
|
|
66
|
+
|
|
62
67
|
/* A row of the host's own DOM above or below a line, as tall as the host makes it. */
|
|
63
68
|
export interface BlockWidget {
|
|
64
69
|
id: string;
|
|
@@ -366,10 +371,13 @@ export function scanDenseLine(
|
|
|
366
371
|
const inlay = lineInlays[nextInlay++]!;
|
|
367
372
|
const shown = visibleBidi(inlay.text);
|
|
368
373
|
|
|
374
|
+
const gaps = (inlay.gapLeft ?? 0) + (inlay.gapRight ?? 0);
|
|
369
375
|
const width =
|
|
370
376
|
inlay.composition || inlay.ghost
|
|
371
377
|
? metrics.measureText(shown)
|
|
372
|
-
:
|
|
378
|
+
: inlay.swatch !== undefined
|
|
379
|
+
? SWATCH_SIZE + gaps
|
|
380
|
+
: Math.max(8, metrics.measureInlay(shown) + 12) + gaps;
|
|
373
381
|
|
|
374
382
|
geometry.inlays.push({ inlay, x, width, subRow: row });
|
|
375
383
|
x += width;
|
package/src/outline.ts
CHANGED
|
@@ -215,6 +215,19 @@ export class Outline {
|
|
|
215
215
|
this.version++;
|
|
216
216
|
}
|
|
217
217
|
|
|
218
|
+
/* Where the named blocks of the structure declare their name, in no order: the stops of Method Up and Down. */
|
|
219
|
+
declarationStops(document: OutlineDocument): number[] {
|
|
220
|
+
return this.structure.flatMap((entry) => {
|
|
221
|
+
if (entry.name === undefined) {
|
|
222
|
+
return [];
|
|
223
|
+
}
|
|
224
|
+
|
|
225
|
+
const header = document.getLine(document.positionAt(entry.from).line);
|
|
226
|
+
|
|
227
|
+
return [header.start + Math.max(0, header.text.indexOf(entry.name))];
|
|
228
|
+
});
|
|
229
|
+
}
|
|
230
|
+
|
|
218
231
|
/* Outermost first. Of two blocks that start on one line, only the larger one. */
|
|
219
232
|
blocks(document: OutlineDocument): readonly OutlineBlock[] {
|
|
220
233
|
if (this.cache?.version !== this.version) {
|
package/src/paint.ts
CHANGED
|
@@ -186,11 +186,20 @@ export function lineElement(
|
|
|
186
186
|
}
|
|
187
187
|
|
|
188
188
|
const span = document.createElement('span');
|
|
189
|
-
span.className = box.inlay.ghost
|
|
189
|
+
span.className = box.inlay.ghost
|
|
190
|
+
? 'se-ghost'
|
|
191
|
+
: box.inlay.composition
|
|
192
|
+
? 'se-composition'
|
|
193
|
+
: box.inlay.swatch !== undefined
|
|
194
|
+
? 'se-inlay se-swatch'
|
|
195
|
+
: 'se-inlay';
|
|
190
196
|
span.dataset.inlayId = box.inlay.id;
|
|
191
197
|
const { parts } = box.inlay;
|
|
192
198
|
|
|
193
|
-
if (
|
|
199
|
+
if (box.inlay.swatch !== undefined) {
|
|
200
|
+
span.style.backgroundColor = box.inlay.swatch;
|
|
201
|
+
span.setAttribute('aria-label', box.inlay.text);
|
|
202
|
+
} else if (parts === undefined) {
|
|
194
203
|
span.textContent = visibleBidi(box.inlay.text);
|
|
195
204
|
} else {
|
|
196
205
|
parts.forEach((part, index) => {
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
import type { DocumentModel } from '@adecore/editor-core';
|
|
2
|
+
import type { EditorPosition, EditorPostfixOperand, EditorRange, EditorSurroundShape, EditorTextPlace, EditorUnwrapTarget } from './types.ts';
|
|
3
|
+
|
|
4
|
+
/* How an editor turns the offsets of the text model into positions and back. */
|
|
5
|
+
export interface Coordinates {
|
|
6
|
+
positionAt(offset: number): EditorPosition;
|
|
7
|
+
offsetAt(position: EditorPosition): number;
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
function rangeOf(coordinates: Coordinates, from: number, to: number): EditorRange {
|
|
11
|
+
return { start: coordinates.positionAt(from), end: coordinates.positionAt(to) };
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
export function placeAt(model: DocumentModel, coordinates: Coordinates, position: EditorPosition, language: string): EditorTextPlace {
|
|
15
|
+
return model.placeAt(coordinates.offsetAt(position), language);
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
export function postfixOperands(model: DocumentModel, coordinates: Coordinates, dot: EditorPosition, language: string): EditorPostfixOperand[] {
|
|
19
|
+
const at = coordinates.offsetAt(dot);
|
|
20
|
+
|
|
21
|
+
return model.postfixOperands(at, language).map((operand) => ({
|
|
22
|
+
range: rangeOf(coordinates, operand.from, operand.from + model.slice(operand.from, at).trimEnd().length),
|
|
23
|
+
statement: operand.statement
|
|
24
|
+
}));
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
export function surroundShape(model: DocumentModel, coordinates: Coordinates, range: EditorRange, language: string): EditorSurroundShape | null {
|
|
28
|
+
const shape = model.surroundShape(coordinates.offsetAt(range.start), coordinates.offsetAt(range.end), language);
|
|
29
|
+
|
|
30
|
+
return shape === null ? null : { range: rangeOf(coordinates, shape.from, shape.to), statements: shape.statements, expression: shape.expression };
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
export function unwrapTargets(
|
|
34
|
+
model: DocumentModel,
|
|
35
|
+
coordinates: Coordinates,
|
|
36
|
+
position: EditorPosition,
|
|
37
|
+
language: string,
|
|
38
|
+
indentUnit: string
|
|
39
|
+
): EditorUnwrapTarget[] {
|
|
40
|
+
return model.unwrapTargets(coordinates.offsetAt(position), language, indentUnit).map((target) => ({
|
|
41
|
+
kind: target.kind,
|
|
42
|
+
range: rangeOf(coordinates, target.range.from, target.range.to),
|
|
43
|
+
edits: target.edits.map((edit) => ({ range: rangeOf(coordinates, edit.from, edit.to), text: edit.text }))
|
|
44
|
+
}));
|
|
45
|
+
}
|
package/src/types.ts
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import type { EditorSearchClientOptions } from './search-client.ts';
|
|
2
2
|
import type { Keymap } from './keymap-table.ts';
|
|
3
|
-
import type { EditorCommand, FoldRole, WhitespaceCleanupOptions } from '@adecore/editor-core';
|
|
3
|
+
import type { EditorCommand, FoldRole, TextPlaceKind, UnwrapKind, WhitespaceCleanupOptions } from '@adecore/editor-core';
|
|
4
4
|
|
|
5
5
|
export type { EditorCommand, FoldRole };
|
|
6
6
|
|
|
@@ -28,7 +28,12 @@ export type EditorViewCommand =
|
|
|
28
28
|
| 'toggleColumnMode'
|
|
29
29
|
| 'increaseFontSize'
|
|
30
30
|
| 'decreaseFontSize'
|
|
31
|
-
| 'resetFontSize'
|
|
31
|
+
| 'resetFontSize'
|
|
32
|
+
| 'methodUp'
|
|
33
|
+
| 'methodDown'
|
|
34
|
+
| 'nextChange'
|
|
35
|
+
| 'previousChange'
|
|
36
|
+
| 'pasteAsPlainText';
|
|
32
37
|
|
|
33
38
|
/* How far a person may zoom the text, and whether the wheel does. */
|
|
34
39
|
export interface EditorZoom {
|
|
@@ -283,6 +288,52 @@ export interface EditorContentChange {
|
|
|
283
288
|
readonly text: string;
|
|
284
289
|
}
|
|
285
290
|
|
|
291
|
+
/* What the text at a position is written in, with embedded regions resolved, and whether it is code, a comment or a string. */
|
|
292
|
+
export interface EditorTextPlace {
|
|
293
|
+
readonly language: string;
|
|
294
|
+
readonly kind: TextPlaceKind;
|
|
295
|
+
}
|
|
296
|
+
|
|
297
|
+
/* An expression that ends right before a dot. */
|
|
298
|
+
export interface EditorPostfixOperand {
|
|
299
|
+
/* The expression, without the whitespace before the dot. */
|
|
300
|
+
readonly range: EditorRange;
|
|
301
|
+
/* Whether it starts a statement; only the widest operand can. */
|
|
302
|
+
readonly statement: boolean;
|
|
303
|
+
}
|
|
304
|
+
|
|
305
|
+
/* What a range can be surrounded with: whole statements, one expression, both or neither. */
|
|
306
|
+
export interface EditorSurroundShape {
|
|
307
|
+
/* The range after trimming whitespace, widened past a trailing line comment for statements. */
|
|
308
|
+
readonly range: EditorRange;
|
|
309
|
+
readonly statements: boolean;
|
|
310
|
+
readonly expression: boolean;
|
|
311
|
+
}
|
|
312
|
+
|
|
313
|
+
/* A construct around a position that can be unwrapped or removed. */
|
|
314
|
+
export interface EditorUnwrapTarget {
|
|
315
|
+
readonly kind: UnwrapKind;
|
|
316
|
+
/* The whole construct, for a preview. */
|
|
317
|
+
readonly range: EditorRange;
|
|
318
|
+
/* Small edits in the coordinates of the text before them, which `applyEdits` takes as they are. */
|
|
319
|
+
readonly edits: readonly EditorContentChange[];
|
|
320
|
+
}
|
|
321
|
+
|
|
322
|
+
/* What a copy or cut put on the clipboard. */
|
|
323
|
+
export interface EditorCopy {
|
|
324
|
+
readonly text: string;
|
|
325
|
+
/* A copy of bare carets takes their lines, which paste back above the lines of bare carets. */
|
|
326
|
+
readonly wholeLines: boolean;
|
|
327
|
+
readonly cut: boolean;
|
|
328
|
+
}
|
|
329
|
+
|
|
330
|
+
export interface EditorPasteOptions {
|
|
331
|
+
/* Pastes above the lines of bare carets, as the paste of a copy of bare carets does; by default when the text is the last such copy. */
|
|
332
|
+
readonly wholeLines?: boolean;
|
|
333
|
+
/* False leaves the indentation of the text alone; the editor's indent on paste setting otherwise. */
|
|
334
|
+
readonly reindent?: boolean;
|
|
335
|
+
}
|
|
336
|
+
|
|
286
337
|
export interface EditorApplyEditsOptions {
|
|
287
338
|
readonly mapSelections?: (range: EditorRange) => EditorRange;
|
|
288
339
|
readonly mergeUndo?: boolean;
|
|
@@ -472,6 +523,8 @@ export interface EditorInlayHint {
|
|
|
472
523
|
/* A gap of 4 pixels that keeps the pill off the character before it or the one after it. */
|
|
473
524
|
readonly paddingLeft?: boolean;
|
|
474
525
|
readonly paddingRight?: boolean;
|
|
526
|
+
/* A square of this CSS color in place of the label, such as the color a literal stands for; the label is then what assistive technology reads. */
|
|
527
|
+
readonly swatch?: string;
|
|
475
528
|
}
|
|
476
529
|
|
|
477
530
|
/* A hint, or one part of its label, under the pointer. */
|
|
@@ -649,6 +702,18 @@ export interface Editor {
|
|
|
649
702
|
setLabel(label: string): void;
|
|
650
703
|
/* What a language server knows about the folds: the bodies of symbols and the ranges it folds. They follow their text through edits until set again; null forgets them. */
|
|
651
704
|
setFoldHints(hints: EditorFoldHints | null): void;
|
|
705
|
+
/* The places Method Up and Down stop at, such as the names of a language server's symbols. They follow their text through edits until set again; null goes back to the editor's own reading of the structure. Optional so an editor from before it still fits. */
|
|
706
|
+
setDeclarations?(positions: readonly EditorPosition[] | null): void;
|
|
707
|
+
/* A command that navigates (Method Up and Down, Code Block Start and End) moved the primary caret away from `from`. Optional so an editor from before it still fits. */
|
|
708
|
+
onNavigate?(listener: (from: EditorPosition) => void): () => void;
|
|
709
|
+
/* Whether the editor has the keyboard focus. Optional so an editor from before it still fits. */
|
|
710
|
+
hasFocus?(): boolean;
|
|
711
|
+
/* The texts of the other open documents, which Hippie completion takes words from after those of this one; null takes them away. Optional so an editor from before it still fits. */
|
|
712
|
+
setWordSources?(source: (() => readonly string[]) | null): void;
|
|
713
|
+
/* Told after a copy or cut, with the text it put on the clipboard. Optional so an editor from before it still fits. */
|
|
714
|
+
onCopy?(listener: (copy: EditorCopy) => void): () => void;
|
|
715
|
+
/* Pastes `text` as a paste from the clipboard does: at every caret, with indent on paste, and refused with its notice when the editor is read-only. False when nothing was pasted. Optional so an editor from before it still fits. */
|
|
716
|
+
paste?(text: string, options?: EditorPasteOptions): boolean;
|
|
652
717
|
/*
|
|
653
718
|
* Marks that follow their lines through edits until the host sets them again. Every layer (default: the unnamed one)
|
|
654
719
|
* holds its own marks and the editor draws the union, so setting one layer never clears another; an empty list
|
|
@@ -699,7 +764,8 @@ export interface Editor {
|
|
|
699
764
|
/* Colors by what the language servers know, over what the grammar made of the text; null takes them away. */
|
|
700
765
|
setSemanticTokens(tokens: readonly EditorSemanticToken[] | null): void;
|
|
701
766
|
/* Hints to draw as soft pills in the text, replacing the ones set before. They follow their text through edits until set again. */
|
|
702
|
-
|
|
767
|
+
/* Each owner sets its own hints and keeps those of the others; ids stay unique across owners. */
|
|
768
|
+
setInlayHints(hints: readonly EditorInlayHint[], owner?: string): void;
|
|
703
769
|
/* The pointer moved onto a hint or one of its parts, or null when it left them. Optional so an editor from before it still fits. */
|
|
704
770
|
onInlayHover?(listener: (target: EditorInlayTarget | null) => void): () => void;
|
|
705
771
|
/* Handlers see a press of the primary button on a hint before the editor does, the first to take it winning. */
|
|
@@ -808,6 +874,14 @@ export interface Editor {
|
|
|
808
874
|
setReadOnly(readOnly: boolean, reason?: string): void;
|
|
809
875
|
/* Whether the editor refuses edits. Optional so an editor from before it still fits. */
|
|
810
876
|
isReadOnly?(): boolean;
|
|
877
|
+
/* What the text at a position is written in and whether it is code, a comment or a string. Optional so an editor from before it still fits. */
|
|
878
|
+
placeAt?(position: EditorPosition): EditorTextPlace;
|
|
879
|
+
/* Every expression that ends right before the `.` at `dot`, innermost first. Optional so an editor from before it still fits. */
|
|
880
|
+
postfixOperands?(dot: EditorPosition): readonly EditorPostfixOperand[];
|
|
881
|
+
/* What a range can be surrounded with; null for a blank range. Optional so an editor from before it still fits. */
|
|
882
|
+
surroundShape?(range: EditorRange): EditorSurroundShape | null;
|
|
883
|
+
/* What can be unwrapped or removed around a position, innermost first. Optional so an editor from before it still fits. */
|
|
884
|
+
unwrapTargets?(position: EditorPosition): readonly EditorUnwrapTarget[];
|
|
811
885
|
/* Runs an editing command on every caret, as its key would. False when it changed nothing. */
|
|
812
886
|
runCommand(command: EditorRunCommand): boolean;
|
|
813
887
|
focus(): void;
|
package/src/view.ts
CHANGED
|
@@ -298,6 +298,7 @@ export class EditorView {
|
|
|
298
298
|
private lenses: BlockWidget[] = [];
|
|
299
299
|
private readonly replacements = new Map<string, BlockReplacement[]>();
|
|
300
300
|
private occurrences: Occurrence[] = [];
|
|
301
|
+
private hippieSource: { from: number; to: number } | null = null;
|
|
301
302
|
private replacePreview: { text: string; preserveCase: boolean } | null = null;
|
|
302
303
|
private dropCaret: number | null = null;
|
|
303
304
|
private activeGuideCache: { key: string; guide: { column: number; first: number; last: number } | null } | undefined;
|
|
@@ -316,6 +317,7 @@ export class EditorView {
|
|
|
316
317
|
private collapsed = new Set<number>();
|
|
317
318
|
/* What a language server said about the folds, in offsets that follow the text through edits. */
|
|
318
319
|
private foldHints: FoldHints | null = null;
|
|
320
|
+
private declarations: number[] | null = null;
|
|
319
321
|
/* The roles that fold by themselves while the file is as it was opened; null once a person edited or the file opened with folds of its own. */
|
|
320
322
|
private defaultRoles: ReadonlySet<FoldRole> | null = null;
|
|
321
323
|
/* The folds somebody already decided on, a person or the defaults, so a later answer of a server leaves them as they are. */
|
|
@@ -600,6 +602,16 @@ export class EditorView {
|
|
|
600
602
|
this.render();
|
|
601
603
|
}
|
|
602
604
|
|
|
605
|
+
/* The word Hippie completion took from this document, shown until the next key or edit; null takes the mark away. */
|
|
606
|
+
setHippieSource(range: { from: number; to: number } | null): void {
|
|
607
|
+
if (range === null && this.hippieSource === null) {
|
|
608
|
+
return;
|
|
609
|
+
}
|
|
610
|
+
|
|
611
|
+
this.hippieSource = range;
|
|
612
|
+
this.render();
|
|
613
|
+
}
|
|
614
|
+
|
|
603
615
|
/* A name underlined as a link a press follows, with the pointer to match; null takes it away. */
|
|
604
616
|
setLink(link: { from: number; to: number } | null): void {
|
|
605
617
|
this.link = link;
|
|
@@ -849,6 +861,7 @@ export class EditorView {
|
|
|
849
861
|
this.markerVersion++;
|
|
850
862
|
const anchor = this.topAnchor;
|
|
851
863
|
this.occurrences = [];
|
|
864
|
+
this.hippieSource = null;
|
|
852
865
|
this.link = null;
|
|
853
866
|
this.content.classList.remove('se-content-link');
|
|
854
867
|
this.outline.edited(batches);
|
|
@@ -916,6 +929,10 @@ export class EditorView {
|
|
|
916
929
|
this.foldHints = mapFoldHints(this.foldHints, map);
|
|
917
930
|
}
|
|
918
931
|
|
|
932
|
+
if (this.declarations !== null) {
|
|
933
|
+
this.declarations = this.declarations.map((offset) => map(offset));
|
|
934
|
+
}
|
|
935
|
+
|
|
919
936
|
this.customFolds = this.customFolds.map((fold) => mapRange(fold, map));
|
|
920
937
|
this.find.mapBounds(changes);
|
|
921
938
|
|
|
@@ -1089,15 +1106,7 @@ export class EditorView {
|
|
|
1089
1106
|
return;
|
|
1090
1107
|
}
|
|
1091
1108
|
|
|
1092
|
-
const ranges =
|
|
1093
|
-
this.model.getLength() > FOLD_LIMIT
|
|
1094
|
-
? []
|
|
1095
|
-
: this.model.getFoldingRanges({
|
|
1096
|
-
indentation: /^(python|py|yaml|yml)$/.test(this.settings.language ?? ''),
|
|
1097
|
-
tabSize: this.settings.tabSize,
|
|
1098
|
-
...(this.settings.language === undefined ? {} : { language: this.settings.language }),
|
|
1099
|
-
...(this.foldHints === null ? {} : { hints: this.foldHints })
|
|
1100
|
-
});
|
|
1109
|
+
const ranges = this.structureRanges();
|
|
1101
1110
|
|
|
1102
1111
|
this.foldRanges = this.withCustomFolds(ranges);
|
|
1103
1112
|
|
|
@@ -1115,6 +1124,20 @@ export class EditorView {
|
|
|
1115
1124
|
this.refoldLayout();
|
|
1116
1125
|
}
|
|
1117
1126
|
|
|
1127
|
+
/* The folding ranges the document has now, a server's included, whether or not the folds are drawn. */
|
|
1128
|
+
structureRanges(): FoldingRange[] {
|
|
1129
|
+
if (this.model.getLength() > FOLD_LIMIT) {
|
|
1130
|
+
return [];
|
|
1131
|
+
}
|
|
1132
|
+
|
|
1133
|
+
return this.model.getFoldingRanges({
|
|
1134
|
+
indentation: /^(python|py|yaml|yml)$/.test(this.settings.language ?? ''),
|
|
1135
|
+
tabSize: this.settings.tabSize,
|
|
1136
|
+
...(this.settings.language === undefined ? {} : { language: this.settings.language }),
|
|
1137
|
+
...(this.foldHints === null ? {} : { hints: this.foldHints })
|
|
1138
|
+
});
|
|
1139
|
+
}
|
|
1140
|
+
|
|
1118
1141
|
/* The folds a person made of a selection, which the document's own structure does not know, among the ones it does. */
|
|
1119
1142
|
private withCustomFolds(ranges: readonly FoldingRange[]): ViewFold[] {
|
|
1120
1143
|
const custom = this.customFolds.flatMap((fold): ViewFold[] => {
|
|
@@ -1383,6 +1406,65 @@ export class EditorView {
|
|
|
1383
1406
|
this.refoldLayout();
|
|
1384
1407
|
}
|
|
1385
1408
|
|
|
1409
|
+
/* The places Method Up and Down stop at; they follow their text through edits until set again, and null goes back to the editor's own reading. */
|
|
1410
|
+
setDeclarations(offsets: readonly number[] | null): void {
|
|
1411
|
+
this.declarations = offsets === null ? null : [...new Set(offsets)].sort((left, right) => left - right);
|
|
1412
|
+
}
|
|
1413
|
+
|
|
1414
|
+
private methodStops(): number[] {
|
|
1415
|
+
if (this.declarations !== null) {
|
|
1416
|
+
return this.declarations;
|
|
1417
|
+
}
|
|
1418
|
+
|
|
1419
|
+
const sections = this.foldRanges.filter((range) => range.kind === 'section').map((range) => this.model.getLine(range.startLine).start);
|
|
1420
|
+
|
|
1421
|
+
return [...new Set([...this.outline.declarationStops(this.model), ...sections])].sort((left, right) => left - right);
|
|
1422
|
+
}
|
|
1423
|
+
|
|
1424
|
+
/* Moves the one caret to the first line of the next or previous change mark, of any layer. False when there is none. */
|
|
1425
|
+
goToChange(direction: -1 | 1): boolean {
|
|
1426
|
+
const line = this.model.positionAt(this.model.getPrimary().head).line;
|
|
1427
|
+
const { runs } = this.resolvedChanges();
|
|
1428
|
+
|
|
1429
|
+
// A run the caret is in is where it already is; a deleted run is the line its mark sits on.
|
|
1430
|
+
const target = direction === 1 ? runs.find((run) => run.start > line) : runs.findLast((run) => (run.kind === 'deleted' ? run.start : run.end) < line);
|
|
1431
|
+
|
|
1432
|
+
if (target === undefined) {
|
|
1433
|
+
return false;
|
|
1434
|
+
}
|
|
1435
|
+
|
|
1436
|
+
const offset = this.model.getLine(Math.min(target.start, this.model.getLineCount() - 1)).start;
|
|
1437
|
+
this.model.setSelections([{ anchor: offset, head: offset }]);
|
|
1438
|
+
this.revealOffset(offset, 'center');
|
|
1439
|
+
|
|
1440
|
+
return true;
|
|
1441
|
+
}
|
|
1442
|
+
|
|
1443
|
+
/* Moves the one caret to the previous or next declaration on another line. False when there is none. */
|
|
1444
|
+
goToMethod(direction: -1 | 1): boolean {
|
|
1445
|
+
// The structure is read after a pause in typing, so a press right after an edit reads it now.
|
|
1446
|
+
if (this.declarations === null) {
|
|
1447
|
+
this.refreshFolds();
|
|
1448
|
+
}
|
|
1449
|
+
|
|
1450
|
+
const line = this.model.positionAt(this.model.getPrimary().head).line;
|
|
1451
|
+
const stops = this.methodStops();
|
|
1452
|
+
|
|
1453
|
+
const target =
|
|
1454
|
+
direction === 1
|
|
1455
|
+
? stops.find((offset) => this.model.positionAt(offset).line > line)
|
|
1456
|
+
: stops.findLast((offset) => this.model.positionAt(offset).line < line);
|
|
1457
|
+
|
|
1458
|
+
if (target === undefined) {
|
|
1459
|
+
return false;
|
|
1460
|
+
}
|
|
1461
|
+
|
|
1462
|
+
this.model.setSelections([{ anchor: target, head: target }]);
|
|
1463
|
+
this.revealOffset(target, direction === 1 ? 'centerDown' : 'centerUp');
|
|
1464
|
+
|
|
1465
|
+
return true;
|
|
1466
|
+
}
|
|
1467
|
+
|
|
1386
1468
|
/* Hands over what a language server knows about the folds; null forgets it. The defaults fold what the answer names, once. */
|
|
1387
1469
|
setFoldHints(hints: FoldHints | null): void {
|
|
1388
1470
|
this.foldHints = hints;
|
|
@@ -2480,6 +2562,10 @@ export class EditorView {
|
|
|
2480
2562
|
}
|
|
2481
2563
|
}
|
|
2482
2564
|
|
|
2565
|
+
if (this.hippieSource !== null) {
|
|
2566
|
+
marks.push({ className: 'se-hippie-source', rects: this.paintRectangles(this.hippieSource.from, this.hippieSource.to, rows) });
|
|
2567
|
+
}
|
|
2568
|
+
|
|
2483
2569
|
const marginX = this.settings.rightMargin === null ? null : this.settings.rightMargin * this.layout.metrics.charWidth;
|
|
2484
2570
|
|
|
2485
2571
|
// A margin past the content would stretch the scroll extent to itself, so it shows only where the text or the view reaches it.
|