Path: blob/main/src/vs/editor/standalone/browser/standaloneEditor.ts
5240 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 { mainWindow } from '../../../base/browser/window.js';6import { Disposable, DisposableStore, IDisposable } from '../../../base/common/lifecycle.js';7import { splitLines } from '../../../base/common/strings.js';8import { URI } from '../../../base/common/uri.js';9import './standalone-tokens.css';10import { FontMeasurements } from '../../browser/config/fontMeasurements.js';11import { ICodeEditor } from '../../browser/editorBrowser.js';12import { EditorCommand, ServicesAccessor } from '../../browser/editorExtensions.js';13import { ICodeEditorService } from '../../browser/services/codeEditorService.js';14import { IInternalWebWorkerOptions, MonacoWebWorker, createWebWorker as actualCreateWebWorker } from './standaloneWebWorker.js';15import { ApplyUpdateResult, ConfigurationChangedEvent, EditorOptions } from '../../common/config/editorOptions.js';16import { EditorZoom } from '../../common/config/editorZoom.js';17import { BareFontInfo, FontInfo } from '../../common/config/fontInfo.js';18import { IPosition } from '../../common/core/position.js';19import { IRange } from '../../common/core/range.js';20import { EditorType, IDiffEditor } from '../../common/editorCommon.js';21import * as languages from '../../common/languages.js';22import { ILanguageService } from '../../common/languages/language.js';23import { PLAINTEXT_LANGUAGE_ID } from '../../common/languages/modesRegistry.js';24import { NullState, nullTokenize } from '../../common/languages/nullTokenize.js';25import { FindMatch, ITextModel, TextModelResolvedOptions } from '../../common/model.js';26import { IModelService } from '../../common/services/model.js';27import * as standaloneEnums from '../../common/standalone/standaloneEnums.js';28import { Colorizer, IColorizerElementOptions, IColorizerOptions } from './colorizer.js';29import { IActionDescriptor, IStandaloneCodeEditor, IStandaloneDiffEditor, IStandaloneDiffEditorConstructionOptions, IStandaloneEditorConstructionOptions, StandaloneDiffEditor2, StandaloneEditor, createTextModel } from './standaloneCodeEditor.js';30import { IEditorOverrideServices, StandaloneKeybindingService, StandaloneServices } from './standaloneServices.js';31import { StandaloneThemeService } from './standaloneThemeService.js';32import { IStandaloneThemeData, IStandaloneThemeService } from '../common/standaloneTheme.js';33import { IMenuItem, MenuId, MenuRegistry } from '../../../platform/actions/common/actions.js';34import { CommandsRegistry, ICommandHandler } from '../../../platform/commands/common/commands.js';35import { ContextKeyExpr } from '../../../platform/contextkey/common/contextkey.js';36import { ITextResourceEditorInput } from '../../../platform/editor/common/editor.js';37import { IKeybindingService } from '../../../platform/keybinding/common/keybinding.js';38import { IMarker, IMarkerData, IMarkerService } from '../../../platform/markers/common/markers.js';39import { IOpenerService } from '../../../platform/opener/common/opener.js';40import { MultiDiffEditorWidget } from '../../browser/widget/multiDiffEditor/multiDiffEditorWidget.js';41import { IWebWorkerService } from '../../../platform/webWorker/browser/webWorkerService.js';4243/**44* Create a new editor under `domElement`.45* `domElement` should be empty (not contain other dom nodes).46* The editor will read the size of `domElement`.47*/48export function create(domElement: HTMLElement, options?: IStandaloneEditorConstructionOptions, override?: IEditorOverrideServices): IStandaloneCodeEditor {49const instantiationService = StandaloneServices.initialize(override || {});50return instantiationService.createInstance(StandaloneEditor, domElement, options);51}5253/**54* Emitted when an editor is created.55* Creating a diff editor might cause this listener to be invoked with the two editors.56* @event57*/58export function onDidCreateEditor(listener: (codeEditor: ICodeEditor) => void): IDisposable {59const codeEditorService = StandaloneServices.get(ICodeEditorService);60return codeEditorService.onCodeEditorAdd((editor) => {61listener(editor);62});63}6465/**66* Emitted when an diff editor is created.67* @event68*/69export function onDidCreateDiffEditor(listener: (diffEditor: IDiffEditor) => void): IDisposable {70const codeEditorService = StandaloneServices.get(ICodeEditorService);71return codeEditorService.onDiffEditorAdd((editor) => {72listener(<IDiffEditor>editor);73});74}7576/**77* Get all the created editors.78*/79export function getEditors(): readonly ICodeEditor[] {80const codeEditorService = StandaloneServices.get(ICodeEditorService);81return codeEditorService.listCodeEditors();82}8384/**85* Get all the created diff editors.86*/87export function getDiffEditors(): readonly IDiffEditor[] {88const codeEditorService = StandaloneServices.get(ICodeEditorService);89return codeEditorService.listDiffEditors();90}9192/**93* Create a new diff editor under `domElement`.94* `domElement` should be empty (not contain other dom nodes).95* The editor will read the size of `domElement`.96*/97export function createDiffEditor(domElement: HTMLElement, options?: IStandaloneDiffEditorConstructionOptions, override?: IEditorOverrideServices): IStandaloneDiffEditor {98const instantiationService = StandaloneServices.initialize(override || {});99return instantiationService.createInstance(StandaloneDiffEditor2, domElement, options);100}101102export function createMultiFileDiffEditor(domElement: HTMLElement, override?: IEditorOverrideServices) {103const instantiationService = StandaloneServices.initialize(override || {});104return new MultiDiffEditorWidget(domElement, {}, instantiationService);105}106107/**108* Description of a command contribution109*/110export interface ICommandDescriptor {111/**112* An unique identifier of the contributed command.113*/114id: string;115/**116* Callback that will be executed when the command is triggered.117*/118run: ICommandHandler;119}120121/**122* Add a command.123*/124export function addCommand(descriptor: ICommandDescriptor): IDisposable {125if ((typeof descriptor.id !== 'string') || (typeof descriptor.run !== 'function')) {126throw new Error('Invalid command descriptor, `id` and `run` are required properties!');127}128return CommandsRegistry.registerCommand(descriptor.id, descriptor.run);129}130131/**132* Add an action to all editors.133*/134export function addEditorAction(descriptor: IActionDescriptor): IDisposable {135if ((typeof descriptor.id !== 'string') || (typeof descriptor.label !== 'string') || (typeof descriptor.run !== 'function')) {136throw new Error('Invalid action descriptor, `id`, `label` and `run` are required properties!');137}138139const precondition = ContextKeyExpr.deserialize(descriptor.precondition);140const run = (accessor: ServicesAccessor, ...args: unknown[]): void | Promise<void> => {141return EditorCommand.runEditorCommand(accessor, args, precondition, (accessor, editor, args) => Promise.resolve(descriptor.run(editor, ...args)));142};143144const toDispose = new DisposableStore();145146// Register the command147toDispose.add(CommandsRegistry.registerCommand(descriptor.id, run));148149// Register the context menu item150if (descriptor.contextMenuGroupId) {151const menuItem: IMenuItem = {152command: {153id: descriptor.id,154title: descriptor.label155},156when: precondition,157group: descriptor.contextMenuGroupId,158order: descriptor.contextMenuOrder || 0159};160toDispose.add(MenuRegistry.appendMenuItem(MenuId.EditorContext, menuItem));161}162163// Register the keybindings164if (Array.isArray(descriptor.keybindings)) {165const keybindingService = StandaloneServices.get(IKeybindingService);166if (!(keybindingService instanceof StandaloneKeybindingService)) {167console.warn('Cannot add keybinding because the editor is configured with an unrecognized KeybindingService');168} else {169const keybindingsWhen = ContextKeyExpr.and(precondition, ContextKeyExpr.deserialize(descriptor.keybindingContext));170toDispose.add(keybindingService.addDynamicKeybindings(descriptor.keybindings.map((keybinding) => {171return {172keybinding,173command: descriptor.id,174when: keybindingsWhen175};176})));177}178}179180return toDispose;181}182183/**184* A keybinding rule.185*/186export interface IKeybindingRule {187keybinding: number;188command?: string | null;189commandArgs?: any;190when?: string | null;191}192193/**194* Add a keybinding rule.195*/196export function addKeybindingRule(rule: IKeybindingRule): IDisposable {197return addKeybindingRules([rule]);198}199200/**201* Add keybinding rules.202*/203export function addKeybindingRules(rules: IKeybindingRule[]): IDisposable {204const keybindingService = StandaloneServices.get(IKeybindingService);205if (!(keybindingService instanceof StandaloneKeybindingService)) {206console.warn('Cannot add keybinding because the editor is configured with an unrecognized KeybindingService');207return Disposable.None;208}209210return keybindingService.addDynamicKeybindings(rules.map((rule) => {211return {212keybinding: rule.keybinding,213command: rule.command,214commandArgs: rule.commandArgs,215when: ContextKeyExpr.deserialize(rule.when),216};217}));218}219220/**221* Create a new editor model.222* You can specify the language that should be set for this model or let the language be inferred from the `uri`.223*/224export function createModel(value: string, language?: string, uri?: URI): ITextModel {225const languageService = StandaloneServices.get(ILanguageService);226const languageId = languageService.getLanguageIdByMimeType(language) || language;227return createTextModel(228StandaloneServices.get(IModelService),229languageService,230value,231languageId,232uri233);234}235236/**237* Change the language for a model.238*/239export function setModelLanguage(model: ITextModel, mimeTypeOrLanguageId: string): void {240const languageService = StandaloneServices.get(ILanguageService);241const languageId = languageService.getLanguageIdByMimeType(mimeTypeOrLanguageId) || mimeTypeOrLanguageId || PLAINTEXT_LANGUAGE_ID;242model.setLanguage(languageService.createById(languageId));243}244245/**246* Set the markers for a model.247*/248export function setModelMarkers(model: ITextModel, owner: string, markers: IMarkerData[]): void {249if (model) {250const markerService = StandaloneServices.get(IMarkerService);251markerService.changeOne(owner, model.uri, markers);252}253}254255/**256* Remove all markers of an owner.257*/258export function removeAllMarkers(owner: string) {259const markerService = StandaloneServices.get(IMarkerService);260markerService.changeAll(owner, []);261}262263/**264* Get markers for owner and/or resource265*266* @returns list of markers267*/268export function getModelMarkers(filter: { owner?: string; resource?: URI; take?: number }): IMarker[] {269const markerService = StandaloneServices.get(IMarkerService);270return markerService.read(filter);271}272273/**274* Emitted when markers change for a model.275* @event276*/277export function onDidChangeMarkers(listener: (e: readonly URI[]) => void): IDisposable {278const markerService = StandaloneServices.get(IMarkerService);279return markerService.onMarkerChanged(listener);280}281282/**283* Get the model that has `uri` if it exists.284*/285export function getModel(uri: URI): ITextModel | null {286const modelService = StandaloneServices.get(IModelService);287return modelService.getModel(uri);288}289290/**291* Get all the created models.292*/293export function getModels(): ITextModel[] {294const modelService = StandaloneServices.get(IModelService);295return modelService.getModels();296}297298/**299* Emitted when a model is created.300* @event301*/302export function onDidCreateModel(listener: (model: ITextModel) => void): IDisposable {303const modelService = StandaloneServices.get(IModelService);304return modelService.onModelAdded(listener);305}306307/**308* Emitted right before a model is disposed.309* @event310*/311export function onWillDisposeModel(listener: (model: ITextModel) => void): IDisposable {312const modelService = StandaloneServices.get(IModelService);313return modelService.onModelRemoved(listener);314}315316/**317* Emitted when a different language is set to a model.318* @event319*/320export function onDidChangeModelLanguage(listener: (e: { readonly model: ITextModel; readonly oldLanguage: string }) => void): IDisposable {321const modelService = StandaloneServices.get(IModelService);322return modelService.onModelLanguageChanged((e) => {323listener({324model: e.model,325oldLanguage: e.oldLanguageId326});327});328}329330/**331* Create a new web worker that has model syncing capabilities built in.332* Specify an AMD module to load that will `create` an object that will be proxied.333*/334export function createWebWorker<T extends object>(opts: IInternalWebWorkerOptions): MonacoWebWorker<T> {335return actualCreateWebWorker<T>(StandaloneServices.get(IModelService), StandaloneServices.get(IWebWorkerService), opts);336}337338/**339* Colorize the contents of `domNode` using attribute `data-lang`.340*/341export function colorizeElement(domNode: HTMLElement, options: IColorizerElementOptions): Promise<void> {342const languageService = StandaloneServices.get(ILanguageService);343const themeService = <StandaloneThemeService>StandaloneServices.get(IStandaloneThemeService);344return Colorizer.colorizeElement(themeService, languageService, domNode, options).then(() => {345themeService.registerEditorContainer(domNode);346});347}348349/**350* Colorize `text` using language `languageId`.351*/352export function colorize(text: string, languageId: string, options: IColorizerOptions): Promise<string> {353const languageService = StandaloneServices.get(ILanguageService);354const themeService = <StandaloneThemeService>StandaloneServices.get(IStandaloneThemeService);355themeService.registerEditorContainer(mainWindow.document.body);356return Colorizer.colorize(languageService, text, languageId, options);357}358359/**360* Colorize a line in a model.361*/362export function colorizeModelLine(model: ITextModel, lineNumber: number, tabSize: number = 4): string {363const themeService = <StandaloneThemeService>StandaloneServices.get(IStandaloneThemeService);364themeService.registerEditorContainer(mainWindow.document.body);365return Colorizer.colorizeModelLine(model, lineNumber, tabSize);366}367368/**369* @internal370*/371function getSafeTokenizationSupport(language: string): Omit<languages.ITokenizationSupport, 'tokenizeEncoded'> {372const tokenizationSupport = languages.TokenizationRegistry.get(language);373if (tokenizationSupport) {374return tokenizationSupport;375}376return {377getInitialState: () => NullState,378tokenize: (line: string, hasEOL: boolean, state: languages.IState) => nullTokenize(language, state)379};380}381382/**383* Tokenize `text` using language `languageId`384*/385export function tokenize(text: string, languageId: string): languages.Token[][] {386// Needed in order to get the mode registered for subsequent look-ups387languages.TokenizationRegistry.getOrCreate(languageId);388389const tokenizationSupport = getSafeTokenizationSupport(languageId);390const lines = splitLines(text);391const result: languages.Token[][] = [];392let state = tokenizationSupport.getInitialState();393for (let i = 0, len = lines.length; i < len; i++) {394const line = lines[i];395const tokenizationResult = tokenizationSupport.tokenize(line, true, state);396397result[i] = tokenizationResult.tokens;398state = tokenizationResult.endState;399}400return result;401}402403/**404* Define a new theme or update an existing theme.405*/406export function defineTheme(themeName: string, themeData: IStandaloneThemeData): void {407const standaloneThemeService = StandaloneServices.get(IStandaloneThemeService);408standaloneThemeService.defineTheme(themeName, themeData);409}410411/**412* Switches to a theme.413*/414export function setTheme(themeName: string): void {415const standaloneThemeService = StandaloneServices.get(IStandaloneThemeService);416standaloneThemeService.setTheme(themeName);417}418419/**420* Clears all cached font measurements and triggers re-measurement.421*/422export function remeasureFonts(): void {423FontMeasurements.clearAllFontInfos();424}425426/**427* Register a command.428*/429export function registerCommand(id: string, handler: (accessor: any, ...args: any[]) => void): IDisposable {430return CommandsRegistry.registerCommand({ id, handler });431}432433export interface ILinkOpener {434open(resource: URI): boolean | Promise<boolean>;435}436437/**438* Registers a handler that is called when a link is opened in any editor. The handler callback should return `true` if the link was handled and `false` otherwise.439* The handler that was registered last will be called first when a link is opened.440*441* Returns a disposable that can unregister the opener again.442*/443export function registerLinkOpener(opener: ILinkOpener): IDisposable {444const openerService = StandaloneServices.get(IOpenerService);445return openerService.registerOpener({446async open(resource: string | URI) {447if (typeof resource === 'string') {448resource = URI.parse(resource);449}450return opener.open(resource);451}452});453}454455/**456* Represents an object that can handle editor open operations (e.g. when "go to definition" is called457* with a resource other than the current model).458*/459export interface ICodeEditorOpener {460/**461* Callback that is invoked when a resource other than the current model should be opened (e.g. when "go to definition" is called).462* The callback should return `true` if the request was handled and `false` otherwise.463* @param source The code editor instance that initiated the request.464* @param resource The URI of the resource that should be opened.465* @param selectionOrPosition An optional position or selection inside the model corresponding to `resource` that can be used to set the cursor.466*/467openCodeEditor(source: ICodeEditor, resource: URI, selectionOrPosition?: IRange | IPosition): boolean | Promise<boolean>;468}469470/**471* Registers a handler that is called when a resource other than the current model should be opened in the editor (e.g. "go to definition").472* The handler callback should return `true` if the request was handled and `false` otherwise.473*474* Returns a disposable that can unregister the opener again.475*476* If no handler is registered the default behavior is to do nothing for models other than the currently attached one.477*/478export function registerEditorOpener(opener: ICodeEditorOpener): IDisposable {479const codeEditorService = StandaloneServices.get(ICodeEditorService);480return codeEditorService.registerCodeEditorOpenHandler(async (input: ITextResourceEditorInput, source: ICodeEditor | null, sideBySide?: boolean) => {481if (!source) {482return null;483}484const selection = input.options?.selection;485let selectionOrPosition: IRange | IPosition | undefined;486if (selection && typeof selection.endLineNumber === 'number' && typeof selection.endColumn === 'number') {487selectionOrPosition = <IRange>selection;488} else if (selection) {489selectionOrPosition = { lineNumber: selection.startLineNumber, column: selection.startColumn };490}491if (await opener.openCodeEditor(source, input.resource, selectionOrPosition)) {492return source; // return source editor to indicate that this handler has successfully handled the opening493}494return null; // fallback to other registered handlers495});496}497498/**499* @internal500*/501export function createMonacoEditorAPI(): typeof monaco.editor {502return {503// methods504// eslint-disable-next-line local/code-no-any-casts505create: <any>create,506// eslint-disable-next-line local/code-no-any-casts507getEditors: <any>getEditors,508// eslint-disable-next-line local/code-no-any-casts509getDiffEditors: <any>getDiffEditors,510// eslint-disable-next-line local/code-no-any-casts511onDidCreateEditor: <any>onDidCreateEditor,512// eslint-disable-next-line local/code-no-any-casts513onDidCreateDiffEditor: <any>onDidCreateDiffEditor,514// eslint-disable-next-line local/code-no-any-casts515createDiffEditor: <any>createDiffEditor,516517// eslint-disable-next-line local/code-no-any-casts518addCommand: <any>addCommand,519// eslint-disable-next-line local/code-no-any-casts520addEditorAction: <any>addEditorAction,521// eslint-disable-next-line local/code-no-any-casts522addKeybindingRule: <any>addKeybindingRule,523// eslint-disable-next-line local/code-no-any-casts524addKeybindingRules: <any>addKeybindingRules,525526// eslint-disable-next-line local/code-no-any-casts527createModel: <any>createModel,528// eslint-disable-next-line local/code-no-any-casts529setModelLanguage: <any>setModelLanguage,530// eslint-disable-next-line local/code-no-any-casts531setModelMarkers: <any>setModelMarkers,532// eslint-disable-next-line local/code-no-any-casts533getModelMarkers: <any>getModelMarkers,534removeAllMarkers: removeAllMarkers,535// eslint-disable-next-line local/code-no-any-casts536onDidChangeMarkers: <any>onDidChangeMarkers,537// eslint-disable-next-line local/code-no-any-casts538getModels: <any>getModels,539// eslint-disable-next-line local/code-no-any-casts540getModel: <any>getModel,541// eslint-disable-next-line local/code-no-any-casts542onDidCreateModel: <any>onDidCreateModel,543// eslint-disable-next-line local/code-no-any-casts544onWillDisposeModel: <any>onWillDisposeModel,545// eslint-disable-next-line local/code-no-any-casts546onDidChangeModelLanguage: <any>onDidChangeModelLanguage,547548549// eslint-disable-next-line local/code-no-any-casts550createWebWorker: <any>createWebWorker,551// eslint-disable-next-line local/code-no-any-casts552colorizeElement: <any>colorizeElement,553// eslint-disable-next-line local/code-no-any-casts554colorize: <any>colorize,555// eslint-disable-next-line local/code-no-any-casts556colorizeModelLine: <any>colorizeModelLine,557// eslint-disable-next-line local/code-no-any-casts558tokenize: <any>tokenize,559// eslint-disable-next-line local/code-no-any-casts560defineTheme: <any>defineTheme,561// eslint-disable-next-line local/code-no-any-casts562setTheme: <any>setTheme,563remeasureFonts: remeasureFonts,564registerCommand: registerCommand,565566registerLinkOpener: registerLinkOpener,567// eslint-disable-next-line local/code-no-any-casts568registerEditorOpener: <any>registerEditorOpener,569570// enums571AccessibilitySupport: standaloneEnums.AccessibilitySupport,572ContentWidgetPositionPreference: standaloneEnums.ContentWidgetPositionPreference,573CursorChangeReason: standaloneEnums.CursorChangeReason,574DefaultEndOfLine: standaloneEnums.DefaultEndOfLine,575EditorAutoIndentStrategy: standaloneEnums.EditorAutoIndentStrategy,576EditorOption: standaloneEnums.EditorOption,577EndOfLinePreference: standaloneEnums.EndOfLinePreference,578EndOfLineSequence: standaloneEnums.EndOfLineSequence,579MinimapPosition: standaloneEnums.MinimapPosition,580MinimapSectionHeaderStyle: standaloneEnums.MinimapSectionHeaderStyle,581MouseTargetType: standaloneEnums.MouseTargetType,582OverlayWidgetPositionPreference: standaloneEnums.OverlayWidgetPositionPreference,583OverviewRulerLane: standaloneEnums.OverviewRulerLane,584GlyphMarginLane: standaloneEnums.GlyphMarginLane,585RenderLineNumbersType: standaloneEnums.RenderLineNumbersType,586RenderMinimap: standaloneEnums.RenderMinimap,587ScrollbarVisibility: standaloneEnums.ScrollbarVisibility,588ScrollType: standaloneEnums.ScrollType,589TextEditorCursorBlinkingStyle: standaloneEnums.TextEditorCursorBlinkingStyle,590TextEditorCursorStyle: standaloneEnums.TextEditorCursorStyle,591TrackedRangeStickiness: standaloneEnums.TrackedRangeStickiness,592WrappingIndent: standaloneEnums.WrappingIndent,593InjectedTextCursorStops: standaloneEnums.InjectedTextCursorStops,594PositionAffinity: standaloneEnums.PositionAffinity,595ShowLightbulbIconMode: standaloneEnums.ShowLightbulbIconMode,596TextDirection: standaloneEnums.TextDirection,597598// classes599// eslint-disable-next-line local/code-no-any-casts600ConfigurationChangedEvent: <any>ConfigurationChangedEvent,601// eslint-disable-next-line local/code-no-any-casts602BareFontInfo: <any>BareFontInfo,603// eslint-disable-next-line local/code-no-any-casts604FontInfo: <any>FontInfo,605// eslint-disable-next-line local/code-no-any-casts606TextModelResolvedOptions: <any>TextModelResolvedOptions,607// eslint-disable-next-line local/code-no-any-casts608FindMatch: <any>FindMatch,609// eslint-disable-next-line local/code-no-any-casts610ApplyUpdateResult: <any>ApplyUpdateResult,611// eslint-disable-next-line local/code-no-any-casts612EditorZoom: <any>EditorZoom,613614// eslint-disable-next-line local/code-no-any-casts615createMultiFileDiffEditor: <any>createMultiFileDiffEditor,616617// vars618EditorType: EditorType,619// eslint-disable-next-line local/code-no-any-casts620EditorOptions: <any>EditorOptions621622};623}624625626