언어 서버 프로토콜이란?
편집기 또는 IDE에서 프로그래밍 언어에 대한 소스 코드 자동 완성 또는 정의로 이동 과 같은 다양한 편집 기능을 지원하는 것은 일반적으로 매우 도전적이고 시간이 많이 걸립니다. 일반적으로 편집기 또는 IDE의 프로그래밍 언어로 도메인 모델(스캐너, 파서, 형식 검사기, 작성기 등)을 작성해야 합니다. 예를 들어 Eclipse IDE에서 C/C++를 지원하는 Eclipse CDT 플러그 인은 Eclipse IDE 자체가 Java로 작성되었기 때문에 Java로 작성됩니다. 이 접근 방식에 따라 Visual Studio Code용 TypeScript에서 C/C++ 도메인 모델을 구현하고 Visual Studio용 C#에서 별도의 도메인 모델을 구현하는 것을 의미합니다.
개발 도구에서 기존 언어별 라이브러리를 다시 사용할 수 있는 경우에도 언어별 도메인 모델을 만드는 것이 훨씬 쉽습니다. 그러나 이러한 라이브러리는 일반적으로 프로그래밍 언어 자체에서 구현됩니다(예: C/C++ 도메인 모델은 C/C++에서 구현됨). C/C++ 라이브러리를 TypeScript로 작성된 편집기에 통합하는 것은 기술적으로 가능하지만 하기는 어렵습니다.
언어 서버
또 다른 방법은 자체 프로세스에서 라이브러리를 실행하고 프로세스 간 통신을 사용하여 통신하는 것입니다. 주고 받는 메시지는 프로토콜을 형성합니다. LSP(언어 서버 프로토콜)는 개발 도구와 언어 서버 프로세스 간에 교환되는 메시지를 표준화하는 제품입니다. 언어 서버 또는 악마를 사용하는 것은 새로운 개념이나 새로운 아이디어가 아닙니다. Vim 및 Emacs와 같은 편집자는 의미 체계 자동 완성 지원을 제공하기 위해 한동안 이 작업을 수행해 왔습니다. LSP의 목표는 이러한 종류의 통합을 간소화하고 다양한 도구에 언어 기능을 노출하는 데 유용한 프레임워크를 제공하는 것이었습니다.
공통 프로토콜을 사용하면 언어 도메인 모델의 기존 구현을 다시 사용하여 프로그래밍 언어 기능을 개발 도구에 통합할 수 있습니다. 언어 서버 백 엔드는 PHP, Python 또는 Java로 작성할 수 있으며 LSP를 사용하면 다양한 도구에 쉽게 통합할 수 있습니다. 프로토콜은 공통 추상화 수준에서 작동하므로 도구가 기본 도메인 모델과 관련된 뉘앙스를 완전히 이해하지 않고도 풍부한 언어 서비스를 제공할 수 있습니다.
LSP 작업 시작 방법
LSP는 시간이 지남에 따라 진화했으며 현재 버전 3.0에 있습니다. C#에 대한 풍부한 편집 기능을 제공하기 위해 OmniSharp에서 언어 서버의 개념을 선택했을 때 시작되었습니다. 처음에 OmniSharp는 JSON 페이로드와 함께 HTTP 프로토콜을 사용했으며 Visual Studio Code를 비롯한 여러 편집기에 통합되었습니다.
거의 동시에 Microsoft는 Emacs 및 Sublime Text와 같은 편집기에서 TypeScript를 지원하는 아이디어로 TypeScript 언어 서버에서 작업을 시작했습니다. 이 구현에서 편집기는 Stdin/stdout을 통해 TypeScript 서버 프로세스와 통신하고 요청 및 응답에 대해 V8 디버거 프로토콜에서 영감을 받은 JSON 페이로드를 사용합니다. TypeScript 서버는 풍부한 TypeScript 편집을 위해 TypeScript Sublime 플러그 인 및 VS Code에 통합되었습니다.
두 개의 서로 다른 언어 서버를 통합한 후 VS Code 팀은 편집기 및 IDE에 대한 공용 언어 서버 프로토콜을 탐색하기 시작했습니다. 공용 프로토콜을 사용하면 언어 공급자가 다른 IDE에서 사용할 수 있는 단일 언어 서버를 만들 수 있습니다. 언어 서버 소비자는 프로토콜의 클라이언트 쪽을 한 번만 구현해야 합니다. 이로 인해 언어 공급자와 언어 소비자 모두에게 윈-윈 상황이 발생합니다.
언어 서버 프로토콜은 TypeScript 서버에서 사용하는 프로토콜로 시작하여 VS Code 언어 API에서 영감을 얻은 더 많은 언어 기능으로 확장했습니다. 이 프로토콜은 단순성과 기존 라이브러리로 인해 원격 호출을 위해 JSON-RPC 지원됩니다.
VS Code 팀은 파일을 lint(검사)하 고 검색된 경고 및 오류 집합을 반환하는 요청에 응답하는 여러 Linter 언어 서버를 구현하여 프로토콜을 프로토타입화했습니다. 목표는 사용자가 문서에서 편집할 때 파일을 lint하는 것이었습니다. 즉, 편집기 세션 중에 많은 linting 요청이 있을 것입니다. 각 사용자 편집에 대해 새 Linting 프로세스를 시작할 필요가 없도록 서버를 계속 가동하고 실행하는 것이 합리적입니다. VS Code의 ESLint 및 TSLint 확장을 포함하여 여러 Linter 서버가 구현되었습니다. 이러한 두 Linter 서버는 모두 TypeScript/JavaScript에서 구현되며 Node.js실행됩니다. 프로토콜의 클라이언트 및 서버 부분을 구현하는 라이브러리를 공유합니다.
LSP 작동 방식
언어 서버는 자체 프로세스에서 실행되며 Visual Studio 또는 VS Code와 같은 도구는 JSON-RPC를 통해 언어 프로토콜을 사용하여 서버와 통신합니다. 전용 프로세스에서 작동하는 언어 서버의 또 다른 이점은 단일 프로세스 모델과 관련된 성능 문제를 방지한다는 것입니다. 클라이언트와 서버가 모두 Node.js작성된 경우 실제 전송 채널은 stdio, 소켓, 명명된 파이프 또는 노드 ipc일 수 있습니다.
다음은 일상적인 편집 세션 중에 도구와 언어 서버가 통신하는 방법에 대한 예입니다.
사용자가 도구에서 파일(문서라고 함)을 엽니다. 이 도구는 언어 서버에 문서가 열려 있음을 알립니다('textDocument/didOpen'). 이제부터 문서 내용에 대한 진실은 더 이상 파일 시스템에 있지 않고 도구가 메모리에 보관합니다.
사용자가 편집합니다. 도구는 문서 변경('textDocument/didChange')에 대해 서버에 알리고 프로그램의 의미 체계 정보는 언어 서버에 의해 업데이트됩니다. 이 경우 언어 서버는 이 정보를 분석하고 검색된 오류 및 경고('textDocument/publishDiagnostics')를 사용하여 도구에 알립니다.
사용자는 편집기에서 기호에 대해 "정의로 이동"을 실행합니다. 도구는 두 개의 매개 변수를 사용하여 'textDocument/definition' 요청을 보냅니다. (1) 문서 URI 및 (2) 정의로 이동 요청이 시작된 텍스트 위치가 서버에 전송됩니다. 서버는 문서 URI 및 문서 내에서 기호 정의의 위치로 응답합니다.
사용자가 문서(파일)를 닫습니다. 도구에서 'textDocument/didClose' 알림이 전송되어 언어 서버에 문서가 더 이상 메모리에 없고 현재 콘텐츠가 파일 시스템에서 최신 상태임을 알립니다.
이 예제에서는 "정의로 이동", "모든 참조 찾기"와 같은 편집기 기능 수준에서 프로토콜이 언어 서버와 통신하는 방법을 보여 줍니다. 프로토콜에서 사용하는 데이터 형식은 현재 열려 있는 텍스트 문서 및 커서 위치와 같은 편집기 또는 IDE '데이터 형식'입니다. 데이터 형식은 일반적으로 추상 구문 트리 및 컴파일러 기호(예: 확인된 형식, 네임스페이스, ...)를 제공하는 프로그래밍 언어 도메인 모델의 수준에 있지 않습니다. 이렇게 하면 프로토콜이 크게 간소화됩니다.
이제 'textDocument/definition' 요청을 자세히 살펴보겠습니다. 다음은 C++ 문서에서 "정의로 이동" 요청을 위해 클라이언트 도구와 언어 서버 간에 전달되는 페이로드입니다.
요청은 다음과 같습니다.
{
"jsonrpc": "2.0",
"id" : 1,
"method": "textDocument/definition",
"params": {
"textDocument": {
"uri": "file:///p%3A/mseng/VSCode/Playgrounds/cpp/use.cpp"
},
"position": {
"line": 3,
"character": 12
}
}
}
응답은 다음과 같습니다.
{
"jsonrpc": "2.0",
"id": "1",
"result": {
"uri": "file:///p%3A/mseng/VSCode/Playgrounds/cpp/provide.cpp",
"range": {
"start": {
"line": 0,
"character": 4
},
"end": {
"line": 0,
"character": 11
}
}
}
}
돌이켜 보면 프로그래밍 언어 모델 수준이 아닌 편집기 수준에서 데이터 형식을 설명하는 것이 언어 서버 프로토콜의 성공 이유 중 하나입니다. 다양한 프로그래밍 언어에서 추상 구문 트리 및 컴파일러 기호를 표준화하는 것과 비교하여 텍스트 문서 URI 또는 커서 위치를 표준화하는 것이 훨씬 간단합니다.
사용자가 다른 언어로 작업하는 경우 VS Code는 일반적으로 각 프로그래밍 언어에 대한 언어 서버를 시작합니다. 아래 예제에서는 사용자가 Java 및 SASS 파일에서 작업하는 세션을 보여 줍니다.
역량
모든 언어 서버가 프로토콜에 정의된 모든 기능을 지원할 수 있는 것은 아닙니다. 따라서 클라이언트와 서버는 '기능'을 통해 지원되는 기능 집합을 발표합니다. 예를 들어 서버는 'textDocument/definition' 요청을 처리할 수 있지만 '작업 영역/기호' 요청을 처리하지 못할 수도 있다고 알립니다. 마찬가지로 클라이언트는 문서를 저장하기 전에 '저장하려고 합니다.' 알림을 제공할 수 있으므로 서버에서 텍스트 편집을 계산하여 편집된 문서의 서식을 자동으로 지정할 수 있습니다.
언어 서버 통합
언어 서버를 특정 도구에 실제로 통합하는 것은 언어 서버 프로토콜에 의해 정의되지 않으며 도구 구현자에 맡깁니다. 일부 도구는 모든 종류의 언어 서버를 시작하고 통신할 수 있는 확장을 사용하여 언어 서버를 일반적으로 통합합니다. VS Code와 같은 다른 사용자는 언어 서버당 사용자 지정 확장을 만들어 확장이 여전히 일부 사용자 지정 언어 기능을 제공할 수 있도록 합니다.
언어 서버 및 클라이언트의 구현을 간소화하기 위해 클라이언트 및 서버 부분에 대한 라이브러리 또는 SDK가 있습니다. 이러한 라이브러리는 다른 언어에 대해 제공됩니다. 예를 들어 언어 서버를 VS Code 확장과 다른 언어 서버 npm 모듈 에 쉽게 통합하여 Node.js사용하여 언어 서버를 작성하는 언어 클라이언트 npm 모듈 이 있습니다. 현재 지원 라이브러리 목록 입니다.
Visual Studio에서 언어 서버 프로토콜 사용
- 언어 서버 프로토콜 확장 추가 - Visual Studio에 언어 서버를 통합하는 방법에 대해 알아봅니다.