Path: blob/main/src/vs/editor/test/browser/testCodeEditor.ts
5222 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 { DisposableStore, IDisposable, toDisposable } from '../../../base/common/lifecycle.js';6import { mock } from '../../../base/test/common/mock.js';7import { EditorConfiguration } from '../../browser/config/editorConfiguration.js';8import { IActiveCodeEditor, ICodeEditor } from '../../browser/editorBrowser.js';9import { ICodeEditorService } from '../../browser/services/codeEditorService.js';10import { View } from '../../browser/view.js';11import { CodeEditorWidget, ICodeEditorWidgetOptions } from '../../browser/widget/codeEditor/codeEditorWidget.js';12import * as editorOptions from '../../common/config/editorOptions.js';13import { IEditorContribution } from '../../common/editorCommon.js';14import { ILanguageService } from '../../common/languages/language.js';15import { ILanguageConfigurationService } from '../../common/languages/languageConfigurationRegistry.js';16import { ITextBufferFactory, ITextModel } from '../../common/model.js';17import { IEditorWorkerService } from '../../common/services/editorWorker.js';18import { ILanguageFeatureDebounceService, LanguageFeatureDebounceService } from '../../common/services/languageFeatureDebounce.js';19import { ILanguageFeaturesService } from '../../common/services/languageFeatures.js';20import { LanguageFeaturesService } from '../../common/services/languageFeaturesService.js';21import { LanguageService } from '../../common/services/languageService.js';22import { IModelService } from '../../common/services/model.js';23import { ModelService } from '../../common/services/modelService.js';24import { ITextResourcePropertiesService } from '../../common/services/textResourceConfiguration.js';25import { ViewModel } from '../../common/viewModel/viewModelImpl.js';26import { TestConfiguration } from './config/testConfiguration.js';27import { TestCodeEditorService, TestCommandService } from './editorTestServices.js';28import { TestLanguageConfigurationService } from '../common/modes/testLanguageConfigurationService.js';29import { TestEditorWorkerService } from '../common/services/testEditorWorkerService.js';30import { TestTextResourcePropertiesService } from '../common/services/testTextResourcePropertiesService.js';31import { instantiateTextModel } from '../common/testTextModel.js';32import { AccessibilitySupport, IAccessibilityService } from '../../../platform/accessibility/common/accessibility.js';33import { TestAccessibilityService } from '../../../platform/accessibility/test/common/testAccessibilityService.js';34import { MenuId } from '../../../platform/actions/common/actions.js';35import { IClipboardService } from '../../../platform/clipboard/common/clipboardService.js';36import { TestClipboardService } from '../../../platform/clipboard/test/common/testClipboardService.js';37import { ICommandService } from '../../../platform/commands/common/commands.js';38import { IConfigurationService } from '../../../platform/configuration/common/configuration.js';39import { TestConfigurationService } from '../../../platform/configuration/test/common/testConfigurationService.js';40import { IContextKeyService, IContextKeyServiceTarget } from '../../../platform/contextkey/common/contextkey.js';41import { IDialogService } from '../../../platform/dialogs/common/dialogs.js';42import { TestDialogService } from '../../../platform/dialogs/test/common/testDialogService.js';43import { IEnvironmentService } from '../../../platform/environment/common/environment.js';44import { SyncDescriptor } from '../../../platform/instantiation/common/descriptors.js';45import { BrandedService, IInstantiationService, ServiceIdentifier, ServicesAccessor } from '../../../platform/instantiation/common/instantiation.js';46import { ServiceCollection } from '../../../platform/instantiation/common/serviceCollection.js';47import { TestInstantiationService } from '../../../platform/instantiation/test/common/instantiationServiceMock.js';48import { IKeybindingService } from '../../../platform/keybinding/common/keybinding.js';49import { MockContextKeyService, MockKeybindingService } from '../../../platform/keybinding/test/common/mockKeybindingService.js';50import { ILoggerService, ILogService, NullLoggerService, NullLogService } from '../../../platform/log/common/log.js';51import { INotificationService } from '../../../platform/notification/common/notification.js';52import { TestNotificationService } from '../../../platform/notification/test/common/testNotificationService.js';53import { IOpenerService } from '../../../platform/opener/common/opener.js';54import { NullOpenerService } from '../../../platform/opener/test/common/nullOpenerService.js';55import { ITelemetryService } from '../../../platform/telemetry/common/telemetry.js';56import { NullTelemetryServiceShape } from '../../../platform/telemetry/common/telemetryUtils.js';57import { IThemeService } from '../../../platform/theme/common/themeService.js';58import { TestThemeService } from '../../../platform/theme/test/common/testThemeService.js';59import { IUndoRedoService } from '../../../platform/undoRedo/common/undoRedo.js';60import { UndoRedoService } from '../../../platform/undoRedo/common/undoRedoService.js';61import { ITreeSitterLibraryService } from '../../common/services/treeSitter/treeSitterLibraryService.js';62import { TestTreeSitterLibraryService } from '../common/services/testTreeSitterLibraryService.js';63import { IInlineCompletionsService, InlineCompletionsService } from '../../browser/services/inlineCompletionsService.js';64import { EditorCommand } from '../../browser/editorExtensions.js';65import { IDataChannelService, NullDataChannelService } from '../../../platform/dataChannel/common/dataChannel.js';66import { IUserInteractionService, MockUserInteractionService } from '../../../platform/userInteraction/browser/userInteractionService.js';6768export interface ITestCodeEditor extends IActiveCodeEditor {69getViewModel(): ViewModel | undefined;70registerAndInstantiateContribution<T extends IEditorContribution, Services extends BrandedService[]>(id: string, ctor: new (editor: ICodeEditor, ...services: Services) => T): T;71registerDisposable(disposable: IDisposable): void;72runCommand(command: ITestEditorCommand, args?: any): void | Promise<void>;73runAction(action: ITestEditorAction, args?: any): void | Promise<void>;74}7576export interface ITestEditorCommand {77runEditorCommand(accessor: ServicesAccessor, editor: ICodeEditor, args?: any): void | Promise<void>;78}7980export interface ITestEditorAction {81run(accessor: ServicesAccessor, editor: ICodeEditor, args?: any): void | Promise<void>;82}8384export class TestCodeEditor extends CodeEditorWidget implements ICodeEditor {8586//#region testing overrides87protected override _createConfiguration(isSimpleWidget: boolean, contextMenuId: MenuId, options: Readonly<TestCodeEditorCreationOptions>): EditorConfiguration {88return new TestConfiguration(options);89}90protected override _createView(viewModel: ViewModel): [View, boolean] {91// Never create a view92return [null! as View, false];93}94private _hasTextFocus = false;95public setHasTextFocus(hasTextFocus: boolean): void {96this._hasTextFocus = hasTextFocus;97}98public override hasTextFocus(): boolean {99return this._hasTextFocus;100}101//#endregion102103//#region Testing utils104public getViewModel(): ViewModel | undefined {105return this._modelData ? this._modelData.viewModel : undefined;106}107public registerAndInstantiateContribution<T extends IEditorContribution>(id: string, ctor: new (editor: ICodeEditor, ...services: BrandedService[]) => T): T {108const r: T = this._instantiationService.createInstance(ctor, this);109this._contributions.set(id, r);110return r;111}112public registerDisposable(disposable: IDisposable): void {113this._register(disposable);114}115public runCommand(command: EditorCommand, args?: any): void | Promise<void> {116return this._instantiationService.invokeFunction((accessor) => {117return command.runEditorCommand(accessor, this, args);118});119}120public runAction(action: ITestEditorAction, args?: any): void | Promise<void> {121return this._instantiationService.invokeFunction((accessor) => {122return action.run(accessor, this, args);123});124}125}126127class TestEditorDomElement {128parentElement: IContextKeyServiceTarget | null = null;129ownerDocument = document;130document = document;131setAttribute(attr: string, value: string): void { }132removeAttribute(attr: string): void { }133hasAttribute(attr: string): boolean { return false; }134getAttribute(attr: string): string | undefined { return undefined; }135addEventListener(event: string): void { }136removeEventListener(event: string): void { }137}138139export interface TestCodeEditorCreationOptions extends editorOptions.IEditorOptions {140/**141* If the editor has text focus.142* Defaults to true.143*/144hasTextFocus?: boolean;145/**146* Env configuration147*/148envConfig?: ITestEnvConfiguration;149}150151export interface TestCodeEditorInstantiationOptions extends TestCodeEditorCreationOptions {152/**153* Services to use.154*/155serviceCollection?: ServiceCollection;156}157158export interface ITestEnvConfiguration {159extraEditorClassName?: string;160outerWidth?: number;161outerHeight?: number;162emptySelectionClipboard?: boolean;163pixelRatio?: number;164accessibilitySupport?: AccessibilitySupport;165}166167export function withTestCodeEditor(text: ITextModel | string | string[] | ITextBufferFactory, options: TestCodeEditorInstantiationOptions, callback: (editor: ITestCodeEditor, viewModel: ViewModel, instantiationService: TestInstantiationService) => void): void {168return _withTestCodeEditor(text, options, callback);169}170171export async function withAsyncTestCodeEditor(text: ITextModel | string | string[] | ITextBufferFactory, options: TestCodeEditorInstantiationOptions, callback: (editor: ITestCodeEditor, viewModel: ViewModel, instantiationService: TestInstantiationService) => Promise<void>): Promise<void> {172return _withTestCodeEditor(text, options, callback);173}174175function isTextModel(arg: ITextModel | string | string[] | ITextBufferFactory): arg is ITextModel {176return Boolean(arg && (arg as ITextModel).uri);177}178179function _withTestCodeEditor(arg: ITextModel | string | string[] | ITextBufferFactory, options: TestCodeEditorInstantiationOptions, callback: (editor: ITestCodeEditor, viewModel: ViewModel, instantiationService: TestInstantiationService) => void): void;180function _withTestCodeEditor(arg: ITextModel | string | string[] | ITextBufferFactory, options: TestCodeEditorInstantiationOptions, callback: (editor: ITestCodeEditor, viewModel: ViewModel, instantiationService: TestInstantiationService) => Promise<void>): Promise<void>;181function _withTestCodeEditor(arg: ITextModel | string | string[] | ITextBufferFactory, options: TestCodeEditorInstantiationOptions, callback: (editor: ITestCodeEditor, viewModel: ViewModel, instantiationService: TestInstantiationService) => Promise<void> | void): Promise<void> | void {182const disposables = new DisposableStore();183const instantiationService = createCodeEditorServices(disposables, options.serviceCollection);184delete options.serviceCollection;185186// create a model if necessary187let model: ITextModel;188if (isTextModel(arg)) {189model = arg;190} else {191model = disposables.add(instantiateTextModel(instantiationService, Array.isArray(arg) ? arg.join('\n') : arg));192}193194const editor = disposables.add(instantiateTestCodeEditor(instantiationService, model, options));195const viewModel = editor.getViewModel()!;196viewModel.setHasFocus(true);197const result = callback(editor, editor.getViewModel()!, instantiationService);198if (result) {199return result.then(() => disposables.dispose());200}201202disposables.dispose();203}204205export function createCodeEditorServices(disposables: Pick<DisposableStore, 'add'>, services: ServiceCollection = new ServiceCollection()): TestInstantiationService {206const serviceIdentifiers: ServiceIdentifier<any>[] = [];207const define = <T>(id: ServiceIdentifier<T>, ctor: new (...args: any[]) => T) => {208if (!services.has(id)) {209services.set(id, new SyncDescriptor(ctor));210}211serviceIdentifiers.push(id);212};213const defineInstance = <T>(id: ServiceIdentifier<T>, instance: T) => {214if (!services.has(id)) {215services.set(id, instance);216}217serviceIdentifiers.push(id);218};219220define(IAccessibilityService, TestAccessibilityService);221define(IKeybindingService, MockKeybindingService);222define(IClipboardService, TestClipboardService);223define(IEditorWorkerService, TestEditorWorkerService);224defineInstance(IOpenerService, NullOpenerService);225define(INotificationService, TestNotificationService);226define(IDialogService, TestDialogService);227define(IUndoRedoService, UndoRedoService);228define(ILanguageService, LanguageService);229define(ILanguageConfigurationService, TestLanguageConfigurationService);230define(IConfigurationService, TestConfigurationService);231define(ITextResourcePropertiesService, TestTextResourcePropertiesService);232define(IThemeService, TestThemeService);233define(ILogService, NullLogService);234define(IModelService, ModelService);235define(ICodeEditorService, TestCodeEditorService);236define(IContextKeyService, MockContextKeyService);237define(ICommandService, TestCommandService);238define(ITelemetryService, NullTelemetryServiceShape);239define(ILoggerService, NullLoggerService);240define(IDataChannelService, NullDataChannelService);241define(IEnvironmentService, class extends mock<IEnvironmentService>() {242declare readonly _serviceBrand: undefined;243override isBuilt: boolean = true;244override isExtensionDevelopment: boolean = false;245});246define(ILanguageFeatureDebounceService, LanguageFeatureDebounceService);247define(ILanguageFeaturesService, LanguageFeaturesService);248define(ITreeSitterLibraryService, TestTreeSitterLibraryService);249define(IInlineCompletionsService, InlineCompletionsService);250define(IUserInteractionService, MockUserInteractionService);251252const instantiationService = disposables.add(new TestInstantiationService(services, true));253disposables.add(toDisposable(() => {254for (const id of serviceIdentifiers) {255const instanceOrDescriptor = services.get(id);256if (typeof instanceOrDescriptor.dispose === 'function') {257instanceOrDescriptor.dispose();258}259}260}));261return instantiationService;262}263264export function createTestCodeEditor(model: ITextModel | undefined, options: TestCodeEditorInstantiationOptions = {}): ITestCodeEditor {265const disposables = new DisposableStore();266const instantiationService = createCodeEditorServices(disposables, options.serviceCollection);267delete options.serviceCollection;268269const editor = instantiateTestCodeEditor(instantiationService, model || null, options);270editor.registerDisposable(disposables);271return editor;272}273274export function instantiateTestCodeEditor(instantiationService: IInstantiationService, model: ITextModel | null, options: TestCodeEditorCreationOptions = {}): ITestCodeEditor {275const codeEditorWidgetOptions: ICodeEditorWidgetOptions = {276contributions: []277};278const editor = instantiationService.createInstance(279TestCodeEditor,280// eslint-disable-next-line local/code-no-any-casts281<HTMLElement><any>new TestEditorDomElement(),282options,283codeEditorWidgetOptions284);285if (typeof options.hasTextFocus === 'undefined') {286options.hasTextFocus = true;287}288editor.setHasTextFocus(options.hasTextFocus);289editor.setModel(model);290const viewModel = editor.getViewModel();291viewModel?.setHasFocus(options.hasTextFocus);292return <ITestCodeEditor>editor;293}294295296