Files
azuredatastudio/src/vs/workbench/contrib/debug/common/debugContentProvider.ts
Anthony Dresser 0b7e7ddbf9 Merge from vscode 8e0f348413f4f616c23a88ae30030efa85811973 (#6381)
* Merge from vscode 8e0f348413f4f616c23a88ae30030efa85811973

* disable strict null check
2019-07-15 22:35:46 -07:00

147 lines
5.6 KiB
TypeScript

/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the Source EULA. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import { URI as uri } from 'vs/base/common/uri';
import { localize } from 'vs/nls';
import { guessMimeTypes, MIME_TEXT } from 'vs/base/common/mime';
import { ITextModel } from 'vs/editor/common/model';
import { IModelService } from 'vs/editor/common/services/modelService';
import { IModeService } from 'vs/editor/common/services/modeService';
import { ITextModelService, ITextModelContentProvider } from 'vs/editor/common/services/resolverService';
import { IWorkbenchContribution } from 'vs/workbench/common/contributions';
import { DEBUG_SCHEME, IDebugService, IDebugSession } from 'vs/workbench/contrib/debug/common/debug';
import { Source } from 'vs/workbench/contrib/debug/common/debugSource';
import { IEditorWorkerService } from 'vs/editor/common/services/editorWorkerService';
import { EditOperation } from 'vs/editor/common/core/editOperation';
import { Range } from 'vs/editor/common/core/range';
import { CancellationTokenSource } from 'vs/base/common/cancellation';
/**
* Debug URI format
*
* a debug URI represents a Source object and the debug session where the Source comes from.
*
* debug:arbitrary_path?session=123e4567-e89b-12d3-a456-426655440000&ref=1016
* \___/ \____________/ \__________________________________________/ \______/
* | | | |
* scheme source.path session id source.reference
*
* the arbitrary_path and the session id are encoded with 'encodeURIComponent'
*
*/
export class DebugContentProvider implements IWorkbenchContribution, ITextModelContentProvider {
private static INSTANCE: DebugContentProvider;
private readonly pendingUpdates = new Map<string, CancellationTokenSource>();
constructor(
@ITextModelService textModelResolverService: ITextModelService,
@IDebugService private readonly debugService: IDebugService,
@IModelService private readonly modelService: IModelService,
@IModeService private readonly modeService: IModeService,
@IEditorWorkerService private readonly editorWorkerService: IEditorWorkerService
) {
textModelResolverService.registerTextModelContentProvider(DEBUG_SCHEME, this);
DebugContentProvider.INSTANCE = this;
}
dispose(): void {
this.pendingUpdates.forEach(cancellationSource => cancellationSource.dispose());
}
provideTextContent(resource: uri): Promise<ITextModel> | null {
return this.createOrUpdateContentModel(resource, true);
}
/**
* Reload the model content of the given resource.
* If there is no model for the given resource, this method does nothing.
*/
static refreshDebugContent(resource: uri): void {
if (DebugContentProvider.INSTANCE) {
DebugContentProvider.INSTANCE.createOrUpdateContentModel(resource, false);
}
}
/**
* Create or reload the model content of the given resource.
*/
private createOrUpdateContentModel(resource: uri, createIfNotExists: boolean): Promise<ITextModel> | null {
const model = this.modelService.getModel(resource);
if (!model && !createIfNotExists) {
// nothing to do
return null;
}
let session: IDebugSession | undefined;
if (resource.query) {
const data = Source.getEncodedDebugData(resource);
session = this.debugService.getModel().getSession(data.sessionId);
}
if (!session) {
// fallback: use focused session
session = this.debugService.getViewModel().focusedSession;
}
if (!session) {
return Promise.reject(new Error(localize('unable', "Unable to resolve the resource without a debug session")));
}
const createErrModel = (errMsg?: string) => {
this.debugService.sourceIsNotAvailable(resource);
const languageSelection = this.modeService.create(MIME_TEXT);
const message = errMsg
? localize('canNotResolveSourceWithError', "Could not load source '{0}': {1}.", resource.path, errMsg)
: localize('canNotResolveSource', "Could not load source '{0}'.", resource.path);
return this.modelService.createModel(message, languageSelection, resource);
};
return session.loadSource(resource).then(response => {
if (response && response.body) {
if (model) {
const newContent = response.body.content;
// cancel and dispose an existing update
const cancellationSource = this.pendingUpdates.get(model.id);
if (cancellationSource) {
cancellationSource.cancel();
}
// create and keep update token
const myToken = new CancellationTokenSource();
this.pendingUpdates.set(model.id, myToken);
// update text model
return this.editorWorkerService.computeMoreMinimalEdits(model.uri, [{ text: newContent, range: model.getFullModelRange() }]).then(edits => {
// remove token
this.pendingUpdates.delete(model.id);
if (!myToken.token.isCancellationRequested && edits && edits.length > 0) {
// use the evil-edit as these models show in readonly-editor only
model.applyEdits(edits.map(edit => EditOperation.replace(Range.lift(edit.range), edit.text)));
}
return model;
});
} else {
// create text model
const mime = response.body.mimeType || guessMimeTypes(resource)[0];
const languageSelection = this.modeService.create(mime);
return this.modelService.createModel(response.body.content, languageSelection, resource);
}
}
return createErrModel();
}, (err: DebugProtocol.ErrorResponse) => createErrModel(err.message));
}
}