@nx-lang / language-protocol
编辑器和NX语言服务之间的契约:语言查询的形状及其
答案,编码双方使用的位置,和 NxLanguageService 连接每个客户端
实现满足。类型,一些常量和保护,以及错误类;没有运行时
依赖。
该接口的实现存在于其他地方:
@nx-lang/language-client-浏览器的HTTP实现,与@nx-lang/language-http-一个可挂载的节点处理程序,通过@nx-lang/sdk-node.
编辑器集成,例如 @nx-lang/monaco 只依赖于这个包,所以一个http支持的,
工人支持的或进程内服务是可互换的。
文档集模型
每个查询都包含将从中得到回答的完整文档集,以及对象的URI 查询的文档是关于:
interface LanguageQuery {
documents: { uri: string; source: string; version?: number; identity?: string }[];
uri: string; // one of documents[].uri
}
该服务在查询之间不保留任何内容。两个相同的查询产生相同的答案 在中间被询问,这是让几个服务器实例在没有会话关联的情况下回答的原因。 单文档主机发送它的一个文档;一个主机编辑一组文件中的一个,每个文件都导入 Other发送整个集合,其中编辑的文档包含编辑器未保存的文本,以及 完成包括编辑的文档从同级文档导入的内容。
version 是编辑想要的;它在应答中被回显,因此客户端可以丢弃
在发送新版本后到达的应答。 identity 是NX解决的工作空间身份
按(tenant/form.nx);当省略时,它从URI派生。
位置是UTF-16代码单位
一、 TextPosition 是零基的 line 和一个零基的 character 以UTF-16编码单位计算
从一线出发,一路前行 String.prototype.length 计数和摩纳哥的方式,codemmirror和
语言服务器协议默认计数。之前的表情符号是两个单位。靶场携带
它的起始和结束位置,在它们旁边, startByte 和 endByte 抵消到
文档的UTF-8文本,这是语言服务测量的。
// let s = "😀" + name
// ^ name starts at character 15 (the emoji is 2 units) and byte 18 (it is 4 bytes)
{ start: { line: 0, character: 15 }, end: { line: 0, character: 19 }, startByte: 18, endByte: 22 }
查询
定义了五个查询;每个都有一个请求和一个通过JSON往返的回答类型。这个
下面的例子编译在 test/examples.test.ts,因此它们不会偏离类型。
hover
const request: HoverRequest = {
documents,
uri: "nx://tenant/form.nx",
position: { line: 1, character: 3 },
};
const answer: Hover | null = {
uri: "nx://tenant/form.nx",
identity: "tenant/form.nx",
version: 7,
range: { start: { line: 1, character: 1 }, end: { line: 1, character: 7 }, startByte: 34, endByte: 40 },
contents: "```nx\n<Button label:string />\n```",
};
contents 有NX碎片的markdown吗 ```nx 栅栏。当服务无事可做时
假设答案是 null,不是错误。
completions
const request: CompletionsRequest = {
documents,
uri: "nx://tenant/form.nx",
position: { line: 1, character: 23 },
};
const answer: CompletionList = {
uri: "nx://tenant/form.nx",
identity: "tenant/form.nx",
version: 7,
items: [{ label: "label", kind: "Property", detail: "label:string", documentation: "The caption." }],
};
kind 是其中之一 Keyword, Type, Declaration, Component, Property, Member.
documentation 是 /// 项目名称的文档,如markdown,当
它没有。
diagnostics
const request: DiagnosticsRequest = { documents, uri: "nx://tenant/form.nx" };
const answer: DiagnosticReport = {
documents: [
{ uri: "nx://tenant/ui.nx", identity: "tenant/ui.nx", version: 3, diagnostics: [] },
{
uri: "nx://tenant/form.nx",
identity: "tenant/form.nx",
version: 7,
diagnostics: [
{
range: { start: { line: 1, character: 8 }, end: { line: 1, character: 13 }, startByte: 41, endByte: 46 },
severity: "Error",
code: "type-mismatch",
message: "Expected string, found int",
related: [],
},
],
},
],
workspace: [],
};
报告涵盖集合中的每个文档,每个文档一个条目,因此可以设置多文档主机
所有的答案都是一个标记。 workspace 保存不属于任何文档的诊断信息,
例如导入既不在集合中也不在库中的模块。 severity 是其中之一
Error, Warning, Info, Hint.
documentSymbols
const request: DocumentSymbolsRequest = { documents, uri: "nx://tenant/ui.nx" };
const answer: DocumentSymbol[] = [
{
name: "Button",
kind: "Component",
range: { start: { line: 0, character: 0 }, end: { line: 0, character: 47 }, startByte: 0, endByte: 47 },
selectionRange: { start: { line: 0, character: 12 }, end: { line: 0, character: 18 }, startByte: 12, endByte: 18 },
},
];
kind 是其中之一 Function, Value, TypeAlias, Record, Action, Union, Component,
Element.
sourceTree (不稳定)
不稳定: 答案的形状可能会在任何版本中改变,直到下一个版本提交它。它是 为NX查看器发布,并在此基础上构建结构更改视图。
const request: SourceTreeRequest = { documents, uri: "nx://tenant/ui.nx" };
const answer: SourceTree = {
uri: "nx://tenant/ui.nx",
identity: "tenant/ui.nx",
version: 3,
nodes: [
{ role: "declaration", range: range(0, 47), key: "Button", name: "Button", declaration: 0, flags: ["export"] },
{ role: "parameter", range: range(19, 31), parent: 0, key: "Button.label", name: "label" },
{ role: "typeReference", range: range(25, 31), parent: 1, key: "Button.label.type", value: "string" },
{ role: "element", range: range(37, 47), parent: 0, key: "Button.body", name: "button", type: "button" },
],
declarations: [
{ module: "tenant/ui.nx", name: "Button", kind: "function", range: range(0, 47), properties: [{ name: "label", type: "string" }] },
],
};
(range(start, end) 代表 EditorRange 这些字节在第0行。)
文档的每个部分都是一个节点,包含未解析的注释和区域,以及每个节点
令牌只属于一个节点:其范围内包含它的最小节点。节点按顺序列出
以父节点的索引开始偏移,每个父节点在其子节点之前。一个节点有一个 role
(element, attribute, literal, case, operator, condition, comment, unparsed 和
剩下的 SourceRole),并载有书面内容 name, value 和 textType,它的
用NX拼写 type 当类型检查器给它一个时,输入 declaration 的指数
它所指的宣言。节点的类型和声明是hover在其中报告的内容。
key 节点在文档中的路径是否为 roleQuestion.value.choices[2].label:
顶层声明的名称,然后是属性、成员、武器和插槽,以及位置
为项目。对另一个声明或另一个属性的编辑不会改变它,所以有两个版本
的值可以通过键进行比较。键是用于比较是否相等的标识符,而不是指向它的路径
拆分:手臂以其图案或测试来命名(is Mode.dark, when x > 1)和
通过路径导入,这些路径可以保存点和空格。读取树的结构 parent.
declarations 列出每个声明一次,一个节点从本文档引用另一个文档
集合或库的:它的模块标识,名称和类型,它在本文档声明时的范围
它、它的文档注释、它的属性按声明顺序排列,首先是继承的属性(每个属性都有它的
类型,它的默认写入,以及它是可选的(内容的还是继承的),它的基,a
组件的状态、联合的情况、别名的类型、值的类型和值的范围。
不存在节点或声明的可选属性,而不是 null 当他们不适用时。
请求 visualizers: {} (或 visualizers: { modules: [...] },命名进一步可视化
模块(宿主提供的)也解析可视化器声明:每个声明都有一个
带有 visualizer,带有标题、id和图标,可选 preview: { renderer: string },它的属性按顺序显示,
对于每个属性或别名,a format 和一个 editor. editor 的编辑器提示
类型: kind (field, dropdown 或者 segmented), min, max, step, multiline,
placeholder 和 choices。每个选择的 value 类型为 sourceEdit setValue 值,所以a
客户端传递它,因为它是写选择:
const sizeProperty: SourcePropertyVisualizer = {
name: "size",
editor: {
max: 96,
choices: [
{ value: { kind: "number", text: "12" }, label: "sm" },
{ value: { kind: "number", text: "16" }, label: "base" },
],
},
};
sourceEdit (不稳定)
不稳定: 请求和答案的形状可能会在任何版本中改变,直到下一个版本 向他们承诺。它是为NX查看器的可视化编辑而发布的。
const request: SourceEditRequest = {
documents,
uri: "nx://tenant/ui.nx",
key: "Button.label.type",
edit: { kind: "setValue", value: { kind: "string", value: "Save" } },
};
const answer: SourceEditAnswer = { edits: [{ range: range(25, 31), newText: '"Save"' }] };
const refused: SourceEditAnswer = { edits: [], refused: "notEditable" };
请求将文档源树的节点命名为 key 还有一个变化: setValue
使用类型化值(string, number 作为输入, boolean,或者 case (通过名称)表示文字或
这是一个完整的值, setText 一行文字代表一组文字内容,
remove 对于作为列表项的属性或元素, addProperty 与一个
属性的名称和元素的类型化值,该值将该属性写入元素的
最后一个,在它的布局中,或者 move 有一个位置 to 对于作为列表项的元素。一个
列表的Item是带括号序列中的元素或元素内容中的元素,而不是 if 分支
或者一个 for Body,在不包含文本的列表中; to 一旦移动,它所占据的位置是否被算作
列表的键数它的项。在将一项写到一行的列表中,一项随其移动或移动
整行,其上方的注释,以及将其与下一项分开的空行
(前一个是最后一个);在一行的列表中,旁边有空格。其他布局有
拒绝 notEditable,和 to 在列表之外 invalidValue。答案是 edits 使
更改:每个范围都在发送时的文档中,没有两个重叠,因此主机将它们一起应用;该服务编写拼写(引号、转义、大括号)
属性的整行)。一个改变,它不能回答没有编辑和 refused: unknownNode,
notEditable 或者 invalidValue。将文本保留原样的更改回答不编辑和不
拒绝。
保留的名字
definition, references, rename, signatureHelp, inlayHints 和 semanticTokens 是
保留。的实现 unsupported-query 错误,而
而不是传输失败或错误的回答,这样客户端就可以区分“还没有”和“坏了”。
isLanguageQueryName 和 isReservedLanguageQueryName 对名称进行分类。
服务接口
interface NxLanguageService {
hover(request: HoverRequest, signal?: AbortSignal): Promise<Hover | null>;
completions(request: CompletionsRequest, signal?: AbortSignal): Promise<CompletionList>;
diagnostics(request: DiagnosticsRequest, signal?: AbortSignal): Promise<DiagnosticReport>;
documentSymbols(request: DocumentSymbolsRequest, signal?: AbortSignal): Promise<DocumentSymbol[]>;
sourceTree(request: SourceTreeRequest, signal?: AbortSignal): Promise<SourceTree>; // unstable
sourceEdit(request: SourceEditRequest, signal?: AbortSignal): Promise<SourceEditAnswer>; // unstable
}
取消 signal 在应答到达之前,使用一个中止错误拒绝承诺
isAbortError 认错了,承诺就永远不会兑现。编辑器集成如下
针对该接口的进程内假例进行了测试,并对HTTP客户端进行了相同的行为
相同的答案。
错误
| 类 | 当 |
|---|---|
UnsupportedQueryError | 服务识别了查询名称,但不支持查询名称。携带 query. |
LanguageServiceHttpError | HTTP实现收到非成功状态。携带 status, url,一个 bodyExcerpt和身体的 code 当它是一个错误体时。 |
LanguageServiceTimeoutError | 请求在客户端超时时间内没有得到响应;请求被中止。携带 timeoutMs. |
HTTP实现用JSON主体回答失败的查询
{ error: { code: LanguageServiceErrorCode; message: string; query?: string } }
哪里 code 是其中之一 unsupported-query, unknown-query, method-not-allowed,
invalid-request, payload-too-large, internal-error. isLanguageServiceErrorBody 认识到
的形状。
源树调用者可以设置 visualizers: { includeUnreferencedDeclarations: true } 包括
加载的可视化器目标和继承的表示元数据,即使入口源没有
直接引用它们。默认情况下将声明表绑定到源树。
未知的预览渲染器id仍然是元数据;主机只激活它们支持的实现。