浏览代码

Editor: add documentSymbolProvider

bdbai 2 年之前
父节点
当前提交
baae6e7336
共有 2 个文件被更改,包括 212 次插入8 次删除
  1. 12 8
      Maple.App/MonacoEditor/src/init.ts
  2. 200 0
      Maple.App/MonacoEditor/src/symbol.ts

+ 12 - 8
Maple.App/MonacoEditor/src/init.ts

@@ -8,6 +8,7 @@ import { referenceProvider, renameProvider } from './reference'
 import { hoverProvider } from './hover'
 import { reportEditorReady, saveFile } from './rpc'
 import { Mutex } from './mutex'
+import { documentSymbolProvider } from './symbol'
 
 declare global {
     interface Window {
@@ -35,14 +36,17 @@ function initLang() {
     };
 
     document.getElementById('loading')!.style.display = 'none'
-    monaco.languages.register({ id: 'leafConf' })
-    monaco.languages.setMonarchTokensProvider('leafConf', monarchTokenProvider)
-    monaco.languages.registerCompletionItemProvider('leafConf', completionProvider)
-    monaco.languages.registerFoldingRangeProvider('leafConf', foldingRangeProvider)
-    monaco.languages.registerDefinitionProvider('leafConf', definitionProvider)
-    monaco.languages.registerReferenceProvider('leafConf', referenceProvider)
-    monaco.languages.registerRenameProvider('leafConf', renameProvider)
-    monaco.languages.registerHoverProvider('leafConf', hoverProvider)
+
+    const leafConfLangId = 'leafConf'
+    monaco.languages.register({ id: leafConfLangId })
+    monaco.languages.setMonarchTokensProvider(leafConfLangId, monarchTokenProvider)
+    monaco.languages.registerCompletionItemProvider(leafConfLangId, completionProvider)
+    monaco.languages.registerFoldingRangeProvider(leafConfLangId, foldingRangeProvider)
+    monaco.languages.registerDefinitionProvider(leafConfLangId, definitionProvider)
+    monaco.languages.registerReferenceProvider(leafConfLangId, referenceProvider)
+    monaco.languages.registerRenameProvider(leafConfLangId, renameProvider)
+    monaco.languages.registerHoverProvider(leafConfLangId, hoverProvider)
+    monaco.languages.registerDocumentSymbolProvider(leafConfLangId, documentSymbolProvider)
 
     const editor = monaco.editor.create(document.getElementById('container')!, {
         wordBasedSuggestions: false,

+ 200 - 0
Maple.App/MonacoEditor/src/symbol.ts

@@ -0,0 +1,200 @@
+import * as monaco from 'monaco-editor'
+import { ILeafConfKvItem, ILeafConfSection, ILeafConfTextSpan, parseKvLine, parseStruct, splitByComma, trimComment } from './parse'
+import * as facts from './facts'
+
+function provideGeneralSectionSymbols(
+    lineId: number,
+    line: string,
+): monaco.languages.DocumentSymbol | undefined {
+    const kv = parseKvLine(line, lineId, 1)
+    if (kv === undefined) {
+        return undefined
+    }
+    return {
+        kind: monaco.languages.SymbolKind.Key,
+        detail: 'setting',
+        name: kv.key,
+        range: new monaco.Range(kv.lineId, kv.keyStartCol, kv.lineId, kv.valueStartCol + kv.value.length),
+        selectionRange: new monaco.Range(kv.lineId, kv.keyStartCol, kv.lineId, kv.keyStartCol + kv.key.length),
+        tags: [],
+    }
+}
+
+function provideProxySectionSymbols(
+    lineId: number,
+    line: string,
+): monaco.languages.DocumentSymbol | undefined {
+    const kv = parseKvLine(line, lineId, 1)
+    if (kv === undefined) {
+        return undefined
+    }
+    const segs = splitByComma(kv.value, kv.valueStartCol)
+    let kvSegs: ILeafConfTextSpan[] = []
+    const protocol = segs[0].text
+    if (facts.PROXY_PROTOCOLS_REQUIRING_HOST_SET.has(protocol)) {
+        kvSegs = segs.slice(1)
+    } else {
+        kvSegs = segs.slice(3)
+    }
+    return {
+        kind: monaco.languages.SymbolKind.Variable,
+        detail: 'proxy',
+        name: kv.key,
+        range: new monaco.Range(kv.lineId, kv.keyStartCol, kv.lineId, kv.valueStartCol + kv.value.length),
+        selectionRange: new monaco.Range(kv.lineId, kv.keyStartCol, kv.lineId, kv.keyStartCol + kv.key.length),
+        tags: [],
+        children: kvSegs.map(s => parseKvLine(s.text, lineId, s.startCol))
+            .filter((argKv): argKv is ILeafConfKvItem => argKv !== undefined)
+            .map(argKv => ({
+                kind: monaco.languages.SymbolKind.Property,
+                detail: 'property',
+                name: argKv.key,
+                range: new monaco.Range(argKv.lineId, argKv.keyStartCol, argKv.lineId, argKv.valueStartCol + argKv.value.length),
+                selectionRange: new monaco.Range(argKv.lineId, argKv.keyStartCol, argKv.lineId, argKv.keyStartCol + argKv.key.length),
+                tags: [],
+            })),
+    }
+}
+
+function provideProxyGroupSectionSymbols(
+    lineId: number,
+    line: string,
+): monaco.languages.DocumentSymbol | undefined {
+    const kv = parseKvLine(line, lineId, 1)
+    if (kv === undefined) {
+        return undefined
+    }
+    const segs = splitByComma(kv.value, kv.valueStartCol)
+    let firstKvArgId = segs.findIndex(s => s.text.includes('='))
+    if (firstKvArgId === -1) {
+        firstKvArgId = segs.length
+    }
+    const actorChildren: monaco.languages.DocumentSymbol[] = segs.slice(1, firstKvArgId)
+        .map(a => ({
+            kind: monaco.languages.SymbolKind.Variable,
+            detail: 'actor',
+            name: a.text,
+            range: new monaco.Range(lineId, a.startCol, lineId, a.startCol + a.text.length),
+            selectionRange: new monaco.Range(lineId, a.startCol, lineId, a.startCol + a.text.length),
+            tags: [],
+        }))
+    const kvChildren: monaco.languages.DocumentSymbol[] = segs.slice(firstKvArgId)
+        .map(s => parseKvLine(s.text, lineId, s.startCol))
+        .filter((argKv): argKv is ILeafConfKvItem => argKv !== undefined)
+        .map(argKv => ({
+            kind: monaco.languages.SymbolKind.Property,
+            detail: 'property',
+            name: argKv.key,
+            range: new monaco.Range(argKv.lineId, argKv.keyStartCol, argKv.lineId, argKv.valueStartCol + argKv.value.length),
+            selectionRange: new monaco.Range(argKv.lineId, argKv.keyStartCol, argKv.lineId, argKv.keyStartCol + argKv.key.length),
+            tags: [],
+        }))
+    return {
+        kind: monaco.languages.SymbolKind.Variable,
+        detail: 'proxy group',
+        name: kv.key,
+        range: new monaco.Range(kv.lineId, kv.keyStartCol, kv.lineId, kv.valueStartCol + kv.value.length),
+        selectionRange: new monaco.Range(kv.lineId, kv.keyStartCol, kv.lineId, kv.keyStartCol + kv.key.length),
+        tags: [],
+        children: [...actorChildren, ...kvChildren],
+    }
+}
+
+function provideRuleSectionSymbols(
+    lineId: number,
+    line: string,
+): monaco.languages.DocumentSymbol | undefined {
+    const segs = splitByComma(line, 1)
+    const ruleType = segs[0].text
+    if (
+        (ruleType === facts.RULE_TYPE_FINAL && segs.length !== 2)
+        || (ruleType !== facts.RULE_TYPE_FINAL && segs.length !== 3)
+    ) {
+        return undefined
+    }
+    const lastSeg = segs[segs.length - 1]
+    return {
+        kind: monaco.languages.SymbolKind.Function,
+        detail: 'rule',
+        name: ruleType,
+        range: new monaco.Range(lineId, segs[0].startCol, lineId, lastSeg.startCol + lastSeg.text.length),
+        selectionRange: new monaco.Range(lineId, segs[0].startCol, lineId, segs[0].startCol + segs[0].text.length),
+        tags: [],
+        children: [{
+            kind: monaco.languages.SymbolKind.Variable,
+            detail: 'outbound',
+            name: lastSeg.text,
+            range: new monaco.Range(lineId, lastSeg.startCol, lineId, lastSeg.startCol + lastSeg.text.length),
+            selectionRange: new monaco.Range(lineId, lastSeg.startCol, lineId, lastSeg.startCol + lastSeg.text.length),
+            tags: [],
+        }]
+    }
+}
+
+function provideHostSectionSymbols(
+    lineId: number,
+    line: string,
+): monaco.languages.DocumentSymbol | undefined {
+    const kv = parseKvLine(line, lineId, 1)
+    if (kv === undefined) {
+        return undefined
+    }
+    const segs = splitByComma(kv.value, kv.valueStartCol)
+    return {
+        kind: monaco.languages.SymbolKind.String,
+        detail: 'host name',
+        name: kv.key,
+        range: new monaco.Range(kv.lineId, kv.keyStartCol, kv.lineId, kv.valueStartCol + kv.value.length),
+        selectionRange: new monaco.Range(kv.lineId, kv.keyStartCol, kv.lineId, kv.keyStartCol + kv.key.length),
+        tags: [],
+        children: segs.map(s => ({
+            kind: monaco.languages.SymbolKind.Number,
+            detail: 'IP address',
+            name: s.text,
+            range: new monaco.Range(lineId, s.startCol, lineId, s.startCol + s.text.length),
+            selectionRange: new monaco.Range(lineId, s.startCol, lineId, s.startCol + s.text.length),
+            tags: [],
+        }))
+    }
+}
+
+export const documentSymbolProvider: monaco.languages.DocumentSymbolProvider = {
+    provideDocumentSymbols(model, _token) {
+        const symbols: monaco.languages.DocumentSymbol[] = []
+        const struct = parseStruct(model)
+        for (const section of struct.sections) {
+            let symbolFn: (lineId: number, line: string) => monaco.languages.DocumentSymbol | undefined
+            switch (section.sectionName) {
+                case facts.SECTION_GENERAL:
+                    symbolFn = provideGeneralSectionSymbols
+                    break
+                case facts.SECTION_PROXY:
+                    symbolFn = provideProxySectionSymbols
+                    break
+                case facts.SECTION_PROXY_GROUP:
+                    symbolFn = provideProxyGroupSectionSymbols
+                    break
+                case facts.SECTION_RULE:
+                    symbolFn = provideRuleSectionSymbols
+                    break
+                case facts.SECTION_HOST:
+                    symbolFn = provideHostSectionSymbols
+                    break
+                default:
+                    continue
+            }
+            symbols.push({
+                kind: monaco.languages.SymbolKind.Namespace,
+                detail: 'section',
+                name: section.sectionName,
+                range: new monaco.Range(section.startLine, 1, section.endLine, model.getLineMaxColumn(section.endLine)),
+                selectionRange: new monaco.Range(section.startLine, 1, section.startLine, model.getLineMaxColumn(section.startLine)),
+                tags: [],
+                children: Array.from({ length: section.endLine - section.startLine + 1 }, (_, i) => section.startLine + i)
+                    .flatMap(lineId => symbolFn(lineId, trimComment(model.getLineContent(lineId))))
+                    .filter((s): s is monaco.languages.DocumentSymbol => s !== undefined),
+            })
+        }
+        return symbols
+    }
+}