Skip to content

Commit 96d0cd1

Browse files
authored
Sync session code view theme with VSCode them (#7068)
1 parent 9733a92 commit 96d0cd1

File tree

8 files changed

+526
-21
lines changed

8 files changed

+526
-21
lines changed

package.json

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3986,6 +3986,7 @@
39863986
"dependencies": {
39873987
"@octokit/rest": "20.1.2",
39883988
"@octokit/types": "13.8.0",
3989+
"@shikijs/monaco": "^3.7.0",
39893990
"@vscode/extension-telemetry": "0.7.5",
39903991
"@vscode/prompt-tsx": "^0.3.0-alpha.12",
39913992
"apollo-boost": "^0.4.9",
@@ -4003,6 +4004,7 @@
40034004
"monaco-editor": "^0.52.2",
40044005
"react": "^16.12.0",
40054006
"react-dom": "^16.12.0",
4007+
"shiki": "^3.7.0",
40064008
"ssh-config": "4.1.1",
40074009
"stream-http": "^3.2.0",
40084010
"tunnel": "0.0.6",

src/view/sessionLogView.ts

Lines changed: 95 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@ import * as vscode from 'vscode';
77
import type * as messages from '../../webviews/sessionLogView/messages';
88
import { AuthProvider } from '../common/authentication';
99
import { Disposable } from '../common/lifecycle';
10-
import { CopilotApi } from '../github/copilotApi';
10+
import { CopilotApi, SessionInfo } from '../github/copilotApi';
1111
import { CredentialStore } from '../github/credentials';
1212
import { PullRequestModel } from '../github/pullRequestModel';
1313
import { hasEnterpriseUri } from '../github/utils';
@@ -92,7 +92,7 @@ export class SessionLogViewManager extends Disposable {
9292
<meta charset="UTF-8">
9393
<meta name="viewport" content="width=device-width, initial-scale=1.0">
9494
<title>Session Log</title>
95-
<meta http-equiv="Content-Security-Policy" content="default-src 'none'; style-src 'unsafe-inline' ${webviewPanel.webview.cspSource}; script-src ${webviewPanel.webview.cspSource};">
95+
<meta http-equiv="Content-Security-Policy" content="default-src 'none'; style-src 'unsafe-inline' ${webviewPanel.webview.cspSource}; script-src ${webviewPanel.webview.cspSource} 'unsafe-eval';">
9696
</head>
9797
<body>
9898
<div id="app"></div>
@@ -106,14 +106,89 @@ export class SessionLogViewManager extends Disposable {
106106
copilotApi.getLogsFromSession(sessionId)
107107
]);
108108

109-
webviewPanel.webview.postMessage({
110-
type: 'init',
111-
info,
112-
logs,
113-
} as messages.InitMessage);
109+
new SessionLogView(info, logs, webviewPanel);
114110
}
115111
}
116112

113+
class SessionLogView extends Disposable {
114+
constructor(
115+
info: SessionInfo,
116+
logs: string,
117+
webviewPanel: vscode.WebviewPanel
118+
) {
119+
super();
120+
121+
loadCurrentThemeData().then(themeData => {
122+
webviewPanel.webview.postMessage({
123+
type: 'init',
124+
info,
125+
logs,
126+
themeData,
127+
} as messages.InitMessage);
128+
});
129+
130+
this._register(webviewPanel.onDidDispose(() => {
131+
this.dispose();
132+
}));
133+
134+
this._register(vscode.workspace.onDidChangeConfiguration(async e => {
135+
if (e.affectsConfiguration('workbench.colorTheme')) {
136+
const themeData = await loadCurrentThemeData();
137+
webviewPanel.webview.postMessage({
138+
type: 'changeTheme',
139+
themeData,
140+
} as messages.ChangeThemeMessage);
141+
}
142+
}));
143+
}
144+
}
145+
146+
147+
async function loadCurrentThemeData(): Promise<any> {
148+
let themeData: any = null;
149+
const currentThemeName = vscode.workspace.getConfiguration('workbench').get<string>('colorTheme');
150+
if (currentThemeName) {
151+
const path = getCurrentThemePath(currentThemeName);
152+
if (path) {
153+
themeData = await loadThemeFromFile(path);
154+
}
155+
}
156+
return themeData;
157+
}
158+
159+
async function loadThemeFromFile(path: vscode.Uri): Promise<any> {
160+
const decoder = new TextDecoder();
161+
162+
let themeData = JSON.parse(decoder.decode(await vscode.workspace.fs.readFile(path)));
163+
164+
// Also load the include file if specified
165+
if (themeData.include) {
166+
try {
167+
const includePath = vscode.Uri.joinPath(path, '..', themeData.include);
168+
const includeData = await loadThemeFromFile(includePath);
169+
themeData = {
170+
...themeData,
171+
colors: {
172+
...(includeData.colors || {}),
173+
...(themeData.colors || {}),
174+
},
175+
tokenColors: [
176+
...(includeData.tokenColors || []),
177+
...(themeData.tokenColors || []),
178+
],
179+
semanticTokenColors: {
180+
...(includeData.semanticTokenColors || {}),
181+
...(themeData.semanticTokenColors || {}),
182+
},
183+
};
184+
} catch (error) {
185+
console.warn(`Failed to load theme include file: ${error}`);
186+
}
187+
}
188+
189+
return themeData;
190+
}
191+
117192
async function getCopilotApi(credentialStore: CredentialStore): Promise<CopilotApi | undefined> {
118193
let authProvider: AuthProvider | undefined;
119194
if (credentialStore.isAuthenticated(AuthProvider.githubEnterprise) && hasEnterpriseUri()) {
@@ -132,3 +207,16 @@ async function getCopilotApi(credentialStore: CredentialStore): Promise<CopilotA
132207
const { token } = await github.octokit.api.auth() as { token: string };
133208
return new CopilotApi(github.octokit, token);
134209
}
210+
211+
function getCurrentThemePath(themeName: string): vscode.Uri | undefined {
212+
for (const ext of vscode.extensions.all) {
213+
const themes = ext.packageJSON.contributes && ext.packageJSON.contributes.themes;
214+
if (!themes) {
215+
continue;
216+
}
217+
const theme = themes.find(theme => theme.label === themeName || theme.id === themeName);
218+
if (theme) {
219+
return vscode.Uri.joinPath(ext.extensionUri, theme.path);
220+
}
221+
}
222+
}

webviews/sessionLogView/app.tsx

Lines changed: 93 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -3,28 +3,36 @@
33
* Licensed under the MIT License. See License.txt in the project root for license information.
44
*--------------------------------------------------------------------------------------------*/
55

6+
import { shikiToMonaco } from '@shikijs/monaco';
7+
import * as monaco from 'monaco-editor';
68
import * as React from 'react';
7-
import { InitMessage } from './messages';
9+
import { createHighlighter } from 'shiki';
10+
import { ChangeThemeMessage, InitMessage } from './messages';
811
import { parseSessionLogs, SessionInfo, SessionResponseLogChunk } from './sessionsApi';
912
import { SessionView } from './sessionView';
1013

14+
const themeName = 'vscode-theme';
15+
1116
type SessionViewState =
1217
{ state: 'loading' }
13-
| { state: 'ready'; readonly info: SessionInfo; readonly logs: SessionResponseLogChunk[] }
18+
| { state: 'ready'; readonly info: SessionInfo; readonly logs: SessionResponseLogChunk[]; themeData: any }
1419

1520

1621
export function App() {
1722
const [state, setState] = React.useState<SessionViewState>({ state: 'loading' });
1823

1924
React.useEffect(() => {
2025
const handleMessage = (event: MessageEvent) => {
21-
const message = event.data as InitMessage;
22-
if (message && message.type === 'init') {
23-
setState({
24-
state: 'ready',
25-
info: message.info,
26-
logs: parseSessionLogs(message.logs),
27-
});
26+
const message = event.data as InitMessage | ChangeThemeMessage;
27+
switch (message?.type) {
28+
case 'init': {
29+
init(message);
30+
break;
31+
}
32+
case 'changeTheme': {
33+
registerMonacoTheme(message.themeData);
34+
break;
35+
}
2836
}
2937
};
3038

@@ -37,4 +45,80 @@ export function App() {
3745
} else {
3846
return <SessionView info={state.info} logs={state.logs} />;
3947
}
48+
49+
async function init(message: InitMessage) {
50+
await registerMonacoTheme(message.themeData);
51+
52+
setState({
53+
state: 'ready',
54+
info: message.info,
55+
logs: parseSessionLogs(message.logs),
56+
themeData: message.themeData,
57+
});
58+
}
59+
}
60+
61+
async function registerMonacoTheme(themeData: any) {
62+
const langs = [
63+
'css',
64+
'html',
65+
'ini',
66+
'java',
67+
'lua',
68+
'makefile',
69+
'perl',
70+
'r',
71+
'ruby',
72+
'php',
73+
'sql',
74+
'xml',
75+
'xsl',
76+
'yaml',
77+
'clojure',
78+
'coffee',
79+
'c',
80+
'cpp',
81+
'diff',
82+
'dockerfile',
83+
'go',
84+
'groovy',
85+
'pug',
86+
'javascript',
87+
'json',
88+
'jsonc',
89+
'less',
90+
'objc',
91+
'swift',
92+
'scss',
93+
'perl6',
94+
'powershell',
95+
'python',
96+
'rust',
97+
'scala',
98+
'shellscript',
99+
'typescript',
100+
'csharp',
101+
'fsharp',
102+
'dart',
103+
'handlebars',
104+
'markdown',
105+
];
106+
107+
const highlighter = await createHighlighter({
108+
themes: [],
109+
langs: langs,
110+
});
111+
112+
await highlighter.loadTheme({
113+
...themeData,
114+
name: themeName,
115+
bg: 'transparent' // Don't set a background color
116+
});
117+
highlighter.setTheme(themeName);
118+
119+
for (const lang of langs) {
120+
monaco.languages.register({ id: lang });
121+
}
122+
123+
shikiToMonaco(highlighter, monaco);
40124
}

webviews/sessionLogView/codeView.tsx

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -32,7 +32,8 @@ export const CodeView: React.FC<CodeViewProps> = ({ label, description, content
3232
value: content.value,
3333
language: content.lang || 'plaintext',
3434
readOnly: true,
35-
theme: 'vs-dark',
35+
theme: 'vscode-theme',
36+
bracketPairColorization: { enabled: false },
3637
minimap: { enabled: false },
3738
scrollbar: {
3839
vertical: 'hidden',

webviews/sessionLogView/index.css

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -32,7 +32,7 @@
3232
justify-content: left;
3333

3434
padding: 6px 12px;
35-
background: #232323;
35+
background: var(--vscode-editorWidget-background);
3636
color: #fff;
3737
font-size: 13px;
3838
gap: 8px;
@@ -42,7 +42,7 @@
4242
margin-left: 8px;
4343
background: none;
4444
border: none;
45-
color: #fff;
45+
color: var(--vscode-descriptionForeground);
4646
cursor: pointer;
4747
font-size: 13px;
4848
padding: 2px 8px;

webviews/sessionLogView/index.tsx

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -5,13 +5,13 @@
55

66
import * as React from 'react';
77
import { render } from 'react-dom';
8+
import { App } from './app';
9+
810
import '../common/common.css';
911
import './index.css';
10-
import { App } from './app';
1112

1213
function main() {
1314
render(<App />, document.getElementById('app'));
1415
}
1516

1617
main();
17-

webviews/sessionLogView/messages.ts

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,4 +9,10 @@ export interface InitMessage {
99
type: 'init';
1010
info: SessionInfo;
1111
logs: string;
12+
themeData: any;
13+
}
14+
15+
export interface ChangeThemeMessage {
16+
type: 'changeTheme';
17+
themeData: any;
1218
}

0 commit comments

Comments
 (0)