dsh-vscode-mode 0.1.36 → 0.1.42

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/lib/client.js CHANGED
@@ -385,6 +385,523 @@ window.__ModuleLoader__.load({
385
385
  window.dispatchEvent(new CustomEvent("edrv:show-launcher", { detail: { tab } }));
386
386
  }
387
387
  //#endregion
388
+ //#region src/client/monaco/theme.ts
389
+ /**
390
+ * dsh-vscode-mode client — Monaco 语法分色主题(rich token 配色)。
391
+ * 编辑器原先硬编码 'vs'(内置基础主题),token 分色层次少;
392
+ * 这里定义 edrv-dark / edrv-light 两套完整 token 配色(对齐 VSCode Dark+ 语义分层),
393
+ * 并随 DSH 明暗主题自动切换(跟随文档根 data 主题属性 / prefers-color-scheme)。
394
+ * 作者 ddj 2026-08-28
395
+ */
396
+ const EDRV_DARK = "edrv-dark";
397
+ const EDRV_LIGHT = "edrv-light";
398
+ /** 语法分色(语义分层:关键字/字符串/数字/注释/函数/变量/类型/运算符/正则/标签/属性…)。 */
399
+ const DARK_RULES = [
400
+ {
401
+ token: "",
402
+ foreground: "d4d4d4",
403
+ background: "1e1e1e"
404
+ },
405
+ {
406
+ token: "comment",
407
+ foreground: "6a9955",
408
+ fontStyle: "italic"
409
+ },
410
+ {
411
+ token: "comment.doc",
412
+ foreground: "7ca97c",
413
+ fontStyle: "italic"
414
+ },
415
+ {
416
+ token: "keyword",
417
+ foreground: "c586c0"
418
+ },
419
+ {
420
+ token: "keyword.control",
421
+ foreground: "c586c0"
422
+ },
423
+ {
424
+ token: "keyword.operator",
425
+ foreground: "d4d4d4"
426
+ },
427
+ {
428
+ token: "keyword.json",
429
+ foreground: "9cdcfe"
430
+ },
431
+ {
432
+ token: "string",
433
+ foreground: "ce9178"
434
+ },
435
+ {
436
+ token: "string.escape",
437
+ foreground: "d7ba7d"
438
+ },
439
+ {
440
+ token: "string.key.json",
441
+ foreground: "9cdcfe"
442
+ },
443
+ {
444
+ token: "string.value.json",
445
+ foreground: "ce9178"
446
+ },
447
+ {
448
+ token: "string.yaml",
449
+ foreground: "ce9178"
450
+ },
451
+ {
452
+ token: "number",
453
+ foreground: "b5cea8"
454
+ },
455
+ {
456
+ token: "number.hex",
457
+ foreground: "b5cea8"
458
+ },
459
+ {
460
+ token: "regexp",
461
+ foreground: "d16969"
462
+ },
463
+ {
464
+ token: "type",
465
+ foreground: "4ec9b0"
466
+ },
467
+ {
468
+ token: "type.identifier",
469
+ foreground: "4ec9b0"
470
+ },
471
+ {
472
+ token: "identifier",
473
+ foreground: "d4d4d4"
474
+ },
475
+ {
476
+ token: "variable",
477
+ foreground: "9cdcfe"
478
+ },
479
+ {
480
+ token: "variable.global",
481
+ foreground: "ff6699"
482
+ },
483
+ {
484
+ token: "variable.local",
485
+ foreground: "9cdcfe"
486
+ },
487
+ {
488
+ token: "variable.mutable",
489
+ foreground: "9cdcfe"
490
+ },
491
+ {
492
+ token: "variable.predefined",
493
+ foreground: "4fc1ff"
494
+ },
495
+ {
496
+ token: "variable.parameter",
497
+ foreground: "00bfae"
498
+ },
499
+ {
500
+ token: "parameter",
501
+ foreground: "00bfae"
502
+ },
503
+ {
504
+ token: "function",
505
+ foreground: "dcdcaa"
506
+ },
507
+ {
508
+ token: "method",
509
+ foreground: "dcdcaa"
510
+ },
511
+ {
512
+ token: "property",
513
+ foreground: "9cdcfe"
514
+ },
515
+ {
516
+ token: "function.identifier",
517
+ foreground: "dcdcaa"
518
+ },
519
+ {
520
+ token: "member",
521
+ foreground: "dcdcaa"
522
+ },
523
+ {
524
+ token: "constant",
525
+ foreground: "4fc1ff"
526
+ },
527
+ {
528
+ token: "constant.numeric",
529
+ foreground: "b5cea8"
530
+ },
531
+ {
532
+ token: "constant.language",
533
+ foreground: "569cd6"
534
+ },
535
+ {
536
+ token: "tag",
537
+ foreground: "569cd6"
538
+ },
539
+ {
540
+ token: "tag.xml",
541
+ foreground: "569cd6"
542
+ },
543
+ {
544
+ token: "metatag",
545
+ foreground: "808080"
546
+ },
547
+ {
548
+ token: "attribute.name",
549
+ foreground: "9cdcfe"
550
+ },
551
+ {
552
+ token: "attribute.value",
553
+ foreground: "ce9178"
554
+ },
555
+ {
556
+ token: "delimiter",
557
+ foreground: "d4d4d4"
558
+ },
559
+ {
560
+ token: "delimiter.bracket",
561
+ foreground: "ffd700"
562
+ },
563
+ {
564
+ token: "delimiter.html",
565
+ foreground: "808080"
566
+ },
567
+ {
568
+ token: "operator",
569
+ foreground: "d4d4d4"
570
+ },
571
+ {
572
+ token: "namespace",
573
+ foreground: "4ec9b0"
574
+ },
575
+ {
576
+ token: "class",
577
+ foreground: "4ec9b0"
578
+ },
579
+ {
580
+ token: "interface",
581
+ foreground: "4ec9b0"
582
+ },
583
+ {
584
+ token: "enum",
585
+ foreground: "4ec9b0"
586
+ },
587
+ {
588
+ token: "struct",
589
+ foreground: "4ec9b0"
590
+ },
591
+ {
592
+ token: "annotation",
593
+ foreground: "dcdcaa"
594
+ },
595
+ {
596
+ token: "punctuation",
597
+ foreground: "d4d4d4"
598
+ },
599
+ {
600
+ token: "invalid",
601
+ foreground: "f44747"
602
+ }
603
+ ];
604
+ const LIGHT_RULES = [
605
+ {
606
+ token: "",
607
+ foreground: "1e1e1e",
608
+ background: "ffffff"
609
+ },
610
+ {
611
+ token: "comment",
612
+ foreground: "008000",
613
+ fontStyle: "italic"
614
+ },
615
+ {
616
+ token: "comment.doc",
617
+ foreground: "2e7d32",
618
+ fontStyle: "italic"
619
+ },
620
+ {
621
+ token: "keyword",
622
+ foreground: "0000ff"
623
+ },
624
+ {
625
+ token: "keyword.control",
626
+ foreground: "af00db"
627
+ },
628
+ {
629
+ token: "keyword.operator",
630
+ foreground: "1e1e1e"
631
+ },
632
+ {
633
+ token: "keyword.json",
634
+ foreground: "0451a5"
635
+ },
636
+ {
637
+ token: "string",
638
+ foreground: "a31515"
639
+ },
640
+ {
641
+ token: "string.escape",
642
+ foreground: "ee0000"
643
+ },
644
+ {
645
+ token: "string.key.json",
646
+ foreground: "0451a5"
647
+ },
648
+ {
649
+ token: "string.value.json",
650
+ foreground: "a31515"
651
+ },
652
+ {
653
+ token: "string.yaml",
654
+ foreground: "a31515"
655
+ },
656
+ {
657
+ token: "number",
658
+ foreground: "098658"
659
+ },
660
+ {
661
+ token: "number.hex",
662
+ foreground: "098658"
663
+ },
664
+ {
665
+ token: "regexp",
666
+ foreground: "811f3f"
667
+ },
668
+ {
669
+ token: "type",
670
+ foreground: "267f99"
671
+ },
672
+ {
673
+ token: "type.identifier",
674
+ foreground: "267f99"
675
+ },
676
+ {
677
+ token: "identifier",
678
+ foreground: "1e1e1e"
679
+ },
680
+ {
681
+ token: "variable",
682
+ foreground: "001080"
683
+ },
684
+ {
685
+ token: "variable.global",
686
+ foreground: "c2185b"
687
+ },
688
+ {
689
+ token: "variable.local",
690
+ foreground: "001080"
691
+ },
692
+ {
693
+ token: "variable.mutable",
694
+ foreground: "001080"
695
+ },
696
+ {
697
+ token: "variable.predefined",
698
+ foreground: "001080"
699
+ },
700
+ {
701
+ token: "variable.parameter",
702
+ foreground: "008577"
703
+ },
704
+ {
705
+ token: "parameter",
706
+ foreground: "008577"
707
+ },
708
+ {
709
+ token: "function",
710
+ foreground: "795e26"
711
+ },
712
+ {
713
+ token: "method",
714
+ foreground: "795e26"
715
+ },
716
+ {
717
+ token: "function.identifier",
718
+ foreground: "795e26"
719
+ },
720
+ {
721
+ token: "member",
722
+ foreground: "795e26"
723
+ },
724
+ {
725
+ token: "property",
726
+ foreground: "001080"
727
+ },
728
+ {
729
+ token: "constant",
730
+ foreground: "0070c1"
731
+ },
732
+ {
733
+ token: "constant.numeric",
734
+ foreground: "098658"
735
+ },
736
+ {
737
+ token: "constant.language",
738
+ foreground: "0000ff"
739
+ },
740
+ {
741
+ token: "tag",
742
+ foreground: "800000"
743
+ },
744
+ {
745
+ token: "tag.xml",
746
+ foreground: "800000"
747
+ },
748
+ {
749
+ token: "metatag",
750
+ foreground: "6b6b6b"
751
+ },
752
+ {
753
+ token: "attribute.name",
754
+ foreground: "e50000"
755
+ },
756
+ {
757
+ token: "attribute.value",
758
+ foreground: "0451a5"
759
+ },
760
+ {
761
+ token: "delimiter",
762
+ foreground: "1e1e1e"
763
+ },
764
+ {
765
+ token: "delimiter.bracket",
766
+ foreground: "b46400"
767
+ },
768
+ {
769
+ token: "delimiter.html",
770
+ foreground: "800000"
771
+ },
772
+ {
773
+ token: "operator",
774
+ foreground: "1e1e1e"
775
+ },
776
+ {
777
+ token: "namespace",
778
+ foreground: "267f99"
779
+ },
780
+ {
781
+ token: "class",
782
+ foreground: "267f99"
783
+ },
784
+ {
785
+ token: "interface",
786
+ foreground: "267f99"
787
+ },
788
+ {
789
+ token: "enum",
790
+ foreground: "267f99"
791
+ },
792
+ {
793
+ token: "struct",
794
+ foreground: "267f99"
795
+ },
796
+ {
797
+ token: "annotation",
798
+ foreground: "795e26"
799
+ },
800
+ {
801
+ token: "punctuation",
802
+ foreground: "1e1e1e"
803
+ },
804
+ {
805
+ token: "invalid",
806
+ foreground: "cd3131"
807
+ }
808
+ ];
809
+ /** 编辑器 UI 配色(与语法配色同套,保证 gutter/minimap/selection 一致)。 */
810
+ const DARK_COLORS = {
811
+ "editor.background": "#1e1e1e",
812
+ "editor.foreground": "#d4d4d4",
813
+ "editorLineNumber.foreground": "#6e7681",
814
+ "editorLineNumber.activeForeground": "#c6c6c6",
815
+ "editor.selectionBackground": "#264f78",
816
+ "editor.inactiveSelectionBackground": "#3a3d41",
817
+ "editor.lineHighlightBackground": "#2a2d2e",
818
+ "editorCursor.foreground": "#aeafad",
819
+ "editorIndentGuide.background1": "#404040",
820
+ "editorIndentGuide.activeBackground1": "#707070",
821
+ "editorWidget.background": "#252526",
822
+ "editorHoverWidget.background": "#252526",
823
+ "editorGutter.background": "#1e1e1e",
824
+ "minimap.background": "#1e1e1e",
825
+ "scrollbarSlider.background": "#79797966",
826
+ "scrollbarSlider.hoverBackground": "#646464b3",
827
+ "scrollbarSlider.activeBackground": "#bfbfbf66"
828
+ };
829
+ const LIGHT_COLORS = {
830
+ "editor.background": "#ffffff",
831
+ "editor.foreground": "#1e1e1e",
832
+ "editorLineNumber.foreground": "#6c6c6c",
833
+ "editorLineNumber.activeForeground": "#171184",
834
+ "editor.selectionBackground": "#add6ff",
835
+ "editor.inactiveSelectionBackground": "#e5ebf1",
836
+ "editor.lineHighlightBackground": "#f0f0f0",
837
+ "editorCursor.foreground": "#000000",
838
+ "editorIndentGuide.background1": "#d3d3d3",
839
+ "editorIndentGuide.activeBackground1": "#939393",
840
+ "editorWidget.background": "#f3f3f3",
841
+ "editorHoverWidget.background": "#f3f3f3",
842
+ "editorGutter.background": "#ffffff",
843
+ "minimap.background": "#ffffff",
844
+ "scrollbarSlider.background": "#64646466",
845
+ "scrollbarSlider.hoverBackground": "#646464b3",
846
+ "scrollbarSlider.activeBackground": "#00000099"
847
+ };
848
+ /**
849
+ * 注册 edrv 双套主题(幂等;Monaco 未加载时静默跳过)。
850
+ * @author ddj 2026年08月28号
851
+ * @param monaco Monaco 实例(window.monaco)
852
+ */
853
+ function registerThemes(monaco) {
854
+ if (!monaco?.editor?.defineTheme) return;
855
+ try {
856
+ monaco.editor.defineTheme(EDRV_DARK, {
857
+ base: "vs-dark",
858
+ inherit: true,
859
+ rules: DARK_RULES,
860
+ colors: DARK_COLORS
861
+ });
862
+ monaco.editor.defineTheme(EDRV_LIGHT, {
863
+ base: "vs",
864
+ inherit: true,
865
+ rules: LIGHT_RULES,
866
+ colors: LIGHT_COLORS
867
+ });
868
+ } catch (error) {}
869
+ }
870
+ /**
871
+ * 探测当前 DSH 明暗(文档根 data 主题属性 → prefers-color-scheme → 暗)。
872
+ * @author ddj 2026年08月28号
873
+ * @returns 'dark' | 'light'
874
+ */
875
+ function detectColorScheme() {
876
+ try {
877
+ const attr = document.documentElement?.getAttribute?.("data-theme") ?? document.body?.getAttribute?.("data-theme");
878
+ const value = String(attr ?? "").toLowerCase();
879
+ if (value.includes("light") || value.includes("catppuccin-latte")) return "light";
880
+ if (value.includes("dark") || value.includes("frappe") || value.includes("macchiato") || value.includes("mocha")) return "dark";
881
+ if (typeof window.matchMedia === "function") return window.matchMedia("(prefers-color-scheme: light)").matches ? "light" : "dark";
882
+ } catch (error) {}
883
+ return "dark";
884
+ }
885
+ /**
886
+ * 主题名:按当前明暗返回已注册主题(未注册回落 vs)。
887
+ * @author ddj 2026年08月28号
888
+ * @returns 主题 id
889
+ */
890
+ function themeNameOf() {
891
+ return detectColorScheme() === "light" ? EDRV_LIGHT : EDRV_DARK;
892
+ }
893
+ /**
894
+ * 应用主题到 Monaco(全局切换;新建编辑器也用该主题)。
895
+ * @author ddj 2026年08月28号
896
+ * @param monaco Monaco 实例
897
+ */
898
+ function applyTheme(monaco) {
899
+ if (!monaco?.editor?.setTheme) return;
900
+ try {
901
+ monaco.editor.setTheme(themeNameOf());
902
+ } catch (error) {}
903
+ }
904
+ //#endregion
388
905
  //#region src/client/monaco/loader.ts
389
906
  /**
390
907
  * dsh-vscode-mode client — Monaco 加载与语言映射(AMD 构建,离线随包分发)。
@@ -500,7 +1017,20 @@ window.__ModuleLoader__.load({
500
1017
  kt: "kotlin",
501
1018
  kts: "kotlin",
502
1019
  dart: "dart",
503
- dockerfile: "dockerfile"
1020
+ dockerfile: "dockerfile",
1021
+ csproj: "xml",
1022
+ props: "xml",
1023
+ targets: "xml",
1024
+ plist: "xml",
1025
+ lua51: "lua",
1026
+ luac: "lua",
1027
+ gitattributes: "ini",
1028
+ editorconfig: "ini",
1029
+ env: "ini",
1030
+ properties: "ini",
1031
+ json5: "jsonc",
1032
+ log: "plaintext",
1033
+ txt: "plaintext"
504
1034
  };
505
1035
  /**
506
1036
  * 路径 → Monaco language id:取 basename 扩展名(dockerfile/Dockerfile 特判)。
@@ -536,6 +1066,10 @@ window.__ModuleLoader__.load({
536
1066
  publishStage("core", MONACO_STAGES.core.progress, MONACO_STAGES.core.message);
537
1067
  window.require(["vs/editor/editor.main"], () => {
538
1068
  publishStage("ready", MONACO_STAGES.ready.progress, MONACO_STAGES.ready.message);
1069
+ try {
1070
+ registerThemes(window.monaco);
1071
+ applyTheme(window.monaco);
1072
+ } catch (error) {}
539
1073
  resolve(window.monaco);
540
1074
  }, (err) => fail(/* @__PURE__ */ new Error("Monaco 模块加载失败:" + String(err))));
541
1075
  } catch (error) {
@@ -1971,6 +2505,48 @@ window.__ModuleLoader__.load({
1971
2505
  }
1972
2506
  //#endregion
1973
2507
  //#region src/shared/lsp.ts
2508
+ /** LSP semantic token 标准类型(host 归一化后,client 与 Monaco 共用)。 */
2509
+ const LSP_SEMANTIC_TOKEN_TYPES = [
2510
+ "namespace",
2511
+ "type",
2512
+ "class",
2513
+ "enum",
2514
+ "interface",
2515
+ "struct",
2516
+ "typeParameter",
2517
+ "parameter",
2518
+ "variable",
2519
+ "property",
2520
+ "enumMember",
2521
+ "event",
2522
+ "function",
2523
+ "method",
2524
+ "macro",
2525
+ "label",
2526
+ "comment",
2527
+ "string",
2528
+ "keyword",
2529
+ "number",
2530
+ "regexp",
2531
+ "operator",
2532
+ "decorator"
2533
+ ];
2534
+ /** LSP semantic token 标准修饰符(host 归一化后,client 与 Monaco 共用)。 */
2535
+ const LSP_SEMANTIC_TOKEN_MODIFIERS = [
2536
+ "declaration",
2537
+ "definition",
2538
+ "readonly",
2539
+ "static",
2540
+ "deprecated",
2541
+ "abstract",
2542
+ "async",
2543
+ "modification",
2544
+ "documentation",
2545
+ "defaultLibrary",
2546
+ "global",
2547
+ "local",
2548
+ "mutable"
2549
+ ];
1974
2550
  /**
1975
2551
  * Monaco 位置(1-based lineNumber/column)→ LSP Position(0-based)。
1976
2552
  * @author ddj 2026年08月27号
@@ -2113,6 +2689,16 @@ window.__ModuleLoader__.load({
2113
2689
  if (!res || !res.ok) return null;
2114
2690
  return res.hover || null;
2115
2691
  }
2692
+ /** 查询当前文档的 semantic tokens(host 已归一化 legend 与 delta data)。 */
2693
+ async function fetchSemanticTokens(path, text) {
2694
+ await syncDoc(path, text, true);
2695
+ const res = await rpc("edrv.lsp.semanticTokens", {
2696
+ sessionId,
2697
+ path
2698
+ }).catch(() => null);
2699
+ if (!res || !res.ok || !res.tokens || !Array.isArray(res.tokens.data)) return null;
2700
+ return res.tokens;
2701
+ }
2116
2702
  /** 拉取服务器状态(带 5s 缓存)。 */
2117
2703
  async function refreshStatus(force = false) {
2118
2704
  if (!force && Date.now() - statusCache.at < 1500) return statusCache.servers;
@@ -2132,7 +2718,10 @@ window.__ModuleLoader__.load({
2132
2718
  const end = range.end || {};
2133
2719
  return {
2134
2720
  uri: window.monaco.Uri.parse(toEdrvUri(loc.uri, loc.root)),
2135
- range: new window.monaco.Range(Math.max(1, (start.line ?? 0) + 1), Math.max(1, (start.character ?? 0) + 1), Math.max(1, (end.line ?? 0) + 1), Math.max(1, (end.character ?? 0) + 1))
2721
+ range: new window.monaco.Range(Math.max(1, (start.line ?? 0) + 1), Math.max(1, (start.character ?? 0) + 1), Math.max(1, (end.line ?? 0) + 1), Math.max(1, (end.character ?? 0) + 1)),
2722
+ lspUri: loc.uri,
2723
+ lspRoot: loc.root,
2724
+ lspRange: loc.range
2136
2725
  };
2137
2726
  }
2138
2727
  /** file:// URI(或裸路径)→ edrv:// 相对路径(去掉盘符/根,与编辑器模型路径一致)。 */
@@ -2144,30 +2733,68 @@ window.__ModuleLoader__.load({
2144
2733
  }
2145
2734
  /** 将 LSP 目标解析为当前工作区相对路径(跨文件跳转与 model URI 对齐)。 */
2146
2735
  function targetOpenPath(loc) {
2147
- return relativeLspPath(loc?.uri, loc?.root);
2736
+ return relativeLspPath(loc?.lspUri ?? loc?.uri, loc?.lspRoot ?? loc?.root);
2148
2737
  }
2149
- /** file URI/裸路径 → root 下的 / 分隔相对路径;root 不匹配时保留规范化绝对路径。 */
2150
- function relativeLspPath(uri, root) {
2738
+ /**
2739
+ * 归一化 Location 的 uri → 工作区路径(剥离 scheme,不补盘符)。
2740
+ * 三种输入形态(缺一即导致 scheme 泄漏进文件路径 → 跳转"文件不存在"):
2741
+ * - Monaco Uri 对象(toClientLocation 产物,scheme=edrv)→ 取 .path(已是工作区相对路径)
2742
+ * - 'edrv:///x' 字符串 → 取路径部分
2743
+ * - 'file:///D:/x' → 去 authority 空段保留盘符
2744
+ * @author ddj 2026年08月28号
2745
+ * @param uri Monaco Uri 对象 / file:// 或 edrv:// URI / 裸路径
2746
+ * @returns / 分隔的路径(edrv 相对路径 或 带盘符绝对路径)
2747
+ */
2748
+ function lspUriToAbs(uri) {
2151
2749
  if (!uri) return "";
2152
- let path = String(uri);
2153
- if (path.startsWith("file://")) try {
2154
- path = decodeURIComponent(path.replace(/^file:\/\//, ""));
2750
+ const isObject = typeof uri === "object";
2751
+ const raw = isObject && typeof uri.path === "string" ? uri.path : String(uri);
2752
+ let path = raw;
2753
+ if (path.startsWith("file://")) {
2754
+ try {
2755
+ path = decodeURIComponent(path.replace(/^file:\/\/\/?/, ""));
2756
+ } catch (error) {
2757
+ path = path.replace(/^file:\/\/\/?/, "");
2758
+ }
2759
+ return path.replace(/\\/g, "/").replace(/^\/([A-Za-z]:)/, "$1");
2760
+ }
2761
+ if (path.startsWith("edrv://")) try {
2762
+ path = decodeURIComponent(path.replace(/^edrv:\/\/\/?/, ""));
2155
2763
  } catch (error) {
2156
- path = path.replace(/^file:\/\//, "");
2764
+ path = path.replace(/^edrv:\/\/\/?/, "");
2157
2765
  }
2158
- path = path.replace(/\\/g, "/").replace(/^\/([A-Za-z]:)/, "$1");
2766
+ if (isObject ? uri.scheme === "edrv" : raw.startsWith("edrv://")) return path.replace(/\\/g, "/").replace(/^\/+/, "");
2767
+ return path.replace(/\\/g, "/").replace(/^\/([A-Za-z]:)/, "$1");
2768
+ }
2769
+ /**
2770
+ * file URI/裸路径 → root 下的 / 分隔相对路径。
2771
+ * root 匹配返回工作区相对路径;root 不匹配返回完整绝对路径(保留盘符),
2772
+ * 供 edrv.read 按绝对路径解析(原先错误剥掉首个 / 导致跳转"文件不存在")。
2773
+ * @author ddj 2026年08月28号
2774
+ * @param uri file:// URI 或裸路径
2775
+ * @param root 工作区根(可选)
2776
+ * @returns 可 edrv.read 的路径
2777
+ */
2778
+ function relativeLspPath(uri, root) {
2779
+ if (!uri) return "";
2780
+ const path = lspUriToAbs(uri);
2159
2781
  const normalizedRoot = root ? String(root).replace(/\\/g, "/").replace(/^\/([A-Za-z]:)/, "$1").replace(/\/+$/, "") : "";
2160
2782
  const lowerPath = path.toLowerCase();
2161
2783
  const lowerRoot = normalizedRoot.toLowerCase();
2162
2784
  if (normalizedRoot && (lowerPath === lowerRoot || lowerPath.startsWith(lowerRoot + "/"))) return path.slice(normalizedRoot.length).replace(/^\/+/, "");
2163
- return path.replace(/^\/+/, "");
2785
+ return path;
2164
2786
  }
2165
2787
  /** 目标行/列(0-based LSP → 1-based Monaco)。 */
2166
2788
  function targetMonoPosition(loc) {
2167
- const start = (loc.range || {}).start || {};
2789
+ const lspRange = loc?.lspRange;
2790
+ if (lspRange?.start) return {
2791
+ line: Math.max(1, (lspRange.start.line ?? 0) + 1),
2792
+ column: Math.max(1, (lspRange.start.character ?? 0) + 1)
2793
+ };
2794
+ const range = loc?.range ?? {};
2168
2795
  return {
2169
- line: Math.max(1, (start.line ?? 0) + 1),
2170
- column: Math.max(1, (start.character ?? 0) + 1)
2796
+ line: Math.max(1, range.startLineNumber ?? 1),
2797
+ column: Math.max(1, range.startColumn ?? 1)
2171
2798
  };
2172
2799
  }
2173
2800
  //#endregion
@@ -2180,6 +2807,10 @@ window.__ModuleLoader__.load({
2180
2807
  * 作者 ddj 2026-08-27
2181
2808
  */
2182
2809
  const LSP_LANGS = ["lua", "csharp"];
2810
+ const SEMANTIC_LEGEND = {
2811
+ tokenTypes: [...LSP_SEMANTIC_TOKEN_TYPES],
2812
+ tokenModifiers: [...LSP_SEMANTIC_TOKEN_MODIFIERS]
2813
+ };
2183
2814
  let registered = false;
2184
2815
  const disposables = [];
2185
2816
  let overlayEl = null;
@@ -2214,6 +2845,19 @@ window.__ModuleLoader__.load({
2214
2845
  };
2215
2846
  disposables.push(monaco.editor.onDidCreateModel(attachModel));
2216
2847
  for (const model of monaco.editor.getModels()) attachModel(model);
2848
+ const onRedetect = (event) => {
2849
+ const languageId = event?.detail?.languageId;
2850
+ if (!languageId) return;
2851
+ for (const model of monaco.editor.getModels()) {
2852
+ const path = pathOfModel(model);
2853
+ if (!path) continue;
2854
+ if (model.getLanguageId && model.getLanguageId() !== languageId) continue;
2855
+ syncDoc(path, model.getValue(), true);
2856
+ }
2857
+ refreshStatus(true);
2858
+ };
2859
+ window.addEventListener("edrv:lsp-redetect", onRedetect);
2860
+ disposables.push(() => window.removeEventListener("edrv:lsp-redetect", onRedetect));
2217
2861
  disposables.push(monaco.languages.registerDefinitionProvider(LSP_LANGS, { provideDefinition: (model, position, token) => {
2218
2862
  const path = pathOfModel(model);
2219
2863
  if (!path) return [];
@@ -2236,7 +2880,17 @@ window.__ModuleLoader__.load({
2236
2880
  contents: hover.contents.map((text) => ({ value: text })),
2237
2881
  range: range || void 0
2238
2882
  };
2239
- } }));
2883
+ } }), monaco.languages.registerDocumentSemanticTokensProvider(LSP_LANGS, {
2884
+ getLegend: () => SEMANTIC_LEGEND,
2885
+ provideDocumentSemanticTokens: async (model, token) => {
2886
+ const path = pathOfModel(model);
2887
+ if (!path || token?.isCancellationRequested) return { data: /* @__PURE__ */ new Uint32Array() };
2888
+ const result = await fetchSemanticTokens(path, model.getValue());
2889
+ if (!result || token?.isCancellationRequested) return { data: /* @__PURE__ */ new Uint32Array() };
2890
+ return { data: Uint32Array.from(result.data) };
2891
+ },
2892
+ releaseDocumentSemanticTokens: () => {}
2893
+ }));
2240
2894
  }
2241
2895
  /** LSP SymbolInfo(host 归一化后)→ Monaco DocumentSymbol。 */
2242
2896
  function toMonacoSymbol(symbol) {
@@ -2625,7 +3279,8 @@ window.__ModuleLoader__.load({
2625
3279
  }));
2626
3280
  setStatus("已加载");
2627
3281
  } else {
2628
- const message = res?.error ? String(res.error) : "读取失败";
3282
+ const base = res?.error ? String(res.error) : "读取失败";
3283
+ const message = res?.resolvedPath ? base + ":" + String(res.resolvedPath) : base;
2629
3284
  setLoadError(message);
2630
3285
  setError(message);
2631
3286
  setStatus("读取失败");
@@ -2892,6 +3547,20 @@ window.__ModuleLoader__.load({
2892
3547
  alive = false;
2893
3548
  };
2894
3549
  }, [monaco, monacoErr]);
3550
+ react.default.useEffect(() => {
3551
+ if (!monaco) return;
3552
+ registerThemes(monaco);
3553
+ applyTheme(monaco);
3554
+ if (typeof window.matchMedia !== "function") return;
3555
+ const query = window.matchMedia("(prefers-color-scheme: dark)");
3556
+ const onChange = () => applyTheme(monaco);
3557
+ if (typeof query.addEventListener === "function") {
3558
+ query.addEventListener("change", onChange);
3559
+ return () => query.removeEventListener("change", onChange);
3560
+ }
3561
+ query.addListener(onChange);
3562
+ return () => query.removeListener(onChange);
3563
+ }, [monaco]);
2895
3564
  monacoRef.current = monaco;
2896
3565
  const getModel = (path, text) => {
2897
3566
  const cache = modelsRef.current;
@@ -3005,7 +3674,7 @@ window.__ModuleLoader__.load({
3005
3674
  const ed = m.editor.create(node, {
3006
3675
  value: "",
3007
3676
  language: "plaintext",
3008
- theme: "vs",
3677
+ theme: themeNameOf(),
3009
3678
  fontFamily: "var(--ds-font-family-code, ui-monospace, monospace)",
3010
3679
  fontSize: 13,
3011
3680
  lineHeight: 20,
@@ -4639,8 +5308,8 @@ window.__ModuleLoader__.load({
4639
5308
  */
4640
5309
  const LANGUAGES = [{
4641
5310
  id: "lua",
4642
- label: "Lua(LuaLS)",
4643
- hint: "lua-language-server,EmmyLua 注解原生支持"
5311
+ label: "Lua(EmmyLua / LuaLS)",
5312
+ hint: "优先使用已安装 EmmyLua;未安装时回退 LuaLS 或 PATH 中的 lua-language-server"
4644
5313
  }, {
4645
5314
  id: "csharp",
4646
5315
  label: "C#(Roslyn / OmniSharp)",
@@ -4678,8 +5347,8 @@ window.__ModuleLoader__.load({
4678
5347
  label: "更新"
4679
5348
  }
4680
5349
  ];
4681
- /** 语言卡片:状态 + 启用开关 + 命令/路径配置。 */
4682
- function LangCard({ lang, config, status, busy, onToggle, onSave }) {
5350
+ /** 语言卡片:状态 + 启用开关 + 命令/路径配置 + 重新检测。 */
5351
+ function LangCard({ lang, config, status, busy, onToggle, onSave, onRedetect }) {
4683
5352
  const [pathDraft, setPathDraft] = react.default.useState(config?.path ?? "");
4684
5353
  const [commandDraft, setCommandDraft] = react.default.useState(config?.command ?? "");
4685
5354
  react.default.useEffect(() => {
@@ -4691,7 +5360,7 @@ window.__ModuleLoader__.load({
4691
5360
  className: "vsm-switch " + (config?.enabled !== false ? "on" : ""),
4692
5361
  onClick: () => onToggle(lang.id, config?.enabled !== false ? false : true),
4693
5362
  "aria-label": "启用/禁用"
4694
- }, config?.enabled !== false ? "●" : "○"))), react.default.createElement("div", { className: "vsm-mcp-meta" }, PHASE_LABEL[phase] ?? phase, " · ", SOURCE_LABEL[status?.source] ?? status?.source, phase === "ready" && status?.root ? " · " + String(status.root).split(/[\\/]/).pop() : ""), status?.reason ? react.default.createElement("div", { className: "vsm-mcp-error" }, status.reason) : null, react.default.createElement("div", { className: "vsm-lsp-form" }, react.default.createElement("label", null, react.default.createElement("span", null, "可执行文件路径(绝对路径,优先)"), react.default.createElement("input", {
5363
+ }, config?.enabled !== false ? "●" : "○"))), react.default.createElement("div", { className: "vsm-mcp-meta" }, PHASE_LABEL[phase] ?? phase, " · ", SOURCE_LABEL[status?.source] ?? status?.source, status?.providerName ? " · " + status.providerName : "", status?.version ? " · v" + status.version : "", phase === "ready" && status?.root ? " · " + String(status.root).split(/[\\/]/).pop() : ""), status?.reason ? react.default.createElement("div", { className: "vsm-mcp-error" }, status.reason) : null, react.default.createElement("div", { className: "vsm-lsp-form" }, react.default.createElement("label", null, react.default.createElement("span", null, "可执行文件路径(绝对路径,优先)"), react.default.createElement("input", {
4695
5364
  value: pathDraft,
4696
5365
  placeholder: "如 C:/.../lua-language-server.exe",
4697
5366
  onChange: (e) => setPathDraft(e.target.value)
@@ -4703,7 +5372,12 @@ window.__ModuleLoader__.load({
4703
5372
  className: "vsm-primary",
4704
5373
  disabled: busy === lang.id,
4705
5374
  onClick: () => onSave(lang.id, pathDraft, commandDraft)
4706
- }, busy === lang.id ? "保存中…" : "保存配置")), react.default.createElement("div", { className: "vsm-lsp-hint" }, lang.hint));
5375
+ }, busy === lang.id ? "保存中…" : "保存配置"), react.default.createElement("button", {
5376
+ className: "vsm-primary vsm-small",
5377
+ disabled: busy === lang.id,
5378
+ onClick: () => onRedetect(lang.id),
5379
+ title: "清缓存并重新选择语言服务器(EmmyLua / LuaLS)"
5380
+ }, busy === lang.id ? "检测中…" : "重新检测")), react.default.createElement("div", { className: "vsm-lsp-hint" }, lang.hint));
4707
5381
  }
4708
5382
  /** 已装扩展卡片:清单 + 卸载/更新。 */
4709
5383
  function InstalledCard({ ext, busy, onUninstall, onUpdate }) {
@@ -4770,6 +5444,14 @@ window.__ModuleLoader__.load({
4770
5444
  if (next === "installed" || next === "updates") refreshExt();
4771
5445
  if (next === "servers") refreshServers();
4772
5446
  };
5447
+ /** 合并 redetect/configUpdate 返回的服务器状态:只替换该语言条目。 */
5448
+ const mergeServers = (languageId, list) => {
5449
+ setServers((prev) => Array.isArray(list) ? [...prev.filter((s) => s.languageId !== languageId), ...list] : prev);
5450
+ };
5451
+ /** 重检测后通知编辑器:与 host 重新同步已打开的模型(触发重新 acquire)。 */
5452
+ const notifyResync = (languageId) => {
5453
+ window.dispatchEvent(new CustomEvent("edrv:lsp-redetect", { detail: { languageId } }));
5454
+ };
4773
5455
  const saveLang = (languageId, path, command) => {
4774
5456
  setBusy(languageId);
4775
5457
  setError("");
@@ -4783,6 +5465,8 @@ window.__ModuleLoader__.load({
4783
5465
  return;
4784
5466
  }
4785
5467
  setConfig(res.config ?? {});
5468
+ mergeServers(languageId, res.servers);
5469
+ notifyResync(languageId);
4786
5470
  }).catch((e) => setError(String(e))).finally(() => setBusy(""));
4787
5471
  };
4788
5472
  const toggleLang = (languageId, enabled) => {
@@ -4791,10 +5475,25 @@ window.__ModuleLoader__.load({
4791
5475
  languageId,
4792
5476
  enabled
4793
5477
  }).then((res) => {
4794
- if (res?.ok) {
4795
- setConfig(res.config ?? {});
4796
- refreshServers();
4797
- } else setError(res?.error ?? "切换失败");
5478
+ if (!res?.ok) {
5479
+ setError(res?.error ?? "切换失败");
5480
+ return;
5481
+ }
5482
+ setConfig(res.config ?? {});
5483
+ mergeServers(languageId, res.servers);
5484
+ notifyResync(languageId);
5485
+ }).catch((e) => setError(String(e))).finally(() => setBusy(""));
5486
+ };
5487
+ const redetectLang = (languageId) => {
5488
+ setBusy(languageId);
5489
+ setError("");
5490
+ rpc("edrv.lsp.redetect", { languageId }).then((res) => {
5491
+ if (!res?.ok) {
5492
+ setError(res?.error ?? "重新检测失败");
5493
+ return;
5494
+ }
5495
+ mergeServers(languageId, res.servers);
5496
+ notifyResync(languageId);
4798
5497
  }).catch((e) => setError(String(e))).finally(() => setBusy(""));
4799
5498
  };
4800
5499
  const searchMarket = () => {
@@ -4874,7 +5573,8 @@ window.__ModuleLoader__.load({
4874
5573
  status: servers.find((s) => s.languageId === lang.id),
4875
5574
  busy,
4876
5575
  onToggle: toggleLang,
4877
- onSave: saveLang
5576
+ onSave: saveLang,
5577
+ onRedetect: redetectLang
4878
5578
  })), react.default.createElement("div", { className: "vsm-lsp-hint" }, "提示:打开 Lua/C# 文件即自动(惰性)启动对应语言服务器;未配置时编辑器功能不受影响,大纲回退内置解析。")));
4879
5579
  else if (tab === "installed") body = panel("已安装扩展", react.default.createElement(react.default.Fragment, null, react.default.createElement("div", { className: "vsm-lsp-hint" }, "已安装的语言服务器扩展(存于 ~/.dsh/dsh-vscode-mode/extensions/)。语言服务器被自动发现为「扩展」源。"), installed.length ? installed.map((ext) => react.default.createElement(InstalledCard, {
4880
5580
  key: ext.id,