Path: blob/main/src/vs/workbench/contrib/debug/common/debugContentProvider.ts
3296 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 { URI as uri } from '../../../../base/common/uri.js';6import { localize } from '../../../../nls.js';7import { getMimeTypes } from '../../../../editor/common/services/languagesAssociations.js';8import { ITextModel } from '../../../../editor/common/model.js';9import { IModelService } from '../../../../editor/common/services/model.js';10import { ILanguageService } from '../../../../editor/common/languages/language.js';11import { ITextModelService, ITextModelContentProvider } from '../../../../editor/common/services/resolverService.js';12import { IWorkbenchContribution } from '../../../common/contributions.js';13import { DEBUG_SCHEME, IDebugService, IDebugSession } from './debug.js';14import { Source } from './debugSource.js';15import { IEditorWorkerService } from '../../../../editor/common/services/editorWorker.js';16import { EditOperation } from '../../../../editor/common/core/editOperation.js';17import { Range } from '../../../../editor/common/core/range.js';18import { CancellationTokenSource } from '../../../../base/common/cancellation.js';19import { PLAINTEXT_LANGUAGE_ID } from '../../../../editor/common/languages/modesRegistry.js';20import { ErrorNoTelemetry } from '../../../../base/common/errors.js';21import { Disposable } from '../../../../base/common/lifecycle.js';2223/**24* Debug URI format25*26* a debug URI represents a Source object and the debug session where the Source comes from.27*28* debug:arbitrary_path?session=123e4567-e89b-12d3-a456-426655440000&ref=101629* \___/ \____________/ \__________________________________________/ \______/30* | | | |31* scheme source.path session id source.reference32*33* the arbitrary_path and the session id are encoded with 'encodeURIComponent'34*35*/36export class DebugContentProvider extends Disposable implements IWorkbenchContribution, ITextModelContentProvider {3738private static INSTANCE: DebugContentProvider;3940private readonly pendingUpdates = new Map<string, CancellationTokenSource>();4142constructor(43@ITextModelService textModelResolverService: ITextModelService,44@IDebugService private readonly debugService: IDebugService,45@IModelService private readonly modelService: IModelService,46@ILanguageService private readonly languageService: ILanguageService,47@IEditorWorkerService private readonly editorWorkerService: IEditorWorkerService48) {49super();50this._store.add(textModelResolverService.registerTextModelContentProvider(DEBUG_SCHEME, this));51DebugContentProvider.INSTANCE = this;52}5354override dispose(): void {55this.pendingUpdates.forEach(cancellationSource => cancellationSource.dispose());56super.dispose();57}5859provideTextContent(resource: uri): Promise<ITextModel> | null {60return this.createOrUpdateContentModel(resource, true);61}6263/**64* Reload the model content of the given resource.65* If there is no model for the given resource, this method does nothing.66*/67static refreshDebugContent(resource: uri): void {68DebugContentProvider.INSTANCE?.createOrUpdateContentModel(resource, false);69}7071/**72* Create or reload the model content of the given resource.73*/74private createOrUpdateContentModel(resource: uri, createIfNotExists: boolean): Promise<ITextModel> | null {7576const model = this.modelService.getModel(resource);77if (!model && !createIfNotExists) {78// nothing to do79return null;80}8182let session: IDebugSession | undefined;8384if (resource.query) {85const data = Source.getEncodedDebugData(resource);86session = this.debugService.getModel().getSession(data.sessionId);87}8889if (!session) {90// fallback: use focused session91session = this.debugService.getViewModel().focusedSession;92}9394if (!session) {95return Promise.reject(new ErrorNoTelemetry(localize('unable', "Unable to resolve the resource without a debug session")));96}97const createErrModel = (errMsg?: string) => {98this.debugService.sourceIsNotAvailable(resource);99const languageSelection = this.languageService.createById(PLAINTEXT_LANGUAGE_ID);100const message = errMsg101? localize('canNotResolveSourceWithError', "Could not load source '{0}': {1}.", resource.path, errMsg)102: localize('canNotResolveSource', "Could not load source '{0}'.", resource.path);103return this.modelService.createModel(message, languageSelection, resource);104};105106return session.loadSource(resource).then(response => {107108if (response && response.body) {109110if (model) {111112const newContent = response.body.content;113114// cancel and dispose an existing update115const cancellationSource = this.pendingUpdates.get(model.id);116cancellationSource?.cancel();117118// create and keep update token119const myToken = new CancellationTokenSource();120this.pendingUpdates.set(model.id, myToken);121122// update text model123return this.editorWorkerService.computeMoreMinimalEdits(model.uri, [{ text: newContent, range: model.getFullModelRange() }]).then(edits => {124125// remove token126this.pendingUpdates.delete(model.id);127128if (!myToken.token.isCancellationRequested && edits && edits.length > 0) {129// use the evil-edit as these models show in readonly-editor only130model.applyEdits(edits.map(edit => EditOperation.replace(Range.lift(edit.range), edit.text)));131}132return model;133});134} else {135// create text model136const mime = response.body.mimeType || getMimeTypes(resource)[0];137const languageSelection = this.languageService.createByMimeType(mime);138return this.modelService.createModel(response.body.content, languageSelection, resource);139}140}141142return createErrModel();143144}, (err: DebugProtocol.ErrorResponse) => createErrModel(err.message));145}146}147148149