XFE Git
XFE Studio Git
Git 首页 全局搜索
XFE 主站 文档 NuGet

XFEToolBox

【WPF】XFE工具箱

公开
关注 0 Fork 0 Star 0
UTF-8
<!DOCTYPE html>
<html lang="zh-CN">
<head>
    <meta charset="utf-8" />
    <meta http-equiv="Content-Security-Policy" content="default-src 'self' data: blob:; script-src 'self' 'unsafe-inline' 'unsafe-eval'; style-src 'self' 'unsafe-inline'; font-src 'self' data:; worker-src 'self' blob:;" />
    <meta name="viewport" content="width=device-width, initial-scale=1" />
    <title>XFEToolBox Code Studio</title>
    <style>
        * { box-sizing: border-box; }
        :root {
            --accent: #9898e7;
            --surface: #fbfbfe;
            --surface-subtle: #f5f4fc;
            --surface-hover: #efeffa;
            --border: #e6e5f0;
            --text: #4a4a60;
            --muted: #9292a4;
            --soft-wrap-marker: #7777c9;
        }
        html, body {
            width: 100%; height: 100%; margin: 0; overflow: hidden;
            background: var(--surface); color: var(--text);
            font-family: "Segoe UI", "Microsoft YaHei UI", sans-serif;
        }
        /* HWND WebView2 不接受 WPF 父级 Clip,圆角必须由 Chromium 自身完成裁切。 */
        body {
            display: grid;
            grid-template-rows: 42px 1fr 27px;
            border-radius: 12px;
            clip-path: inset(0 round 12px);
            contain: paint;
        }
        #tabs {
            display: flex; align-items: flex-end; gap: 4px; overflow-x: auto; overflow-y: hidden;
            padding: 5px 7px 0; background: var(--surface-subtle); border-bottom: 1px solid var(--border);
        }
        #tabs::-webkit-scrollbar { height: 8px; }
        #tabs::-webkit-scrollbar-track { background: #ececfa; border-radius: 5px; }
        #tabs::-webkit-scrollbar-thumb { background: #aaa9e9; border: 2px solid #ececfa; border-radius: 5px; }
        #tabs::-webkit-scrollbar-thumb:hover { background: #9292df; }
        .tab {
            min-width: 126px; max-width: 220px; height: 36px; display: flex; align-items: center;
            padding: 0 10px; gap: 7px; border: 1px solid transparent; border-bottom: 0;
            border-radius: 10px 10px 0 0; color: #858598; cursor: pointer; user-select: none;
            font-size: 12px; transition: background .14s ease, color .14s ease, border-color .14s ease;
        }
        .tab.dragging { opacity: .42; }
        .tab.drag-over { background: #e4e3f8; box-shadow: inset 3px 0 var(--accent); }
        .tab:hover { background: #ecebf8; color: #5b5b70; }
        .tab.active { color: #45455b; background: var(--surface); border-color: var(--border); box-shadow: inset 0 3px var(--accent); }
        .tab .file-icon {
            width: 20px; height: 20px; display: grid; place-items: center; flex: 0 0 auto;
            border-radius: 6px; background: #ecebfb; color: #7777c9; font-size: 8px; font-weight: 700;
        }
        .tab .name { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; flex: 1; }
        .tab .dirty { color: var(--accent); width: 10px; text-align: center; font-size: 9px; }
        .tab .close {
            width: 20px; height: 20px; display: grid; place-items: center; flex: 0 0 auto;
            margin-right: -5px; border: 0; border-radius: 6px; background: transparent;
            color: #77778d; font: 15px/1 "Segoe UI"; cursor: pointer; opacity: 0;
        }
        .tab:hover .close, .tab.active .close { opacity: 1; }
        .tab .close:hover { background: #deddf2; color: #3f3f55; }
        #editor { min-width: 0; min-height: 0; }
        #editor .view-line.xfe-soft-wrap-break::after {
            content: "↩";
            position: absolute;
            top: 0;
            right: 5px;
            z-index: 2;
            padding-left: 5px;
            color: var(--soft-wrap-marker);
            background: linear-gradient(90deg, transparent, var(--surface) 38%);
            font-family: "Segoe UI Symbol", "Segoe UI", sans-serif;
            font-size: 12px;
            font-weight: 700;
            pointer-events: none;
        }
        #status {
            display: flex; align-items: center; justify-content: flex-end; gap: 0;
            padding: 0 12px; font-size: 10px; background: #f1f0fa; color: #67677e;
            border-top: 1px solid #e1e0ef; user-select: none;
        }
        #status span { display: flex; align-items: center; height: 100%; padding: 0 10px; }
        #status span + span { border-left: 1px solid #dcdbea; }
        #diagnostics { color: #6969b8; }
        #empty { position: fixed; inset: 42px 0 27px; display: grid; place-items: center; pointer-events: none; }
        .empty-card { text-align: center; color: var(--muted); }
        .empty-icon {
            width: 64px; height: 64px; display: grid; place-items: center; margin: 0 auto 14px;
            border-radius: 20px; background: #f0effc; color: var(--accent); font-size: 18px; font-weight: 700;
        }
        .empty-title { color: #66667b; font-size: 14px; font-weight: 600; }
        .empty-hint { margin-top: 5px; font-size: 11px; }
        .empty-shortcuts { display: flex; justify-content: center; gap: 8px; margin-top: 14px; }
        kbd { padding: 3px 7px; border: 1px solid #deddec; border-radius: 6px; background: white; color: #77778d; font: 10px "Segoe UI"; }

        body.dark {
            --surface: #202033; --surface-subtle: #1b1b2b; --surface-hover: #2a2a40;
            --border: #34344a; --text: #deddec; --muted: #85859b; --soft-wrap-marker: #b6b6f1;
        }
        body.dark #tabs { background: #1b1b2b; }
        body.dark #tabs::-webkit-scrollbar-track { background: #2d2d45; }
        body.dark #tabs::-webkit-scrollbar-thumb { background: #7777c9; border-color: #2d2d45; }
        body.dark #tabs::-webkit-scrollbar-thumb:hover { background: #9898e7; }
        body.dark .tab:hover { background: #29293e; color: #d3d3e4; }
        body.dark .tab.drag-over { background: #353550; }
        body.dark .tab.active { background: #202033; color: white; }
        body.dark .tab .file-icon { background: #31314a; color: #b6b6f1; }
        body.dark .tab .close { color: #b6b5c8; }
        body.dark .tab .close:hover { background: #3b3b55; color: white; }
        body.dark #status { background: #29293f; color: #c0bfd1; border-color: #383850; }
        body.dark #status span + span { border-color: #414157; }
        body.dark #diagnostics { color: #b8b7f0; }
        body.dark .empty-icon { background: #2c2c46; }
        body.dark .empty-title { color: #c4c4d5; }
        body.dark kbd { background: #29293f; border-color: #414157; color: #c2c2d5; }
    </style>
    <script src="./monaco/vs/loader.js"></script>
</head>
<body class="light">
    <div id="tabs"></div>
    <div id="editor"></div>
    <div id="empty">
        <div class="empty-card">
            <div class="empty-icon">&lt;/&gt;</div>
            <div class="empty-title">准备开始编写工具</div>
            <div class="empty-hint">从左侧选择文件,或者在工程中创建一个新文件</div>
            <div class="empty-shortcuts"><kbd>Ctrl + N</kbd><kbd>Ctrl + O</kbd><kbd>Ctrl + S</kbd></div>
        </div>
    </div>
    <div id="status"><span id="diagnostics">✓ 0 个问题</span><span id="position">Ln 1, Col 1</span><span id="language">纯文本</span><span>UTF-8</span></div>
    <script>
        const states = new Map();
        let editor;
        let activePath = null;
        let suppressChange = false;
        let currentTheme = 'light';
        let tabOrder = [];
        let renderAllWhitespace = false;
        let wordWrapEnabled = false;
        let allRegionsFolded = false;
        let softWrapMarkerFrame = 0;
        let softWrapObserver;

        require.config({ paths: { vs: './monaco/vs' } });
        require(['vs/editor/editor.main'], function () {
            monaco.editor.defineTheme('xfe-light', {
                base: 'vs', inherit: true, semanticHighlighting: true,
                rules: [
                    { token: 'keyword', foreground: '7553B5', fontStyle: 'bold' },
                    { token: 'keyword.control', foreground: '8A3FA0', fontStyle: 'bold' },
                    { token: 'keyword.json', foreground: '9A496D', fontStyle: 'bold' },
                    { token: 'namespace', foreground: '7061B4' },
                    { token: 'namespace.cpp', foreground: 'A45178', fontStyle: 'bold' },
                    { token: 'type', foreground: '2F6FA3' },
                    { token: 'class', foreground: '2F6FA3', fontStyle: 'bold' },
                    { token: 'interface', foreground: '087B83', fontStyle: 'bold' },
                    { token: 'struct', foreground: 'A75C36', fontStyle: 'bold' },
                    { token: 'enum', foreground: '99671F', fontStyle: 'bold' },
                    { token: 'attribute', foreground: '9D4D78' },
                    { token: 'method', foreground: '7250A5' },
                    { token: 'property', foreground: 'A14970' },
                    { token: 'field', foreground: '956025' },
                    { token: 'event', foreground: 'B04C69' },
                    { token: 'parameter', foreground: '855E42' },
                    { token: 'variable', foreground: '4D6578' },
                    { token: 'constant', foreground: 'B04D61', fontStyle: 'bold' },
                    { token: 'operator', foreground: '6B6682' },
                    { token: 'delimiter', foreground: '77728B' },
                    { token: 'number', foreground: 'B45B32' },
                    { token: 'number.hex', foreground: 'A35467' },
                    { token: 'string', foreground: '2E7D5B' },
                    { token: 'string.key.json', foreground: '2F6FA3' },
                    { token: 'string.value.json', foreground: '2E7D5B' },
                    { token: 'string.escape', foreground: 'B45B32', fontStyle: 'bold' },
                    { token: 'string.escape.invalid', foreground: 'D13F57', fontStyle: 'bold underline' },
                    { token: 'comment', foreground: '7F8998', fontStyle: 'italic' },
                    { token: 'tag', foreground: '6652B5', fontStyle: 'bold' },
                    { token: 'attribute.name', foreground: 'A04D70' },
                    { token: 'attribute.value', foreground: '2E7D5B' },
                    { token: 'metatag', foreground: '8C5B2F' },
                    { token: 'strong', foreground: 'A14970', fontStyle: 'bold' },
                    { token: 'emphasis', foreground: '2F6FA3', fontStyle: 'italic' },
                    { token: 'link', foreground: '6262C2', fontStyle: 'underline' },
                    { token: 'variable.source', foreground: '985B2F' }
                ],
                colors: {
                    'editor.background': '#FBFBFE', 'editor.foreground': '#444459',
                    'editorLineNumber.foreground': '#B0AFC0', 'editorLineNumber.activeForeground': '#6969B8',
                    'editor.selectionBackground': '#9898E74A', 'editor.inactiveSelectionBackground': '#B9B9E72E',
                    'editor.selectionHighlightBackground': '#9898E724', 'editor.wordHighlightBackground': '#70A7D92A',
                    'editor.wordHighlightStrongBackground': '#B17BB632', 'editor.findMatchBackground': '#F0C66A75',
                    'editor.findMatchHighlightBackground': '#F0C66A36', 'editor.findRangeHighlightBackground': '#9898E71D',
                    'editorCursor.foreground': '#7777C9', 'editorIndentGuide.background1': '#E8E7F0',
                    'editorIndentGuide.activeBackground1': '#C7C6DF', 'editor.lineHighlightBackground': '#F3F2FA',
                    'editorWhitespace.foreground': '#D5D4E2', 'editorWidget.background': '#FFFFFF',
                    'editorWidget.border': '#D9D8E7', 'editorWidget.resizeBorder': '#9898E7',
                    'editorSuggestWidget.background': '#FFFFFF', 'editorSuggestWidget.foreground': '#343449',
                    'editorSuggestWidget.selectedForeground': '#29293D', 'editorSuggestWidget.highlightForeground': '#5555B8',
                    'editorSuggestWidget.focusHighlightForeground': '#4545A7', 'editorSuggestWidget.selectedBackground': '#DCDCF6',
                    'editorSuggestWidget.selectedIconForeground': '#4A4AA5', 'editorSuggestWidgetStatus.foreground': '#66667B',
                    'editorSuggestWidget.border': '#BEBED8', 'editorHoverWidget.background': '#FFFFFF',
                    'editorHoverWidget.foreground': '#343449', 'editorHoverWidget.border': '#BEBED8',
                    'editorHoverWidget.highlightForeground': '#5959B5', 'editorHoverWidget.statusBarBackground': '#F4F3FB',
                    'editorCodeLens.foreground': '#85849B', 'editorLink.activeForeground': '#5858BC',
                    'editorInlayHint.foreground': '#77758D', 'editorInlayHint.background': '#EEEFFA',
                    'editorError.foreground': '#D9435D', 'editorWarning.foreground': '#C77A1E',
                    'editorInfo.foreground': '#3C7FBA', 'editorHint.foreground': '#766AC0',
                    'editorOverviewRuler.errorForeground': '#D9435DB8', 'editorOverviewRuler.warningForeground': '#C77A1EB8',
                    'editorOverviewRuler.infoForeground': '#3C7FBAB8', 'editorLightBulb.foreground': '#B47A19',
                    'editorBracketHighlight.foreground1': '#7857C4', 'editorBracketHighlight.foreground2': '#3279B5',
                    'editorBracketHighlight.foreground3': '#25866C', 'editorBracketHighlight.foreground4': '#C06A34',
                    'editorBracketHighlight.foreground5': '#B04D78', 'editorBracketHighlight.foreground6': '#3D8E98',
                    'editorBracketHighlight.unexpectedBracket.foreground': '#D9435D',
                    'editorBracketPairGuide.background1': '#7857C438', 'editorBracketPairGuide.background2': '#3279B538',
                    'editorBracketPairGuide.background3': '#25866C38', 'editorBracketPairGuide.activeBackground1': '#7857C4A8',
                    'editorBracketPairGuide.activeBackground2': '#3279B5A8', 'editorBracketPairGuide.activeBackground3': '#25866CA8',
                    'editorGutter.foldingControlForeground': '#7777B8', 'editorStickyScroll.background': '#F7F6FC',
                    'editorStickyScroll.border': '#E2E1ED', 'editorStickyScrollHover.background': '#EEEFFA',
                    'minimap.background': '#F8F8FC', 'scrollbarSlider.background': '#9898E77A',
                    'scrollbarSlider.hoverBackground': '#8989D9B3', 'scrollbarSlider.activeBackground': '#7777C9E6'
                }
            });
            monaco.editor.defineTheme('xfe-dark', {
                base: 'vs-dark', inherit: true, semanticHighlighting: true,
                rules: [
                    { token: 'keyword', foreground: 'C69CF4', fontStyle: 'bold' },
                    { token: 'keyword.control', foreground: 'F0A7E1', fontStyle: 'bold' },
                    { token: 'keyword.json', foreground: 'F5A6CD', fontStyle: 'bold' },
                    { token: 'namespace', foreground: 'AFA2E8' },
                    { token: 'namespace.cpp', foreground: 'F5A6CD', fontStyle: 'bold' },
                    { token: 'type', foreground: '7DCFFF' },
                    { token: 'class', foreground: '7DCFFF', fontStyle: 'bold' },
                    { token: 'interface', foreground: '5AD6C8', fontStyle: 'bold' },
                    { token: 'struct', foreground: 'F5A97F', fontStyle: 'bold' },
                    { token: 'enum', foreground: 'F0C57A', fontStyle: 'bold' },
                    { token: 'attribute', foreground: 'F5A6CD' },
                    { token: 'method', foreground: 'D2B0F3' },
                    { token: 'property', foreground: 'FFB3D2' },
                    { token: 'field', foreground: 'E8C07D' },
                    { token: 'event', foreground: 'FF8EAC' },
                    { token: 'parameter', foreground: 'F0C6A8' },
                    { token: 'variable', foreground: 'C6D0F5' },
                    { token: 'constant', foreground: 'FF8EA1', fontStyle: 'bold' },
                    { token: 'operator', foreground: 'B7B0D8' },
                    { token: 'delimiter', foreground: '9390AA' },
                    { token: 'string', foreground: 'A6E3A1' },
                    { token: 'number', foreground: 'FAB387' },
                    { token: 'number.hex', foreground: 'F5A6CD' },
                    { token: 'string.key.json', foreground: '8AADF4' },
                    { token: 'string.value.json', foreground: 'A6E3A1' },
                    { token: 'string.escape', foreground: 'F9C36A', fontStyle: 'bold' },
                    { token: 'string.escape.invalid', foreground: 'FF6F91', fontStyle: 'bold underline' },
                    { token: 'comment', foreground: '8389A1', fontStyle: 'italic' },
                    { token: 'tag', foreground: 'CBA6F7', fontStyle: 'bold' },
                    { token: 'attribute.name', foreground: 'F5C2E7' },
                    { token: 'attribute.value', foreground: 'A6E3A1' },
                    { token: 'metatag', foreground: 'F0C57A' },
                    { token: 'strong', foreground: 'FFB3D2', fontStyle: 'bold' },
                    { token: 'emphasis', foreground: '7DCFFF', fontStyle: 'italic' },
                    { token: 'link', foreground: 'B4BEFE', fontStyle: 'underline' },
                    { token: 'variable.source', foreground: 'F5A97F' }
                ],
                colors: {
                    'editor.background': '#202033', 'editor.foreground': '#d9d9e8',
                    'editorLineNumber.foreground': '#58586b', 'editorLineNumber.activeForeground': '#cdd6f4',
                    'editor.selectionBackground': '#55559A70', 'editor.inactiveSelectionBackground': '#45456A55',
                    'editor.selectionHighlightBackground': '#7373BA35', 'editor.wordHighlightBackground': '#5A9BCD32',
                    'editor.wordHighlightStrongBackground': '#BE7DB43D', 'editor.findMatchBackground': '#D9A84D76',
                    'editor.findMatchHighlightBackground': '#D9A84D3E', 'editor.findRangeHighlightBackground': '#7777C929',
                    'editorCursor.foreground': '#b4befe', 'editorIndentGuide.background1': '#343447',
                    'editorIndentGuide.activeBackground1': '#666684', 'editor.lineHighlightBackground': '#26263A',
                    'editorWhitespace.foreground': '#414159', 'editorWidget.background': '#29293E',
                    'editorWidget.border': '#555574', 'editorWidget.resizeBorder': '#9898E7',
                    'editorSuggestWidget.background': '#29293E', 'editorSuggestWidget.foreground': '#ECECF6',
                    'editorSuggestWidget.selectedForeground': '#FFFFFF', 'editorSuggestWidget.highlightForeground': '#C7C7FF',
                    'editorSuggestWidget.focusHighlightForeground': '#D9D9FF', 'editorSuggestWidget.selectedBackground': '#444466',
                    'editorSuggestWidget.selectedIconForeground': '#D7D7FF', 'editorSuggestWidgetStatus.foreground': '#B8B8CA',
                    'editorSuggestWidget.border': '#555574', 'editorHoverWidget.background': '#29293E',
                    'editorHoverWidget.foreground': '#ECECF6', 'editorHoverWidget.border': '#555574',
                    'editorHoverWidget.highlightForeground': '#C8C8FF', 'editorHoverWidget.statusBarBackground': '#242438',
                    'editorCodeLens.foreground': '#9998B0', 'editorLink.activeForeground': '#C7C7FF',
                    'editorInlayHint.foreground': '#BBB9CC', 'editorInlayHint.background': '#313149',
                    'editorError.foreground': '#FF6F91', 'editorWarning.foreground': '#F0B35B',
                    'editorInfo.foreground': '#69B9F5', 'editorHint.foreground': '#B4A7F5',
                    'editorOverviewRuler.errorForeground': '#FF6F91C8', 'editorOverviewRuler.warningForeground': '#F0B35BC8',
                    'editorOverviewRuler.infoForeground': '#69B9F5C8', 'editorLightBulb.foreground': '#F0C57A',
                    'editorBracketHighlight.foreground1': '#C69CF4', 'editorBracketHighlight.foreground2': '#7DCFFF',
                    'editorBracketHighlight.foreground3': '#5AD6C8', 'editorBracketHighlight.foreground4': '#F5A97F',
                    'editorBracketHighlight.foreground5': '#F5A6CD', 'editorBracketHighlight.foreground6': '#F0C57A',
                    'editorBracketHighlight.unexpectedBracket.foreground': '#FF6F91',
                    'editorBracketPairGuide.background1': '#C69CF44A', 'editorBracketPairGuide.background2': '#7DCFFF4A',
                    'editorBracketPairGuide.background3': '#5AD6C84A', 'editorBracketPairGuide.activeBackground1': '#C69CF4C0',
                    'editorBracketPairGuide.activeBackground2': '#7DCFFFC0', 'editorBracketPairGuide.activeBackground3': '#5AD6C8C0',
                    'editorGutter.foldingControlForeground': '#AFA2E8', 'editorStickyScroll.background': '#222236',
                    'editorStickyScroll.border': '#3D3D55', 'editorStickyScrollHover.background': '#303049',
                    'minimap.background': '#1E1E30',
                    'scrollbarSlider.background': '#9898E77A', 'scrollbarSlider.hoverBackground': '#AAAAEEB3',
                    'scrollbarSlider.activeBackground': '#BDBDF5E6'
                }
            });
            registerSemanticHighlighting();
            editor = monaco.editor.create(document.getElementById('editor'), {
                theme: 'xfe-light', automaticLayout: true, fontFamily: "'Cascadia Code', 'JetBrains Mono', Consolas, monospace",
                fontSize: 13.5, lineHeight: 22, fontLigatures: true, fontWeight: '450',
                minimap: { enabled: true, renderCharacters: false, maxColumn: 100 }, smoothScrolling: true,
                cursorSmoothCaretAnimation: 'on', bracketPairColorization: { enabled: true },
                cursorBlinking: 'smooth', guides: { bracketPairs: true, bracketPairsHorizontal: 'active', highlightActiveBracketPair: true, indentation: true, highlightActiveIndentation: true }, padding: { top: 14, bottom: 12 },
                quickSuggestions: { other: true, comments: false, strings: false },
                suggestOnTriggerCharacters: true, wordBasedSuggestions: 'matchingDocuments', acceptSuggestionOnEnter: 'smart',
                tabCompletion: 'on', snippetSuggestions: 'top', suggestSelection: 'recentlyUsedByPrefix',
                suggest: { showIcons: true, showStatusBar: true, preview: true, previewMode: 'subwordSmart', showInlineDetails: true, localityBonus: true, shareSuggestSelections: true },
                parameterHints: { enabled: true, cycle: true }, hover: { enabled: true, delay: 280, sticky: true },
                inlineSuggest: { enabled: true, showToolbar: 'onHover' }, inlayHints: { enabled: 'on' },
                'semanticHighlighting.enabled': true,
                formatOnPaste: true, formatOnType: true, tabSize: 4, insertSpaces: true,
                autoClosingBrackets: 'always', autoClosingQuotes: 'always', autoSurround: 'languageDefined',
                matchBrackets: 'always', selectionHighlight: true, occurrencesHighlight: 'singleFile',
                renderLineHighlight: 'all', renderWhitespace: 'selection', renderControlCharacters: true, scrollBeyondLastLine: false,
                wordWrap: 'off', wrappingIndent: 'none',
                folding: true, foldingHighlight: true, showFoldingControls: 'mouseover', links: true, colorDecorators: true,
                stickyScroll: { enabled: true, maxLineCount: 4 }, scrollbar: { verticalScrollbarSize: 12, horizontalScrollbarSize: 12 }
            });

            registerCompletions();
            registerLanguageHints();
            registerFormatters();
            editor.onDidChangeCursorPosition(updateStatus);
            editor.onDidChangeModel(scheduleSoftWrapMarkers);
            editor.onDidChangeModelDecorations(scheduleSoftWrapMarkers);
            editor.onDidLayoutChange(scheduleSoftWrapMarkers);
            editor.onDidScrollChange(scheduleSoftWrapMarkers);
            editor.onDidChangeModelContent(() => {
                if (suppressChange || !activePath) return;
                const state = states.get(activePath);
                if (!state) return;
                state.dirty = true;
                renderTabs();
                validateModel(editor.getModel());
                post({ type: 'changed', path: activePath });
            });
            const viewLines = editor.getDomNode()?.querySelector('.view-lines');
            if (viewLines) {
                softWrapObserver = new MutationObserver(scheduleSoftWrapMarkers);
                softWrapObserver.observe(viewLines, { childList: true, subtree: true, attributes: true, attributeFilter: ['style'] });
            }
            registerVisualStudioKeybindings();
            scheduleSoftWrapMarkers();
            post({ type: 'ready' });
        });

        function registerVisualStudioKeybindings() {
            const Ctrl = monaco.KeyMod.CtrlCmd;
            const Shift = monaco.KeyMod.Shift;
            const Alt = monaco.KeyMod.Alt;
            const chord = (first, second) => monaco.KeyMod.chord(first, second);
            const bind = (keybinding, handler) => editor.addCommand(keybinding, handler);
            const ctrlKey = key => Ctrl | key;
            const ctrlShiftKey = key => Ctrl | Shift | key;

            // Visual Studio:文件、项目、生成与运行。
            bind(ctrlKey(monaco.KeyCode.KeyS), requestHostSave);
            bind(ctrlShiftKey(monaco.KeyCode.KeyS), requestHostSave);
            bind(ctrlKey(monaco.KeyCode.KeyN), () => post({ type: 'command', command: 'newFile' }));
            bind(ctrlShiftKey(monaco.KeyCode.KeyA), () => post({ type: 'command', command: 'newFile' }));
            bind(ctrlShiftKey(monaco.KeyCode.KeyN), () => post({ type: 'command', command: 'newProject' }));
            bind(ctrlShiftKey(monaco.KeyCode.KeyO), () => post({ type: 'command', command: 'openProject' }));
            bind(ctrlShiftKey(monaco.KeyCode.KeyB), () => post({ type: 'command', command: 'build' }));
            bind(ctrlShiftKey(monaco.KeyCode.KeyE), () => post({ type: 'command', command: 'export' }));
            bind(monaco.KeyCode.F5, () => post({ type: 'command', command: 'run' }));
            bind(Ctrl | monaco.KeyCode.F5, () => post({ type: 'command', command: 'run' }));
            bind(ctrlShiftKey(monaco.KeyCode.KeyW), () => post({ type: 'command', command: 'preview' }));

            // Visual Studio:格式化、代码清理、注释及快速信息。
            bind(chord(ctrlKey(monaco.KeyCode.KeyK), ctrlKey(monaco.KeyCode.KeyD)), () => runEditorAction('editor.action.formatDocument'));
            bind(chord(ctrlKey(monaco.KeyCode.KeyK), ctrlKey(monaco.KeyCode.KeyF)), formatCurrentSelection);
            bind(chord(ctrlKey(monaco.KeyCode.KeyK), ctrlKey(monaco.KeyCode.KeyE)), cleanupCurrentDocument);
            bind(chord(ctrlKey(monaco.KeyCode.KeyK), ctrlKey(monaco.KeyCode.KeyC)), () => runEditorAction('editor.action.addCommentLine'));
            bind(chord(ctrlKey(monaco.KeyCode.KeyK), ctrlKey(monaco.KeyCode.KeyU)), () => runEditorAction('editor.action.removeCommentLine'));
            bind(chord(ctrlKey(monaco.KeyCode.KeyK), ctrlKey(monaco.KeyCode.KeyI)), () => runEditorAction('editor.action.showHover'));

            // Visual Studio:折叠、括号跳转、空白及自动换行。
            bind(chord(ctrlKey(monaco.KeyCode.KeyM), ctrlKey(monaco.KeyCode.KeyM)), () => runEditorAction('editor.toggleFold'));
            bind(chord(ctrlKey(monaco.KeyCode.KeyM), ctrlKey(monaco.KeyCode.KeyA)), () => { allRegionsFolded = true; return runEditorAction('editor.foldAll'); });
            bind(chord(ctrlKey(monaco.KeyCode.KeyM), ctrlKey(monaco.KeyCode.KeyO)), () => { allRegionsFolded = true; return runEditorAction('editor.foldAll'); });
            bind(chord(ctrlKey(monaco.KeyCode.KeyM), ctrlKey(monaco.KeyCode.KeyX)), () => { allRegionsFolded = false; return runEditorAction('editor.unfoldAll'); });
            bind(chord(ctrlKey(monaco.KeyCode.KeyM), ctrlKey(monaco.KeyCode.KeyS)), () => runEditorAction('editor.fold'));
            bind(chord(ctrlKey(monaco.KeyCode.KeyM), ctrlKey(monaco.KeyCode.KeyE)), () => runEditorAction('editor.unfold'));
            bind(chord(ctrlKey(monaco.KeyCode.KeyM), ctrlKey(monaco.KeyCode.KeyL)), toggleAllFolding);
            bind(chord(ctrlKey(monaco.KeyCode.KeyE), ctrlKey(monaco.KeyCode.KeyW)), toggleWordWrap);
            bind(chord(ctrlKey(monaco.KeyCode.KeyR), ctrlKey(monaco.KeyCode.KeyW)), toggleWhitespace);
            bind(ctrlKey(monaco.KeyCode.BracketRight), () => runEditorAction('editor.action.jumpToBracket'));
            bind(ctrlShiftKey(monaco.KeyCode.BracketRight), () => runEditorAction('editor.action.selectToBracket'));

            // Visual Studio:补全、参数提示、大小写、重复行与插入行。
            bind(ctrlKey(monaco.KeyCode.Space), () => runEditorAction('editor.action.triggerSuggest'));
            bind(ctrlKey(monaco.KeyCode.KeyJ), () => runEditorAction('editor.action.triggerSuggest'));
            bind(ctrlShiftKey(monaco.KeyCode.Space), () => runEditorAction('editor.action.triggerParameterHints'));
            bind(ctrlKey(monaco.KeyCode.KeyD), () => runEditorAction('editor.action.duplicateSelection'));
            bind(ctrlKey(monaco.KeyCode.KeyU), () => runEditorAction('editor.action.transformToLowercase'));
            bind(ctrlShiftKey(monaco.KeyCode.KeyU), () => runEditorAction('editor.action.transformToUppercase'));
            bind(ctrlKey(monaco.KeyCode.Enter), () => runEditorAction('editor.action.insertLineBefore'));
            bind(ctrlShiftKey(monaco.KeyCode.Enter), () => runEditorAction('editor.action.insertLineAfter'));
            bind(Shift | Alt | monaco.KeyCode.KeyT, () => runEditorAction('editor.action.transpose'));
        }

        function runEditorAction(actionId) {
            const action = editor?.getAction(actionId);
            if (action) return action.run();
            editor?.trigger('xfe.visualStudioKeybindings', actionId, null);
            return Promise.resolve();
        }

        async function cleanupCurrentDocument() {
            await runEditorAction('editor.action.trimTrailingWhitespace');
            await runEditorAction('editor.action.formatDocument');
        }

        function formatCurrentSelection() {
            const model = editor?.getModel();
            const selection = editor?.getSelection();
            if (!model || !selection || selection.isEmpty()) return runEditorAction('editor.action.formatDocument');
            const language = model.getLanguageId();
            const selectedText = model.getValueInRange(selection);
            const formatted = language === 'xml'
                ? formatXml(selectedText)
                : language === 'csharp'
                    ? formatCSharp(selectedText)
                    : selectedText;
            if (formatted === selectedText) return;
            editor.executeEdits('xfe.formatSelection', [{ range: selection, text: preserveSelectionIndentation(model, selection, formatted), forceMoveMarkers: true }]);
        }

        function preserveSelectionIndentation(model, selection, formatted) {
            const line = model.getLineContent(selection.startLineNumber);
            const baseIndent = line.slice(0, Math.max(0, selection.startColumn - 1)).match(/^\s*/)?.[0] || '';
            return formatted.split('\n').map((value, index) => index === 0 || !value ? value : baseIndent + value).join('\n');
        }

        function toggleAllFolding() {
            allRegionsFolded = !allRegionsFolded;
            return runEditorAction(allRegionsFolded ? 'editor.foldAll' : 'editor.unfoldAll');
        }

        function toggleWordWrap() {
            wordWrapEnabled = !wordWrapEnabled;
            editor.updateOptions({ wordWrap: wordWrapEnabled ? 'on' : 'off', wrappingIndent: 'none' });
            scheduleSoftWrapMarkers();
        }

        function scheduleSoftWrapMarkers() {
            if (softWrapMarkerFrame) cancelAnimationFrame(softWrapMarkerFrame);
            softWrapMarkerFrame = requestAnimationFrame(() => {
                softWrapMarkerFrame = 0;
                renderSoftWrapMarkers();
            });
        }

        function renderSoftWrapMarkers() {
            const viewLines = document.querySelectorAll('#editor .view-lines > .view-line');
            viewLines.forEach(line => line.classList.remove('xfe-soft-wrap-break'));
            if (!wordWrapEnabled || !editor || !viewLines.length) return;

            const viewModel = editor._getViewModel?.();
            const viewLayout = viewModel?.viewLayout;
            const converter = viewModel?.coordinatesConverter;
            if (!viewLayout || !converter) return;

            const viewLineCount = viewModel.getLineCount();
            viewLines.forEach(line => {
                const top = Number.parseFloat(line.style.top);
                if (!Number.isFinite(top)) return;

                const viewLineNumber = viewLayout.getLineNumberAtVerticalOffset(top + 1);
                if (viewLineNumber < 1 || viewLineNumber >= viewLineCount) return;

                const current = converter.convertViewPositionToModelPosition(new monaco.Position(viewLineNumber, 1));
                const next = converter.convertViewPositionToModelPosition(new monaco.Position(viewLineNumber + 1, 1));
                if (current.lineNumber === next.lineNumber)
                    line.classList.add('xfe-soft-wrap-break');
            });
        }

        function toggleWhitespace() {
            renderAllWhitespace = !renderAllWhitespace;
            editor.updateOptions({ renderWhitespace: renderAllWhitespace ? 'all' : 'selection' });
        }

        function languageFor(path) {
            const lower = path.toLowerCase();
            if (lower.endsWith('.cs')) return 'csharp';
            if (lower.endsWith('.xaml') || lower.endsWith('.xml') || lower.endsWith('.resx') || lower.endsWith('.config') || lower.endsWith('.props') || lower.endsWith('.targets')) return 'xml';
            if (lower.endsWith('.json')) return 'json';
            if (['.md', '.markdown', '.mdown', '.mkd', '.mkdn'].some(extension => lower.endsWith(extension))) return 'markdown';
            if (lower.endsWith('.yml') || lower.endsWith('.yaml')) return 'yaml';
            return 'plaintext';
        }

        function friendlyLanguage(id) { return ({ csharp: 'C#', xml: 'XAML / XML', json: 'JSON', markdown: 'Markdown', yaml: 'YAML', plaintext: '纯文本' })[id] || id; }

        function loadWorkspace(payload) {
            states.forEach(state => state.model.dispose());
            states.clear();
            tabOrder = [];
            activePath = null;
            (payload.files || []).forEach(file => {
                const uri = monaco.Uri.parse('inmemory://xfetoolbox/' + encodeURI(file.path));
                const model = monaco.editor.createModel(file.content || '', languageFor(file.path), uri);
                states.set(file.path, { model, dirty: false });
                tabOrder.push(file.path);
            });
            renderTabs();
            const first = payload.activePath && states.has(payload.activePath) ? payload.activePath : states.keys().next().value;
            if (first) activateFile(first);
            document.getElementById('empty').style.display = first ? 'none' : 'grid';
        }

        function upsertFile(file) {
            if (!states.has(file.path)) {
                const uri = monaco.Uri.parse('inmemory://xfetoolbox/' + encodeURI(file.path));
                states.set(file.path, { model: monaco.editor.createModel(file.content || '', languageFor(file.path), uri), dirty: true });
            }
            if (!tabOrder.includes(file.path)) tabOrder.push(file.path);
            activateFile(file.path);
            renderTabs();
        }

        function removeFile(path) {
            const state = states.get(path);
            if (!state) return;
            state.model.dispose(); states.delete(path);
            tabOrder = tabOrder.filter(item => item !== path);
            if (activePath === path) {
                activePath = null;
                const next = tabOrder[0];
                if (next) activateFile(next); else { editor.setModel(null); updateEmptyStatus(); }
            }
            renderTabs();
            document.getElementById('empty').style.display = states.size ? 'none' : 'grid';
        }

        function activateFile(path) {
            const state = states.get(path);
            if (!state || !editor) return;
            if (!tabOrder.includes(path)) tabOrder.push(path);
            activePath = path;
            suppressChange = true;
            editor.setModel(state.model);
            suppressChange = false;
            editor.focus();
            renderTabs(); updateStatus(); validateModel(state.model);
            document.getElementById('empty').style.display = 'none';
            post({ type: 'activated', path });
        }

        function renderTabs() {
            const tabs = document.getElementById('tabs'); tabs.innerHTML = '';
            tabOrder.filter(path => states.has(path)).forEach(path => {
                const state = states.get(path);
                const tab = document.createElement('div'); tab.className = 'tab' + (path === activePath ? ' active' : '');
                tab.title = `${path}\n拖动可调整顺序`; tab.onclick = () => activateFile(path); tab.draggable = true;
                tab.ondragstart = event => beginTabDrag(event, path, tab);
                tab.ondragover = event => dragTabOver(event, tab);
                tab.ondragleave = () => tab.classList.remove('drag-over');
                tab.ondrop = event => dropTab(event, path, tab);
                tab.ondragend = () => clearTabDragStyles();
                const icon = document.createElement('span'); icon.className = 'file-icon'; icon.textContent = iconFor(path);
                const name = document.createElement('span'); name.className = 'name'; name.textContent = path.split('/').pop();
                const dirty = document.createElement('span'); dirty.className = 'dirty'; dirty.textContent = state.dirty ? '●' : '';
                const close = document.createElement('button'); close.className = 'close'; close.type = 'button'; close.title = '关闭选项卡'; close.textContent = '×';
                close.onclick = event => { event.stopPropagation(); closeFileTab(path); };
                tab.append(icon, name, dirty, close); tabs.appendChild(tab);
            });
        }

        function beginTabDrag(event, path, tab) {
            event.dataTransfer.effectAllowed = 'move'; event.dataTransfer.setData('text/x-xfe-tab', path);
            requestAnimationFrame(() => tab.classList.add('dragging'));
        }
        function dragTabOver(event, tab) { event.preventDefault(); event.dataTransfer.dropEffect = 'move'; tab.classList.add('drag-over'); }
        function dropTab(event, targetPath, tab) {
            event.preventDefault();
            const sourcePath = event.dataTransfer.getData('text/x-xfe-tab');
            tab.classList.remove('drag-over');
            if (!sourcePath || sourcePath === targetPath) return;
            const sourceIndex = tabOrder.indexOf(sourcePath); const targetIndex = tabOrder.indexOf(targetPath);
            if (sourceIndex < 0 || targetIndex < 0) return;
            tabOrder.splice(sourceIndex, 1); tabOrder.splice(targetIndex, 0, sourcePath); renderTabs();
        }
        function clearTabDragStyles() { document.querySelectorAll('.tab').forEach(tab => tab.classList.remove('dragging', 'drag-over')); }
        function closeFileTab(path) {
            const index = tabOrder.indexOf(path); if (index < 0) return;
            tabOrder.splice(index, 1);
            if (activePath === path) {
                const next = tabOrder[Math.min(index, tabOrder.length - 1)];
                activePath = null;
                if (next) activateFile(next); else { editor.setModel(null); updateEmptyStatus(); }
            }
            renderTabs();
        }
        function updateEmptyStatus() {
            document.getElementById('empty').style.display = activePath ? 'none' : 'grid';
            if (!activePath) {
                document.getElementById('position').textContent = 'Ln 1, Col 1';
                document.getElementById('language').textContent = '纯文本';
                document.getElementById('diagnostics').textContent = '✓ 0 个问题';
            }
        }

        function iconFor(path) {
            const lower = path.toLowerCase();
            if (lower.endsWith('.cs')) return 'C#';
            if (lower.endsWith('.xaml') || lower.endsWith('.xml')) return '<>';
            if (lower.endsWith('.json')) return '{}';
            if (['.md', '.markdown', '.mdown', '.mkd', '.mkdn'].some(extension => lower.endsWith(extension))) return 'M';
            if (lower.endsWith('.yml') || lower.endsWith('.yaml')) return 'Y';
            return '·';
        }

        function requestHostSave() { post({ type: 'saveWorkspace', files: collectFiles() }); }
        function collectFiles() { return Array.from(states, ([path, state]) => ({ path, content: state.model.getValue() })); }
        function getWorkspaceJson() { return JSON.stringify(collectFiles()); }
        function getFileContent(path) { return states.get(path)?.model.getValue() ?? null; }
        function setFileContent(path, content) {
            const state = states.get(path);
            if (!state) return false;
            const nextContent = content ?? '';
            if (state.model.getValue() !== nextContent) state.model.setValue(nextContent);
            return true;
        }
        function markSaved() { states.forEach(state => state.dirty = false); renderTabs(); }

        function updateStatus() {
            if (!editor || !editor.getModel()) return;
            const pos = editor.getPosition();
            document.getElementById('position').textContent = `Ln ${pos.lineNumber}, Col ${pos.column}`;
            document.getElementById('language').textContent = friendlyLanguage(editor.getModel().getLanguageId());
        }

        function validateModel(model) {
            if (!model) return;
            const markers = [];
            const text = model.getValue();
            if (model.getLanguageId() === 'csharp') {
                const stack = [];
                const pairs = { '{': '}', '[': ']', '(': ')' };
                const closing = new Set(Object.values(pairs));
                const lines = model.getLinesContent();
                const lexicalLines = monaco.editor.tokenize(text, 'csharp');
                for (let lineIndex = 0; lineIndex < lines.length; lineIndex++) {
                    const line = lines[lineIndex];
                    const tokens = lexicalLines[lineIndex] || [];
                    for (let columnIndex = 0; columnIndex < line.length; columnIndex++) {
                        if (/comment|string/.test(lexicalTypeAt(tokens, columnIndex))) continue;
                        const character = line[columnIndex];
                        if (pairs[character]) {
                            stack.push({ character, line: lineIndex + 1, column: columnIndex + 1 });
                            continue;
                        }
                        if (!closing.has(character)) continue;
                        const opening = stack[stack.length - 1];
                        if (!opening) {
                            markers.push(marker(`没有与“${character}”匹配的左括号`, lineIndex + 1, columnIndex + 1));
                        } else if (pairs[opening.character] !== character) {
                            markers.push(marker(`此处应使用“${pairs[opening.character]}”来匹配第 ${opening.line} 行的“${opening.character}”`, lineIndex + 1, columnIndex + 1));
                            stack.pop();
                        } else {
                            stack.pop();
                        }
                    }
                }
                stack.forEach(opening => markers.push(marker(`缺少与“${opening.character}”匹配的“${pairs[opening.character]}”`, opening.line, opening.column)));
            } else if (model.getLanguageId() === 'xml' && text.trim()) {
                const parsed = new DOMParser().parseFromString(text, 'application/xml');
                const error = parsed.querySelector('parsererror');
                if (error) {
                    const message = error.textContent.split('\n')[0];
                    const line = Number(/(?:line|行)\s*[::]?\s*(\d+)/i.exec(error.textContent)?.[1] || 1);
                    const column = Number(/(?:column|列)\s*[::]?\s*(\d+)/i.exec(error.textContent)?.[1] || 1);
                    markers.push(marker(`XAML / XML 结构错误:${message}`, line, column));
                }
            } else if (model.getLanguageId() === 'json' && text.trim()) {
                try {
                    JSON.parse(text);
                } catch (error) {
                    const offset = Number(/position\s+(\d+)/i.exec(error.message)?.[1] || 0);
                    const position = model.getPositionAt(Math.min(offset, text.length));
                    markers.push(marker(`JSON 语法错误:${error.message}`, position.lineNumber, position.column));
                }
            }
            monaco.editor.setModelMarkers(model, 'xfe-basic-diagnostics', markers);
            document.getElementById('diagnostics').textContent = markers.length ? `⚠ ${markers.length} 个问题` : '✓ 0 个问题';
        }

        function marker(message, line, column) {
            return { severity: monaco.MarkerSeverity.Error, source: 'XFE 编辑器', message, startLineNumber: line, startColumn: column, endLineNumber: line, endColumn: column + 1 };
        }

        function registerSemanticHighlighting() {
            const legend = {
                tokenTypes: ['namespace', 'type', 'class', 'interface', 'struct', 'enum', 'attribute', 'method', 'property', 'field', 'event', 'parameter', 'variable', 'constant'],
                tokenModifiers: ['declaration', 'readonly', 'static', 'async']
            };
            const typeIndexes = new Map(legend.tokenTypes.map((type, index) => [type, index]));
            monaco.languages.registerDocumentSemanticTokensProvider('csharp', {
                getLegend: () => legend,
                provideDocumentSemanticTokens(model, _lastResultId, cancellationToken) {
                    const values = [];
                    const lines = model.getLinesContent();
                    const lexicalLines = monaco.editor.tokenize(model.getValue(), 'csharp');
                    for (let lineIndex = 0; lineIndex < lines.length; lineIndex++) {
                        if (cancellationToken.isCancellationRequested) return null;
                        const line = lines[lineIndex];
                        const lexicalTokens = lexicalLines[lineIndex] || [];
                        const identifierPattern = /@?[A-Za-z_]\w*/g;
                        let match;
                        while ((match = identifierPattern.exec(line)) !== null) {
                            const lexicalType = lexicalTypeAt(lexicalTokens, match.index);
                            if (/comment|string|number|keyword|directive/.test(lexicalType)) continue;
                            const rawWord = match[0];
                            const word = rawWord.startsWith('@') ? rawWord.slice(1) : rawWord;
                            const before = line.slice(0, match.index);
                            const after = line.slice(match.index + rawWord.length);
                            const classification = classifyCSharpIdentifier(word, before, after);
                            if (!classification || !typeIndexes.has(classification.type)) continue;
                            let modifierMask = 0;
                            classification.modifiers.forEach(modifier => {
                                const modifierIndex = legend.tokenModifiers.indexOf(modifier);
                                if (modifierIndex >= 0) modifierMask |= (1 << modifierIndex);
                            });
                            values.push({ line: lineIndex, start: match.index, length: rawWord.length, type: typeIndexes.get(classification.type), modifiers: modifierMask });
                        }
                    }
                    values.sort((left, right) => left.line - right.line || left.start - right.start);
                    const data = [];
                    let previousLine = 0;
                    let previousStart = 0;
                    values.forEach(value => {
                        const deltaLine = value.line - previousLine;
                        const deltaStart = deltaLine === 0 ? value.start - previousStart : value.start;
                        data.push(deltaLine, deltaStart, value.length, value.type, value.modifiers);
                        previousLine = value.line;
                        previousStart = value.start;
                    });
                    return { data: new Uint32Array(data) };
                },
                releaseDocumentSemanticTokens() { }
            });
        }

        function lexicalTypeAt(tokens, offset) {
            let type = '';
            for (const token of tokens) {
                if (token.offset > offset) break;
                type = token.type || '';
            }
            return type;
        }

        function classifyCSharpIdentifier(word, before, after) {
            const modifiers = [];
            const addDeclarationModifiers = () => {
                modifiers.push('declaration');
                if (/\breadonly\b/.test(before)) modifiers.push('readonly');
                if (/\bstatic\b/.test(before)) modifiers.push('static');
                if (/\basync\b/.test(before)) modifiers.push('async');
            };
            if (/\b(?:namespace|using)\s+(?:global::)?(?:[A-Za-z_]\w*\.)*$/.test(before)) return { type: 'namespace', modifiers };
            if (/\b(?:class|record)\s+$/.test(before)) { addDeclarationModifiers(); return { type: 'class', modifiers }; }
            if (/\binterface\s+$/.test(before)) { addDeclarationModifiers(); return { type: 'interface', modifiers }; }
            if (/\bstruct\s+$/.test(before)) { addDeclarationModifiers(); return { type: 'struct', modifiers }; }
            if (/\benum\s+$/.test(before)) { addDeclarationModifiers(); return { type: 'enum', modifiers }; }

            const lastAttributeOpen = before.lastIndexOf('[');
            const lastAttributeClose = before.lastIndexOf(']');
            if (lastAttributeOpen > lastAttributeClose && /^[A-Z]/.test(word)) return { type: 'attribute', modifiers };

            const declarationPrefix = /\b(?:var|bool|byte|sbyte|short|ushort|int|uint|long|ulong|float|double|decimal|char|string|object|dynamic|[A-Z][A-Za-z0-9_.]*(?:<[^;=(){}]+>)?(?:\[\])?)[?]?\s+$/;
            const isDeclaration = declarationPrefix.test(before);
            if (/\bevent\s+[A-Za-z_][\w.<>,?\[\]]*\s+$/.test(before)) { addDeclarationModifiers(); return { type: 'event', modifiers }; }
            if (/\bconst\s+[A-Za-z_][\w.<>,?\[\]]*\s+$/.test(before)) { addDeclarationModifiers(); modifiers.push('readonly'); return { type: 'constant', modifiers }; }
            if (isDeclaration && /^\s*(?:\{|=>)/.test(after)) { addDeclarationModifiers(); return { type: 'property', modifiers }; }

            if (/^\s*(?:<[^>\r\n]+>)?\s*\(/.test(after)) {
                if (isDeclaration) addDeclarationModifiers();
                return { type: 'method', modifiers };
            }
            if (/\.\s*$/.test(before)) return { type: 'property', modifiers };
            if (isDeclaration) {
                addDeclarationModifiers();
                const lastOpenParenthesis = before.lastIndexOf('(');
                const lastCloseParenthesis = before.lastIndexOf(')');
                if (lastOpenParenthesis > lastCloseParenthesis) return { type: 'parameter', modifiers };
                return { type: word.startsWith('_') ? 'field' : 'variable', modifiers };
            }
            if (/^[A-Z][A-Z0-9_]+$/.test(word)) return { type: 'constant', modifiers: ['readonly'] };
            if (word.startsWith('_')) return { type: 'field', modifiers };
            if (/^[A-Z]/.test(word)) return { type: 'type', modifiers };
            return { type: 'variable', modifiers };
        }

        function registerCompletions() {
            const K = monaco.languages.CompletionItemKind;
            monaco.languages.registerCompletionItemProvider('csharp', {
                triggerCharacters: ['.', '<'],
                provideCompletionItems: () => ({ suggestions: [
                    ...['public', 'private', 'protected', 'internal', 'static', 'sealed', 'abstract', 'partial',
                        'class', 'struct', 'interface', 'enum', 'record', 'namespace', 'using', 'var', 'string',
                        'int', 'long', 'double', 'decimal', 'bool', 'object', 'void', 'async', 'await', 'return',
                        'new', 'this', 'base', 'null', 'true', 'false', 'get', 'set', 'init', 'required', 'readonly',
                        'virtual', 'override', 'event', 'delegate', 'if', 'else', 'switch', 'case', 'for', 'foreach',
                        'while', 'try', 'catch', 'finally', 'throw'].map(keyword => word(keyword, K.Keyword, 'C# 关键字', csharpKeywordDocumentation(keyword))),
                    snippet('wpf-page', 'WPF UserControl 模板', 'public partial class ${1:ToolView} : UserControl\n{\n    public ${1:ToolView}()\n    {\n        InitializeComponent();\n    }\n}', K.Snippet),
                    snippet('observable-property', 'MVVM 可观察属性', '[ObservableProperty]\nprivate ${1:string} ${2:value} = ${3:string.Empty};', K.Snippet),
                    snippet('relay-command', 'MVVM 命令', '[RelayCommand]\nprivate void ${1:Execute}()\n{\n    ${0}\n}', K.Snippet),
                    word('UserControl', K.Class, 'WPF 控件基类', '用于创建可复用 WPF 用户控件。'),
                    word('DependencyProperty', K.Class, 'WPF 依赖属性', '支持绑定、样式、动画和属性值继承。'),
                    word('ObservableObject', K.Class, 'MVVM 可观察对象', 'CommunityToolkit.Mvvm 提供的属性通知基类。'),
                    word('RelayCommand', K.Class, 'MVVM 命令', '将方法包装为可供界面绑定的命令。'),
                    word('ICommand', K.Interface, '命令接口', 'WPF 命令绑定使用的标准接口。'),
                    word('InitializeComponent', K.Method, '加载 XAML', '加载并连接当前页面或控件对应的 XAML。'),
                    word('DataContext', K.Property, '绑定数据源', '当前元素及其子元素默认使用的数据绑定源。'),
                    word('Dispatcher', K.Property, 'UI 调度器', '将操作调度到拥有当前对象的 UI 线程。')
                ] })
            });
            monaco.languages.registerCompletionItemProvider('xml', {
                triggerCharacters: ['<', ' ', '{'],
                provideCompletionItems: () => ({ suggestions: [
                    snippet('Grid', 'Grid 布局', '<Grid>\n    ${0}\n</Grid>', K.Snippet),
                    snippet('StackPanel', 'StackPanel 布局', '<StackPanel Orientation="${1:Vertical}">\n    ${0}\n</StackPanel>', K.Snippet),
                    snippet('TextBlock', '文本控件', '<TextBlock Text="${1:文本}" />', K.Snippet),
                    snippet('Button', '按钮控件', '<Button Content="${1:按钮}" Command="{Binding ${2:Command}}" />', K.Snippet),
                    snippet('Binding', '数据绑定', '{Binding ${1:Property}, Mode=${2:OneWay}}', K.Snippet),
                    word('Grid.Row', K.Property, 'Grid 附加属性', '指定元素所在的网格行。'),
                    word('Grid.Column', K.Property, 'Grid 附加属性', '指定元素所在的网格列。'),
                    word('HorizontalAlignment', K.Property, '布局属性', '设置元素的水平对齐方式。'),
                    word('VerticalAlignment', K.Property, '布局属性', '设置元素的垂直对齐方式。'),
                    word('Margin', K.Property, '布局属性', '设置元素边界外侧的留白。'),
                    word('Padding', K.Property, '布局属性', '设置控件边界与内容之间的留白。')
                ] })
            });
        }

        function csharpKeywordDocumentation(keyword) {
            return ({
                async: '声明异步方法或匿名函数,通常与 await 配合使用。',
                await: '异步等待任务完成,而不阻塞当前线程。',
                record: '声明具有值相等性的数据类型。',
                required: '要求对象初始化时为成员赋值。',
                init: '限制属性只能在初始化阶段设置。',
                partial: '把同一个类型或成员的实现拆分到多个源文件。',
                using: '导入命名空间,或声明可自动释放的资源。',
                namespace: '声明用于组织类型的命名空间。',
                var: '由编译器推断局部变量的静态类型。',
                override: '重写基类中可重写的成员。',
                readonly: '限制字段只能在声明处或构造函数中赋值。',
                sealed: '阻止类型被继承,或阻止成员继续被重写。'
            })[keyword] || `插入 C# 关键字 \`${keyword}\`。`;
        }

        function registerLanguageHints() {
            const csharpHints = {
                async: '将方法标记为异步方法。通常与 `await` 配合使用,返回 `Task`、`Task<T>` 或 `ValueTask`。',
                await: '异步等待任务完成,同时不会阻塞当前线程。只能用于异步方法或异步匿名函数中。',
                var: '让编译器根据右侧表达式推断局部变量的静态类型,并不代表动态类型。',
                record: '声明以值相等性为核心的数据类型,适合不可变数据模型和消息对象。',
                required: '要求对象初始化时必须为该字段或属性赋值。',
                init: '仅允许在对象初始化期间设置属性,之后保持只读语义。',
                partial: '允许把同一个类型、方法或属性的定义拆分到多个源文件中。',
                using: '导入命名空间,或声明会在作用域结束时自动释放的对象。',
                namespace: '为类型组织逻辑作用域,避免名称冲突。',
                UserControl: 'WPF 可复用用户控件基类,通常与对应的 XAML 文件共同组成界面。',
                DependencyProperty: 'WPF 依赖属性,支持绑定、样式、动画、继承及默认值元数据。',
                ObservableObject: 'CommunityToolkit.Mvvm 提供的可观察对象基类,可简化属性变更通知。',
                RelayCommand: '把方法包装为可绑定命令,适合 MVVM 中的按钮和交互行为。',
                DataContext: 'WPF 绑定默认使用的数据源对象,并可沿可视树或逻辑树继承。',
                Dispatcher: '用于把操作调度回拥有当前 UI 对象的线程。',
                InitializeComponent: '加载并连接编译后的 XAML 组件,应在页面或控件构造函数中调用。'
            };
            const xamlHints = {
                Binding: '创建 WPF 数据绑定。可设置 `Path`、`Mode`、`Converter` 与 `UpdateSourceTrigger`。',
                DataContext: '设置当前元素及其子元素默认使用的绑定数据源。',
                'Grid.Row': '指定元素所在的 Grid 行,索引从 0 开始。',
                'Grid.Column': '指定元素所在的 Grid 列,索引从 0 开始。',
                HorizontalAlignment: '控制元素在可用水平空间中的对齐方式。',
                VerticalAlignment: '控制元素在可用垂直空间中的对齐方式。',
                Margin: '设置元素边界外侧的留白,顺序为左、上、右、下。',
                Padding: '设置控件边界与其内容之间的内侧留白。'
            };
            registerHoverHints('csharp', csharpHints, 'C#');
            registerHoverHints('xml', xamlHints, 'XAML');

            const signatures = {
                SetValue: { label: 'void SetValue(DependencyProperty dp, object value)', documentation: '为当前依赖对象设置依赖属性值。', parameters: ['要设置的依赖属性标识符。', '新的属性值。'] },
                GetValue: { label: 'object GetValue(DependencyProperty dp)', documentation: '读取当前依赖对象上的依赖属性值。', parameters: ['要读取的依赖属性标识符。'] },
                OnPropertyChanged: { label: 'void OnPropertyChanged(string? propertyName = null)', documentation: '通知绑定系统指定属性已经发生变化。', parameters: ['发生变化的属性名。'] },
                WriteLine: { label: 'void Console.WriteLine(string? value)', documentation: '将文本及换行符写入标准输出。', parameters: ['要写出的文本或对象。'] },
                Delay: { label: 'Task Task.Delay(int millisecondsDelay)', documentation: '创建一个在指定时间后完成的异步任务。', parameters: ['延迟的毫秒数。'] },
                IsNullOrWhiteSpace: { label: 'bool string.IsNullOrWhiteSpace(string? value)', documentation: '判断字符串是否为 null、空字符串或仅包含空白字符。', parameters: ['要检查的字符串。'] },
                Show: { label: 'MessageBoxResult MessageBox.Show(string messageBoxText, string caption)', documentation: '显示消息框。', parameters: ['消息正文。', '窗口标题。'] }
            };
            monaco.languages.registerSignatureHelpProvider('csharp', {
                signatureHelpTriggerCharacters: ['(', ','],
                signatureHelpRetriggerCharacters: [','],
                provideSignatureHelp(model, position) {
                    const linePrefix = model.getLineContent(position.lineNumber).slice(0, position.column - 1);
                    const call = /([A-Za-z_]\w*)\s*\(([^()]*)$/.exec(linePrefix);
                    if (!call || !signatures[call[1]]) return null;
                    const definition = signatures[call[1]];
                    const activeParameter = Math.min((call[2].match(/,/g) || []).length, definition.parameters.length - 1);
                    return {
                        value: {
                            signatures: [{
                                label: definition.label,
                                documentation: definition.documentation,
                                parameters: definition.parameters.map((documentation, index) => ({ label: signatureParameterLabel(definition.label, index), documentation }))
                            }],
                            activeSignature: 0,
                            activeParameter: Math.max(0, activeParameter)
                        },
                        dispose() { }
                    };
                }
            });
        }

        function registerHoverHints(language, hints, label) {
            monaco.languages.registerHoverProvider(language, {
                provideHover(model, position) {
                    const range = model.getWordAtPosition(position);
                    if (!range) return null;
                    let name = range.word;
                    if (language === 'xml') {
                        const line = model.getLineContent(position.lineNumber);
                        const offset = position.column - 1;
                        const attributePattern = /[A-Za-z_]\w*(?:\.[A-Za-z_]\w*)?/g;
                        let candidate;
                        while ((candidate = attributePattern.exec(line)) !== null) {
                            if (offset >= candidate.index && offset <= candidate.index + candidate[0].length && hints[candidate[0]]) {
                                name = candidate[0];
                                break;
                            }
                        }
                    }
                    const help = hints[name];
                    if (!help) return null;
                    return {
                        range: new monaco.Range(position.lineNumber, range.startColumn, position.lineNumber, range.endColumn),
                        contents: [{ value: `**${name}** · ${label}` }, { value: help }]
                    };
                }
            });
        }

        function signatureParameterLabel(signature, index) {
            const open = signature.indexOf('(');
            const close = signature.lastIndexOf(')');
            if (open < 0 || close <= open) return '';
            return (signature.slice(open + 1, close).split(',')[index] || '').trim();
        }

        function word(label, kind, detail = '代码建议', documentation = '') {
            const item = { label, kind, insertText: label, detail };
            if (documentation) item.documentation = documentation;
            return item;
        }
        function snippet(label, detail, insertText, kind) {
            return { label, detail, documentation: detail, kind, insertText, sortText: `0_${label}`, insertTextRules: monaco.languages.CompletionItemInsertTextRule.InsertAsSnippet };
        }

        function registerFormatters() {
            monaco.languages.registerDocumentFormattingEditProvider('xml', { provideDocumentFormattingEdits(model) { return [{ range: model.getFullModelRange(), text: formatXml(model.getValue()) }]; } });
            monaco.languages.registerDocumentFormattingEditProvider('csharp', { provideDocumentFormattingEdits(model) { return [{ range: model.getFullModelRange(), text: formatCSharp(model.getValue()) }]; } });
        }

        function formatXml(xml) {
            const normalized = xml.replace(/>\s*</g, '>\n<').trim().split('\n'); let depth = 0;
            return normalized.map(line => { const value = line.trim(); if (/^<\//.test(value)) depth = Math.max(0, depth - 1); const result = '    '.repeat(depth) + value; if (/^<[^!?/][^>]*[^/]?>$/.test(value) && !value.includes('</')) depth++; return result; }).join('\n');
        }
        function formatCSharp(code) {
            let depth = 0; return code.split('\n').map(line => { const value = line.trim(); if (value.startsWith('}')) depth = Math.max(0, depth - 1); const result = value ? '    '.repeat(depth) + value : ''; if (value.endsWith('{')) depth++; return result; }).join('\n');
        }

        function toggleTheme() {
            currentTheme = currentTheme === 'light' ? 'dark' : 'light';
            document.body.classList.toggle('dark', currentTheme === 'dark');
            document.body.classList.toggle('light', currentTheme === 'light');
            monaco.editor.setTheme(currentTheme === 'dark' ? 'xfe-dark' : 'xfe-light');
            return currentTheme;
        }

        function post(message) { if (window.chrome && window.chrome.webview) window.chrome.webview.postMessage(message); }
        window.editorHost = { loadWorkspace, upsertFile, removeFile, activateFile, closeFileTab, requestHostSave, getWorkspaceJson, getFileContent, setFileContent, markSaved, toggleTheme };
    </script>
</body>
</html>