Skip to content

Commit

Permalink
Monaco editor theming. This required adding a monaco base theme to th…
Browse files Browse the repository at this point in the history
…e theme json, and then creating a subscriber model so different areas of the code can get notified when the theme changes (so the monaco editor knows to update its base theme).
  • Loading branch information
thsparks committed Feb 14, 2025
1 parent 620c8fd commit e47c248
Show file tree
Hide file tree
Showing 5 changed files with 66 additions and 24 deletions.
1 change: 1 addition & 0 deletions docfiles/themes/high-contrast.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
{
"id": "high-contrast",
"name": "High Contrast",
"monacoBaseTheme": "hc-black",
"overrideFiles": [
"/docfiles/themes/overrides/high-contrast-overrides.css"
],
Expand Down
37 changes: 34 additions & 3 deletions react-common/components/theming/themeManager.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,16 +2,23 @@ export interface ThemeInfo {
id: string;
name: string;
overrides?: string;
monacoBaseTheme?: string; // https://code.visualstudio.com/docs/getstarted/themes
colors: { [key: string]: string };
}

export interface ThemeChangeSubscriber {
subscriberId: string;
onThemeChange: () => void;
}

/*
* Singleton class to handle theming operations, like loading and switching themes.
*/
export class ThemeManager {
private static instance: ThemeManager;
private themes: ThemeInfo[];
private activeTheme: ThemeInfo;
private subscribers: ThemeChangeSubscriber[];

private constructor() {} // private to ensure singleton

Expand Down Expand Up @@ -46,6 +53,7 @@ export class ThemeManager {
const theme: ThemeInfo = {
id: themeData.id,
name: themeData.name,
monacoBaseTheme: themeData.monacoBaseTheme,
colors: themeData.colors
};

Expand All @@ -68,8 +76,8 @@ export class ThemeManager {
}
}

public getActiveThemeId(): string {
return this.activeTheme?.id;
public getActiveTheme(): Readonly<ThemeInfo> {
return this.activeTheme;
}

public async getAllThemes(): Promise<ThemeInfo[]> {
Expand All @@ -81,7 +89,7 @@ export class ThemeManager {
}

public async switchTheme(themeId: string) {
if (themeId === this.getActiveThemeId()) {
if (themeId === this.getActiveTheme()?.id) {
return;
}

Expand All @@ -103,7 +111,30 @@ export class ThemeManager {

// textContent is safer than innerHTML, less vulnerable to XSS
styleElement.textContent = `.pxt-theme-root { ${themeAsStyle} }`;

this.activeTheme = theme;
this.notifySubscribers();
}
}

public subscribe(subscriberId: string, onThemeChange: () => void) {
if (this.subscribers?.some(s => s.subscriberId === subscriberId)) {
return;
}

if (!this.subscribers) {
this.subscribers = [];
}

this.subscribers.push({ subscriberId, onThemeChange });
}

public unsubscribe(subscriberId: string) {
this.subscribers = this.subscribers.filter(s => s.subscriberId !== subscriberId);
}

private notifySubscribers() {
this.subscribers.forEach(s => s.onThemeChange());
}
}

Expand Down
30 changes: 20 additions & 10 deletions theme/monaco.less
Original file line number Diff line number Diff line change
Expand Up @@ -32,23 +32,33 @@
Monaco Editor
*******************************/

.monaco-editor {
background-color: var(--pxt-target-background1) !important;
color: var(--pxt-target-foreground1) !important;

.monaco-editor-background, .margin {
background-color: var(--pxt-target-background1) !important;
color: var(--pxt-target-foreground1) !important;
}
}

.monaco-editor .monaco-scrollable-element.editor-scrollable {
margin-left: .5rem;
}

/* Monaco Editor Line number colors, changed from the default light theme */
.monaco-editor.vs .line-numbers {
color: #2c3e50 !important;
color: var(--pxt-target-foreground1) !important;
}

/* Monaco Editor Line number background, changed from the default light theme */
.monaco-editor.vs .margin-view-overlays.monaco-editor-background {
background: rgba(0, 0, 0, 0.05) !important;
background: var(--pxt-neutral-alpha05) !important;
}

/* Monaco Editor Current line highlighting, only needed for the light theme */
.monaco-editor.vs .current-line {
background: rgba(0, 0, 255, 0.1) !important;
background: var(--pxt-colors-blue-alpha10) !important;
}

/* Monaco Editor Suggest Widget, styling */
Expand All @@ -57,17 +67,17 @@
z-index: @monacoSuggestZIndex !important;
}
.monaco-list-row.focused {
background-color:#0078D7 !important;
color: #fff !important;
background-color: var(--pxt-colors-blue-background) !important;
color: var(--pxt-colors-blue-foreground) !important;
}
.monaco-editor.vs .suggest-widget:not(.frozen) .monaco-list .monaco-list-row .monaco-highlighted-label .highlight {
color: #0078D7 !important;
color: var(--pxt-colors-blue-background) !important;
}
.monaco-editor.vs .suggest-widget:not(.frozen) .monaco-list .monaco-list-row.focused .monaco-highlighted-label .highlight {
color: #fff !important;
color: var(--pxt-colors-blue-foreground) !important;
}
.monaco-editor.vs .suggest-widget .details > .monaco-scrollable-element > .body > .type, .monaco-editor .suggest-widget .monaco-list .monaco-list-row .type-label {
color: #FFB900 !important;
color: var(--pxt-colors-orange-background) !important;
}

/* Monaco Editor Rename Widget: default positioning causes scroll with transparent editor toolbar, so we reposition */
Expand All @@ -82,8 +92,8 @@

/* Monaco Editor Main tokens */
.monaco-editor .highlight-statement {
background-color: #b8b8fd;
color: black !important;
background-color: var(--pxt-target-background-hover1);
color: var(--pxt-target-foreground-hover1) !important;
}

/* Monaco Toolbox */
Expand Down
2 changes: 1 addition & 1 deletion webapp/src/app.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -6197,7 +6197,7 @@ document.addEventListener("DOMContentLoaded", async () => {
const defaultColorThemeId = pxt.appTarget?.appTheme?.defaultColorTheme;
if (defaultColorThemeId) {
const themeManager = ThemeManager.getInstance();
if (defaultColorThemeId !== themeManager.getActiveThemeId()) {
if (defaultColorThemeId !== themeManager.getActiveTheme()?.id) {
return themeManager.switchTheme(defaultColorThemeId);
}
}
Expand Down
20 changes: 10 additions & 10 deletions webapp/src/monaco.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@ import IProjectView = pxt.editor.IProjectView;
import ErrorListState = pxt.editor.ErrorListState;

import * as pxtblockly from "../../pxtblocks";
import { ThemeChangeSubscriber, ThemeManager } from "../../react-common/components/theming/themeManager";

const MIN_EDITOR_FONT_SIZE = 10
const MAX_EDITOR_FONT_SIZE = 40
Expand Down Expand Up @@ -390,6 +391,8 @@ export class Editor extends toolboxeditor.ToolboxEditor {
this.onUserPreferencesChanged = this.onUserPreferencesChanged.bind(this);

data.subscribe(this.userPreferencesSubscriber, auth.HIGHCONTRAST);

ThemeManager.getInstance()?.subscribe("monaco", () => this.onUserPreferencesChanged());
}

onUserPreferencesChanged() {
Expand Down Expand Up @@ -747,8 +750,11 @@ export class Editor extends toolboxeditor.ToolboxEditor {
}

const colors = pxt.appTarget.appTheme.monacoColors || {};
const baseTheme: monaco.editor.BuiltinTheme = hc
? "hc-black"
: (ThemeManager.getInstance()?.getActiveTheme()?.monacoBaseTheme as monaco.editor.BuiltinTheme) ?? (inverted ? "vs-dark" : "vs");
monaco.editor.defineTheme('pxtTheme', {
base: hc ? 'hc-black' : (inverted ? 'vs-dark' : 'vs'), // can also be vs-dark or hc-black
base: baseTheme,
inherit: true, // can also be false to completely replace the builtin rules
rules: rules,
colors: hc ? {} : colors
Expand Down Expand Up @@ -2109,15 +2115,9 @@ export class Editor extends toolboxeditor.ToolboxEditor {
}

protected getEditorColor() {
if (pxt.appTarget.appTheme.monacoColors && pxt.appTarget.appTheme.monacoColors["editor.background"]) {
return pxt.appTarget.appTheme.monacoColors["editor.background"]
}
else if (pxt.appTarget.appTheme.invertedMonaco) {
return "#1e1e1e"
}
else {
return "#ffffff"
}
// Editor color is set to var(--pxt-target-background1), which can change.
// This is not ideal, but just return empty for now.
return "";
}
}

Expand Down

0 comments on commit e47c248

Please sign in to comment.