Skip to content

Commit

Permalink
Programming exercises: Add custom themes for the Monaco editor (#9463)
Browse files Browse the repository at this point in the history
  • Loading branch information
pzdr7 authored Oct 13, 2024
1 parent f013784 commit e072169
Show file tree
Hide file tree
Showing 10 changed files with 384 additions and 10 deletions.
Original file line number Diff line number Diff line change
@@ -0,0 +1,86 @@
/**
* Interface for the colors of the editor.
* See https://code.visualstudio.com/api/references/theme-color
* All colors must be in the format '#RRGGBB' or '#RRGGBBAA'.
*/
export interface EditorColors {
/**
* The background color of the editor.
*/
backgroundColor?: string;
/**
* The default color of all text in the editor, not including syntax highlighting.
*/
foregroundColor?: string;
/**
* Colors for line numbers in the editor.
*/
lineNumbers?: {
/**
* The color of the line numbers.
*/
foregroundColor?: string;
/**
* The color of the line number of the line that the cursor is on.
*/
activeForegroundColor?: string;
/**
* The color of the line numbers for dimmed lines. This is used for the final newline of the code.
*/
dimmedForegroundColor?: string;
};
/**
* Colors for the active line highlight in the editor.
*/
lineHighlight?: {
/**
* The color used as the background color for the cursor's current line.
*/
backgroundColor?: string;
/**
* The color used for the border of the cursor's current line.
*/
borderColor?: string;
};
/**
* Colors for the diff editor.
*/
diff?: {
/**
* The background color for inserted lines in the diff editor.
*/
insertedLineBackgroundColor?: string;
/**
* The background color for inserted text in the diff editor.
* This will overlap with the `insertedLineBackgroundColor`.
*/
insertedTextBackgroundColor?: string;
/**
* The background color for removed lines in the diff editor.
*/
removedTextBackgroundColor?: string;
/**
* The background color for removed text in the diff editor.
* This will overlap with the `removedLineBackgroundColor`.
*/
removedLineBackgroundColor?: string;
/**
* The color used for the diagonal fill in the diff editor.
* This is used when the diff editor pads the length of the files to align the lines of the original and modified files.
*/
diagonalFillColor?: string;
/**
* Colors for the diff editor gutter. This is the area to the left of the editor that shows the line numbers.
*/
gutter?: {
/**
* The background color for inserted lines in the diff editor gutter.
*/
insertedLineBackgroundColor?: string;
/**
* The background color for removed lines in the diff editor gutter.
*/
removedLineBackgroundColor?: string;
};
};
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
/**
* Interface for the style of a token in a language.
* The editor applies these styles to the tokens in the specified language (or all languages), e.g. identifiers, keywords, etc.
*/
export interface LanguageTokenStyleDefinition {
/**
* The token to style, e.g. identifier
*/
token: string;
/**
* The language ID for which the token style should be applied.
* If not specified, the style is applied to all languages.
*/
languageId?: string;
/**
* The color of the text that should be applied to the token.
*/
foregroundColor?: string;
/**
* The background color that should be applied to the token.
*/
backgroundColor?: string;
/**
* The font style that should be applied to the token.
*/
fontStyle?: 'italic' | 'bold' | 'underline';
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
import { MonacoThemeDefinition } from 'app/shared/monaco-editor/model/themes/monaco-theme-definition.interface';

export const MONACO_DARK_THEME_DEFINITION: MonacoThemeDefinition = {
id: 'custom-dark',
baseTheme: 'vs-dark',
tokenStyles: [
{
token: 'keyword',
foregroundColor: '#ff7b72',
},
{
token: 'comment',
foregroundColor: '#9198a1',
},
{
token: 'string',
foregroundColor: '#a5d6ff',
},
{
token: 'number',
foregroundColor: '#79c0ff',
},
],
editorColors: {
backgroundColor: '#181a18',
lineHighlight: {
borderColor: '#00000000',
backgroundColor: '#282a2e',
},
lineNumbers: {
foregroundColor: '#ffffff',
activeForegroundColor: '#ffffff',
dimmedForegroundColor: '#ffffff',
},
diff: {
insertedLineBackgroundColor: '#2ea04326',
insertedTextBackgroundColor: '#2ea04326',
removedLineBackgroundColor: '#f8514926',
removedTextBackgroundColor: '#f8514946',
diagonalFillColor: '#00000000',
gutter: {
insertedLineBackgroundColor: '#3fb9504d',
removedLineBackgroundColor: '#f851494d',
},
},
},
};
Original file line number Diff line number Diff line change
@@ -0,0 +1,66 @@
import { MonacoThemeDefinition } from 'app/shared/monaco-editor/model/themes/monaco-theme-definition.interface';
import * as monaco from 'monaco-editor';

export class MonacoEditorTheme {
constructor(private readonly themeDefinition: MonacoThemeDefinition) {}

getId(): string {
return this.themeDefinition.id;
}

/**
* Creates a new record without any entries that have a value of `undefined`.
* @param record The record whose keys to filter.
* @returns The new record, only containing keys with defined values.
* @private
*/
private getRecordWithoutUndefinedEntries(record: Record<string, string | undefined>): Record<string, string> {
const result: Record<string, string> = {};
for (const [key, value] of Object.entries(record)) {
if (value !== undefined) {
result[key] = value;
}
}
return result;
}

register(): void {
const colorDefinitions = this.themeDefinition.editorColors;
// The color keys are available here: https://code.visualstudio.com/api/references/theme-color
const colors = {
'editor.background': colorDefinitions.backgroundColor,
'editor.foreground': colorDefinitions.foregroundColor,
'editorLineNumber.foreground': colorDefinitions.lineNumbers?.foregroundColor,
'editorLineNumber.activeForeground': colorDefinitions.lineNumbers?.activeForegroundColor,
'editorLineNumber.dimmedForeground': colorDefinitions.lineNumbers?.dimmedForegroundColor,
'editor.lineHighlightBackground': colorDefinitions.lineHighlight?.backgroundColor,
'editor.lineHighlightBorder': colorDefinitions.lineHighlight?.borderColor,
'diffEditor.insertedLineBackground': colorDefinitions.diff?.insertedLineBackgroundColor,
'diffEditor.insertedTextBackground': colorDefinitions.diff?.insertedTextBackgroundColor,
'diffEditor.removedTextBackground': colorDefinitions.diff?.removedTextBackgroundColor,
'diffEditor.removedLineBackground': colorDefinitions.diff?.removedLineBackgroundColor,
'diffEditor.diagonalFill': colorDefinitions.diff?.diagonalFillColor,
'diffEditorGutter.insertedLineBackground': colorDefinitions.diff?.gutter?.insertedLineBackgroundColor,
'diffEditorGutter.removedLineBackground': colorDefinitions.diff?.gutter?.removedLineBackgroundColor,
};

const tokenStyleDefinitions = this.themeDefinition.tokenStyles;
const rules = tokenStyleDefinitions.map((tokenDefinition) => {
// Language-specific tokens have the key `token.languageId`, e.g. keyword.custom-md
return {
token: `${tokenDefinition.token}${tokenDefinition.languageId ? '.' + tokenDefinition.languageId : ''}`,
foreground: tokenDefinition.foregroundColor,
background: tokenDefinition.backgroundColor,
fontStyle: tokenDefinition.fontStyle,
};
});

// We cannot pass undefined colors to Monaco, so we filter them out to preserve the default values.
monaco.editor.defineTheme(this.getId(), {
base: this.themeDefinition.baseTheme,
inherit: true,
rules: rules,
colors: this.getRecordWithoutUndefinedEntries(colors),
});
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
import { MonacoThemeDefinition } from 'app/shared/monaco-editor/model/themes/monaco-theme-definition.interface';

export const MONACO_LIGHT_THEME_DEFINITION: MonacoThemeDefinition = {
id: 'custom-light',
baseTheme: 'vs',
tokenStyles: [
{
token: 'keyword',
foregroundColor: '#cf222e',
},
{
token: 'comment',
foregroundColor: '#59636e',
},
{
token: 'string',
foregroundColor: '#0a3069',
},
{
token: 'number',
foregroundColor: '#0550ae',
},
],
editorColors: {
lineHighlight: {
borderColor: '#00000000',
backgroundColor: '#e8e8e8',
},
lineNumbers: {
foregroundColor: '#000000',
activeForegroundColor: '#000000',
dimmedForegroundColor: '#000000',
},
diff: {
insertedLineBackgroundColor: '#dafbe1e6',
insertedTextBackgroundColor: '#aceebbe6',
removedLineBackgroundColor: '#ffebe9ef',
removedTextBackgroundColor: '#ff818250',
diagonalFillColor: '#00000000',
gutter: {
insertedLineBackgroundColor: '#d1f8d9',
removedLineBackgroundColor: '#ffcecb',
},
},
},
};
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
import { LanguageTokenStyleDefinition } from 'app/shared/monaco-editor/model/themes/language-token-style-definition.interface';
import { EditorColors } from 'app/shared/monaco-editor/model/themes/editor-colors.interface';

export interface MonacoThemeDefinition {
id: string;
baseTheme: 'vs' | 'vs-dark' | 'hc-light' | 'hc-black';
tokenStyles: LanguageTokenStyleDefinition[];
editorColors: EditorColors;
}
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,11 @@
.monaco-editor-container {
width: 100%;
height: 100%;

.monaco-editor {
// Disables the focus border around the editor.
outline: none;
}
}

/*
Expand Down
33 changes: 26 additions & 7 deletions src/main/webapp/app/shared/monaco-editor/monaco-editor.service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,9 @@ import * as monaco from 'monaco-editor';
import { CUSTOM_MARKDOWN_CONFIG, CUSTOM_MARKDOWN_LANGUAGE, CUSTOM_MARKDOWN_LANGUAGE_ID } from 'app/shared/monaco-editor/model/languages/monaco-custom-markdown.language';
import { Theme, ThemeService } from 'app/core/theme/theme.service';
import { toSignal } from '@angular/core/rxjs-interop';
import { MONACO_LIGHT_THEME_DEFINITION } from 'app/shared/monaco-editor/model/themes/monaco-light.theme';
import { MonacoEditorTheme } from 'app/shared/monaco-editor/model/themes/monaco-editor-theme.model';
import { MONACO_DARK_THEME_DEFINITION } from 'app/shared/monaco-editor/model/themes/monaco-dark.theme';

/**
* Service providing shared functionality for the Monaco editor.
Expand All @@ -11,29 +14,41 @@ import { toSignal } from '@angular/core/rxjs-interop';
*/
@Injectable({ providedIn: 'root' })
export class MonacoEditorService {
static readonly LIGHT_THEME_ID = 'vs';
static readonly DARK_THEME_ID = 'vs-dark';

private readonly themeService: ThemeService = inject(ThemeService);
private readonly currentTheme = toSignal(this.themeService.getCurrentThemeObservable(), { requireSync: true });

private lightTheme: MonacoEditorTheme;
private darkTheme: MonacoEditorTheme;

constructor() {
monaco.languages.register({ id: CUSTOM_MARKDOWN_LANGUAGE_ID });
monaco.languages.setLanguageConfiguration(CUSTOM_MARKDOWN_LANGUAGE_ID, CUSTOM_MARKDOWN_CONFIG);
monaco.languages.setMonarchTokensProvider(CUSTOM_MARKDOWN_LANGUAGE_ID, CUSTOM_MARKDOWN_LANGUAGE);
this.registerCustomThemes();
this.registerCustomMarkdownLanguage();

effect(() => {
this.applyTheme(this.currentTheme());
});
}

private registerCustomThemes(): void {
this.lightTheme = new MonacoEditorTheme(MONACO_LIGHT_THEME_DEFINITION);
this.darkTheme = new MonacoEditorTheme(MONACO_DARK_THEME_DEFINITION);
this.lightTheme.register();
this.darkTheme.register();
}

private registerCustomMarkdownLanguage(): void {
monaco.languages.register({ id: CUSTOM_MARKDOWN_LANGUAGE_ID });
monaco.languages.setLanguageConfiguration(CUSTOM_MARKDOWN_LANGUAGE_ID, CUSTOM_MARKDOWN_CONFIG);
monaco.languages.setMonarchTokensProvider(CUSTOM_MARKDOWN_LANGUAGE_ID, CUSTOM_MARKDOWN_LANGUAGE);
}

/**
* Applies the given theme to the Monaco editor.
* @param artemisTheme The theme to apply.
* @private
*/
private applyTheme(artemisTheme: Theme): void {
monaco.editor.setTheme(artemisTheme === Theme.LIGHT ? MonacoEditorService.LIGHT_THEME_ID : MonacoEditorService.DARK_THEME_ID);
monaco.editor.setTheme(artemisTheme === Theme.LIGHT ? this.lightTheme.getId() : this.darkTheme.getId());
}

/**
Expand Down Expand Up @@ -79,6 +94,10 @@ export class MonacoEditorService {
hideUnchangedRegions: {
enabled: true,
},
guides: {
indentation: false,
},
renderLineHighlight: 'none',
fontSize: 12,
});
}
Expand Down
Loading

0 comments on commit e072169

Please sign in to comment.