vscode theme color에 접근하는 방법 및 githru에 적용 관련해서 리서치를 했습니다. 그래서 아래 gitlens처럼 vscode theme 컬러가 달라질 때, 아래 gitlens+의 CommitGraph와 Visual File History 처럼 배경화면색도 그에 맞게 변하는 기능을 구현하려는게 목적이었는데요.
vscode api로는 theme color에 마땅히 접근할 방법이 없었습니다. onDidChangeActiveThemeColor api가 있지만 접근해서 뭘 하기 유의미한 데이터를 제공하지 않습니다.
onDidChangeActiveThemeColor api에 대해서 조금 설명을 드리면, 호출시 이벤트 핸들러의 인자로 kind 프로퍼티가 오는데, kind에 할당되는 숫자가 theme의 변경에 따라서 바뀌지만, 몇몇 theme은 같은 숫자를 공유하기 때문에 의미가 없습니다.
그래서 어제 오늘 git lens를 분해해봤는데요. 앞서 말한 CommitGraph와 Visual File History를 생성할 때 document.body에 Mutation Observer를 등록해서 컬러를 업데이트 하고있습니다.(코드는 맨 아래에 있스빈다) 이를 저희쪽에서 이용한다면 아래와 같이 코드를 작성할 수 있습니다.
<!DOCTYPE html>
<html lang="en">
<head>
// 생략
</head>
<body>
<div id="root">
<script src="${reactAppUri}"></script>
<script>
const callback = () => {
const computedStyles = window.getComputedStyle(document.body);
console.log(computedStyles.getPropertyValue('--vscode-font-weight')); // 배경 theme color를 가져옴
}
const observer = new MutationObserver(callback);
observer.observe(document.body, { attributes: true, attributeFilter: ['class'] });
</script>
</body>
</html>
테마를 바꿀때 출력되는 gif를 보면 아래와 같습니다. 웹뷰를 띄우고 개발자 도구( command + Option + i)에서 콘솔을 찍은 것 입니다.
getPropertyValue로 가져올 수 있는 vscode theme color들은 다음과 같습니다.
git lens에서는 아래와 같이 몇 가지 vscode theme color를 가져와서 자신들이 제공하는 feature가 vscode theme과 잘 녹아들게 하는 것 같습니다. (실제로 어디에, 어떻게 적용되는지는 모르겠습니다. 로컬에서 git lens extension 코드를 돌려도 프리미엄인 commit graph 나 file history view에는 접근할 수가 없어요. ㅜ_ㅜ)
그런데 아래 gif처럼, 단순히 background-color: transparent로 두어도 정상적으로 theme color가 반영이 되기 때문에, git lens처럼 컬러를 섬세하게 표현하고 검토해야하는 상황이 못된다면 background-color를 transparent하게 두는게 좋을 것 같습니다.
git lens 파일을 조금 까본 내용을 공유드립니다. 아마 누군가는 git lens를 볼 일이 있을 것 같아서요. 조금이라도 삽질을 줄이셨으면 하는 마음에서...
graph.ts 내부에서 graph.html 내부의 전체적인 상태와 마크업을 GraphWrapper가 그려주어 commit graph를 보여주고, timeline.ts 내부에서 timeline.html 내부의 전체적인 상태와 마크업을 chart.ts가 그려주어 Time Line(File History View)을 렌더링합니다. 화면에 보여지는 것은 왠만해선 이 폴더에서 다루어집니다.
/*global window document MutationObserver*/
import { darken, lighten, opacity } from './colors';
// here : theme changed
export function initializeAndWatchThemeColors(callback?: () => void) {
const onColorThemeChanged = () => {
const body = document.body;
const computedStyle = window.getComputedStyle(body);
const isLightTheme =
body.classList.contains('vscode-light') || body.classList.contains('vscode-high-contrast-light');
// const isHighContrastTheme = body.classList.contains('vscode-high-contrast');
const bodyStyle = body.style;
bodyStyle.setProperty('--font-family', computedStyle.getPropertyValue('--vscode-font-family').trim());
bodyStyle.setProperty('--font-size', computedStyle.getPropertyValue('--vscode-font-size').trim());
bodyStyle.setProperty('--font-weight', computedStyle.getPropertyValue('--vscode-font-weight').trim());
bodyStyle.setProperty(
'--editor-font-family',
computedStyle.getPropertyValue('--vscode-editor-font-family').trim(),
);
bodyStyle.setProperty('--editor-font-size', computedStyle.getPropertyValue('--vscode-editor-font-size').trim());
bodyStyle.setProperty(
'--editor-font-weight',
computedStyle.getPropertyValue('--vscode-editor-font-weight').trim(),
);
const backgroundColor = computedStyle.getPropertyValue('--vscode-editor-background').trim();
let color = backgroundColor;
bodyStyle.setProperty('--color-background', color);
bodyStyle.setProperty('--color-background--lighten-05', lighten(color, 5));
bodyStyle.setProperty('--color-background--darken-05', darken(color, 5));
bodyStyle.setProperty('--color-background--lighten-075', lighten(color, 7.5));
bodyStyle.setProperty('--color-background--darken-075', darken(color, 7.5));
bodyStyle.setProperty('--color-background--lighten-10', lighten(color, 10));
bodyStyle.setProperty('--color-background--darken-10', darken(color, 10));
bodyStyle.setProperty('--color-background--lighten-15', lighten(color, 15));
bodyStyle.setProperty('--color-background--darken-15', darken(color, 15));
bodyStyle.setProperty('--color-background--lighten-30', lighten(color, 30));
bodyStyle.setProperty('--color-background--darken-30', darken(color, 30));
bodyStyle.setProperty('--color-background--lighten-50', lighten(color, 50));
bodyStyle.setProperty('--color-background--darken-50', darken(color, 50));
color = computedStyle.getPropertyValue('--vscode-button-background').trim();
bodyStyle.setProperty('--color-button-background', color);
bodyStyle.setProperty('--color-button-background--darken-30', darken(color, 30));
bodyStyle.setProperty('--color-highlight', color);
bodyStyle.setProperty('--color-highlight--75', opacity(color, 75));
bodyStyle.setProperty('--color-highlight--50', opacity(color, 50));
bodyStyle.setProperty('--color-highlight--25', opacity(color, 25));
color = computedStyle.getPropertyValue('--vscode-button-secondaryBackground').trim();
bodyStyle.setProperty('--color-button-secondary-background', color);
bodyStyle.setProperty('--color-button-secondary-background--darken-30', darken(color, 30));
color = computedStyle.getPropertyValue('--vscode-button-foreground').trim();
bodyStyle.setProperty('--color-button-foreground', color);
let foregroundColor = computedStyle.getPropertyValue('--vscode-editor-foreground').trim();
if (!foregroundColor) {
foregroundColor = computedStyle.getPropertyValue('--vscode-foreground').trim();
}
bodyStyle.setProperty('--color-foreground', foregroundColor);
bodyStyle.setProperty('--color-foreground--85', opacity(foregroundColor, 85));
bodyStyle.setProperty('--color-foreground--75', opacity(foregroundColor, 75));
bodyStyle.setProperty('--color-foreground--65', opacity(foregroundColor, 65));
bodyStyle.setProperty('--color-foreground--50', opacity(foregroundColor, 50));
color = computedStyle.getPropertyValue('--vscode-focusBorder').trim();
bodyStyle.setProperty('--color-focus-border', color);
color = computedStyle.getPropertyValue('--vscode-textLink-foreground').trim();
bodyStyle.setProperty('--color-link-foreground', color);
bodyStyle.setProperty('--color-link-foreground--darken-20', darken(color, 20));
bodyStyle.setProperty('--color-link-foreground--lighten-20', lighten(color, 20));
color = computedStyle.getPropertyValue('--vscode-sideBar-background').trim();
bodyStyle.setProperty('--color-view-background', color || backgroundColor);
color = computedStyle.getPropertyValue('--vscode-sideBar-foreground').trim();
bodyStyle.setProperty('--color-view-foreground', color || foregroundColor);
bodyStyle.setProperty(
'--color-view-header-foreground',
computedStyle.getPropertyValue('--vscode-sideBarSectionHeader-foreground').trim() ||
color ||
foregroundColor,
);
color = computedStyle.getPropertyValue('--vscode-editorHoverWidget-background').trim();
bodyStyle.setProperty('--color-hover-background', color);
color = computedStyle.getPropertyValue('--vscode-editorHoverWidget-border').trim();
bodyStyle.setProperty('--color-hover-border', color);
color = computedStyle.getPropertyValue('--vscode-editorHoverWidget-foreground').trim();
bodyStyle.setProperty('--color-hover-foreground', color);
color = computedStyle.getPropertyValue('--vscode-editorHoverWidget-statusBarBackground').trim();
bodyStyle.setProperty('--color-hover-statusBarBackground', color);
// graph-specific colors
bodyStyle.setProperty(
'--graph-panel-bg',
isLightTheme ? darken(backgroundColor, 5) : lighten(backgroundColor, 5),
);
bodyStyle.setProperty('--graph-theme-opacity-factor', isLightTheme ? '0.5' : '1');
// alert colors
color = computedStyle.getPropertyValue('--vscode-inputValidation-infoBackground').trim();
bodyStyle.setProperty('--color-alert-infoHoverBackground', isLightTheme ? darken(color, 5) : lighten(color, 5));
bodyStyle.setProperty('--color-alert-infoBackground', color);
color = computedStyle.getPropertyValue('--vscode-inputValidation-warningBackground').trim();
bodyStyle.setProperty(
'--color-alert-warningHoverBackground',
isLightTheme ? darken(color, 5) : lighten(color, 5),
);
bodyStyle.setProperty('--color-alert-warningBackground', color);
color = computedStyle.getPropertyValue('--vscode-inputValidation-errorBackground').trim();
bodyStyle.setProperty(
'--color-alert-errorHoverBackground',
isLightTheme ? darken(color, 5) : lighten(color, 5),
);
bodyStyle.setProperty('--color-alert-errorBackground', color);
color = isLightTheme ? darken(backgroundColor, 5) : lighten(backgroundColor, 5);
bodyStyle.setProperty(
'--color-alert-neutralHoverBackground',
isLightTheme ? darken(color, 5) : lighten(color, 5),
);
bodyStyle.setProperty('--color-alert-neutralBackground', color);
bodyStyle.setProperty('--color-alert-infoBorder', 'var(--vscode-inputValidation-infoBorder)');
bodyStyle.setProperty('--color-alert-warningBorder', 'var(--vscode-inputValidation-warningBorder)');
bodyStyle.setProperty('--color-alert-errorBorder', 'var(--vscode-inputValidation-errorBorder)');
bodyStyle.setProperty('--color-alert-neutralBorder', 'var(--vscode-input-foreground)');
bodyStyle.setProperty('--color-alert-foreground', 'var(--vscode-input-foreground)');
callback?.();
};
const observer = new MutationObserver(onColorThemeChanged);
observer.observe(document.body, { attributes: true, attributeFilter: ['class'] });
onColorThemeChanged();
return observer;
}