언어 서버 프로토콜 구현
16011 단어 typescripttutorialdevopsproductivity
우리가 여기 어떻게 왔는지
2021년이 끝나기 전에 내 작업은 내 조직을 위한 해커톤을 예약했습니다. 요구 사항은 우리 또는 플레이어에게 유용해야 한다는 것입니다. 엔지니어가 생산 스택에 서비스를 제공할 수 있도록 language server을 개발하기로 결정했습니다.
우리의 스택은 예쁘고complex 많은 학습이 필요합니다. 상호 작용하는 언어는 yaml이지만 실제로는 DSL이며 많은 지원이 필요합니다. 이 도구의 목표는 스택에 익숙하지 않거나 일반적으로 익숙하지 않은 사람이 필요로 하는 지원의 양을 줄이는 데 도움을 주는 것이었습니다.
시작하고 실행하는 것이 꽤 어려웠기 때문에 다른 개발자 생산성 열광자와 내 작업 및 진행 상황을 공유하고 싶었습니다!
언어 서버란?
언어 서버는 언어 서버와 언어 클라이언트를 정의하는 Microsoft에서 개발한 표준입니다. 언어 구문 구문 분석, linting, 코드 힌트(등) 및 개발자와의 실제 인터페이스를 각각 처리합니다. 두 가지 언어 서버 클라이언트는 Visual Studio Code와 최근 버전 0.6의 Neovim입니다(전체가 Lua!로 작성됨). 일부 언어 서버에는
yamlls
, sourcegraph-go
및 rust-analyzer
가 포함됩니다. 그런 다음 언어 서버 프로토콜은 이 두 구성 요소 사이에 glue을 정의합니다. 접착제는 JSON-rcp를 통해 발생합니다.맞춤형 LSP의 경우 yaml-language-server maintained by redhat 을 분기하여 시작합니다.
시작하기
가장 먼저 해야 할 일은 선택한 편집기에 대해 LSP를 활성화하는 것입니다. 제가 vim 진영에 푹 빠져서 네오빔을 했습니다. 이것은 yamlls를 실행하는 데 필요한 일반적인 설정 코드입니다.
local nvim_lsp = require('lspconfig')
local servers = {
'yamlls',
}
for _, lsp in ipairs(servers) do
nvim_lsp[lsp].setup {
on_attach = on_attach,
flags = {
debounce_text_changes = 150,
}
}
end
언어 서버와 대화하도록 클라이언트를 설정하도록 neovim LSP에 지시합니다. yaml-language-server는 로컬 컴퓨터에서 바이너리를 실행합니다. 기본값은
--stdio
플래그로, 표준 입력을 받아들이고 유효성을 검사합니다: yaml-language-server --stdio
. vim에서 :LspInfo
를 실행하면 이 정보가 표시됩니다.1 client(s) attached to this buffer: yamlls
Client: yamlls (id 1)
root: /Users/ischweer/dev/shards
filetypes: yaml
cmd: yaml-language-server --stdio
설정할 수 있는 구성이 많은 경향이 있습니다. 한 창에서 언어 서버를 실행하도록 선택한 경우
--rpc-port
및 --rpc-host
옵션을 사용하여 언어 서버를 별도로 실행하고 다른 창에서 테스트할 수도 있습니다. .내가 만든 것의 작은 데모
개발을 시작할 준비가 되었습니다. 제 경우에는 yaml-language-server를 포크하고 중앙 서비스에서 애플리케이션 사양, 해당 구성 및 WAN 설정을 가져오는 몇 가지 추가 논리를 추가하기 시작했습니다. Riot의 서비스에는 더 많은 비즈니스 DSL이 포함된 이러한 종류의 kube-esque 정의가 있습니다.
application-instance:
name: some.app
network:
inbound:
- name: another.app
location: usw2
http를 통해 이들 모두를 아래로 당기는 것은 매우 간단합니다. 모든 yaml이 있으면 캐시됩니다.
async downloadApps() {
// we want to go through all the apps in the env, and get
// the yaml'd app definitions
await this.getDiscoverous();
// now we can get the lol-services 710e env
const url = `https://${this.gandalf_host}/api/v1/environments/lol-services/${env.LATEST_VERSION}`;
const resp: XHRResponse = await xhr({ url: url, headers: { authorization: `Bearer ${this.gandalf_token}` } });
console.log('Successfully grab 710e instance');
const cache_builder: Array<Promise<boolean>> = [];
for (const appMetadata of environmentInstance['environment']['applications']) {
cache_builder.push(this.downloadApp(appMetadata));
}
await Promise.all(cache_builder);
}
async downloadApp(app: { name: string; version: string }): Promise<any> {
const url = `https://${this.gandalf_host}/api/v1/applications/${app.name}/${app.version}`;
const resp = await xhr({ url: url, headers: { authorization: `Bearer ${this.gandalf_token}` } });
const _d = new YAML.Document();
_d.contents = JSON.parse(resp.responseText);
return new Promise(() => {
fsp
.writeFile(`${homedir()}/.cache/gandalf/${app.name}.yaml`, _d.toString(), { flag: 'wx' })
.then()
.catch((err) => {
console.log(`Did not write file because it exists already ${app.name}.yaml`);
});
});
}
이 yaml 구문 분석을 완전히 이해하려면 riot eng 블로그의 후속 블로그 게시물을 읽어야 합니다. 서비스 Y에 지정된 대화입니까, 아니면 오타입니까?". 타이프 스크립트에서 이렇게 보입니다.
for (const defined_outbound of appSpec.outbounds || []) {
let found = false;
for (const _instanced_outbound of app_instance_outbounds.items || []) {
const instanced_outbound = _instanced_outbound as YAMLMap;
if (instanced_outbound.get('service') == defined_outbound) {
found = true;
break;
}
}
if (!found) {
errors.push({
message: `Missing required outbound for ${defined_outbound}`,
location: { start: app_instance_outbounds.range[0], end: app_instance_outbounds.range[2], toLineEnd: true },
severity: 1,
source: 'Gandalf',
code: ErrorCode.Undefined,
} as YAMLDocDiagnostic);
}
}
이를 통해 서비스가 서로 통신할 것으로 예상되지만 그렇게 지정되지 않은 경우 편집기에서 멋진 작은 오류를 볼 수 있습니다.
실제로 일을 하고
LSP는 RPC 전체에 걸쳐 있으며 위에서 지정한 모든 이벤트를 볼 수 있습니다. 모든 것은 비동기식이며 클라이언트는 모든 것에 대해 json 직렬화된 텍스트를 처리할 수 있어야 합니다. LSP가 어떻게 작동하는지 완전히 이해하려면 편집자 내부를 배워야 합니다 :').
neovim의 경우 항상 lsp 로그를 확인하십시오:
tail -n 10000 ~/.cache/nvim/lsp.log | bat
. 대부분의 경우 직렬화 오류 또는 알 수 없는 속성 문제가 있으므로 매우 유용합니다. 디버깅할 때 다음과 같은 "항상 기록"접근 방식을 추가하는 것이 도움이 된다는 것을 발견했습니다(편집기 속도가 느려지지만).console.error = (arg) => {
if (arg === null) {
connection.console.info(arg);
} else {
connection.console.error(arg);
}
};
이렇게 하면 더 일반적인 타이프스크립트 코드를 작성하고 로그에서 결과 오류를 읽은 다음 약간 더 긴밀한 피드백 루프를 가질 수 있습니다.
Reference
이 문제에 관하여(언어 서버 프로토콜 구현), 우리는 이곳에서 더 많은 자료를 발견하고 링크를 클릭하여 보았다 https://dev.to/ianschweer/implementing-the-language-server-protocol-3nfg텍스트를 자유롭게 공유하거나 복사할 수 있습니다.하지만 이 문서의 URL은 참조 URL로 남겨 두십시오.
우수한 개발자 콘텐츠 발견에 전념 (Collection and Share based on the CC Protocol.)