@singapore-editor/plugin-ui 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +27 -0
- package/dist/anchoredSurface.d.ts +48 -0
- package/dist/anchoredSurface.d.ts.map +1 -0
- package/dist/anchoredSurface.js +140 -0
- package/dist/anchoredSurface.js.map +1 -0
- package/dist/codeTokens.d.ts +24 -0
- package/dist/codeTokens.d.ts.map +1 -0
- package/dist/codeTokens.js +78 -0
- package/dist/codeTokens.js.map +1 -0
- package/dist/hoverController.d.ts +30 -0
- package/dist/hoverController.d.ts.map +1 -0
- package/dist/hoverController.js +277 -0
- package/dist/hoverController.js.map +1 -0
- package/dist/hoverParticipant.d.ts +48 -0
- package/dist/hoverParticipant.d.ts.map +1 -0
- package/dist/hoverParticipant.js +2 -0
- package/dist/hoverPlugin.d.ts +14 -0
- package/dist/hoverPlugin.d.ts.map +1 -0
- package/dist/hoverPlugin.js +49 -0
- package/dist/hoverPlugin.js.map +1 -0
- package/dist/hoverRegistry.d.ts +7 -0
- package/dist/hoverRegistry.d.ts.map +1 -0
- package/dist/hoverRegistry.js +24 -0
- package/dist/hoverRegistry.js.map +1 -0
- package/dist/hoverToken.d.ts +9 -0
- package/dist/hoverToken.d.ts.map +1 -0
- package/dist/hoverToken.js +17 -0
- package/dist/hoverToken.js.map +1 -0
- package/dist/index.d.ts +11 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +11 -0
- package/dist/markdownTooltip.d.ts +12 -0
- package/dist/markdownTooltip.d.ts.map +1 -0
- package/dist/markdownTooltip.js +282 -0
- package/dist/markdownTooltip.js.map +1 -0
- package/dist/offsetRange.d.ts +24 -0
- package/dist/offsetRange.d.ts.map +1 -0
- package/dist/offsetRange.js +81 -0
- package/dist/offsetRange.js.map +1 -0
- package/dist/styles.d.ts +13 -0
- package/dist/styles.d.ts.map +1 -0
- package/dist/styles.js +39 -0
- package/dist/styles.js.map +1 -0
- package/dist/tooltip.d.ts +71 -0
- package/dist/tooltip.d.ts.map +1 -0
- package/dist/tooltip.js +786 -0
- package/dist/tooltip.js.map +1 -0
- package/package.json +100 -0
|
@@ -0,0 +1,277 @@
|
|
|
1
|
+
import { anchoredSurfaceFollowsUpdate } from "./anchoredSurface.js";
|
|
2
|
+
import { createTooltipCodeTokenizer } from "./codeTokens.js";
|
|
3
|
+
import { tooltipCodeBlocks } from "./markdownTooltip.js";
|
|
4
|
+
import { createTooltipController } from "./tooltip.js";
|
|
5
|
+
import { EDITOR_HOVER_PARTICIPANT } from "./hoverToken.js";
|
|
6
|
+
import { hoverTargetRangeInSource, sameOffsetRange, unionOffsetRange } from "./offsetRange.js";
|
|
7
|
+
import { EDITOR_SNIPPET_TOKENS_FEATURE } from "@singapore-editor/core/extensions";
|
|
8
|
+
//#region src/hoverController.ts
|
|
9
|
+
/**
|
|
10
|
+
* The one hover an editor view has.
|
|
11
|
+
*
|
|
12
|
+
* Every plugin with something to say at a position registers an EditorHoverParticipant; this asks
|
|
13
|
+
* all of them on one clock and paints their answers in one surface. Two plugins can therefore never
|
|
14
|
+
* put two hovers on screen, and neither can open on a different delay than the other: the timing
|
|
15
|
+
* is not theirs to set.
|
|
16
|
+
*
|
|
17
|
+
* The clock is VS Code's. With a 300ms delay, async work starts at 150ms so a server answer
|
|
18
|
+
* overlaps the rest of the dwell, sync work runs and the surface paints at 300ms, and a hover still
|
|
19
|
+
* waiting on a server at 900ms shows a loading row.
|
|
20
|
+
*/
|
|
21
|
+
function createHoverController(options) {
|
|
22
|
+
const { context } = options;
|
|
23
|
+
const element = context.scrollElement;
|
|
24
|
+
const ownerDocument = context.container.ownerDocument;
|
|
25
|
+
let operation = null;
|
|
26
|
+
let nextOperationId = 0;
|
|
27
|
+
let theme = context.getSnapshot().theme ?? null;
|
|
28
|
+
let disposed = false;
|
|
29
|
+
const snippetTokens = context.getFeature(EDITOR_SNIPPET_TOKENS_FEATURE);
|
|
30
|
+
const codeTokenizer = snippetTokens ? createTooltipCodeTokenizer(snippetTokens) : null;
|
|
31
|
+
const tooltip = createTooltipController({
|
|
32
|
+
document: ownerDocument,
|
|
33
|
+
themeSource: element,
|
|
34
|
+
reentryElement: element,
|
|
35
|
+
markdownCodeBackground: options.markdownCodeBackground,
|
|
36
|
+
classNamespace: options.classNamespace,
|
|
37
|
+
codeTokenizer,
|
|
38
|
+
onDidHide: () => cancelOperation(),
|
|
39
|
+
onRequestEditorFocus: () => context.focusEditor()
|
|
40
|
+
});
|
|
41
|
+
const cancelOperation = () => {
|
|
42
|
+
const current = operation;
|
|
43
|
+
if (!current) return;
|
|
44
|
+
operation = null;
|
|
45
|
+
clearTimers(current);
|
|
46
|
+
current.abort.abort();
|
|
47
|
+
};
|
|
48
|
+
const hide = () => {
|
|
49
|
+
cancelOperation();
|
|
50
|
+
tooltip.hide();
|
|
51
|
+
};
|
|
52
|
+
const warmCodeBlock = (block) => {
|
|
53
|
+
codeTokenizer?.tokenize(block.text, block.languageId);
|
|
54
|
+
};
|
|
55
|
+
const isCurrent = (candidate) => operation === candidate;
|
|
56
|
+
const render = (current) => {
|
|
57
|
+
if (!isCurrent(current)) return;
|
|
58
|
+
const parts = orderedParts(current);
|
|
59
|
+
const blocks = parts.flatMap((part) => part.markdown ? tooltipCodeBlocks(part.markdown) : []);
|
|
60
|
+
if (!current.revealed) return blocks.forEach(warmCodeBlock);
|
|
61
|
+
const pending = current.pendingAsync > 0;
|
|
62
|
+
if (parts.length === 0 && pending && !current.loading) return;
|
|
63
|
+
if (parts.length === 0 && !pending) return hide();
|
|
64
|
+
const tokens = codeTokenizer?.prepare(blocks);
|
|
65
|
+
if (tokens) return void tokens.then(() => render(current));
|
|
66
|
+
const range = unionOffsetRange(parts.map((part) => part.range)) ?? current.anchor.range;
|
|
67
|
+
const rect = context.getRangeClientRect(range.start, range.end);
|
|
68
|
+
if (!rect) return hide();
|
|
69
|
+
tooltip.show({
|
|
70
|
+
anchor: rect,
|
|
71
|
+
hoverText: null,
|
|
72
|
+
parts,
|
|
73
|
+
theme,
|
|
74
|
+
loading: current.loading && pending,
|
|
75
|
+
focus: current.focusOnShow && !current.shown,
|
|
76
|
+
preferredPlacement: "top"
|
|
77
|
+
});
|
|
78
|
+
current.shown = true;
|
|
79
|
+
};
|
|
80
|
+
const runAsync = (current, request) => {
|
|
81
|
+
for (const participant of current.participants) {
|
|
82
|
+
if (!participant.computeAsync) continue;
|
|
83
|
+
current.pendingAsync += 1;
|
|
84
|
+
const emit = (parts) => {
|
|
85
|
+
if (!isCurrent(current)) return;
|
|
86
|
+
resultFor(current, participant).async = parts;
|
|
87
|
+
render(current);
|
|
88
|
+
};
|
|
89
|
+
participant.computeAsync(request, emit).catch(() => void 0).finally(() => {
|
|
90
|
+
if (!isCurrent(current)) return;
|
|
91
|
+
current.pendingAsync -= 1;
|
|
92
|
+
if (current.pendingAsync === 0 && current.loadingTimer) clearTimeout(current.loadingTimer);
|
|
93
|
+
render(current);
|
|
94
|
+
});
|
|
95
|
+
}
|
|
96
|
+
};
|
|
97
|
+
const runSync = (current, request) => {
|
|
98
|
+
for (const participant of current.participants) {
|
|
99
|
+
if (!participant.computeSync) continue;
|
|
100
|
+
resultFor(current, participant).sync = participant.computeSync(request);
|
|
101
|
+
}
|
|
102
|
+
};
|
|
103
|
+
const reveal = (current, request) => {
|
|
104
|
+
if (!isCurrent(current)) return;
|
|
105
|
+
runSync(current, request);
|
|
106
|
+
current.revealed = true;
|
|
107
|
+
render(current);
|
|
108
|
+
};
|
|
109
|
+
const revealLoading = (current) => {
|
|
110
|
+
if (!isCurrent(current) || current.pendingAsync === 0) return;
|
|
111
|
+
current.revealed = true;
|
|
112
|
+
current.loading = true;
|
|
113
|
+
current.loadingTimer = null;
|
|
114
|
+
render(current);
|
|
115
|
+
};
|
|
116
|
+
const start = (anchor, focusOnShow) => {
|
|
117
|
+
const snapshot = context.getSnapshot();
|
|
118
|
+
const participants = context.getProviders(EDITOR_HOVER_PARTICIPANT, snapshot.languageId);
|
|
119
|
+
if (participants.length === 0) return false;
|
|
120
|
+
hide();
|
|
121
|
+
nextOperationId += 1;
|
|
122
|
+
const current = {
|
|
123
|
+
id: nextOperationId,
|
|
124
|
+
anchor,
|
|
125
|
+
snapshot,
|
|
126
|
+
participants,
|
|
127
|
+
results: /* @__PURE__ */ new Map(),
|
|
128
|
+
abort: new AbortController(),
|
|
129
|
+
focusOnShow,
|
|
130
|
+
pendingAsync: 0,
|
|
131
|
+
revealed: false,
|
|
132
|
+
loading: false,
|
|
133
|
+
shown: false,
|
|
134
|
+
dispatchTimer: null,
|
|
135
|
+
revealTimer: null,
|
|
136
|
+
loadingTimer: null
|
|
137
|
+
};
|
|
138
|
+
operation = current;
|
|
139
|
+
const request = {
|
|
140
|
+
anchor,
|
|
141
|
+
snapshot,
|
|
142
|
+
signal: current.abort.signal
|
|
143
|
+
};
|
|
144
|
+
current.loadingTimer = setTimeout(() => revealLoading(current), 900);
|
|
145
|
+
if (focusOnShow) {
|
|
146
|
+
runAsync(current, request);
|
|
147
|
+
reveal(current, request);
|
|
148
|
+
return true;
|
|
149
|
+
}
|
|
150
|
+
current.dispatchTimer = setTimeout(() => {
|
|
151
|
+
current.dispatchTimer = null;
|
|
152
|
+
runAsync(current, request);
|
|
153
|
+
}, 150);
|
|
154
|
+
current.revealTimer = setTimeout(() => {
|
|
155
|
+
current.revealTimer = null;
|
|
156
|
+
reveal(current, request);
|
|
157
|
+
}, 300);
|
|
158
|
+
return true;
|
|
159
|
+
};
|
|
160
|
+
const scheduleAt = (offset, point) => {
|
|
161
|
+
tooltip.cancelHide();
|
|
162
|
+
const snapshot = context.getSnapshot();
|
|
163
|
+
const range = hoverTargetRangeInSource(snapshot.textSnapshot, offset);
|
|
164
|
+
const current = operation;
|
|
165
|
+
if (current && current.snapshot.textVersion === snapshot.textVersion && sameOffsetRange(current.anchor.range, range)) return;
|
|
166
|
+
start({
|
|
167
|
+
offset,
|
|
168
|
+
range,
|
|
169
|
+
source: "pointer",
|
|
170
|
+
point
|
|
171
|
+
}, false);
|
|
172
|
+
};
|
|
173
|
+
const handlePointerMove = (event) => {
|
|
174
|
+
if (event.buttons !== 0) return hide();
|
|
175
|
+
if (tooltip.shouldKeepForPointer(event.clientX, event.clientY)) return tooltip.cancelHide();
|
|
176
|
+
if (!context.hasDocument()) return hide();
|
|
177
|
+
const offset = context.textOffsetFromPoint(event.clientX, event.clientY);
|
|
178
|
+
if (offset === null) {
|
|
179
|
+
cancelOperation();
|
|
180
|
+
return tooltip.scheduleHide();
|
|
181
|
+
}
|
|
182
|
+
scheduleAt(offset, {
|
|
183
|
+
clientX: event.clientX,
|
|
184
|
+
clientY: event.clientY
|
|
185
|
+
});
|
|
186
|
+
};
|
|
187
|
+
const handlePointerLeave = (event) => {
|
|
188
|
+
if (tooltip.containsTarget(event.relatedTarget)) return tooltip.cancelHide();
|
|
189
|
+
if (operation && !operation.shown) cancelOperation();
|
|
190
|
+
tooltip.scheduleHide();
|
|
191
|
+
};
|
|
192
|
+
const handleDocumentPointerDown = (event) => {
|
|
193
|
+
if (event.button !== 0) return;
|
|
194
|
+
if (tooltip.containsTarget(event.target)) return;
|
|
195
|
+
hide();
|
|
196
|
+
};
|
|
197
|
+
const handleKeyDown = (event) => {
|
|
198
|
+
if (isModifierKey(event.key)) return;
|
|
199
|
+
hide();
|
|
200
|
+
};
|
|
201
|
+
element.addEventListener("pointermove", handlePointerMove);
|
|
202
|
+
element.addEventListener("pointerleave", handlePointerLeave);
|
|
203
|
+
element.addEventListener("keydown", handleKeyDown);
|
|
204
|
+
ownerDocument.addEventListener("pointerdown", handleDocumentPointerDown, { capture: true });
|
|
205
|
+
return {
|
|
206
|
+
update: (snapshot, kind) => {
|
|
207
|
+
const nextTheme = snapshot.theme ?? null;
|
|
208
|
+
if (nextTheme !== theme) codeTokenizer?.clear();
|
|
209
|
+
theme = nextTheme;
|
|
210
|
+
if (shouldHideOnUpdate(kind)) hide();
|
|
211
|
+
},
|
|
212
|
+
showAtOffset: (offset, showOptions = {}) => {
|
|
213
|
+
if (disposed || !context.hasDocument()) return false;
|
|
214
|
+
const range = hoverTargetRangeInSource(context.getSnapshot().textSnapshot, offset);
|
|
215
|
+
return start({
|
|
216
|
+
offset,
|
|
217
|
+
range,
|
|
218
|
+
source: "keyboard"
|
|
219
|
+
}, showOptions.focus ?? false);
|
|
220
|
+
},
|
|
221
|
+
hide,
|
|
222
|
+
containsTarget: (target) => tooltip.containsTarget(target),
|
|
223
|
+
dispose: () => {
|
|
224
|
+
if (disposed) return;
|
|
225
|
+
disposed = true;
|
|
226
|
+
element.removeEventListener("pointermove", handlePointerMove);
|
|
227
|
+
element.removeEventListener("pointerleave", handlePointerLeave);
|
|
228
|
+
element.removeEventListener("keydown", handleKeyDown);
|
|
229
|
+
ownerDocument.removeEventListener("pointerdown", handleDocumentPointerDown, { capture: true });
|
|
230
|
+
hide();
|
|
231
|
+
tooltip.dispose();
|
|
232
|
+
}
|
|
233
|
+
};
|
|
234
|
+
}
|
|
235
|
+
function resultFor(operation, participant) {
|
|
236
|
+
const existing = operation.results.get(participant);
|
|
237
|
+
if (existing) return existing;
|
|
238
|
+
const created = {
|
|
239
|
+
sync: [],
|
|
240
|
+
async: []
|
|
241
|
+
};
|
|
242
|
+
operation.results.set(participant, created);
|
|
243
|
+
return created;
|
|
244
|
+
}
|
|
245
|
+
/** Every part on screen, lowest ordinal first; ties keep participant and emission order. */
|
|
246
|
+
function orderedParts(operation) {
|
|
247
|
+
const parts = [];
|
|
248
|
+
for (const participant of operation.participants) {
|
|
249
|
+
const result = operation.results.get(participant);
|
|
250
|
+
if (!result) continue;
|
|
251
|
+
parts.push(...result.sync, ...result.async);
|
|
252
|
+
}
|
|
253
|
+
return parts.toSorted((left, right) => left.ordinal - right.ordinal);
|
|
254
|
+
}
|
|
255
|
+
function clearTimers(operation) {
|
|
256
|
+
if (operation.dispatchTimer) clearTimeout(operation.dispatchTimer);
|
|
257
|
+
if (operation.revealTimer) clearTimeout(operation.revealTimer);
|
|
258
|
+
if (operation.loadingTimer) clearTimeout(operation.loadingTimer);
|
|
259
|
+
operation.dispatchTimer = null;
|
|
260
|
+
operation.revealTimer = null;
|
|
261
|
+
operation.loadingTimer = null;
|
|
262
|
+
}
|
|
263
|
+
/**
|
|
264
|
+
* A scroll or relayout moves the text out from under the hover, and an edit may have changed what
|
|
265
|
+
* it says. Both belong to the old frame, so the hover goes with them.
|
|
266
|
+
*/
|
|
267
|
+
function shouldHideOnUpdate(kind) {
|
|
268
|
+
if (anchoredSurfaceFollowsUpdate(kind)) return true;
|
|
269
|
+
return kind === "content" || kind === "document" || kind === "clear";
|
|
270
|
+
}
|
|
271
|
+
function isModifierKey(key) {
|
|
272
|
+
return key === "Shift" || key === "Control" || key === "Alt" || key === "Meta";
|
|
273
|
+
}
|
|
274
|
+
//#endregion
|
|
275
|
+
export { createHoverController };
|
|
276
|
+
|
|
277
|
+
//# sourceMappingURL=hoverController.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"hoverController.js","names":[],"sources":["../src/hoverController.ts"],"sourcesContent":["import type {\n EditorViewContributionContext,\n EditorViewContributionUpdateKind,\n EditorViewSnapshot,\n} from '@singapore-editor/core/extensions'\nimport type { EditorTheme } from '@singapore-editor/core/rendering'\n\nimport { EDITOR_SNIPPET_TOKENS_FEATURE } from '@singapore-editor/core/extensions'\n\nimport { anchoredSurfaceFollowsUpdate } from './anchoredSurface'\nimport { createTooltipCodeTokenizer, type TooltipCodeBlock } from './codeTokens'\nimport { tooltipCodeBlocks } from './markdownTooltip'\nimport type {\n EditorHoverParticipant,\n HoverAnchor,\n HoverPart,\n HoverRequest,\n} from './hoverParticipant'\nimport { EDITOR_HOVER_PARTICIPANT } from './hoverToken'\nimport { hoverTargetRangeInSource, sameOffsetRange, unionOffsetRange } from './offsetRange'\nimport {\n createTooltipController,\n HOVER_ASYNC_DISPATCH_DELAY_MS,\n HOVER_LOADING_DELAY_MS,\n HOVER_REQUEST_DEBOUNCE_MS,\n type TooltipController,\n} from './tooltip'\n\nexport type HoverControllerOptions = {\n readonly context: EditorViewContributionContext\n readonly classNamespace?: string\n readonly markdownCodeBackground?: boolean\n}\n\nexport type HoverController = {\n update(snapshot: EditorViewSnapshot, kind: EditorViewContributionUpdateKind): void\n /** Shows the hover at an offset right away, as a keyboard summons does. */\n showAtOffset(offset: number, options?: { readonly focus?: boolean }): boolean\n hide(): void\n containsTarget(target: EventTarget | null): boolean\n dispose(): void\n}\n\ntype ParticipantResult = {\n sync: readonly HoverPart[]\n async: readonly HoverPart[]\n}\n\ntype HoverOperation = {\n readonly id: number\n readonly anchor: HoverAnchor\n readonly snapshot: EditorViewSnapshot\n readonly participants: readonly EditorHoverParticipant[]\n readonly results: Map<EditorHoverParticipant, ParticipantResult>\n readonly abort: AbortController\n readonly focusOnShow: boolean\n pendingAsync: number\n revealed: boolean\n loading: boolean\n shown: boolean\n dispatchTimer: ReturnType<typeof setTimeout> | null\n revealTimer: ReturnType<typeof setTimeout> | null\n loadingTimer: ReturnType<typeof setTimeout> | null\n}\n\n/**\n * The one hover an editor view has.\n *\n * Every plugin with something to say at a position registers an EditorHoverParticipant; this asks\n * all of them on one clock and paints their answers in one surface. Two plugins can therefore never\n * put two hovers on screen, and neither can open on a different delay than the other: the timing\n * is not theirs to set.\n *\n * The clock is VS Code's. With a 300ms delay, async work starts at 150ms so a server answer\n * overlaps the rest of the dwell, sync work runs and the surface paints at 300ms, and a hover still\n * waiting on a server at 900ms shows a loading row.\n */\nexport function createHoverController(options: HoverControllerOptions): HoverController {\n const { context } = options\n const element = context.scrollElement\n const ownerDocument = context.container.ownerDocument\n let operation: HoverOperation | null = null\n let nextOperationId = 0\n let theme: EditorTheme | null = context.getSnapshot().theme ?? null\n let disposed = false\n const snippetTokens = context.getFeature(EDITOR_SNIPPET_TOKENS_FEATURE)\n const codeTokenizer = snippetTokens ? createTooltipCodeTokenizer(snippetTokens) : null\n\n const tooltip: TooltipController = createTooltipController({\n document: ownerDocument,\n themeSource: element,\n reentryElement: element,\n markdownCodeBackground: options.markdownCodeBackground,\n classNamespace: options.classNamespace,\n codeTokenizer,\n onDidHide: () => cancelOperation(),\n onRequestEditorFocus: () => context.focusEditor(),\n })\n\n const cancelOperation = (): void => {\n const current = operation\n if (!current) return\n operation = null\n clearTimers(current)\n current.abort.abort()\n }\n\n const hide = (): void => {\n cancelOperation()\n tooltip.hide()\n }\n\n const warmCodeBlock = (block: TooltipCodeBlock): void => {\n void codeTokenizer?.tokenize(block.text, block.languageId)\n }\n\n const isCurrent = (candidate: HoverOperation): boolean => operation === candidate\n\n const render = (current: HoverOperation): void => {\n if (!isCurrent(current)) return\n\n const parts = orderedParts(current)\n const blocks = parts.flatMap((part) => (part.markdown ? tooltipCodeBlocks(part.markdown) : []))\n // An answer usually lands inside the dwell, so its tokens are asked for before the reveal.\n if (!current.revealed) return blocks.forEach(warmCodeBlock)\n\n const pending = current.pendingAsync > 0\n if (parts.length === 0 && pending && !current.loading) return\n if (parts.length === 0 && !pending) return hide()\n\n const tokens = codeTokenizer?.prepare(blocks)\n if (tokens) return void tokens.then(() => render(current))\n\n const range = unionOffsetRange(parts.map((part) => part.range)) ?? current.anchor.range\n const rect = context.getRangeClientRect(range.start, range.end)\n if (!rect) return hide()\n\n tooltip.show({\n anchor: rect,\n hoverText: null,\n parts,\n theme,\n loading: current.loading && pending,\n focus: current.focusOnShow && !current.shown,\n preferredPlacement: 'top',\n })\n current.shown = true\n }\n\n const runAsync = (current: HoverOperation, request: HoverRequest): void => {\n for (const participant of current.participants) {\n if (!participant.computeAsync) continue\n current.pendingAsync += 1\n const emit = (parts: readonly HoverPart[]): void => {\n if (!isCurrent(current)) return\n resultFor(current, participant).async = parts\n render(current)\n }\n participant\n .computeAsync(request, emit)\n .catch(() => undefined)\n .finally(() => {\n if (!isCurrent(current)) return\n current.pendingAsync -= 1\n if (current.pendingAsync === 0 && current.loadingTimer) clearTimeout(current.loadingTimer)\n render(current)\n })\n }\n }\n\n const runSync = (current: HoverOperation, request: HoverRequest): void => {\n for (const participant of current.participants) {\n if (!participant.computeSync) continue\n resultFor(current, participant).sync = participant.computeSync(request)\n }\n }\n\n const reveal = (current: HoverOperation, request: HoverRequest): void => {\n if (!isCurrent(current)) return\n runSync(current, request)\n current.revealed = true\n render(current)\n }\n\n const revealLoading = (current: HoverOperation): void => {\n if (!isCurrent(current) || current.pendingAsync === 0) return\n current.revealed = true\n current.loading = true\n current.loadingTimer = null\n render(current)\n }\n\n const start = (anchor: HoverAnchor, focusOnShow: boolean): boolean => {\n const snapshot = context.getSnapshot()\n const participants = context.getProviders(EDITOR_HOVER_PARTICIPANT, snapshot.languageId)\n if (participants.length === 0) return false\n\n hide()\n nextOperationId += 1\n const current: HoverOperation = {\n id: nextOperationId,\n anchor,\n snapshot,\n participants,\n results: new Map(),\n abort: new AbortController(),\n focusOnShow,\n pendingAsync: 0,\n revealed: false,\n loading: false,\n shown: false,\n dispatchTimer: null,\n revealTimer: null,\n loadingTimer: null,\n }\n operation = current\n const request: HoverRequest = { anchor, snapshot, signal: current.abort.signal }\n // @justification Delays the loading row until the participants are observably slow; every\n // target change cancels it.\n current.loadingTimer = setTimeout(() => revealLoading(current), HOVER_LOADING_DELAY_MS)\n if (focusOnShow) {\n runAsync(current, request)\n reveal(current, request)\n return true\n }\n\n // @justification Starts server work midway through the pointer dwell so transient targets are\n // skipped while network time overlaps the rest of the dwell.\n current.dispatchTimer = setTimeout(() => {\n current.dispatchTimer = null\n runAsync(current, request)\n }, HOVER_ASYNC_DISPATCH_DELAY_MS)\n // @justification Enforces the hover dwell before paint; the pointer leaving the target clears it.\n current.revealTimer = setTimeout(() => {\n current.revealTimer = null\n reveal(current, request)\n }, HOVER_REQUEST_DEBOUNCE_MS)\n return true\n }\n\n const scheduleAt = (offset: number, point: HoverAnchor['point']): void => {\n tooltip.cancelHide()\n const snapshot = context.getSnapshot()\n const range = hoverTargetRangeInSource(snapshot.textSnapshot, offset)\n const current = operation\n if (\n current &&\n current.snapshot.textVersion === snapshot.textVersion &&\n sameOffsetRange(current.anchor.range, range)\n ) {\n return\n }\n\n start({ offset, range, source: 'pointer', point }, false)\n }\n\n const handlePointerMove = (event: PointerEvent): void => {\n if (event.buttons !== 0) return hide()\n if (tooltip.shouldKeepForPointer(event.clientX, event.clientY)) return tooltip.cancelHide()\n if (!context.hasDocument()) return hide()\n\n const offset = context.textOffsetFromPoint(event.clientX, event.clientY)\n if (offset === null) {\n cancelOperation()\n return tooltip.scheduleHide()\n }\n\n scheduleAt(offset, { clientX: event.clientX, clientY: event.clientY })\n }\n\n const handlePointerLeave = (event: PointerEvent): void => {\n if (tooltip.containsTarget(event.relatedTarget)) return tooltip.cancelHide()\n // A hover that has not painted yet has nothing to keep: the pointer is gone.\n if (operation && !operation.shown) cancelOperation()\n tooltip.scheduleHide()\n }\n\n const handleDocumentPointerDown = (event: PointerEvent): void => {\n if (event.button !== 0) return\n if (tooltip.containsTarget(event.target)) return\n hide()\n }\n\n const handleKeyDown = (event: KeyboardEvent): void => {\n if (isModifierKey(event.key)) return\n hide()\n }\n\n element.addEventListener('pointermove', handlePointerMove)\n element.addEventListener('pointerleave', handlePointerLeave)\n element.addEventListener('keydown', handleKeyDown)\n ownerDocument.addEventListener('pointerdown', handleDocumentPointerDown, { capture: true })\n\n return {\n update: (snapshot, kind) => {\n const nextTheme = snapshot.theme ?? null\n if (nextTheme !== theme) codeTokenizer?.clear()\n theme = nextTheme\n if (shouldHideOnUpdate(kind)) hide()\n },\n showAtOffset: (offset, showOptions = {}) => {\n if (disposed || !context.hasDocument()) return false\n const range = hoverTargetRangeInSource(context.getSnapshot().textSnapshot, offset)\n return start({ offset, range, source: 'keyboard' }, showOptions.focus ?? false)\n },\n hide,\n containsTarget: (target) => tooltip.containsTarget(target),\n dispose: () => {\n if (disposed) return\n disposed = true\n element.removeEventListener('pointermove', handlePointerMove)\n element.removeEventListener('pointerleave', handlePointerLeave)\n element.removeEventListener('keydown', handleKeyDown)\n ownerDocument.removeEventListener('pointerdown', handleDocumentPointerDown, { capture: true })\n hide()\n tooltip.dispose()\n },\n }\n}\n\nfunction resultFor(\n operation: HoverOperation,\n participant: EditorHoverParticipant,\n): ParticipantResult {\n const existing = operation.results.get(participant)\n if (existing) return existing\n const created: ParticipantResult = { sync: [], async: [] }\n operation.results.set(participant, created)\n return created\n}\n\n/** Every part on screen, lowest ordinal first; ties keep participant and emission order. */\nfunction orderedParts(operation: HoverOperation): readonly HoverPart[] {\n const parts: HoverPart[] = []\n for (const participant of operation.participants) {\n const result = operation.results.get(participant)\n if (!result) continue\n parts.push(...result.sync, ...result.async)\n }\n return parts.toSorted((left, right) => left.ordinal - right.ordinal)\n}\n\nfunction clearTimers(operation: HoverOperation): void {\n if (operation.dispatchTimer) clearTimeout(operation.dispatchTimer)\n if (operation.revealTimer) clearTimeout(operation.revealTimer)\n if (operation.loadingTimer) clearTimeout(operation.loadingTimer)\n operation.dispatchTimer = null\n operation.revealTimer = null\n operation.loadingTimer = null\n}\n\n/**\n * A scroll or relayout moves the text out from under the hover, and an edit may have changed what\n * it says. Both belong to the old frame, so the hover goes with them.\n */\nfunction shouldHideOnUpdate(kind: EditorViewContributionUpdateKind): boolean {\n if (anchoredSurfaceFollowsUpdate(kind)) return true\n return kind === 'content' || kind === 'document' || kind === 'clear'\n}\n\nfunction isModifierKey(key: string): boolean {\n return key === 'Shift' || key === 'Control' || key === 'Alt' || key === 'Meta'\n}\n"],"mappings":"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"}
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
import type { EditorViewSnapshot } from '@singapore-editor/core/extensions';
|
|
2
|
+
import type { OffsetRange } from './offsetRange';
|
|
3
|
+
import type { TooltipPart } from './tooltip';
|
|
4
|
+
export { EDITOR_HOVER_PARTICIPANT, EDITOR_HOVER_PARTICIPANT_ID } from './hoverToken';
|
|
5
|
+
/** Where the hover is being asked about. */
|
|
6
|
+
export type HoverAnchor = {
|
|
7
|
+
readonly offset: number;
|
|
8
|
+
/** The identifier under the offset, or the single character there. */
|
|
9
|
+
readonly range: OffsetRange;
|
|
10
|
+
readonly source: 'pointer' | 'keyboard';
|
|
11
|
+
/**
|
|
12
|
+
* Where the pointer is, for a participant whose subject is a painted element rather than text:
|
|
13
|
+
* a zero-width character's marker has an extent the text offset does not.
|
|
14
|
+
*/
|
|
15
|
+
readonly point?: {
|
|
16
|
+
readonly clientX: number;
|
|
17
|
+
readonly clientY: number;
|
|
18
|
+
};
|
|
19
|
+
};
|
|
20
|
+
export type HoverRequest = {
|
|
21
|
+
readonly anchor: HoverAnchor;
|
|
22
|
+
readonly snapshot: EditorViewSnapshot;
|
|
23
|
+
/** Aborted the moment the hover moves on; an answer after that is never shown. */
|
|
24
|
+
readonly signal: AbortSignal;
|
|
25
|
+
};
|
|
26
|
+
/** One thing a participant has to say. The hover shows every part of every participant at once. */
|
|
27
|
+
export type HoverPart = TooltipPart & {
|
|
28
|
+
/** Lower comes first. Parts from one participant keep the order they were emitted in. */
|
|
29
|
+
readonly ordinal: number;
|
|
30
|
+
/** The text this part is about; the hover anchors to the union of its parts' ranges. */
|
|
31
|
+
readonly range: OffsetRange;
|
|
32
|
+
};
|
|
33
|
+
/**
|
|
34
|
+
* A source of hover content. The hover controller owns the pointer, the delay and the surface; a
|
|
35
|
+
* participant only answers what is at a position. Modeled on VS Code's hover participants: sync
|
|
36
|
+
* answers come from what is already in the view (markers, decorations), async ones from a server.
|
|
37
|
+
*/
|
|
38
|
+
export type EditorHoverParticipant = {
|
|
39
|
+
/** Asked once the pointer has settled. Cheap: it runs on the main thread at reveal time. */
|
|
40
|
+
computeSync?(request: HoverRequest): readonly HoverPart[];
|
|
41
|
+
/**
|
|
42
|
+
* Started halfway through the delay so network time overlaps the rest of the dwell. `emit`
|
|
43
|
+
* replaces this participant's async parts, so a progressive answer shows as it grows. Resolves
|
|
44
|
+
* when the participant has nothing more to add.
|
|
45
|
+
*/
|
|
46
|
+
computeAsync?(request: HoverRequest, emit: (parts: readonly HoverPart[]) => void): Promise<void>;
|
|
47
|
+
};
|
|
48
|
+
//# sourceMappingURL=hoverParticipant.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"hoverParticipant.d.ts","sourceRoot":"","sources":["../src/hoverParticipant.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,kBAAkB,EAAE,MAAM,mCAAmC,CAAA;AAE3E,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,eAAe,CAAA;AAChD,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,WAAW,CAAA;AAE5C,OAAO,EAAE,wBAAwB,EAAE,2BAA2B,EAAE,MAAM,cAAc,CAAA;AAEpF,4CAA4C;AAC5C,MAAM,MAAM,WAAW,GAAG;IACxB,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAA;IACvB,sEAAsE;IACtE,QAAQ,CAAC,KAAK,EAAE,WAAW,CAAA;IAC3B,QAAQ,CAAC,MAAM,EAAE,SAAS,GAAG,UAAU,CAAA;IACvC;;;OAGG;IACH,QAAQ,CAAC,KAAK,CAAC,EAAE;QAAE,QAAQ,CAAC,OAAO,EAAE,MAAM,CAAC;QAAC,QAAQ,CAAC,OAAO,EAAE,MAAM,CAAA;KAAE,CAAA;CACxE,CAAA;AAED,MAAM,MAAM,YAAY,GAAG;IACzB,QAAQ,CAAC,MAAM,EAAE,WAAW,CAAA;IAC5B,QAAQ,CAAC,QAAQ,EAAE,kBAAkB,CAAA;IACrC,kFAAkF;IAClF,QAAQ,CAAC,MAAM,EAAE,WAAW,CAAA;CAC7B,CAAA;AAED,mGAAmG;AACnG,MAAM,MAAM,SAAS,GAAG,WAAW,GAAG;IACpC,yFAAyF;IACzF,QAAQ,CAAC,OAAO,EAAE,MAAM,CAAA;IACxB,wFAAwF;IACxF,QAAQ,CAAC,KAAK,EAAE,WAAW,CAAA;CAC5B,CAAA;AAED;;;;GAIG;AACH,MAAM,MAAM,sBAAsB,GAAG;IACnC,4FAA4F;IAC5F,WAAW,CAAC,CAAC,OAAO,EAAE,YAAY,GAAG,SAAS,SAAS,EAAE,CAAA;IACzD;;;;OAIG;IACH,YAAY,CAAC,CAAC,OAAO,EAAE,YAAY,EAAE,IAAI,EAAE,CAAC,KAAK,EAAE,SAAS,SAAS,EAAE,KAAK,IAAI,GAAG,OAAO,CAAC,IAAI,CAAC,CAAA;CACjG,CAAA"}
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
import type { EditorPlugin } from '@singapore-editor/core/extensions';
|
|
2
|
+
export type HoverPluginOptions = {
|
|
3
|
+
readonly name?: string;
|
|
4
|
+
readonly classNamespace?: string;
|
|
5
|
+
readonly markdownCodeBackground?: boolean;
|
|
6
|
+
};
|
|
7
|
+
/**
|
|
8
|
+
* The one hover per view and the command that summons it from the caret. Participants register
|
|
9
|
+
* through EDITOR_HOVER_PARTICIPANT; this plugin has no content of its own. An editor installs it by
|
|
10
|
+
* itself once a participant registers (see hoverToken.ts); a test with a hand-made view context
|
|
11
|
+
* installs it directly.
|
|
12
|
+
*/
|
|
13
|
+
export declare function createHoverPlugin(options?: HoverPluginOptions): EditorPlugin;
|
|
14
|
+
//# sourceMappingURL=hoverPlugin.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"hoverPlugin.d.ts","sourceRoot":"","sources":["../src/hoverPlugin.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EACV,YAAY,EAGb,MAAM,mCAAmC,CAAA;AAK1C,MAAM,MAAM,kBAAkB,GAAG;IAC/B,QAAQ,CAAC,IAAI,CAAC,EAAE,MAAM,CAAA;IACtB,QAAQ,CAAC,cAAc,CAAC,EAAE,MAAM,CAAA;IAChC,QAAQ,CAAC,sBAAsB,CAAC,EAAE,OAAO,CAAA;CAC1C,CAAA;AAOD;;;;;GAKG;AACH,wBAAgB,iBAAiB,CAAC,OAAO,GAAE,kBAAuB,GAAG,YAAY,CAKhF"}
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
import { createHoverController } from "./hoverController.js";
|
|
2
|
+
import { clearHoverController, setHoverController } from "./hoverRegistry.js";
|
|
3
|
+
//#region src/hoverPlugin.ts
|
|
4
|
+
/**
|
|
5
|
+
* The one hover per view and the command that summons it from the caret. Participants register
|
|
6
|
+
* through EDITOR_HOVER_PARTICIPANT; this plugin has no content of its own. An editor installs it by
|
|
7
|
+
* itself once a participant registers (see hoverToken.ts); a test with a hand-made view context
|
|
8
|
+
* installs it directly.
|
|
9
|
+
*/
|
|
10
|
+
function createHoverPlugin(options = {}) {
|
|
11
|
+
return {
|
|
12
|
+
name: options.name ?? "editor.hover",
|
|
13
|
+
activate: (plugin) => activateHover(plugin, options)
|
|
14
|
+
};
|
|
15
|
+
}
|
|
16
|
+
function activateHover(plugin, options) {
|
|
17
|
+
const live = /* @__PURE__ */ new Set();
|
|
18
|
+
return [plugin.registerViewContribution({ createContribution: (context) => {
|
|
19
|
+
const controller = createHoverController({
|
|
20
|
+
context,
|
|
21
|
+
...options
|
|
22
|
+
});
|
|
23
|
+
const entry = {
|
|
24
|
+
context,
|
|
25
|
+
controller
|
|
26
|
+
};
|
|
27
|
+
live.add(entry);
|
|
28
|
+
setHoverController(context.scrollElement, controller);
|
|
29
|
+
return {
|
|
30
|
+
update: (snapshot, kind) => controller.update(snapshot, kind),
|
|
31
|
+
dispose: () => {
|
|
32
|
+
live.delete(entry);
|
|
33
|
+
clearHoverController(context.scrollElement, controller);
|
|
34
|
+
controller.dispose();
|
|
35
|
+
}
|
|
36
|
+
};
|
|
37
|
+
} }), plugin.registerCommandContribution({ createContribution: (commands) => commands.registerCommand("editor.action.showHover", () => {
|
|
38
|
+
for (const { context, controller } of live) {
|
|
39
|
+
const selection = context.getSnapshot().selections[0];
|
|
40
|
+
if (!selection) continue;
|
|
41
|
+
if (controller.showAtOffset(selection.headOffset, { focus: true })) return true;
|
|
42
|
+
}
|
|
43
|
+
return false;
|
|
44
|
+
}) })];
|
|
45
|
+
}
|
|
46
|
+
//#endregion
|
|
47
|
+
export { createHoverPlugin };
|
|
48
|
+
|
|
49
|
+
//# sourceMappingURL=hoverPlugin.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"hoverPlugin.js","names":[],"sources":["../src/hoverPlugin.ts"],"sourcesContent":["import type {\n EditorPlugin,\n EditorPluginContext,\n EditorViewContributionContext,\n} from '@singapore-editor/core/extensions'\n\nimport { createHoverController, type HoverController } from './hoverController'\nimport { clearHoverController, setHoverController } from './hoverRegistry'\n\nexport type HoverPluginOptions = {\n readonly name?: string\n readonly classNamespace?: string\n readonly markdownCodeBackground?: boolean\n}\n\ntype LiveHover = {\n readonly context: EditorViewContributionContext\n readonly controller: HoverController\n}\n\n/**\n * The one hover per view and the command that summons it from the caret. Participants register\n * through EDITOR_HOVER_PARTICIPANT; this plugin has no content of its own. An editor installs it by\n * itself once a participant registers (see hoverToken.ts); a test with a hand-made view context\n * installs it directly.\n */\nexport function createHoverPlugin(options: HoverPluginOptions = {}): EditorPlugin {\n return {\n name: options.name ?? 'editor.hover',\n activate: (plugin) => activateHover(plugin, options),\n }\n}\n\nfunction activateHover(plugin: EditorPluginContext, options: HoverPluginOptions) {\n // Per activation, so the command only walks the views of the editor that dispatched it.\n const live = new Set<LiveHover>()\n return [\n plugin.registerViewContribution({\n createContribution: (context) => {\n const controller = createHoverController({ context, ...options })\n const entry: LiveHover = { context, controller }\n live.add(entry)\n setHoverController(context.scrollElement, controller)\n return {\n update: (snapshot, kind) => controller.update(snapshot, kind),\n dispose: () => {\n live.delete(entry)\n clearHoverController(context.scrollElement, controller)\n controller.dispose()\n },\n }\n },\n }),\n plugin.registerCommandContribution({\n createContribution: (commands) =>\n commands.registerCommand('editor.action.showHover', () => {\n for (const { context, controller } of live) {\n const selection = context.getSnapshot().selections[0]\n if (!selection) continue\n if (controller.showAtOffset(selection.headOffset, { focus: true })) return true\n }\n return false\n }),\n }),\n ]\n}\n"],"mappings":";;;;;;;;;AA0BA,SAAgB,kBAAkB,UAA8B,CAAC,GAAiB;CAChF,OAAO;EACL,MAAM,QAAQ,QAAQ;EACtB,WAAW,WAAW,cAAc,QAAQ,OAAO;CACrD;AACF;AAEA,SAAS,cAAc,QAA6B,SAA6B;CAE/E,MAAM,uBAAO,IAAI,IAAe;CAChC,OAAO,CACL,OAAO,yBAAyB,EAC9B,qBAAqB,YAAY;EAC/B,MAAM,aAAa,sBAAsB;GAAE;GAAS,GAAG;EAAQ,CAAC;EAChE,MAAM,QAAmB;GAAE;GAAS;EAAW;EAC/C,KAAK,IAAI,KAAK;EACd,mBAAmB,QAAQ,eAAe,UAAU;EACpD,OAAO;GACL,SAAS,UAAU,SAAS,WAAW,OAAO,UAAU,IAAI;GAC5D,eAAe;IACb,KAAK,OAAO,KAAK;IACjB,qBAAqB,QAAQ,eAAe,UAAU;IACtD,WAAW,QAAQ;GACrB;EACF;CACF,EACF,CAAC,GACD,OAAO,4BAA4B,EACjC,qBAAqB,aACnB,SAAS,gBAAgB,iCAAiC;EACxD,KAAK,MAAM,EAAE,SAAS,gBAAgB,MAAM;GAC1C,MAAM,YAAY,QAAQ,YAAY,CAAC,CAAC,WAAW;GACnD,IAAI,CAAC,WAAW;GAChB,IAAI,WAAW,aAAa,UAAU,YAAY,EAAE,OAAO,KAAK,CAAC,GAAG,OAAO;EAC7E;EACA,OAAO;CACT,CAAC,EACL,CAAC,CACH;AACF"}
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
import type { HoverController } from './hoverController';
|
|
2
|
+
/** Null before the hover has activated on that view. */
|
|
3
|
+
export declare function hoverControllerFor(scrollElement: HTMLElement): HoverController | null;
|
|
4
|
+
export declare function setHoverController(scrollElement: HTMLElement, controller: HoverController): void;
|
|
5
|
+
/** Ignores a controller that a later activation has already replaced. */
|
|
6
|
+
export declare function clearHoverController(scrollElement: HTMLElement, controller: HoverController): void;
|
|
7
|
+
//# sourceMappingURL=hoverRegistry.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"hoverRegistry.d.ts","sourceRoot":"","sources":["../src/hoverRegistry.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,mBAAmB,CAAA;AAUxD,wDAAwD;AACxD,wBAAgB,kBAAkB,CAAC,aAAa,EAAE,WAAW,GAAG,eAAe,GAAG,IAAI,CAErF;AAED,wBAAgB,kBAAkB,CAAC,aAAa,EAAE,WAAW,EAAE,UAAU,EAAE,eAAe,GAAG,IAAI,CAEhG;AAED,yEAAyE;AACzE,wBAAgB,oBAAoB,CAClC,aAAa,EAAE,WAAW,EAC1B,UAAU,EAAE,eAAe,GAC1B,IAAI,CAGN"}
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
//#region src/hoverRegistry.ts
|
|
2
|
+
/**
|
|
3
|
+
* The hover controller of each view, kept apart from the plugin that fills it. A plugin that has to
|
|
4
|
+
* hide the hover when its own surface opens reads this; importing the plugin instead would anchor
|
|
5
|
+
* the hover surface, its Markdown renderer and their dependencies into whoever asked, which is the
|
|
6
|
+
* load hoverToken.ts defers.
|
|
7
|
+
*/
|
|
8
|
+
var controllers = /* @__PURE__ */ new WeakMap();
|
|
9
|
+
/** Null before the hover has activated on that view. */
|
|
10
|
+
function hoverControllerFor(scrollElement) {
|
|
11
|
+
return controllers.get(scrollElement) ?? null;
|
|
12
|
+
}
|
|
13
|
+
function setHoverController(scrollElement, controller) {
|
|
14
|
+
controllers.set(scrollElement, controller);
|
|
15
|
+
}
|
|
16
|
+
/** Ignores a controller that a later activation has already replaced. */
|
|
17
|
+
function clearHoverController(scrollElement, controller) {
|
|
18
|
+
if (controllers.get(scrollElement) !== controller) return;
|
|
19
|
+
controllers.delete(scrollElement);
|
|
20
|
+
}
|
|
21
|
+
//#endregion
|
|
22
|
+
export { clearHoverController, hoverControllerFor, setHoverController };
|
|
23
|
+
|
|
24
|
+
//# sourceMappingURL=hoverRegistry.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"hoverRegistry.js","names":[],"sources":["../src/hoverRegistry.ts"],"sourcesContent":["import type { HoverController } from './hoverController'\n\n/**\n * The hover controller of each view, kept apart from the plugin that fills it. A plugin that has to\n * hide the hover when its own surface opens reads this; importing the plugin instead would anchor\n * the hover surface, its Markdown renderer and their dependencies into whoever asked, which is the\n * load hoverToken.ts defers.\n */\nconst controllers = new WeakMap<HTMLElement, HoverController>()\n\n/** Null before the hover has activated on that view. */\nexport function hoverControllerFor(scrollElement: HTMLElement): HoverController | null {\n return controllers.get(scrollElement) ?? null\n}\n\nexport function setHoverController(scrollElement: HTMLElement, controller: HoverController): void {\n controllers.set(scrollElement, controller)\n}\n\n/** Ignores a controller that a later activation has already replaced. */\nexport function clearHoverController(\n scrollElement: HTMLElement,\n controller: HoverController,\n): void {\n if (controllers.get(scrollElement) !== controller) return\n controllers.delete(scrollElement)\n}\n"],"mappings":";;;;;;;AAQA,IAAM,8BAAc,IAAI,QAAsC;;AAG9D,SAAgB,mBAAmB,eAAoD;CACrF,OAAO,YAAY,IAAI,aAAa,KAAK;AAC3C;AAEA,SAAgB,mBAAmB,eAA4B,YAAmC;CAChG,YAAY,IAAI,eAAe,UAAU;AAC3C;;AAGA,SAAgB,qBACd,eACA,YACM;CACN,IAAI,YAAY,IAAI,aAAa,MAAM,YAAY;CACnD,YAAY,OAAO,aAAa;AAClC"}
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
import type { EditorHoverParticipant } from './hoverParticipant';
|
|
2
|
+
/**
|
|
3
|
+
* Named rather than imported by everyone who answers it: a language server, a character warning or
|
|
4
|
+
* a diff pane has no reason to depend on each other, and restating the id is how they reach the
|
|
5
|
+
* same hover.
|
|
6
|
+
*/
|
|
7
|
+
export declare const EDITOR_HOVER_PARTICIPANT_ID = "editor.hoverParticipant";
|
|
8
|
+
export declare const EDITOR_HOVER_PARTICIPANT: import("@singapore-editor/core").EditorLanguageFeatureToken<EditorHoverParticipant>;
|
|
9
|
+
//# sourceMappingURL=hoverToken.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"hoverToken.d.ts","sourceRoot":"","sources":["../src/hoverToken.ts"],"names":[],"mappings":"AAKA,OAAO,KAAK,EAAE,sBAAsB,EAAE,MAAM,oBAAoB,CAAA;AAEhE;;;;GAIG;AACH,eAAO,MAAM,2BAA2B,4BAA4B,CAAA;AAEpE,eAAO,MAAM,wBAAwB,qFAEpC,CAAA"}
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
import { createEditorLanguageFeatureToken, registerAmbientEditorPlugin } from "@singapore-editor/core/extensions";
|
|
2
|
+
//#region src/hoverToken.ts
|
|
3
|
+
/**
|
|
4
|
+
* Named rather than imported by everyone who answers it: a language server, a character warning or
|
|
5
|
+
* a diff pane has no reason to depend on each other, and restating the id is how they reach the
|
|
6
|
+
* same hover.
|
|
7
|
+
*/
|
|
8
|
+
var EDITOR_HOVER_PARTICIPANT_ID = "editor.hoverParticipant";
|
|
9
|
+
var EDITOR_HOVER_PARTICIPANT = createEditorLanguageFeatureToken(EDITOR_HOVER_PARTICIPANT_ID);
|
|
10
|
+
registerAmbientEditorPlugin({
|
|
11
|
+
demand: EDITOR_HOVER_PARTICIPANT,
|
|
12
|
+
load: () => import("./hoverPlugin.js").then((module) => module.createHoverPlugin())
|
|
13
|
+
});
|
|
14
|
+
//#endregion
|
|
15
|
+
export { EDITOR_HOVER_PARTICIPANT, EDITOR_HOVER_PARTICIPANT_ID };
|
|
16
|
+
|
|
17
|
+
//# sourceMappingURL=hoverToken.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"hoverToken.js","names":[],"sources":["../src/hoverToken.ts"],"sourcesContent":["import {\n createEditorLanguageFeatureToken,\n registerAmbientEditorPlugin,\n} from '@singapore-editor/core/extensions'\n\nimport type { EditorHoverParticipant } from './hoverParticipant'\n\n/**\n * Named rather than imported by everyone who answers it: a language server, a character warning or\n * a diff pane has no reason to depend on each other, and restating the id is how they reach the\n * same hover.\n */\nexport const EDITOR_HOVER_PARTICIPANT_ID = 'editor.hoverParticipant'\n\nexport const EDITOR_HOVER_PARTICIPANT = createEditorLanguageFeatureToken<EditorHoverParticipant>(\n EDITOR_HOVER_PARTICIPANT_ID,\n)\n\n// Importing the token declares that hover content can exist. The hover itself — the surface, the\n// Markdown renderer and their dependencies — loads the first time a participant registers in some\n// editor, and that editor installs it for as long as it has one.\nregisterAmbientEditorPlugin({\n demand: EDITOR_HOVER_PARTICIPANT,\n load: () => import('./hoverPlugin').then((module) => module.createHoverPlugin()),\n})\n"],"mappings":";;;;;;;AAYA,IAAa,8BAA8B;AAE3C,IAAa,2BAA2B,iCACtC,2BACF;AAKA,4BAA4B;CAC1B,QAAQ;CACR,YAAY,OAAO,mBAAgB,CAAC,MAAM,WAAW,OAAO,kBAAkB,CAAC;AACjF,CAAC"}
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
export { anchoredSurfaceFollowsUpdate, createAnchoredSurface, isInsideEditorPopup, type AnchoredSurface, type AnchoredSurfaceAlignment, type AnchoredSurfaceOptions, type AnchoredSurfacePlacement, } from './anchoredSurface';
|
|
2
|
+
export { createTooltipCodeTokenizer, type TooltipCodeTokenizer } from './codeTokens';
|
|
3
|
+
export { normalizeTooltipMarkdown, renderTooltipMarkdown, type TooltipMarkdownRenderOptions, } from './markdownTooltip';
|
|
4
|
+
export { HOVER_COLORS } from './styles';
|
|
5
|
+
export { COPY_BUTTON_RESET_DELAY_MS, createTooltipController, HOVER_ASYNC_DISPATCH_DELAY_MS, HOVER_LOADING_DELAY_MS, HOVER_REQUEST_DEBOUNCE_MS, TOOLTIP_HIDE_DELAY_MS, type TooltipAction, type TooltipController, type TooltipNote, type TooltipNoteLink, type TooltipOptions, type TooltipPart, type TooltipShowOptions, } from './tooltip';
|
|
6
|
+
export { createHoverController, type HoverController, type HoverControllerOptions, } from './hoverController';
|
|
7
|
+
export { EDITOR_HOVER_PARTICIPANT, EDITOR_HOVER_PARTICIPANT_ID, type EditorHoverParticipant, type HoverAnchor, type HoverPart, type HoverRequest, } from './hoverParticipant';
|
|
8
|
+
export { createHoverPlugin, type HoverPluginOptions } from './hoverPlugin';
|
|
9
|
+
export { hoverControllerFor } from './hoverRegistry';
|
|
10
|
+
export { hoverTargetRange, hoverTargetRangeInSource, identifierRangeAtOffset, sameOffsetRange, unionOffsetRange, type OffsetRange, } from './offsetRange';
|
|
11
|
+
//# sourceMappingURL=index.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EACL,4BAA4B,EAC5B,qBAAqB,EACrB,mBAAmB,EACnB,KAAK,eAAe,EACpB,KAAK,wBAAwB,EAC7B,KAAK,sBAAsB,EAC3B,KAAK,wBAAwB,GAC9B,MAAM,mBAAmB,CAAA;AAC1B,OAAO,EAAE,0BAA0B,EAAE,KAAK,oBAAoB,EAAE,MAAM,cAAc,CAAA;AACpF,OAAO,EACL,wBAAwB,EACxB,qBAAqB,EACrB,KAAK,4BAA4B,GAClC,MAAM,mBAAmB,CAAA;AAC1B,OAAO,EAAE,YAAY,EAAE,MAAM,UAAU,CAAA;AACvC,OAAO,EACL,0BAA0B,EAC1B,uBAAuB,EACvB,6BAA6B,EAC7B,sBAAsB,EACtB,yBAAyB,EACzB,qBAAqB,EACrB,KAAK,aAAa,EAClB,KAAK,iBAAiB,EACtB,KAAK,WAAW,EAChB,KAAK,eAAe,EACpB,KAAK,cAAc,EACnB,KAAK,WAAW,EAChB,KAAK,kBAAkB,GACxB,MAAM,WAAW,CAAA;AAClB,OAAO,EACL,qBAAqB,EACrB,KAAK,eAAe,EACpB,KAAK,sBAAsB,GAC5B,MAAM,mBAAmB,CAAA;AAC1B,OAAO,EACL,wBAAwB,EACxB,2BAA2B,EAC3B,KAAK,sBAAsB,EAC3B,KAAK,WAAW,EAChB,KAAK,SAAS,EACd,KAAK,YAAY,GAClB,MAAM,oBAAoB,CAAA;AAC3B,OAAO,EAAE,iBAAiB,EAAE,KAAK,kBAAkB,EAAE,MAAM,eAAe,CAAA;AAC1E,OAAO,EAAE,kBAAkB,EAAE,MAAM,iBAAiB,CAAA;AACpD,OAAO,EACL,gBAAgB,EAChB,wBAAwB,EACxB,uBAAuB,EACvB,eAAe,EACf,gBAAgB,EAChB,KAAK,WAAW,GACjB,MAAM,eAAe,CAAA"}
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import { anchoredSurfaceFollowsUpdate, createAnchoredSurface, isInsideEditorPopup } from "./anchoredSurface.js";
|
|
2
|
+
import { createTooltipCodeTokenizer } from "./codeTokens.js";
|
|
3
|
+
import { normalizeTooltipMarkdown, renderTooltipMarkdown } from "./markdownTooltip.js";
|
|
4
|
+
import { HOVER_COLORS } from "./styles.js";
|
|
5
|
+
import { COPY_BUTTON_RESET_DELAY_MS, HOVER_ASYNC_DISPATCH_DELAY_MS, HOVER_LOADING_DELAY_MS, HOVER_REQUEST_DEBOUNCE_MS, TOOLTIP_HIDE_DELAY_MS, createTooltipController } from "./tooltip.js";
|
|
6
|
+
import { EDITOR_HOVER_PARTICIPANT, EDITOR_HOVER_PARTICIPANT_ID } from "./hoverToken.js";
|
|
7
|
+
import { hoverTargetRange, hoverTargetRangeInSource, identifierRangeAtOffset, sameOffsetRange, unionOffsetRange } from "./offsetRange.js";
|
|
8
|
+
import { createHoverController } from "./hoverController.js";
|
|
9
|
+
import { hoverControllerFor } from "./hoverRegistry.js";
|
|
10
|
+
import { createHoverPlugin } from "./hoverPlugin.js";
|
|
11
|
+
export { COPY_BUTTON_RESET_DELAY_MS, EDITOR_HOVER_PARTICIPANT, EDITOR_HOVER_PARTICIPANT_ID, HOVER_ASYNC_DISPATCH_DELAY_MS, HOVER_COLORS, HOVER_LOADING_DELAY_MS, HOVER_REQUEST_DEBOUNCE_MS, TOOLTIP_HIDE_DELAY_MS, anchoredSurfaceFollowsUpdate, createAnchoredSurface, createHoverController, createHoverPlugin, createTooltipCodeTokenizer, createTooltipController, hoverControllerFor, hoverTargetRange, hoverTargetRangeInSource, identifierRangeAtOffset, isInsideEditorPopup, normalizeTooltipMarkdown, renderTooltipMarkdown, sameOffsetRange, unionOffsetRange };
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import { type EditorTheme } from '@singapore-editor/core/rendering';
|
|
2
|
+
import { type TooltipCodeBlock, type TooltipCodeTokenizer } from './codeTokens';
|
|
3
|
+
export type TooltipMarkdownRenderOptions = {
|
|
4
|
+
readonly codeBackground?: boolean;
|
|
5
|
+
readonly classNamespace?: string;
|
|
6
|
+
readonly codeTokenizer?: TooltipCodeTokenizer | null;
|
|
7
|
+
};
|
|
8
|
+
export declare function normalizeTooltipMarkdown(markdown: string): string;
|
|
9
|
+
/** The fenced blocks a render of this Markdown would ask tokens for. */
|
|
10
|
+
export declare function tooltipCodeBlocks(markdown: string): readonly TooltipCodeBlock[];
|
|
11
|
+
export declare function renderTooltipMarkdown(document: Document, markdown: string, theme?: EditorTheme | null, options?: TooltipMarkdownRenderOptions): HTMLElement;
|
|
12
|
+
//# sourceMappingURL=markdownTooltip.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"markdownTooltip.d.ts","sourceRoot":"","sources":["../src/markdownTooltip.ts"],"names":[],"mappings":"AAAA,OAAO,EAAoB,KAAK,WAAW,EAAE,MAAM,kCAAkC,CAAA;AAMrF,OAAO,EAAmB,KAAK,gBAAgB,EAAE,KAAK,oBAAoB,EAAE,MAAM,cAAc,CAAA;AAchG,MAAM,MAAM,4BAA4B,GAAG;IACzC,QAAQ,CAAC,cAAc,CAAC,EAAE,OAAO,CAAA;IACjC,QAAQ,CAAC,cAAc,CAAC,EAAE,MAAM,CAAA;IAChC,QAAQ,CAAC,aAAa,CAAC,EAAE,oBAAoB,GAAG,IAAI,CAAA;CACrD,CAAA;AA4BD,wBAAgB,wBAAwB,CAAC,QAAQ,EAAE,MAAM,GAAG,MAAM,CAEjE;AAED,wEAAwE;AACxE,wBAAgB,iBAAiB,CAAC,QAAQ,EAAE,MAAM,GAAG,SAAS,gBAAgB,EAAE,CAI/E;AAYD,wBAAgB,qBAAqB,CACnC,QAAQ,EAAE,QAAQ,EAClB,QAAQ,EAAE,MAAM,EAChB,KAAK,CAAC,EAAE,WAAW,GAAG,IAAI,EAC1B,OAAO,GAAE,4BAAiC,GACzC,WAAW,CAab"}
|