Path: blob/main/src/vs/editor/standalone/browser/standaloneCodeEditor.ts
3294 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';4445/**46* Description of an action contribution47*/48export interface IActionDescriptor {49/**50* An unique identifier of the contributed action.51*/52id: string;53/**54* A label of the action that will be presented to the user.55*/56label: string;57/**58* Precondition rule. The value should be a [context key expression](https://code.visualstudio.com/docs/getstarted/keybindings#_when-clause-contexts).59*/60precondition?: string;61/**62* An array of keybindings for the action.63*/64keybindings?: number[];65/**66* The keybinding rule (condition on top of precondition).67*/68keybindingContext?: string;69/**70* Control if the action should show up in the context menu and where.71* The context menu of the editor has these default:72* navigation - The navigation group comes first in all cases.73* 1_modification - This group comes next and contains commands that modify your code.74* 9_cutcopypaste - The last default group with the basic editing commands.75* You can also create your own group.76* Defaults to null (don't show in context menu).77*/78contextMenuGroupId?: string;79/**80* Control the order in the context menu group.81*/82contextMenuOrder?: number;83/**84* Method that will be executed when the action is triggered.85* @param editor The editor instance is passed in as a convenience86*/87run(editor: ICodeEditor, ...args: any[]): void | Promise<void>;88}8990/**91* Options which apply for all editors.92*/93export interface IGlobalEditorOptions {94/**95* The number of spaces a tab is equal to.96* This setting is overridden based on the file contents when `detectIndentation` is on.97* Defaults to 4.98*/99tabSize?: number;100/**101* Insert spaces when pressing `Tab`.102* This setting is overridden based on the file contents when `detectIndentation` is on.103* Defaults to true.104*/105insertSpaces?: boolean;106/**107* Controls whether `tabSize` and `insertSpaces` will be automatically detected when a file is opened based on the file contents.108* Defaults to true.109*/110detectIndentation?: boolean;111/**112* Remove trailing auto inserted whitespace.113* Defaults to true.114*/115trimAutoWhitespace?: boolean;116/**117* Special handling for large files to disable certain memory intensive features.118* Defaults to true.119*/120largeFileOptimizations?: boolean;121/**122* Controls whether completions should be computed based on words in the document.123* Defaults to true.124*/125wordBasedSuggestions?: 'off' | 'currentDocument' | 'matchingDocuments' | 'allDocuments';126/**127* Controls whether word based completions should be included from opened documents of the same language or any language.128*/129wordBasedSuggestionsOnlySameLanguage?: boolean;130/**131* Controls whether the semanticHighlighting is shown for the languages that support it.132* true: semanticHighlighting is enabled for all themes133* false: semanticHighlighting is disabled for all themes134* 'configuredByTheme': semanticHighlighting is controlled by the current color theme's semanticHighlighting setting.135* Defaults to 'byTheme'.136*/137'semanticHighlighting.enabled'?: true | false | 'configuredByTheme';138/**139* Keep peek editors open even when double-clicking their content or when hitting `Escape`.140* Defaults to false.141*/142stablePeek?: boolean;143/**144* Lines above this length will not be tokenized for performance reasons.145* Defaults to 20000.146*/147maxTokenizationLineLength?: number;148/**149* Theme to be used for rendering.150* The current out-of-the-box available themes are: 'vs' (default), 'vs-dark', 'hc-black', 'hc-light'.151* You can create custom themes via `monaco.editor.defineTheme`.152* To switch a theme, use `monaco.editor.setTheme`.153* **NOTE**: The theme might be overwritten if the OS is in high contrast mode, unless `autoDetectHighContrast` is set to false.154*/155theme?: string;156/**157* If enabled, will automatically change to high contrast theme if the OS is using a high contrast theme.158* Defaults to true.159*/160autoDetectHighContrast?: boolean;161}162163/**164* The options to create an editor.165*/166export interface IStandaloneEditorConstructionOptions extends IEditorConstructionOptions, IGlobalEditorOptions {167/**168* The initial model associated with this code editor.169*/170model?: ITextModel | null;171/**172* The initial value of the auto created model in the editor.173* To not automatically create a model, use `model: null`.174*/175value?: string;176/**177* The initial language of the auto created model in the editor.178* To not automatically create a model, use `model: null`.179*/180language?: string;181/**182* Initial theme to be used for rendering.183* The current out-of-the-box available themes are: 'vs' (default), 'vs-dark', 'hc-black', 'hc-light.184* You can create custom themes via `monaco.editor.defineTheme`.185* To switch a theme, use `monaco.editor.setTheme`.186* **NOTE**: The theme might be overwritten if the OS is in high contrast mode, unless `autoDetectHighContrast` is set to false.187*/188theme?: string;189/**190* If enabled, will automatically change to high contrast theme if the OS is using a high contrast theme.191* Defaults to true.192*/193autoDetectHighContrast?: boolean;194/**195* An URL to open when Ctrl+H (Windows and Linux) or Cmd+H (OSX) is pressed in196* the accessibility help dialog in the editor.197*198* Defaults to "https://go.microsoft.com/fwlink/?linkid=852450"199*/200accessibilityHelpUrl?: string;201/**202* Container element to use for ARIA messages.203* Defaults to document.body.204*/205ariaContainerElement?: HTMLElement;206}207208/**209* The options to create a diff editor.210*/211export interface IStandaloneDiffEditorConstructionOptions extends IDiffEditorConstructionOptions {212/**213* Initial theme to be used for rendering.214* The current out-of-the-box available themes are: 'vs' (default), 'vs-dark', 'hc-black', 'hc-light.215* You can create custom themes via `monaco.editor.defineTheme`.216* To switch a theme, use `monaco.editor.setTheme`.217* **NOTE**: The theme might be overwritten if the OS is in high contrast mode, unless `autoDetectHighContrast` is set to false.218*/219theme?: string;220/**221* If enabled, will automatically change to high contrast theme if the OS is using a high contrast theme.222* Defaults to true.223*/224autoDetectHighContrast?: boolean;225}226227export interface IStandaloneCodeEditor extends ICodeEditor {228updateOptions(newOptions: IEditorOptions & IGlobalEditorOptions): void;229addCommand(keybinding: number, handler: ICommandHandler, context?: string): string | null;230createContextKey<T extends ContextKeyValue = ContextKeyValue>(key: string, defaultValue: T): IContextKey<T>;231addAction(descriptor: IActionDescriptor): IDisposable;232}233234export interface IStandaloneDiffEditor extends IDiffEditor {235addCommand(keybinding: number, handler: ICommandHandler, context?: string): string | null;236createContextKey<T extends ContextKeyValue = ContextKeyValue>(key: string, defaultValue: T): IContextKey<T>;237addAction(descriptor: IActionDescriptor): IDisposable;238239getOriginalEditor(): IStandaloneCodeEditor;240getModifiedEditor(): IStandaloneCodeEditor;241}242243let LAST_GENERATED_COMMAND_ID = 0;244245let ariaDomNodeCreated = false;246/**247* Create ARIA dom node inside parent,248* or only for the first editor instantiation inside document.body.249* @param parent container element for ARIA dom node250*/251function createAriaDomNode(parent: HTMLElement | undefined) {252if (!parent) {253if (ariaDomNodeCreated) {254return;255}256ariaDomNodeCreated = true;257}258aria.setARIAContainer(parent || mainWindow.document.body);259}260261/**262* A code editor to be used both by the standalone editor and the standalone diff editor.263*/264export class StandaloneCodeEditor extends CodeEditorWidget implements IStandaloneCodeEditor {265266private readonly _standaloneKeybindingService: StandaloneKeybindingService | null;267268constructor(269domElement: HTMLElement,270_options: Readonly<IStandaloneEditorConstructionOptions>,271@IInstantiationService instantiationService: IInstantiationService,272@ICodeEditorService codeEditorService: ICodeEditorService,273@ICommandService commandService: ICommandService,274@IContextKeyService contextKeyService: IContextKeyService,275@IHoverService hoverService: IHoverService,276@IKeybindingService keybindingService: IKeybindingService,277@IThemeService themeService: IThemeService,278@INotificationService notificationService: INotificationService,279@IAccessibilityService accessibilityService: IAccessibilityService,280@ILanguageConfigurationService languageConfigurationService: ILanguageConfigurationService,281@ILanguageFeaturesService languageFeaturesService: ILanguageFeaturesService,282) {283const options = { ..._options };284options.ariaLabel = options.ariaLabel || StandaloneCodeEditorNLS.editorViewAccessibleLabel;285super(domElement, options, {}, instantiationService, codeEditorService, commandService, contextKeyService, themeService, notificationService, accessibilityService, languageConfigurationService, languageFeaturesService);286287if (keybindingService instanceof StandaloneKeybindingService) {288this._standaloneKeybindingService = keybindingService;289} else {290this._standaloneKeybindingService = null;291}292293createAriaDomNode(options.ariaContainerElement);294295setHoverDelegateFactory((placement, enableInstantHover) => instantiationService.createInstance(WorkbenchHoverDelegate, placement, { instantHover: enableInstantHover }, {}));296setBaseLayerHoverDelegate(hoverService);297}298299public addCommand(keybinding: number, handler: ICommandHandler, context?: string): string | null {300if (!this._standaloneKeybindingService) {301console.warn('Cannot add command because the editor is configured with an unrecognized KeybindingService');302return null;303}304const commandId = 'DYNAMIC_' + (++LAST_GENERATED_COMMAND_ID);305const whenExpression = ContextKeyExpr.deserialize(context);306this._standaloneKeybindingService.addDynamicKeybinding(commandId, keybinding, handler, whenExpression);307return commandId;308}309310public createContextKey<T extends ContextKeyValue = ContextKeyValue>(key: string, defaultValue: T): IContextKey<T> {311return this._contextKeyService.createKey(key, defaultValue);312}313314public addAction(_descriptor: IActionDescriptor): IDisposable {315if ((typeof _descriptor.id !== 'string') || (typeof _descriptor.label !== 'string') || (typeof _descriptor.run !== 'function')) {316throw new Error('Invalid action descriptor, `id`, `label` and `run` are required properties!');317}318if (!this._standaloneKeybindingService) {319console.warn('Cannot add keybinding because the editor is configured with an unrecognized KeybindingService');320return Disposable.None;321}322323// Read descriptor options324const id = _descriptor.id;325const label = _descriptor.label;326const precondition = ContextKeyExpr.and(327ContextKeyExpr.equals('editorId', this.getId()),328ContextKeyExpr.deserialize(_descriptor.precondition)329);330const keybindings = _descriptor.keybindings;331const keybindingsWhen = ContextKeyExpr.and(332precondition,333ContextKeyExpr.deserialize(_descriptor.keybindingContext)334);335const contextMenuGroupId = _descriptor.contextMenuGroupId || null;336const contextMenuOrder = _descriptor.contextMenuOrder || 0;337const run = (_accessor?: ServicesAccessor, ...args: any[]): Promise<void> => {338return Promise.resolve(_descriptor.run(this, ...args));339};340341342const toDispose = new DisposableStore();343344// Generate a unique id to allow the same descriptor.id across multiple editor instances345const uniqueId = this.getId() + ':' + id;346347// Register the command348toDispose.add(CommandsRegistry.registerCommand(uniqueId, run));349350// Register the context menu item351if (contextMenuGroupId) {352const menuItem: IMenuItem = {353command: {354id: uniqueId,355title: label356},357when: precondition,358group: contextMenuGroupId,359order: contextMenuOrder360};361toDispose.add(MenuRegistry.appendMenuItem(MenuId.EditorContext, menuItem));362}363364// Register the keybindings365if (Array.isArray(keybindings)) {366for (const kb of keybindings) {367toDispose.add(this._standaloneKeybindingService.addDynamicKeybinding(uniqueId, kb, run, keybindingsWhen));368}369}370371// Finally, register an internal editor action372const internalAction = new InternalEditorAction(373uniqueId,374label,375label,376undefined,377precondition,378(...args: unknown[]) => Promise.resolve(_descriptor.run(this, ...args)),379this._contextKeyService380);381382// Store it under the original id, such that trigger with the original id will work383this._actions.set(id, internalAction);384toDispose.add(toDisposable(() => {385this._actions.delete(id);386}));387388return toDispose;389}390391protected override _triggerCommand(handlerId: string, payload: any): void {392if (this._codeEditorService instanceof StandaloneCodeEditorService) {393// Help commands find this editor as the active editor394try {395this._codeEditorService.setActiveCodeEditor(this);396super._triggerCommand(handlerId, payload);397} finally {398this._codeEditorService.setActiveCodeEditor(null);399}400} else {401super._triggerCommand(handlerId, payload);402}403}404}405406export class StandaloneEditor extends StandaloneCodeEditor implements IStandaloneCodeEditor {407408private readonly _configurationService: IConfigurationService;409private readonly _standaloneThemeService: IStandaloneThemeService;410private _ownsModel: boolean;411412constructor(413domElement: HTMLElement,414_options: Readonly<IStandaloneEditorConstructionOptions> | undefined,415@IInstantiationService instantiationService: IInstantiationService,416@ICodeEditorService codeEditorService: ICodeEditorService,417@ICommandService commandService: ICommandService,418@IContextKeyService contextKeyService: IContextKeyService,419@IHoverService hoverService: IHoverService,420@IKeybindingService keybindingService: IKeybindingService,421@IStandaloneThemeService themeService: IStandaloneThemeService,422@INotificationService notificationService: INotificationService,423@IConfigurationService configurationService: IConfigurationService,424@IAccessibilityService accessibilityService: IAccessibilityService,425@IModelService modelService: IModelService,426@ILanguageService languageService: ILanguageService,427@ILanguageConfigurationService languageConfigurationService: ILanguageConfigurationService,428@ILanguageFeaturesService languageFeaturesService: ILanguageFeaturesService,429) {430const options = { ..._options };431updateConfigurationService(configurationService, options, false);432const themeDomRegistration = (<StandaloneThemeService>themeService).registerEditorContainer(domElement);433if (typeof options.theme === 'string') {434themeService.setTheme(options.theme);435}436if (typeof options.autoDetectHighContrast !== 'undefined') {437themeService.setAutoDetectHighContrast(Boolean(options.autoDetectHighContrast));438}439const _model: ITextModel | null | undefined = options.model;440delete options.model;441super(domElement, options, instantiationService, codeEditorService, commandService, contextKeyService, hoverService, keybindingService, themeService, notificationService, accessibilityService, languageConfigurationService, languageFeaturesService);442443this._configurationService = configurationService;444this._standaloneThemeService = themeService;445this._register(themeDomRegistration);446447let model: ITextModel | null;448if (typeof _model === 'undefined') {449const languageId = languageService.getLanguageIdByMimeType(options.language) || options.language || PLAINTEXT_LANGUAGE_ID;450model = createTextModel(modelService, languageService, options.value || '', languageId, undefined);451this._ownsModel = true;452} else {453model = _model;454this._ownsModel = false;455}456457this._attachModel(model);458if (model) {459const e: IModelChangedEvent = {460oldModelUrl: null,461newModelUrl: model.uri462};463this._onDidChangeModel.fire(e);464}465}466467public override dispose(): void {468super.dispose();469}470471public override updateOptions(newOptions: Readonly<IEditorOptions & IGlobalEditorOptions>): void {472updateConfigurationService(this._configurationService, newOptions, false);473if (typeof newOptions.theme === 'string') {474this._standaloneThemeService.setTheme(newOptions.theme);475}476if (typeof newOptions.autoDetectHighContrast !== 'undefined') {477this._standaloneThemeService.setAutoDetectHighContrast(Boolean(newOptions.autoDetectHighContrast));478}479super.updateOptions(newOptions);480}481482protected override _postDetachModelCleanup(detachedModel: ITextModel): void {483super._postDetachModelCleanup(detachedModel);484if (detachedModel && this._ownsModel) {485detachedModel.dispose();486this._ownsModel = false;487}488}489}490491export class StandaloneDiffEditor2 extends DiffEditorWidget implements IStandaloneDiffEditor {492493private readonly _configurationService: IConfigurationService;494private readonly _standaloneThemeService: IStandaloneThemeService;495496constructor(497domElement: HTMLElement,498_options: Readonly<IStandaloneDiffEditorConstructionOptions> | undefined,499@IInstantiationService instantiationService: IInstantiationService,500@IContextKeyService contextKeyService: IContextKeyService,501@ICodeEditorService codeEditorService: ICodeEditorService,502@IStandaloneThemeService themeService: IStandaloneThemeService,503@INotificationService notificationService: INotificationService,504@IConfigurationService configurationService: IConfigurationService,505@IContextMenuService contextMenuService: IContextMenuService,506@IEditorProgressService editorProgressService: IEditorProgressService,507@IClipboardService clipboardService: IClipboardService,508@IAccessibilitySignalService accessibilitySignalService: IAccessibilitySignalService,509) {510const options = { ..._options };511updateConfigurationService(configurationService, options, true);512const themeDomRegistration = (<StandaloneThemeService>themeService).registerEditorContainer(domElement);513if (typeof options.theme === 'string') {514themeService.setTheme(options.theme);515}516if (typeof options.autoDetectHighContrast !== 'undefined') {517themeService.setAutoDetectHighContrast(Boolean(options.autoDetectHighContrast));518}519520super(521domElement,522options,523{},524contextKeyService,525instantiationService,526codeEditorService,527accessibilitySignalService,528editorProgressService,529);530531this._configurationService = configurationService;532this._standaloneThemeService = themeService;533534this._register(themeDomRegistration);535}536537public override dispose(): void {538super.dispose();539}540541public override updateOptions(newOptions: Readonly<IDiffEditorOptions & IGlobalEditorOptions>): void {542updateConfigurationService(this._configurationService, newOptions, true);543if (typeof newOptions.theme === 'string') {544this._standaloneThemeService.setTheme(newOptions.theme);545}546if (typeof newOptions.autoDetectHighContrast !== 'undefined') {547this._standaloneThemeService.setAutoDetectHighContrast(Boolean(newOptions.autoDetectHighContrast));548}549super.updateOptions(newOptions);550}551552protected override _createInnerEditor(instantiationService: IInstantiationService, container: HTMLElement, options: Readonly<IEditorOptions>): CodeEditorWidget {553return instantiationService.createInstance(StandaloneCodeEditor, container, options);554}555556public override getOriginalEditor(): IStandaloneCodeEditor {557return <StandaloneCodeEditor>super.getOriginalEditor();558}559560public override getModifiedEditor(): IStandaloneCodeEditor {561return <StandaloneCodeEditor>super.getModifiedEditor();562}563564public addCommand(keybinding: number, handler: ICommandHandler, context?: string): string | null {565return this.getModifiedEditor().addCommand(keybinding, handler, context);566}567568public createContextKey<T extends ContextKeyValue = ContextKeyValue>(key: string, defaultValue: T): IContextKey<T> {569return this.getModifiedEditor().createContextKey(key, defaultValue);570}571572public addAction(descriptor: IActionDescriptor): IDisposable {573return this.getModifiedEditor().addAction(descriptor);574}575}576577/**578* @internal579*/580export function createTextModel(modelService: IModelService, languageService: ILanguageService, value: string, languageId: string | undefined, uri: URI | undefined): ITextModel {581value = value || '';582if (!languageId) {583const firstLF = value.indexOf('\n');584let firstLine = value;585if (firstLF !== -1) {586firstLine = value.substring(0, firstLF);587}588return doCreateModel(modelService, value, languageService.createByFilepathOrFirstLine(uri || null, firstLine), uri);589}590return doCreateModel(modelService, value, languageService.createById(languageId), uri);591}592593/**594* @internal595*/596function doCreateModel(modelService: IModelService, value: string, languageSelection: ILanguageSelection, uri: URI | undefined): ITextModel {597return modelService.createModel(value, languageSelection, uri);598}599600601