Path: blob/main/src/vs/editor/standalone/browser/standaloneCodeEditor.ts
5221 views
/*---------------------------------------------------------------------------------------------1* Copyright (c) Microsoft Corporation. All rights reserved.2* Licensed under the MIT License. See License.txt in the project root for license information.3*--------------------------------------------------------------------------------------------*/45import * as aria from '../../../base/browser/ui/aria/aria.js';6import { Disposable, IDisposable, toDisposable, DisposableStore } from '../../../base/common/lifecycle.js';7import { ICodeEditor, IDiffEditor, IDiffEditorConstructionOptions } from '../../browser/editorBrowser.js';8import { ICodeEditorService } from '../../browser/services/codeEditorService.js';9import { CodeEditorWidget } from '../../browser/widget/codeEditor/codeEditorWidget.js';10import { IDiffEditorOptions, IEditorOptions } from '../../common/config/editorOptions.js';11import { InternalEditorAction } from '../../common/editorAction.js';12import { IModelChangedEvent } from '../../common/editorCommon.js';13import { ITextModel } from '../../common/model.js';14import { StandaloneKeybindingService, updateConfigurationService } from './standaloneServices.js';15import { IStandaloneThemeService } from '../common/standaloneTheme.js';16import { IMenuItem, MenuId, MenuRegistry } from '../../../platform/actions/common/actions.js';17import { CommandsRegistry, ICommandHandler, ICommandService } from '../../../platform/commands/common/commands.js';18import { IConfigurationService } from '../../../platform/configuration/common/configuration.js';19import { ContextKeyExpr, ContextKeyValue, IContextKey, IContextKeyService } from '../../../platform/contextkey/common/contextkey.js';20import { IContextMenuService } from '../../../platform/contextview/browser/contextView.js';21import { IInstantiationService, ServicesAccessor } from '../../../platform/instantiation/common/instantiation.js';22import { IKeybindingService } from '../../../platform/keybinding/common/keybinding.js';23import { INotificationService } from '../../../platform/notification/common/notification.js';24import { IThemeService } from '../../../platform/theme/common/themeService.js';25import { IAccessibilityService } from '../../../platform/accessibility/common/accessibility.js';26import { StandaloneCodeEditorNLS } from '../../common/standaloneStrings.js';27import { IClipboardService } from '../../../platform/clipboard/common/clipboardService.js';28import { IEditorProgressService } from '../../../platform/progress/common/progress.js';29import { StandaloneThemeService } from './standaloneThemeService.js';30import { IModelService } from '../../common/services/model.js';31import { ILanguageSelection, ILanguageService } from '../../common/languages/language.js';32import { URI } from '../../../base/common/uri.js';33import { StandaloneCodeEditorService } from './standaloneCodeEditorService.js';34import { PLAINTEXT_LANGUAGE_ID } from '../../common/languages/modesRegistry.js';35import { ILanguageConfigurationService } from '../../common/languages/languageConfigurationRegistry.js';36import { IEditorConstructionOptions } from '../../browser/config/editorConfiguration.js';37import { ILanguageFeaturesService } from '../../common/services/languageFeatures.js';38import { DiffEditorWidget } from '../../browser/widget/diffEditor/diffEditorWidget.js';39import { IAccessibilitySignalService } from '../../../platform/accessibilitySignal/browser/accessibilitySignalService.js';40import { mainWindow } from '../../../base/browser/window.js';41import { setHoverDelegateFactory } from '../../../base/browser/ui/hover/hoverDelegateFactory.js';42import { IHoverService, WorkbenchHoverDelegate } from '../../../platform/hover/browser/hover.js';43import { setBaseLayerHoverDelegate } from '../../../base/browser/ui/hover/hoverDelegate2.js';44import { IMarkdownRendererService } from '../../../platform/markdown/browser/markdownRenderer.js';45import { EditorMarkdownCodeBlockRenderer } from '../../browser/widget/markdownRenderer/browser/editorMarkdownCodeBlockRenderer.js';46import { IUserInteractionService } from '../../../platform/userInteraction/browser/userInteractionService.js';4748/**49* Description of an action contribution50*/51export interface IActionDescriptor {52/**53* An unique identifier of the contributed action.54*/55id: string;56/**57* A label of the action that will be presented to the user.58*/59label: string;60/**61* Precondition rule. The value should be a [context key expression](https://code.visualstudio.com/docs/getstarted/keybindings#_when-clause-contexts).62*/63precondition?: string;64/**65* An array of keybindings for the action.66*/67keybindings?: number[];68/**69* The keybinding rule (condition on top of precondition).70*/71keybindingContext?: string;72/**73* Control if the action should show up in the context menu and where.74* The context menu of the editor has these default:75* navigation - The navigation group comes first in all cases.76* 1_modification - This group comes next and contains commands that modify your code.77* 9_cutcopypaste - The last default group with the basic editing commands.78* You can also create your own group.79* Defaults to null (don't show in context menu).80*/81contextMenuGroupId?: string;82/**83* Control the order in the context menu group.84*/85contextMenuOrder?: number;86/**87* Method that will be executed when the action is triggered.88* @param editor The editor instance is passed in as a convenience89*/90run(editor: ICodeEditor, ...args: unknown[]): void | Promise<void>;91}9293/**94* Options which apply for all editors.95*/96export interface IGlobalEditorOptions {97/**98* The number of spaces a tab is equal to.99* This setting is overridden based on the file contents when `detectIndentation` is on.100* Defaults to 4.101*/102tabSize?: number;103/**104* Insert spaces when pressing `Tab`.105* This setting is overridden based on the file contents when `detectIndentation` is on.106* Defaults to true.107*/108insertSpaces?: boolean;109/**110* Controls whether `tabSize` and `insertSpaces` will be automatically detected when a file is opened based on the file contents.111* Defaults to true.112*/113detectIndentation?: boolean;114/**115* Remove trailing auto inserted whitespace.116* Defaults to true.117*/118trimAutoWhitespace?: boolean;119/**120* Special handling for large files to disable certain memory intensive features.121* Defaults to true.122*/123largeFileOptimizations?: boolean;124/**125* Controls whether completions should be computed based on words in the document.126* Defaults to true.127*/128wordBasedSuggestions?: 'off' | 'currentDocument' | 'matchingDocuments' | 'allDocuments';129/**130* Controls whether word based completions should be included from opened documents of the same language or any language.131*/132wordBasedSuggestionsOnlySameLanguage?: boolean;133/**134* Controls whether the semanticHighlighting is shown for the languages that support it.135* true: semanticHighlighting is enabled for all themes136* false: semanticHighlighting is disabled for all themes137* 'configuredByTheme': semanticHighlighting is controlled by the current color theme's semanticHighlighting setting.138* Defaults to 'byTheme'.139*/140'semanticHighlighting.enabled'?: true | false | 'configuredByTheme';141/**142* Keep peek editors open even when double-clicking their content or when hitting `Escape`.143* Defaults to false.144*/145stablePeek?: boolean;146/**147* Lines above this length will not be tokenized for performance reasons.148* Defaults to 20000.149*/150maxTokenizationLineLength?: number;151/**152* Theme to be used for rendering.153* The current out-of-the-box available themes are: 'vs' (default), 'vs-dark', 'hc-black', 'hc-light'.154* You can create custom themes via `monaco.editor.defineTheme`.155* To switch a theme, use `monaco.editor.setTheme`.156* **NOTE**: The theme might be overwritten if the OS is in high contrast mode, unless `autoDetectHighContrast` is set to false.157*/158theme?: string;159/**160* If enabled, will automatically change to high contrast theme if the OS is using a high contrast theme.161* Defaults to true.162*/163autoDetectHighContrast?: boolean;164}165166/**167* The options to create an editor.168*/169export interface IStandaloneEditorConstructionOptions extends IEditorConstructionOptions, IGlobalEditorOptions {170/**171* The initial model associated with this code editor.172*/173model?: ITextModel | null;174/**175* The initial value of the auto created model in the editor.176* To not automatically create a model, use `model: null`.177*/178value?: string;179/**180* The initial language of the auto created model in the editor.181* To not automatically create a model, use `model: null`.182*/183language?: string;184/**185* Initial theme to be used for rendering.186* The current out-of-the-box available themes are: 'vs' (default), 'vs-dark', 'hc-black', 'hc-light.187* You can create custom themes via `monaco.editor.defineTheme`.188* To switch a theme, use `monaco.editor.setTheme`.189* **NOTE**: The theme might be overwritten if the OS is in high contrast mode, unless `autoDetectHighContrast` is set to false.190*/191theme?: string;192/**193* If enabled, will automatically change to high contrast theme if the OS is using a high contrast theme.194* Defaults to true.195*/196autoDetectHighContrast?: boolean;197/**198* An URL to open when Ctrl+H (Windows and Linux) or Cmd+H (OSX) is pressed in199* the accessibility help dialog in the editor.200*201* Defaults to "https://go.microsoft.com/fwlink/?linkid=852450"202*/203accessibilityHelpUrl?: string;204/**205* Container element to use for ARIA messages.206* Defaults to document.body.207*/208ariaContainerElement?: HTMLElement;209}210211/**212* The options to create a diff editor.213*/214export interface IStandaloneDiffEditorConstructionOptions extends IDiffEditorConstructionOptions {215/**216* Initial theme to be used for rendering.217* The current out-of-the-box available themes are: 'vs' (default), 'vs-dark', 'hc-black', 'hc-light.218* You can create custom themes via `monaco.editor.defineTheme`.219* To switch a theme, use `monaco.editor.setTheme`.220* **NOTE**: The theme might be overwritten if the OS is in high contrast mode, unless `autoDetectHighContrast` is set to false.221*/222theme?: string;223/**224* If enabled, will automatically change to high contrast theme if the OS is using a high contrast theme.225* Defaults to true.226*/227autoDetectHighContrast?: boolean;228}229230export interface IStandaloneCodeEditor extends ICodeEditor {231updateOptions(newOptions: IEditorOptions & IGlobalEditorOptions): void;232addCommand(keybinding: number, handler: ICommandHandler, context?: string): string | null;233createContextKey<T extends ContextKeyValue = ContextKeyValue>(key: string, defaultValue: T): IContextKey<T>;234addAction(descriptor: IActionDescriptor): IDisposable;235}236237export interface IStandaloneDiffEditor extends IDiffEditor {238addCommand(keybinding: number, handler: ICommandHandler, context?: string): string | null;239createContextKey<T extends ContextKeyValue = ContextKeyValue>(key: string, defaultValue: T): IContextKey<T>;240addAction(descriptor: IActionDescriptor): IDisposable;241242getOriginalEditor(): IStandaloneCodeEditor;243getModifiedEditor(): IStandaloneCodeEditor;244}245246let LAST_GENERATED_COMMAND_ID = 0;247248let ariaDomNodeCreated = false;249/**250* Create ARIA dom node inside parent,251* or only for the first editor instantiation inside document.body.252* @param parent container element for ARIA dom node253*/254function createAriaDomNode(parent: HTMLElement | undefined) {255if (!parent) {256if (ariaDomNodeCreated) {257return;258}259ariaDomNodeCreated = true;260}261aria.setARIAContainer(parent || mainWindow.document.body);262}263264/**265* A code editor to be used both by the standalone editor and the standalone diff editor.266*/267export class StandaloneCodeEditor extends CodeEditorWidget implements IStandaloneCodeEditor {268269private readonly _standaloneKeybindingService: StandaloneKeybindingService | null;270271constructor(272domElement: HTMLElement,273_options: Readonly<IStandaloneEditorConstructionOptions>,274@IInstantiationService instantiationService: IInstantiationService,275@ICodeEditorService codeEditorService: ICodeEditorService,276@ICommandService commandService: ICommandService,277@IContextKeyService contextKeyService: IContextKeyService,278@IHoverService hoverService: IHoverService,279@IKeybindingService keybindingService: IKeybindingService,280@IThemeService themeService: IThemeService,281@INotificationService notificationService: INotificationService,282@IAccessibilityService accessibilityService: IAccessibilityService,283@ILanguageConfigurationService languageConfigurationService: ILanguageConfigurationService,284@ILanguageFeaturesService languageFeaturesService: ILanguageFeaturesService,285@IMarkdownRendererService markdownRendererService: IMarkdownRendererService,286@IUserInteractionService userInteractionService: IUserInteractionService,287) {288const options = { ..._options };289options.ariaLabel = options.ariaLabel || StandaloneCodeEditorNLS.editorViewAccessibleLabel;290super(domElement, options, {}, instantiationService, codeEditorService, commandService, contextKeyService, themeService, notificationService, accessibilityService, languageConfigurationService, languageFeaturesService, userInteractionService);291292if (keybindingService instanceof StandaloneKeybindingService) {293this._standaloneKeybindingService = keybindingService;294} else {295this._standaloneKeybindingService = null;296}297298createAriaDomNode(options.ariaContainerElement);299300setHoverDelegateFactory((placement, enableInstantHover) => instantiationService.createInstance(WorkbenchHoverDelegate, placement, { instantHover: enableInstantHover }, {}));301setBaseLayerHoverDelegate(hoverService);302303markdownRendererService.setDefaultCodeBlockRenderer(instantiationService.createInstance(EditorMarkdownCodeBlockRenderer));304}305306public addCommand(keybinding: number, handler: ICommandHandler, context?: string): string | null {307if (!this._standaloneKeybindingService) {308console.warn('Cannot add command because the editor is configured with an unrecognized KeybindingService');309return null;310}311const commandId = 'DYNAMIC_' + (++LAST_GENERATED_COMMAND_ID);312const whenExpression = ContextKeyExpr.deserialize(context);313this._standaloneKeybindingService.addDynamicKeybinding(commandId, keybinding, handler, whenExpression);314return commandId;315}316317public createContextKey<T extends ContextKeyValue = ContextKeyValue>(key: string, defaultValue: T): IContextKey<T> {318return this._contextKeyService.createKey(key, defaultValue);319}320321public addAction(_descriptor: IActionDescriptor): IDisposable {322if ((typeof _descriptor.id !== 'string') || (typeof _descriptor.label !== 'string') || (typeof _descriptor.run !== 'function')) {323throw new Error('Invalid action descriptor, `id`, `label` and `run` are required properties!');324}325if (!this._standaloneKeybindingService) {326console.warn('Cannot add keybinding because the editor is configured with an unrecognized KeybindingService');327return Disposable.None;328}329330// Read descriptor options331const id = _descriptor.id;332const label = _descriptor.label;333const precondition = ContextKeyExpr.and(334ContextKeyExpr.equals('editorId', this.getId()),335ContextKeyExpr.deserialize(_descriptor.precondition)336);337const keybindings = _descriptor.keybindings;338const keybindingsWhen = ContextKeyExpr.and(339precondition,340ContextKeyExpr.deserialize(_descriptor.keybindingContext)341);342const contextMenuGroupId = _descriptor.contextMenuGroupId || null;343const contextMenuOrder = _descriptor.contextMenuOrder || 0;344const run = (_accessor?: ServicesAccessor, ...args: unknown[]): Promise<void> => {345return Promise.resolve(_descriptor.run(this, ...args));346};347348349const toDispose = new DisposableStore();350351// Generate a unique id to allow the same descriptor.id across multiple editor instances352const uniqueId = this.getId() + ':' + id;353354// Register the command355toDispose.add(CommandsRegistry.registerCommand(uniqueId, run));356357// Register the context menu item358if (contextMenuGroupId) {359const menuItem: IMenuItem = {360command: {361id: uniqueId,362title: label363},364when: precondition,365group: contextMenuGroupId,366order: contextMenuOrder367};368toDispose.add(MenuRegistry.appendMenuItem(MenuId.EditorContext, menuItem));369}370371// Register the keybindings372if (Array.isArray(keybindings)) {373for (const kb of keybindings) {374toDispose.add(this._standaloneKeybindingService.addDynamicKeybinding(uniqueId, kb, run, keybindingsWhen));375}376}377378// Finally, register an internal editor action379const internalAction = new InternalEditorAction(380uniqueId,381label,382label,383undefined,384precondition,385(...args: unknown[]) => Promise.resolve(_descriptor.run(this, ...args)),386this._contextKeyService387);388389// Store it under the original id, such that trigger with the original id will work390this._actions.set(id, internalAction);391toDispose.add(toDisposable(() => {392this._actions.delete(id);393}));394395return toDispose;396}397398protected override _triggerCommand(handlerId: string, payload: unknown): void {399if (this._codeEditorService instanceof StandaloneCodeEditorService) {400// Help commands find this editor as the active editor401try {402this._codeEditorService.setActiveCodeEditor(this);403super._triggerCommand(handlerId, payload);404} finally {405this._codeEditorService.setActiveCodeEditor(null);406}407} else {408super._triggerCommand(handlerId, payload);409}410}411}412413export class StandaloneEditor extends StandaloneCodeEditor implements IStandaloneCodeEditor {414415private readonly _configurationService: IConfigurationService;416private readonly _standaloneThemeService: IStandaloneThemeService;417private _ownsModel: boolean;418419constructor(420domElement: HTMLElement,421_options: Readonly<IStandaloneEditorConstructionOptions> | undefined,422@IInstantiationService instantiationService: IInstantiationService,423@ICodeEditorService codeEditorService: ICodeEditorService,424@ICommandService commandService: ICommandService,425@IContextKeyService contextKeyService: IContextKeyService,426@IHoverService hoverService: IHoverService,427@IKeybindingService keybindingService: IKeybindingService,428@IStandaloneThemeService themeService: IStandaloneThemeService,429@INotificationService notificationService: INotificationService,430@IConfigurationService configurationService: IConfigurationService,431@IAccessibilityService accessibilityService: IAccessibilityService,432@IModelService modelService: IModelService,433@ILanguageService languageService: ILanguageService,434@ILanguageConfigurationService languageConfigurationService: ILanguageConfigurationService,435@ILanguageFeaturesService languageFeaturesService: ILanguageFeaturesService,436@IMarkdownRendererService markdownRendererService: IMarkdownRendererService,437@IUserInteractionService userInteractionService: IUserInteractionService,438) {439const options = { ..._options };440updateConfigurationService(configurationService, options, false);441const themeDomRegistration = (<StandaloneThemeService>themeService).registerEditorContainer(domElement);442if (typeof options.theme === 'string') {443themeService.setTheme(options.theme);444}445if (typeof options.autoDetectHighContrast !== 'undefined') {446themeService.setAutoDetectHighContrast(Boolean(options.autoDetectHighContrast));447}448const _model: ITextModel | null | undefined = options.model;449delete options.model;450super(domElement, options, instantiationService, codeEditorService, commandService, contextKeyService, hoverService, keybindingService, themeService, notificationService, accessibilityService, languageConfigurationService, languageFeaturesService, markdownRendererService, userInteractionService);451452this._configurationService = configurationService;453this._standaloneThemeService = themeService;454this._register(themeDomRegistration);455456let model: ITextModel | null;457if (typeof _model === 'undefined') {458const languageId = languageService.getLanguageIdByMimeType(options.language) || options.language || PLAINTEXT_LANGUAGE_ID;459model = createTextModel(modelService, languageService, options.value || '', languageId, undefined);460this._ownsModel = true;461} else {462model = _model;463this._ownsModel = false;464}465466this._attachModel(model);467if (model) {468const e: IModelChangedEvent = {469oldModelUrl: null,470newModelUrl: model.uri471};472this._onDidChangeModel.fire(e);473}474}475476public override dispose(): void {477super.dispose();478}479480public override updateOptions(newOptions: Readonly<IEditorOptions & IGlobalEditorOptions>): void {481updateConfigurationService(this._configurationService, newOptions, false);482if (typeof newOptions.theme === 'string') {483this._standaloneThemeService.setTheme(newOptions.theme);484}485if (typeof newOptions.autoDetectHighContrast !== 'undefined') {486this._standaloneThemeService.setAutoDetectHighContrast(Boolean(newOptions.autoDetectHighContrast));487}488super.updateOptions(newOptions);489}490491protected override _postDetachModelCleanup(detachedModel: ITextModel): void {492super._postDetachModelCleanup(detachedModel);493if (detachedModel && this._ownsModel) {494detachedModel.dispose();495this._ownsModel = false;496}497}498}499500export class StandaloneDiffEditor2 extends DiffEditorWidget implements IStandaloneDiffEditor {501502private readonly _configurationService: IConfigurationService;503private readonly _standaloneThemeService: IStandaloneThemeService;504505constructor(506domElement: HTMLElement,507_options: Readonly<IStandaloneDiffEditorConstructionOptions> | undefined,508@IInstantiationService instantiationService: IInstantiationService,509@IContextKeyService contextKeyService: IContextKeyService,510@ICodeEditorService codeEditorService: ICodeEditorService,511@IStandaloneThemeService themeService: IStandaloneThemeService,512@INotificationService notificationService: INotificationService,513@IConfigurationService configurationService: IConfigurationService,514@IContextMenuService contextMenuService: IContextMenuService,515@IEditorProgressService editorProgressService: IEditorProgressService,516@IClipboardService clipboardService: IClipboardService,517@IAccessibilitySignalService accessibilitySignalService: IAccessibilitySignalService,518) {519const options = { ..._options };520updateConfigurationService(configurationService, options, true);521const themeDomRegistration = (<StandaloneThemeService>themeService).registerEditorContainer(domElement);522if (typeof options.theme === 'string') {523themeService.setTheme(options.theme);524}525if (typeof options.autoDetectHighContrast !== 'undefined') {526themeService.setAutoDetectHighContrast(Boolean(options.autoDetectHighContrast));527}528529super(530domElement,531options,532{},533contextKeyService,534instantiationService,535codeEditorService,536accessibilitySignalService,537editorProgressService,538);539540this._configurationService = configurationService;541this._standaloneThemeService = themeService;542543this._register(themeDomRegistration);544}545546public override dispose(): void {547super.dispose();548}549550public override updateOptions(newOptions: Readonly<IDiffEditorOptions & IGlobalEditorOptions>): void {551updateConfigurationService(this._configurationService, newOptions, true);552if (typeof newOptions.theme === 'string') {553this._standaloneThemeService.setTheme(newOptions.theme);554}555if (typeof newOptions.autoDetectHighContrast !== 'undefined') {556this._standaloneThemeService.setAutoDetectHighContrast(Boolean(newOptions.autoDetectHighContrast));557}558super.updateOptions(newOptions);559}560561protected override _createInnerEditor(instantiationService: IInstantiationService, container: HTMLElement, options: Readonly<IEditorOptions>): CodeEditorWidget {562return instantiationService.createInstance(StandaloneCodeEditor, container, options);563}564565public override getOriginalEditor(): IStandaloneCodeEditor {566return <StandaloneCodeEditor>super.getOriginalEditor();567}568569public override getModifiedEditor(): IStandaloneCodeEditor {570return <StandaloneCodeEditor>super.getModifiedEditor();571}572573public addCommand(keybinding: number, handler: ICommandHandler, context?: string): string | null {574return this.getModifiedEditor().addCommand(keybinding, handler, context);575}576577public createContextKey<T extends ContextKeyValue = ContextKeyValue>(key: string, defaultValue: T): IContextKey<T> {578return this.getModifiedEditor().createContextKey(key, defaultValue);579}580581public addAction(descriptor: IActionDescriptor): IDisposable {582return this.getModifiedEditor().addAction(descriptor);583}584}585586/**587* @internal588*/589export function createTextModel(modelService: IModelService, languageService: ILanguageService, value: string, languageId: string | undefined, uri: URI | undefined): ITextModel {590value = value || '';591if (!languageId) {592const firstLF = value.indexOf('\n');593let firstLine = value;594if (firstLF !== -1) {595firstLine = value.substring(0, firstLF);596}597return doCreateModel(modelService, value, languageService.createByFilepathOrFirstLine(uri || null, firstLine), uri);598}599return doCreateModel(modelService, value, languageService.createById(languageId), uri);600}601602/**603* @internal604*/605function doCreateModel(modelService: IModelService, value: string, languageSelection: ILanguageSelection, uri: URI | undefined): ITextModel {606return modelService.createModel(value, languageSelection, uri);607}608609610