dsh-vscode-mode 0.1.35 → 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 +1117 -156
- package/lib/client.js.map +1 -1
- package/lib/index.js +447 -62
- package/lib/index.js.map +1 -1
- package/package.json +1 -1
- package/src/client/monaco/loader.ts +12 -1
- package/src/client/monaco/lsp/lspClient.ts +65 -11
- package/src/client/monaco/lsp/providers.ts +34 -1
- package/src/client/monaco/theme.ts +229 -0
- package/src/client/paths.ts +2 -0
- package/src/client/state/workspaceFoldCache.ts +95 -0
- package/src/client/styles/mcp.css +35 -12
- package/src/client/ui/EditorView.ts +21 -2
- package/src/client/ui/KeybindingsPanel.ts +8 -5
- package/src/client/ui/LspSettings.ts +53 -15
- package/src/client/ui/McpSettings.ts +110 -49
- package/src/client/ui/PerfSettings.ts +145 -34
- package/src/lsp/client.ts +29 -2
- package/src/lsp/manager.ts +15 -0
- package/src/lsp/providers.ts +203 -24
- package/src/lsp/rpc.ts +86 -4
- package/src/lsp/server.ts +80 -3
- package/src/paths.ts +7 -0
- package/src/rpc.ts +4 -1
- package/src/shared/lsp.ts +24 -0
- package/src/shared/rpc.ts +6 -2
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) {
|
|
@@ -1861,7 +2395,9 @@ window.__ModuleLoader__.load({
|
|
|
1861
2395
|
/** 搜索面板条件(SearchPanel v1)。 */
|
|
1862
2396
|
search: "edrv.search.v1.",
|
|
1863
2397
|
/** 侧边栏提示已关闭标记(全局,无会话)。 */
|
|
1864
|
-
sideHint: "edrv.side-hint-dismissed"
|
|
2398
|
+
sideHint: "edrv.side-hint-dismissed",
|
|
2399
|
+
/** 「性能优化」页工作区栏目展开状态(workspaceFoldCache v1,全局不按会话)。 */
|
|
2400
|
+
workspaceFold: "edrv.ws-fold.v1."
|
|
1865
2401
|
};
|
|
1866
2402
|
//#endregion
|
|
1867
2403
|
//#region src/client/state/viewStateCache.ts
|
|
@@ -1969,6 +2505,48 @@ window.__ModuleLoader__.load({
|
|
|
1969
2505
|
}
|
|
1970
2506
|
//#endregion
|
|
1971
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
|
+
];
|
|
1972
2550
|
/**
|
|
1973
2551
|
* Monaco 位置(1-based lineNumber/column)→ LSP Position(0-based)。
|
|
1974
2552
|
* @author ddj 2026年08月27号
|
|
@@ -2111,6 +2689,16 @@ window.__ModuleLoader__.load({
|
|
|
2111
2689
|
if (!res || !res.ok) return null;
|
|
2112
2690
|
return res.hover || null;
|
|
2113
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
|
+
}
|
|
2114
2702
|
/** 拉取服务器状态(带 5s 缓存)。 */
|
|
2115
2703
|
async function refreshStatus(force = false) {
|
|
2116
2704
|
if (!force && Date.now() - statusCache.at < 1500) return statusCache.servers;
|
|
@@ -2130,7 +2718,10 @@ window.__ModuleLoader__.load({
|
|
|
2130
2718
|
const end = range.end || {};
|
|
2131
2719
|
return {
|
|
2132
2720
|
uri: window.monaco.Uri.parse(toEdrvUri(loc.uri, loc.root)),
|
|
2133
|
-
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
|
|
2134
2725
|
};
|
|
2135
2726
|
}
|
|
2136
2727
|
/** file:// URI(或裸路径)→ edrv:// 相对路径(去掉盘符/根,与编辑器模型路径一致)。 */
|
|
@@ -2142,30 +2733,68 @@ window.__ModuleLoader__.load({
|
|
|
2142
2733
|
}
|
|
2143
2734
|
/** 将 LSP 目标解析为当前工作区相对路径(跨文件跳转与 model URI 对齐)。 */
|
|
2144
2735
|
function targetOpenPath(loc) {
|
|
2145
|
-
return relativeLspPath(loc?.uri, loc?.root);
|
|
2736
|
+
return relativeLspPath(loc?.lspUri ?? loc?.uri, loc?.lspRoot ?? loc?.root);
|
|
2146
2737
|
}
|
|
2147
|
-
/**
|
|
2148
|
-
|
|
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) {
|
|
2149
2749
|
if (!uri) return "";
|
|
2150
|
-
|
|
2151
|
-
|
|
2152
|
-
|
|
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:\/\/\/?/, ""));
|
|
2153
2763
|
} catch (error) {
|
|
2154
|
-
path = path.replace(/^
|
|
2764
|
+
path = path.replace(/^edrv:\/\/\/?/, "");
|
|
2155
2765
|
}
|
|
2156
|
-
|
|
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);
|
|
2157
2781
|
const normalizedRoot = root ? String(root).replace(/\\/g, "/").replace(/^\/([A-Za-z]:)/, "$1").replace(/\/+$/, "") : "";
|
|
2158
2782
|
const lowerPath = path.toLowerCase();
|
|
2159
2783
|
const lowerRoot = normalizedRoot.toLowerCase();
|
|
2160
2784
|
if (normalizedRoot && (lowerPath === lowerRoot || lowerPath.startsWith(lowerRoot + "/"))) return path.slice(normalizedRoot.length).replace(/^\/+/, "");
|
|
2161
|
-
return path
|
|
2785
|
+
return path;
|
|
2162
2786
|
}
|
|
2163
2787
|
/** 目标行/列(0-based LSP → 1-based Monaco)。 */
|
|
2164
2788
|
function targetMonoPosition(loc) {
|
|
2165
|
-
const
|
|
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 ?? {};
|
|
2166
2795
|
return {
|
|
2167
|
-
line: Math.max(1,
|
|
2168
|
-
column: Math.max(1,
|
|
2796
|
+
line: Math.max(1, range.startLineNumber ?? 1),
|
|
2797
|
+
column: Math.max(1, range.startColumn ?? 1)
|
|
2169
2798
|
};
|
|
2170
2799
|
}
|
|
2171
2800
|
//#endregion
|
|
@@ -2178,6 +2807,10 @@ window.__ModuleLoader__.load({
|
|
|
2178
2807
|
* 作者 ddj 2026-08-27
|
|
2179
2808
|
*/
|
|
2180
2809
|
const LSP_LANGS = ["lua", "csharp"];
|
|
2810
|
+
const SEMANTIC_LEGEND = {
|
|
2811
|
+
tokenTypes: [...LSP_SEMANTIC_TOKEN_TYPES],
|
|
2812
|
+
tokenModifiers: [...LSP_SEMANTIC_TOKEN_MODIFIERS]
|
|
2813
|
+
};
|
|
2181
2814
|
let registered = false;
|
|
2182
2815
|
const disposables = [];
|
|
2183
2816
|
let overlayEl = null;
|
|
@@ -2212,6 +2845,19 @@ window.__ModuleLoader__.load({
|
|
|
2212
2845
|
};
|
|
2213
2846
|
disposables.push(monaco.editor.onDidCreateModel(attachModel));
|
|
2214
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));
|
|
2215
2861
|
disposables.push(monaco.languages.registerDefinitionProvider(LSP_LANGS, { provideDefinition: (model, position, token) => {
|
|
2216
2862
|
const path = pathOfModel(model);
|
|
2217
2863
|
if (!path) return [];
|
|
@@ -2234,7 +2880,17 @@ window.__ModuleLoader__.load({
|
|
|
2234
2880
|
contents: hover.contents.map((text) => ({ value: text })),
|
|
2235
2881
|
range: range || void 0
|
|
2236
2882
|
};
|
|
2237
|
-
} })
|
|
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
|
+
}));
|
|
2238
2894
|
}
|
|
2239
2895
|
/** LSP SymbolInfo(host 归一化后)→ Monaco DocumentSymbol。 */
|
|
2240
2896
|
function toMonacoSymbol(symbol) {
|
|
@@ -2623,7 +3279,8 @@ window.__ModuleLoader__.load({
|
|
|
2623
3279
|
}));
|
|
2624
3280
|
setStatus("已加载");
|
|
2625
3281
|
} else {
|
|
2626
|
-
const
|
|
3282
|
+
const base = res?.error ? String(res.error) : "读取失败";
|
|
3283
|
+
const message = res?.resolvedPath ? base + ":" + String(res.resolvedPath) : base;
|
|
2627
3284
|
setLoadError(message);
|
|
2628
3285
|
setError(message);
|
|
2629
3286
|
setStatus("读取失败");
|
|
@@ -2890,6 +3547,20 @@ window.__ModuleLoader__.load({
|
|
|
2890
3547
|
alive = false;
|
|
2891
3548
|
};
|
|
2892
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]);
|
|
2893
3564
|
monacoRef.current = monaco;
|
|
2894
3565
|
const getModel = (path, text) => {
|
|
2895
3566
|
const cache = modelsRef.current;
|
|
@@ -3003,7 +3674,7 @@ window.__ModuleLoader__.load({
|
|
|
3003
3674
|
const ed = m.editor.create(node, {
|
|
3004
3675
|
value: "",
|
|
3005
3676
|
language: "plaintext",
|
|
3006
|
-
theme:
|
|
3677
|
+
theme: themeNameOf(),
|
|
3007
3678
|
fontFamily: "var(--ds-font-family-code, ui-monospace, monospace)",
|
|
3008
3679
|
fontSize: 13,
|
|
3009
3680
|
lineHeight: 20,
|
|
@@ -4230,7 +4901,7 @@ window.__ModuleLoader__.load({
|
|
|
4230
4901
|
}
|
|
4231
4902
|
//#endregion
|
|
4232
4903
|
//#region \0dsh-css:/home/runner/work/DSH_VsCodeMode/DSH_VsCodeMode/src/client/styles/mcp.css.mjs
|
|
4233
|
-
const css = "/* dsh-vscode-mode MCP 管理界面样式。\n 全部使用 DSH 真实主题变量(--dsw-alias-*),不再用不存在的 fg-* 变量。 */\n.vsm-mcp-page{display:flex;flex-direction:column;gap:18px;width:100%;max-width:920px;box-sizing:border-box;color:var(--dsw-alias-label-primary,#e6e6e6);padding-bottom:32px}\n\n/* 顶部:标题 + 副标题 + 添加按钮 */\n.vsm-mcp-header{display:flex;align-items:flex-start;justify-content:space-between;gap:20px}\n.vsm-mcp-header h2{margin:0 0 4px;font-size:20px;font-weight:600;line-height:1.35;color:var(--dsw-alias-label-primary,#e6e6e6)}\n.vsm-mcp-header p{margin:0;color:var(--dsw-alias-label-tertiary,#9aa5b1);font-size:13px;line-height:1.5}\n.vsm-primary{border:0;border-radius:6px;padding:8px 16px;background:var(--dsw-alias-button-primary-fill,#6d55ff);color:var(--dsw-alias-button-contrast-fill,#fff);font-size:13px;font-weight:500;cursor:pointer;white-space:nowrap}\n.vsm-primary:hover{background:var(--dsw-alias-button-primary-hover,#5a46e6)}\n.vsm-primary:disabled{opacity:.55;cursor:default}\n\n/* 子 Tab:我的 MCP / MCP 市场 */\n.vsm-mcp-tabs{display:flex;gap:24px;border-bottom:1px solid var(--dsw-alias-border-l2,#3a3a3a)}\n.vsm-mcp-tabs button{border:0;background:none;color:var(--dsw-alias-label-tertiary,#9aa5b1);padding:9px 2px 11px;cursor:pointer;font-size:14px;position:relative}\n.vsm-mcp-tabs button:hover{color:var(--dsw-alias-label-secondary,#c8c8c8)}\n.vsm-mcp-tabs button.active{color:var(--dsw-alias-label-primary,#fff);font-weight:500}\n.vsm-mcp-tabs button.active::after{content:\"\";position:absolute;left:0;right:0;bottom:-1px;height:2px;background:var(--dsw-alias-brand-primary,#6d55ff);border-radius:2px}\n\n/* 通用设置 */\n.vsm-general-page{display:flex;flex-direction:column;gap:10px;max-width:720px}\n.vsm-general-page h2{margin:0;font-size:20px;color:var(--dsw-alias-label-primary,#e6e6e6)}\n.vsm-general-page p{margin:0 0 8px;color:var(--dsw-alias-label-tertiary,#9aa5b1);font-size:13px}\n.vsm-general-row{display:flex;align-items:center;justify-content:space-between;gap:20px;padding:14px 0;border-bottom:1px solid var(--dsw-alias-border-l1,#333);color:var(--dsw-alias-label-primary,#eee);font-size:14px}\n.vsm-general-row select{min-width:220px;padding:8px 10px;border:1px solid var(--dsw-alias-border-l2,#555);border-radius:6px;background:var(--dsw-alias-bg-layer-1,#2b2b2b);color:var(--dsw-alias-label-primary,#eee);font:inherit}\n.vsm-general-row small{color:var(--dsw-alias-label-tertiary,#9aa5b1)}\n\n/* 服务卡片 */\n.vsm-mcp-card{border:1px solid var(--dsw-alias-border-l2,#3a3a3a);border-radius:10px;padding:14px 16px;margin-bottom:10px;background:var(--dsw-alias-bg-layer-2,transparent);transition:border-color .15s}\n.vsm-mcp-card:hover{border-color:var(--dsw-alias-border-l3,#555)}\n.vsm-mcp-card-head{display:flex;justify-content:space-between;align-items:center;gap:12px}\n.vsm-mcp-title{display:flex;align-items:center;gap:9px;font-weight:600;font-size:14px;color:var(--dsw-alias-label-primary,#e6e6e6);min-width:0}\n.vsm-mcp-source{font-size:11px;font-weight:400;padding:2px 7px;border-radius:4px;background:var(--dsw-alias-bg-mask-2,#ffffff22);color:var(--dsw-alias-label-tertiary,#9aa5b1);flex-shrink:0}\n.vsm-mcp-dot{width:8px;height:8px;border-radius:50%;flex-shrink:0;background:var(--dsw-alias-state-error-primary,#e05252)}\n.vsm-mcp-dot.connected{background:var(--dsw-alias-state-success-primary,#38bd75)}\n.vsm-mcp-dot.connecting{background:var(--dsw-alias-state-warn-primary,#e5b93f)}\n.vsm-mcp-dot.disabled{background:var(--dsw-alias-label-tertiary,#9aa5b1)}\n.vsm-mcp-actions{display:flex;align-items:center;gap:4px;flex-shrink:0}\n.vsm-mcp-actions button{border:0;background:none;color:var(--dsw-alias-label-tertiary,#9aa5b1);cursor:pointer;font-size:15px;width:28px;height:28px;border-radius:6px;line-height:1}\n.vsm-mcp-actions button:hover{color:var(--dsw-alias-label-primary,#fff);background:var(--dsw-alias-interactive-bg-hover,#ffffff14)}\n.vsm-mcp-actions button.vsm-danger:hover{color:var(--dsw-alias-state-error-primary,#f07878);background:var(--dsw-alias-interactive-bg-hover-danger,#ff000014)}\n.vsm-switch{font-size:14px !important}\n.vsm-switch.on{color:var(--dsw-alias-state-success-primary,#38bd75)}\n.vsm-mcp-meta{font-size:12px;color:var(--dsw-alias-label-tertiary,#9aa5b1);margin:9px 0}\n.vsm-mcp-error{color:var(--dsw-alias-state-error-primary,#f07878);font-size:12px;margin:8px 0}\n.vsm-mcp-banner{padding:9px 12px;border:1px solid var(--dsw-alias-state-error-secondary,#d9534f55);border-radius:6px;background:var(--dsw-alias-state-error-secondary,#d9534f1a);margin-bottom:10px}\n\n/* 工具 chips */\n.vsm-mcp-tools{display:flex;flex-wrap:wrap;gap:6px}\n.vsm-mcp-chip{font:12px var(--ds-font-family-code,ui-monospace,monospace);padding:4px 8px;border-radius:5px;background:var(--dsw-alias-bg-mask-2,#ffffff14);color:var(--dsw-alias-label-secondary,#c8c8c8);border:1px solid var(--dsw-alias-border-l1,#333)}\n\n/* 空态 / 加载态 */\n.vsm-mcp-empty{text-align:center;padding:56px 20px;color:var(--dsw-alias-label-tertiary,#9aa5b1);border:1px dashed var(--dsw-alias-border-l2,#3a3a3a);border-radius:8px;font-size:13px}\n\n/* 项目搜索下拉框 */\n.vsm-project-picker{position:relative;width:100%;z-index:4}\n.vsm-project-picker-trigger{width:100%;display:flex;align-items:center;justify-content:space-between;gap:12px;padding:10px 12px;border:1px solid var(--dsw-alias-border-l2,#3a3a3a);border-radius:8px;background:var(--dsw-alias-bg-layer-1,transparent);color:var(--dsw-alias-label-primary,#e6e6e6);cursor:pointer;text-align:left}\n.vsm-project-picker-trigger:hover{border-color:var(--dsw-alias-brand-primary,#6d55ff)}\n.vsm-project-picker-label{display:flex;flex-direction:column;min-width:0;font-size:13px;font-weight:500}\n.vsm-project-picker-label small{margin-top:3px;color:var(--dsw-alias-label-tertiary,#9aa5b1);font:11px var(--ds-font-family-code,ui-monospace,monospace);overflow:hidden;text-overflow:ellipsis;white-space:nowrap}\n.vsm-project-picker-arrow{color:var(--dsw-alias-label-tertiary,#9aa5b1);font-size:16px}\n.vsm-project-picker-menu{position:absolute;top:calc(100% + 6px);left:0;right:0;padding:8px;border:1px solid var(--dsw-alias-border-l2,#3a3a3a);border-radius:9px;background:var(--dsw-alias-bg-overlay,#202020);box-shadow:0 12px 36px #0008}\n.vsm-project-picker-search{width:100%;box-sizing:border-box;padding:8px 10px;border:1px solid var(--dsw-alias-border-l2,#555);border-radius:6px;background:var(--dsw-alias-bg-layer-1,#2b2b2b);color:var(--dsw-alias-label-primary,#eee);outline:none}\n.vsm-project-picker-search:focus{border-color:var(--dsw-alias-brand-primary,#6d55ff)}\n.vsm-project-picker-options{max-height:260px;overflow:auto;margin-top:6px}\n.vsm-project-option{display:flex;width:100%;flex-direction:column;gap:3px;padding:8px 9px;border:0;border-radius:6px;background:none;color:var(--dsw-alias-label-primary,#eee);cursor:pointer;text-align:left}\n.vsm-project-option:hover,.vsm-project-option.highlight{background:var(--dsw-alias-interactive-bg-hover,#ffffff14)}\n.vsm-project-option.selected{color:var(--dsw-alias-brand-primary,#6d55ff)}\n.vsm-project-option small{overflow:hidden;color:var(--dsw-alias-label-tertiary,#9aa5b1);font:11px var(--ds-font-family-code,ui-monospace,monospace);text-overflow:ellipsis;white-space:nowrap}\n.vsm-project-picker-empty{padding:18px 8px;color:var(--dsw-alias-label-tertiary,#9aa5b1);font-size:12px;text-align:center}\n\n/* 项目级 MCP 分组 */\n.vsm-project{border:1px solid var(--dsw-alias-border-l2,#3a3a3a);border-radius:12px;margin-bottom:14px;overflow:hidden;background:var(--dsw-alias-bg-layer-1,transparent)}\n.vsm-project-head{display:flex;align-items:center;justify-content:space-between;gap:12px;padding:12px 16px;border-bottom:1px solid var(--dsw-alias-border-l1,#333);background:var(--dsw-alias-bg-mask-1,#ffffff0a)}\n.vsm-project-title{display:flex;align-items:center;gap:10px;min-width:0}\n.vsm-project-icon{color:var(--dsw-alias-brand-primary,#6d55ff);font-size:15px;flex-shrink:0}\n.vsm-project-name{font-size:14px;font-weight:600;color:var(--dsw-alias-label-primary,#e6e6e6);display:block}\n.vsm-project-path{font-size:11px;color:var(--dsw-alias-label-tertiary,#9aa5b1);font-family:var(--ds-font-family-code,ui-monospace,monospace);display:block;margin-top:2px;word-break:break-all}\n.vsm-primary.vsm-small{padding:5px 12px;font-size:12px}\n.vsm-project-empty{text-align:center;padding:26px 16px;color:var(--dsw-alias-label-tertiary,#9aa5b1);font-size:12px}\n.vsm-project .vsm-mcp-card{border:0;border-radius:0;margin:0;border-bottom:1px solid var(--dsw-alias-border-l1,#333);background:transparent}\n.vsm-project .vsm-mcp-card:last-child{border-bottom:0}\n.vsm-project .vsm-mcp-banner{border-radius:0;margin:0}\n\n/* 新增 MCP 弹窗 */\n.vsm-mcp-modal{position:fixed;inset:0;background:var(--dsw-alias-bg-mask-drop,#000000aa);display:flex;align-items:center;justify-content:center;z-index:50}\n.vsm-mcp-dialog{width:min(560px,calc(100vw - 40px));max-height:88vh;overflow:auto;padding:22px 24px;border-radius:12px;background:var(--dsw-alias-bg-overlay,#202020);border:1px solid var(--dsw-alias-border-l2,#444);box-shadow:0 16px 60px #000c;color:var(--dsw-alias-label-primary,#e6e6e6)}\n.vsm-mcp-dialog h3{margin:0 0 14px;font-size:16px;font-weight:600}\n.vsm-mcp-dialog label{display:flex;flex-direction:column;gap:6px;margin:12px 0;font-size:12px;color:var(--dsw-alias-label-secondary,#aaa)}\n.vsm-mcp-dialog input,.vsm-mcp-dialog select,.vsm-mcp-dialog textarea{font:inherit;color:var(--dsw-alias-label-primary,#eee);background:var(--dsw-alias-bg-layer-1,#2b2b2b);border:1px solid var(--dsw-alias-border-l2,#555);border-radius:6px;padding:8px 10px}\n.vsm-mcp-dialog input:focus,.vsm-mcp-dialog select:focus,.vsm-mcp-dialog textarea:focus{outline:none;border-color:var(--dsw-alias-brand-primary,#6d55ff)}\n.vsm-mcp-dialog textarea{min-height:64px;resize:vertical;font-family:var(--ds-font-family-code,ui-monospace,monospace)}\n.vsm-mcp-dialog-actions{display:flex;justify-content:flex-end;gap:10px;margin-top:20px}\n.vsm-mcp-dialog-actions button{padding:8px 14px;border:1px solid var(--dsw-alias-border-l2,#555);border-radius:6px;background:none;color:var(--dsw-alias-label-primary,#eee);cursor:pointer;font-size:13px}\n.vsm-mcp-dialog-actions button:hover{background:var(--dsw-alias-interactive-bg-hover,#ffffff14)}\n\n/* 兼容性小节 */\n.vsm-compat-h3{margin:16px 0 2px;font-size:14px;font-weight:600;color:var(--dsw-alias-label-secondary,#c8c8c8)}\n.vsm-compat-item{display:flex;align-items:baseline;justify-content:space-between;gap:16px;padding:10px 0;border-bottom:1px solid var(--dsw-alias-border-l1,#333);font-size:13px;color:var(--dsw-alias-label-primary,#eee)}\n.vsm-compat-item.ok .vsm-compat-name::before{content:\"● \";color:var(--dsw-alias-state-success-primary,#38bd75)}\n.vsm-compat-item.warn .vsm-compat-name::before{content:\"● \";color:var(--dsw-alias-state-warn-primary,#e5b93f)}\n.vsm-compat-name{font-weight:500;min-width:0}\n.vsm-compat-note{color:var(--dsw-alias-label-tertiary,#9aa5b1);font-size:12px;text-align:right;min-width:0}\n\n/* 开发形态开关 */\n.vsm-devform-note{display:block;margin-top:4px;font-size:11px;color:var(--dsw-alias-label-tertiary,#9aa5b1);word-break:break-all}\n.vsm-devform-actions{display:flex;flex-direction:column;align-items:flex-end;gap:6px;min-width:0}\n.vsm-devform-msg{color:var(--dsw-alias-state-warn-primary,#e5b93f);font-size:11px;text-align:right}\n.vsm-general-row button{border:1px solid var(--dsw-alias-border-l2,#555);border-radius:6px;padding:6px 12px;background:none;color:var(--dsw-alias-label-primary,#eee);cursor:pointer;font-size:12px;white-space:nowrap}\n.vsm-general-row button:hover:not(:disabled){background:var(--dsw-alias-interactive-bg-hover,#ffffff14)}\n.vsm-general-row button:disabled{opacity:.55;cursor:default}\n.vsm-general-row button.vsm-devform-close{color:var(--dsw-alias-state-error-primary,#f07878);border-color:var(--dsw-alias-state-error-secondary,#d9534f55)}\n\n/* keyboard shortcuts page */\n.vsm-kb-list{display:flex;flex-direction:column;gap:6px;margin-top:8px}\n.vsm-kb-row{display:flex;align-items:center;gap:10px;padding:8px 10px;border:1px solid var(--dsw-alias-border-l2,#555);border-radius:8px;background:var(--dsw-alias-bg-layer-1,#161616)}\n.vsm-kb-row.rec{border-color:var(--dsw-alias-brand-primary,#4f8cff)}\n.vsm-kb-label{flex:1;min-width:0;display:flex;flex-direction:column;gap:2px;color:var(--dsw-alias-label-primary,#eee);font-size:13px}\n.vsm-kb-warn{color:var(--dsw-alias-state-warn-primary,#e5b93f);font-size:11px}\n.vsm-kb-key{min-width:96px;text-align:center;padding:3px 10px;border:1px solid var(--dsw-alias-border-l2,#555);border-radius:6px;background:var(--dsw-alias-bg-base,#111);color:var(--dsw-alias-label-primary,#eee);font-size:12px;font-family:var(--ds-font-family-code,ui-monospace,monospace)}\n.vsm-kb-key.warn{border-color:var(--dsw-alias-state-warn-primary,#e5b93f);color:var(--dsw-alias-state-warn-primary,#e5b93f)}\n.vsm-kb-key.invalid{border-color:var(--dsw-alias-state-error-primary,#d9534f);color:var(--dsw-alias-state-error-primary,#d9534f)}\n.vsm-kb-rec{font-size:12px;color:var(--dsw-alias-label-secondary,#9aa5b1)}\n.vsm-kb-actions{display:flex;gap:6px}\n.vsm-kb-actions button{border:1px solid var(--dsw-alias-border-l2,#555);border-radius:6px;padding:4px 10px;background:none;color:var(--dsw-alias-label-primary,#eee);cursor:pointer;font-size:12px;white-space:nowrap}\n.vsm-kb-actions button:hover:not(:disabled){background:var(--dsw-alias-interactive-bg-hover,#ffffff14)}\n.vsm-kb-actions button:disabled{opacity:.55;cursor:default}\n.vsm-kb-actionsbar{display:flex;align-items:center;gap:10px;margin-top:12px}\n.vsm-kb-dirty{flex:1;font-size:12px;color:var(--dsw-alias-state-warn-primary,#e5b93f)}\n.vsm-kb-message{font-size:12px;color:var(--dsw-alias-brand-primary,#4f8cff);margin-top:8px}\n\n/* 语言服务器(LSP)设置子 Tab */\n.vsm-lsp-form{display:flex;flex-direction:column;gap:6px;margin-top:8px}\n.vsm-lsp-form label{display:flex;flex-direction:column;gap:4px;font-size:12px;color:var(--dsw-alias-label-secondary,#9aa5b1)}\n.vsm-lsp-form input{height:26px;padding:0 8px;border:1px solid var(--dsw-alias-border-l2,#555);border-radius:6px;background:var(--dsw-alias-bg-base,#111);color:var(--dsw-alias-label-primary,#eee);font-size:12px;outline:none}\n.vsm-lsp-form input:focus{border-color:var(--dsw-alias-brand-primary,#4f8cff)}\n.vsm-lsp-form button{align-self:flex-start;margin-top:2px}\n.vsm-lsp-hint{font-size:12px;color:var(--dsw-alias-label-tertiary,#9aa5b1);margin-top:10px;line-height:1.6}\n\n/* 会话性能(PerfSettings 子 Tab) */\n.vsm-perf-page{display:flex;flex-direction:column;gap:14px;max-width:920px}\n.vsm-perf-page h2{margin:0;font-size:20px;color:var(--dsw-alias-label-primary,#e6e6e6)}\n.vsm-perf-page p{margin:0;color:var(--dsw-alias-label-tertiary,#9aa5b1);font-size:13px;line-height:1.5}\n.vsm-perf-cards{display:flex;gap:12px;flex-wrap:wrap}\n.vsm-perf-card{display:flex;flex-direction:column;gap:4px;min-width:120px;padding:12px 16px;border:1px solid var(--dsw-alias-border-l2,#3a3a3a);border-radius:10px;background:var(--dsw-alias-bg-layer-2,transparent)}\n.vsm-perf-card span{font-size:12px;color:var(--dsw-alias-label-tertiary,#9aa5b1)}\n.vsm-perf-card b{font-size:20px;color:var(--dsw-alias-label-primary,#e6e6e6)}\n.vsm-perf-card b.big{color:var(--dsw-alias-state-error-primary,#e05252)}\n.vsm-perf-toolbar{display:flex;align-items:center;gap:8px;flex-wrap:wrap}\n.vsm-perf-selcount{font-size:12px;color:var(--dsw-alias-label-tertiary,#9aa5b1);margin:0 4px}\n.vsm-perf-ws{border:1px solid var(--dsw-alias-border-l2,#3a3a3a);border-radius:10px;overflow:hidden}\n.vsm-perf-ws-head{display:flex;justify-content:space-between;align-items:center;gap:12px;padding:10px 14px;background:var(--dsw-alias-bg-layer-1,#262626);border-bottom:1px solid var(--dsw-alias-border-l2,#3a3a3a)}\n.vsm-perf-ws-name{font-size:13px;font-weight:600;color:var(--dsw-alias-label-primary,#e6e6e6);font-family:var(--ds-font-family-code,ui-monospace,monospace);overflow:hidden;text-overflow:ellipsis;white-space:nowrap}\n.vsm-perf-ws-meta{font-size:12px;color:var(--dsw-alias-label-tertiary,#9aa5b1);flex-shrink:0}\n.vsm-perf-rows{display:flex;flex-direction:column}\n.vsm-perf-row{display:flex;align-items:center;gap:10px;padding:7px 14px;border-bottom:1px solid var(--dsw-alias-border-l1,#333);font-size:12px;color:var(--dsw-alias-label-secondary,#c8c8c8)}\n.vsm-perf-row:last-child{border-bottom:0}\n.vsm-perf-row:hover{background:var(--dsw-alias-interactive-bg-hover,#ffffff0d)}\n.vsm-perf-row input[type=checkbox]{accent-color:var(--dsw-alias-brand-primary,#6d55ff);cursor:pointer}\n.vsm-perf-cell{min-width:0}\n.vsm-perf-id{flex:1;overflow:hidden;text-overflow:ellipsis;white-space:nowrap;font-family:var(--ds-font-family-code,ui-monospace,monospace);color:var(--dsw-alias-label-primary,#eee)}\n.vsm-perf-size{width:70px;text-align:right;flex-shrink:0;color:var(--dsw-alias-label-secondary,#c8c8c8)}\n.vsm-perf-size.big{color:var(--dsw-alias-state-error-primary,#e05252);font-weight:600}\n.vsm-perf-mtime{width:44px;flex-shrink:0;color:var(--dsw-alias-label-tertiary,#9aa5b1)}\n.vsm-perf-active{width:44px;flex-shrink:0;font-size:11px;color:var(--dsw-alias-state-warn-primary,#e5b93f);text-align:right}\n.vsm-perf-moved{border:1px solid var(--dsw-alias-border-l2,#3a3a3a);border-radius:10px;padding:12px 14px;background:var(--dsw-alias-bg-layer-2,transparent)}\n.vsm-perf-moved h3{margin:0 0 8px;font-size:14px;color:var(--dsw-alias-label-primary,#e6e6e6)}\n.vsm-perf-config{border:1px solid var(--dsw-alias-border-l2,#3a3a3a);border-radius:10px;padding:12px 14px;background:var(--dsw-alias-bg-layer-2,transparent)}\n.vsm-perf-config h3{margin:0 0 8px;font-size:14px;color:var(--dsw-alias-label-primary,#e6e6e6)}\n.vsm-perf-config-state{display:flex;align-items:center;gap:12px;margin:10px 0}\n.vsm-perf-note{font-size:12px;color:var(--dsw-alias-state-warn-primary,#e5b93f)}\n.vsm-perf-block{margin:8px 0 0;padding:10px 12px;border:1px solid var(--dsw-alias-border-l2,#3a3a3a);border-radius:8px;background:var(--dsw-alias-bg-base,#111);color:var(--dsw-alias-label-secondary,#c8c8c8);font-size:11px;line-height:1.5;font-family:var(--ds-font-family-code,ui-monospace,monospace);white-space:pre-wrap;overflow-x:auto;max-width:100%}\n.vsm-perf-guide{border:1px solid var(--dsw-alias-border-l2,#3a3a3a);border-radius:10px;padding:12px 14px;background:var(--dsw-alias-bg-layer-2,transparent)}\n.vsm-perf-guide h3{margin:0 0 8px;font-size:14px;color:var(--dsw-alias-label-primary,#e6e6e6)}\n.vsm-perf-modal{position:fixed;inset:0;display:flex;align-items:center;justify-content:center;background:var(--dsw-alias-bg-mask-1,#000000aa);z-index:1000;padding:20px}\n.vsm-perf-modal-card{width:min(560px,92vw);max-height:72vh;display:flex;flex-direction:column;gap:12px;padding:18px 20px;border:1px solid var(--dsw-alias-border-l2,#3a3a3a);border-radius:12px;background:var(--dsw-alias-bg-layer-2,#1f1f1f);color:var(--dsw-alias-label-primary,#e6e6e6)}\n.vsm-perf-modal-card h3{margin:0;font-size:16px}\n.vsm-perf-modal-note{font-size:12px}\n.vsm-perf-modal-list{list-style:none;margin:0;padding:0;overflow-y:auto;font-size:12px;color:var(--dsw-alias-label-secondary,#c8c8c8);display:flex;flex-direction:column;gap:4px}\n.vsm-perf-modal-list li{padding:4px 6px;border-radius:6px;background:var(--dsw-alias-bg-layer-1,#262626);overflow:hidden;text-overflow:ellipsis;white-space:nowrap}\n.vsm-perf-modal-more{font-size:12px;color:var(--dsw-alias-label-tertiary,#9aa5b1)}\n.vsm-perf-modal-actions{display:flex;justify-content:flex-end;gap:10px;margin-top:4px}\n.vsm-secondary{border:1px solid var(--dsw-alias-border-l2,#555);border-radius:6px;padding:8px 16px;background:none;color:var(--dsw-alias-label-primary,#eee);font-size:13px;cursor:pointer;white-space:nowrap}\n.vsm-secondary:hover:not(:disabled){background:var(--dsw-alias-interactive-bg-hover,#ffffff14)}\n.vsm-secondary:disabled{opacity:.55;cursor:default}\n.vsm-small{padding:4px 10px;font-size:12px}\n\n/* 扩展管理(LSP 语言服务器) */\n.vsm-mcp-actions button.vsm-primary,\n.vsm-mcp-actions button.vsm-danger{width:auto;height:auto;padding:5px 12px;font-size:12px;border-radius:6px;line-height:1.4}\n.vsm-mcp-actions button.vsm-danger{color:var(--dsw-alias-state-error-primary,#f07878);background:none}\n.vsm-mcp-actions button.vsm-danger:hover{background:var(--dsw-alias-interactive-bg-hover-danger,#ff000014)}\n.vsm-ext-ver{font-weight:400;font-size:12px;color:var(--dsw-alias-label-tertiary,#9aa5b1);white-space:nowrap;overflow:hidden;text-overflow:ellipsis}\n";
|
|
4904
|
+
const css = "/* dsh-vscode-mode MCP 管理界面样式。\n 全部使用 DSH 真实主题变量(--dsw-alias-*),不再用不存在的 fg-* 变量。 */\n.vsm-mcp-page{display:flex;flex-direction:column;gap:18px;width:100%;max-width:920px;box-sizing:border-box;color:var(--dsw-alias-label-primary,#e6e6e6);padding-bottom:32px}\n\n/* 顶部:标题 + 副标题 + 添加按钮 */\n.vsm-mcp-header{display:flex;align-items:flex-start;justify-content:space-between;gap:20px}\n.vsm-mcp-header h2{margin:0 0 4px;font-size:20px;font-weight:600;line-height:1.35;color:var(--dsw-alias-label-primary,#e6e6e6)}\n.vsm-mcp-header p{margin:0;color:var(--dsw-alias-label-tertiary,#9aa5b1);font-size:13px;line-height:1.5}\n.vsm-primary{border:0;border-radius:6px;padding:8px 16px;background:var(--dsw-alias-button-primary-fill,#6d55ff);color:var(--dsw-alias-button-contrast-fill,#fff);font-size:13px;font-weight:500;cursor:pointer;white-space:nowrap}\n.vsm-primary:hover{background:var(--dsw-alias-button-primary-hover,#5a46e6)}\n.vsm-primary:disabled{opacity:.55;cursor:default}\n\n/* MCP 管理下的二级子页签:我的 MCP / 项目 MCP / MCP 市场 */\n.vsm-mcp-subhead{display:flex;align-items:center;justify-content:space-between;gap:16px;margin-top:-4px}\n.vsm-mcp-subtabs{display:flex;gap:18px;border-bottom:1px solid var(--dsw-alias-border-l1,#2e2e2e);flex:1}\n.vsm-mcp-subtabs button{border:0;background:none;color:var(--dsw-alias-label-tertiary,#9aa5b1);padding:7px 2px 9px;cursor:pointer;font-size:13px;position:relative}\n.vsm-mcp-subtabs button:hover{color:var(--dsw-alias-label-secondary,#c8c8c8)}\n.vsm-mcp-subtabs button.active{color:var(--dsw-alias-label-primary,#fff);font-weight:500}\n.vsm-mcp-subtabs button.active::after{content:\"\";position:absolute;left:0;right:0;bottom:-1px;height:2px;background:var(--dsw-alias-brand-primary,#6d55ff);border-radius:2px}\n\n/* 顶层 Tab:通用 / 快捷键 / MCP 管理 / 语言服务器 / 性能优化 / 兼容性 */\n.vsm-mcp-tabs{display:flex;gap:24px;border-bottom:1px solid var(--dsw-alias-border-l2,#3a3a3a)}\n.vsm-mcp-tabs button{border:0;background:none;color:var(--dsw-alias-label-tertiary,#9aa5b1);padding:9px 2px 11px;cursor:pointer;font-size:14px;position:relative}\n.vsm-mcp-tabs button:hover{color:var(--dsw-alias-label-secondary,#c8c8c8)}\n.vsm-mcp-tabs button.active{color:var(--dsw-alias-label-primary,#fff);font-weight:500}\n.vsm-mcp-tabs button.active::after{content:\"\";position:absolute;left:0;right:0;bottom:-1px;height:2px;background:var(--dsw-alias-brand-primary,#6d55ff);border-radius:2px}\n\n/* 统一线框面板:圆角矩形边框圈住各设置分区,提升可读性。\n 用法:<section class=\"vsm-panel\"> + 可选 <h3 class=\"vsm-panel-title\">。 */\n.vsm-panel{border:1px solid var(--dsw-alias-border-l2,#3a3a3a);border-radius:12px;background:var(--dsw-alias-bg-layer-2,transparent);overflow:hidden}\n.vsm-panel-pad{padding:14px 16px}\n.vsm-panel-title{margin:0;padding:10px 16px;font-size:13px;font-weight:600;color:var(--dsw-alias-label-secondary,#c8c8c8);background:var(--dsw-alias-bg-mask-1,#ffffff0a);border-bottom:1px solid var(--dsw-alias-border-l1,#333)}\n.vsm-panel-body{padding:6px 16px}\n.vsm-panel-body>*:first-child{margin-top:8px}\n.vsm-panel-body>*:last-child{margin-bottom:10px}\n\n/* 通用设置 */\n.vsm-general-page{display:flex;flex-direction:column;gap:14px;max-width:720px}\n.vsm-general-page h2{margin:0;font-size:20px;color:var(--dsw-alias-label-primary,#e6e6e6)}\n.vsm-general-page p{margin:0;color:var(--dsw-alias-label-tertiary,#9aa5b1);font-size:13px}\n.vsm-general-row{display:flex;align-items:center;justify-content:space-between;gap:20px;padding:14px 0;border-bottom:1px solid var(--dsw-alias-border-l1,#333);color:var(--dsw-alias-label-primary,#eee);font-size:14px}\n.vsm-general-row:last-child{border-bottom:0}\n.vsm-general-row select{min-width:220px;padding:8px 10px;border:1px solid var(--dsw-alias-border-l2,#555);border-radius:6px;background:var(--dsw-alias-bg-layer-1,#2b2b2b);color:var(--dsw-alias-label-primary,#eee);font:inherit}\n.vsm-general-row small{color:var(--dsw-alias-label-tertiary,#9aa5b1)}\n\n/* 服务卡片 */\n.vsm-mcp-card{border:1px solid var(--dsw-alias-border-l2,#3a3a3a);border-radius:10px;padding:14px 16px;margin-bottom:10px;background:var(--dsw-alias-bg-layer-2,transparent);transition:border-color .15s}\n.vsm-mcp-card:hover{border-color:var(--dsw-alias-border-l3,#555)}\n.vsm-mcp-card-head{display:flex;justify-content:space-between;align-items:center;gap:12px}\n.vsm-mcp-title{display:flex;align-items:center;gap:9px;font-weight:600;font-size:14px;color:var(--dsw-alias-label-primary,#e6e6e6);min-width:0}\n.vsm-mcp-source{font-size:11px;font-weight:400;padding:2px 7px;border-radius:4px;background:var(--dsw-alias-bg-mask-2,#ffffff22);color:var(--dsw-alias-label-tertiary,#9aa5b1);flex-shrink:0}\n.vsm-mcp-dot{width:8px;height:8px;border-radius:50%;flex-shrink:0;background:var(--dsw-alias-state-error-primary,#e05252)}\n.vsm-mcp-dot.connected{background:var(--dsw-alias-state-success-primary,#38bd75)}\n.vsm-mcp-dot.connecting{background:var(--dsw-alias-state-warn-primary,#e5b93f)}\n.vsm-mcp-dot.disabled{background:var(--dsw-alias-label-tertiary,#9aa5b1)}\n.vsm-mcp-actions{display:flex;align-items:center;gap:4px;flex-shrink:0}\n.vsm-mcp-actions button{border:0;background:none;color:var(--dsw-alias-label-tertiary,#9aa5b1);cursor:pointer;font-size:15px;width:28px;height:28px;border-radius:6px;line-height:1}\n.vsm-mcp-actions button:hover{color:var(--dsw-alias-label-primary,#fff);background:var(--dsw-alias-interactive-bg-hover,#ffffff14)}\n.vsm-mcp-actions button.vsm-danger:hover{color:var(--dsw-alias-state-error-primary,#f07878);background:var(--dsw-alias-interactive-bg-hover-danger,#ff000014)}\n.vsm-switch{font-size:14px !important}\n.vsm-switch.on{color:var(--dsw-alias-state-success-primary,#38bd75)}\n.vsm-mcp-meta{font-size:12px;color:var(--dsw-alias-label-tertiary,#9aa5b1);margin:9px 0}\n.vsm-mcp-error{color:var(--dsw-alias-state-error-primary,#f07878);font-size:12px;margin:8px 0}\n.vsm-mcp-banner{padding:9px 12px;border:1px solid var(--dsw-alias-state-error-secondary,#d9534f55);border-radius:6px;background:var(--dsw-alias-state-error-secondary,#d9534f1a);margin-bottom:10px}\n\n/* 工具 chips */\n.vsm-mcp-tools{display:flex;flex-wrap:wrap;gap:6px}\n.vsm-mcp-chip{font:12px var(--ds-font-family-code,ui-monospace,monospace);padding:4px 8px;border-radius:5px;background:var(--dsw-alias-bg-mask-2,#ffffff14);color:var(--dsw-alias-label-secondary,#c8c8c8);border:1px solid var(--dsw-alias-border-l1,#333)}\n\n/* 空态 / 加载态 */\n.vsm-mcp-empty{text-align:center;padding:56px 20px;color:var(--dsw-alias-label-tertiary,#9aa5b1);border:1px dashed var(--dsw-alias-border-l2,#3a3a3a);border-radius:8px;font-size:13px}\n\n/* 项目搜索下拉框 */\n.vsm-project-picker{position:relative;width:100%;z-index:4}\n.vsm-project-picker-trigger{width:100%;display:flex;align-items:center;justify-content:space-between;gap:12px;padding:10px 12px;border:1px solid var(--dsw-alias-border-l2,#3a3a3a);border-radius:8px;background:var(--dsw-alias-bg-layer-1,transparent);color:var(--dsw-alias-label-primary,#e6e6e6);cursor:pointer;text-align:left}\n.vsm-project-picker-trigger:hover{border-color:var(--dsw-alias-brand-primary,#6d55ff)}\n.vsm-project-picker-label{display:flex;flex-direction:column;min-width:0;font-size:13px;font-weight:500}\n.vsm-project-picker-label small{margin-top:3px;color:var(--dsw-alias-label-tertiary,#9aa5b1);font:11px var(--ds-font-family-code,ui-monospace,monospace);overflow:hidden;text-overflow:ellipsis;white-space:nowrap}\n.vsm-project-picker-arrow{color:var(--dsw-alias-label-tertiary,#9aa5b1);font-size:16px}\n.vsm-project-picker-menu{position:absolute;top:calc(100% + 6px);left:0;right:0;padding:8px;border:1px solid var(--dsw-alias-border-l2,#3a3a3a);border-radius:9px;background:var(--dsw-alias-bg-overlay,#202020);box-shadow:0 12px 36px #0008}\n.vsm-project-picker-search{width:100%;box-sizing:border-box;padding:8px 10px;border:1px solid var(--dsw-alias-border-l2,#555);border-radius:6px;background:var(--dsw-alias-bg-layer-1,#2b2b2b);color:var(--dsw-alias-label-primary,#eee);outline:none}\n.vsm-project-picker-search:focus{border-color:var(--dsw-alias-brand-primary,#6d55ff)}\n.vsm-project-picker-options{max-height:260px;overflow:auto;margin-top:6px}\n.vsm-project-option{display:flex;width:100%;flex-direction:column;gap:3px;padding:8px 9px;border:0;border-radius:6px;background:none;color:var(--dsw-alias-label-primary,#eee);cursor:pointer;text-align:left}\n.vsm-project-option:hover,.vsm-project-option.highlight{background:var(--dsw-alias-interactive-bg-hover,#ffffff14)}\n.vsm-project-option.selected{color:var(--dsw-alias-brand-primary,#6d55ff)}\n.vsm-project-option small{overflow:hidden;color:var(--dsw-alias-label-tertiary,#9aa5b1);font:11px var(--ds-font-family-code,ui-monospace,monospace);text-overflow:ellipsis;white-space:nowrap}\n.vsm-project-picker-empty{padding:18px 8px;color:var(--dsw-alias-label-tertiary,#9aa5b1);font-size:12px;text-align:center}\n\n/* 项目级 MCP 分组 */\n.vsm-project{border:1px solid var(--dsw-alias-border-l2,#3a3a3a);border-radius:12px;margin-bottom:14px;overflow:hidden;background:var(--dsw-alias-bg-layer-1,transparent)}\n.vsm-project-head{display:flex;align-items:center;justify-content:space-between;gap:12px;padding:12px 16px;border-bottom:1px solid var(--dsw-alias-border-l1,#333);background:var(--dsw-alias-bg-mask-1,#ffffff0a)}\n.vsm-project-title{display:flex;align-items:center;gap:10px;min-width:0}\n.vsm-project-icon{color:var(--dsw-alias-brand-primary,#6d55ff);font-size:15px;flex-shrink:0}\n.vsm-project-name{font-size:14px;font-weight:600;color:var(--dsw-alias-label-primary,#e6e6e6);display:block}\n.vsm-project-path{font-size:11px;color:var(--dsw-alias-label-tertiary,#9aa5b1);font-family:var(--ds-font-family-code,ui-monospace,monospace);display:block;margin-top:2px;word-break:break-all}\n.vsm-primary.vsm-small{padding:5px 12px;font-size:12px}\n.vsm-project-empty{text-align:center;padding:26px 16px;color:var(--dsw-alias-label-tertiary,#9aa5b1);font-size:12px}\n.vsm-project .vsm-mcp-card{border:0;border-radius:0;margin:0;border-bottom:1px solid var(--dsw-alias-border-l1,#333);background:transparent}\n.vsm-project .vsm-mcp-card:last-child{border-bottom:0}\n.vsm-project .vsm-mcp-banner{border-radius:0;margin:0}\n\n/* 新增 MCP 弹窗 */\n.vsm-mcp-modal{position:fixed;inset:0;background:var(--dsw-alias-bg-mask-drop,#000000aa);display:flex;align-items:center;justify-content:center;z-index:50}\n.vsm-mcp-dialog{width:min(560px,calc(100vw - 40px));max-height:88vh;overflow:auto;padding:22px 24px;border-radius:12px;background:var(--dsw-alias-bg-overlay,#202020);border:1px solid var(--dsw-alias-border-l2,#444);box-shadow:0 16px 60px #000c;color:var(--dsw-alias-label-primary,#e6e6e6)}\n.vsm-mcp-dialog h3{margin:0 0 14px;font-size:16px;font-weight:600}\n.vsm-mcp-dialog label{display:flex;flex-direction:column;gap:6px;margin:12px 0;font-size:12px;color:var(--dsw-alias-label-secondary,#aaa)}\n.vsm-mcp-dialog input,.vsm-mcp-dialog select,.vsm-mcp-dialog textarea{font:inherit;color:var(--dsw-alias-label-primary,#eee);background:var(--dsw-alias-bg-layer-1,#2b2b2b);border:1px solid var(--dsw-alias-border-l2,#555);border-radius:6px;padding:8px 10px}\n.vsm-mcp-dialog input:focus,.vsm-mcp-dialog select:focus,.vsm-mcp-dialog textarea:focus{outline:none;border-color:var(--dsw-alias-brand-primary,#6d55ff)}\n.vsm-mcp-dialog textarea{min-height:64px;resize:vertical;font-family:var(--ds-font-family-code,ui-monospace,monospace)}\n.vsm-mcp-dialog-actions{display:flex;justify-content:flex-end;gap:10px;margin-top:20px}\n.vsm-mcp-dialog-actions button{padding:8px 14px;border:1px solid var(--dsw-alias-border-l2,#555);border-radius:6px;background:none;color:var(--dsw-alias-label-primary,#eee);cursor:pointer;font-size:13px}\n.vsm-mcp-dialog-actions button:hover{background:var(--dsw-alias-interactive-bg-hover,#ffffff14)}\n\n/* 兼容性条目(面板标题改用 .vsm-panel-title) */\n.vsm-compat-item{display:flex;align-items:baseline;justify-content:space-between;gap:16px;padding:10px 0;border-bottom:1px solid var(--dsw-alias-border-l1,#333);font-size:13px;color:var(--dsw-alias-label-primary,#eee)}\n.vsm-compat-item.ok .vsm-compat-name::before{content:\"● \";color:var(--dsw-alias-state-success-primary,#38bd75)}\n.vsm-compat-item.warn .vsm-compat-name::before{content:\"● \";color:var(--dsw-alias-state-warn-primary,#e5b93f)}\n.vsm-compat-name{font-weight:500;min-width:0}\n.vsm-compat-note{color:var(--dsw-alias-label-tertiary,#9aa5b1);font-size:12px;text-align:right;min-width:0}\n\n/* 开发形态开关 */\n.vsm-devform-note{display:block;margin-top:4px;font-size:11px;color:var(--dsw-alias-label-tertiary,#9aa5b1);word-break:break-all}\n.vsm-devform-actions{display:flex;flex-direction:column;align-items:flex-end;gap:6px;min-width:0}\n.vsm-devform-msg{color:var(--dsw-alias-state-warn-primary,#e5b93f);font-size:11px;text-align:right}\n.vsm-general-row button{border:1px solid var(--dsw-alias-border-l2,#555);border-radius:6px;padding:6px 12px;background:none;color:var(--dsw-alias-label-primary,#eee);cursor:pointer;font-size:12px;white-space:nowrap}\n.vsm-general-row button:hover:not(:disabled){background:var(--dsw-alias-interactive-bg-hover,#ffffff14)}\n.vsm-general-row button:disabled{opacity:.55;cursor:default}\n.vsm-general-row button.vsm-devform-close{color:var(--dsw-alias-state-error-primary,#f07878);border-color:var(--dsw-alias-state-error-secondary,#d9534f55)}\n\n/* keyboard shortcuts page */\n.vsm-kb-list{display:flex;flex-direction:column;gap:6px;margin-top:8px}\n.vsm-kb-row{display:flex;align-items:center;gap:10px;padding:8px 10px;border:1px solid var(--dsw-alias-border-l2,#555);border-radius:8px;background:var(--dsw-alias-bg-layer-1,#161616)}\n.vsm-kb-row.rec{border-color:var(--dsw-alias-brand-primary,#4f8cff)}\n.vsm-kb-label{flex:1;min-width:0;display:flex;flex-direction:column;gap:2px;color:var(--dsw-alias-label-primary,#eee);font-size:13px}\n.vsm-kb-warn{color:var(--dsw-alias-state-warn-primary,#e5b93f);font-size:11px}\n.vsm-kb-key{min-width:96px;text-align:center;padding:3px 10px;border:1px solid var(--dsw-alias-border-l2,#555);border-radius:6px;background:var(--dsw-alias-bg-base,#111);color:var(--dsw-alias-label-primary,#eee);font-size:12px;font-family:var(--ds-font-family-code,ui-monospace,monospace)}\n.vsm-kb-key.warn{border-color:var(--dsw-alias-state-warn-primary,#e5b93f);color:var(--dsw-alias-state-warn-primary,#e5b93f)}\n.vsm-kb-key.invalid{border-color:var(--dsw-alias-state-error-primary,#d9534f);color:var(--dsw-alias-state-error-primary,#d9534f)}\n.vsm-kb-rec{font-size:12px;color:var(--dsw-alias-label-secondary,#9aa5b1)}\n.vsm-kb-actions{display:flex;gap:6px}\n.vsm-kb-actions button{border:1px solid var(--dsw-alias-border-l2,#555);border-radius:6px;padding:4px 10px;background:none;color:var(--dsw-alias-label-primary,#eee);cursor:pointer;font-size:12px;white-space:nowrap}\n.vsm-kb-actions button:hover:not(:disabled){background:var(--dsw-alias-interactive-bg-hover,#ffffff14)}\n.vsm-kb-actions button:disabled{opacity:.55;cursor:default}\n.vsm-kb-actionsbar{display:flex;align-items:center;gap:10px;margin-top:12px}\n.vsm-kb-dirty{flex:1;font-size:12px;color:var(--dsw-alias-state-warn-primary,#e5b93f)}\n.vsm-kb-message{font-size:12px;color:var(--dsw-alias-brand-primary,#4f8cff);margin-top:8px}\n\n/* 语言服务器(LSP)设置子 Tab */\n.vsm-lsp-form{display:flex;flex-direction:column;gap:6px;margin-top:8px}\n.vsm-lsp-form label{display:flex;flex-direction:column;gap:4px;font-size:12px;color:var(--dsw-alias-label-secondary,#9aa5b1)}\n.vsm-lsp-form input{height:26px;padding:0 8px;border:1px solid var(--dsw-alias-border-l2,#555);border-radius:6px;background:var(--dsw-alias-bg-base,#111);color:var(--dsw-alias-label-primary,#eee);font-size:12px;outline:none}\n.vsm-lsp-form input:focus{border-color:var(--dsw-alias-brand-primary,#4f8cff)}\n.vsm-lsp-form button{align-self:flex-start;margin-top:2px}\n.vsm-lsp-hint{font-size:12px;color:var(--dsw-alias-label-tertiary,#9aa5b1);margin-top:10px;line-height:1.6}\n\n/* 会话性能(PerfSettings 子 Tab) */\n.vsm-perf-page{display:flex;flex-direction:column;gap:14px;max-width:920px}\n.vsm-perf-page>.vsm-panel-body>*:first-child{margin-top:0}\n.vsm-perf-page h2{margin:0;font-size:20px;color:var(--dsw-alias-label-primary,#e6e6e6)}\n.vsm-perf-page p{margin:0;color:var(--dsw-alias-label-tertiary,#9aa5b1);font-size:13px;line-height:1.5}\n.vsm-perf-cards{display:flex;gap:12px;flex-wrap:wrap}\n.vsm-perf-card{display:flex;flex-direction:column;gap:4px;min-width:120px;padding:12px 16px;border:1px solid var(--dsw-alias-border-l2,#3a3a3a);border-radius:10px;background:var(--dsw-alias-bg-layer-2,transparent)}\n.vsm-perf-card span{font-size:12px;color:var(--dsw-alias-label-tertiary,#9aa5b1)}\n.vsm-perf-card b{font-size:20px;color:var(--dsw-alias-label-primary,#e6e6e6)}\n.vsm-perf-card b.big{color:var(--dsw-alias-state-error-primary,#e05252)}\n.vsm-perf-toolbar{display:flex;align-items:center;gap:8px;flex-wrap:wrap}\n.vsm-perf-selcount{font-size:12px;color:var(--dsw-alias-label-tertiary,#9aa5b1);margin:0 4px}\n/* 工作区列表分页器 */\n.vsm-perf-pager{display:flex;align-items:center;justify-content:space-between;gap:12px;flex-wrap:wrap;margin-top:4px}\n.vsm-perf-pager-text{font-size:12px;color:var(--dsw-alias-label-tertiary,#9aa5b1)}\n.vsm-perf-pager-actions{display:flex;gap:8px}\n.vsm-perf-ws-list{display:flex;flex-direction:column;gap:8px}\n.vsm-perf-ws{border:1px solid var(--dsw-alias-border-l2,#3a3a3a);border-radius:10px;overflow:hidden}\n.vsm-perf-ws-head{display:flex;align-items:center;gap:10px;padding:10px 14px;background:var(--dsw-alias-bg-layer-1,#262626);cursor:pointer;user-select:none}\n.vsm-perf-ws-head:hover{background:var(--dsw-alias-interactive-bg-hover,#ffffff14)}\n.vsm-perf-ws-head:focus-visible{outline:1px solid var(--dsw-alias-brand-primary,#6d55ff);outline-offset:-1px}\n.vsm-perf-ws.expanded .vsm-perf-ws-head{border-bottom:1px solid var(--dsw-alias-border-l2,#3a3a3a)}\n.vsm-perf-ws-caret{color:var(--dsw-alias-label-tertiary,#9aa5b1);font-size:12px;flex-shrink:0;transition:transform .15s;display:inline-block}\n.vsm-perf-ws-caret.open{transform:rotate(90deg)}\n.vsm-perf-ws .vsm-perf-ws-name{flex:1}\n.vsm-perf-ws-name{font-size:13px;font-weight:600;color:var(--dsw-alias-label-primary,#e6e6e6);font-family:var(--ds-font-family-code,ui-monospace,monospace);overflow:hidden;text-overflow:ellipsis;white-space:nowrap}\n.vsm-perf-ws-meta{font-size:12px;color:var(--dsw-alias-label-tertiary,#9aa5b1);flex-shrink:0}\n.vsm-perf-rows{display:flex;flex-direction:column}\n.vsm-perf-row{display:flex;align-items:center;gap:10px;padding:7px 14px;border-bottom:1px solid var(--dsw-alias-border-l1,#333);font-size:12px;color:var(--dsw-alias-label-secondary,#c8c8c8)}\n.vsm-perf-row:last-child{border-bottom:0}\n.vsm-perf-row:hover{background:var(--dsw-alias-interactive-bg-hover,#ffffff0d)}\n.vsm-perf-row input[type=checkbox]{accent-color:var(--dsw-alias-brand-primary,#6d55ff);cursor:pointer}\n.vsm-perf-cell{min-width:0}\n.vsm-perf-id{flex:1;overflow:hidden;text-overflow:ellipsis;white-space:nowrap;font-family:var(--ds-font-family-code,ui-monospace,monospace);color:var(--dsw-alias-label-primary,#eee)}\n.vsm-perf-size{width:70px;text-align:right;flex-shrink:0;color:var(--dsw-alias-label-secondary,#c8c8c8)}\n.vsm-perf-size.big{color:var(--dsw-alias-state-error-primary,#e05252);font-weight:600}\n.vsm-perf-mtime{width:44px;flex-shrink:0;color:var(--dsw-alias-label-tertiary,#9aa5b1)}\n.vsm-perf-active{width:44px;flex-shrink:0;font-size:11px;color:var(--dsw-alias-state-warn-primary,#e5b93f);text-align:right}\n.vsm-perf-config-state{display:flex;align-items:center;gap:12px;margin:10px 0}\n.vsm-perf-note{font-size:12px;color:var(--dsw-alias-state-warn-primary,#e5b93f)}\n.vsm-perf-block{margin:8px 0 0;padding:10px 12px;border:1px solid var(--dsw-alias-border-l2,#3a3a3a);border-radius:8px;background:var(--dsw-alias-bg-base,#111);color:var(--dsw-alias-label-secondary,#c8c8c8);font-size:11px;line-height:1.5;font-family:var(--ds-font-family-code,ui-monospace,monospace);white-space:pre-wrap;overflow-x:auto;max-width:100%}\n.vsm-perf-modal{position:fixed;inset:0;display:flex;align-items:center;justify-content:center;background:var(--dsw-alias-bg-mask-1,#000000aa);z-index:1000;padding:20px}\n.vsm-perf-modal-card{width:min(560px,92vw);max-height:72vh;display:flex;flex-direction:column;gap:12px;padding:18px 20px;border:1px solid var(--dsw-alias-border-l2,#3a3a3a);border-radius:12px;background:var(--dsw-alias-bg-layer-2,#1f1f1f);color:var(--dsw-alias-label-primary,#e6e6e6)}\n.vsm-perf-modal-card h3{margin:0;font-size:16px}\n.vsm-perf-modal-note{font-size:12px}\n.vsm-perf-modal-list{list-style:none;margin:0;padding:0;overflow-y:auto;font-size:12px;color:var(--dsw-alias-label-secondary,#c8c8c8);display:flex;flex-direction:column;gap:4px}\n.vsm-perf-modal-list li{padding:4px 6px;border-radius:6px;background:var(--dsw-alias-bg-layer-1,#262626);overflow:hidden;text-overflow:ellipsis;white-space:nowrap}\n.vsm-perf-modal-more{font-size:12px;color:var(--dsw-alias-label-tertiary,#9aa5b1)}\n.vsm-perf-modal-actions{display:flex;justify-content:flex-end;gap:10px;margin-top:4px}\n.vsm-secondary{border:1px solid var(--dsw-alias-border-l2,#555);border-radius:6px;padding:8px 16px;background:none;color:var(--dsw-alias-label-primary,#eee);font-size:13px;cursor:pointer;white-space:nowrap}\n.vsm-secondary:hover:not(:disabled){background:var(--dsw-alias-interactive-bg-hover,#ffffff14)}\n.vsm-secondary:disabled{opacity:.55;cursor:default}\n.vsm-small{padding:4px 10px;font-size:12px}\n\n/* 扩展管理(LSP 语言服务器) */\n.vsm-mcp-actions button.vsm-primary,\n.vsm-mcp-actions button.vsm-danger{width:auto;height:auto;padding:5px 12px;font-size:12px;border-radius:6px;line-height:1.4}\n.vsm-mcp-actions button.vsm-danger{color:var(--dsw-alias-state-error-primary,#f07878);background:none}\n.vsm-mcp-actions button.vsm-danger:hover{background:var(--dsw-alias-interactive-bg-hover-danger,#ff000014)}\n.vsm-ext-ver{font-weight:400;font-size:12px;color:var(--dsw-alias-label-tertiary,#9aa5b1);white-space:nowrap;overflow:hidden;text-overflow:ellipsis}\n";
|
|
4234
4905
|
const tagId = "dsh-vscode-mode/mcp.css";
|
|
4235
4906
|
if (typeof document !== "undefined" && document.querySelector("style[data-plugin-css=" + JSON.stringify(tagId) + "]") === null) {
|
|
4236
4907
|
const tag = document.createElement("style");
|
|
@@ -4617,7 +5288,7 @@ window.__ModuleLoader__.load({
|
|
|
4617
5288
|
onClick: () => resetRow(command.id)
|
|
4618
5289
|
}, "恢复"))));
|
|
4619
5290
|
});
|
|
4620
|
-
return react.default.createElement("section", { className: "vsm-general-page" }, react.default.createElement("h2", null, "快捷键"), react.default.createElement("p", null, "配置编辑器视图的快捷键(保存 / 快速打开 / 侧边栏 / 搜索)。修改即时写入配置,重新聚焦编辑器后生效。"), unavailable && react.default.createElement("div", { className: "vsm-mcp-error vsm-mcp-banner" }, "设置服务暂不可用,当前使用默认键位。"), error && react.default.createElement("div", { className: "vsm-mcp-error vsm-mcp-banner" }, error), message && react.default.createElement("div", { className: "vsm-kb-message" }, message), react.default.createElement("div", { className: "vsm-kb-list" }, rows), react.default.createElement("div", { className: "vsm-kb-actionsbar" }, react.default.createElement("span", { className: "vsm-kb-dirty" }, dirty ? "有未保存的修改" : ""), react.default.createElement("button", {
|
|
5291
|
+
return react.default.createElement("section", { className: "vsm-general-page" }, react.default.createElement("h2", null, "快捷键"), react.default.createElement("p", null, "配置编辑器视图的快捷键(保存 / 快速打开 / 侧边栏 / 搜索)。修改即时写入配置,重新聚焦编辑器后生效。"), unavailable && react.default.createElement("div", { className: "vsm-mcp-error vsm-mcp-banner" }, "设置服务暂不可用,当前使用默认键位。"), error && react.default.createElement("div", { className: "vsm-mcp-error vsm-mcp-banner" }, error), message && react.default.createElement("div", { className: "vsm-kb-message" }, message), react.default.createElement("section", { className: "vsm-panel" }, react.default.createElement("h3", { className: "vsm-panel-title" }, "编辑器键位"), react.default.createElement("div", { className: "vsm-panel-body" }, react.default.createElement("div", { className: "vsm-kb-list" }, rows)), react.default.createElement("div", { className: "vsm-panel-body vsm-kb-actionsbar" }, react.default.createElement("span", { className: "vsm-kb-dirty" }, dirty ? "有未保存的修改" : ""), react.default.createElement("button", {
|
|
4621
5292
|
className: "vsm-primary vsm-small",
|
|
4622
5293
|
disabled: unavailable || notReady || busy || snapshot?.writable === false || !dirty,
|
|
4623
5294
|
onClick: save
|
|
@@ -4625,7 +5296,7 @@ window.__ModuleLoader__.load({
|
|
|
4625
5296
|
className: "vsm-small",
|
|
4626
5297
|
disabled: unavailable || notReady || busy || snapshot?.writable === false,
|
|
4627
5298
|
onClick: resetAll
|
|
4628
|
-
}, "恢复默认")));
|
|
5299
|
+
}, "恢复默认"))));
|
|
4629
5300
|
}
|
|
4630
5301
|
//#endregion
|
|
4631
5302
|
//#region src/client/ui/LspSettings.ts
|
|
@@ -4637,8 +5308,8 @@ window.__ModuleLoader__.load({
|
|
|
4637
5308
|
*/
|
|
4638
5309
|
const LANGUAGES = [{
|
|
4639
5310
|
id: "lua",
|
|
4640
|
-
label: "Lua(LuaLS)",
|
|
4641
|
-
hint: "lua-language-server
|
|
5311
|
+
label: "Lua(EmmyLua / LuaLS)",
|
|
5312
|
+
hint: "优先使用已安装 EmmyLua;未安装时回退 LuaLS 或 PATH 中的 lua-language-server"
|
|
4642
5313
|
}, {
|
|
4643
5314
|
id: "csharp",
|
|
4644
5315
|
label: "C#(Roslyn / OmniSharp)",
|
|
@@ -4676,8 +5347,8 @@ window.__ModuleLoader__.load({
|
|
|
4676
5347
|
label: "更新"
|
|
4677
5348
|
}
|
|
4678
5349
|
];
|
|
4679
|
-
/** 语言卡片:状态 + 启用开关 +
|
|
4680
|
-
function LangCard({ lang, config, status, busy, onToggle, onSave }) {
|
|
5350
|
+
/** 语言卡片:状态 + 启用开关 + 命令/路径配置 + 重新检测。 */
|
|
5351
|
+
function LangCard({ lang, config, status, busy, onToggle, onSave, onRedetect }) {
|
|
4681
5352
|
const [pathDraft, setPathDraft] = react.default.useState(config?.path ?? "");
|
|
4682
5353
|
const [commandDraft, setCommandDraft] = react.default.useState(config?.command ?? "");
|
|
4683
5354
|
react.default.useEffect(() => {
|
|
@@ -4689,7 +5360,7 @@ window.__ModuleLoader__.load({
|
|
|
4689
5360
|
className: "vsm-switch " + (config?.enabled !== false ? "on" : ""),
|
|
4690
5361
|
onClick: () => onToggle(lang.id, config?.enabled !== false ? false : true),
|
|
4691
5362
|
"aria-label": "启用/禁用"
|
|
4692
|
-
}, 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", {
|
|
4693
5364
|
value: pathDraft,
|
|
4694
5365
|
placeholder: "如 C:/.../lua-language-server.exe",
|
|
4695
5366
|
onChange: (e) => setPathDraft(e.target.value)
|
|
@@ -4701,7 +5372,12 @@ window.__ModuleLoader__.load({
|
|
|
4701
5372
|
className: "vsm-primary",
|
|
4702
5373
|
disabled: busy === lang.id,
|
|
4703
5374
|
onClick: () => onSave(lang.id, pathDraft, commandDraft)
|
|
4704
|
-
}, busy === lang.id ? "保存中…" : "保存配置")
|
|
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));
|
|
4705
5381
|
}
|
|
4706
5382
|
/** 已装扩展卡片:清单 + 卸载/更新。 */
|
|
4707
5383
|
function InstalledCard({ ext, busy, onUninstall, onUpdate }) {
|
|
@@ -4768,6 +5444,14 @@ window.__ModuleLoader__.load({
|
|
|
4768
5444
|
if (next === "installed" || next === "updates") refreshExt();
|
|
4769
5445
|
if (next === "servers") refreshServers();
|
|
4770
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
|
+
};
|
|
4771
5455
|
const saveLang = (languageId, path, command) => {
|
|
4772
5456
|
setBusy(languageId);
|
|
4773
5457
|
setError("");
|
|
@@ -4781,6 +5465,8 @@ window.__ModuleLoader__.load({
|
|
|
4781
5465
|
return;
|
|
4782
5466
|
}
|
|
4783
5467
|
setConfig(res.config ?? {});
|
|
5468
|
+
mergeServers(languageId, res.servers);
|
|
5469
|
+
notifyResync(languageId);
|
|
4784
5470
|
}).catch((e) => setError(String(e))).finally(() => setBusy(""));
|
|
4785
5471
|
};
|
|
4786
5472
|
const toggleLang = (languageId, enabled) => {
|
|
@@ -4789,10 +5475,25 @@ window.__ModuleLoader__.load({
|
|
|
4789
5475
|
languageId,
|
|
4790
5476
|
enabled
|
|
4791
5477
|
}).then((res) => {
|
|
4792
|
-
if (res?.ok) {
|
|
4793
|
-
|
|
4794
|
-
|
|
4795
|
-
}
|
|
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);
|
|
4796
5497
|
}).catch((e) => setError(String(e))).finally(() => setBusy(""));
|
|
4797
5498
|
};
|
|
4798
5499
|
const searchMarket = () => {
|
|
@@ -4862,24 +5563,27 @@ window.__ModuleLoader__.load({
|
|
|
4862
5563
|
onClick: refresh
|
|
4863
5564
|
}, "↻ 刷新"));
|
|
4864
5565
|
const banner = error ? react.default.createElement("div", { className: "vsm-mcp-error vsm-mcp-banner" }, error) : null;
|
|
5566
|
+
/** 线框面板容器:标题 + 内容体。 */
|
|
5567
|
+
const panel = (title, children) => react.default.createElement("section", { className: "vsm-panel" }, react.default.createElement("h3", { className: "vsm-panel-title" }, title), react.default.createElement("div", { className: "vsm-panel-body" }, children));
|
|
4865
5568
|
let body = null;
|
|
4866
|
-
if (tab === "servers") body = react.default.createElement(
|
|
5569
|
+
if (tab === "servers") body = panel("语言服务器", react.default.createElement(react.default.Fragment, null, LANGUAGES.map((lang) => react.default.createElement(LangCard, {
|
|
4867
5570
|
key: lang.id,
|
|
4868
5571
|
lang,
|
|
4869
5572
|
config: config[lang.id] ?? {},
|
|
4870
5573
|
status: servers.find((s) => s.languageId === lang.id),
|
|
4871
5574
|
busy,
|
|
4872
5575
|
onToggle: toggleLang,
|
|
4873
|
-
onSave: saveLang
|
|
4874
|
-
|
|
4875
|
-
|
|
5576
|
+
onSave: saveLang,
|
|
5577
|
+
onRedetect: redetectLang
|
|
5578
|
+
})), react.default.createElement("div", { className: "vsm-lsp-hint" }, "提示:打开 Lua/C# 文件即自动(惰性)启动对应语言服务器;未配置时编辑器功能不受影响,大纲回退内置解析。")));
|
|
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, {
|
|
4876
5580
|
key: ext.id,
|
|
4877
5581
|
ext,
|
|
4878
5582
|
busy,
|
|
4879
5583
|
onUninstall: uninstallExt,
|
|
4880
5584
|
onUpdate: updateExt
|
|
4881
|
-
})) : react.default.createElement("div", { className: "vsm-mcp-empty" }, "还没有安装扩展(去「市场」或指定本地 .vsix)"));
|
|
4882
|
-
else if (tab === "market") body = react.default.createElement(
|
|
5585
|
+
})) : react.default.createElement("div", { className: "vsm-mcp-empty" }, "还没有安装扩展(去「市场」或指定本地 .vsix)")));
|
|
5586
|
+
else if (tab === "market") body = panel("扩展市场", react.default.createElement(react.default.Fragment, null, react.default.createElement("div", { className: "vsm-lsp-form" }, react.default.createElement("label", null, react.default.createElement("span", null, "搜索 Open VSX 市场"), react.default.createElement("input", {
|
|
4883
5587
|
value: query,
|
|
4884
5588
|
placeholder: "如 sumneko.lua / lua language server",
|
|
4885
5589
|
onChange: (e) => setQuery(e.target.value),
|
|
@@ -4904,18 +5608,107 @@ window.__ModuleLoader__.load({
|
|
|
4904
5608
|
installed,
|
|
4905
5609
|
busy,
|
|
4906
5610
|
onInstall: installMarket
|
|
4907
|
-
})));
|
|
4908
|
-
else body = react.default.createElement(
|
|
5611
|
+
}))));
|
|
5612
|
+
else body = panel("可用更新", react.default.createElement(react.default.Fragment, null, react.default.createElement("div", { className: "vsm-lsp-hint" }, "已装扩展与 Open VSX 最新版的差异。"), updates.length ? updates.map((u) => react.default.createElement("article", {
|
|
4909
5613
|
key: u.id,
|
|
4910
5614
|
className: "vsm-mcp-card"
|
|
4911
5615
|
}, react.default.createElement("div", { className: "vsm-mcp-card-head" }, react.default.createElement("div", { className: "vsm-mcp-title" }, react.default.createElement("span", { className: "vsm-mcp-dot error" }), react.default.createElement("span", null, u.displayName || u.id), react.default.createElement("span", { className: "vsm-ext-ver" }, "v" + u.current + " → v" + u.latest)), react.default.createElement("div", { className: "vsm-mcp-actions" }, react.default.createElement("button", {
|
|
4912
5616
|
className: "vsm-primary vsm-small",
|
|
4913
5617
|
disabled: busy === u.id,
|
|
4914
5618
|
onClick: () => updateExt(u)
|
|
4915
|
-
}, busy === u.id ? "更新中…" : "更新"))))) : react.default.createElement("div", { className: "vsm-mcp-empty" }, "全部已是最新"));
|
|
5619
|
+
}, busy === u.id ? "更新中…" : "更新"))))) : react.default.createElement("div", { className: "vsm-mcp-empty" }, "全部已是最新")));
|
|
4916
5620
|
return react.default.createElement("div", null, header, nav, banner, body);
|
|
4917
5621
|
}
|
|
4918
5622
|
//#endregion
|
|
5623
|
+
//#region src/client/state/workspaceFoldCache.ts
|
|
5624
|
+
/**
|
|
5625
|
+
* dsh-vscode-mode client — 「性能优化」页工作区栏目展开状态持久化(纯函数,可单测)。
|
|
5626
|
+
* 只持久化「已展开」的工作区键集合(默认全部折叠),对齐 explorerCache 的做法:
|
|
5627
|
+
* 不缓存会话条目本身,条目由 edrv.perf.inventory 实时返回。
|
|
5628
|
+
* 键前缀统一走 paths.ts PathConst(CACHE_KEY.workspaceFold)。
|
|
5629
|
+
* 作者 ddj 2026-09-02
|
|
5630
|
+
*/
|
|
5631
|
+
/** localStorage 键(全局,不按会话隔离)。 */
|
|
5632
|
+
const STORE_KEY = CACHE_KEY.workspaceFold;
|
|
5633
|
+
/**
|
|
5634
|
+
* 归一化工作区键:仅去首尾空白(工作区键非文件系统路径,不做斜杠转换)。
|
|
5635
|
+
* @author ddj 2026年09月02号
|
|
5636
|
+
* @param key 工作区标识
|
|
5637
|
+
* @returns 归一化键,空值返回 ''
|
|
5638
|
+
*/
|
|
5639
|
+
function normalizeFoldKey(key) {
|
|
5640
|
+
return String(key ?? "").trim();
|
|
5641
|
+
}
|
|
5642
|
+
/**
|
|
5643
|
+
* 裁剪展开键集:去重、丢弃空值、超容量丢弃尾部(保持传入顺序)。
|
|
5644
|
+
* @author ddj 2026年09月02号
|
|
5645
|
+
* @param expanded 原始展开键集合
|
|
5646
|
+
* @returns 规范化后的键数组
|
|
5647
|
+
*/
|
|
5648
|
+
function sanitizeFolded(expanded) {
|
|
5649
|
+
if (!Array.isArray(expanded)) return [];
|
|
5650
|
+
const seen = /* @__PURE__ */ new Set();
|
|
5651
|
+
const out = [];
|
|
5652
|
+
for (const item of expanded) {
|
|
5653
|
+
const key = normalizeFoldKey(typeof item === "string" ? item : "");
|
|
5654
|
+
if (!key || seen.has(key)) continue;
|
|
5655
|
+
seen.add(key);
|
|
5656
|
+
out.push(key);
|
|
5657
|
+
if (out.length >= 300) break;
|
|
5658
|
+
}
|
|
5659
|
+
return out;
|
|
5660
|
+
}
|
|
5661
|
+
/**
|
|
5662
|
+
* 序列化展开状态(JSON,带 schema 版本号)。
|
|
5663
|
+
* @author ddj 2026年09月02号
|
|
5664
|
+
* @param expanded 已展开的项目键集合
|
|
5665
|
+
* @returns JSON 字符串
|
|
5666
|
+
*/
|
|
5667
|
+
function serializeFold(expanded) {
|
|
5668
|
+
return JSON.stringify({
|
|
5669
|
+
v: 1,
|
|
5670
|
+
expanded: sanitizeFolded(expanded)
|
|
5671
|
+
});
|
|
5672
|
+
}
|
|
5673
|
+
/**
|
|
5674
|
+
* 解析缓存文本;损坏/格式不符 → 空集合。
|
|
5675
|
+
* @author ddj 2026年09月02号
|
|
5676
|
+
* @param text localStorage 原文
|
|
5677
|
+
* @returns 已展开键集合
|
|
5678
|
+
*/
|
|
5679
|
+
function parseFold(text) {
|
|
5680
|
+
if (!text) return [];
|
|
5681
|
+
try {
|
|
5682
|
+
const data = JSON.parse(text);
|
|
5683
|
+
if (!data || typeof data !== "object") return [];
|
|
5684
|
+
return sanitizeFolded(data.expanded);
|
|
5685
|
+
} catch (error) {
|
|
5686
|
+
return [];
|
|
5687
|
+
}
|
|
5688
|
+
}
|
|
5689
|
+
/**
|
|
5690
|
+
* 读取展开状态(localStorage 不可用 → 空集合)。
|
|
5691
|
+
* @author ddj 2026年09月02号
|
|
5692
|
+
* @returns 已展开键集合
|
|
5693
|
+
*/
|
|
5694
|
+
function foldLoad() {
|
|
5695
|
+
try {
|
|
5696
|
+
return parseFold(window.localStorage.getItem(STORE_KEY));
|
|
5697
|
+
} catch (error) {
|
|
5698
|
+
return [];
|
|
5699
|
+
}
|
|
5700
|
+
}
|
|
5701
|
+
/**
|
|
5702
|
+
* 写入展开状态(配额满/隐私模式失败静默)。
|
|
5703
|
+
* @author ddj 2026年09月02号
|
|
5704
|
+
* @param expanded 已展开键集合
|
|
5705
|
+
*/
|
|
5706
|
+
function foldSave(expanded) {
|
|
5707
|
+
try {
|
|
5708
|
+
window.localStorage.setItem(STORE_KEY, serializeFold(expanded));
|
|
5709
|
+
} catch (error) {}
|
|
5710
|
+
}
|
|
5711
|
+
//#endregion
|
|
4919
5712
|
//#region src/client/ui/PerfSettings.ts
|
|
4920
5713
|
/**
|
|
4921
5714
|
* dsh-vscode-mode client — PerfSettings:「性能优化」设置子页(会话卫生 + 压缩调优)。
|
|
@@ -4935,6 +5728,43 @@ window.__ModuleLoader__.load({
|
|
|
4935
5728
|
}
|
|
4936
5729
|
/** 会话行 key:workspaceKey \u0000 sessionId。 */
|
|
4937
5730
|
const keyOf = (ws, id) => ws + "\0" + id;
|
|
5731
|
+
/** 工作区列表每页条数。 */
|
|
5732
|
+
const PAGE_SIZE = 7;
|
|
5733
|
+
/**
|
|
5734
|
+
* 计算分页(纯函数,可单测)。
|
|
5735
|
+
* 作者 ddj 2026年09月02号
|
|
5736
|
+
* @param total 总条数
|
|
5737
|
+
* @param pageSize 每页条数
|
|
5738
|
+
* @param page 当前页码(1 起)
|
|
5739
|
+
* @returns 归一化后的分页信息(page 被夹到有效范围)
|
|
5740
|
+
*/
|
|
5741
|
+
function paginate(total, pageSize, page) {
|
|
5742
|
+
const size = Math.max(1, Math.floor(pageSize) || 1);
|
|
5743
|
+
const pages = Math.max(1, Math.ceil(total / size));
|
|
5744
|
+
const current = Math.min(Math.max(1, Math.floor(page) || 1), pages);
|
|
5745
|
+
const start = (current - 1) * size;
|
|
5746
|
+
return {
|
|
5747
|
+
page: current,
|
|
5748
|
+
pages,
|
|
5749
|
+
pageSize: size,
|
|
5750
|
+
start,
|
|
5751
|
+
end: Math.min(start + size, total),
|
|
5752
|
+
total
|
|
5753
|
+
};
|
|
5754
|
+
}
|
|
5755
|
+
/**
|
|
5756
|
+
* 取当前页切片(纯函数,可单测)。
|
|
5757
|
+
* 作者 ddj 2026年09月02号
|
|
5758
|
+
* @param items 全量列表
|
|
5759
|
+
* @param page 当前页码(1 起)
|
|
5760
|
+
* @param pageSize 每页条数
|
|
5761
|
+
* @returns 当前页子数组
|
|
5762
|
+
*/
|
|
5763
|
+
function pageSlice(items, page, pageSize) {
|
|
5764
|
+
const list = items ?? [];
|
|
5765
|
+
const { start, end } = paginate(list.length, pageSize, page);
|
|
5766
|
+
return list.slice(start, end);
|
|
5767
|
+
}
|
|
4938
5768
|
/** 单行会话。 */
|
|
4939
5769
|
function SessionRow({ row, checked, disabled, onToggle }) {
|
|
4940
5770
|
const age = Math.max(0, (Date.now() - row.mtime) / 864e5);
|
|
@@ -4953,6 +5783,63 @@ window.__ModuleLoader__.load({
|
|
|
4953
5783
|
}, when), row.active ? react.default.createElement("span", { className: "vsm-perf-cell vsm-perf-active" }, "活跃") : null);
|
|
4954
5784
|
}
|
|
4955
5785
|
/**
|
|
5786
|
+
* 工作区列表分页器:顶部文案 + 上一页/下一页交互。
|
|
5787
|
+
* 作者 ddj 2026年09月02号
|
|
5788
|
+
* @param {object} info paginate() 的分页信息
|
|
5789
|
+
* @param {Function} onPrev 上一页回调
|
|
5790
|
+
* @param {Function} onNext 下一页回调
|
|
5791
|
+
*/
|
|
5792
|
+
function WorkspacePager({ info, onPrev, onNext }) {
|
|
5793
|
+
const { page, pages, start, end, total } = info;
|
|
5794
|
+
return react.default.createElement("div", { className: "vsm-perf-pager" }, react.default.createElement("span", { className: "vsm-perf-pager-text" }, total ? `第 ${start + 1}–${end} 个 / 共 ${total} 个工作区(第 ${page}/${pages} 页)` : "暂无工作区"), react.default.createElement("div", { className: "vsm-perf-pager-actions" }, react.default.createElement("button", {
|
|
5795
|
+
className: "vsm-secondary vsm-small",
|
|
5796
|
+
onClick: onPrev,
|
|
5797
|
+
disabled: page <= 1
|
|
5798
|
+
}, "‹ 上一页"), react.default.createElement("button", {
|
|
5799
|
+
className: "vsm-secondary vsm-small",
|
|
5800
|
+
onClick: onNext,
|
|
5801
|
+
disabled: page >= pages
|
|
5802
|
+
}, "下一页 ›")));
|
|
5803
|
+
}
|
|
5804
|
+
/**
|
|
5805
|
+
* 工作区栏目(可展开/折叠),内含该工作区的会话行。
|
|
5806
|
+
* 作者 ddj 2026年09月02号
|
|
5807
|
+
* @param {object} ws 工作区汇总(workspaceKey / totalBytes)
|
|
5808
|
+
* @param {object[]} rows 该工作区的会话行
|
|
5809
|
+
* @param {boolean} expanded 是否展开
|
|
5810
|
+
* @param {Function} onToggleFold 展开/折叠切换回调
|
|
5811
|
+
* @param {object} selected 已选会话键集合
|
|
5812
|
+
* @param {boolean} locked 会话勾选是否禁用(移出执行中)
|
|
5813
|
+
* @param {Function} onToggleRow 单行勾选回调
|
|
5814
|
+
*/
|
|
5815
|
+
function WorkspaceBlock({ ws, rows, expanded, onToggleFold, selected, locked, onToggleRow }) {
|
|
5816
|
+
const head = react.default.createElement("div", {
|
|
5817
|
+
className: "vsm-perf-ws-head",
|
|
5818
|
+
role: "button",
|
|
5819
|
+
tabIndex: 0,
|
|
5820
|
+
"aria-expanded": expanded,
|
|
5821
|
+
onClick: onToggleFold,
|
|
5822
|
+
onKeyDown: (event) => {
|
|
5823
|
+
if (event.key === "Enter" || event.key === " ") {
|
|
5824
|
+
event.preventDefault();
|
|
5825
|
+
onToggleFold();
|
|
5826
|
+
}
|
|
5827
|
+
}
|
|
5828
|
+
}, react.default.createElement("span", { className: "vsm-perf-ws-caret " + (expanded ? "open" : "") }, "▸"), react.default.createElement("span", {
|
|
5829
|
+
className: "vsm-perf-ws-name",
|
|
5830
|
+
title: ws.workspaceKey
|
|
5831
|
+
}, ws.workspaceKey), react.default.createElement("span", { className: "vsm-perf-ws-meta" }, rows.length + " 会话 · " + formatBytes(ws.totalBytes)));
|
|
5832
|
+
if (!expanded) return react.default.createElement("div", { className: "vsm-perf-ws" }, head);
|
|
5833
|
+
const body = rows.length ? rows.map((row) => react.default.createElement(SessionRow, {
|
|
5834
|
+
key: keyOf(row.workspaceKey, row.sessionId),
|
|
5835
|
+
row,
|
|
5836
|
+
checked: !!selected[keyOf(row.workspaceKey, row.sessionId)],
|
|
5837
|
+
disabled: row.active || locked,
|
|
5838
|
+
onToggle: () => onToggleRow(keyOf(row.workspaceKey, row.sessionId))
|
|
5839
|
+
})) : react.default.createElement("div", { className: "vsm-mcp-empty" }, "无会话");
|
|
5840
|
+
return react.default.createElement("div", { className: "vsm-perf-ws expanded" }, head, react.default.createElement("div", { className: "vsm-perf-rows" }, body));
|
|
5841
|
+
}
|
|
5842
|
+
/**
|
|
4956
5843
|
* 会话卫生 + 压缩调优设置子页。
|
|
4957
5844
|
* @author ddj 2026年09月02号
|
|
4958
5845
|
*/
|
|
@@ -4964,6 +5851,26 @@ window.__ModuleLoader__.load({
|
|
|
4964
5851
|
const [confirm, setConfirm] = react.default.useState(null);
|
|
4965
5852
|
const [moved, setMoved] = react.default.useState(null);
|
|
4966
5853
|
const [config, setConfig] = react.default.useState(null);
|
|
5854
|
+
const [expanded, setExpanded] = react.default.useState(() => foldLoad());
|
|
5855
|
+
const [page, setPage] = react.default.useState(1);
|
|
5856
|
+
react.default.useEffect(() => {
|
|
5857
|
+
foldSave(expanded);
|
|
5858
|
+
}, [expanded]);
|
|
5859
|
+
/** 数据变化后把越界页码夹回有效范围。 */
|
|
5860
|
+
const workspaces = inv?.workspaces ?? [];
|
|
5861
|
+
const pageInfo = paginate(workspaces.length, PAGE_SIZE, page);
|
|
5862
|
+
react.default.useEffect(() => {
|
|
5863
|
+
if (page !== pageInfo.page) setPage(pageInfo.page);
|
|
5864
|
+
}, [page, pageInfo.page]);
|
|
5865
|
+
/** 切换某工作区栏目的展开/折叠(默认全折叠)。 */
|
|
5866
|
+
const toggleFold = (key) => setExpanded((old) => old.includes(key) ? old.filter((item) => item !== key) : old.concat(key));
|
|
5867
|
+
/** 切换单会话勾选。 */
|
|
5868
|
+
const toggleRow = (k) => setSelected((old) => {
|
|
5869
|
+
const next = { ...old };
|
|
5870
|
+
if (next[k]) delete next[k];
|
|
5871
|
+
else next[k] = true;
|
|
5872
|
+
return next;
|
|
5873
|
+
});
|
|
4967
5874
|
const load = react.default.useCallback(() => {
|
|
4968
5875
|
setError("");
|
|
4969
5876
|
Promise.all([rpc("edrv.perf.inventory", {}), rpc("edrv.perf.configGet", {})]).then(([i, c]) => {
|
|
@@ -5066,30 +5973,25 @@ window.__ModuleLoader__.load({
|
|
|
5066
5973
|
};
|
|
5067
5974
|
const totals = inv?.totals;
|
|
5068
5975
|
const summaryCards = totals ? react.default.createElement("div", { className: "vsm-perf-cards" }, react.default.createElement("div", { className: "vsm-perf-card" }, react.default.createElement("span", null, "工作区"), react.default.createElement("b", null, String(totals.workspaces))), react.default.createElement("div", { className: "vsm-perf-card" }, react.default.createElement("span", null, "会话数"), react.default.createElement("b", null, String(totals.sessions))), react.default.createElement("div", { className: "vsm-perf-card" }, react.default.createElement("span", null, "压缩态总体积"), react.default.createElement("b", { className: totals.totalBytes >= 52428800 ? "big" : "" }, formatBytes(totals.totalBytes)))) : null;
|
|
5069
|
-
const
|
|
5070
|
-
|
|
5071
|
-
|
|
5072
|
-
|
|
5073
|
-
|
|
5074
|
-
}, react.default.createElement("div", { className: "vsm-perf-ws-head" }, react.default.createElement("span", {
|
|
5075
|
-
className: "vsm-perf-ws-name",
|
|
5076
|
-
title: ws.workspaceKey
|
|
5077
|
-
}, ws.workspaceKey), react.default.createElement("span", { className: "vsm-perf-ws-meta" }, rows.length + " 会话 · " + formatBytes(ws.totalBytes))), react.default.createElement("div", { className: "vsm-perf-rows" }, rows.length ? rows.map((row) => react.default.createElement(SessionRow, {
|
|
5078
|
-
key: keyOf(row.workspaceKey, row.sessionId),
|
|
5079
|
-
row,
|
|
5080
|
-
checked: !!selected[keyOf(row.workspaceKey, row.sessionId)],
|
|
5081
|
-
disabled: row.active || busy === "move",
|
|
5082
|
-
onToggle: () => {
|
|
5083
|
-
const k = keyOf(row.workspaceKey, row.sessionId);
|
|
5084
|
-
setSelected((old) => {
|
|
5085
|
-
const next = { ...old };
|
|
5086
|
-
if (next[k]) delete next[k];
|
|
5087
|
-
else next[k] = true;
|
|
5088
|
-
return next;
|
|
5089
|
-
});
|
|
5090
|
-
}
|
|
5091
|
-
})) : react.default.createElement("div", { className: "vsm-mcp-empty" }, "无会话")));
|
|
5976
|
+
const visible = pageSlice(workspaces, pageInfo.page, PAGE_SIZE);
|
|
5977
|
+
const workspacePager = workspaces.length > PAGE_SIZE ? react.default.createElement(WorkspacePager, {
|
|
5978
|
+
info: pageInfo,
|
|
5979
|
+
onPrev: () => setPage((old) => Math.max(1, old - 1)),
|
|
5980
|
+
onNext: () => setPage((old) => Math.min(pageInfo.pages, old + 1))
|
|
5092
5981
|
}) : null;
|
|
5982
|
+
const workspaceBlocks = workspaces.length ? react.default.createElement("div", { className: "vsm-perf-ws-list" }, visible.map((ws) => {
|
|
5983
|
+
const key = normalizeFoldKey(ws.workspaceKey);
|
|
5984
|
+
return react.default.createElement(WorkspaceBlock, {
|
|
5985
|
+
key: ws.workspaceKey,
|
|
5986
|
+
ws,
|
|
5987
|
+
rows: inv.sessions.filter((s) => s.workspaceKey === ws.workspaceKey),
|
|
5988
|
+
expanded: expanded.includes(key),
|
|
5989
|
+
onToggleFold: () => toggleFold(key),
|
|
5990
|
+
selected,
|
|
5991
|
+
locked: busy === "move",
|
|
5992
|
+
onToggleRow: toggleRow
|
|
5993
|
+
});
|
|
5994
|
+
})) : null;
|
|
5093
5995
|
const selCount = Object.keys(selected).length;
|
|
5094
5996
|
const confirmBody = confirm ? react.default.createElement("div", { className: "vsm-perf-modal" }, react.default.createElement("div", { className: "vsm-perf-modal-card" }, react.default.createElement("h3", null, "确认移出 " + confirm.items.length + " 个会话?"), react.default.createElement("p", { className: "vsm-perf-modal-note" }, "将从 ~/.dsh/sessions 移出到 ~/.dsh/sessions-archive(可逆,重启后不再回放,释放 " + formatBytes(confirm.reclaimedBytes) + ")。活跃会话已排除。"), react.default.createElement("ul", { className: "vsm-perf-modal-list" }, confirm.items.slice(0, 20).map((item) => react.default.createElement("li", { key: item.workspaceKey + item.sessionId }, item.workspaceKey + " / " + item.sessionId + "(" + formatBytes(item.bytes) + ")"))), confirm.items.length > 20 ? react.default.createElement("div", { className: "vsm-perf-modal-more" }, "…等 " + confirm.items.length + " 个") : null, react.default.createElement("div", { className: "vsm-perf-modal-actions" }, react.default.createElement("button", {
|
|
5095
5997
|
className: "vsm-primary",
|
|
@@ -5099,15 +6001,15 @@ window.__ModuleLoader__.load({
|
|
|
5099
6001
|
className: "vsm-secondary",
|
|
5100
6002
|
onClick: () => setConfirm(null)
|
|
5101
6003
|
}, "取消")))) : null;
|
|
5102
|
-
const movedBody = moved?.moved?.length ? react.default.createElement("
|
|
6004
|
+
const movedBody = moved?.moved?.length ? react.default.createElement("section", { className: "vsm-panel" }, react.default.createElement("h3", { className: "vsm-panel-title" }, "已移出 " + moved.moved.length + " 个会话(释放 " + formatBytes(moved.reclaimedBytes) + ")"), react.default.createElement("div", { className: "vsm-panel-body" }, react.default.createElement("div", { className: "vsm-perf-rows" }, moved.moved.map((item) => react.default.createElement("div", {
|
|
5103
6005
|
key: item.workspaceKey + item.sessionId,
|
|
5104
6006
|
className: "vsm-perf-row"
|
|
5105
6007
|
}, react.default.createElement("span", { className: "vsm-perf-cell vsm-perf-id" }, item.sessionId), react.default.createElement("span", { className: "vsm-perf-cell vsm-perf-size" }, formatBytes(item.bytes)), react.default.createElement("button", {
|
|
5106
6008
|
className: "vsm-secondary vsm-small",
|
|
5107
6009
|
onClick: () => doRestore(item),
|
|
5108
6010
|
disabled: busy === "restore"
|
|
5109
|
-
}, "恢复")))), moved.failures?.length ? react.default.createElement("div", { className: "vsm-mcp-error vsm-mcp-banner" }, "失败:" + moved.failures.join(";")) : null) : null;
|
|
5110
|
-
const configBody = config ? react.default.createElement("
|
|
6011
|
+
}, "恢复")))), moved.failures?.length ? react.default.createElement("div", { className: "vsm-mcp-error vsm-mcp-banner" }, "失败:" + moved.failures.join(";")) : null)) : null;
|
|
6012
|
+
const configBody = config ? react.default.createElement("section", { className: "vsm-panel" }, react.default.createElement("h3", { className: "vsm-panel-title" }, "DSH 内置压缩调优"), react.default.createElement("div", { className: "vsm-panel-body" }, react.default.createElement("p", null, "调低 compaction-basic / tool-result-pruner 触发阈值,让长循环会话更早压缩,降低模型可见上下文压力(减少 token 与防 context overflow)。"), react.default.createElement("p", { className: "vsm-perf-note" }, "注意:压缩只追加 compaction 事件,不重写已持久化会话日志(append-only),因此不会缩小已存在的会话文件——存量瘦身请用上方「移出到归档」。packChunks 已默认开启,无需配置。"), react.default.createElement("div", { className: "vsm-perf-config-state" }, react.default.createElement("span", { className: "vsm-perf-cell" }, "状态:" + (config.applied ? "已应用" : "未应用")), config.applied ? react.default.createElement("button", {
|
|
5111
6013
|
className: "vsm-secondary vsm-small",
|
|
5112
6014
|
onClick: undoConfig,
|
|
5113
6015
|
disabled: busy === "undo"
|
|
@@ -5115,9 +6017,9 @@ window.__ModuleLoader__.load({
|
|
|
5115
6017
|
className: "vsm-primary vsm-small",
|
|
5116
6018
|
onClick: applyConfig,
|
|
5117
6019
|
disabled: busy === "apply"
|
|
5118
|
-
}, "应用建议配置(需重启)")), react.default.createElement("pre", { className: "vsm-perf-block" }, config.block)) : null;
|
|
5119
|
-
const guideBody = react.default.createElement("
|
|
5120
|
-
return react.default.createElement("section", { className: "vsm-perf-page" }, react.default.createElement("h2", null, "会话性能"), react.default.createElement("p", null, "DSH 启动会回放 ~/.dsh/sessions 全部会话(V8 展开约 10×),体积越大内存越吃紧。定期把巨型/旧会话移出到归档可显著降低启动内存。"), error ? react.default.createElement("div", { className: "vsm-mcp-error vsm-mcp-banner" }, error) : null, summaryCards, react.default.createElement("div", { className: "vsm-perf-toolbar" }, react.default.createElement("button", {
|
|
6020
|
+
}, "应用建议配置(需重启)")), react.default.createElement("pre", { className: "vsm-perf-block" }, config.block))) : null;
|
|
6021
|
+
const guideBody = react.default.createElement("section", { className: "vsm-panel" }, react.default.createElement("h3", { className: "vsm-panel-title" }, "侧车摘要(agent/脚本取关键字段,不整份读 sidecar)"), react.default.createElement("div", { className: "vsm-panel-body" }, react.default.createElement("p", null, "工作区根目录的 .dsh-edit-review.json(可能数 MB)只应通过摘要接口读取,禁止 agent 整份 read/write 进对话:"), react.default.createElement("pre", { className: "vsm-perf-block" }, "curl -s -X POST http://127.0.0.1:3080/edrv/rpc -H 'content-type: application/json' -d '{\"method\":\"edrv.perf.sidecarSummary\",\"args\":{}}'\n# 差异记录列表(含每文件 pending 摘要):edrv.list\n# 历史归档:edrv.archiveList / edrv.archiveRead")));
|
|
6022
|
+
return react.default.createElement("section", { className: "vsm-perf-page" }, react.default.createElement("h2", null, "会话性能"), react.default.createElement("p", null, "DSH 启动会回放 ~/.dsh/sessions 全部会话(V8 展开约 10×),体积越大内存越吃紧。定期把巨型/旧会话移出到归档可显著降低启动内存。"), error ? react.default.createElement("div", { className: "vsm-mcp-error vsm-mcp-banner" }, error) : null, summaryCards, react.default.createElement("section", { className: "vsm-panel" }, react.default.createElement("h3", { className: "vsm-panel-title" }, "会话盘点"), react.default.createElement("div", { className: "vsm-panel-body" }, react.default.createElement("div", { className: "vsm-perf-toolbar" }, react.default.createElement("button", {
|
|
5121
6023
|
className: "vsm-secondary vsm-small",
|
|
5122
6024
|
onClick: selectBig
|
|
5123
6025
|
}, "圈选 ≥1MB 非活跃"), react.default.createElement("button", {
|
|
@@ -5136,15 +6038,14 @@ window.__ModuleLoader__.load({
|
|
|
5136
6038
|
className: "vsm-secondary vsm-small",
|
|
5137
6039
|
onClick: load,
|
|
5138
6040
|
disabled: busy === "load"
|
|
5139
|
-
}, "⟳ 刷新")), workspaceBlocks, movedBody, configBody, guideBody, confirmBody);
|
|
6041
|
+
}, "⟳ 刷新")), workspacePager, workspaceBlocks)), movedBody, configBody, guideBody, confirmBody);
|
|
5140
6042
|
}
|
|
5141
6043
|
//#endregion
|
|
5142
6044
|
//#region src/client/ui/McpSettings.ts
|
|
5143
6045
|
/**
|
|
5144
|
-
* dsh-vscode-mode client — VSCodeMode 设置区:通用 / 快捷键 / MCP
|
|
5145
|
-
*
|
|
5146
|
-
*
|
|
5147
|
-
* 作者 ddj 2026年08月22号 / 2026年08月26号
|
|
6046
|
+
* dsh-vscode-mode client — VSCodeMode 设置区:通用 / 快捷键 / MCP 管理 / 语言服务器 / 性能优化 / 兼容性。
|
|
6047
|
+
* MCP 管理 Tab 下含三个子页签:我的 MCP(profile 全局)/ 项目 MCP(各项目 .mcp.json)/ MCP 市场(占位)。
|
|
6048
|
+
* 作者 ddj 2026年08月22号 / 2026年08月26号 / 2026年08月27号
|
|
5148
6049
|
*/
|
|
5149
6050
|
const EMPTY = {
|
|
5150
6051
|
serverName: "",
|
|
@@ -5357,14 +6258,14 @@ window.__ModuleLoader__.load({
|
|
|
5357
6258
|
disabled: devBusy,
|
|
5358
6259
|
onClick: closeDevForm
|
|
5359
6260
|
}, devBusy ? "切换中…" : "关闭开发形态"))) : null;
|
|
5360
|
-
return react.default.createElement("section", { className: "vsm-general-page" }, react.default.createElement("h2", null, "通用"), react.default.createElement("p", null, "配置 DSH 对话中文件链接的打开工具。工具由当前运行时自动扫描。"), unavailable && react.default.createElement("div", { className: "vsm-mcp-error vsm-mcp-banner" }, "设置服务暂不可用,当前使用自动选择。"), error && react.default.createElement("div", { className: "vsm-mcp-error vsm-mcp-banner" }, error), react.default.createElement("label", { className: "vsm-general-row" }, react.default.createElement("span", null, "文件链接使用工具"), react.default.createElement("select", {
|
|
6261
|
+
return react.default.createElement("section", { className: "vsm-general-page" }, react.default.createElement("h2", null, "通用"), react.default.createElement("p", null, "配置 DSH 对话中文件链接的打开工具。工具由当前运行时自动扫描。"), unavailable && react.default.createElement("div", { className: "vsm-mcp-error vsm-mcp-banner" }, "设置服务暂不可用,当前使用自动选择。"), error && react.default.createElement("div", { className: "vsm-mcp-error vsm-mcp-banner" }, error), react.default.createElement("section", { className: "vsm-panel" }, react.default.createElement("h3", { className: "vsm-panel-title" }, "文件与安装"), react.default.createElement("div", { className: "vsm-panel-body" }, react.default.createElement("label", { className: "vsm-general-row" }, react.default.createElement("span", null, "文件链接使用工具"), react.default.createElement("select", {
|
|
5361
6262
|
value: currentOption,
|
|
5362
6263
|
disabled: loading || unavailable || notReady || busy || snapshot?.writable === false,
|
|
5363
6264
|
onChange: (event) => save(event.target.value)
|
|
5364
6265
|
}, options.map((item) => react.default.createElement("option", {
|
|
5365
6266
|
key: item.id,
|
|
5366
6267
|
value: item.id
|
|
5367
|
-
}, item.label))), loading && react.default.createElement("small", null, "读取中…")), devFormRow);
|
|
6268
|
+
}, item.label))), loading && react.default.createElement("small", null, "读取中…")), devFormRow)));
|
|
5368
6269
|
}
|
|
5369
6270
|
/** 兼容性小节:host 报告(RPC compat)+ 客户端本地适配项。 */
|
|
5370
6271
|
function CompatSection({ getSummary }) {
|
|
@@ -5387,12 +6288,111 @@ window.__ModuleLoader__.load({
|
|
|
5387
6288
|
key: item.name,
|
|
5388
6289
|
className: "vsm-compat-item " + (item.active ? "ok" : "warn")
|
|
5389
6290
|
}, react.default.createElement("span", { className: "vsm-compat-name" }, item.name), react.default.createElement("span", { className: "vsm-compat-note" }, item.note ?? (item.active ? "正常" : "未生效"))));
|
|
6291
|
+
/** 兼容性分组面板:标题 + 线框内容区。 */
|
|
6292
|
+
const group = (title, children) => react.default.createElement("section", { className: "vsm-panel" }, react.default.createElement("h3", { className: "vsm-panel-title" }, title), react.default.createElement("div", { className: "vsm-panel-body" }, children));
|
|
5390
6293
|
const warnings = report?.warnings ?? [];
|
|
5391
6294
|
const devForm = report?.devForm;
|
|
5392
|
-
return react.default.createElement("section", { className: "vsm-general-page" }, react.default.createElement("h2", null, "兼容性"), react.default.createElement("p", null, "检测与其他插件 / DSH 版本的适配状态与已知问题。" + (report?.pluginVersion ? "(插件版本 " + report.pluginVersion + ")" : "")), error && react.default.createElement("div", { className: "vsm-mcp-error vsm-mcp-banner" }, error), devForm?.enabled &&
|
|
6295
|
+
return react.default.createElement("section", { className: "vsm-general-page" }, react.default.createElement("h2", null, "兼容性"), react.default.createElement("p", null, "检测与其他插件 / DSH 版本的适配状态与已知问题。" + (report?.pluginVersion ? "(插件版本 " + report.pluginVersion + ")" : "")), error && react.default.createElement("div", { className: "vsm-mcp-error vsm-mcp-banner" }, error), devForm?.enabled && group("开发形态", react.default.createElement("div", { className: "vsm-compat-item ok" }, react.default.createElement("span", { className: "vsm-compat-name" }, "开发形态开启(工作区链接安装)"), react.default.createElement("span", { className: "vsm-compat-note" }, devForm.path))), group("外部插件与服务", renderItems(report?.external)), group("客户端适配", renderItems(getSummary?.() ?? [])), group("护栏", renderItems(report?.guards)), group("警告", warnings.length ? warnings.map((w, index) => react.default.createElement("div", {
|
|
5393
6296
|
key: index,
|
|
5394
6297
|
className: "vsm-mcp-error vsm-mcp-banner"
|
|
5395
|
-
}, w)) : react.default.createElement("div", { className: "vsm-compat-item ok" }, react.default.createElement("span", { className: "vsm-compat-name" }, "未检测到兼容性问题"), react.default.createElement("span", { className: "vsm-compat-note" }, "")));
|
|
6298
|
+
}, w)) : react.default.createElement("div", { className: "vsm-compat-item ok" }, react.default.createElement("span", { className: "vsm-compat-name" }, "未检测到兼容性问题"), react.default.createElement("span", { className: "vsm-compat-note" }, ""))));
|
|
6299
|
+
}
|
|
6300
|
+
/** MCP 管理子页签定义(我的 / 项目 / 市场)。 */
|
|
6301
|
+
const MCP_TABS = [
|
|
6302
|
+
{
|
|
6303
|
+
id: "mine",
|
|
6304
|
+
label: "我的 MCP"
|
|
6305
|
+
},
|
|
6306
|
+
{
|
|
6307
|
+
id: "projects",
|
|
6308
|
+
label: "项目 MCP"
|
|
6309
|
+
},
|
|
6310
|
+
{
|
|
6311
|
+
id: "market",
|
|
6312
|
+
label: "MCP 市场"
|
|
6313
|
+
}
|
|
6314
|
+
];
|
|
6315
|
+
/**
|
|
6316
|
+
* MCP 管理面板:顶层「MCP 管理」Tab 下的三个子页签容器。
|
|
6317
|
+
* 作者 ddj 2026年08月27号
|
|
6318
|
+
* @param {object[]} servers 全局 MCP 服务列表
|
|
6319
|
+
* @param {object[]} projects 项目及其项目级 MCP 列表
|
|
6320
|
+
* @param {string} busy 正在执行的操作标识
|
|
6321
|
+
* @param {object} draft 新增表单草稿
|
|
6322
|
+
* @param {Function} edit 草稿字段变更回调
|
|
6323
|
+
* @param {Function} resetDraft 清空草稿回调
|
|
6324
|
+
*/
|
|
6325
|
+
function McpManagePanel({ servers, projects, busy, draft, edit, resetDraft, refreshGlobal, toggleGlobal, removeGlobal, refreshProject, toggleProject, removeProject, saveGlobal, saveProject }) {
|
|
6326
|
+
const [mcpTab, setMcpTab] = react.default.useState("mine");
|
|
6327
|
+
const [showForm, setShowForm] = react.default.useState(false);
|
|
6328
|
+
const [projectForm, setProjectForm] = react.default.useState(null);
|
|
6329
|
+
const [selectedPath, setSelectedPath] = react.default.useState("");
|
|
6330
|
+
react.default.useEffect(() => {
|
|
6331
|
+
if (!projects.length) {
|
|
6332
|
+
setSelectedPath("");
|
|
6333
|
+
return;
|
|
6334
|
+
}
|
|
6335
|
+
if (!projects.some((project) => project.workspacePath === selectedPath)) setSelectedPath(projects[0].workspacePath);
|
|
6336
|
+
}, [projects, selectedPath]);
|
|
6337
|
+
const closeGlobalForm = () => setShowForm(false);
|
|
6338
|
+
const closeProjectForm = () => setProjectForm(null);
|
|
6339
|
+
const addProject = (project) => {
|
|
6340
|
+
resetDraft?.();
|
|
6341
|
+
setProjectForm({
|
|
6342
|
+
workspacePath: project.workspacePath,
|
|
6343
|
+
title: project.title
|
|
6344
|
+
});
|
|
6345
|
+
};
|
|
6346
|
+
const selectedProject = projects.find((project) => project.workspacePath === selectedPath);
|
|
6347
|
+
const mineBody = servers.length ? servers.map((s) => react.default.createElement(ServerCard, {
|
|
6348
|
+
key: s.id,
|
|
6349
|
+
server: s,
|
|
6350
|
+
onRefresh: refreshGlobal,
|
|
6351
|
+
onToggle: toggleGlobal,
|
|
6352
|
+
onRemove: removeGlobal
|
|
6353
|
+
})) : react.default.createElement("div", { className: "vsm-mcp-empty" }, "还没有配置全局 MCP");
|
|
6354
|
+
const projectBody = projects.length ? react.default.createElement(react.default.Fragment, null, react.default.createElement(ProjectPicker, {
|
|
6355
|
+
projects,
|
|
6356
|
+
value: selectedPath,
|
|
6357
|
+
onChange: setSelectedPath
|
|
6358
|
+
}), selectedProject ? react.default.createElement(ProjectGroup, {
|
|
6359
|
+
project: selectedProject,
|
|
6360
|
+
busy,
|
|
6361
|
+
onAdd: addProject,
|
|
6362
|
+
onRefresh: refreshProject,
|
|
6363
|
+
onToggle: toggleProject,
|
|
6364
|
+
onRemove: removeProject
|
|
6365
|
+
}) : react.default.createElement("div", { className: "vsm-mcp-empty" }, "请选择项目")) : react.default.createElement("div", { className: "vsm-mcp-empty" }, "还没有项目");
|
|
6366
|
+
let body;
|
|
6367
|
+
if (mcpTab === "projects") body = projectBody;
|
|
6368
|
+
else if (mcpTab === "market") body = react.default.createElement("div", { className: "vsm-mcp-empty" }, "MCP 市场暂未接入");
|
|
6369
|
+
else body = mineBody;
|
|
6370
|
+
const form = showForm ? react.default.createElement(McpForm, {
|
|
6371
|
+
title: "添加全局 MCP",
|
|
6372
|
+
draft,
|
|
6373
|
+
busy,
|
|
6374
|
+
edit,
|
|
6375
|
+
save: () => saveGlobal(closeGlobalForm),
|
|
6376
|
+
close: closeGlobalForm
|
|
6377
|
+
}) : projectForm ? react.default.createElement(McpForm, {
|
|
6378
|
+
title: "添加项目 MCP · " + projectForm.title,
|
|
6379
|
+
draft,
|
|
6380
|
+
busy,
|
|
6381
|
+
edit,
|
|
6382
|
+
save: () => saveProject(projectForm.workspacePath, closeProjectForm),
|
|
6383
|
+
close: closeProjectForm
|
|
6384
|
+
}) : null;
|
|
6385
|
+
return react.default.createElement(react.default.Fragment, null, react.default.createElement("div", { className: "vsm-mcp-subhead" }, react.default.createElement("nav", { className: "vsm-mcp-subtabs" }, MCP_TABS.map((item) => react.default.createElement("button", {
|
|
6386
|
+
key: item.id,
|
|
6387
|
+
className: mcpTab === item.id ? "active" : "",
|
|
6388
|
+
onClick: () => setMcpTab(item.id)
|
|
6389
|
+
}, item.label))), react.default.createElement("button", {
|
|
6390
|
+
className: "vsm-primary vsm-small",
|
|
6391
|
+
onClick: () => {
|
|
6392
|
+
resetDraft?.();
|
|
6393
|
+
setShowForm(true);
|
|
6394
|
+
}
|
|
6395
|
+
}, "+ 添加全局 MCP")), body, form);
|
|
5396
6396
|
}
|
|
5397
6397
|
/** MCP 管理主面板。 */
|
|
5398
6398
|
function McpSettings(props) {
|
|
@@ -5405,16 +6405,6 @@ window.__ModuleLoader__.load({
|
|
|
5405
6405
|
const [error, setError] = react.default.useState("");
|
|
5406
6406
|
const [tab, setTab] = react.default.useState("general");
|
|
5407
6407
|
const [draft, setDraft] = react.default.useState(EMPTY);
|
|
5408
|
-
const [showForm, setShowForm] = react.default.useState(false);
|
|
5409
|
-
const [projectForm, setProjectForm] = react.default.useState(null);
|
|
5410
|
-
const [selectedPath, setSelectedPath] = react.default.useState("");
|
|
5411
|
-
react.default.useEffect(() => {
|
|
5412
|
-
if (!projects.length) {
|
|
5413
|
-
setSelectedPath("");
|
|
5414
|
-
return;
|
|
5415
|
-
}
|
|
5416
|
-
if (!projects.some((project) => project.workspacePath === selectedPath)) setSelectedPath(projects[0].workspacePath);
|
|
5417
|
-
}, [projects, selectedPath]);
|
|
5418
6408
|
const loadAll = react.default.useCallback(() => {
|
|
5419
6409
|
setLoading(true);
|
|
5420
6410
|
setError("");
|
|
@@ -5464,116 +6454,87 @@ window.__ModuleLoader__.load({
|
|
|
5464
6454
|
workspacePath: p.workspacePath,
|
|
5465
6455
|
serverName: s.serverName
|
|
5466
6456
|
}, replaceProject(p.workspacePath));
|
|
5467
|
-
|
|
6457
|
+
/**
|
|
6458
|
+
* 保存全局 MCP:成功后由回调关闭子面板。
|
|
6459
|
+
* 作者 ddj 2026年08月27号
|
|
6460
|
+
* @param {Function} close 关闭新增表单回调
|
|
6461
|
+
*/
|
|
6462
|
+
const saveGlobal = (close) => {
|
|
5468
6463
|
setBusy("save");
|
|
5469
6464
|
setError("");
|
|
5470
6465
|
rpc("mcp.save", { config: configOf(draft) }).then(finish).then((result) => {
|
|
5471
6466
|
setServers((old) => old.some((s) => s.id === result.server.id) ? old.map((s) => s.id === result.server.id ? result.server : s) : old.concat(result.server));
|
|
5472
6467
|
setDraft(EMPTY);
|
|
5473
|
-
|
|
6468
|
+
close();
|
|
5474
6469
|
}).catch((e) => setError(String(e))).finally(() => setBusy(""));
|
|
5475
6470
|
};
|
|
5476
|
-
|
|
5477
|
-
|
|
6471
|
+
/**
|
|
6472
|
+
* 保存项目 MCP:成功后由回调关闭子面板。
|
|
6473
|
+
* 作者 ddj 2026年08月27号
|
|
6474
|
+
* @param {string} workspacePath 目标项目路径
|
|
6475
|
+
* @param {Function} close 关闭新增表单回调
|
|
6476
|
+
*/
|
|
6477
|
+
const saveProject = (workspacePath, close) => {
|
|
5478
6478
|
setBusy("project-save");
|
|
5479
6479
|
setError("");
|
|
5480
6480
|
const config = configOf(draft);
|
|
5481
6481
|
rpc("mcp.projectSave", {
|
|
5482
|
-
workspacePath
|
|
6482
|
+
workspacePath,
|
|
5483
6483
|
serverName: config.serverName,
|
|
5484
6484
|
config
|
|
5485
6485
|
}).then(finish).then((result) => {
|
|
5486
|
-
setProjects((old) => old.map((p) => p.workspacePath ===
|
|
6486
|
+
setProjects((old) => old.map((p) => p.workspacePath === workspacePath ? result.project : p));
|
|
5487
6487
|
setDraft(EMPTY);
|
|
5488
|
-
|
|
6488
|
+
close();
|
|
5489
6489
|
}).catch((e) => setError(String(e))).finally(() => setBusy(""));
|
|
5490
6490
|
};
|
|
5491
6491
|
const edit = (key, value) => setDraft((old) => ({
|
|
5492
6492
|
...old,
|
|
5493
6493
|
[key]: value
|
|
5494
6494
|
}));
|
|
5495
|
-
const
|
|
5496
|
-
const selectedProject = projects.find((project) => project.workspacePath === selectedPath);
|
|
5497
|
-
const projectBody = projects.length ? react.default.createElement(react.default.Fragment, null, react.default.createElement(ProjectPicker, {
|
|
5498
|
-
projects,
|
|
5499
|
-
value: selectedPath,
|
|
5500
|
-
onChange: setSelectedPath
|
|
5501
|
-
}), selectedProject ? react.default.createElement(ProjectGroup, {
|
|
5502
|
-
project: selectedProject,
|
|
5503
|
-
busy,
|
|
5504
|
-
onAdd: (project) => {
|
|
5505
|
-
setDraft(EMPTY);
|
|
5506
|
-
setProjectForm({
|
|
5507
|
-
workspacePath: project.workspacePath,
|
|
5508
|
-
title: project.title
|
|
5509
|
-
});
|
|
5510
|
-
},
|
|
5511
|
-
onRefresh: refreshProject,
|
|
5512
|
-
onToggle: toggleProject,
|
|
5513
|
-
onRemove: removeProject
|
|
5514
|
-
}) : react.default.createElement("div", { className: "vsm-mcp-empty" }, "请选择项目")) : react.default.createElement("div", { className: "vsm-mcp-empty" }, "还没有项目");
|
|
6495
|
+
const resetDraft = () => setDraft(EMPTY);
|
|
5515
6496
|
let body;
|
|
5516
6497
|
if (loading) body = react.default.createElement("div", { className: "vsm-mcp-empty" }, "正在读取 MCP 服务…");
|
|
5517
6498
|
else if (tab === "general") body = react.default.createElement(GeneralSettings, { registry: openerRegistry });
|
|
5518
6499
|
else if (tab === "keys") body = react.default.createElement(KeybindingsPanel, null);
|
|
5519
|
-
else if (tab === "
|
|
5520
|
-
|
|
5521
|
-
|
|
5522
|
-
onRefresh: refreshGlobal,
|
|
5523
|
-
onToggle: toggleGlobal,
|
|
5524
|
-
onRemove: removeGlobal
|
|
5525
|
-
})) : react.default.createElement("div", { className: "vsm-mcp-empty" }, "还没有配置全局 MCP"));
|
|
5526
|
-
else if (tab === "projects") body = projectBody;
|
|
5527
|
-
else if (tab === "compat") body = react.default.createElement(CompatSection, { getSummary: compatSummary });
|
|
5528
|
-
else if (tab === "lsp") body = react.default.createElement(LspSettings, null);
|
|
5529
|
-
else if (tab === "perf") body = react.default.createElement(PerfSettings, null);
|
|
5530
|
-
else body = marketBody;
|
|
5531
|
-
const form = showForm ? react.default.createElement(McpForm, {
|
|
5532
|
-
title: "添加全局 MCP",
|
|
5533
|
-
draft,
|
|
6500
|
+
else if (tab === "mcp") body = react.default.createElement(McpManagePanel, {
|
|
6501
|
+
servers,
|
|
6502
|
+
projects,
|
|
5534
6503
|
busy,
|
|
5535
|
-
edit,
|
|
5536
|
-
save: saveGlobal,
|
|
5537
|
-
close: () => setShowForm(false)
|
|
5538
|
-
}) : projectForm ? react.default.createElement(McpForm, {
|
|
5539
|
-
title: "添加项目 MCP · " + projectForm.title,
|
|
5540
6504
|
draft,
|
|
5541
|
-
busy,
|
|
5542
6505
|
edit,
|
|
5543
|
-
|
|
5544
|
-
|
|
5545
|
-
|
|
5546
|
-
|
|
5547
|
-
|
|
5548
|
-
|
|
5549
|
-
|
|
5550
|
-
|
|
5551
|
-
|
|
5552
|
-
}
|
|
6506
|
+
resetDraft,
|
|
6507
|
+
saveGlobal: (close) => saveGlobal(close),
|
|
6508
|
+
saveProject: (workspacePath, close) => saveProject(workspacePath, close),
|
|
6509
|
+
refreshGlobal,
|
|
6510
|
+
toggleGlobal,
|
|
6511
|
+
removeGlobal,
|
|
6512
|
+
refreshProject,
|
|
6513
|
+
toggleProject,
|
|
6514
|
+
removeProject
|
|
6515
|
+
});
|
|
6516
|
+
else if (tab === "compat") body = react.default.createElement(CompatSection, { getSummary: compatSummary });
|
|
6517
|
+
else if (tab === "lsp") body = react.default.createElement(LspSettings, null);
|
|
6518
|
+
else body = react.default.createElement(PerfSettings, null);
|
|
6519
|
+
return react.default.createElement("section", { className: "vsm-mcp-page" }, react.default.createElement("header", { className: "vsm-mcp-header" }, react.default.createElement("div", null, react.default.createElement("h2", null, "VSCodeMode"), react.default.createElement("p", null, "管理当前 profile 与各项目的 Model Context Protocol 服务。"))), react.default.createElement("nav", { className: "vsm-mcp-tabs" }, react.default.createElement("button", {
|
|
5553
6520
|
className: tab === "general" ? "active" : "",
|
|
5554
6521
|
onClick: () => setTab("general")
|
|
5555
6522
|
}, "通用"), react.default.createElement("button", {
|
|
5556
6523
|
className: tab === "keys" ? "active" : "",
|
|
5557
6524
|
onClick: () => setTab("keys")
|
|
5558
6525
|
}, "快捷键"), react.default.createElement("button", {
|
|
5559
|
-
className: tab === "
|
|
5560
|
-
onClick: () => setTab("
|
|
5561
|
-
}, "
|
|
5562
|
-
className: tab === "projects" ? "active" : "",
|
|
5563
|
-
onClick: () => setTab("projects")
|
|
5564
|
-
}, "项目 MCP"), react.default.createElement("button", {
|
|
6526
|
+
className: tab === "mcp" ? "active" : "",
|
|
6527
|
+
onClick: () => setTab("mcp")
|
|
6528
|
+
}, "MCP 管理"), react.default.createElement("button", {
|
|
5565
6529
|
className: tab === "lsp" ? "active" : "",
|
|
5566
6530
|
onClick: () => setTab("lsp")
|
|
5567
6531
|
}, "语言服务器"), react.default.createElement("button", {
|
|
5568
6532
|
className: tab === "perf" ? "active" : "",
|
|
5569
6533
|
onClick: () => setTab("perf")
|
|
5570
6534
|
}, "性能优化"), react.default.createElement("button", {
|
|
5571
|
-
className: tab === "market" ? "active" : "",
|
|
5572
|
-
onClick: () => setTab("market")
|
|
5573
|
-
}, "MCP 市场"), react.default.createElement("button", {
|
|
5574
6535
|
className: tab === "compat" ? "active" : "",
|
|
5575
6536
|
onClick: () => setTab("compat")
|
|
5576
|
-
}, "兼容性")), error && react.default.createElement("div", { className: "vsm-mcp-error vsm-mcp-banner" }, error), body
|
|
6537
|
+
}, "兼容性")), error && react.default.createElement("div", { className: "vsm-mcp-error vsm-mcp-banner" }, error), body);
|
|
5577
6538
|
}
|
|
5578
6539
|
/** MCP 新增表单(全局/项目共用)。 */
|
|
5579
6540
|
function McpForm({ title, draft, busy, edit, save, close }) {
|