Path: blob/main/extensions/copilot/src/extension/inlineEdits/vscode-node/parts/vscodeWorkspace.ts
13405 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 { CancellationToken, CodeAction, CodeActionKind, commands, Diagnostic, DiagnosticSeverity, languages, NotebookCell, NotebookCellKind, NotebookDocument, Range, TextDocument, TextEditor, Uri, window, workspace } from 'vscode';6import { ConfigKey, IConfigurationService } from '../../../../platform/configuration/common/configurationService';7import { CodeActionData } from '../../../../platform/inlineEdits/common/dataTypes/codeActionData';8import { DiagnosticData } from '../../../../platform/inlineEdits/common/dataTypes/diagnosticData';9import { DocumentId } from '../../../../platform/inlineEdits/common/dataTypes/documentId';10import { LanguageId } from '../../../../platform/inlineEdits/common/dataTypes/languageId';11import { EditReason } from '../../../../platform/inlineEdits/common/editReason';12import { IObservableDocument, ObservableWorkspace, StringEditWithReason } from '../../../../platform/inlineEdits/common/observableWorkspace';13import { createAlternativeNotebookDocument, IAlternativeNotebookDocument, toAltNotebookCellChangeEdit, toAltNotebookChangeEdit } from '../../../../platform/notebook/common/alternativeNotebookTextDocument';14import { getDefaultLanguage } from '../../../../platform/notebook/common/helpers';15import { IExperimentationService } from '../../../../platform/telemetry/common/nullExperimentationService';16import { IWorkspaceService } from '../../../../platform/workspace/common/workspaceService';17import { getLanguage } from '../../../../util/common/languages';18import { findNotebook } from '../../../../util/common/notebooks';19import { coalesce } from '../../../../util/vs/base/common/arrays';20import { asPromise, raceCancellation, raceTimeout } from '../../../../util/vs/base/common/async';21import { diffMaps } from '../../../../util/vs/base/common/collections';22import { onUnexpectedError } from '../../../../util/vs/base/common/errors';23import { Lazy } from '../../../../util/vs/base/common/lazy';24import { DisposableStore, IDisposable } from '../../../../util/vs/base/common/lifecycle';25import { ResourceSet } from '../../../../util/vs/base/common/map';26import { Schemas } from '../../../../util/vs/base/common/network';27import { autorun, derived, IObservable, IReader, ISettableObservable, mapObservableArrayCached, observableFromEvent, observableValue, transaction } from '../../../../util/vs/base/common/observableInternal';28import { isDefined } from '../../../../util/vs/base/common/types';29import { URI } from '../../../../util/vs/base/common/uri';30import { TextReplacement } from '../../../../util/vs/editor/common/core/edits/textEdit';31import { OffsetRange } from '../../../../util/vs/editor/common/core/ranges/offsetRange';32import { StringText } from '../../../../util/vs/editor/common/core/text/abstractText';33import { IInstantiationService } from '../../../../util/vs/platform/instantiation/common/instantiation';34import { toInternalTextEdit } from '../utils/translations';35import { editFromTextDocumentContentChangeEvents, stringValueFromDoc } from './common';36import { DocumentFilter } from './documentFilter';37import { VerifyTextDocumentChanges } from './verifyTextDocumentChanges';3839export class VSCodeWorkspace extends ObservableWorkspace implements IDisposable {40private readonly _openDocuments = observableValue<readonly IVSCodeObservableDocument[], { added: readonly IVSCodeObservableDocument[]; removed: readonly IVSCodeObservableDocument[] }>(this, []);41public readonly openDocuments = this._openDocuments;4243private readonly _store = new DisposableStore();44private readonly _filter = this._instaService.createInstance(DocumentFilter);45private get _useAlternativeNotebookFormat(): boolean {46return this._configurationService.getExperimentBasedConfig(ConfigKey.Advanced.UseAlternativeNESNotebookFormat, this._experimentationService)47|| this._configurationService.getExperimentBasedConfig(ConfigKey.UseAlternativeNESNotebookFormat, this._experimentationService);48}49private readonly _markdownNotebookCells = new Lazy<ResourceSet>(() => {50const markdownCellUris = new ResourceSet();51workspace.notebookDocuments.forEach(doc => trackMarkdownCells(doc.getCells(), markdownCellUris));52return markdownCellUris;53});5455constructor(56@IWorkspaceService private readonly _workspaceService: IWorkspaceService,57@IInstantiationService private readonly _instaService: IInstantiationService,58@IConfigurationService private readonly _configurationService: IConfigurationService,59@IExperimentationService private readonly _experimentationService: IExperimentationService,60) {61super();6263const config = this._configurationService.getExperimentBasedConfigObservable(ConfigKey.TeamInternal.VerifyTextDocumentChanges, this._experimentationService);64this._store.add(autorun(reader => {65if (config.read(reader)) {66reader.store.add(this._instaService.createInstance(VerifyTextDocumentChanges));67}68}));6970let lastDocs: Map<DocumentId, IVSCodeObservableDocument> = new Map();71this._store.add(autorun(reader => {72// Manually copy over the documents to get the delta73const curDocs = this._obsDocsByDocId.read(reader);74const diff = diffMaps(lastDocs, curDocs);75lastDocs = curDocs;7677this._openDocuments.set([...curDocs.values()], undefined, {78added: diff.added,79removed: diff.removed,80});81}));8283this._store.add(workspace.onDidChangeTextDocument(e => {84const doc = this._getDocumentByDocumentAndUpdateShouldTrack(e.document.uri);85if (!doc) {86return;87}88if (doc instanceof VSCodeObservableTextDocument) {89const edit = editFromTextDocumentContentChangeEvents(e.contentChanges);90const editWithReason = new StringEditWithReason(edit.replacements, EditReason.create(e.detailedReason?.metadata as any));91transaction(tx => {92doc.languageId.set(LanguageId.create(e.document.languageId), tx);93doc.value.set(stringValueFromDoc(e.document), tx, editWithReason);94doc.version.set(e.document.version, tx);95});96} else {97const edit = toAltNotebookCellChangeEdit(doc.altNotebook, e.document, e.contentChanges);98doc.altNotebook.applyCellChanges(e.document, e.contentChanges);99const editWithReason = new StringEditWithReason(edit.replacements, EditReason.create(e.detailedReason?.metadata as any));100transaction(tx => {101doc.value.set(stringValueFromDoc(doc.altNotebook), tx, editWithReason);102doc.version.set(doc.notebook.version, tx);103});104}105}));106107if (this._useAlternativeNotebookFormat) {108this._store.add(workspace.onDidOpenNotebookDocument(e => trackMarkdownCells(e.getCells(), this._markdownNotebookCells.value)));109}110111this._store.add(workspace.onDidChangeNotebookDocument(e => {112const doc = this._getDocumentByDocumentAndUpdateShouldTrack(e.notebook.uri);113if (!doc || !e.contentChanges.length || doc instanceof VSCodeObservableTextDocument) {114return;115}116const edit = toAltNotebookChangeEdit(doc.altNotebook, e.contentChanges);117if (!edit) {118return;119}120doc.altNotebook.applyNotebookChanges(e.contentChanges);121const editWithReason = new StringEditWithReason(edit.replacements, EditReason.unknown);122transaction(tx => {123doc.value.set(stringValueFromDoc(doc.altNotebook), tx, editWithReason);124doc.version.set(doc.notebook.version, tx);125});126127if (this._useAlternativeNotebookFormat) {128e.contentChanges.map(c => c.removedCells).flat().forEach(c => {129this._markdownNotebookCells.value.delete(c.document.uri);130});131trackMarkdownCells(e.contentChanges.map(c => c.addedCells).flat(), this._markdownNotebookCells.value);132}133}));134135this._store.add(window.onDidChangeTextEditorSelection(e => {136const doc = this._getDocumentByDocumentAndUpdateShouldTrack(e.textEditor.document.uri);137if (!doc) {138return;139}140const selections = doc instanceof VSCodeObservableTextDocument ?141coalesce(e.selections.map(s => doc.toOffsetRange(e.textEditor.document, s))) :142this.getNotebookSelections(doc.notebook, e.textEditor);143doc.selection.set(selections, undefined);144doc.primarySelectionLine.set(e.selections[0]?.start.line, undefined);145}));146147this._store.add(window.onDidChangeTextEditorVisibleRanges(e => {148const doc = this._getDocumentByDocumentAndUpdateShouldTrack(e.textEditor.document.uri);149if (!doc) {150return;151}152const visibleRanges = doc instanceof VSCodeObservableTextDocument ?153coalesce(e.visibleRanges.map(r => doc.toOffsetRange(e.textEditor.document, r))) :154this.getNotebookVisibleRanges(doc.notebook);155doc.visibleRanges.set(visibleRanges, undefined);156}));157158this._store.add(languages.onDidChangeDiagnostics(e => {159e.uris.forEach(uri => {160const document = this._getDocumentByDocumentAndUpdateShouldTrack(uri);161if (!document) {162return;163}164const diagnostics = document instanceof VSCodeObservableTextDocument ?165this._createTextDocumentDiagnosticData(document) :166this._createNotebookDiagnosticData(document.altNotebook);167document.diagnostics.set(diagnostics, undefined);168});169}));170}171172public dispose(): void {173this._store.dispose();174}175176private readonly _obsDocsByDocId = derived(this, reader => {177const textDocs = this._textDocsWithShouldTrackFlag.read(reader);178const obsDocs = textDocs.map(d => d.obsDoc.read(reader)).filter(isDefined);179const map: Map<DocumentId, VSCodeObservableTextDocument | VSCodeObservableNotebookDocument> = new Map(obsDocs.map(d => [d.id, d]));180const notebookDocs = this._notebookDocsWithShouldTrackFlag.read(reader);181const obsNotebookDocs = notebookDocs.map(d => d.obsDoc.read(reader)).filter(isDefined);182obsNotebookDocs.forEach(d => map.set(d.id, d));183184return map;185});186187private readonly _vscodeTextDocuments: IObservable<readonly TextDocument[]> = getObservableTextDocumentList(this._useAlternativeNotebookFormat, this._markdownNotebookCells.value);188private readonly _textDocsWithShouldTrackFlag = mapObservableArrayCached(this, this._vscodeTextDocuments, (doc, store) => {189const shouldTrack = observableValue<boolean>(this, false);190const updateShouldTrack = () => {191// @ulugbekna: not sure if invoking `isCopilotIgnored` on every textDocument-edit event is a good idea192// also not sure if we should be enforcing local copilot-ignore rules (vs only remote-exclusion rules)193this._filter.isTrackingEnabled(doc).then(v => {194shouldTrack.set(v, undefined);195}).catch(e => {196onUnexpectedError(e);197});198};199const obsDoc = derived(this, reader => {200if (!shouldTrack.read(reader)) {201return undefined;202}203204const documentId = DocumentId.create(doc.uri.toString());205const openedTextEditor = window.visibleTextEditors.find(e => e.document.uri.toString() === doc.uri.toString());206const document = new VSCodeObservableTextDocument(documentId, stringValueFromDoc(doc), doc.version, [], [], LanguageId.create(doc.languageId), [], doc);207208const selections = coalesce((openedTextEditor?.selections || []).map(s => document.toOffsetRange(doc, s)));209const visibleRanges = coalesce((openedTextEditor?.visibleRanges || []).map(r => document.toOffsetRange(doc, r)));210transaction(tx => {211document.selection.set(selections, tx);212document.primarySelectionLine.set(openedTextEditor?.selections[0]?.start.line, tx);213document.visibleRanges.set(visibleRanges, tx);214document.diagnostics.set(this._createTextDocumentDiagnosticData(document), tx);215});216return document;217}).recomputeInitiallyAndOnChange(store);218219updateShouldTrack();220return {221doc,222updateShouldTrack,223obsDoc,224};225});226227private getNotebookDocuments() {228if (!this._useAlternativeNotebookFormat) {229return observableValue('', []);230}231return getNotebookDocuments();232}233private readonly _vscodeNotebookDocuments: IObservable<readonly NotebookDocument[]> = this.getNotebookDocuments();234private readonly _altNotebookDocs = new WeakMap<NotebookDocument, IAlternativeNotebookDocument>();235private getAltNotebookDocument(doc: NotebookDocument): IAlternativeNotebookDocument {236let altNotebook = this._altNotebookDocs.get(doc);237if (!altNotebook) {238altNotebook = createAlternativeNotebookDocument(doc, true);239this._altNotebookDocs.set(doc, altNotebook);240}241return altNotebook;242}243private readonly _notebookDocsWithShouldTrackFlag = mapObservableArrayCached(this, this._vscodeNotebookDocuments, (doc, store) => {244const shouldTrack = observableValue<boolean>(this, false);245const updateShouldTrack = () => {246// @ulugbekna: not sure if invoking `isCopilotIgnored` on every textDocument-edit event is a good idea247// also not sure if we should be enforcing local copilot-ignore rules (vs only remote-exclusion rules)248this._filter.isTrackingEnabled(doc).then(v => {249shouldTrack.set(v, undefined);250}).catch(e => {251onUnexpectedError(e);252});253};254const obsDoc = derived(this, reader => {255if (!shouldTrack.read(reader)) {256return undefined;257}258const altNotebook = this.getAltNotebookDocument(doc);259const documentId = DocumentId.create(doc.uri.toString());260const selections = this.getNotebookSelections(doc);261const visibleRanges = this.getNotebookVisibleRanges(doc);262const diagnostics = this._createNotebookDiagnosticData(altNotebook);263const language = getLanguage(getDefaultLanguage(altNotebook.notebook)).languageId;264const document = new VSCodeObservableNotebookDocument(documentId, stringValueFromDoc(altNotebook), doc.version, selections ?? [], visibleRanges ?? [], LanguageId.create(language), diagnostics, doc, altNotebook);265return document;266}).recomputeInitiallyAndOnChange(store);267268updateShouldTrack();269return {270doc,271updateShouldTrack,272obsDoc,273};274});275276private getNotebookSelections(doc: NotebookDocument, activeCellEditor?: TextEditor) {277const altNotebook = this.getAltNotebookDocument(doc);278const visibleTextEditors = new Map(window.visibleTextEditors.map(e => [e.document, e]));279// As notebooks have multiple cells, and each cell can have its own selection,280// We should focus on the active cell to determine the cursor position.281const selectedCellRange = window.activeNotebookEditor?.selection;282const selectedCell = activeCellEditor ? altNotebook.getCell(activeCellEditor.document) : (selectedCellRange && selectedCellRange.start < doc.cellCount ? doc.cellAt(selectedCellRange.start) : undefined);283const selectedCellEditor = selectedCell ? visibleTextEditors.get(selectedCell.document) : undefined;284// We only care about the active cell where cursor is, don't care about multi-cursor.285// As the edits are to be performed around wher the cursor is.286return (selectedCellEditor && selectedCell) ? altNotebook.toAltOffsetRange(selectedCell, selectedCellEditor.selections) : [];287}288289private getNotebookVisibleRanges(doc: NotebookDocument) {290const altNotebook = this.getAltNotebookDocument(doc);291const visibleTextEditors = new Map(window.visibleTextEditors.map(e => [e.document, e]));292const cellTextEditors = coalesce(doc.getCells().map(cell => visibleTextEditors.has(cell.document) ? [cell, visibleTextEditors.get(cell.document)!] as const : undefined));293return cellTextEditors.flatMap(e => altNotebook.toAltOffsetRange(e[0], e[1].visibleRanges));294}295296private _getDocumentByDocumentAndUpdateShouldTrack(uri: URI): VSCodeObservableTextDocument | VSCodeObservableNotebookDocument | undefined {297const internalDoc = this._getInternalDocument(uri);298if (!internalDoc) {299return undefined;300}301internalDoc.updateShouldTrack();302return internalDoc.obsDoc.get();303}304305private _getInternalDocument(uri: Uri, reader?: IReader) {306const document = this._obsDocsWithUpdateIgnored.read(reader).get(uri.toString());307return document;308}309310private _createTextDocumentDiagnosticData(document: VSCodeObservableTextDocument) {311return languages.getDiagnostics(document.textDocument.uri).map(d => this._createDiagnosticData(d, document)).filter(isDefined);312}313314private _createDiagnosticData(diagnostic: Diagnostic, doc: VSCodeObservableTextDocument): DiagnosticData | undefined {315return toDiagnosticData(diagnostic, doc.textDocument.uri, (range) => doc.toOffsetRange(doc.textDocument, range));316}317318private _createNotebookDiagnosticData(altNotebook: IAlternativeNotebookDocument) {319return coalesce(altNotebook.notebook.getCells().flatMap(c => languages.getDiagnostics(c.document.uri).map(d => this._createNotebookCellDiagnosticData(d, altNotebook, c.document))));320}321322private _createNotebookCellDiagnosticData(diagnostic: Diagnostic, altNotebook: IAlternativeNotebookDocument, doc: TextDocument): DiagnosticData | undefined {323const cell = altNotebook.getCell(doc);324if (!cell) {325return undefined;326}327return toDiagnosticData(diagnostic, altNotebook.notebook.uri, range => {328const offsetRanges = altNotebook.toAltOffsetRange(cell, [range]);329return offsetRanges.length ? offsetRanges[0] : undefined;330});331}332333private readonly _obsDocsWithUpdateIgnored = derived(this, reader => {334const docs = this._textDocsWithShouldTrackFlag.read(reader);335const map: Map<string, {336doc: TextDocument | NotebookDocument;337updateShouldTrack: () => void;338obsDoc: IObservable<VSCodeObservableTextDocument | VSCodeObservableNotebookDocument | undefined>;339}> = new Map(docs.map(d => [d.doc.uri.toString(), d]));340const notebookDocs = this._notebookDocsWithShouldTrackFlag.read(reader);341notebookDocs.forEach(d => {342map.set(d.doc.uri.toString(), d);343// Markdown cells will be treated as standalone text documents (old behaviour).344d.doc.getCells().filter(cell => cell.kind === NotebookCellKind.Code).forEach(cell => map.set(cell.document.uri.toString(), d));345});346return map;347});348349/**350* Returns undefined for documents that are not tracked (e.g. filtered out).351*/352public getDocumentByTextDocument(doc: TextDocument, reader?: IReader): IVSCodeObservableDocument | undefined {353this._store.assertNotDisposed();354355const internalDoc = this._getInternalDocument(doc.uri, reader);356if (!internalDoc) {357return undefined;358}359return internalDoc.obsDoc.get();360}361362public getWorkspaceRoot(documentId: DocumentId): URI | undefined {363let uri = documentId.toUri();364if (uri.scheme === Schemas.vscodeNotebookCell) {365const notebook = findNotebook(uri, this._workspaceService.notebookDocuments);366if (notebook) {367uri = notebook.uri;368}369}370return workspace.getWorkspaceFolder(uri)?.uri;371}372}373374export interface IVSCodeObservableDocument extends IObservableDocument {375/**376* Converts the OffsetRange of the Observable document to a range within the provided Text document.377* If this is a Text Document, performs a simple OffsetRange to Range translation.378* If this is a Notebook Document, then this method converts an offset range of the Observable document to a range within the provided notebook cell. If the range provided does not belong to the provided cell (as identified by TextDocument), it returns undefined.379*/380fromOffsetRange(textDocument: TextDocument, range: OffsetRange): Range | undefined;381/**382* Converts the OffsetRange of the Observable document to range(s) within the provided Text document.383* If this is a Text Document, performs a simple OffsetRange to Range translation.384* If this is a Notebook Document, then this method converts an offset range of the Observable document to a range(s) of multiple notebook cells.385*/386fromOffsetRange(range: OffsetRange): [TextDocument, Range][];387388/**389* Converts the Range of the Observable document to a range within the provided Text document.390* If this is a Text Document, then this returns the same value passed in.391* If this is a Notebook Document, then this method converts a range of the Observable document to a range within the provided notebook cell. If the range provided does not belong to the provided cell (as identified by TextDocument), it returns undefined.392*/393fromRange(textDocument: TextDocument, range: Range): Range | undefined;394/**395* Converts the Range of the Observable document to range(s) within the provided Text document.396* If this is a Text Document, then this returns the same value passed in.397* If this is a Notebook Document, then this method converts a range of the Observable document to a range(s) of multiple notebook cells.398*/399fromRange(range: Range): [TextDocument, Range][];400401/**402* Converts the Range of the provided Text Document into an OffsetRange within the Observable Document.403* If this is a Text Document, performs a simple Range to OffsetRange translation.404* If this is a Notebook Document, then this method converts a range within the provided Notebook Cell to an Offset within the Observable document. If the provided document does not belong to the Notebook Cell, it returns undefined.405*/406toOffsetRange(textDocument: TextDocument, range: Range): OffsetRange | undefined;407408/**409* Converts the Range of the provided Text Document into a Range within the Observable Document.410* If this is a Text Document, then this returns the same value passed in.411* If this is a Notebook Document, then this method converts a range within the provided Notebook Cell to a Range within the Observable document. If the provided document does not belong to the Notebook Cell, it returns undefined.412*/413toRange(textDocument: TextDocument, range: Range): Range | undefined;414415/**416* Gets code actions for the specific range within the document417* @param itemResolveCount Number of code actions to resolve (too large numbers slow down code actions)418*/419getCodeActions(range: OffsetRange, itemResolveCount: number, token: CancellationToken): Promise<CodeActionData[] | undefined>;420}421422abstract class AbstractVSCodeObservableDocument {423public readonly value: ISettableObservable<StringText, StringEditWithReason>;424public readonly version: ISettableObservable<number>;425public readonly selection: ISettableObservable<readonly OffsetRange[]>;426public readonly primarySelectionLine: ISettableObservable<number | undefined>;427public readonly visibleRanges: ISettableObservable<readonly OffsetRange[]>;428public readonly languageId: ISettableObservable<LanguageId>;429public readonly diagnostics: ISettableObservable<readonly DiagnosticData[]>;430431constructor(432public readonly id: DocumentId,433value: StringText,434versionId: number,435selection: readonly OffsetRange[],436visibleRanges: readonly OffsetRange[],437languageId: LanguageId,438diagnostics: DiagnosticData[]439) {440this.value = observableValue(this, value);441this.version = observableValue(this, versionId);442this.selection = observableValue(this, selection);443this.primarySelectionLine = observableValue(this, undefined);444this.visibleRanges = observableValue(this, visibleRanges);445this.languageId = observableValue(this, languageId);446this.diagnostics = observableValue(this, diagnostics);447}448}449450class VSCodeObservableTextDocument extends AbstractVSCodeObservableDocument implements IVSCodeObservableDocument {451constructor(452id: DocumentId,453value: StringText,454versionId: number,455selection: readonly OffsetRange[],456visibleRanges: readonly OffsetRange[],457languageId: LanguageId,458diagnostics: DiagnosticData[],459public readonly textDocument: TextDocument,460) {461super(id, value, versionId, selection, visibleRanges, languageId, diagnostics);462}463464fromOffsetRange(textDocument: TextDocument, range: OffsetRange): Range | undefined;465fromOffsetRange(range: OffsetRange): [TextDocument, Range][];466fromOffsetRange(arg1: TextDocument | OffsetRange, range?: OffsetRange): Range | undefined | [TextDocument, Range][] {467let textDocument: TextDocument = this.textDocument;468let offsetRange: OffsetRange | undefined;469470if (arg1 instanceof OffsetRange) {471offsetRange = arg1;472} else if (range !== undefined) {473textDocument = arg1;474offsetRange = range;475}476if (textDocument !== this.textDocument) {477throw new Error('TextDocument does not match the one of this observable document.');478}479if (!offsetRange) {480throw new Error('OffsetRange is not defined.');481}482const result = new Range(483textDocument.positionAt(offsetRange.start),484textDocument.positionAt(offsetRange.endExclusive)485);486if (arg1 instanceof OffsetRange) {487return [[this.textDocument, result]];488} else {489return result;490}491}492toOffsetRange(textDocument: TextDocument, range: Range): OffsetRange | undefined {493return new OffsetRange(textDocument.offsetAt(range.start), textDocument.offsetAt(range.end));494}495496fromRange(textDocument: TextDocument, range: Range): Range | undefined;497fromRange(range: Range): [TextDocument, Range][];498fromRange(arg1: TextDocument | Range, range?: Range): Range | undefined | [TextDocument, Range][] {499if (arg1 instanceof Range) {500return [[this.textDocument, arg1]];501} else if (range !== undefined) {502if (arg1 !== this.textDocument) {503throw new Error('TextDocument does not match the one of this observable document.');504}505return range;506} else {507return undefined;508}509}510511toRange(_textDocument: TextDocument, range: Range): Range | undefined {512return range;513}514515async getCodeActions(offsetRange: OffsetRange, itemResolveCount: number, token: CancellationToken): Promise<CodeActionData[] | undefined> {516const range = this.fromOffsetRange(this.textDocument, offsetRange);517if (!range) {518return;519}520const actions = await raceTimeout(521raceCancellation(522getQuickFixCodeActions(this.textDocument.uri, range, itemResolveCount),523token524),5251000526);527528return actions?.map(action => toCodeActionData(action, this, range => this.toOffsetRange(this.textDocument, range)));529}530}531532class VSCodeObservableNotebookDocument extends AbstractVSCodeObservableDocument implements IVSCodeObservableDocument {533constructor(534id: DocumentId,535value: StringText,536versionId: number,537selection: readonly OffsetRange[],538visibleRanges: readonly OffsetRange[],539languageId: LanguageId,540diagnostics: DiagnosticData[],541public readonly notebook: NotebookDocument,542public readonly altNotebook: IAlternativeNotebookDocument,543) {544super(id, value, versionId, selection, visibleRanges, languageId, diagnostics);545}546547fromOffsetRange(textDocument: TextDocument, range: OffsetRange): Range | undefined;548fromOffsetRange(range: OffsetRange): [TextDocument, Range][];549fromOffsetRange(arg1: TextDocument | OffsetRange, range?: OffsetRange): Range | undefined | [TextDocument, Range][] {550if (arg1 instanceof OffsetRange) {551return this.altNotebook.fromAltOffsetRange(arg1).map(r => [r[0].document, r[1]]);552} else if (range !== undefined) {553const cell = this.altNotebook.getCell(arg1);554if (!cell) {555return undefined;556}557const results = this.altNotebook.fromAltOffsetRange(range);558const found = results.find(r => r[0].document === arg1);559return found ? found[1] : undefined;560}561return undefined;562}563564fromRange(textDocument: TextDocument, range: Range): Range | undefined;565fromRange(range: Range): [TextDocument, Range][];566fromRange(arg1: TextDocument | Range, range?: Range): Range | undefined | [TextDocument, Range][] {567if (arg1 instanceof Range) {568return this.altNotebook.fromAltRange(arg1).map(r => [r[0].document, r[1]]);569} else if (range !== undefined) {570const cell = this.altNotebook.getCell(arg1);571if (!cell) {572return undefined;573}574const results = this.altNotebook.fromAltRange(range);575const found = results.find(r => r[0].document === arg1);576return found ? found[1] : undefined;577}578}579580toOffsetRange(textDocument: TextDocument, range: Range): OffsetRange | undefined {581const cell = this.altNotebook.getCell(textDocument);582if (!cell) {583return undefined;584}585const offsetRanges = this.altNotebook.toAltOffsetRange(cell, [range]);586return offsetRanges.length ? offsetRanges[0] : undefined;587}588589toRange(textDocument: TextDocument, range: Range): Range | undefined {590const cell = this.altNotebook.getCell(textDocument);591if (!cell) {592return undefined;593}594const ranges = this.altNotebook.toAltRange(cell, [range]);595return ranges.length > 0 ? ranges[0] : undefined;596}597598async getCodeActions(offsetRange: OffsetRange, itemResolveCount: number, token: CancellationToken): Promise<CodeActionData[] | undefined> {599const cellRanges = this.fromOffsetRange(offsetRange);600if (!cellRanges || cellRanges.length === 0) {601return;602}603return Promise.all(cellRanges.map(async ([cellTextDocument, range]) => {604const cell = this.altNotebook.getCell(cellTextDocument);605if (!cell) {606return undefined;607}608const actions = await raceTimeout(609raceCancellation(610getQuickFixCodeActions(cellTextDocument.uri, range, itemResolveCount),611token612),6131000614);615616return actions?.map(action => toCodeActionData(action, this, (range) => {617const offsetRanges = this.altNotebook.toAltOffsetRange(cell, [range]);618return offsetRanges.length ? offsetRanges[0] : undefined;619}));620})).then(results => coalesce(results.flat()));621}622}623624function getObservableTextDocumentList(excludeNotebookCells: boolean, markdownCellUris: ResourceSet): IObservable<readonly TextDocument[]> {625return observableFromEvent(undefined, e => {626const d1 = workspace.onDidOpenTextDocument(e);627const d2 = workspace.onDidCloseTextDocument(e);628return {629dispose: () => {630d1.dispose();631d2.dispose();632}633};634// If we're meant to exclude notebook cells, we will still include the markdown cells as separate documents.635// Thats because markdown cells will be treated as standalone text documents in the editor.636}, () => excludeNotebookCells ? workspace.textDocuments.filter(doc => doc.uri.scheme !== Schemas.vscodeNotebookCell || markdownCellUris.has(doc.uri)) : workspace.textDocuments);637}638639function getNotebookDocuments(): IObservable<readonly NotebookDocument[]> {640return observableFromEvent(undefined, e => {641const d1 = workspace.onDidOpenNotebookDocument(e);642const d2 = workspace.onDidCloseNotebookDocument(e);643return {644dispose: () => {645d1.dispose();646d2.dispose();647}648};649}, () => workspace.notebookDocuments);650}651652function trackMarkdownCells(cells: NotebookCell[], resources: ResourceSet): void {653cells.forEach(c => {654if (c.kind === NotebookCellKind.Markup) {655resources.add(c.document.uri);656}657});658}659660function toDiagnosticData(diagnostic: Diagnostic, uri: URI, translateRange: (range: Range) => OffsetRange | undefined) {661if (!diagnostic.source || (diagnostic.severity !== DiagnosticSeverity.Error && diagnostic.severity !== DiagnosticSeverity.Warning)) {662return undefined;663}664665const range = translateRange(diagnostic.range);666if (!range) {667return undefined;668}669return new DiagnosticData(670uri,671diagnostic.message,672diagnostic.severity === DiagnosticSeverity.Error ? 'error' : 'warning',673range,674diagnostic.code && !(typeof diagnostic.code === 'number') && !(typeof diagnostic.code === 'string') ? diagnostic.code.value : diagnostic.code,675diagnostic.source676);677}678679function toCodeActionData(codeAction: CodeAction, workspaceDocument: IVSCodeObservableDocument, translateRange: (range: Range) => OffsetRange | undefined): CodeActionData {680const uri = workspaceDocument.id.toUri();681const diagnostics = coalesce((codeAction.diagnostics || []).map(d => toDiagnosticData(d, uri, translateRange))).concat(682getCommandDiagnostics(codeAction, uri, translateRange)683);684685// remove no-op edits686let documentEdits = getDocumentEdits(codeAction, workspaceDocument);687if (documentEdits) {688const documentContent = workspaceDocument.value.get();689documentEdits = documentEdits.filter(edit => documentContent.getValueOfRange(edit.range) !== edit.text);690}691692const codeActionData = new CodeActionData(693codeAction.title,694diagnostics,695documentEdits,696);697return codeActionData;698}699700function getCommandDiagnostics(codeAction: CodeAction, uri: URI, translateRange: (range: Range) => OffsetRange | undefined): DiagnosticData[] {701const commandArgs = codeAction.command?.arguments || [];702703return coalesce(commandArgs.map(arg => {704if (arg && typeof arg === 'object' && 'diagnostic' in arg) {705const diagnostic = arg.diagnostic;706// @benibenj Can we not check `diagnostic instanceOf vscode.Diagnostic?707if (diagnostic && typeof diagnostic === 'object' && 'range' in diagnostic && 'message' in diagnostic && 'severity' in diagnostic) {708return toDiagnosticData(diagnostic, uri, translateRange);709}710}711}));712}713714function getDocumentEdits(codeAction: CodeAction, workspaceDocument: IVSCodeObservableDocument): TextReplacement[] | undefined {715const edit = codeAction.edit;716if (!edit) {717return undefined;718}719720if (workspaceDocument instanceof VSCodeObservableTextDocument) {721return edit.get(workspaceDocument.id.toUri()).map(e => toInternalTextEdit(e.range, e.newText));722} else if (workspaceDocument instanceof VSCodeObservableNotebookDocument) {723const edits = coalesce(workspaceDocument.notebook.getCells().flatMap(cell =>724edit.get(cell.document.uri).map(e => {725const range = workspaceDocument.toRange(cell.document, e.range);726return range ? toInternalTextEdit(range, e.newText) : undefined;727})728));729return edits.length ? edits : undefined;730}731}732733async function getQuickFixCodeActions(uri: Uri, range: Range, itemResolveCount: number): Promise<CodeAction[]> {734return asPromise(735() => commands.executeCommand<CodeAction[]>(736'vscode.executeCodeActionProvider',737uri,738range,739CodeActionKind.QuickFix.value,740itemResolveCount741)742);743}744745746