Path: blob/main/src/vs/editor/standalone/browser/standaloneEditor.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 { 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';4142/**43* Create a new editor under `domElement`.44* `domElement` should be empty (not contain other dom nodes).45* The editor will read the size of `domElement`.46*/47export function create(domElement: HTMLElement, options?: IStandaloneEditorConstructionOptions, override?: IEditorOverrideServices): IStandaloneCodeEditor {48const instantiationService = StandaloneServices.initialize(override || {});49return instantiationService.createInstance(StandaloneEditor, domElement, options);50}5152/**53* Emitted when an editor is created.54* Creating a diff editor might cause this listener to be invoked with the two editors.55* @event56*/57export function onDidCreateEditor(listener: (codeEditor: ICodeEditor) => void): IDisposable {58const codeEditorService = StandaloneServices.get(ICodeEditorService);59return codeEditorService.onCodeEditorAdd((editor) => {60listener(<ICodeEditor>editor);61});62}6364/**65* Emitted when an diff editor is created.66* @event67*/68export function onDidCreateDiffEditor(listener: (diffEditor: IDiffEditor) => void): IDisposable {69const codeEditorService = StandaloneServices.get(ICodeEditorService);70return codeEditorService.onDiffEditorAdd((editor) => {71listener(<IDiffEditor>editor);72});73}7475/**76* Get all the created editors.77*/78export function getEditors(): readonly ICodeEditor[] {79const codeEditorService = StandaloneServices.get(ICodeEditorService);80return codeEditorService.listCodeEditors();81}8283/**84* Get all the created diff editors.85*/86export function getDiffEditors(): readonly IDiffEditor[] {87const codeEditorService = StandaloneServices.get(ICodeEditorService);88return codeEditorService.listDiffEditors();89}9091/**92* Create a new diff editor under `domElement`.93* `domElement` should be empty (not contain other dom nodes).94* The editor will read the size of `domElement`.95*/96export function createDiffEditor(domElement: HTMLElement, options?: IStandaloneDiffEditorConstructionOptions, override?: IEditorOverrideServices): IStandaloneDiffEditor {97const instantiationService = StandaloneServices.initialize(override || {});98return instantiationService.createInstance(StandaloneDiffEditor2, domElement, options);99}100101export function createMultiFileDiffEditor(domElement: HTMLElement, override?: IEditorOverrideServices) {102const instantiationService = StandaloneServices.initialize(override || {});103return new MultiDiffEditorWidget(domElement, {}, instantiationService);104}105106/**107* Description of a command contribution108*/109export interface ICommandDescriptor {110/**111* An unique identifier of the contributed command.112*/113id: string;114/**115* Callback that will be executed when the command is triggered.116*/117run: ICommandHandler;118}119120/**121* Add a command.122*/123export function addCommand(descriptor: ICommandDescriptor): IDisposable {124if ((typeof descriptor.id !== 'string') || (typeof descriptor.run !== 'function')) {125throw new Error('Invalid command descriptor, `id` and `run` are required properties!');126}127return CommandsRegistry.registerCommand(descriptor.id, descriptor.run);128}129130/**131* Add an action to all editors.132*/133export function addEditorAction(descriptor: IActionDescriptor): IDisposable {134if ((typeof descriptor.id !== 'string') || (typeof descriptor.label !== 'string') || (typeof descriptor.run !== 'function')) {135throw new Error('Invalid action descriptor, `id`, `label` and `run` are required properties!');136}137138const precondition = ContextKeyExpr.deserialize(descriptor.precondition);139const run = (accessor: ServicesAccessor, ...args: any[]): void | Promise<void> => {140return EditorCommand.runEditorCommand(accessor, args, precondition, (accessor, editor, args) => Promise.resolve(descriptor.run(editor, ...args)));141};142143const toDispose = new DisposableStore();144145// Register the command146toDispose.add(CommandsRegistry.registerCommand(descriptor.id, run));147148// Register the context menu item149if (descriptor.contextMenuGroupId) {150const menuItem: IMenuItem = {151command: {152id: descriptor.id,153title: descriptor.label154},155when: precondition,156group: descriptor.contextMenuGroupId,157order: descriptor.contextMenuOrder || 0158};159toDispose.add(MenuRegistry.appendMenuItem(MenuId.EditorContext, menuItem));160}161162// Register the keybindings163if (Array.isArray(descriptor.keybindings)) {164const keybindingService = StandaloneServices.get(IKeybindingService);165if (!(keybindingService instanceof StandaloneKeybindingService)) {166console.warn('Cannot add keybinding because the editor is configured with an unrecognized KeybindingService');167} else {168const keybindingsWhen = ContextKeyExpr.and(precondition, ContextKeyExpr.deserialize(descriptor.keybindingContext));169toDispose.add(keybindingService.addDynamicKeybindings(descriptor.keybindings.map((keybinding) => {170return {171keybinding,172command: descriptor.id,173when: keybindingsWhen174};175})));176}177}178179return toDispose;180}181182/**183* A keybinding rule.184*/185export interface IKeybindingRule {186keybinding: number;187command?: string | null;188commandArgs?: any;189when?: string | null;190}191192/**193* Add a keybinding rule.194*/195export function addKeybindingRule(rule: IKeybindingRule): IDisposable {196return addKeybindingRules([rule]);197}198199/**200* Add keybinding rules.201*/202export function addKeybindingRules(rules: IKeybindingRule[]): IDisposable {203const keybindingService = StandaloneServices.get(IKeybindingService);204if (!(keybindingService instanceof StandaloneKeybindingService)) {205console.warn('Cannot add keybinding because the editor is configured with an unrecognized KeybindingService');206return Disposable.None;207}208209return keybindingService.addDynamicKeybindings(rules.map((rule) => {210return {211keybinding: rule.keybinding,212command: rule.command,213commandArgs: rule.commandArgs,214when: ContextKeyExpr.deserialize(rule.when),215};216}));217}218219/**220* Create a new editor model.221* You can specify the language that should be set for this model or let the language be inferred from the `uri`.222*/223export function createModel(value: string, language?: string, uri?: URI): ITextModel {224const languageService = StandaloneServices.get(ILanguageService);225const languageId = languageService.getLanguageIdByMimeType(language) || language;226return createTextModel(227StandaloneServices.get(IModelService),228languageService,229value,230languageId,231uri232);233}234235/**236* Change the language for a model.237*/238export function setModelLanguage(model: ITextModel, mimeTypeOrLanguageId: string): void {239const languageService = StandaloneServices.get(ILanguageService);240const languageId = languageService.getLanguageIdByMimeType(mimeTypeOrLanguageId) || mimeTypeOrLanguageId || PLAINTEXT_LANGUAGE_ID;241model.setLanguage(languageService.createById(languageId));242}243244/**245* Set the markers for a model.246*/247export function setModelMarkers(model: ITextModel, owner: string, markers: IMarkerData[]): void {248if (model) {249const markerService = StandaloneServices.get(IMarkerService);250markerService.changeOne(owner, model.uri, markers);251}252}253254/**255* Remove all markers of an owner.256*/257export function removeAllMarkers(owner: string) {258const markerService = StandaloneServices.get(IMarkerService);259markerService.changeAll(owner, []);260}261262/**263* Get markers for owner and/or resource264*265* @returns list of markers266*/267export function getModelMarkers(filter: { owner?: string; resource?: URI; take?: number }): IMarker[] {268const markerService = StandaloneServices.get(IMarkerService);269return markerService.read(filter);270}271272/**273* Emitted when markers change for a model.274* @event275*/276export function onDidChangeMarkers(listener: (e: readonly URI[]) => void): IDisposable {277const markerService = StandaloneServices.get(IMarkerService);278return markerService.onMarkerChanged(listener);279}280281/**282* Get the model that has `uri` if it exists.283*/284export function getModel(uri: URI): ITextModel | null {285const modelService = StandaloneServices.get(IModelService);286return modelService.getModel(uri);287}288289/**290* Get all the created models.291*/292export function getModels(): ITextModel[] {293const modelService = StandaloneServices.get(IModelService);294return modelService.getModels();295}296297/**298* Emitted when a model is created.299* @event300*/301export function onDidCreateModel(listener: (model: ITextModel) => void): IDisposable {302const modelService = StandaloneServices.get(IModelService);303return modelService.onModelAdded(listener);304}305306/**307* Emitted right before a model is disposed.308* @event309*/310export function onWillDisposeModel(listener: (model: ITextModel) => void): IDisposable {311const modelService = StandaloneServices.get(IModelService);312return modelService.onModelRemoved(listener);313}314315/**316* Emitted when a different language is set to a model.317* @event318*/319export function onDidChangeModelLanguage(listener: (e: { readonly model: ITextModel; readonly oldLanguage: string }) => void): IDisposable {320const modelService = StandaloneServices.get(IModelService);321return modelService.onModelLanguageChanged((e) => {322listener({323model: e.model,324oldLanguage: e.oldLanguageId325});326});327}328329/**330* Create a new web worker that has model syncing capabilities built in.331* Specify an AMD module to load that will `create` an object that will be proxied.332*/333export function createWebWorker<T extends object>(opts: IInternalWebWorkerOptions): MonacoWebWorker<T> {334return actualCreateWebWorker<T>(StandaloneServices.get(IModelService), opts);335}336337/**338* Colorize the contents of `domNode` using attribute `data-lang`.339*/340export function colorizeElement(domNode: HTMLElement, options: IColorizerElementOptions): Promise<void> {341const languageService = StandaloneServices.get(ILanguageService);342const themeService = <StandaloneThemeService>StandaloneServices.get(IStandaloneThemeService);343return Colorizer.colorizeElement(themeService, languageService, domNode, options).then(() => {344themeService.registerEditorContainer(domNode);345});346}347348/**349* Colorize `text` using language `languageId`.350*/351export function colorize(text: string, languageId: string, options: IColorizerOptions): Promise<string> {352const languageService = StandaloneServices.get(ILanguageService);353const themeService = <StandaloneThemeService>StandaloneServices.get(IStandaloneThemeService);354themeService.registerEditorContainer(mainWindow.document.body);355return Colorizer.colorize(languageService, text, languageId, options);356}357358/**359* Colorize a line in a model.360*/361export function colorizeModelLine(model: ITextModel, lineNumber: number, tabSize: number = 4): string {362const themeService = <StandaloneThemeService>StandaloneServices.get(IStandaloneThemeService);363themeService.registerEditorContainer(mainWindow.document.body);364return Colorizer.colorizeModelLine(model, lineNumber, tabSize);365}366367/**368* @internal369*/370function getSafeTokenizationSupport(language: string): Omit<languages.ITokenizationSupport, 'tokenizeEncoded'> {371const tokenizationSupport = languages.TokenizationRegistry.get(language);372if (tokenizationSupport) {373return tokenizationSupport;374}375return {376getInitialState: () => NullState,377tokenize: (line: string, hasEOL: boolean, state: languages.IState) => nullTokenize(language, state)378};379}380381/**382* Tokenize `text` using language `languageId`383*/384export function tokenize(text: string, languageId: string): languages.Token[][] {385// Needed in order to get the mode registered for subsequent look-ups386languages.TokenizationRegistry.getOrCreate(languageId);387388const tokenizationSupport = getSafeTokenizationSupport(languageId);389const lines = splitLines(text);390const result: languages.Token[][] = [];391let state = tokenizationSupport.getInitialState();392for (let i = 0, len = lines.length; i < len; i++) {393const line = lines[i];394const tokenizationResult = tokenizationSupport.tokenize(line, true, state);395396result[i] = tokenizationResult.tokens;397state = tokenizationResult.endState;398}399return result;400}401402/**403* Define a new theme or update an existing theme.404*/405export function defineTheme(themeName: string, themeData: IStandaloneThemeData): void {406const standaloneThemeService = StandaloneServices.get(IStandaloneThemeService);407standaloneThemeService.defineTheme(themeName, themeData);408}409410/**411* Switches to a theme.412*/413export function setTheme(themeName: string): void {414const standaloneThemeService = StandaloneServices.get(IStandaloneThemeService);415standaloneThemeService.setTheme(themeName);416}417418/**419* Clears all cached font measurements and triggers re-measurement.420*/421export function remeasureFonts(): void {422FontMeasurements.clearAllFontInfos();423}424425/**426* Register a command.427*/428export function registerCommand(id: string, handler: (accessor: any, ...args: any[]) => void): IDisposable {429return CommandsRegistry.registerCommand({ id, handler });430}431432export interface ILinkOpener {433open(resource: URI): boolean | Promise<boolean>;434}435436/**437* 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.438* The handler that was registered last will be called first when a link is opened.439*440* Returns a disposable that can unregister the opener again.441*/442export function registerLinkOpener(opener: ILinkOpener): IDisposable {443const openerService = StandaloneServices.get(IOpenerService);444return openerService.registerOpener({445async open(resource: string | URI) {446if (typeof resource === 'string') {447resource = URI.parse(resource);448}449return opener.open(resource);450}451});452}453454/**455* Represents an object that can handle editor open operations (e.g. when "go to definition" is called456* with a resource other than the current model).457*/458export interface ICodeEditorOpener {459/**460* Callback that is invoked when a resource other than the current model should be opened (e.g. when "go to definition" is called).461* The callback should return `true` if the request was handled and `false` otherwise.462* @param source The code editor instance that initiated the request.463* @param resource The URI of the resource that should be opened.464* @param selectionOrPosition An optional position or selection inside the model corresponding to `resource` that can be used to set the cursor.465*/466openCodeEditor(source: ICodeEditor, resource: URI, selectionOrPosition?: IRange | IPosition): boolean | Promise<boolean>;467}468469/**470* 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").471* The handler callback should return `true` if the request was handled and `false` otherwise.472*473* Returns a disposable that can unregister the opener again.474*475* If no handler is registered the default behavior is to do nothing for models other than the currently attached one.476*/477export function registerEditorOpener(opener: ICodeEditorOpener): IDisposable {478const codeEditorService = StandaloneServices.get(ICodeEditorService);479return codeEditorService.registerCodeEditorOpenHandler(async (input: ITextResourceEditorInput, source: ICodeEditor | null, sideBySide?: boolean) => {480if (!source) {481return null;482}483const selection = input.options?.selection;484let selectionOrPosition: IRange | IPosition | undefined;485if (selection && typeof selection.endLineNumber === 'number' && typeof selection.endColumn === 'number') {486selectionOrPosition = <IRange>selection;487} else if (selection) {488selectionOrPosition = { lineNumber: selection.startLineNumber, column: selection.startColumn };489}490if (await opener.openCodeEditor(source, input.resource, selectionOrPosition)) {491return source; // return source editor to indicate that this handler has successfully handled the opening492}493return null; // fallback to other registered handlers494});495}496497/**498* @internal499*/500export function createMonacoEditorAPI(): typeof monaco.editor {501return {502// methods503create: <any>create,504getEditors: <any>getEditors,505getDiffEditors: <any>getDiffEditors,506onDidCreateEditor: <any>onDidCreateEditor,507onDidCreateDiffEditor: <any>onDidCreateDiffEditor,508createDiffEditor: <any>createDiffEditor,509510addCommand: <any>addCommand,511addEditorAction: <any>addEditorAction,512addKeybindingRule: <any>addKeybindingRule,513addKeybindingRules: <any>addKeybindingRules,514515createModel: <any>createModel,516setModelLanguage: <any>setModelLanguage,517setModelMarkers: <any>setModelMarkers,518getModelMarkers: <any>getModelMarkers,519removeAllMarkers: removeAllMarkers,520onDidChangeMarkers: <any>onDidChangeMarkers,521getModels: <any>getModels,522getModel: <any>getModel,523onDidCreateModel: <any>onDidCreateModel,524onWillDisposeModel: <any>onWillDisposeModel,525onDidChangeModelLanguage: <any>onDidChangeModelLanguage,526527528createWebWorker: <any>createWebWorker,529colorizeElement: <any>colorizeElement,530colorize: <any>colorize,531colorizeModelLine: <any>colorizeModelLine,532tokenize: <any>tokenize,533defineTheme: <any>defineTheme,534setTheme: <any>setTheme,535remeasureFonts: remeasureFonts,536registerCommand: registerCommand,537538registerLinkOpener: registerLinkOpener,539registerEditorOpener: <any>registerEditorOpener,540541// enums542AccessibilitySupport: standaloneEnums.AccessibilitySupport,543ContentWidgetPositionPreference: standaloneEnums.ContentWidgetPositionPreference,544CursorChangeReason: standaloneEnums.CursorChangeReason,545DefaultEndOfLine: standaloneEnums.DefaultEndOfLine,546EditorAutoIndentStrategy: standaloneEnums.EditorAutoIndentStrategy,547EditorOption: standaloneEnums.EditorOption,548EndOfLinePreference: standaloneEnums.EndOfLinePreference,549EndOfLineSequence: standaloneEnums.EndOfLineSequence,550MinimapPosition: standaloneEnums.MinimapPosition,551MinimapSectionHeaderStyle: standaloneEnums.MinimapSectionHeaderStyle,552MouseTargetType: standaloneEnums.MouseTargetType,553OverlayWidgetPositionPreference: standaloneEnums.OverlayWidgetPositionPreference,554OverviewRulerLane: standaloneEnums.OverviewRulerLane,555GlyphMarginLane: standaloneEnums.GlyphMarginLane,556RenderLineNumbersType: standaloneEnums.RenderLineNumbersType,557RenderMinimap: standaloneEnums.RenderMinimap,558ScrollbarVisibility: standaloneEnums.ScrollbarVisibility,559ScrollType: standaloneEnums.ScrollType,560TextEditorCursorBlinkingStyle: standaloneEnums.TextEditorCursorBlinkingStyle,561TextEditorCursorStyle: standaloneEnums.TextEditorCursorStyle,562TrackedRangeStickiness: standaloneEnums.TrackedRangeStickiness,563WrappingIndent: standaloneEnums.WrappingIndent,564InjectedTextCursorStops: standaloneEnums.InjectedTextCursorStops,565PositionAffinity: standaloneEnums.PositionAffinity,566ShowLightbulbIconMode: standaloneEnums.ShowLightbulbIconMode,567TextDirection: standaloneEnums.TextDirection,568569// classes570ConfigurationChangedEvent: <any>ConfigurationChangedEvent,571BareFontInfo: <any>BareFontInfo,572FontInfo: <any>FontInfo,573TextModelResolvedOptions: <any>TextModelResolvedOptions,574FindMatch: <any>FindMatch,575ApplyUpdateResult: <any>ApplyUpdateResult,576EditorZoom: <any>EditorZoom,577578createMultiFileDiffEditor: <any>createMultiFileDiffEditor,579580// vars581EditorType: EditorType,582EditorOptions: <any>EditorOptions583584};585}586587588