@theia/monaco
Version:
Theia - Monaco Extension
204 lines (183 loc) • 11.2 kB
text/typescript
// *****************************************************************************
// Copyright (C) 2018 Ericsson 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 { injectable, inject, postConstruct } from '@theia/core/shared/inversify';
import { ColorTheme, CssStyleCollector, FrontendApplicationContribution, QuickAccessRegistry, StylingParticipant } from '@theia/core/lib/browser';
import { MonacoSnippetSuggestProvider } from './monaco-snippet-suggest-provider';
import * as monaco from '@theia/monaco-editor-core';
import { setSnippetSuggestSupport } from '@theia/monaco-editor-core/esm/vs/editor/contrib/suggest/browser/suggest';
import { CompletionItemProvider } from '@theia/monaco-editor-core/esm/vs/editor/common/languages';
import { MonacoTextModelService } from './monaco-text-model-service';
import { MonacoThemingService } from './monaco-theming-service';
import { isHighContrast } from '@theia/core/lib/common/theme';
import { editorOptionsRegistry, IEditorOption } from '@theia/monaco-editor-core/esm/vs/editor/common/config/editorOptions';
import { MAX_SAFE_INTEGER, PreferenceSchemaService } from '@theia/core';
import { editorGeneratedPreferenceProperties } from '@theia/editor/lib/common/editor-generated-preference-schema';
import { WorkspaceFileService } from '@theia/workspace/lib/common/workspace-file-service';
import { SecondaryWindowHandler } from '@theia/core/lib/browser/secondary-window-handler';
import { EditorWidget } from '@theia/editor/lib/browser';
import { MonacoEditor } from './monaco-editor';
import { StandaloneServices } from '@theia/monaco-editor-core/esm/vs/editor/standalone/browser/standaloneServices';
import { StandaloneThemeService } from '@theia/monaco-editor-core/esm/vs/editor/standalone/browser/standaloneThemeService';
import { IStandaloneThemeService } from '@theia/monaco-editor-core/esm/vs/editor/standalone/common/standaloneTheme';
import { SecondaryWindowService } from '@theia/core/lib/browser/window/secondary-window-service';
import { registerWindow } from '@theia/monaco-editor-core/esm/vs/base/browser/dom';
type CodeWindow = Window & typeof globalThis & {
vscodeWindowId: number;
};
()
export class MonacoFrontendApplicationContribution implements FrontendApplicationContribution, StylingParticipant {
protected readonly windowsById = new Map<number, monaco.IDisposable>();
protected nextWindowId = 2; // the main window has the id "1"
(MonacoTextModelService)
protected readonly textModelService: MonacoTextModelService;
(MonacoSnippetSuggestProvider)
protected readonly snippetSuggestProvider: MonacoSnippetSuggestProvider;
(PreferenceSchemaService)
protected readonly preferenceSchema: PreferenceSchemaService;
(QuickAccessRegistry)
protected readonly quickAccessRegistry: QuickAccessRegistry;
(MonacoThemingService) protected readonly monacoThemingService: MonacoThemingService;
(WorkspaceFileService) protected readonly workspaceFileService: WorkspaceFileService;
(SecondaryWindowHandler)
protected readonly secondaryWindowHandler: SecondaryWindowHandler;
(SecondaryWindowService)
protected readonly secondaryWindowService: SecondaryWindowService;
()
protected init(): void {
this.addAdditionalPreferenceValidations();
// Monaco registers certain quick access providers (e.g. QuickCommandAccess) at import time, but we want to use our own.
this.quickAccessRegistry.clear();
/**
* @monaco-uplift.Should be guaranteed to work.
* Incomparable enums prevent TypeScript from believing that public ITextModel satisfied private ITextModel
*/
setSnippetSuggestSupport(this.snippetSuggestProvider as unknown as CompletionItemProvider);
for (const language of monaco.languages.getLanguages()) {
this.preferenceSchema.registerOverrideIdentifier(language.id);
}
const registerLanguage = monaco.languages.register.bind(monaco.languages);
monaco.languages.register = language => {
// first register override identifier, because monaco will immediately update already opened documents and then initialize with bad preferences.
this.preferenceSchema.registerOverrideIdentifier(language.id);
registerLanguage(language);
};
this.monacoThemingService.initialize();
}
initialize(): void {
const workspaceExtensions = this.workspaceFileService.getWorkspaceFileExtensions();
monaco.languages.register({
id: 'jsonc',
'aliases': [
'JSON with Comments'
],
'extensions': workspaceExtensions.map(ext => `.${ext}`)
});
}
onStart(): void {
this.secondaryWindowHandler.onDidAddWidget(([widget, window]) => {
if (widget instanceof EditorWidget && widget.editor instanceof MonacoEditor) {
const themeService = StandaloneServices.get(IStandaloneThemeService) as StandaloneThemeService;
themeService.registerEditorContainer(widget.node);
}
});
this.secondaryWindowService.onWindowOpened(window => {
const codeWindow: CodeWindow = window as CodeWindow;
codeWindow.vscodeWindowId = this.nextWindowId++;
this.windowsById.set(codeWindow.vscodeWindowId, registerWindow(codeWindow));
});
this.secondaryWindowService.onWindowClosed(window => {
const codeWindow: CodeWindow = window as CodeWindow;
this.windowsById.get(codeWindow.vscodeWindowId)?.dispose();
});
}
registerThemeStyle(theme: ColorTheme, collector: CssStyleCollector): void {
if (isHighContrast(theme.type)) {
const focusBorder = theme.getColor('focusBorder');
const contrastBorder = theme.getColor('contrastBorder');
if (focusBorder) {
// Quick input
collector.addRule(`
.quick-input-list .monaco-list-row {
outline-offset: -1px;
}
.quick-input-list .monaco-list-row.focused {
outline: 1px dotted ${focusBorder};
}
.quick-input-list .monaco-list-row:hover {
outline: 1px dashed ${focusBorder};
}
`);
// Input box always displays an outline, even when unfocused
collector.addRule(`
.monaco-editor .find-widget .monaco-inputbox {
outline: var(--theia-border-width) solid;
outline-offset: calc(-1 * var(--theia-border-width));
outline-color: var(--theia-focusBorder);
}
`);
}
if (contrastBorder) {
collector.addRule(`
.quick-input-widget {
outline: 1px solid ${contrastBorder};
outline-offset: -1px;
}
`);
}
} else {
collector.addRule(`
.quick-input-widget {
box-shadow: rgb(0 0 0 / 36%) 0px 0px 8px 2px;
}
`);
}
}
/**
* For reasons that are unclear, while most preferences that apply in editors are validated, a few are not.
* There is a utility in `examples/api-samples/src/browser/monaco-editor-preferences/monaco-editor-preference-extractor.ts` to help determine which are not.
* Check `src/vs/editor/common/config/editorOptions.ts` for constructor arguments and to make sure that the preference names used to extract constructors are still accurate.
*/
protected addAdditionalPreferenceValidations(): void {
let editorIntConstructor: undefined | (new (...args: unknown[]) => IEditorOption<number, number>);
let editorBoolConstructor: undefined | (new (...args: unknown[]) => IEditorOption<number, boolean>);
let editorStringEnumConstructor: undefined | (new (...args: unknown[]) => IEditorOption<number, string>);
for (const validator of editorOptionsRegistry) {
/* eslint-disable @typescript-eslint/no-explicit-any,max-len */
if (editorIntConstructor && editorBoolConstructor && editorStringEnumConstructor) { break; }
if (validator.name === 'acceptSuggestionOnCommitCharacter') {
editorBoolConstructor = validator.constructor as any;
} else if (validator.name === 'acceptSuggestionOnEnter') {
editorStringEnumConstructor = validator.constructor as any;
} else if (validator.name === 'accessibilityPageSize') {
editorIntConstructor = validator.constructor as any;
}
/* eslint-enable @typescript-eslint/no-explicit-any */
}
if (editorIntConstructor && editorBoolConstructor && editorStringEnumConstructor) {
let id = 200; // Needs to be bigger than the biggest index in the EditorOption enum.
editorOptionsRegistry.push(
new editorIntConstructor(id++, 'tabSize', 4, 1, MAX_SAFE_INTEGER, editorGeneratedPreferenceProperties['editor.tabSize']),
new editorBoolConstructor(id++, 'insertSpaces', true, editorGeneratedPreferenceProperties['editor.insertSpaces']),
new editorBoolConstructor(id++, 'detectIndentation', true, editorGeneratedPreferenceProperties['editor.detectIndentation']),
new editorBoolConstructor(id++, 'trimAutoWhitespace', true, editorGeneratedPreferenceProperties['editor.trimAutoWhitespace']),
new editorBoolConstructor(id++, 'largeFileOptimizations', true, editorGeneratedPreferenceProperties['editor.largeFileOptimizations']),
new editorStringEnumConstructor(id++, 'wordBasedSuggestions', 'matchingDocuments', editorGeneratedPreferenceProperties['editor.wordBasedSuggestions'].enum, editorGeneratedPreferenceProperties['editor.wordBasedSuggestions']),
new editorBoolConstructor(id++, 'stablePeek', false, editorGeneratedPreferenceProperties['editor.stablePeek']),
new editorIntConstructor(id++, 'maxTokenizationLineLength', 20000, 1, MAX_SAFE_INTEGER, editorGeneratedPreferenceProperties['editor.maxTokenizationLineLength']),
);
}
}
}