임베디드 프로그래밍 언어
Visual Studio Code는 프로그래밍 언어에 대한 풍부한 언어 기능을 제공합니다. 언어 서버 확장 가이드에서 읽으셨듯이, 어떤 프로그래밍 언어든 지원하기 위해 언어 서버를 작성할 수 있습니다. 하지만 임베디드 언어에 대한 지원을 활성화하는 데는 더 많은 노력이 필요합니다.
오늘날 다음과 같이 임베디드 언어가 점점 늘어나고 있습니다.
- HTML의 JavaScript 및 CSS
- JavaScript의 JSX
- 템플릿 언어의 보간, 예를 들어 Vue, Handlebars 및 Razor
- PHP의 HTML
이 가이드에서는 임베디드 언어에 대한 언어 기능 구현에 중점을 둡니다. 임베디드 언어에 대한 구문 강조 표시를 제공하는 데 관심이 있다면 구문 강조 표시 가이드에서 정보를 찾을 수 있습니다.
이 가이드에는 두 가지 접근 방식을 보여주는 두 가지 샘플이 포함되어 있습니다: **언어 서비스** 및 **요청 전달**. 두 샘플을 모두 검토하고 각 접근 방식의 장단점으로 결론을 내릴 것입니다.
두 샘플의 소스 코드는 다음에서 찾을 수 있습니다.
이제 우리가 구축할 임베디드 언어 서버입니다.

두 샘플 모두 설명을 위해 html1이라는 새 언어를 추가합니다. .html1 파일을 만들고 다음 기능을 테스트할 수 있습니다.
- HTML 태그에 대한 자동 완성
<style>태그 내 CSS에 대한 자동 완성- CSS에 대한 진단 (언어 서비스 샘플에서만)
언어 서비스
**언어 서비스**는 단일 언어에 대한 프로그래밍 방식 언어 기능을 구현하는 라이브러리입니다. **언어 서버**는 언어 서비스를 포함하여 임베디드 언어를 처리할 수 있습니다.
VS Code의 HTML 지원 개요입니다.
- 내장 html 확장은 HTML에 대한 구문 강조 표시 및 언어 구성만 제공합니다.
- 내장 html-language-features 확장에는 HTML 프로그래밍 방식 언어 기능을 제공하는 HTML 언어 서버가 포함되어 있습니다.
- HTML 언어 서버는 vscode-html-languageservice를 사용하여 HTML을 지원합니다.
- CSS 언어 서버는 vscode-css-languageservice를 사용하여 HTML 내 CSS를 지원합니다.
HTML 언어 서버는 HTML 문서를 분석하고 언어 영역으로 분해한 다음 해당 언어 서비스를 사용하여 언어 서버 요청을 처리합니다.
예를 들어,
<|에서의 자동 완성 요청의 경우, HTML 언어 서버는 HTML 언어 서비스를 사용하여 HTML 자동 완성을 제공합니다.<style>.foo { | }</style>에서의 자동 완성 요청의 경우, HTML 언어 서버는 CSS 언어 서비스를 사용하여 CSS 자동 완성을 제공합니다.
HTML 및 CSS에 대한 자동 완성, CSS 진단 오류를 구현하는 HTML 언어 서버의 간소화된 버전인 lsp-embedded-language-service 샘플을 살펴보겠습니다.
언어 서비스 샘플
**참고**: 이 샘플은 프로그래밍 방식 언어 기능 주제 및 언어 서버 확장 가이드에 대한 지식이 있다고 가정합니다. 코드는 lsp-sample을 기반으로 합니다.
소스 코드는 microsoft/vscode-extension-samples에서 사용할 수 있습니다.
lsp-sample과 비교하여 클라이언트 측 코드는 동일합니다.
위에서 언급했듯이 서버는 문서를 다른 언어 영역으로 분해하여 임베디드 콘텐츠를 처리합니다.
간단한 예시는 다음과 같습니다.
<div></div>
<style>.foo { }</style>
이 경우 서버는 <style> 태그를 감지하고 .foo { }를 CSS 영역으로 표시합니다.
특정 위치에 대한 자동 완성 요청이 주어지면 서버는 다음 로직을 사용하여 응답을 계산합니다.
- 위치가 영역에 속하는 경우
- 해당 영역의 언어를 사용하는 가상 문서로 처리하고 다른 모든 영역은 공백으로 대체합니다.
- 위치가 영역에 속하지 않는 경우
- HTML의 가상 문서로 처리하고 모든 영역을 공백으로 대체합니다.
예를 들어, 이 위치에서 자동 완성을 수행할 때
<div></div>
<style>.foo { | }</style>
서버는 해당 위치가 영역 내에 있다고 판단하고 다음과 같은 내용의 가상 CSS 문서를 계산합니다 (█는 공백을 나타냄).
███████████
███████.foo { | }████████
서버는 vscode-css-languageservice를 사용하여 이 문서를 분석하고 자동 완성 항목 목록을 계산합니다. 이제 콘텐츠에 HTML이 없으므로 CSS 언어 서비스는 문제없이 처리할 수 있습니다. CSS가 아닌 모든 콘텐츠를 공백으로 대체함으로써 수동으로 위치를 오프셋하는 수고를 덜 수 있습니다.
자동 완성 요청을 처리하는 서버 코드
connection.onCompletion(async (textDocumentPosition, token) => {
const document = documents.get(textDocumentPosition.textDocument.uri);
if (!document) {
return null;
}
const mode = languageModes.getModeAtPosition(document, textDocumentPosition.position);
if (!mode || !mode.doComplete) {
return CompletionList.create();
}
const doComplete = mode.doComplete!;
return doComplete(document, textDocumentPosition.position);
});
CSS 영역에 속하는 모든 언어 서버 요청을 처리하는 CSS 모드
export function getCSSMode(
cssLanguageService: CSSLanguageService,
documentRegions: LanguageModelCache<HTMLDocumentRegions>
): LanguageMode {
return {
getId() {
return 'css';
},
doComplete(document: TextDocument, position: Position) {
// Get virtual CSS document, with all non-CSS code replaced with whitespace
const embedded = documentRegions.get(document).getEmbeddedDocument('css');
// Compute a response with vscode-css-languageservice
const stylesheet = cssLanguageService.parseStylesheet(embedded);
return cssLanguageService.doComplete(embedded, position, stylesheet);
}
};
}
이것은 임베디드 언어를 처리하는 간단하고 효과적인 접근 방식입니다. 그러나 이 접근 방식에는 몇 가지 단점이 있습니다.
- 언어 서버가 의존하는 언어 서비스를 지속적으로 업데이트해야 합니다.
- 언어 서버와 다른 언어로 작성된 언어 서비스를 포함하는 것은 어려울 수 있습니다. 예를 들어, PHP로 작성된 PHP 언어 서버는 TypeScript로 작성된
vscode-css-languageservice를 포함하는 것이 번거로울 것입니다.
이제 위에 언급된 문제를 해결할 **요청 전달**에 대해 다루겠습니다.
요청 전달
간단히 말해, 요청 전달은 언어 서비스와 유사한 방식으로 작동합니다. 요청 전달 접근 방식도 언어 서버 요청을 받아 가상 콘텐츠를 계산하고 응답을 계산합니다.
주요 차이점은 다음과 같습니다.
- 언어 서비스 접근 방식은 라이브러리를 사용하여 언어 서버 응답을 계산하는 반면, 요청 전달은 요청을 VS Code로 다시 보내 활성화되어 임베디드 언어에 대한 자동 완성 제공자를 등록한 확장을 사용합니다.
다시 간단한 예시입니다.
<div></div>
<style>.foo { | }</style>
자동 완성은 다음과 같이 발생합니다.
- 언어 클라이언트는
workspace.registerTextDocumentContentProvider를 사용하여embedded-content문서에 대한 가상 텍스트 문서 제공자를 등록합니다. - 언어 클라이언트는
<FILE_URI>에 대한 자동 완성 요청을 가로챕니다. - 언어 클라이언트는 요청 위치가 CSS 영역에 속한다고 판단합니다.
- 언어 클라이언트는
embedded-content://css/<FILE_URI>.css와 같은 새 URI를 구성합니다. - 그런 다음 언어 클라이언트는
commands.executeCommand('vscode.executeCompletionItemProvider', ...)를 호출합니다.- VS Code의 CSS 언어 서버가 이 제공자 요청에 응답합니다.
- 가상 텍스트 문서 제공자는 CSS가 아닌 코드를 모두 공백으로 대체한 가상 콘텐츠를 CSS 언어 서버에 제공합니다.
- 언어 클라이언트는 VS Code로부터 응답을 받아 응답으로 보냅니다.
이 접근 방식을 사용하면 CSS를 이해하는 라이브러리를 코드에 포함하지 않고도 CSS 자동 완성을 계산할 수 있습니다. VS Code가 CSS 언어 서버를 업데이트함에 따라 코드를 업데이트하지 않고도 최신 CSS 언어 지원을 받을 수 있습니다.
이제 샘플 코드를 살펴보겠습니다.
요청 전달 샘플
**참고**: 이 샘플은 프로그래밍 방식 언어 기능 주제 및 언어 서버 확장 가이드에 대한 지식이 있다고 가정합니다. 코드는 lsp-sample을 기반으로 합니다.
소스 코드는 microsoft/vscode-extension-samples에서 사용할 수 있습니다.
문서 URI와 가상 문서 간의 매핑을 유지하고 해당 요청에 대해 제공합니다.
const virtualDocumentContents = new Map<string, string>();
workspace.registerTextDocumentContentProvider('embedded-content', {
provideTextDocumentContent: uri => {
// Remove leading `/` and ending `.css` to get original URI
const originalUri = uri.path.slice(1).slice(0, -4);
const decodedUri = decodeURIComponent(originalUri);
return virtualDocumentContents.get(decodedUri);
}
});
언어 클라이언트의 middleware 옵션을 사용하여 자동 완성 요청을 가로챕니다.
let clientOptions: LanguageClientOptions = {
documentSelector: [{ scheme: 'file', language: 'html' }],
middleware: {
provideCompletionItem: async (document, position, context, token, next) => {
// If not in `<style>`, do not perform request forwarding
if (
!isInsideStyleRegion(
htmlLanguageService,
document.getText(),
document.offsetAt(position)
)
) {
return await next(document, position, context, token);
}
const originalUri = document.uri.toString(true);
virtualDocumentContents.set(
originalUri,
getCSSVirtualContent(htmlLanguageService, document.getText())
);
const vdocUriString = `embedded-content://css/${encodeURIComponent(originalUri)}.css`;
const vdocUri = Uri.parse(vdocUriString);
return await commands.executeCommand<CompletionList>(
'vscode.executeCompletionItemProvider',
vdocUri,
position,
context.triggerCharacter
);
}
}
};
잠재적 문제
임베디드 언어 서버를 구현하는 동안 많은 문제를 겪었습니다. 아직 완벽한 해결책은 없지만, 여러분도 이러한 문제를 겪을 가능성이 높기 때문에 미리 알려드립니다.
언어 기능 구현의 어려움
일반적으로 언어 영역 경계를 넘나드는 언어 기능은 구현하기가 더 어렵습니다. 예를 들어, 자동 완성이나 호버 콘텐츠는 임베디드 콘텐츠의 언어를 감지하고 임베디드 콘텐츠를 기반으로 응답을 계산할 수 있으므로 구현하기 쉽습니다. 그러나 포맷팅이나 이름 바꾸기와 같은 언어 기능은 특별한 처리가 필요할 수 있습니다. 포맷팅의 경우 단일 문서 내의 여러 영역에 대한 들여쓰기 및 포맷터 설정을 처리해야 합니다. 이름 바꾸기의 경우 다른 문서 내의 서로 다른 영역 간에 작동하도록 하는 것이 어려울 수 있습니다.
언어 서비스는 상태 저장 및 포함이 어려울 수 있습니다.
VS Code의 HTML 지원은 HTML, CSS 및 JavaScript 언어 기능을 제공합니다. HTML 및 CSS 언어 서비스는 상태 저장되지 않지만, JavaScript 언어 기능을 지원하는 TypeScript 서버는 상태 저장됩니다. TypeScript에 프로젝트의 상태를 알리기 어렵기 때문에 HTML 문서 내에서는 기본적인 JavaScript 지원만 제공합니다. 예를 들어 CDN에서 호스팅되는 lodash 라이브러리를 가리키는 <script> 태그를 포함하면 <script> 태그 내에서 _. 자동 완성을 얻을 수 없습니다.
인코딩 및 디코딩
문서의 기본 언어는 내장 언어와 다른 인코딩 또는 이스케이프 규칙을 가질 수 있습니다. 예를 들어, 이 HTML 문서는 HTML 사양에 따라 유효하지 않습니다.
<SCRIPT type="text/javascript">
document.write ("<EM>This won't work</EM>")
</SCRIPT>
이 경우 내장 JavaScript의 언어 서버가 </를 포함하는 결과를 반환하면 <\/로 이스케이프해야 합니다.
결론
두 접근 방식 모두 장단점이 있습니다.
언어 서비스
- + 언어 서버 및 사용자 경험에 대한 완전한 제어.
- + 다른 언어 서버에 대한 의존성이 없습니다. 모든 코드가 하나의 리포지토리에 있습니다.
- + 언어 서버는 모든 LSP 호환 코드 편집기에서 재사용할 수 있습니다.
- - 다른 언어로 작성된 언어 서비스를 포함하기 어려울 수 있습니다.
- - 언어 서비스 종속성으로부터 새로운 기능을 얻기 위해 지속적인 유지 관리가 필요합니다.
요청 전달
- + 언어 서버 언어로 작성되지 않은 언어 서비스 포함 문제 방지 (예: C# 지원을 위한 Razor 언어 서버에 C# 컴파일러 포함).
- + 다른 언어 서비스로부터 새로운 기능을 업스트림으로 얻기 위한 유지 관리가 필요 없습니다.
- - 진단 오류에는 작동하지 않습니다. VS Code API는 진단을 '풀링'(요청)할 수 있는 진단 제공자를 지원하지 않습니다.
- - 제어 부족으로 인해 다른 언어 서버와 상태를 공유하기 어렵습니다.
- - 언어 간 기능 구현이 어려울 수 있습니다 (예:
<div class="foo">가 있을 때.foo에 대한 CSS 자동 완성 제공).
전반적으로, 이 접근 방식은 사용자 경험에 대한 제어권을 더 많이 제공하고 서버가 LSP 호환 편집기에서 재사용 가능하므로 언어 서비스를 포함하여 언어 서버를 구축하는 것이 좋습니다. 그러나 임베디드 콘텐츠를 컨텍스트나 언어 서버 상태 없이 쉽게 처리할 수 있는 간단한 사용 사례가 있거나 Node.js 라이브러리 번들링이 문제가 되는 경우 요청 전달 접근 방식을 고려할 수 있습니다.