@duet3d/monacotokens 3.6.0 → 3.6.2
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/expressions/expressions.json +277 -0
- package/dist/expressions/index.d.ts +24 -0
- package/dist/expressions/index.js +2 -0
- package/dist/gcodes/gcodes.json +5821 -0
- package/dist/gcodes/index.d.ts +60 -0
- package/dist/gcodes/index.js +17 -0
- package/dist/gcodes/local-variables.d.ts +26 -0
- package/dist/gcodes/local-variables.js +62 -0
- package/dist/gcodes/search.d.ts +17 -0
- package/dist/gcodes/search.js +589 -0
- package/dist/monaco-gcode.js +22 -9
- package/dist/monaco-menu.d.ts +3 -0
- package/dist/monaco-menu.js +32 -0
- package/dist/monaco-stm32.d.ts +3 -0
- package/dist/monaco-stm32.js +81 -0
- package/dist/objectmodel/deprecations.d.ts +12 -0
- package/dist/objectmodel/deprecations.js +42 -0
- package/dist/objectmodel/enums.d.ts +4 -0
- package/dist/objectmodel/enums.js +17 -0
- package/dist/objectmodel/machine-context.d.ts +33 -0
- package/dist/objectmodel/machine-context.js +30 -0
- package/dist/objectmodel/paths.d.ts +3 -0
- package/dist/objectmodel/paths.js +5 -0
- package/dist/providers.d.ts +55 -0
- package/dist/providers.js +1581 -0
- package/dist/register.d.ts +13 -0
- package/dist/register.js +73 -0
- package/package.json +1 -1
|
@@ -0,0 +1,589 @@
|
|
|
1
|
+
import { gcodeData } from ".";
|
|
2
|
+
import { getMachineContext } from "../objectmodel/machine-context";
|
|
3
|
+
import { getLocalVariables } from "./local-variables";
|
|
4
|
+
import { getPathDeprecation } from "../objectmodel/deprecations";
|
|
5
|
+
import { flattenObjectModel, isInsideExpression } from "../providers";
|
|
6
|
+
const widgetId = "duet.gcodeSearchWidget";
|
|
7
|
+
let activeWidget = null;
|
|
8
|
+
/**
|
|
9
|
+
* Open the gcode search overlay anchored to the current cursor position, styled like the F2 rename widget.
|
|
10
|
+
*/
|
|
11
|
+
export function showGcodeSearch(monacoInstance, editor) {
|
|
12
|
+
if (activeWidget) {
|
|
13
|
+
activeWidget.dispose();
|
|
14
|
+
}
|
|
15
|
+
const root = document.createElement("div");
|
|
16
|
+
root.style.cssText = [
|
|
17
|
+
"width: min(600px, 90vw)",
|
|
18
|
+
"background: var(--vscode-editorWidget-background, #252526)",
|
|
19
|
+
"color: var(--vscode-editorWidget-foreground, #cccccc)",
|
|
20
|
+
"border: 1px solid var(--vscode-editorWidget-border, #454545)",
|
|
21
|
+
"box-shadow: 0 2px 8px rgba(0,0,0,0.4)",
|
|
22
|
+
"font-family: var(--monaco-monospace-font)",
|
|
23
|
+
"font-size: 13px"
|
|
24
|
+
].join("; ");
|
|
25
|
+
const input = document.createElement("input");
|
|
26
|
+
input.type = "text";
|
|
27
|
+
input.placeholder = "Search G/M-code by description";
|
|
28
|
+
input.style.cssText = [
|
|
29
|
+
"display: block",
|
|
30
|
+
"width: 100%",
|
|
31
|
+
"box-sizing: border-box",
|
|
32
|
+
"padding: 4px 6px",
|
|
33
|
+
"background: var(--vscode-input-background, #3c3c3c)",
|
|
34
|
+
"color: var(--vscode-input-foreground, #cccccc)",
|
|
35
|
+
"border: none",
|
|
36
|
+
"border-bottom: 1px solid var(--vscode-editorWidget-border, #454545)",
|
|
37
|
+
"outline: none",
|
|
38
|
+
"font: inherit"
|
|
39
|
+
].join("; ");
|
|
40
|
+
const listAndDetails = document.createElement("div");
|
|
41
|
+
listAndDetails.style.cssText = "display: flex; max-height: 240px";
|
|
42
|
+
const list = document.createElement("div");
|
|
43
|
+
list.style.cssText = [
|
|
44
|
+
"flex: 1",
|
|
45
|
+
"overflow-y: auto",
|
|
46
|
+
"min-width: 0"
|
|
47
|
+
].join("; ");
|
|
48
|
+
const details = document.createElement("div");
|
|
49
|
+
details.style.cssText = [
|
|
50
|
+
"display: none",
|
|
51
|
+
"flex: 1",
|
|
52
|
+
"overflow-y: auto",
|
|
53
|
+
"padding: 6px 8px",
|
|
54
|
+
"border-left: 1px solid var(--vscode-editorWidget-border, #454545)",
|
|
55
|
+
"white-space: pre-wrap",
|
|
56
|
+
"line-height: 1.4"
|
|
57
|
+
].join("; ");
|
|
58
|
+
listAndDetails.appendChild(list);
|
|
59
|
+
listAndDetails.appendChild(details);
|
|
60
|
+
root.appendChild(input);
|
|
61
|
+
root.appendChild(listAndDetails);
|
|
62
|
+
// Detect the active Monaco theme and pick the matching keyword blue
|
|
63
|
+
const editorEl = editor.getDomNode();
|
|
64
|
+
const isDarkTheme = !!(editorEl && (editorEl.classList.contains("vs-dark") || editorEl.classList.contains("hc-black")));
|
|
65
|
+
const codeColor = isDarkTheme ? "#569cd6" : "#0000ff";
|
|
66
|
+
let entries = [];
|
|
67
|
+
let selectedIndex = 0;
|
|
68
|
+
let rowEls = [];
|
|
69
|
+
let detailsVisible = false;
|
|
70
|
+
function renderDetails() {
|
|
71
|
+
if (!detailsVisible) {
|
|
72
|
+
details.style.display = "none";
|
|
73
|
+
return;
|
|
74
|
+
}
|
|
75
|
+
details.style.display = "block";
|
|
76
|
+
details.innerHTML = "";
|
|
77
|
+
const info = entries[selectedIndex];
|
|
78
|
+
if (!info) {
|
|
79
|
+
details.textContent = "No entry selected.";
|
|
80
|
+
return;
|
|
81
|
+
}
|
|
82
|
+
const header = document.createElement("div");
|
|
83
|
+
header.style.cssText = `font-weight: bold; color: ${codeColor}; margin-bottom: 4px`;
|
|
84
|
+
header.textContent = `${info.code} - ${info.summary}`;
|
|
85
|
+
details.appendChild(header);
|
|
86
|
+
if (info.deprecated) {
|
|
87
|
+
const dep = document.createElement("div");
|
|
88
|
+
dep.style.cssText = "color: #cca700; margin-bottom: 4px";
|
|
89
|
+
dep.textContent = `⚠ Deprecated: ${info.deprecated}`;
|
|
90
|
+
details.appendChild(dep);
|
|
91
|
+
}
|
|
92
|
+
if (info.description) {
|
|
93
|
+
const desc = document.createElement("div");
|
|
94
|
+
desc.style.cssText = "margin-bottom: 4px; opacity: 0.9";
|
|
95
|
+
desc.textContent = info.description;
|
|
96
|
+
details.appendChild(desc);
|
|
97
|
+
}
|
|
98
|
+
if (info.parameters.length > 0) {
|
|
99
|
+
const p = document.createElement("div");
|
|
100
|
+
p.style.cssText = "margin-top: 4px";
|
|
101
|
+
const heading = document.createElement("div");
|
|
102
|
+
heading.style.fontWeight = "bold";
|
|
103
|
+
heading.textContent = "Parameters:";
|
|
104
|
+
p.appendChild(heading);
|
|
105
|
+
for (const param of info.parameters) {
|
|
106
|
+
const row = document.createElement("div");
|
|
107
|
+
row.style.cssText = "margin-left: 8px";
|
|
108
|
+
const letter = document.createElement("span");
|
|
109
|
+
letter.style.fontWeight = "bold";
|
|
110
|
+
letter.textContent = param.letter;
|
|
111
|
+
row.appendChild(letter);
|
|
112
|
+
row.appendChild(document.createTextNode(` - ${param.description}`));
|
|
113
|
+
if (param.deprecated) {
|
|
114
|
+
const tag = document.createElement("span");
|
|
115
|
+
tag.style.cssText = "color: #cca700; margin-left: 4px";
|
|
116
|
+
tag.textContent = "(deprecated)";
|
|
117
|
+
row.appendChild(tag);
|
|
118
|
+
}
|
|
119
|
+
p.appendChild(row);
|
|
120
|
+
}
|
|
121
|
+
details.appendChild(p);
|
|
122
|
+
}
|
|
123
|
+
}
|
|
124
|
+
function highlight() {
|
|
125
|
+
for (let i = 0; i < rowEls.length; i++) {
|
|
126
|
+
const active = i === selectedIndex;
|
|
127
|
+
rowEls[i].style.background = active
|
|
128
|
+
? "var(--vscode-list-activeSelectionBackground, #094771)"
|
|
129
|
+
: "transparent";
|
|
130
|
+
rowEls[i].style.color = active
|
|
131
|
+
? "var(--vscode-list-activeSelectionForeground, #ffffff)"
|
|
132
|
+
: "inherit";
|
|
133
|
+
}
|
|
134
|
+
if (rowEls[selectedIndex]) {
|
|
135
|
+
rowEls[selectedIndex].scrollIntoView({ block: "nearest" });
|
|
136
|
+
}
|
|
137
|
+
renderDetails();
|
|
138
|
+
}
|
|
139
|
+
function render(query) {
|
|
140
|
+
const q = query.trim().toLowerCase();
|
|
141
|
+
entries = q.length === 0
|
|
142
|
+
? gcodeData.slice()
|
|
143
|
+
: gcodeData.filter(g => g.code.toLowerCase().includes(q) || g.summary.toLowerCase().includes(q));
|
|
144
|
+
selectedIndex = 0;
|
|
145
|
+
list.innerHTML = "";
|
|
146
|
+
rowEls = [];
|
|
147
|
+
for (const info of entries) {
|
|
148
|
+
const row = document.createElement("div");
|
|
149
|
+
row.style.cssText = "padding: 3px 8px; cursor: pointer; display: flex; gap: 8px; white-space: nowrap";
|
|
150
|
+
const code = document.createElement("span");
|
|
151
|
+
code.textContent = info.code;
|
|
152
|
+
code.style.cssText = `min-width: 48px; font-weight: bold; color: ${codeColor}`;
|
|
153
|
+
const desc = document.createElement("span");
|
|
154
|
+
desc.textContent = info.summary;
|
|
155
|
+
desc.style.cssText = "flex: 1; opacity: 0.85; overflow: hidden; text-overflow: ellipsis";
|
|
156
|
+
row.appendChild(code);
|
|
157
|
+
row.appendChild(desc);
|
|
158
|
+
row.addEventListener("mouseenter", () => {
|
|
159
|
+
selectedIndex = rowEls.indexOf(row);
|
|
160
|
+
highlight();
|
|
161
|
+
});
|
|
162
|
+
row.addEventListener("mousedown", (e) => {
|
|
163
|
+
// mousedown so the input doesn't lose focus before we can read selection
|
|
164
|
+
e.preventDefault();
|
|
165
|
+
accept();
|
|
166
|
+
});
|
|
167
|
+
list.appendChild(row);
|
|
168
|
+
rowEls.push(row);
|
|
169
|
+
}
|
|
170
|
+
highlight();
|
|
171
|
+
}
|
|
172
|
+
function accept() {
|
|
173
|
+
const choice = entries[selectedIndex];
|
|
174
|
+
if (choice) {
|
|
175
|
+
const selection = editor.getSelection();
|
|
176
|
+
if (selection) {
|
|
177
|
+
editor.executeEdits("duet-gcode-search", [{
|
|
178
|
+
range: selection,
|
|
179
|
+
text: choice.code,
|
|
180
|
+
forceMoveMarkers: true
|
|
181
|
+
}]);
|
|
182
|
+
editor.pushUndoStop();
|
|
183
|
+
}
|
|
184
|
+
}
|
|
185
|
+
close();
|
|
186
|
+
}
|
|
187
|
+
function close() {
|
|
188
|
+
if (activeWidget && activeWidget.editor === editor) {
|
|
189
|
+
activeWidget.dispose();
|
|
190
|
+
}
|
|
191
|
+
editor.focus();
|
|
192
|
+
}
|
|
193
|
+
input.addEventListener("input", (e) => {
|
|
194
|
+
e.stopPropagation();
|
|
195
|
+
render(input.value);
|
|
196
|
+
});
|
|
197
|
+
// Stop key events from bubbling to Monaco so it doesn't run its own auto-complete / typing handlers
|
|
198
|
+
const stopKey = (e) => e.stopPropagation();
|
|
199
|
+
input.addEventListener("keypress", stopKey);
|
|
200
|
+
input.addEventListener("keyup", stopKey);
|
|
201
|
+
const pageStep = 8;
|
|
202
|
+
input.addEventListener("keydown", (e) => {
|
|
203
|
+
e.stopPropagation();
|
|
204
|
+
if (e.key === "ArrowDown") {
|
|
205
|
+
e.preventDefault();
|
|
206
|
+
selectedIndex = Math.min(selectedIndex + 1, rowEls.length - 1);
|
|
207
|
+
highlight();
|
|
208
|
+
}
|
|
209
|
+
else if (e.key === "ArrowUp") {
|
|
210
|
+
e.preventDefault();
|
|
211
|
+
selectedIndex = Math.max(selectedIndex - 1, 0);
|
|
212
|
+
highlight();
|
|
213
|
+
}
|
|
214
|
+
else if (e.key === "PageDown") {
|
|
215
|
+
e.preventDefault();
|
|
216
|
+
selectedIndex = Math.min(selectedIndex + pageStep, rowEls.length - 1);
|
|
217
|
+
highlight();
|
|
218
|
+
}
|
|
219
|
+
else if (e.key === "PageUp") {
|
|
220
|
+
e.preventDefault();
|
|
221
|
+
selectedIndex = Math.max(selectedIndex - pageStep, 0);
|
|
222
|
+
highlight();
|
|
223
|
+
}
|
|
224
|
+
else if (e.key === "Home") {
|
|
225
|
+
e.preventDefault();
|
|
226
|
+
selectedIndex = 0;
|
|
227
|
+
highlight();
|
|
228
|
+
}
|
|
229
|
+
else if (e.key === "End") {
|
|
230
|
+
e.preventDefault();
|
|
231
|
+
selectedIndex = Math.max(0, rowEls.length - 1);
|
|
232
|
+
highlight();
|
|
233
|
+
}
|
|
234
|
+
else if (e.key === "Enter") {
|
|
235
|
+
e.preventDefault();
|
|
236
|
+
accept();
|
|
237
|
+
}
|
|
238
|
+
else if (e.key === "Escape") {
|
|
239
|
+
e.preventDefault();
|
|
240
|
+
close();
|
|
241
|
+
}
|
|
242
|
+
else if ((e.ctrlKey || e.metaKey) && (e.key === " " || e.code === "Space")) {
|
|
243
|
+
// Toggle the details panel, matching Monaco's suggest-widget behaviour
|
|
244
|
+
e.preventDefault();
|
|
245
|
+
detailsVisible = !detailsVisible;
|
|
246
|
+
renderDetails();
|
|
247
|
+
}
|
|
248
|
+
});
|
|
249
|
+
// Use an overlay widget (not a content widget) so the editor's scroll/layout aren't touched at all
|
|
250
|
+
// We position it manually near the cursor by absolute-positioning `root` inside the editor overlay container
|
|
251
|
+
root.style.position = "absolute";
|
|
252
|
+
function positionNearCursor() {
|
|
253
|
+
const cursorPos = editor.getPosition();
|
|
254
|
+
if (!cursorPos) {
|
|
255
|
+
return;
|
|
256
|
+
}
|
|
257
|
+
const coord = editor.getScrolledVisiblePosition(cursorPos);
|
|
258
|
+
const layout = editor.getLayoutInfo();
|
|
259
|
+
if (!coord) {
|
|
260
|
+
return;
|
|
261
|
+
}
|
|
262
|
+
const widgetWidth = root.offsetWidth || 600;
|
|
263
|
+
const widgetHeight = root.offsetHeight || 280;
|
|
264
|
+
const lineHeight = editor.getOption(monacoInstance.editor.EditorOption.lineHeight);
|
|
265
|
+
// Prefer below the cursor, flip above if it would overflow the editor viewport
|
|
266
|
+
let top = coord.top + lineHeight;
|
|
267
|
+
if (top + widgetHeight > layout.height) {
|
|
268
|
+
top = Math.max(0, coord.top - widgetHeight);
|
|
269
|
+
}
|
|
270
|
+
let left = coord.left;
|
|
271
|
+
if (left + widgetWidth > layout.width) {
|
|
272
|
+
left = Math.max(0, layout.width - widgetWidth - 8);
|
|
273
|
+
}
|
|
274
|
+
root.style.top = `${top}px`;
|
|
275
|
+
root.style.left = `${left}px`;
|
|
276
|
+
}
|
|
277
|
+
const widget = {
|
|
278
|
+
getId: () => widgetId,
|
|
279
|
+
getDomNode: () => root,
|
|
280
|
+
getPosition: () => null
|
|
281
|
+
};
|
|
282
|
+
editor.addOverlayWidget(widget);
|
|
283
|
+
positionNearCursor();
|
|
284
|
+
// Re-position once the DOM has actually measured the widget (offsetWidth/Height are 0 at first paint)
|
|
285
|
+
requestAnimationFrame(positionNearCursor);
|
|
286
|
+
// Close on Esc anywhere in the document (even when focus has drifted to the editor)
|
|
287
|
+
const onDocKeyDown = (e) => {
|
|
288
|
+
if (e.key === "Escape") {
|
|
289
|
+
e.preventDefault();
|
|
290
|
+
e.stopPropagation();
|
|
291
|
+
close();
|
|
292
|
+
}
|
|
293
|
+
};
|
|
294
|
+
// Close on mousedown outside the widget
|
|
295
|
+
const onDocMouseDown = (e) => {
|
|
296
|
+
if (!root.contains(e.target)) {
|
|
297
|
+
close();
|
|
298
|
+
}
|
|
299
|
+
};
|
|
300
|
+
document.addEventListener("keydown", onDocKeyDown, true);
|
|
301
|
+
document.addEventListener("mousedown", onDocMouseDown, true);
|
|
302
|
+
render("");
|
|
303
|
+
// preventScroll: stops the browser auto-scrolling the page when the cursor is low in the editor
|
|
304
|
+
setTimeout(() => input.focus({ preventScroll: true }), 0);
|
|
305
|
+
activeWidget = {
|
|
306
|
+
editor,
|
|
307
|
+
dispose: () => {
|
|
308
|
+
document.removeEventListener("keydown", onDocKeyDown, true);
|
|
309
|
+
document.removeEventListener("mousedown", onDocMouseDown, true);
|
|
310
|
+
editor.removeOverlayWidget(widget);
|
|
311
|
+
activeWidget = null;
|
|
312
|
+
}
|
|
313
|
+
};
|
|
314
|
+
}
|
|
315
|
+
/**
|
|
316
|
+
* Open a variant of the search overlay that lists object-model paths instead of G/M-codes. The model is
|
|
317
|
+
* flattened once on open (up to depth 3) and cached in the widget for the duration of the session; arrays
|
|
318
|
+
* are represented by their first element with an `[0]` placeholder. Local `var` / `global` declarations
|
|
319
|
+
* scanned from the current editor model are folded in as `var.<name>` / `global.<name>` entries.
|
|
320
|
+
*/
|
|
321
|
+
export function showObjectModelSearch(monacoInstance, editor) {
|
|
322
|
+
if (activeWidget) {
|
|
323
|
+
activeWidget.dispose();
|
|
324
|
+
}
|
|
325
|
+
const root = document.createElement("div");
|
|
326
|
+
root.style.cssText = [
|
|
327
|
+
"width: min(600px, 90vw)",
|
|
328
|
+
"background: var(--vscode-editorWidget-background, #252526)",
|
|
329
|
+
"color: var(--vscode-editorWidget-foreground, #cccccc)",
|
|
330
|
+
"border: 1px solid var(--vscode-editorWidget-border, #454545)",
|
|
331
|
+
"box-shadow: 0 2px 8px rgba(0,0,0,0.4)",
|
|
332
|
+
"font-family: var(--monaco-monospace-font)",
|
|
333
|
+
"font-size: 13px"
|
|
334
|
+
].join("; ");
|
|
335
|
+
const input = document.createElement("input");
|
|
336
|
+
input.type = "text";
|
|
337
|
+
input.placeholder = "Search object-model path...";
|
|
338
|
+
input.style.cssText = [
|
|
339
|
+
"display: block",
|
|
340
|
+
"width: 100%",
|
|
341
|
+
"box-sizing: border-box",
|
|
342
|
+
"padding: 4px 6px",
|
|
343
|
+
"background: var(--vscode-input-background, #3c3c3c)",
|
|
344
|
+
"color: var(--vscode-input-foreground, #cccccc)",
|
|
345
|
+
"border: none",
|
|
346
|
+
"border-bottom: 1px solid var(--vscode-editorWidget-border, #454545)",
|
|
347
|
+
"outline: none",
|
|
348
|
+
"font: inherit"
|
|
349
|
+
].join("; ");
|
|
350
|
+
const list = document.createElement("div");
|
|
351
|
+
list.style.cssText = "display: block; max-height: 240px; overflow-y: auto";
|
|
352
|
+
root.appendChild(input);
|
|
353
|
+
root.appendChild(list);
|
|
354
|
+
const editorEl = editor.getDomNode();
|
|
355
|
+
const isDarkTheme = !!(editorEl && (editorEl.classList.contains("vs-dark") || editorEl.classList.contains("hc-black")));
|
|
356
|
+
const pathColor = isDarkTheme ? "#9CDCFE" : "#001080";
|
|
357
|
+
// Collect all paths once - model-derived paths plus the local scanner's var/global declarations
|
|
358
|
+
const allPaths = new Set();
|
|
359
|
+
const ctx = getMachineContext();
|
|
360
|
+
if (ctx?.model) {
|
|
361
|
+
for (const p of flattenObjectModel(ctx.model)) {
|
|
362
|
+
allPaths.add(p);
|
|
363
|
+
}
|
|
364
|
+
}
|
|
365
|
+
const model = editor.getModel();
|
|
366
|
+
if (model) {
|
|
367
|
+
const locals = getLocalVariables(model);
|
|
368
|
+
for (const n of locals.vars) {
|
|
369
|
+
allPaths.add(`var.${n}`);
|
|
370
|
+
}
|
|
371
|
+
for (const n of locals.globals) {
|
|
372
|
+
allPaths.add(`global.${n}`);
|
|
373
|
+
}
|
|
374
|
+
}
|
|
375
|
+
const flatPaths = Array.from(allPaths).sort();
|
|
376
|
+
let entries = [];
|
|
377
|
+
let selectedIndex = 0;
|
|
378
|
+
let rowEls = [];
|
|
379
|
+
function highlight() {
|
|
380
|
+
for (let i = 0; i < rowEls.length; i++) {
|
|
381
|
+
const active = i === selectedIndex;
|
|
382
|
+
rowEls[i].style.background = active
|
|
383
|
+
? "var(--vscode-list-activeSelectionBackground, #094771)"
|
|
384
|
+
: "transparent";
|
|
385
|
+
rowEls[i].style.color = active
|
|
386
|
+
? "var(--vscode-list-activeSelectionForeground, #ffffff)"
|
|
387
|
+
: "inherit";
|
|
388
|
+
}
|
|
389
|
+
if (rowEls[selectedIndex]) {
|
|
390
|
+
rowEls[selectedIndex].scrollIntoView({ block: "nearest" });
|
|
391
|
+
}
|
|
392
|
+
}
|
|
393
|
+
function render(query) {
|
|
394
|
+
const q = query.trim().toLowerCase();
|
|
395
|
+
entries = q.length === 0 ? flatPaths.slice() : flatPaths.filter(p => p.toLowerCase().includes(q));
|
|
396
|
+
selectedIndex = 0;
|
|
397
|
+
list.innerHTML = "";
|
|
398
|
+
rowEls = [];
|
|
399
|
+
const ctxModel = ctx?.model ?? null;
|
|
400
|
+
for (const path of entries) {
|
|
401
|
+
const row = document.createElement("div");
|
|
402
|
+
row.style.cssText = "padding: 3px 8px; cursor: pointer; white-space: nowrap; overflow: hidden; display: flex; gap: 8px; align-items: baseline";
|
|
403
|
+
const pathSpan = document.createElement("span");
|
|
404
|
+
pathSpan.textContent = path;
|
|
405
|
+
pathSpan.style.cssText = `color: ${pathColor}; flex: 1; overflow: hidden; text-overflow: ellipsis`;
|
|
406
|
+
const deprecation = ctxModel ? getPathDeprecation(path) : null;
|
|
407
|
+
if (deprecation !== null) {
|
|
408
|
+
pathSpan.style.textDecoration = "line-through";
|
|
409
|
+
pathSpan.style.opacity = "0.7";
|
|
410
|
+
const tag = document.createElement("span");
|
|
411
|
+
tag.textContent = deprecation ? `deprecated - ${deprecation}` : "deprecated";
|
|
412
|
+
tag.style.cssText = "color: #cca700; font-style: italic; flex-shrink: 1; overflow: hidden; text-overflow: ellipsis";
|
|
413
|
+
row.title = `Deprecated${deprecation ? ": " + deprecation : ""}`;
|
|
414
|
+
row.appendChild(pathSpan);
|
|
415
|
+
row.appendChild(tag);
|
|
416
|
+
}
|
|
417
|
+
else {
|
|
418
|
+
row.appendChild(pathSpan);
|
|
419
|
+
}
|
|
420
|
+
row.addEventListener("mouseenter", () => {
|
|
421
|
+
selectedIndex = rowEls.indexOf(row);
|
|
422
|
+
highlight();
|
|
423
|
+
});
|
|
424
|
+
row.addEventListener("mousedown", (e) => {
|
|
425
|
+
e.preventDefault();
|
|
426
|
+
accept();
|
|
427
|
+
});
|
|
428
|
+
list.appendChild(row);
|
|
429
|
+
rowEls.push(row);
|
|
430
|
+
}
|
|
431
|
+
highlight();
|
|
432
|
+
}
|
|
433
|
+
function accept() {
|
|
434
|
+
const choice = entries[selectedIndex];
|
|
435
|
+
if (choice) {
|
|
436
|
+
const selection = editor.getSelection();
|
|
437
|
+
if (selection) {
|
|
438
|
+
editor.executeEdits("duet-om-search", [{
|
|
439
|
+
range: selection,
|
|
440
|
+
text: choice,
|
|
441
|
+
forceMoveMarkers: true
|
|
442
|
+
}]);
|
|
443
|
+
editor.pushUndoStop();
|
|
444
|
+
}
|
|
445
|
+
}
|
|
446
|
+
close();
|
|
447
|
+
}
|
|
448
|
+
function close() {
|
|
449
|
+
if (activeWidget && activeWidget.editor === editor) {
|
|
450
|
+
activeWidget.dispose();
|
|
451
|
+
}
|
|
452
|
+
editor.focus();
|
|
453
|
+
}
|
|
454
|
+
input.addEventListener("input", (e) => {
|
|
455
|
+
e.stopPropagation();
|
|
456
|
+
render(input.value);
|
|
457
|
+
});
|
|
458
|
+
const stopKey = (e) => e.stopPropagation();
|
|
459
|
+
input.addEventListener("keypress", stopKey);
|
|
460
|
+
input.addEventListener("keyup", stopKey);
|
|
461
|
+
const pageStep = 8;
|
|
462
|
+
input.addEventListener("keydown", (e) => {
|
|
463
|
+
e.stopPropagation();
|
|
464
|
+
if (e.key === "ArrowDown") {
|
|
465
|
+
e.preventDefault();
|
|
466
|
+
selectedIndex = Math.min(selectedIndex + 1, rowEls.length - 1);
|
|
467
|
+
highlight();
|
|
468
|
+
}
|
|
469
|
+
else if (e.key === "ArrowUp") {
|
|
470
|
+
e.preventDefault();
|
|
471
|
+
selectedIndex = Math.max(selectedIndex - 1, 0);
|
|
472
|
+
highlight();
|
|
473
|
+
}
|
|
474
|
+
else if (e.key === "PageDown") {
|
|
475
|
+
e.preventDefault();
|
|
476
|
+
selectedIndex = Math.min(selectedIndex + pageStep, rowEls.length - 1);
|
|
477
|
+
highlight();
|
|
478
|
+
}
|
|
479
|
+
else if (e.key === "PageUp") {
|
|
480
|
+
e.preventDefault();
|
|
481
|
+
selectedIndex = Math.max(selectedIndex - pageStep, 0);
|
|
482
|
+
highlight();
|
|
483
|
+
}
|
|
484
|
+
else if (e.key === "Home") {
|
|
485
|
+
e.preventDefault();
|
|
486
|
+
selectedIndex = 0;
|
|
487
|
+
highlight();
|
|
488
|
+
}
|
|
489
|
+
else if (e.key === "End") {
|
|
490
|
+
e.preventDefault();
|
|
491
|
+
selectedIndex = Math.max(0, rowEls.length - 1);
|
|
492
|
+
highlight();
|
|
493
|
+
}
|
|
494
|
+
else if (e.key === "Enter") {
|
|
495
|
+
e.preventDefault();
|
|
496
|
+
accept();
|
|
497
|
+
}
|
|
498
|
+
else if (e.key === "Escape") {
|
|
499
|
+
e.preventDefault();
|
|
500
|
+
close();
|
|
501
|
+
}
|
|
502
|
+
});
|
|
503
|
+
root.style.position = "absolute";
|
|
504
|
+
function positionNearCursor() {
|
|
505
|
+
const cursorPos = editor.getPosition();
|
|
506
|
+
if (!cursorPos) {
|
|
507
|
+
return;
|
|
508
|
+
}
|
|
509
|
+
const coord = editor.getScrolledVisiblePosition(cursorPos);
|
|
510
|
+
const layout = editor.getLayoutInfo();
|
|
511
|
+
if (!coord) {
|
|
512
|
+
return;
|
|
513
|
+
}
|
|
514
|
+
const widgetWidth = root.offsetWidth || 600;
|
|
515
|
+
const widgetHeight = root.offsetHeight || 280;
|
|
516
|
+
const lineHeight = editor.getOption(monacoInstance.editor.EditorOption.lineHeight);
|
|
517
|
+
let top = coord.top + lineHeight;
|
|
518
|
+
if (top + widgetHeight > layout.height) {
|
|
519
|
+
top = Math.max(0, coord.top - widgetHeight);
|
|
520
|
+
}
|
|
521
|
+
let left = coord.left;
|
|
522
|
+
if (left + widgetWidth > layout.width) {
|
|
523
|
+
left = Math.max(0, layout.width - widgetWidth - 8);
|
|
524
|
+
}
|
|
525
|
+
root.style.top = `${top}px`;
|
|
526
|
+
root.style.left = `${left}px`;
|
|
527
|
+
}
|
|
528
|
+
const widget = {
|
|
529
|
+
getId: () => widgetId,
|
|
530
|
+
getDomNode: () => root,
|
|
531
|
+
getPosition: () => null
|
|
532
|
+
};
|
|
533
|
+
editor.addOverlayWidget(widget);
|
|
534
|
+
positionNearCursor();
|
|
535
|
+
requestAnimationFrame(positionNearCursor);
|
|
536
|
+
const onDocKeyDown = (e) => {
|
|
537
|
+
if (e.key === "Escape") {
|
|
538
|
+
e.preventDefault();
|
|
539
|
+
e.stopPropagation();
|
|
540
|
+
close();
|
|
541
|
+
}
|
|
542
|
+
};
|
|
543
|
+
const onDocMouseDown = (e) => {
|
|
544
|
+
if (!root.contains(e.target)) {
|
|
545
|
+
close();
|
|
546
|
+
}
|
|
547
|
+
};
|
|
548
|
+
document.addEventListener("keydown", onDocKeyDown, true);
|
|
549
|
+
document.addEventListener("mousedown", onDocMouseDown, true);
|
|
550
|
+
render("");
|
|
551
|
+
setTimeout(() => input.focus({ preventScroll: true }), 0);
|
|
552
|
+
activeWidget = {
|
|
553
|
+
editor,
|
|
554
|
+
dispose: () => {
|
|
555
|
+
document.removeEventListener("keydown", onDocKeyDown, true);
|
|
556
|
+
document.removeEventListener("mousedown", onDocMouseDown, true);
|
|
557
|
+
editor.removeOverlayWidget(widget);
|
|
558
|
+
activeWidget = null;
|
|
559
|
+
}
|
|
560
|
+
};
|
|
561
|
+
}
|
|
562
|
+
/**
|
|
563
|
+
* Register the F4 search action on a freshly created editor instance.
|
|
564
|
+
* Call this once per editor right after `monaco.editor.create(...)`.
|
|
565
|
+
*/
|
|
566
|
+
export function addGcodeSearchAction(monacoInstance, editor) {
|
|
567
|
+
return editor.addAction({
|
|
568
|
+
id: "duet.searchGcode",
|
|
569
|
+
label: "Search G/M-code or object-model path",
|
|
570
|
+
keybindings: [monacoInstance.KeyCode.F4],
|
|
571
|
+
run: () => {
|
|
572
|
+
// Hide the suggest widget and parameter-hints tooltip so our overlay doesn't visually compete with them
|
|
573
|
+
editor.trigger("gcode-search", "hideSuggestWidget", null);
|
|
574
|
+
editor.trigger("gcode-search", "closeParameterHints", null);
|
|
575
|
+
// Switch to object-model search when the cursor is inside an expression context
|
|
576
|
+
const model = editor.getModel();
|
|
577
|
+
const position = editor.getPosition();
|
|
578
|
+
if (model && position) {
|
|
579
|
+
const lineContent = model.getLineContent(position.lineNumber);
|
|
580
|
+
const beforeCursor = lineContent.substring(0, position.column - 1);
|
|
581
|
+
if (isInsideExpression(beforeCursor)) {
|
|
582
|
+
showObjectModelSearch(monacoInstance, editor);
|
|
583
|
+
return;
|
|
584
|
+
}
|
|
585
|
+
}
|
|
586
|
+
showGcodeSearch(monacoInstance, editor);
|
|
587
|
+
}
|
|
588
|
+
});
|
|
589
|
+
}
|
package/dist/monaco-gcode.js
CHANGED
|
@@ -18,9 +18,12 @@ function generateMonarchLanguage(fdmMode) {
|
|
|
18
18
|
includeLF: true,
|
|
19
19
|
tokenizer: {
|
|
20
20
|
root: [
|
|
21
|
-
// G/M/T-codes
|
|
22
|
-
|
|
23
|
-
[/[
|
|
21
|
+
// G/M/T-codes - M-codes use their own state because T is a valid M-code parameter letter,
|
|
22
|
+
// whereas inside a G-code's parameter list T starts a new T-code on the same line
|
|
23
|
+
[/[gG][0123](?=\D)/, "keyword", fdmMode ? "normalGcode" : "moveGcode"],
|
|
24
|
+
[/[gG]\d+(\.\d+)?/, "keyword", "normalGcode"],
|
|
25
|
+
[/[mM]\d+(\.\d+)?/, "keyword", "normalMcode"],
|
|
26
|
+
[/[tT](?=\{)/, "keyword", "normalGcodeWithT"],
|
|
24
27
|
[/[tT]-?\d+/, "keyword", "normalGcodeWithT"],
|
|
25
28
|
// meta keywords
|
|
26
29
|
[/[a-z_$][\w$]*/, {
|
|
@@ -41,10 +44,10 @@ function generateMonarchLanguage(fdmMode) {
|
|
|
41
44
|
[/\(.*\)/, fdmMode ? "invalid" : "comment"]
|
|
42
45
|
],
|
|
43
46
|
gcode: [
|
|
44
|
-
// next G/M
|
|
47
|
+
// next G/M-code on the same line (T re-entry is handled per-state - see normalGcode)
|
|
45
48
|
[/[gG][0123](?=\D)/, "keyword", "moveGcode"],
|
|
46
|
-
[/[
|
|
47
|
-
[/[
|
|
49
|
+
[/[gG]\d+(\.\d*)?/, "keyword", "normalGcode"],
|
|
50
|
+
[/[mM]\d+(\.\d*)?/, "keyword", "normalMcode"],
|
|
48
51
|
// parameter letters
|
|
49
52
|
[/'?[a-zA-Z]/, "keyword"],
|
|
50
53
|
// expressions
|
|
@@ -52,7 +55,7 @@ function generateMonarchLanguage(fdmMode) {
|
|
|
52
55
|
// enclosed comments
|
|
53
56
|
[/\(.*\)/, "comment"],
|
|
54
57
|
// parameter expressions
|
|
55
|
-
[/{/, "expression", "@expression"],
|
|
58
|
+
[/T?{/, "expression", "@expression"],
|
|
56
59
|
// include defaults
|
|
57
60
|
{ include: "root" }
|
|
58
61
|
],
|
|
@@ -63,11 +66,21 @@ function generateMonarchLanguage(fdmMode) {
|
|
|
63
66
|
{ include: "gcode" }
|
|
64
67
|
],
|
|
65
68
|
normalGcode: [
|
|
69
|
+
// T inside a G-code parameter list starts a new T-code (T is not a valid G-code parameter)
|
|
70
|
+
[/[tT](?=\{)/, "keyword", "normalGcodeWithT"],
|
|
71
|
+
[/[tT]-?\d+/, "keyword", "normalGcodeWithT"],
|
|
66
72
|
// include normal gcode
|
|
67
73
|
{ include: "gcode" },
|
|
68
74
|
// EOL
|
|
69
75
|
[/\n/, "", "@popall"]
|
|
70
76
|
],
|
|
77
|
+
normalMcode: [
|
|
78
|
+
// T is a parameter letter inside an M-code, not a new T-code - so this state intentionally
|
|
79
|
+
// omits the T-code re-entry rules that normalGcode has
|
|
80
|
+
{ include: "gcode" },
|
|
81
|
+
// EOL
|
|
82
|
+
[/\n/, "", "@popall"]
|
|
83
|
+
],
|
|
71
84
|
normalGcodeWithT: [
|
|
72
85
|
// already had a T parameter, starting a new T-code
|
|
73
86
|
[/(?=T)/, "keyword", "@popall"],
|
|
@@ -76,9 +89,9 @@ function generateMonarchLanguage(fdmMode) {
|
|
|
76
89
|
],
|
|
77
90
|
expression: [
|
|
78
91
|
// variables
|
|
79
|
-
[/(global|param|var)\.[a-zA-
|
|
92
|
+
[/(global|param|var)\.[a-zA-Z_$][\w$]*/, "variable.name"],
|
|
80
93
|
// object model properties
|
|
81
|
-
[
|
|
94
|
+
[/\w+(\.\w+)*/, "variable"],
|
|
82
95
|
// consts and functions
|
|
83
96
|
[/[a-z]\w*/, {
|
|
84
97
|
cases: {
|