Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[view] vscode 테마 컬러 가져오기 및 git lens 분석 내용 공유 #119

Closed
wherehows opened this issue Sep 10, 2022 · 6 comments
Closed
Assignees

Comments

@wherehows
Copy link
Contributor

wherehows commented Sep 10, 2022

vscode theme color에 접근하는 방법 및 githru에 적용 관련해서 리서치를 했습니다. 그래서 아래 gitlens처럼 vscode theme 컬러가 달라질 때, 아래 gitlens+의 CommitGraph와 Visual File History 처럼 배경화면색도 그에 맞게 변하는 기능을 구현하려는게 목적이었는데요.

commit graph

vscode api로는 theme color에 마땅히 접근할 방법이 없었습니다. onDidChangeActiveThemeColor api가 있지만 접근해서 뭘 하기 유의미한 데이터를 제공하지 않습니다.

onDidChangeActiveThemeColor api에 대해서 조금 설명을 드리면, 호출시 이벤트 핸들러의 인자로 kind 프로퍼티가 오는데, kind에 할당되는 숫자가 theme의 변경에 따라서 바뀌지만, 몇몇 theme은 같은 숫자를 공유하기 때문에 의미가 없습니다.

image

그래서 어제 오늘 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하게 두는게 좋을 것 같습니다.

transparent

git lens 분석 내용 공유

git lens 파일을 조금 까본 내용을 공유드립니다. 아마 누군가는 git lens를 볼 일이 있을 것 같아서요. 조금이라도 삽질을 줄이셨으면 하는 마음에서...

  1. git lens는 저희와는 다르게 YeomanVscode Extension Generator를 이용하지 않고, 밑바닥부터 환경을 구성한 것 같습니다.

  2. commit graph와 file history view 렌더링 관련 코드를 위치 ( src/webview/apps/plus)

graph.ts 내부에서 graph.html 내부의 전체적인 상태와 마크업을 GraphWrapper가 그려주어 commit graph를 보여주고, timeline.ts 내부에서 timeline.html 내부의 전체적인 상태와 마크업을 chart.ts가 그려주어 Time Line(File History View)을 렌더링합니다. 화면에 보여지는 것은 왠만해선 이 폴더에서 다루어집니다.

  1. 화면에 보여지는 것 이외에, 웹뷰를 띄우거나 닫는 것 혹은 vscode와의 통신은 아래 폴더에서 다루어집니다. (src/plus/webviews)

  1. grpah.ts 내부 클래스는 appBase.ts를 상속받고, appBase.ts 내부에서 MutationObserver를 등록하는 함수(initilalizeAndWatchThemeColor)를 호출합니다. initilalizeAndWatchThemeColor 함수는 theme.ts 파일에 존재합니다. MutationObserver를 등록하는 풀 코드는 아래와 같습니다.
/*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;
}

@wherehows wherehows self-assigned this Sep 10, 2022
@hanseul-lee
Copy link
Contributor

와👍👍👍 정성스럽게 분석해주셔서 너무 감사합니다!!

@wherehows
Copy link
Contributor Author

wherehows commented Sep 10, 2022

와👍👍👍 정성스럽게 분석해주셔서 너무 감사합니다!!

지금은 개발 초반단계고 일정에 쫓겨서 힘들지만, 한번은 git lens 코드를 보고 또 써볼만 한것 같아용. 👍
당장 배경 지식이 없어서 color 코드를 가져오는 코드 해석에 급급했지만, 뭔가 아이디어적으로든 코드적으로든 githru에 도움될만한게 많아보입니다.

(궁금) commit graph 와 file history view는 유료인 것 같던데 한슬님 결제하셨나요? @__@? 저는 3일의 trial 기간으로 사용했습니다

@hanseul-lee
Copy link
Contributor

@wherehows 혹시 github에 학생 계정 연동하셨나요? 그러면 기간 제한 없이 무료로 이용 가능하더라구요~!

@ytaek
Copy link
Contributor

ytaek commented Sep 10, 2022

WOW 진짜 분석 멋집니다!!!!
향후 프로젝트에 많은 도움이 될 것 같네요! 🦈

제가 알기로도 theme main color를 가져오는 방법은 정식(?)으로는 없는데, 올려주신 workaround를 생각해볼 수도 있겠네요.

�---

조금 결이 다른 얘기를 하자면, 개인적으로는 theme color에 민감하게 반응하지 않아도 될 것 같습니다.
배경색이 어떤가에 따라서 저희 view component들의 색을 전부 바꿔줘야 할 수도 있습니다.
theme 이란게 customize가 가능하기 때문에, 혹자는 새빨간색, 샛파랑색으로 배경을 칠할 수도 있지요;
그리고 배경 색에 따라서 보여지는 정보가 왜곡될 수 있구요.

그래서, 저희는 light/dark 2가지 모드만 생각해도 충분할 것 같긴합니다.
그리고 light냐 dark냐는 전에 찾아본 기억으로는 vscode에서 가져올 수 있기도 하구요.


아무튼 멋진 분석 감사합니다.

다음번에는 gitLens의 시각화 자체에 대해서도 좀 얘기해보면 좋겠네요 :- )
차트가 매우 이쁘게 나오는 것은 좋은데, '정보 전달' 이라는 관점에서는 좀 논의해볼 것들이 있을 것 같아요 ㅎㅎ

@jejecrunch
Copy link
Contributor

와우 자세한 분석 감사합니다.
개인적으로 background를 투명으로 두는 것도 좋은 방법인 것 같습니다.
대신 어떤 배경색이 와도 데이터 시각화 그래프가 잘 보일 수 있는 컬러를 선정하는 게 관건이라고 생각합니다.

@jin-Pro
Copy link
Member

jin-Pro commented Sep 15, 2022

오ㅏ 너무 훌륭합니다,,, 최고에요 ㅋㅋㅋ 저도 한번 공부해봐야겠네요 감사합니다.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

No branches or pull requests

5 participants