monaco-editor-core
Version:
A browser based code editor
479 lines (478 loc) • 19.7 kB
JavaScript
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import { mainWindow } from '../../../base/browser/window.js';
import { Disposable, DisposableStore } from '../../../base/common/lifecycle.js';
import { splitLines } from '../../../base/common/strings.js';
import { URI } from '../../../base/common/uri.js';
import './standalone-tokens.css';
import { FontMeasurements } from '../../browser/config/fontMeasurements.js';
import { EditorCommand } from '../../browser/editorExtensions.js';
import { ICodeEditorService } from '../../browser/services/codeEditorService.js';
import { createWebWorker as actualCreateWebWorker } from './standaloneWebWorker.js';
import { ApplyUpdateResult, ConfigurationChangedEvent, EditorOptions } from '../../common/config/editorOptions.js';
import { EditorZoom } from '../../common/config/editorZoom.js';
import { BareFontInfo, FontInfo } from '../../common/config/fontInfo.js';
import { EditorType } from '../../common/editorCommon.js';
import * as languages from '../../common/languages.js';
import { ILanguageService } from '../../common/languages/language.js';
import { PLAINTEXT_LANGUAGE_ID } from '../../common/languages/modesRegistry.js';
import { NullState, nullTokenize } from '../../common/languages/nullTokenize.js';
import { FindMatch, TextModelResolvedOptions } from '../../common/model.js';
import { IModelService } from '../../common/services/model.js';
import * as standaloneEnums from '../../common/standalone/standaloneEnums.js';
import { Colorizer } from './colorizer.js';
import { StandaloneDiffEditor2, StandaloneEditor, createTextModel } from './standaloneCodeEditor.js';
import { StandaloneKeybindingService, StandaloneServices } from './standaloneServices.js';
import { IStandaloneThemeService } from '../common/standaloneTheme.js';
import { MenuId, MenuRegistry } from '../../../platform/actions/common/actions.js';
import { CommandsRegistry } from '../../../platform/commands/common/commands.js';
import { ContextKeyExpr } from '../../../platform/contextkey/common/contextkey.js';
import { IKeybindingService } from '../../../platform/keybinding/common/keybinding.js';
import { IMarkerService } from '../../../platform/markers/common/markers.js';
import { IOpenerService } from '../../../platform/opener/common/opener.js';
import { MultiDiffEditorWidget } from '../../browser/widget/multiDiffEditor/multiDiffEditorWidget.js';
/**
* Create a new editor under `domElement`.
* `domElement` should be empty (not contain other dom nodes).
* The editor will read the size of `domElement`.
*/
export function create(domElement, options, override) {
const instantiationService = StandaloneServices.initialize(override || {});
return instantiationService.createInstance(StandaloneEditor, domElement, options);
}
/**
* Emitted when an editor is created.
* Creating a diff editor might cause this listener to be invoked with the two editors.
* @event
*/
export function onDidCreateEditor(listener) {
const codeEditorService = StandaloneServices.get(ICodeEditorService);
return codeEditorService.onCodeEditorAdd((editor) => {
listener(editor);
});
}
/**
* Emitted when an diff editor is created.
* @event
*/
export function onDidCreateDiffEditor(listener) {
const codeEditorService = StandaloneServices.get(ICodeEditorService);
return codeEditorService.onDiffEditorAdd((editor) => {
listener(editor);
});
}
/**
* Get all the created editors.
*/
export function getEditors() {
const codeEditorService = StandaloneServices.get(ICodeEditorService);
return codeEditorService.listCodeEditors();
}
/**
* Get all the created diff editors.
*/
export function getDiffEditors() {
const codeEditorService = StandaloneServices.get(ICodeEditorService);
return codeEditorService.listDiffEditors();
}
/**
* Create a new diff editor under `domElement`.
* `domElement` should be empty (not contain other dom nodes).
* The editor will read the size of `domElement`.
*/
export function createDiffEditor(domElement, options, override) {
const instantiationService = StandaloneServices.initialize(override || {});
return instantiationService.createInstance(StandaloneDiffEditor2, domElement, options);
}
export function createMultiFileDiffEditor(domElement, override) {
const instantiationService = StandaloneServices.initialize(override || {});
return new MultiDiffEditorWidget(domElement, {}, instantiationService);
}
/**
* Add a command.
*/
export function addCommand(descriptor) {
if ((typeof descriptor.id !== 'string') || (typeof descriptor.run !== 'function')) {
throw new Error('Invalid command descriptor, `id` and `run` are required properties!');
}
return CommandsRegistry.registerCommand(descriptor.id, descriptor.run);
}
/**
* Add an action to all editors.
*/
export function addEditorAction(descriptor) {
if ((typeof descriptor.id !== 'string') || (typeof descriptor.label !== 'string') || (typeof descriptor.run !== 'function')) {
throw new Error('Invalid action descriptor, `id`, `label` and `run` are required properties!');
}
const precondition = ContextKeyExpr.deserialize(descriptor.precondition);
const run = (accessor, ...args) => {
return EditorCommand.runEditorCommand(accessor, args, precondition, (accessor, editor, args) => Promise.resolve(descriptor.run(editor, ...args)));
};
const toDispose = new DisposableStore();
// Register the command
toDispose.add(CommandsRegistry.registerCommand(descriptor.id, run));
// Register the context menu item
if (descriptor.contextMenuGroupId) {
const menuItem = {
command: {
id: descriptor.id,
title: descriptor.label
},
when: precondition,
group: descriptor.contextMenuGroupId,
order: descriptor.contextMenuOrder || 0
};
toDispose.add(MenuRegistry.appendMenuItem(MenuId.EditorContext, menuItem));
}
// Register the keybindings
if (Array.isArray(descriptor.keybindings)) {
const keybindingService = StandaloneServices.get(IKeybindingService);
if (!(keybindingService instanceof StandaloneKeybindingService)) {
console.warn('Cannot add keybinding because the editor is configured with an unrecognized KeybindingService');
}
else {
const keybindingsWhen = ContextKeyExpr.and(precondition, ContextKeyExpr.deserialize(descriptor.keybindingContext));
toDispose.add(keybindingService.addDynamicKeybindings(descriptor.keybindings.map((keybinding) => {
return {
keybinding,
command: descriptor.id,
when: keybindingsWhen
};
})));
}
}
return toDispose;
}
/**
* Add a keybinding rule.
*/
export function addKeybindingRule(rule) {
return addKeybindingRules([rule]);
}
/**
* Add keybinding rules.
*/
export function addKeybindingRules(rules) {
const keybindingService = StandaloneServices.get(IKeybindingService);
if (!(keybindingService instanceof StandaloneKeybindingService)) {
console.warn('Cannot add keybinding because the editor is configured with an unrecognized KeybindingService');
return Disposable.None;
}
return keybindingService.addDynamicKeybindings(rules.map((rule) => {
return {
keybinding: rule.keybinding,
command: rule.command,
commandArgs: rule.commandArgs,
when: ContextKeyExpr.deserialize(rule.when),
};
}));
}
/**
* Create a new editor model.
* You can specify the language that should be set for this model or let the language be inferred from the `uri`.
*/
export function createModel(value, language, uri) {
const languageService = StandaloneServices.get(ILanguageService);
const languageId = languageService.getLanguageIdByMimeType(language) || language;
return createTextModel(StandaloneServices.get(IModelService), languageService, value, languageId, uri);
}
/**
* Change the language for a model.
*/
export function setModelLanguage(model, mimeTypeOrLanguageId) {
const languageService = StandaloneServices.get(ILanguageService);
const languageId = languageService.getLanguageIdByMimeType(mimeTypeOrLanguageId) || mimeTypeOrLanguageId || PLAINTEXT_LANGUAGE_ID;
model.setLanguage(languageService.createById(languageId));
}
/**
* Set the markers for a model.
*/
export function setModelMarkers(model, owner, markers) {
if (model) {
const markerService = StandaloneServices.get(IMarkerService);
markerService.changeOne(owner, model.uri, markers);
}
}
/**
* Remove all markers of an owner.
*/
export function removeAllMarkers(owner) {
const markerService = StandaloneServices.get(IMarkerService);
markerService.changeAll(owner, []);
}
/**
* Get markers for owner and/or resource
*
* @returns list of markers
*/
export function getModelMarkers(filter) {
const markerService = StandaloneServices.get(IMarkerService);
return markerService.read(filter);
}
/**
* Emitted when markers change for a model.
* @event
*/
export function onDidChangeMarkers(listener) {
const markerService = StandaloneServices.get(IMarkerService);
return markerService.onMarkerChanged(listener);
}
/**
* Get the model that has `uri` if it exists.
*/
export function getModel(uri) {
const modelService = StandaloneServices.get(IModelService);
return modelService.getModel(uri);
}
/**
* Get all the created models.
*/
export function getModels() {
const modelService = StandaloneServices.get(IModelService);
return modelService.getModels();
}
/**
* Emitted when a model is created.
* @event
*/
export function onDidCreateModel(listener) {
const modelService = StandaloneServices.get(IModelService);
return modelService.onModelAdded(listener);
}
/**
* Emitted right before a model is disposed.
* @event
*/
export function onWillDisposeModel(listener) {
const modelService = StandaloneServices.get(IModelService);
return modelService.onModelRemoved(listener);
}
/**
* Emitted when a different language is set to a model.
* @event
*/
export function onDidChangeModelLanguage(listener) {
const modelService = StandaloneServices.get(IModelService);
return modelService.onModelLanguageChanged((e) => {
listener({
model: e.model,
oldLanguage: e.oldLanguageId
});
});
}
/**
* Create a new web worker that has model syncing capabilities built in.
* Specify an AMD module to load that will `create` an object that will be proxied.
*/
export function createWebWorker(opts) {
return actualCreateWebWorker(StandaloneServices.get(IModelService), opts);
}
/**
* Colorize the contents of `domNode` using attribute `data-lang`.
*/
export function colorizeElement(domNode, options) {
const languageService = StandaloneServices.get(ILanguageService);
const themeService = StandaloneServices.get(IStandaloneThemeService);
return Colorizer.colorizeElement(themeService, languageService, domNode, options).then(() => {
themeService.registerEditorContainer(domNode);
});
}
/**
* Colorize `text` using language `languageId`.
*/
export function colorize(text, languageId, options) {
const languageService = StandaloneServices.get(ILanguageService);
const themeService = StandaloneServices.get(IStandaloneThemeService);
themeService.registerEditorContainer(mainWindow.document.body);
return Colorizer.colorize(languageService, text, languageId, options);
}
/**
* Colorize a line in a model.
*/
export function colorizeModelLine(model, lineNumber, tabSize = 4) {
const themeService = StandaloneServices.get(IStandaloneThemeService);
themeService.registerEditorContainer(mainWindow.document.body);
return Colorizer.colorizeModelLine(model, lineNumber, tabSize);
}
/**
* @internal
*/
function getSafeTokenizationSupport(language) {
const tokenizationSupport = languages.TokenizationRegistry.get(language);
if (tokenizationSupport) {
return tokenizationSupport;
}
return {
getInitialState: () => NullState,
tokenize: (line, hasEOL, state) => nullTokenize(language, state)
};
}
/**
* Tokenize `text` using language `languageId`
*/
export function tokenize(text, languageId) {
// Needed in order to get the mode registered for subsequent look-ups
languages.TokenizationRegistry.getOrCreate(languageId);
const tokenizationSupport = getSafeTokenizationSupport(languageId);
const lines = splitLines(text);
const result = [];
let state = tokenizationSupport.getInitialState();
for (let i = 0, len = lines.length; i < len; i++) {
const line = lines[i];
const tokenizationResult = tokenizationSupport.tokenize(line, true, state);
result[i] = tokenizationResult.tokens;
state = tokenizationResult.endState;
}
return result;
}
/**
* Define a new theme or update an existing theme.
*/
export function defineTheme(themeName, themeData) {
const standaloneThemeService = StandaloneServices.get(IStandaloneThemeService);
standaloneThemeService.defineTheme(themeName, themeData);
}
/**
* Switches to a theme.
*/
export function setTheme(themeName) {
const standaloneThemeService = StandaloneServices.get(IStandaloneThemeService);
standaloneThemeService.setTheme(themeName);
}
/**
* Clears all cached font measurements and triggers re-measurement.
*/
export function remeasureFonts() {
FontMeasurements.clearAllFontInfos();
}
/**
* Register a command.
*/
export function registerCommand(id, handler) {
return CommandsRegistry.registerCommand({ id, handler });
}
/**
* 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.
* The handler that was registered last will be called first when a link is opened.
*
* Returns a disposable that can unregister the opener again.
*/
export function registerLinkOpener(opener) {
const openerService = StandaloneServices.get(IOpenerService);
return openerService.registerOpener({
async open(resource) {
if (typeof resource === 'string') {
resource = URI.parse(resource);
}
return opener.open(resource);
}
});
}
/**
* 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").
* The handler callback should return `true` if the request was handled and `false` otherwise.
*
* Returns a disposable that can unregister the opener again.
*
* If no handler is registered the default behavior is to do nothing for models other than the currently attached one.
*/
export function registerEditorOpener(opener) {
const codeEditorService = StandaloneServices.get(ICodeEditorService);
return codeEditorService.registerCodeEditorOpenHandler(async (input, source, sideBySide) => {
if (!source) {
return null;
}
const selection = input.options?.selection;
let selectionOrPosition;
if (selection && typeof selection.endLineNumber === 'number' && typeof selection.endColumn === 'number') {
selectionOrPosition = selection;
}
else if (selection) {
selectionOrPosition = { lineNumber: selection.startLineNumber, column: selection.startColumn };
}
if (await opener.openCodeEditor(source, input.resource, selectionOrPosition)) {
return source; // return source editor to indicate that this handler has successfully handled the opening
}
return null; // fallback to other registered handlers
});
}
/**
* @internal
*/
export function createMonacoEditorAPI() {
return {
// methods
create: create,
getEditors: getEditors,
getDiffEditors: getDiffEditors,
onDidCreateEditor: onDidCreateEditor,
onDidCreateDiffEditor: onDidCreateDiffEditor,
createDiffEditor: createDiffEditor,
addCommand: addCommand,
addEditorAction: addEditorAction,
addKeybindingRule: addKeybindingRule,
addKeybindingRules: addKeybindingRules,
createModel: createModel,
setModelLanguage: setModelLanguage,
setModelMarkers: setModelMarkers,
getModelMarkers: getModelMarkers,
removeAllMarkers: removeAllMarkers,
onDidChangeMarkers: onDidChangeMarkers,
getModels: getModels,
getModel: getModel,
onDidCreateModel: onDidCreateModel,
onWillDisposeModel: onWillDisposeModel,
onDidChangeModelLanguage: onDidChangeModelLanguage,
createWebWorker: createWebWorker,
colorizeElement: colorizeElement,
colorize: colorize,
colorizeModelLine: colorizeModelLine,
tokenize: tokenize,
defineTheme: defineTheme,
setTheme: setTheme,
remeasureFonts: remeasureFonts,
registerCommand: registerCommand,
registerLinkOpener: registerLinkOpener,
registerEditorOpener: registerEditorOpener,
// enums
AccessibilitySupport: standaloneEnums.AccessibilitySupport,
ContentWidgetPositionPreference: standaloneEnums.ContentWidgetPositionPreference,
CursorChangeReason: standaloneEnums.CursorChangeReason,
DefaultEndOfLine: standaloneEnums.DefaultEndOfLine,
EditorAutoIndentStrategy: standaloneEnums.EditorAutoIndentStrategy,
EditorOption: standaloneEnums.EditorOption,
EndOfLinePreference: standaloneEnums.EndOfLinePreference,
EndOfLineSequence: standaloneEnums.EndOfLineSequence,
MinimapPosition: standaloneEnums.MinimapPosition,
MinimapSectionHeaderStyle: standaloneEnums.MinimapSectionHeaderStyle,
MouseTargetType: standaloneEnums.MouseTargetType,
OverlayWidgetPositionPreference: standaloneEnums.OverlayWidgetPositionPreference,
OverviewRulerLane: standaloneEnums.OverviewRulerLane,
GlyphMarginLane: standaloneEnums.GlyphMarginLane,
RenderLineNumbersType: standaloneEnums.RenderLineNumbersType,
RenderMinimap: standaloneEnums.RenderMinimap,
ScrollbarVisibility: standaloneEnums.ScrollbarVisibility,
ScrollType: standaloneEnums.ScrollType,
TextEditorCursorBlinkingStyle: standaloneEnums.TextEditorCursorBlinkingStyle,
TextEditorCursorStyle: standaloneEnums.TextEditorCursorStyle,
TrackedRangeStickiness: standaloneEnums.TrackedRangeStickiness,
WrappingIndent: standaloneEnums.WrappingIndent,
InjectedTextCursorStops: standaloneEnums.InjectedTextCursorStops,
PositionAffinity: standaloneEnums.PositionAffinity,
ShowLightbulbIconMode: standaloneEnums.ShowLightbulbIconMode,
// classes
ConfigurationChangedEvent: ConfigurationChangedEvent,
BareFontInfo: BareFontInfo,
FontInfo: FontInfo,
TextModelResolvedOptions: TextModelResolvedOptions,
FindMatch: FindMatch,
ApplyUpdateResult: ApplyUpdateResult,
EditorZoom: EditorZoom,
createMultiFileDiffEditor: createMultiFileDiffEditor,
// vars
EditorType: EditorType,
EditorOptions: EditorOptions
};
}