@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/paint.d.ts
ADDED
|
@@ -0,0 +1,86 @@
|
|
|
1
|
+
import { type AttributedLines } from './attribution.ts';
|
|
2
|
+
import { type EditorLayout, type LayoutRect, type LayoutRow, type LineGeometry } from './layout.ts';
|
|
3
|
+
import type { EditorChangeKind, LineToken } from './types.ts';
|
|
4
|
+
export interface RowPaint {
|
|
5
|
+
layoutVersion: number;
|
|
6
|
+
whitespace: boolean;
|
|
7
|
+
tokensOf(line: number): readonly LineToken[] | null;
|
|
8
|
+
viewportWidth: number;
|
|
9
|
+
hostLeft: number;
|
|
10
|
+
hostWidth: number;
|
|
11
|
+
onUnfold(line: number): void;
|
|
12
|
+
}
|
|
13
|
+
export declare function styleSpan(span: HTMLElement, token: LineToken): void;
|
|
14
|
+
export declare function lineElement(document: Document, geometry: LineGeometry, tokens: readonly LineToken[] | null, lineHeight: number, firstRowOnly: boolean, whitespace?: boolean): HTMLElement;
|
|
15
|
+
export declare class RowPainter {
|
|
16
|
+
private readonly rows;
|
|
17
|
+
private readonly code;
|
|
18
|
+
private readonly layout;
|
|
19
|
+
private readonly observer;
|
|
20
|
+
constructor(code: HTMLElement, layout: EditorLayout, observer: ResizeObserver | undefined);
|
|
21
|
+
paint(rows: readonly LayoutRow[], paint: RowPaint): boolean;
|
|
22
|
+
private paintText;
|
|
23
|
+
private buildText;
|
|
24
|
+
private appendTail;
|
|
25
|
+
private paintBlock;
|
|
26
|
+
private fillBlock;
|
|
27
|
+
clear(): void;
|
|
28
|
+
}
|
|
29
|
+
export interface GutterPaint {
|
|
30
|
+
activeLines: ReadonlySet<number>;
|
|
31
|
+
changes: ReadonlyMap<number, EditorChangeKind>;
|
|
32
|
+
attribution: ReadonlyMap<number, AttributedLines>;
|
|
33
|
+
highlights: ReadonlyMap<number, AttributedLines>;
|
|
34
|
+
foldable: ReadonlyMap<number, boolean>;
|
|
35
|
+
action: {
|
|
36
|
+
line: number;
|
|
37
|
+
label: string;
|
|
38
|
+
} | null;
|
|
39
|
+
markers: ReadonlyMap<number, {
|
|
40
|
+
id: string;
|
|
41
|
+
label: string;
|
|
42
|
+
}>;
|
|
43
|
+
}
|
|
44
|
+
export declare function paintGutter(container: HTMLElement, layout: EditorLayout, rows: readonly LayoutRow[], paint: GutterPaint): void;
|
|
45
|
+
export interface OverlayPaint {
|
|
46
|
+
currentLine: LayoutRect | null;
|
|
47
|
+
selections: readonly LayoutRect[];
|
|
48
|
+
focused: boolean;
|
|
49
|
+
marks: readonly {
|
|
50
|
+
className: string;
|
|
51
|
+
rects: readonly LayoutRect[];
|
|
52
|
+
background?: string;
|
|
53
|
+
}[];
|
|
54
|
+
}
|
|
55
|
+
export declare function paintOverlays(container: HTMLElement, paint: OverlayPaint): void;
|
|
56
|
+
export declare function paintOver(container: HTMLElement, layers: readonly {
|
|
57
|
+
className: string;
|
|
58
|
+
rects: readonly LayoutRect[];
|
|
59
|
+
}[]): void;
|
|
60
|
+
export declare function paintCarets(container: HTMLElement, carets: readonly (LayoutRect & {
|
|
61
|
+
primary: boolean;
|
|
62
|
+
drop?: boolean;
|
|
63
|
+
})[]): void;
|
|
64
|
+
export interface RemoteCaret {
|
|
65
|
+
id: string;
|
|
66
|
+
name: string;
|
|
67
|
+
color: string;
|
|
68
|
+
x: number;
|
|
69
|
+
y: number;
|
|
70
|
+
height: number;
|
|
71
|
+
below: boolean;
|
|
72
|
+
}
|
|
73
|
+
export declare function paintRemoteCarets(container: HTMLElement, carets: readonly RemoteCaret[]): void;
|
|
74
|
+
export interface StickyEntry {
|
|
75
|
+
line: number;
|
|
76
|
+
geometry: LineGeometry;
|
|
77
|
+
tokens: readonly LineToken[] | null;
|
|
78
|
+
}
|
|
79
|
+
export declare function paintSticky(container: HTMLElement, entries: readonly StickyEntry[], lineHeight: number): void;
|
|
80
|
+
export declare function arrowSign(document: Document, width: number, lineHeight: number, className: string): SVGSVGElement;
|
|
81
|
+
export interface WrapSign {
|
|
82
|
+
x: number;
|
|
83
|
+
y: number;
|
|
84
|
+
width: number;
|
|
85
|
+
}
|
|
86
|
+
export declare function paintSigns(container: HTMLElement, signs: readonly WrapSign[], lineHeight: number): void;
|
package/dist/paint.js
ADDED
|
@@ -0,0 +1,481 @@
|
|
|
1
|
+
import { colorValue } from './attribution.js';
|
|
2
|
+
import { scanLine } from './layout.js';
|
|
3
|
+
const ITALIC = 1;
|
|
4
|
+
const BOLD = 2;
|
|
5
|
+
const UNDERLINE = 4;
|
|
6
|
+
const STRIKETHROUGH = 8;
|
|
7
|
+
/* The gap around a collapsed fold's placeholder, so it reads as `{…}` and not as three separate things. */
|
|
8
|
+
const FOLD_GAP = 4;
|
|
9
|
+
const FOLD_CHIP_PADDING = 12;
|
|
10
|
+
export function styleSpan(span, token) {
|
|
11
|
+
if (token.color !== '') {
|
|
12
|
+
span.style.color = token.color;
|
|
13
|
+
}
|
|
14
|
+
if (token.fontStyle & ITALIC) {
|
|
15
|
+
span.style.fontStyle = 'italic';
|
|
16
|
+
}
|
|
17
|
+
if (token.fontStyle & BOLD) {
|
|
18
|
+
span.style.fontWeight = 'bold';
|
|
19
|
+
}
|
|
20
|
+
const decorations = [token.fontStyle & UNDERLINE ? 'underline' : '', token.fontStyle & STRIKETHROUGH ? 'line-through' : ''].filter(Boolean);
|
|
21
|
+
if (decorations.length > 0) {
|
|
22
|
+
span.style.textDecoration = decorations.join(' ');
|
|
23
|
+
}
|
|
24
|
+
}
|
|
25
|
+
/* Text in an element, with each space drawn as a faint dot when whitespace shows. A dot is as wide as a space in the code face, so nothing moves. */
|
|
26
|
+
function setText(element, text, whitespace) {
|
|
27
|
+
if (!whitespace || !text.includes(' ')) {
|
|
28
|
+
element.textContent = text;
|
|
29
|
+
return;
|
|
30
|
+
}
|
|
31
|
+
const document = element.ownerDocument;
|
|
32
|
+
for (const part of text.split(/( +)/)) {
|
|
33
|
+
if (part.startsWith(' ')) {
|
|
34
|
+
const dots = document.createElement('span');
|
|
35
|
+
dots.className = 'se-ws';
|
|
36
|
+
dots.textContent = '·'.repeat(part.length);
|
|
37
|
+
element.append(dots);
|
|
38
|
+
}
|
|
39
|
+
else if (part !== '') {
|
|
40
|
+
element.append(part);
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
/* The text of one run, split along the colors of the line. `tokensFrom` is the offset the first token starts at, which is the geometry's own start unless it is a slice of a line. */
|
|
45
|
+
function fillRun(run, geometry, from, to, tokens, whitespace, tokensFrom = geometry.start) {
|
|
46
|
+
if (tokens === null) {
|
|
47
|
+
setText(run, geometry.text.slice(from - geometry.start, to - geometry.start), whitespace);
|
|
48
|
+
return;
|
|
49
|
+
}
|
|
50
|
+
let tokenStart = tokensFrom;
|
|
51
|
+
for (const token of tokens) {
|
|
52
|
+
const tokenEnd = tokenStart + token.length;
|
|
53
|
+
const start = Math.max(from, tokenStart);
|
|
54
|
+
const end = Math.min(to, tokenEnd);
|
|
55
|
+
if (start < end) {
|
|
56
|
+
const span = run.ownerDocument.createElement('span');
|
|
57
|
+
setText(span, geometry.text.slice(start - geometry.start, end - geometry.start), whitespace);
|
|
58
|
+
styleSpan(span, token);
|
|
59
|
+
run.append(span);
|
|
60
|
+
}
|
|
61
|
+
tokenStart = tokenEnd;
|
|
62
|
+
if (tokenStart >= to) {
|
|
63
|
+
break;
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
/* One line of text as absolutely placed runs and inlays. `firstRowOnly` leaves out what a wrapped line carries onto later visual lines. */
|
|
68
|
+
export function lineElement(document, geometry, tokens, lineHeight, firstRowOnly, whitespace = false) {
|
|
69
|
+
const element = document.createElement('div');
|
|
70
|
+
element.className = 'se-line';
|
|
71
|
+
element.style.width = `${geometry.width}px`;
|
|
72
|
+
for (const run of geometry.runs) {
|
|
73
|
+
if (firstRowOnly && run.subRow > 0) {
|
|
74
|
+
continue;
|
|
75
|
+
}
|
|
76
|
+
const span = document.createElement('span');
|
|
77
|
+
span.className = 'se-run';
|
|
78
|
+
span.style.left = `${run.x}px`;
|
|
79
|
+
span.style.top = `${run.subRow * lineHeight}px`;
|
|
80
|
+
fillRun(span, geometry, run.from, run.to, tokens, whitespace);
|
|
81
|
+
element.append(span);
|
|
82
|
+
}
|
|
83
|
+
if (whitespace) {
|
|
84
|
+
appendTabSigns(element, geometry, lineHeight, firstRowOnly);
|
|
85
|
+
}
|
|
86
|
+
for (const box of geometry.inlays) {
|
|
87
|
+
if (firstRowOnly && box.subRow > 0) {
|
|
88
|
+
continue;
|
|
89
|
+
}
|
|
90
|
+
const span = document.createElement('span');
|
|
91
|
+
span.className = box.inlay.ghost ? 'se-ghost' : box.inlay.composition ? 'se-composition' : 'se-inlay';
|
|
92
|
+
span.dataset.inlayId = box.inlay.id;
|
|
93
|
+
span.textContent = box.inlay.text;
|
|
94
|
+
span.style.left = `${box.x}px`;
|
|
95
|
+
span.style.top = `${box.subRow * lineHeight}px`;
|
|
96
|
+
span.style.width = `${box.width}px`;
|
|
97
|
+
element.append(span);
|
|
98
|
+
}
|
|
99
|
+
return element;
|
|
100
|
+
}
|
|
101
|
+
/*
|
|
102
|
+
* The text layer: one element per visible row, kept while what it shows stays the same. A line is
|
|
103
|
+
* drawn as absolutely placed runs of text, so the layout alone decides where each character is and
|
|
104
|
+
* the browser only has to paint it.
|
|
105
|
+
*/
|
|
106
|
+
export class RowPainter {
|
|
107
|
+
rows = new Map();
|
|
108
|
+
code;
|
|
109
|
+
layout;
|
|
110
|
+
observer;
|
|
111
|
+
constructor(code, layout, observer) {
|
|
112
|
+
this.code = code;
|
|
113
|
+
this.layout = layout;
|
|
114
|
+
this.observer = observer;
|
|
115
|
+
}
|
|
116
|
+
/* Draws the rows and takes the rest away. Returns whether a widget's height moved the layout. */
|
|
117
|
+
paint(rows, paint) {
|
|
118
|
+
const visible = new Set(rows.map((row) => row.key));
|
|
119
|
+
for (const [key, entry] of this.rows) {
|
|
120
|
+
if (!visible.has(key)) {
|
|
121
|
+
this.observer?.unobserve(entry.element);
|
|
122
|
+
entry.element.remove();
|
|
123
|
+
this.rows.delete(key);
|
|
124
|
+
}
|
|
125
|
+
}
|
|
126
|
+
let measured = false;
|
|
127
|
+
for (const row of rows) {
|
|
128
|
+
if (row.kind === 'text') {
|
|
129
|
+
this.paintText(row, paint);
|
|
130
|
+
}
|
|
131
|
+
else {
|
|
132
|
+
measured = this.paintBlock(row, paint) || measured;
|
|
133
|
+
}
|
|
134
|
+
}
|
|
135
|
+
return measured;
|
|
136
|
+
}
|
|
137
|
+
paintText(row, paint) {
|
|
138
|
+
const geometry = this.layout.geometry(row);
|
|
139
|
+
const tokens = paint.tokensOf(row.line);
|
|
140
|
+
const folded = row.lastLine > row.line;
|
|
141
|
+
const lastLine = folded && row.tail !== null ? this.layout.document.getLine(row.lastLine) : null;
|
|
142
|
+
const tail = lastLine === null ? '' : lastLine.text.slice(row.tail - lastLine.start);
|
|
143
|
+
const tailTokens = tail === '' ? null : paint.tokensOf(row.lastLine);
|
|
144
|
+
let entry = this.rows.get(row.key);
|
|
145
|
+
if (!entry ||
|
|
146
|
+
entry.text !== geometry.text ||
|
|
147
|
+
entry.tokens !== tokens ||
|
|
148
|
+
entry.layoutVersion !== paint.layoutVersion ||
|
|
149
|
+
entry.whitespace !== paint.whitespace ||
|
|
150
|
+
entry.folded !== folded ||
|
|
151
|
+
entry.tail !== tail ||
|
|
152
|
+
entry.tailTokens !== tailTokens) {
|
|
153
|
+
const element = this.buildText(row, geometry, tokens, paint, tail, tailTokens);
|
|
154
|
+
if (entry) {
|
|
155
|
+
entry.element.replaceWith(element);
|
|
156
|
+
}
|
|
157
|
+
else {
|
|
158
|
+
this.code.append(element);
|
|
159
|
+
}
|
|
160
|
+
entry = {
|
|
161
|
+
element,
|
|
162
|
+
text: geometry.text,
|
|
163
|
+
tokens,
|
|
164
|
+
layoutVersion: paint.layoutVersion,
|
|
165
|
+
whitespace: paint.whitespace,
|
|
166
|
+
folded,
|
|
167
|
+
tail,
|
|
168
|
+
tailTokens,
|
|
169
|
+
revision: 0
|
|
170
|
+
};
|
|
171
|
+
this.rows.set(row.key, entry);
|
|
172
|
+
}
|
|
173
|
+
entry.element.style.top = `${row.top}px`;
|
|
174
|
+
entry.element.style.height = `${row.height}px`;
|
|
175
|
+
entry.element.dataset.line = String(row.line);
|
|
176
|
+
}
|
|
177
|
+
buildText(row, geometry, tokens, paint, tail, tailTokens) {
|
|
178
|
+
const document = this.code.ownerDocument;
|
|
179
|
+
const lineHeight = this.layout.metrics.lineHeight;
|
|
180
|
+
const element = lineElement(document, geometry, tokens, lineHeight, false, paint.whitespace);
|
|
181
|
+
if (row.lastLine > row.line) {
|
|
182
|
+
const chip = document.createElement('button');
|
|
183
|
+
chip.type = 'button';
|
|
184
|
+
chip.className = 'se-fold-chip';
|
|
185
|
+
chip.textContent = '…';
|
|
186
|
+
chip.tabIndex = -1;
|
|
187
|
+
const chipLeft = geometry.rowEnds[geometry.rowEnds.length - 1] + FOLD_GAP;
|
|
188
|
+
const chipWidth = Math.ceil(this.layout.metrics.charWidth) + FOLD_CHIP_PADDING;
|
|
189
|
+
chip.style.left = `${chipLeft}px`;
|
|
190
|
+
chip.style.top = `${(geometry.subRows - 1) * lineHeight}px`;
|
|
191
|
+
chip.style.width = `${chipWidth}px`;
|
|
192
|
+
chip.addEventListener('pointerdown', (event) => event.stopPropagation());
|
|
193
|
+
chip.addEventListener('click', () => paint.onUnfold(row.line));
|
|
194
|
+
element.append(chip);
|
|
195
|
+
if (tail !== '' && row.tail !== null) {
|
|
196
|
+
this.appendTail(element, row, geometry, tail, tailTokens, chipLeft + chipWidth + FOLD_GAP);
|
|
197
|
+
}
|
|
198
|
+
}
|
|
199
|
+
return element;
|
|
200
|
+
}
|
|
201
|
+
/* The closing delimiter of a collapsed fold, laid out as a line of its own that starts after the placeholder. */
|
|
202
|
+
appendTail(element, row, header, tail, tokens, left) {
|
|
203
|
+
const document = this.code.ownerDocument;
|
|
204
|
+
const lineStart = this.layout.document.getLine(row.lastLine).start;
|
|
205
|
+
const geometry = scanLine({ start: row.tail, end: row.tail + tail.length, text: tail }, this.layout.metrics, null, []);
|
|
206
|
+
for (const run of geometry.runs) {
|
|
207
|
+
const span = document.createElement('span');
|
|
208
|
+
span.className = 'se-run';
|
|
209
|
+
span.style.left = `${left + run.x}px`;
|
|
210
|
+
span.style.top = `${(header.subRows - 1) * this.layout.metrics.lineHeight}px`;
|
|
211
|
+
fillRun(span, geometry, run.from, run.to, tokens, false, lineStart);
|
|
212
|
+
element.append(span);
|
|
213
|
+
}
|
|
214
|
+
}
|
|
215
|
+
paintBlock(row, paint) {
|
|
216
|
+
const { widget } = row;
|
|
217
|
+
const revision = widget.revision ?? 0;
|
|
218
|
+
let entry = this.rows.get(row.key);
|
|
219
|
+
if (!entry) {
|
|
220
|
+
const element = this.code.ownerDocument.createElement('div');
|
|
221
|
+
element.className = 'se-block';
|
|
222
|
+
element.dataset.rowKey = row.key;
|
|
223
|
+
element.dataset.widgetId = widget.id;
|
|
224
|
+
this.fillBlock(element, widget);
|
|
225
|
+
this.code.append(element);
|
|
226
|
+
if (!widget.lens) {
|
|
227
|
+
this.observer?.observe(element);
|
|
228
|
+
}
|
|
229
|
+
entry = { element, text: '', tokens: null, layoutVersion: 0, whitespace: false, folded: false, tail: '', tailTokens: null, revision };
|
|
230
|
+
this.rows.set(row.key, entry);
|
|
231
|
+
}
|
|
232
|
+
else if (entry.revision !== revision) {
|
|
233
|
+
entry.element.replaceChildren();
|
|
234
|
+
this.fillBlock(entry.element, widget);
|
|
235
|
+
entry.revision = revision;
|
|
236
|
+
}
|
|
237
|
+
entry.element.style.top = `${row.top}px`;
|
|
238
|
+
if (widget.lens) {
|
|
239
|
+
entry.element.style.left = `${this.layout.indentX(row.line)}px`;
|
|
240
|
+
entry.element.style.height = `${row.height}px`;
|
|
241
|
+
return false;
|
|
242
|
+
}
|
|
243
|
+
if (widget.render) {
|
|
244
|
+
entry.element.style.left = `${paint.hostLeft}px`;
|
|
245
|
+
entry.element.style.width = `${paint.hostWidth}px`;
|
|
246
|
+
}
|
|
247
|
+
else {
|
|
248
|
+
entry.element.style.width = `${Math.max(80, paint.viewportWidth)}px`;
|
|
249
|
+
}
|
|
250
|
+
const height = entry.element.getBoundingClientRect?.().height ?? 0;
|
|
251
|
+
return height > 0 && this.layout.setMeasuredHeight(row.key, height);
|
|
252
|
+
}
|
|
253
|
+
fillBlock(element, widget) {
|
|
254
|
+
if (widget.render) {
|
|
255
|
+
widget.render(element);
|
|
256
|
+
}
|
|
257
|
+
else {
|
|
258
|
+
element.textContent = widget.text ?? '';
|
|
259
|
+
}
|
|
260
|
+
}
|
|
261
|
+
clear() {
|
|
262
|
+
for (const entry of this.rows.values()) {
|
|
263
|
+
this.observer?.unobserve(entry.element);
|
|
264
|
+
entry.element.remove();
|
|
265
|
+
}
|
|
266
|
+
this.rows.clear();
|
|
267
|
+
}
|
|
268
|
+
}
|
|
269
|
+
/* The line numbers of the visible rows, and the fold control of the lines that have one. */
|
|
270
|
+
export function paintGutter(container, layout, rows, paint) {
|
|
271
|
+
const document = container.ownerDocument;
|
|
272
|
+
const fragment = document.createDocumentFragment();
|
|
273
|
+
for (const row of rows) {
|
|
274
|
+
if (row.kind !== 'text') {
|
|
275
|
+
continue;
|
|
276
|
+
}
|
|
277
|
+
const item = document.createElement('div');
|
|
278
|
+
item.className = paint.activeLines.has(row.line) ? 'se-line-number se-active-number' : 'se-line-number';
|
|
279
|
+
item.style.top = `${row.top}px`;
|
|
280
|
+
item.style.height = `${layout.metrics.lineHeight}px`;
|
|
281
|
+
const fill = paint.highlights.get(row.line)?.fill;
|
|
282
|
+
if (fill !== undefined) {
|
|
283
|
+
item.style.background = colorValue(fill);
|
|
284
|
+
}
|
|
285
|
+
const number = document.createElement('span');
|
|
286
|
+
number.textContent = String(row.line + 1);
|
|
287
|
+
item.append(number);
|
|
288
|
+
if (paint.action?.line === row.line) {
|
|
289
|
+
const button = document.createElement('button');
|
|
290
|
+
button.type = 'button';
|
|
291
|
+
button.className = 'se-gutter-action';
|
|
292
|
+
button.dataset.gutterAction = String(row.line);
|
|
293
|
+
button.tabIndex = -1;
|
|
294
|
+
button.setAttribute('aria-label', paint.action.label);
|
|
295
|
+
item.append(button);
|
|
296
|
+
}
|
|
297
|
+
const marker = paint.markers.get(row.line);
|
|
298
|
+
if (marker !== undefined) {
|
|
299
|
+
const button = document.createElement('button');
|
|
300
|
+
button.type = 'button';
|
|
301
|
+
button.className = 'se-gutter-marker';
|
|
302
|
+
button.dataset.gutterMarker = marker.id;
|
|
303
|
+
button.tabIndex = -1;
|
|
304
|
+
button.setAttribute('aria-label', marker.label);
|
|
305
|
+
item.append(button);
|
|
306
|
+
}
|
|
307
|
+
const sign = paint.highlights.get(row.line)?.sign;
|
|
308
|
+
if (sign) {
|
|
309
|
+
const mark = document.createElement('span');
|
|
310
|
+
mark.className = 'se-line-sign';
|
|
311
|
+
mark.textContent = sign;
|
|
312
|
+
mark.style.color = colorValue(paint.highlights.get(row.line).color);
|
|
313
|
+
item.append(mark);
|
|
314
|
+
}
|
|
315
|
+
const bar = paint.attribution.get(row.line);
|
|
316
|
+
if (bar) {
|
|
317
|
+
const mark = document.createElement('span');
|
|
318
|
+
mark.className = 'se-attribution';
|
|
319
|
+
mark.dataset.attributionId = bar.id;
|
|
320
|
+
mark.style.background = colorValue(bar.color);
|
|
321
|
+
mark.style.height = `${row.height}px`;
|
|
322
|
+
item.append(mark);
|
|
323
|
+
}
|
|
324
|
+
const change = paint.changes.get(row.line);
|
|
325
|
+
// The bar of an agent stands where the mark of a change would, and a removal is a triangle between lines that the bar never hides.
|
|
326
|
+
if (change && (!bar || change === 'deleted')) {
|
|
327
|
+
const mark = document.createElement('span');
|
|
328
|
+
mark.className = `se-change se-change-${change}`;
|
|
329
|
+
if (change !== 'deleted') {
|
|
330
|
+
mark.style.height = `${row.height}px`;
|
|
331
|
+
}
|
|
332
|
+
item.append(mark);
|
|
333
|
+
}
|
|
334
|
+
const collapsed = paint.foldable.get(row.line);
|
|
335
|
+
if (collapsed !== undefined) {
|
|
336
|
+
const button = document.createElement('button');
|
|
337
|
+
button.type = 'button';
|
|
338
|
+
button.className = collapsed ? 'se-fold-toggle se-folded' : 'se-fold-toggle';
|
|
339
|
+
button.dataset.foldLine = String(row.line);
|
|
340
|
+
button.tabIndex = -1;
|
|
341
|
+
button.setAttribute('aria-label', collapsed ? 'Expand' : 'Collapse');
|
|
342
|
+
item.append(button);
|
|
343
|
+
}
|
|
344
|
+
fragment.append(item);
|
|
345
|
+
}
|
|
346
|
+
container.replaceChildren(fragment);
|
|
347
|
+
}
|
|
348
|
+
function box(document, className, rect) {
|
|
349
|
+
const element = document.createElement('div');
|
|
350
|
+
element.className = className;
|
|
351
|
+
element.style.left = `${rect.x}px`;
|
|
352
|
+
element.style.top = `${rect.y}px`;
|
|
353
|
+
element.style.width = `${Math.max(1, rect.width)}px`;
|
|
354
|
+
element.style.height = `${rect.height}px`;
|
|
355
|
+
return element;
|
|
356
|
+
}
|
|
357
|
+
/* What sits behind the text: the current line, the selections and the marks of find and brackets. */
|
|
358
|
+
export function paintOverlays(container, paint) {
|
|
359
|
+
const document = container.ownerDocument;
|
|
360
|
+
const fragment = document.createDocumentFragment();
|
|
361
|
+
if (paint.currentLine) {
|
|
362
|
+
fragment.append(box(document, 'se-current-line', paint.currentLine));
|
|
363
|
+
}
|
|
364
|
+
for (const mark of paint.marks) {
|
|
365
|
+
for (const rect of mark.rects) {
|
|
366
|
+
const element = box(document, mark.className, rect);
|
|
367
|
+
if (mark.background) {
|
|
368
|
+
element.style.background = mark.background;
|
|
369
|
+
}
|
|
370
|
+
fragment.append(element);
|
|
371
|
+
}
|
|
372
|
+
}
|
|
373
|
+
for (const rect of paint.selections) {
|
|
374
|
+
fragment.append(box(document, paint.focused ? 'se-selection' : 'se-selection se-selection-inactive', rect));
|
|
375
|
+
}
|
|
376
|
+
container.replaceChildren(fragment);
|
|
377
|
+
}
|
|
378
|
+
/* What is drawn over the text, such as the fading of code nothing uses. */
|
|
379
|
+
export function paintOver(container, layers) {
|
|
380
|
+
const document = container.ownerDocument;
|
|
381
|
+
const fragment = document.createDocumentFragment();
|
|
382
|
+
for (const layer of layers) {
|
|
383
|
+
for (const rect of layer.rects) {
|
|
384
|
+
fragment.append(box(document, layer.className, rect));
|
|
385
|
+
}
|
|
386
|
+
}
|
|
387
|
+
container.replaceChildren(fragment);
|
|
388
|
+
}
|
|
389
|
+
/* The carets, above the text. */
|
|
390
|
+
export function paintCarets(container, carets) {
|
|
391
|
+
const document = container.ownerDocument;
|
|
392
|
+
container.replaceChildren(...carets.map((rect) => box(document, rect.drop ? 'se-drop-caret' : rect.primary ? 'se-caret se-primary-caret' : 'se-caret', { ...rect, width: 2 })));
|
|
393
|
+
}
|
|
394
|
+
/* The carets of agents with their names, above the text; they take no pointer and never move the real caret. */
|
|
395
|
+
export function paintRemoteCarets(container, carets) {
|
|
396
|
+
const document = container.ownerDocument;
|
|
397
|
+
container.replaceChildren(...carets.map((caret) => {
|
|
398
|
+
const element = box(document, 'se-remote-caret', { x: caret.x, y: caret.y, width: 2, height: caret.height });
|
|
399
|
+
element.dataset.remoteId = caret.id;
|
|
400
|
+
element.style.background = colorValue(caret.color);
|
|
401
|
+
const label = document.createElement('span');
|
|
402
|
+
label.className = 'se-remote-label';
|
|
403
|
+
label.textContent = caret.name;
|
|
404
|
+
label.style.background = colorValue(caret.color);
|
|
405
|
+
if (caret.below) {
|
|
406
|
+
label.dataset.below = 'true';
|
|
407
|
+
}
|
|
408
|
+
element.append(label);
|
|
409
|
+
return element;
|
|
410
|
+
}));
|
|
411
|
+
}
|
|
412
|
+
/* The headers pinned at the top: a line number in the gutter's column, then the header's own text. */
|
|
413
|
+
export function paintSticky(container, entries, lineHeight) {
|
|
414
|
+
const document = container.ownerDocument;
|
|
415
|
+
container.replaceChildren(...entries.map((entry, depth) => {
|
|
416
|
+
const row = document.createElement('div');
|
|
417
|
+
row.className = 'se-sticky-row';
|
|
418
|
+
row.dataset.line = String(entry.line);
|
|
419
|
+
row.dataset.depth = String(depth);
|
|
420
|
+
const number = document.createElement('span');
|
|
421
|
+
number.className = 'se-sticky-number';
|
|
422
|
+
number.textContent = String(entry.line + 1);
|
|
423
|
+
const code = document.createElement('div');
|
|
424
|
+
code.className = 'se-sticky-code';
|
|
425
|
+
code.append(lineElement(document, entry.geometry, entry.tokens, lineHeight, true));
|
|
426
|
+
row.append(number, code);
|
|
427
|
+
return row;
|
|
428
|
+
}));
|
|
429
|
+
}
|
|
430
|
+
const SVG = 'http://www.w3.org/2000/svg';
|
|
431
|
+
/*
|
|
432
|
+
* An arrow as the platform draws a tab or a soft wrap: a line across the middle of half a line's height,
|
|
433
|
+
* ending in a head that points right with a bar behind it, in as much room as it has.
|
|
434
|
+
*/
|
|
435
|
+
export function arrowSign(document, width, lineHeight, className) {
|
|
436
|
+
const svg = document.createElementNS(SVG, 'svg');
|
|
437
|
+
svg.setAttribute('class', className);
|
|
438
|
+
svg.setAttribute('width', String(Math.max(1, Math.round(width))));
|
|
439
|
+
svg.setAttribute('height', String(lineHeight));
|
|
440
|
+
const height = Math.round(lineHeight / 2);
|
|
441
|
+
const stop = Math.max(1, Math.round(width)) - 1;
|
|
442
|
+
const bottom = Math.round((lineHeight + height) / 2);
|
|
443
|
+
const top = bottom - height;
|
|
444
|
+
const middle = bottom - height / 2;
|
|
445
|
+
const head = Math.max(1, Math.round(height / 2));
|
|
446
|
+
const path = document.createElementNS(SVG, 'path');
|
|
447
|
+
path.setAttribute('d', `M0 ${middle}H${stop}M${stop} ${top}V${bottom}M${stop - head} ${top}L${stop} ${middle}L${stop - head} ${bottom}Z`);
|
|
448
|
+
path.setAttribute('fill', 'currentColor');
|
|
449
|
+
path.setAttribute('stroke', 'currentColor');
|
|
450
|
+
path.setAttribute('stroke-width', '1');
|
|
451
|
+
svg.append(path);
|
|
452
|
+
return svg;
|
|
453
|
+
}
|
|
454
|
+
/* An arrow over each tab of a line. */
|
|
455
|
+
function appendTabSigns(element, geometry, lineHeight, firstRowOnly) {
|
|
456
|
+
const document = element.ownerDocument;
|
|
457
|
+
for (let stop = 0; stop < geometry.offsets.length - 1; stop++) {
|
|
458
|
+
if (geometry.text[geometry.offsets[stop]] !== '\t' || (firstRowOnly && geometry.rowOf[stop] > 0)) {
|
|
459
|
+
continue;
|
|
460
|
+
}
|
|
461
|
+
const left = geometry.before[stop];
|
|
462
|
+
const right = geometry.rowOf[stop + 1] === geometry.rowOf[stop] ? geometry.before[stop + 1] : geometry.rowEnds[geometry.rowOf[stop]];
|
|
463
|
+
if (right - left < 3) {
|
|
464
|
+
continue;
|
|
465
|
+
}
|
|
466
|
+
const sign = arrowSign(document, right - left - 2, lineHeight, 'se-tab-sign');
|
|
467
|
+
sign.style.left = `${left + 1}px`;
|
|
468
|
+
sign.style.top = `${geometry.rowOf[stop] * lineHeight}px`;
|
|
469
|
+
element.append(sign);
|
|
470
|
+
}
|
|
471
|
+
}
|
|
472
|
+
/* The signs of the soft wraps: an arrow where a row ends in the middle of a line, and one at the start of the row that goes on. */
|
|
473
|
+
export function paintSigns(container, signs, lineHeight) {
|
|
474
|
+
const document = container.ownerDocument;
|
|
475
|
+
container.replaceChildren(...signs.map((sign) => {
|
|
476
|
+
const arrow = arrowSign(document, sign.width, lineHeight, 'se-wrap-sign');
|
|
477
|
+
arrow.style.left = `${sign.x}px`;
|
|
478
|
+
arrow.style.top = `${sign.y}px`;
|
|
479
|
+
return arrow;
|
|
480
|
+
}));
|
|
481
|
+
}
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
import type { Highlighter } from 'shiki';
|
|
2
|
+
type LanguageRegistration = Extract<Parameters<Highlighter['loadLanguage']>[0], {
|
|
3
|
+
readonly scopeName: string;
|
|
4
|
+
}>;
|
|
5
|
+
export declare const PHP_DOCUMENT_ID = "php-html";
|
|
6
|
+
export declare const PHP_DOCUMENT: LanguageRegistration;
|
|
7
|
+
export declare function documentLanguageOf(language: string): string;
|
|
8
|
+
export declare function loadDocumentLanguage(highlighter: Highlighter, language: string): Promise<void>;
|
|
9
|
+
export {};
|
package/dist/php-html.js
ADDED
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
/* Shiki's `php` is only the code inside the tags, which makes a plain `<?php` an operator and a constant. */
|
|
2
|
+
export const PHP_DOCUMENT_ID = 'php-html';
|
|
3
|
+
/*
|
|
4
|
+
* A PHP file as a document: HTML with PHP between its tags, the way `text.html.php` of the PHP
|
|
5
|
+
* grammar family does it. Shiki bundles no such grammar, so it is written here on top of `html` and
|
|
6
|
+
* `php`, which have to be loaded first. The tags carry the scope a theme draws a tag's brackets in.
|
|
7
|
+
*/
|
|
8
|
+
export const PHP_DOCUMENT = {
|
|
9
|
+
name: PHP_DOCUMENT_ID,
|
|
10
|
+
scopeName: 'text.html.php',
|
|
11
|
+
embeddedLangs: ['html', 'php'],
|
|
12
|
+
patterns: [
|
|
13
|
+
{
|
|
14
|
+
begin: '(?i)<\\?(?:php|=)?',
|
|
15
|
+
beginCaptures: { '0': { name: 'punctuation.definition.tag.begin.php' } },
|
|
16
|
+
end: '\\?>',
|
|
17
|
+
endCaptures: { '0': { name: 'punctuation.definition.tag.end.php' } },
|
|
18
|
+
name: 'meta.embedded.block.php',
|
|
19
|
+
contentName: 'source.php',
|
|
20
|
+
patterns: [{ include: 'source.php' }]
|
|
21
|
+
},
|
|
22
|
+
{ include: 'text.html.basic' }
|
|
23
|
+
]
|
|
24
|
+
};
|
|
25
|
+
/* The grammar to color a whole file in: a PHP file is a document, and every other language is its own. */
|
|
26
|
+
export function documentLanguageOf(language) {
|
|
27
|
+
return language === 'php' ? PHP_DOCUMENT_ID : language;
|
|
28
|
+
}
|
|
29
|
+
/* Loads what a language needs besides its own grammar; a grammar that is already in is left alone. */
|
|
30
|
+
export async function loadDocumentLanguage(highlighter, language) {
|
|
31
|
+
if (language !== PHP_DOCUMENT_ID || highlighter.getLoadedLanguages().includes(PHP_DOCUMENT_ID)) {
|
|
32
|
+
return;
|
|
33
|
+
}
|
|
34
|
+
await highlighter.loadLanguage('php');
|
|
35
|
+
await highlighter.loadLanguage(PHP_DOCUMENT);
|
|
36
|
+
}
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
import type { DocumentModel, Selection } from '@adecore/editor-core';
|
|
2
|
+
import type { ContentPoint } from './column-selection.ts';
|
|
3
|
+
export interface VisualLine {
|
|
4
|
+
start: number;
|
|
5
|
+
end: number;
|
|
6
|
+
next: number;
|
|
7
|
+
y: number;
|
|
8
|
+
}
|
|
9
|
+
export interface PointerHost {
|
|
10
|
+
readonly model: DocumentModel;
|
|
11
|
+
readonly viewport: HTMLElement;
|
|
12
|
+
offsetAt(clientX: number, clientY: number): number;
|
|
13
|
+
contentPoint(clientX: number, clientY: number): ContentPoint;
|
|
14
|
+
columnSelections(from: ContentPoint, to: ContentPoint): Selection[] | null;
|
|
15
|
+
visualLine(offset: number): VisualLine;
|
|
16
|
+
lineAt(offset: number): {
|
|
17
|
+
from: number;
|
|
18
|
+
to: number;
|
|
19
|
+
};
|
|
20
|
+
camelHumps(): boolean;
|
|
21
|
+
columnMode(): boolean;
|
|
22
|
+
selectionAt(clientX: number, clientY: number): Selection | null;
|
|
23
|
+
setDropCaret(offset: number | null): void;
|
|
24
|
+
drop(from: number, to: number, offset: number, copy: boolean): void;
|
|
25
|
+
copyHeld(event: PointerEvent): boolean;
|
|
26
|
+
focus(): void;
|
|
27
|
+
scrolled(): void;
|
|
28
|
+
}
|
|
29
|
+
export declare class PointerSelection {
|
|
30
|
+
private drag;
|
|
31
|
+
private frame;
|
|
32
|
+
private lastPress;
|
|
33
|
+
private readonly host;
|
|
34
|
+
constructor(host: PointerHost);
|
|
35
|
+
get dragging(): boolean;
|
|
36
|
+
start(event: PointerEvent, area?: 'text' | 'gutter'): void;
|
|
37
|
+
private pressLineNumber;
|
|
38
|
+
private selectUnit;
|
|
39
|
+
private tweakedByLines;
|
|
40
|
+
private countClick;
|
|
41
|
+
move(event: PointerEvent): void;
|
|
42
|
+
end(event?: PointerEvent): void;
|
|
43
|
+
private updateCarry;
|
|
44
|
+
dispose(): void;
|
|
45
|
+
private cancelFrame;
|
|
46
|
+
private update;
|
|
47
|
+
private reach;
|
|
48
|
+
private autoScroll;
|
|
49
|
+
}
|