구문 강조 가이드
Syntax Highlight Guide
구문 강조는 Visual Studio Code 에디터에서 표시되는 소스 코드의 색상과 스타일을 결정합니다. 이는 자바스크립트의 if
나 for
와 같은 키워드를 다른 문자열이나 주석, 변수 이름과 다르게 채색하는 역할을 합니다.
구문 강조에는 2가지 구성 요소가 있습니다.
- grammar를 이용하여 텍스트를 토큰과 스코프로 나누기
- 그 다음 테마를 이용하여 스코프를 특정 색상, 스타일에 매핑하기
이 문서는 첫번째부분만 다룹니다: 텍스트를 기존의 색상 테마가 채색 할 수 있게 토큰과 스코프로 나누는것. 에디터의 다른 스코프의 스타일을 커스터마이즈 하는것에 대해서는, 색상 테마 가이드를 참조하십시오.
TextMate grammars
VS Code는 TextMate grammars를 사용하여 텍스트를 토큰으로 나눕니다. TextMate grammar는 구조화된 Oniguruma regular expressions의 모음이며, 일반적으로 plist나 JSON으로 작성되어 있습니다. 여러분은 TextMate grammar에 대한 유용한 소개를 여기에서 확인 할 수 있으며, 기존의 TextMate grammar를 통해 어떻게 작동하는지 배울 수 있습니다.
토큰과 스코프
토큰은 동일한 프로그램 엘리먼트의 일부인 한개 이상의 문자 입니다. 예를 들면 +
, *
와 같은 연산자, myVar
같은 변수이름, "my string"
같은 문자열이 있습니다.
각 토큰은 토큰의 컨텍스트를 지정하는 스코프와 연관이 있습니다. 스코프는 점으로 구분된 식별자 목록으로, 현재 토큰의 컨텍스트를 지정합니다. 자바스크립트의 +
연산자는 keyword.operator.arithmetic.js
라는 스코프를 갖습니다.
테마는 스코프를 매핑하여 구문 강조에 색상과 스타일을 지정합니다. TextMate는 많은 테마에서 쓰이는 공통 스코프 목록를 제공합니다. 여러분의 grammar가 가능한 많은 부분을 지원하기 위해서, 새롭게 작성하기 보단, 기존의 스코프를 기반으로 작성 하십시오.
스코프는 각 토큰이 상위 스코프와 연관 되도록 중첩됩니다. 아래의 예시는 간단한 자바스크립트 합수에서 +
연산자의 스코프 구조를 보여주기 위해 스코프 인스펙터를 사용한 것입다. 이 때 가장 구체적인 스코프는 위쪽에, 일반적인 스코프는 아래쪽에 위치합니다.
부모 스코프 정보 또한 테마에 쓰입니다. 테마가 스코프를 타겟할때 따로 색상이 지정되지 않은 모든 토큰은 부모 스코프에 따라 색상이 지정됩니다.
기본 grammar 작성
VS Code는 json TextMate grammar를 지원합니다. 이는 grammars
contribution point를 통해 작성 될 수 있습니다.
각 grammar 작성은 : grammar가 적용될 언어 식별자, grammar의 토큰의 최상위 스코프 이름, grammar 파일의 상대 경로를 명시합니다. 아래의 예시는 가상의 abc
언어에 대한 grammar 작성을 표시합니다.
{
"contributes": {
"languages": [
{
"id": "abc",
"extensions": [".abc"]
}
],
"grammars": [
{
"language": "abc",
"scopeName": "source.abc",
"path": "./syntaxes/abc.tmGrammar.json"
}
]
}
}
Grammar파일 자체는 최상위 레벨 규칙으로 구성되어 있습니다. 이는 일반적으로 프로그램의 최상위 레벨 엘리먼트의 목록인 patterns
와 각 엘리먼트를 정의하는 repository
로 구성됩니다. Grammar의 다른 규칙은 repository
에서 {"include":"#id"}
를 사용하여 엘리먼트를 참조 할 수 있습니다.
예시 abc
grammar는 a
,b
그리고 c
를 keyword로 그리고 parens의 중첩을 expression으로 표기합니다.
{
"scopeName": "source.abc",
"patterns": [{ "include": "#expression" }],
"repository": {
"expression": {
"patterns": [{ "include": "#letter" }, { "include": "#paren-expression" }]
},
"letter": {
"match": "a|b|c",
"name": "keyword.letter"
},
"paren-expression": {
"begin": "\\(",
"end": "\\)",
"beginCaptures": {
"0": { "name": "punctuation.paren.open" }
},
"endCaptures": {
"0": { "name": "punctuation.paren.close" }
},
"name": "expression.group",
"patterns": [{ "include": "#expression" }]
}
}
}
아래의 간단한 프로그램 처럼, Grammar 엔진은 expression
규칙을 문서의 모든 텍스트에 적용하려 시도 할 것입니다 :
a
(
b
)
x
(
(
c
xyz
)
)
(
a
예시 grammar는 다음과 같은 스코프를 생성합니다(구체적인것에서 일반적인것으로 좌우로 나열):
a keyword.letter, source.abc
( punctuation.paren.open, expression.group, source.abc
b keyword.letter, expression.group, source.abc
) punctuation.paren.close, expression.group, source.abc
x source.abc
( punctuation.paren.open, expression.group, source.abc
( punctuation.paren.open, expression.group, expression.group, source.abc
c keyword.letter, expression.group, expression.group, source.abc
xyz expression.group, expression.group, source.abc
) punctuation.paren.close, expression.group, expression.group, source.abc
) punctuation.paren.close, expression.group, source.abc
( source.abc
a keyword.letter, source.abc
문자열 xyz
처럼 규칙과 일치 하지 않는 텍스트는 현재 스코프에 포함되는 것을 유념하십시오. 파일 끝의 마지막 paren은 end
규칙에 맞지 않기 때문에 expression.group
의 부분이 되지 않습니다.
임베디드 언어
HTML 안의 CSS 스타일 블록처럼, grammar가 부모 언어 내의 임베디드 언어를 포함한다면 embeddedLanguages
contribution point를 사용하여 VS Code가 임베디드 언어를 부모 언어와 다르게 처리 할 수 있도록 하십시오. 이는 대괄호 매칭, 주석, 그리고 임베디드 언어에서 기대되는 기본 언어 기능들을 제공합니다.
embeddedlanguages
contribution point는 임베디드 언어의 스코프를 최상위 레벨 언어 스코프로 매핑합니다. 아래의 예시에서, meta.embedded.block.javascript
스코프의 모든 토큰은 자바스크립트 주석으로써 처리 될 것입니다.
{
"contributes": {
"grammars": [
{
"path": "./syntaxes/abc.tmLanguage.json",
"scopeName": "source.abc",
"embeddedLanguages": {
"meta.embedded.block.javascript": "javascript"
}
}
]
}
}
이제 meta.embedded.block.javascript
로 표기된 토큰 내부에서 주석을 달거나 snippet을 시도 할 경우, 올바른 자바스크립트 스타일 주석 //
과 snippet을 갖게 될 것입니다.
새 grammar 익스텐션 개발
빠르게 새 grammar 익스텐션을 생성하기 위해, VS Code’s Yeoman templates를 사용하여 yo code
를 실행하고 New Language
를 선택하십시오:
Yeoman은 여러분에게 몇가지 기초 질문을 통해 새 익스텐션 생성을 도울 것입니다. 새 grammar를 생성하기 위한 중요한 질문은 아래와 같습니다:
Language Id
- 언어의 특수한 식별자.Language Name
- 사람이 읽을 수 있는 언어의 이름.Scope names
- Grammar의 루트 TextMate 스코프
생성기는 여러분이 새 언어와 기존 언어의 새 grammar를 작성하는 것으로 간주 합니다. 만약 기존의 언어에 대해 grammar를 생성하는 경우, 이를 목표 언어의 정보에 채워넣고 생성된 language
contribution point를 삭제하십시오.
모든 질문에 답변을 하고나면, Yeoman은 구조를 가진 새 익스텐션을 생성 할 것입니다:
VS Code가 이미 알고 있는 언어의 grammar를 작성하려는 경우, package.json
의 languages
contribution point를 삭제하는 것을 잊지 마십시오.
기존 TextMate grammar 변형
yo code
는 기존 TextMage grammar를 VS Code 익스텐션으로 변환할때도 쓰입니다. 다시, yo code
를 실행하고 Language extension
을 선택하십시오. 기존 grammar 파일을 요청 할 경우 .tmLanguage
나 .json
TextMate grammar 파일의 전체 경로를 제공 하십시오.
YAML로 Grammar 작성
Grammar가 복잡해질수록, 이해하기와 json으로 유지하는 것은 어려워집니다. 만약 복잡한 정규표현식을 작성하거나 grammar에 설명을 하는 주석을 달아야 한다면, yaml을 사용하는 방안을 고려해보십시오.
Yaml grammar는 json 기반 grammar와 동일한 구조를 갖지만 대신 yaml의 더 간결한 기능, 여러줄 문자열 이나 주석등을 사용 할 수 있게 합니다.
VS Code는 json grammar만 불러 올 수 있기 때문에, yaml 기반 grammar는 json으로 변환 되어야 합니다. 이는 js-yaml
package와 커맨드 라인 도구를 이용하면 쉽게 수행 할 수 있습니다.
# Install js-yaml as a development only dependency in your extension
$ npm install js-yaml --save-dev
# Use the command-line tool to convert the yaml grammar to json
$ npx js-yaml syntaxes/abc.tmLanguage.yaml > syntaxes/abc.tmLanguage.json
스코프 인스펙터
VS Code의 빌트인 스코프 인스펙터 도구는 Grammar를 디버그 할 수 있게 돕습니다. 이는 파일의 현재 위치에 있는 토큰의 스코프를 보여주며, 해당 토큰에 적용 되는 테마 규칙에 대한 메타 데이터를 보여줍니다.
Command Palette에서 Developer: Inspect TM Scores
커맨드 나 키바인딩 생성을 통해 스코프 인스펙터를 실행 하십시오.
{
"key": "cmd+alt+shift+i",
"command": "editor.action.inspectTMScopes"
}
스코프 인스펙터는 다음 정보를 보여줍니다:
- 현재 토큰.
- 토큰의 메타데이터와 계산된 모양. 임베디드 언어의 경우
language
와token type
이 중요합니다. - 토큰에 적용된 테마 규칙들. 토큰에 적용된 현재 스타일 테마 규칙만 보여주며 오버라이드 된 규칙은 보여주지 않습니다.
- 완전한 스코프 목록, 가장 구체적인것은 최상단에 위치합니다.
Injection grammars
Injection grammars 는 기존 Grammar를 확장하게 합니다. 이는 일반 TextMate grammar로 기존 grammar에서 특정 스코프에 추가 됩니다. Injection grammar의 예시 역할는 아래와 같습니다:
- 주석내의
TODO
와 같은 키워드 강조. - 기존 grammar에 특정 스코프 정보 추가.
- 새 언어 Markdown 코드 블록에 대해 강조 추가.
기본 injection grammar 생성
Injection grammar는 일반 grammar와 같이 package.json
에 작성 됩니다. 그러나 language
에 명시되지 않고 injectTo
를 사용하여 목표 언어 스코프목록에 추가 될 지를 명시합니다.
아래 예시의 경우, 자바스크립트의 TODO
키워드를 강조하는 단순한 injection grammar를 만들것 입니다. 자바스크립트 파일에서 이를 적용 하기위해, source.js
를 injectTo
의 타겟 언어 스코프로 사용하십시오:
{
"contributes": {
"grammars": [
{
"path": "./syntaxes/injection.json",
"scopeName": "todo-comment.injection",
"injectTo": ["source.js"]
}
]
}
}
Grammar자체는 최상위 injectionSelector
를 제외하면 표준 TextMate grammar입니다. injectionSelector
는 스코프 선택기로 어느 범위에 injected grammar가 적용 될지를 선택합니다. 예시에선 모든 //
주석에 TODO
단어를 강조하는것이 목표입니다. 스코프 인스펙터 를 사용하여, 자바스크립트의 이중 슬래시 주석이 comment.line.double-slash
라는 스코프를 가진다는것을 알 수 있으니 우리의 injection 선택기는 L:comment.line.double-slash
입니다:
{
"scopeName": "todo-comment.injection",
"injectionSelector": "L:comment.line.double-slash",
"patterns": [
{
"include": "#todo-keyword"
}
],
"repository": {
"todo-keyword": {
"match": "TODO",
"name": "keyword.todo"
}
}
}
injection 선택기의 L:
은 injection이 기존 grammar 규칙의 왼쪽에 추가 된다는 의미입니다. 이는 우리의 injected grammar 규칙이 기존 grammar 규칙보다 먼저 적용 됨을 의미합니다.
임베디드 언어
Injection grammar는 부모 grammar에 임베디드 언어를 제공 할 수 있게 합니다. 일반 grammar와 마찬가지로 embeddedLanguages
를 사용하여 임베디드 언어의 스코프를 최상위 레벨 언어의 스코프로 매핑하십시오.
자바스크립트 문자열에서 SQL 쿼리를 강조하는 익스텐션은 embeddedLanguages
를 사용하여 meta.embedded.inline.sql
로 표기된 문자열 내부의 모든 토큰이 대괄호 일치나 snippet 선택 같은 기본 언어 기능을 sql로 처리 할 수 있게 합니다.
{
"contributes": {
"grammars": [
{
"path": "./syntaxes/injection.json",
"scopeName": "sql-string.injection",
"injectTo": ["source.js"],
"embeddedLanguages": {
"meta.embedded.inline.sql": "sql"
}
}
]
}
}
토큰 타입과 임베디드 언어
inejction 임베디드 언어에 대하여 한가지 추가로 알아야 할 점은: VS Code는 문자열 내의 모든 토큰을 문자열 컨텐츠로 취급하고 주석의 모든 토큰을 토큰으로 취급합니다. 대괄호 매칭 같은 기능과 자동 닫기 등이 문자열과 주석내에선 사용 불가능하기 때문에, 만약 임베디드 언어가 문자열이나 주석내에 나타난다면 이러한 기능 또한 임베디드 언어에서는 비활성화될 것입니다.
이 규칙을 오버라이드 하기 이ㅜ해, meta.embedded.*
스코프를 사용하여 VS Code의 문자령이나 주석 컨텐츠에 대한 토큰 마킹을 다시 설정하십시오. VS Code가 임베디드 언어를 적절히 다루기 위해 meta.embedded.*
스코프 내부에 임베디드 언어를 래핑하는것은 좋은 선택입니다.
만약 meat.embedded.*
스코프를 grammar에 더할 수 없다면 대신 grammar의 tokenTypes
contribution point를 사용하여 특정 스코프를 컨텐츠에 매핑 할 수 있습니다. 아래의 tokenTypes
주제는 my.sql.template.string
스코프의 모든 컨텐츠가 소스 코드로 취급 되는 것을 보여 줍니다.
{
"contributes": {
"grammars": [
{
"path": "./syntaxes/injection.json",
"scopeName": "sql-string.injection",
"injectTo": ["source.js"],
"embeddedLanguages": {
"my.sql.template.string": "sql"
},
"tokenTypes": {
"my.sql.template.string": "other"
}
}
]
}
}