@duet3d/monacotokens 3.6.1 → 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.
@@ -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
+ }
@@ -18,9 +18,11 @@ function generateMonarchLanguage(fdmMode) {
18
18
  includeLF: true,
19
19
  tokenizer: {
20
20
  root: [
21
- // G/M/T-codes
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
22
23
  [/[gG][0123](?=\D)/, "keyword", fdmMode ? "normalGcode" : "moveGcode"],
23
- [/[gGmM]\d+(\.\d+)?/, "keyword", "normalGcode"],
24
+ [/[gG]\d+(\.\d+)?/, "keyword", "normalGcode"],
25
+ [/[mM]\d+(\.\d+)?/, "keyword", "normalMcode"],
24
26
  [/[tT](?=\{)/, "keyword", "normalGcodeWithT"],
25
27
  [/[tT]-?\d+/, "keyword", "normalGcodeWithT"],
26
28
  // meta keywords
@@ -42,11 +44,10 @@ function generateMonarchLanguage(fdmMode) {
42
44
  [/\(.*\)/, fdmMode ? "invalid" : "comment"]
43
45
  ],
44
46
  gcode: [
45
- // next G/M/T-code
47
+ // next G/M-code on the same line (T re-entry is handled per-state - see normalGcode)
46
48
  [/[gG][0123](?=\D)/, "keyword", "moveGcode"],
47
- [/[gGmM]\d+(\.\d*)?/, "keyword", "normalGcode"],
48
- [/[tT](?=\{)/, "keyword", "normalGcodeWithT"],
49
- [/[tT]-?\d+/, "keyword", "normalGcodeWithT"],
49
+ [/[gG]\d+(\.\d*)?/, "keyword", "normalGcode"],
50
+ [/[mM]\d+(\.\d*)?/, "keyword", "normalMcode"],
50
51
  // parameter letters
51
52
  [/'?[a-zA-Z]/, "keyword"],
52
53
  // expressions
@@ -65,11 +66,21 @@ function generateMonarchLanguage(fdmMode) {
65
66
  { include: "gcode" }
66
67
  ],
67
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"],
68
72
  // include normal gcode
69
73
  { include: "gcode" },
70
74
  // EOL
71
75
  [/\n/, "", "@popall"]
72
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
+ ],
73
84
  normalGcodeWithT: [
74
85
  // already had a T parameter, starting a new T-code
75
86
  [/(?=T)/, "keyword", "@popall"],
@@ -0,0 +1,3 @@
1
+ import type * as monaco from "monaco-editor-core/esm/vs/editor/editor.api.js";
2
+ export declare const menuLanguage: monaco.languages.IMonarchLanguage;
3
+ export declare const menuLanguageConfiguration: monaco.languages.LanguageConfiguration;
@@ -0,0 +1,32 @@
1
+ export const menuLanguage = {
2
+ keywords: ["image", "text", "button", "value", "alter", "files"],
3
+ symbols: /[=><!~?:&|+\-*#\/\^%]+/,
4
+ operators: ['*', '/', '+', '-', "==", "!=", '=', "<=", '<', ">=", ">>>", ">>", '>', '!', "&&", '&', "||", '|', '^', '?', ':'],
5
+ includeLF: true,
6
+ tokenizer: {
7
+ root: [
8
+ // keywords
9
+ [/[a-z_$][\w$]*/, {
10
+ cases: {
11
+ "@keywords": { token: "keyword" }
12
+ }
13
+ }],
14
+ // numbers
15
+ [/\d*\.\d+([eE][\-+]?\d+)?/, "number.float"],
16
+ [/0[xX][0-9a-fA-F]+/, "number.hex"],
17
+ [/\d+/, "number"],
18
+ // strings
19
+ [/"(.|\"\")*?"/, "string"],
20
+ // comments
21
+ [/;.*/, "comment"],
22
+ // parameter letters
23
+ [/'?[A-Z]/, "keyword"],
24
+ [/'[a-z]/, "keyword"]
25
+ ]
26
+ }
27
+ };
28
+ export const menuLanguageConfiguration = {
29
+ comments: {
30
+ lineComment: ";"
31
+ }
32
+ };
@@ -0,0 +1,3 @@
1
+ import type * as monaco from "monaco-editor-core/esm/vs/editor/editor.api.js";
2
+ export declare const stm32Language: monaco.languages.IMonarchLanguage;
3
+ export declare const stm32LanguageConfiguration: monaco.languages.LanguageConfiguration;