1. colorvariabletracker
- 이번 포스팅은 “[vscode] 컬러 변수 뷰어 만들기” 시리즈의 마지막인 “extension 구현하기”이다.
- 구현한 extension은 colorvariabletracker 로, 현재 확장을 릴리즈한 상태이다. (링크)
- 컬러 변수 뷰어(colorvariabletracker)는 특정 파일에 선언된 컬러 변수를 추적하는 확장이다.
- 컬러 변수 뷰어에서 구현한 기능은 6가지이다.
- 이번 시간에는 이 6가지를 구현한 방법은 간단하게 설명하고자 한다 🙂
기능 | 방식 |
---|---|
추적하고 싶은 파일을 설정할 수 있다. | .vscode/settings.json에 파일 경로 지정 |
선언된 컬러변수를 vscode 사이드바에서 표 형식으로 볼 수 있어야 한다. | webview로 구현 |
검색창에서 컬러 변수명 혹은 컬러값 입력시 해당하는 컬러변수를 볼 수 있다. | 검색어에 해당하는 컬러변수를 webview에 보여줌 |
현재 추적하고 컬러 파일을 열 수 있다. | vscode의 showTextDocument() 사용 |
현재 추적하고 있는 컬러값이 수정되면, 사이드바에 반영된다. | 파일 변경을 감지하여, webview 업데이트 |
사이드바에서 클립보드로 컬러를 복사할 수 있다. | vscode의 clipboard.writeText() 사용 |
잠깐! 이 포스팅은 webview에 대한 지식이 필요하므로, 만약 webview를 모른다면 이전 포스팅을 읽는 건 추천한다. (이전 포스팅은 여기!)
2. 구현 방식 살펴보기
1) 추적할 파일 경로 설정하기
(1) 컨셉
- 확장을 사용하기 위해, 먼저 추적할 컬러 파일의 상대 경로를 지정해야 한다.
.vscode/settings.json
에 추적하고 싶은 컬러 파일의 상대 경로를 추가하도록 설정했다.- 예를 들어 경로를 “/color.scss”로 설정하면, 루트위치의 color.scss를 추적한다는 의미가 된다.
- 내부 구현에서는 어떻게 이 데이터에 접근해 파일 경로를 설정했을까?
// .vscode/settings.json
{
"colorVariableTracker.filePath": "/color.scss"
}
(2) 구현 방식
- 방식은 확장이 구동될 때,
vscode.workspace.getConfiguration()
을 호출해 파일 경로를 구성하는 것이다. resolveWebviewView
은 초기webview
를 설정하는 함수인데, 이 함수 내부에 파일을 탐색하는 코드를 추가한다.
// SidebarProvider.ts
public async resolveWebviewView(webviewView: vscode.WebviewView) {
registerColorFilePath(); // 파일 경로 추적 함수
};
registerColorFilePath()
은 확장이 구동될 때, 추적할 파일 경로를 구성한다.
// SidebarProvider.ts
public registerColorFilePath() {
new Promise((resolve, reject) => {
// (a)
const config = vscode.workspace.getConfiguration('colorVariableTracker');
const filePath = config.get('filePath') as string;
// (b)
if (!filePath) {
vscode.window.showErrorMessage('Please set a color file path in /.vscode/settings.json');
}
// (c)
else if (!filePath.includes('.css') && !filePath.includes('.scss') && !filePath.includes('.sass')) {
vscode.window.showErrorMessage('Color file must be a .css or .scss or .sass file.');
}
// (d)
else {
this._colorFilePath = `${this._baseFilePath}${filePath}`;
vscode.window.showInformationMessage(`Color file path is set to ${this._colorFilePath}`);
}
resolve('');
});
}
코드번호 | 설명 |
---|---|
(a) | - vscode.workspace.getConfiguration 을 사용해 .vscode/settings.json에 접근한다.- 그리고 colorVariableTracker섹션에서 filePath를 가져온다. |
(b) | - filePath 가 없다면, 경고 메시지를 표시한다. |
(c) | - 가져온 파일 경로가 올바르지 않으면(확장자가 .css, .scss, 또는 .sass가 아니면), 경고 메시지를 표시한다. |
(d) | - 파일 경로가 올바르다면, 내부 변수 _colorFilePath 에 파일 경로를 저장한다.- 그리고 사용자에게 파일 경로가 설정되었다는 정보 메시지를 표시한다. |
2) webview로 컬러변수 보여주기
(1) 컨셉
- 사이드바에 컬러변수명, 변수값을 보여주기 위해 표 형식으로 데이터를 렌더링해야한다.
webview
는 html 형식으로 데이터를 표현할 수 있는데,getHtmlForWebview()
에 원하는 데이터를 넣어서 html 형식을 렌더링하면 된다.- 아래 코드는
webview
에html
를 렌더링하는 간단 예시이다.
// 초기 webview 렌더링하는 함수
public async resolveWebviewView(webviewView: vscode.WebviewView) {
...
webviewView.webview.html = this.getHtmlForWebview(webviewView.webview);
this._view = webviewView;
}
private getHtmlForWebview(webview: vscode.Webview) {
return `
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
</head>
<body>
<!-- 여기에 원하는 html 태그 추가 -->
</body>
</html>`;
}
};
(2) 구현 방식
- 그렇다면, 표 형식으로 데이터를 띄우려면 어떻게 구현해야할까?
- 구현 방식은 (a) 추적하는 파일의 데이터를 탐색하여 (b) 선언된 변수 중 값이 컬러인 데이터를 찾아서 (c) html형태로 렌더링하면 된다.
// SidebarProvider.ts
// 초기 webview 렌더링하는 함수
public async resolveWebviewView(webviewView: vscode.WebviewView) {
// (a)
await this.registerColorFilePath();
// (b)
this._docText = await this.getColorVariables();
// (c)
webviewView.webview.options = { enableScripts: true, localResourceRoots: [this._extensionUri] };
webviewView.webview.html = this.getHtmlForWebview(webviewView.webview);
this._view = webviewView;
}
코드번호 | 설명 |
---|---|
(a) | - 추적할 컬러 파일의 상대 경로를 지정한다. 상대 경로는 _colorFilePath 변수에 저장된다. |
(b) | - _colorFilePath 경로에 있는 파일을 탐색하여, 컬러변수값을 _docText 에 저장한다.- this._docText 는 getHtmlForWebview() 에서 쓰인다. |
(c) | - webviewView.webview.html 에 getHtmlForWebview() 값을 넣어서, html를 렌더링한다. |
getColorVariables()
는openTextDocument()
로 파일 데이터에 접근하여, html형식으로 포맷팅된 값을 리턴한다.
// SidebarProvider.ts
private async getColorVariables() {
if (!this._colorFilePath) return '';
try {
const document = await vscode.workspace.openTextDocument(this._colorFilePath);
return this.formatColorVariables(document.getText());
} catch (error) {
return 'No color variables found in the file.';
}
}
formatColorVariables()
는 표 형식으로 html를 구성하는 함수이다.
// SidebarProvider.ts
private formatColorVariables(text: string) {
const colorObj = getColorVariableObj(text, this._searchText);
const colorText: string[] = [];
Object.keys(colorObj).forEach((key) => {
const row = `
<tr>
<td>${key}</td>
<td>
<button class="colorBox"
style="background-color:${colorObj[key]}"
title="${colorObj[key]}"
data-color="${colorObj[key]}"
>
</button>
</td>
</tr>`;
colorText.push(row);
}
);
return colorText.join("");
};
getColorVariableObj()
는 정규 표현식으로, 선언된 변수 중 컬러값을 가지는 변수만을 리턴해준다.
// SidebarProvider.ts
function getColorVariableObj(cssCode: string) {
const pattern = /(--[\w-]+|\$[\w-]+)\s*:\s*(#[0-9a-fA-F]{3,6}|rgba?\([^)]*\));/g;
const matches = cssCode.matchAll(pattern);
const filteredMatches = Array.from(matches).filter((match) => {
return match[1].includes(searchText) || match[2].includes(searchText);
});
const colorVariable: { [key: string]: string } = {};
for (const match of filteredMatches) {
colorVariable[match[1]] = match[2];
}
return colorVariable;
}
- 앞선 과정을 거쳐 리턴된 html 코드는
_docText
에 저장되는데, getHtmlForWebview()
에${_docText}
를 삽입하여 렌더링하면 완성이다!
// SidebarProvider.ts
private getHtmlForWebview(webview: vscode.Webview) {
const styleResetUri = webview.asWebviewUri(
vscode.Uri.joinPath(this._extensionUri, "media", "reset.css")
);
const styleVSCodeUri = webview.asWebviewUri(
vscode.Uri.joinPath(this._extensionUri, "media", "vscode.css")
);
return `<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<link rel='stylesheet' href='${styleResetUri}' />
<link rel='stylesheet' href='${styleVSCodeUri}' />
</head>
<body>
<table>
<thead>
<tr>
<th>Variable Name</th>
<th>Color Value</th>
</tr>
</thead>
<tbody>
${ this._docText } <!-- 변수 테이블 삽입 -->
</tbody>
</table>
</body>
</html>`;
}
}
- 실제 렌더링 예시는 아래와 같다.
3) 검색어에 해당하는 컬러변수를 webview에 보여주기
(1) 컨셉
- 검색창에 컬러 변수명이나 변수값을 입력하고, 검색을 눌렀을 때 결과값을 보여주고자 한다.
- 그렇다면, 검색창, 버튼 그리고 이벤트는 어떻게 등록할 수 있을까?
webview
는 html 형식을 렌더링할 수 있기에, 버튼이나 입력창도 추가할 수 있다.- 또한
<body>
에<script>
태그를 사용해 이벤트도 선언할 수 있다. - 그래서 검색창이나 검색 버튼을 추가하고, 버튼 클릭시 그에 따른 데이터 조작이 가능하다.
- 만약 버튼이 하나 있다고 가정했을 때, 이벤트 동작 흐름은 아래와 같다.
선언 위치 | 동작 |
---|---|
웹뷰 > body > button | - 사용자가 웹뷰에서 버튼을 클릭한다. |
웹뷰 > script에 선언된 이벤트 | - 클릭 이벤트가 감지되면, 이벤트 리스너에 의해 등록된 함수가 호출된다. |
- 호출된 함수에서 acquireVsCodeApi() 를 사용하여 VSCode API를 가져온다. - 이를 통해 VSCode 확장 프로그램과 웹뷰 간의 통신이 가능하다. |
|
- acquireVsCodeApi().postMessage 로 VSCode 확장 프로그램으로 메시지를 보낸다. |
|
vscode | - VSCode 확장 프로그램은 이 메시지를 수신하여 특정 동작을 실행한다. |
(2) 구현 방식
- 검색 관련 기능은 (1) 검색어 업데이트, (2) Enter로 검색하기, (3) 검색하기 버튼, (4) 검색어 초기화이다.
- 이 중 (1) 검색어 업데이트, (2) Enter로 검색하기 기능만 살펴보자.
- 우선 검색폼과 검색하기 버튼을 HTML 렌더링 함수에 추가한다.
// SidebarProvider.ts
private getHtmlForWebview(webview: vscode.Webview) {
return `
<!DOCTYPE html>
<html lang="en">
...
<body>
<div class="search-bar">
<div class="input-wrapper">
<input
type="text"
id="searchInput"
value="${this._searchText}"
placeholder="search color..."
>
</div>
<button type="button" id="btnSearch">Search</button>
</div>
<table> ... </table>
</body>
</html>`;
..
}
}
<script>
에 입력, 검색 버튼 이벤트를 등록해야 한다.- 동작은 검색어 입력시 → vscode에 선언한
_searchText
변수를 업데이트하고, - 검색 버튼 클릭시 →
_searchText
로 컬러변수표를 필터링하여webview
를 업데이트해야 한다. - vscode에 선언한 변수와
webview
를 컨트롤하기 위해, vscode에 메시지를 송신하는 방식을 써야 한다. - 이 기능을 위해 총 2개의 메시지를 선언해서 사용했다.
vscode 메시지타입 | 이벤트 수신시 동작 |
---|---|
onInput | - _searchText 를 업데이트한다.- _searchText 값에 따라 컬러변수표를 필터링한다. |
onUpdate | - webview 를 업데이트한다. |
- 먼저 메시지를 사용하기 위해, 초기
webview
렌더링시onDidReceiveMessage()
로 메시지를 등록한다.
// SidebarProvider.ts
// 초기 webview 렌더링 함수
public async resolveWebviewView(webviewView: vscode.WebviewView) {
...
// 추가!
webviewView.webview.onDidReceiveMessage(this.onReceiveMessage.bind(this));
this._view = webviewView;
}
onReceiveMessage()
은 송신한 메시지 타입에 따라, 특정 함수를 실행한다.
// SidebarProvider.ts
private async onReceiveMessage(data: { type: string; value?: any }) {
switch (data.type) {
case "onInput": {
this._searchText = data.value; // (a)
break;
}
case "onUpdate": {
this.update(this._doc); // (b)
break;
}
...
}
public async update(doc?: vscode.TextDocument) {
else if (doc.uri.path !== this._colorFilePath) return;
...
this._view.webview.html = this.getHtmlForWebview(this._view.webview);
}
코드번호 | 설명 |
---|---|
(a) | - onInput 은 입력폼에서 검색어 입력시 검색어 변수(_searchText )를 업데이트하는 이벤트이다.- 유저가 입력폼에 텍스트를 입력하면 onInput 이벤트를 호출해 _searchText 를 입력값으로 변경한다. |
(b) | - onUpdate 는 검색하기, 검색어 초기화하기 실행시 webview 를 업데이트하는 이벤트이다.- 검색하기, 검색어 초기화 버튼 클릭시, webview 를 업데이트한다. - 이때 webview 에서는 _searchText 에 해당하는 데이터만 보여주도록 컬러변수표를 재구성한다. |
- 마지막으로
<script>
에 각 요소별 이벤트 핸들러를 추가한다.
// SidebarProvider.ts
private getHtmlForWebview(webview: vscode.Webview) {
return `
...
<script>
(function() {
// (a)
const vscode = acquireVsCodeApi();
// (b)
const btnSearch = document.getElementById('btnSearch');
const searchInput = document.getElementById('searchInput');
// (c)
btnSearch.addEventListener('click', () => {
vscode.postMessage({ type: 'onInput', value: searchInput.value });
vscode.postMessage({ type: 'onUpdate' });
});
// (d)
searchInput.addEventListener('keyup', (e) => {
vscode.postMessage({ type: 'onInput', value: searchInput.value });
if (e.key === 'Enter') vscode.postMessage({ type: 'onUpdate' });
});
}())
</script>
</body>
</html>`;
}
}
코드 번호 | 설명 |
---|---|
(a) | - acquireVsCodeApi() 함수를 사용하여 vscode 객체를 가져온다. - 이 객체를 사용하여 VSCode 확장 프로그램과 웹뷰 간의 통신을 할 수 있다. |
(b) | - HTML 요소들을 가져온다. - btnSearch 는 검색 버튼을, searchInput 은 검색 입력란이다. |
(c) | 검색 버튼시, vscode.postMessage 로 VSCode에 메시지를 전송한다. - onInput 메시지에는 현재 검색한 단어를 전송하여, 외부 변수를 업데이트하고, onUpdate 메시지를 송신해 webview 를 업데이트한다. |
(d) | - 검색어 입력시, vscode.postMessage 로 VSCode에 메시지를 전송한다. - onInput 메시지에는 현재 검색한 단어를 전송하여, 외부 변수를 업데이트하고, 만약 Enter키 입력시 onUpdate 메시지를 송신해 webview 를 업데이트한다. |
- 그러면 검색어를 입력하여 컬러변수표를 업데이트할 수 있다.
4) 변경을 감지하여, webview 업데이트하기
(1) 구현 방식
- vscode 이벤트를 사용하면 특정 파일이 업데이트 되었을 때
webview
를 업데이트할 수 있다! onDidSaveTextDocument
이벤트는 사용자가 문서를 저장할 때마다 발생한다.onDidSaveTextDocument
이벤트가 발생할 때마다update()
함수를 실행하자
// extension.ts
import * as vscode from 'vscode';
import { SidebarProvider } from './SidebarProvider';
export function activate(context: vscode.ExtensionContext) {
const sidebarProvider = new SidebarProvider(context.extensionUri);
...
// 추가!
context.subscriptions.push(vscode.workspace.onDidSaveTextDocument((e) => {
sidebarProvider.update(e);
}));
}
update()
는 현재 수정된 파일과 추적 중인 파일이 같으면,webview
를 업데이트한다.
// SidebarProvider.ts
public async update(doc?: vscode.TextDocument) {
else if (doc.uri.path !== this._colorFilePath) return;
...
this._view.webview.html = this.getHtmlForWebview(this._view.webview);
}
- 이를 통해, 현재 추적 중인 파일이 수정될 때마다 데이터가 동적으로 변경된다.
5) 현재 추적하고 컬러 파일 열기
(1) 컨셉
- 만약 vscode에서 특정 파일을 열고 싶다면,
showTextDocument()
를 사용하면 된다. showTextDocument()
에 열고 싶은 파일의 경로를 넘기고 호출하면, 대상 파일을 열 수 있다.
vscode.window.showTextDocument(path);
(2) 구현 방식
- 확장에서
[Open Color File]
클릭시, 현재 추적하고 있는 컬러 파일을 열도록 했다. - 동작은 버튼 클릭시
postMessage()
로 이벤트를 전송해서 파일을 여는 방식이다. - 이 동작을 위해, 타입이 “openFile”인 메시지를 등록한다.
- 이 메시지가 수신되면,
showTextDocument()
로 현재 추적 중인 파일(this._colorFilePath
)을 연다.
// SidebarProvider.ts
private async onReceiveMessage(data: { type: string; value?: any }) {
switch (data.type) {
case "openFile": {
if (!this._colorFilePath) {
vscode.window.showErrorMessage("Color file not found");
return;
}
vscode.window.showTextDocument(vscode.Uri.file(this._colorFilePath));
break;
}
...
}
}
webview
에 버튼을 추가하고,<script>
에서 버튼 클릭시 “openFile” 이벤트를 송신한다.
// SidebarProvider.ts
private getHtmlForWebview(webview: vscode.Webview) {
return `
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
</head>
<body>
<div class="search-bar"> ... </div>
<table>
<button id="btnOpen">Open Color File</button>
...
</table>
<script>
(function() {
const vscode = acquireVsCodeApi();
const btnOpen = document.getElementById('btnOpen');
btnOpen.addEventListener('click', () => {
vscode.postMessage({ type: 'openFile' });
});
...
}())
</script>
</body>
</html>`;
}
}
- 그러면 아래와 같이 동작이 진행된다.
6) 클립보드 형식으로 컬러 복사하기
- vscode에서 클립보드를 하고 싶다면?
vscode.env.clipboard.writeText()
를 사용하자! writeText()
에 데이터를 인자로 넘기면, 해당 데이터가 복사된다.
vscode.env.clipboard.writeText('내가 복사될 거야!');
- 구현 방식의 경우, 이전과 동일하게
postMessage
를 활용하면 된다.
// SidebarProvider.ts
private getHtmlForWebview(webview: vscode.Webview) {
// html의 script 부분
return `
<script>
...
const vscode = acquireVsCodeApi();
const colorBoxes = document.querySelectorAll('.colorBox');
colorBoxes.forEach((box) => {
box.addEventListener('click', (e) => {
const color = e.target.dataset.color;
vscode.postMessage({ type: 'copyToClipboard', value: color });
});
});
</script>
`;
}
// SidebarProvider.ts
private async onReceiveMessage(data: { type: string; value?: any }) {
switch (data.type) {
case "copyToClipboard": {
if (!data.value) return;
vscode.env.clipboard.writeText(data.value);
vscode.window.showInformationMessage(`Copied ${data.value} to clipboard`);
break;
}
};
- 구현된 동작은 아래와 같다.
2. 마치며…
- 이번 시간에는 컬러 변수 뷰어 만들기 시리즈의 마지막인, extension 만드는 법을 알아보았다.
- 이 포스팅에서는
postMessage
로 메시지를 송수신하여 함수를 호출하는 법,clipboard.writeText
로 클립보드하는 법,webview
로 html을 주입하는 법 등 여러가지 방법을 알 수 있었다. - 포스팅에 사용한 코드는 colorvariabletracker 확장의 일부인데, 만약 전체 코드를 보고 싶다면 여기, 확장 기능을 구경하고 싶다면 여기를 참고하면 된다.
반응형
'개발 기술 > 개발 이야기' 카테고리의 다른 글
이미지, 배경이미지의 지연 로드 구현 방법 (with. intersectionObserver API) (0) | 2024.05.30 |
---|---|
[React] 아이콘 컴포넌트를 선언하는 3가지 방법 (0) | 2024.04.14 |
[vscode] 컬러 변수 뷰어 만들기(1) - webview API 사용법 (4) | 2024.03.16 |
Vue 3.4 변경점 파헤치기 (0) | 2024.02.29 |
feature flag로 지속적 배포하기(with. postHog, react) (0) | 2024.01.20 |
댓글