// ***************************************************************************** // Copyright (C) 2024 TypeFox and others. // // This program and the accompanying materials are made available under the // terms of the Eclipse Public License v. 2.0 which is available at // http://www.eclipse.org/legal/epl-2.0. // // This Source Code may also be made available under the following Secondary // Licenses when the conditions for such availability set forth in the Eclipse // Public License v. 2.0 are satisfied: GNU General Public License, version 2 // with the GNU Classpath Exception which is available at // https://www.gnu.org/software/classpath/license.html. // // SPDX-License-Identifier: EPL-2.0 OR GPL-2.0-only WITH Classpath-exception-2.0 // ***************************************************************************** import { inject, injectable, postConstruct } from '@theia/core/shared/inversify'; import { PreferenceService } from '@theia/core/lib/common'; import { Emitter } from '@theia/core'; import { NotebookPreferences, notebookPreferenceSchema } from '../../common/notebook-preferences'; import { EditorPreferences } from '@theia/editor/lib/common/editor-preferences'; import { BareFontInfo } from '@theia/monaco-editor-core/esm/vs/editor/common/config/fontInfo'; import { createBareFontInfoFromRawSettings } from '@theia/monaco-editor-core/esm/vs/editor/common/config/fontInfoFromSettings'; import { PixelRatio } from '@theia/monaco-editor-core/esm/vs/base/browser/pixelRatio'; const notebookOutputOptionsRelevantPreferences = [ 'editor.fontSize', 'editor.fontFamily', NotebookPreferences.NOTEBOOK_LINE_NUMBERS, NotebookPreferences.OUTPUT_LINE_HEIGHT, NotebookPreferences.OUTPUT_FONT_SIZE, NotebookPreferences.OUTPUT_FONT_FAMILY, NotebookPreferences.OUTPUT_SCROLLING, NotebookPreferences.OUTPUT_WORD_WRAP, NotebookPreferences.OUTPUT_LINE_LIMIT ]; export interface NotebookOutputOptions { // readonly outputNodePadding: number; readonly outputNodeLeftPadding: number; // readonly previewNodePadding: number; // readonly markdownLeftMargin: number; // readonly leftMargin: number; // readonly rightMargin: number; // readonly runGutter: number; // readonly dragAndDropEnabled: boolean; readonly fontSize: number; readonly outputFontSize?: number; readonly fontFamily: string; readonly outputFontFamily?: string; // readonly markupFontSize: number; // readonly markdownLineHeight: number; readonly outputLineHeight: number; readonly outputScrolling: boolean; readonly outputWordWrap: boolean; readonly outputLineLimit: number; // readonly outputLinkifyFilePaths: boolean; // readonly minimalError: boolean; } @injectable() export class NotebookOptionsService { @inject(PreferenceService) protected readonly preferenceService: PreferenceService; @inject(EditorPreferences) protected readonly editorPreferences: EditorPreferences; protected outputOptionsChangedEmitter = new Emitter(); onDidChangeOutputOptions = this.outputOptionsChangedEmitter.event; protected fontInfo?: BareFontInfo; get editorFontInfo(): BareFontInfo { return this.getOrCreateMonacoFontInfo(); } @postConstruct() protected init(): void { this.preferenceService.onPreferencesChanged(async preferenceChanges => { if (notebookOutputOptionsRelevantPreferences.some(p => p in preferenceChanges)) { this.outputOptionsChangedEmitter.fire(this.computeOutputOptions()); } }); } computeOutputOptions(): NotebookOutputOptions { const outputLineHeight = this.getNotebookPreferenceWithDefault(NotebookPreferences.OUTPUT_LINE_HEIGHT); const fontSize = this.preferenceService.get('editor.fontSize')!; const outputFontSize = this.getNotebookPreferenceWithDefault(NotebookPreferences.OUTPUT_FONT_SIZE); return { fontSize, outputFontSize: outputFontSize, fontFamily: this.preferenceService.get('editor.fontFamily')!, outputNodeLeftPadding: 8, outputFontFamily: this.getNotebookPreferenceWithDefault(NotebookPreferences.OUTPUT_FONT_FAMILY), outputLineHeight: this.computeOutputLineHeight(outputLineHeight, outputFontSize ?? fontSize), outputScrolling: this.getNotebookPreferenceWithDefault(NotebookPreferences.OUTPUT_SCROLLING)!, outputWordWrap: this.getNotebookPreferenceWithDefault(NotebookPreferences.OUTPUT_WORD_WRAP)!, outputLineLimit: this.getNotebookPreferenceWithDefault(NotebookPreferences.OUTPUT_LINE_LIMIT)! }; } protected getNotebookPreferenceWithDefault(key: string): T { return this.preferenceService.get(key, notebookPreferenceSchema.properties?.[key]?.default as T); } protected computeOutputLineHeight(lineHeight: number, outputFontSize: number): number { const minimumLineHeight = 9; if (lineHeight === 0) { // use editor line height lineHeight = this.editorFontInfo.lineHeight; } else if (lineHeight < minimumLineHeight) { // Values too small to be line heights in pixels are in ems. let fontSize = outputFontSize; if (fontSize === 0) { fontSize = this.preferenceService.get('editor.fontSize')!; } lineHeight = lineHeight * fontSize; } // Enforce integer, minimum constraints lineHeight = Math.round(lineHeight); if (lineHeight < minimumLineHeight) { lineHeight = minimumLineHeight; } return lineHeight; } protected getOrCreateMonacoFontInfo(): BareFontInfo { if (!this.fontInfo) { this.fontInfo = this.createFontInfo(); this.editorPreferences.onPreferenceChanged(e => this.fontInfo = this.createFontInfo()); } return this.fontInfo; } protected createFontInfo(): BareFontInfo { return createBareFontInfoFromRawSettings({ fontFamily: this.editorPreferences['editor.fontFamily'], fontWeight: String(this.editorPreferences['editor.fontWeight']), fontSize: this.editorPreferences['editor.fontSize'], fontLigatures: this.editorPreferences['editor.fontLigatures'], lineHeight: this.editorPreferences['editor.lineHeight'], letterSpacing: this.editorPreferences['editor.letterSpacing'], }, PixelRatio.getInstance(window).value); } }