Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
microsoft
GitHub Repository: microsoft/vscode
Path: blob/main/extensions/copilot/src/util/common/notebooks.ts
13397 views
1
/*---------------------------------------------------------------------------------------------
2
* Copyright (c) Microsoft Corporation. All rights reserved.
3
* Licensed under the MIT License. See License.txt in the project root for license information.
4
*--------------------------------------------------------------------------------------------*/
5
6
import type * as vscode from 'vscode';
7
import { Uri } from '../../vscodeTypes';
8
import * as glob from '../vs/base/common/glob';
9
import { Schemas } from '../vs/base/common/network';
10
import { basename } from '../vs/base/common/path';
11
import { isEqual } from '../vs/base/common/resources';
12
import { URI } from '../vs/base/common/uri';
13
14
15
export interface INotebookSection {
16
title: string;
17
content: string;
18
}
19
20
export interface INotebookOutline {
21
description: string;
22
sections: INotebookSection[];
23
}
24
25
export interface INotebookExclusiveDocumentFilter {
26
include?: string | vscode.RelativePattern;
27
exclude?: string | vscode.RelativePattern;
28
}
29
30
export interface INotebookFilenamePattern {
31
filenamePattern: string;
32
excludeFileNamePattern?: string;
33
}
34
35
export type NotebookSelector = vscode.GlobPattern | INotebookExclusiveDocumentFilter | INotebookFilenamePattern;
36
37
export enum RegisteredEditorPriority {
38
builtin = 'builtin',
39
option = 'option',
40
exclusive = 'exclusive',
41
default = 'default'
42
}
43
44
export interface INotebookEditorContribution {
45
readonly type: string;
46
readonly displayName: string;
47
readonly priority?: RegisteredEditorPriority;
48
49
selector: NotebookSelector[];
50
}
51
52
export interface EditorAssociation {
53
viewType: string;
54
filenamePattern?: string;
55
}
56
57
/**
58
* Find a notebook document by uri or cell uri.
59
*/
60
export function findNotebook(uri: vscode.Uri, notebookDocuments: readonly vscode.NotebookDocument[]): vscode.NotebookDocument | undefined {
61
return notebookDocuments.find(doc => isEqual(doc.uri, uri) || doc.uri.path === uri.path || findCell(uri, doc));
62
}
63
64
export function findCell(cellUri: vscode.Uri, notebook: vscode.NotebookDocument): vscode.NotebookCell | undefined {
65
if (cellUri.scheme === Schemas.vscodeNotebookCell || cellUri.scheme === Schemas.vscodeNotebookCellOutput) {
66
// Fragment is not unique to a notebook, hence ensure we compaure the path as well.
67
const index = notebook.getCells().findIndex(cell => isEqual(cell.document.uri, cellUri) || (cell.document.uri.fragment === cellUri.fragment && cell.document.uri.path === cellUri.path));
68
if (index !== -1) {
69
return notebook.getCells()[index];
70
}
71
}
72
}
73
74
75
export function getNotebookCellOutput(outputUri: Uri, notebookDocuments: readonly vscode.NotebookDocument[]): [vscode.NotebookDocument, vscode.NotebookCell, vscode.NotebookCellOutput] | undefined {
76
if (outputUri.scheme !== Schemas.vscodeNotebookCellOutput) {
77
return undefined;
78
}
79
const params = new URLSearchParams(outputUri.query);
80
const [notebook, cell] = getNotebookAndCellFromUri(outputUri, notebookDocuments);
81
if (!cell || !cell.outputs.length) {
82
return undefined;
83
}
84
const outputIndex = (params.get('outputIndex') ? parseInt(params.get('outputIndex') || '', 10) : undefined) || 0;
85
if (outputIndex > (cell.outputs.length - 1)) {
86
return;
87
}
88
return [notebook, cell, cell.outputs[outputIndex]] as const;
89
}
90
91
export function getNotebookAndCellFromUri(uri: Uri, notebookDocuments: readonly vscode.NotebookDocument[]): [undefined, undefined] | [vscode.NotebookDocument, vscode.NotebookCell | undefined] {
92
const notebook = findNotebook(uri, notebookDocuments) || notebookDocuments.find(doc => doc.uri.path === uri.path);
93
if (!notebook) {
94
return [undefined, undefined];
95
}
96
const cell = findCell(uri, notebook);
97
if (cell === undefined) {
98
// Possible the cell has since been deleted.
99
return [notebook, undefined];
100
}
101
return [notebook, cell];
102
}
103
104
export function isNotebookCellOrNotebookChatInput(uri: vscode.Uri): boolean {
105
return uri.scheme === Schemas.vscodeNotebookCell
106
// Support the experimental cell chat widget
107
|| (uri.scheme === 'untitled' && uri.fragment.startsWith('notebook-chat-input'));
108
}
109
110
export function isNotebookCell(uri: vscode.Uri): boolean {
111
return uri.scheme === Schemas.vscodeNotebookCell;
112
}
113
114
export function isJupyterNotebookUri(uri: vscode.Uri): boolean {
115
return uri.path.endsWith('.ipynb');
116
}
117
118
export function isJupyterNotebook(notebook: vscode.NotebookDocument): boolean {
119
return notebook.notebookType === 'jupyter-notebook';
120
}
121
122
123
export function serializeNotebookDocument(document: vscode.NotebookDocument, features: { cell_uri_fragment?: boolean } = {}): string {
124
return JSON.stringify({
125
cells: document.getCells().map(cell => ({
126
uri_fragment: features.cell_uri_fragment ? cell.document.uri.fragment : undefined,
127
cell_type: cell.kind,
128
source: cell.document.getText().split(/\r?\n/),
129
}))
130
});
131
}
132
133
export function extractNotebookOutline(response: string): INotebookOutline | undefined {
134
try {
135
const trimmedResponse = response.replace(/\n/g, '');
136
const regex = /```(?:json)?(.+)/g;
137
const match = regex.exec(trimmedResponse);
138
if (match) {
139
const prefixTrimed = match[1];
140
// remove content after ```
141
const suffixBacktick = prefixTrimed.indexOf('```');
142
const json = suffixBacktick === -1 ? prefixTrimed : prefixTrimed.substring(0, suffixBacktick);
143
return JSON.parse(json) as INotebookOutline;
144
}
145
} catch (ex) { }
146
147
return undefined;
148
}
149
150
/**
151
* Checks if the provided pattern is a document exclude pattern
152
*/
153
export function isDocumentExcludePattern(pattern: string | vscode.RelativePattern | INotebookExclusiveDocumentFilter | INotebookFilenamePattern): pattern is INotebookExclusiveDocumentFilter {
154
const arg = pattern as INotebookExclusiveDocumentFilter;
155
156
// Check if it has include property (exclude is optional)
157
return typeof arg === 'object' && arg !== null &&
158
(typeof arg.include === 'string' || isRelativePattern(arg.include));
159
}
160
161
/**
162
* Checks if the provided pattern is a filename pattern
163
*/
164
export function isFilenamePattern(pattern: string | vscode.RelativePattern | INotebookExclusiveDocumentFilter | INotebookFilenamePattern): pattern is INotebookFilenamePattern {
165
const arg = pattern as INotebookFilenamePattern;
166
167
// Check if it has filenamePattern property
168
return typeof arg === 'object' && arg !== null && typeof arg.filenamePattern === 'string';
169
}
170
171
/**a
172
* Checks if the provided object is a RelativePattern
173
*/
174
export function isRelativePattern(obj: unknown): obj is vscode.RelativePattern {
175
const rp = obj as vscode.RelativePattern | undefined | null;
176
if (!rp) {
177
return false;
178
}
179
180
return typeof rp.base === 'string' && typeof rp.pattern === 'string';
181
}
182
183
/**
184
* Checks if the provided object is a valid INotebookEditorContribution
185
*/
186
export function isNotebookEditorContribution(contrib: unknown): contrib is INotebookEditorContribution {
187
const candidate = contrib as INotebookEditorContribution | undefined;
188
return !!candidate && !!candidate.type && !!candidate.displayName && !!candidate.selector;
189
}
190
191
/**
192
* Extracts editor associations from the raw editor association config object
193
*
194
* @param raw The raw editor association config object
195
* @returns An array of EditorAssociation objects
196
*/
197
export function extractEditorAssociation(raw: { [fileNamePattern: string]: string }): EditorAssociation[] {
198
const associations: EditorAssociation[] = [];
199
for (const [filenamePattern, viewType] of Object.entries(raw)) {
200
if (viewType) {
201
associations.push({ filenamePattern, viewType });
202
}
203
}
204
return associations;
205
}
206
207
/**
208
* Checks if a resource matches a selector
209
*/
210
export function notebookSelectorMatches(resource: URI, selector: NotebookSelector): boolean {
211
if (typeof selector === 'string') {
212
// selector as string
213
if (glob.match(selector.toLowerCase(), basename(resource.fsPath).toLowerCase())) {
214
return true;
215
}
216
}
217
218
if (isDocumentExcludePattern(selector)) {
219
// selector as INotebookExclusiveDocumentFilter
220
const filenamePattern = selector.include;
221
const excludeFilenamePattern = selector.exclude;
222
223
if (!filenamePattern) {
224
return false;
225
}
226
227
if (glob.match(filenamePattern, basename(resource.fsPath).toLowerCase())) {
228
if (excludeFilenamePattern && glob.match(excludeFilenamePattern, basename(resource.fsPath).toLowerCase())) {
229
return false;
230
}
231
return true;
232
}
233
}
234
235
if (isFilenamePattern(selector)) {
236
// selector as INotebookFilenamePattern
237
if (glob.match(selector.filenamePattern, basename(resource.fsPath).toLowerCase())) {
238
if (selector.excludeFileNamePattern && glob.match(selector.excludeFileNamePattern, basename(resource.fsPath).toLowerCase())) {
239
return false;
240
}
241
return true;
242
}
243
}
244
245
return false;
246
}
247
248
/**
249
* Returns all associations that match the glob of the provided resource
250
*/
251
export function getNotebookEditorAssociations(resource: Uri, editorAssociations: EditorAssociation[]): EditorAssociation[] {
252
const validAssociations: EditorAssociation[] = [];
253
for (const a of editorAssociations) {
254
if (a.filenamePattern && glob.match(a.filenamePattern.toLowerCase(), basename(resource.fsPath).toLowerCase())) {
255
validAssociations.push({ filenamePattern: a.filenamePattern, viewType: a.viewType });
256
}
257
}
258
259
return validAssociations;
260
}
261
262
/**
263
* Checks if the provided resource has a supported notebook provider
264
*/
265
export function _hasSupportedNotebooks(uri: Uri, workspaceNotebookDocuments: readonly vscode.NotebookDocument[], notebookEditorContributions: INotebookEditorContribution[], editorAssociations: EditorAssociation[]): boolean {
266
if (findNotebook(uri, workspaceNotebookDocuments)) {
267
return true;
268
}
269
270
const validNotebookEditorContribs: INotebookEditorContribution[] = notebookEditorContributions.filter(notebookEditorContrib => notebookEditorContrib.selector.some(selector => notebookSelectorMatches(uri, selector)));
271
if (validNotebookEditorContribs.length === 0) {
272
return false;
273
}
274
275
const validAssociations = getNotebookEditorAssociations(uri, editorAssociations);
276
for (const association of validAssociations) {
277
if (validNotebookEditorContribs.some(notebookEditorContrib => notebookEditorContrib.type === association.viewType)) {
278
return true;
279
}
280
}
281
282
// often users won't have associations that take priority, so check the priority of our valid providers
283
// a provider with priority !default will only be chosen if there is an association that matches, so we need default at this point
284
// In VS Code, if priority is empty, it defaults to `default`, vscode/main/src/vs/workbench/contrib/notebook/browser/notebookExtensionPoint.ts#L110
285
if (validNotebookEditorContribs.some(notebookEditorContrib => (notebookEditorContrib.priority ?? RegisteredEditorPriority.default) === RegisteredEditorPriority.default)) {
286
return true;
287
} else {
288
return false;
289
}
290
}
291
292