你有几种方法可以解决这个问题,你可以:
- 创建一个 IDE 独有的扩展来完成所有工作(解析文件、理解所有标记的含义并提供符号列表等)
- 通过使用实现Microsoft 指定的语言服务器协议的语言服务器来创建语言服务器客户端扩展。在这种情况下,您的 VSCode 扩展本质上将是一个启动语言服务器并与之通信的“shell”扩展。如何处理服务器返回的信息已经由
vscode-languageclient
包的LanguageClient
类开箱即用地实现了。
选项 2. 有一个巨大的优势:它修复了 N*M 问题:所有支持语言服务器协议的编辑器都可以与您的语言服务器通信,因此可以轻松地为多个编辑器(VSCode、Visual Studio、Atom、Vim)提供语言支持, emacs 等, IntelliJ 以有限的方式使用 3rd 方插件) 以最小的努力。
在您的问题中,我不清楚您是否打算编写一个可以完成所有工作的扩展程序,或者您是否打算开发一个语言服务器和一个语言客户端扩展程序来配合它。
答案实际上取决于您的语言(或 DSL)的复杂性。如果它有一些复杂的语法并且你已经有一个编译器,那么制作一个语言服务器是有意义的,因为大部分解析和逻辑已经在编译器中实现了。如果您的语言真的像您给出的示例一样简单并且没有语法,那么它可能是矫枉过正的。
如果你走“语言服务器”路线,那么有不同语言的库可以简化服务器的实现,它们负责 LSP 的 JSON-RPC 部分,你只需要覆盖语言服务器定义的方法协议(例如,用于任何 JVM 语言的 LSP4J,或用于NodeJS 的 vscode-languageserver(尽管它的名称,该包并不特定于 VSCode))。在这种情况下,用符号的层次结构填充 VSCode 的大纲视图只需要您在语言服务器中实现textDocument/documentSymbol请求,而无需在扩展中执行其他任何操作,它由vscode-languageclient
包的LanguageClient
类负责!
- 如果您的语言客户端支持分层文档符号:在语言服务器
initialize
方法中,您将收到 InitializeParams textDocument.documentSymbol.hierarchicalDocumentSymbolSupport == true
(VSCode 确实支持这一点)然后您返回DocumentSymbol[]
(它有一个children
属性,从而为您的客户端提供符号树)。
- 如果您的语言服务器客户端不支持分层文档符号(例如 Visual Studio),那么您返回
SymbolInformation[]
,这是一个平面符号列表。
需要注意的一件重要事情:VSCode在显示符号树(在大纲视图中)时会考虑 的range
属性。DocumentSymbol
您的子符号range
必须包含在其父range
符号中,否则 VSCode 将不会在大纲视图中显示任何符号,即使您的符号树结构和selectionRange
s 是正确的。这range
是定义范围(符号的整个定义,例如对于打字稿类,它从关键字开始class
并以结束符结束}
)并且selectionRange
通常只是符号的标记范围(但根据规范,它还可以包括doc 注释块和可见性修饰符,这是实现者的选择)。例如
class MyClass {
/**
* Some method documentation
*/
private function myFunction(param1) {
console.log(param1);
}
}
在这种情况下,范围将是
rangeOfClass = {
"start": { "line": 0, "character": 0 },
"end": { "line": 7, "character": 1 }
};
rangeOfMethod = {
"start": { "line": 4, "character": 2 },
"end": { "line": 6, "character": 3 }
};
并且 selectionRange 可能只是符号的名称
selectionRangeOfClass = {
"start": { "line": 0, "character": 6 },
"end": { "line": 0, "character": 13 }
};
selectionRangeOfMethod = {
"start": { "line": 4, "character": 19 },
"end": { "line": 4, "character": 29 }
};
或者它可以包括从文档、关键字和修饰符到符号的末尾
selectionRangeOfClass = {
"start": { "line": 0, "character": 0 },
"end": { "line": 0, "character": 13 }
};
selectionRangeOfMethod = {
"start": { "line": 1, "character": 2 },
"end": { "line": 4, "character": 29 }
};
有了这个,你会得到一个很好的大纲:
