Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
microsoft
GitHub Repository: microsoft/vscode
Path: blob/main/extensions/copilot/src/platform/notebook/common/alternativeNotebookTextDocument.ts
13401 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 { NotebookCell, NotebookDocument, NotebookDocumentContentChange, TextDocument, TextDocumentContentChangeEvent } from 'vscode';
7
import { coalesce } from '../../../util/vs/base/common/arrays';
8
import { findLastIdxMonotonous } from '../../../util/vs/base/common/arraysFind';
9
import { StringEdit } from '../../../util/vs/editor/common/core/edits/stringEdit';
10
import { OffsetRange } from '../../../util/vs/editor/common/core/ranges/offsetRange';
11
import { NotebookCellKind, Position, Range } from '../../../vscodeTypes';
12
import { stringEditFromTextContentChange } from '../../editing/common/edit';
13
import { PositionOffsetTransformer } from '../../editing/common/positionOffsetTransformer';
14
import { generateCellTextMarker, getBlockComment, getLineCommentStart } from './alternativeContentProvider.text';
15
import { EOL, summarize } from './helpers';
16
import { CrLfOffsetTranslator } from './offsetTranslator';
17
18
19
class AlternativeNotebookCellSnapshot {
20
private readonly positionTransformer: PositionOffsetTransformer;
21
private readonly crlfTranslator: CrLfOffsetTranslator;
22
public readonly lineCount: number;
23
/** Range of the alternative cell code */
24
public readonly altRange: Range;
25
/** Last line in the actual cell code */
26
private readonly lastLineLength: number;
27
public static fromNotebookCell(cell: NotebookCell, blockComment: [string, string], lineCommentStart: string): AlternativeNotebookCellSnapshot {
28
const summary = summarize(cell);
29
const cellMarker = generateCellTextMarker(summary, lineCommentStart);
30
const code = cell.document.getText().replace(/\r\n|\n/g, EOL);
31
const prefix = cell.kind === NotebookCellKind.Markup ? `${cellMarker}${EOL}${blockComment[0]}${EOL}` : `${cellMarker}${EOL}`;
32
const suffix = cell.kind === NotebookCellKind.Markup ? `${EOL}${blockComment[1]}` : '';
33
return new AlternativeNotebookCellSnapshot(cell, blockComment, lineCommentStart, code, prefix, suffix);
34
}
35
constructor(
36
public readonly cell: NotebookCell,
37
private readonly blockComment: [string, string],
38
private readonly lineCommentStart: string,
39
private readonly code: string,
40
private readonly prefix: string,
41
private readonly suffix: string
42
) {
43
this.crlfTranslator = new CrLfOffsetTranslator(cell.document.getText(), cell.document.eol);
44
this.positionTransformer = new PositionOffsetTransformer(`${prefix}${code}${suffix}`);
45
const lastPosition = this.positionTransformer.getPosition(this.positionTransformer.getText().length);
46
this.altRange = new Range(0, 0, lastPosition.line, lastPosition.character);
47
this.lineCount = this.altRange.end.line + 1;
48
this.lastLineLength = this.suffix.length === 0 ? this.altRange.end.character : this.positionTransformer.getPosition(this.positionTransformer.getText().length - this.suffix.length).character;
49
}
50
51
public normalizeEdits(edits: readonly TextDocumentContentChangeEvent[]): TextDocumentContentChangeEvent[] {
52
return edits.map(e => {
53
const range = this.toAltRange(e.range);
54
const rangeOffset = this.crlfTranslator.translate(e.rangeOffset);
55
const endOffset = this.crlfTranslator.translate(e.rangeOffset + e.rangeLength);
56
return {
57
range,
58
rangeLength: endOffset - rangeOffset,
59
rangeOffset,
60
text: e.text.replace(/\r\n|\n/g, EOL), // Normalize line endings to EOL
61
};
62
});
63
}
64
65
public withTextEdit(edit: StringEdit): AlternativeNotebookCellSnapshot {
66
const newCode = edit.apply(this.code);
67
return new AlternativeNotebookCellSnapshot(this.cell, this.blockComment, this.lineCommentStart, newCode, this.prefix, this.suffix);
68
}
69
70
public get altText(): string {
71
return this.positionTransformer.getText();
72
}
73
74
public toAltOffsetRange(range: Range): OffsetRange {
75
const startOffset = this.toAltOffset(range.start);
76
const endOffset = this.toAltOffset(range.end);
77
return new OffsetRange(startOffset, endOffset);
78
}
79
80
public toAltOffset(position: Position): number {
81
// Remove the lines we've added for the cell marker and block comments
82
const extraLinesAdded = this.cell.kind === NotebookCellKind.Markup ? 2 : 1;
83
return this.positionTransformer.getOffset(new Position(position.line + extraLinesAdded, position.character));
84
}
85
86
public toAltRange(range: Range): Range {
87
// Remove the lines we've added for the cell marker and block comments
88
const extraLinesAdded = this.cell.kind === NotebookCellKind.Markup ? 2 : 1;
89
return new Range(range.start.line + extraLinesAdded, range.start.character, range.end.line + extraLinesAdded, range.end.character);
90
}
91
92
public fromAltOffsetRange(offsetRange: OffsetRange): Range {
93
const startOffset = offsetRange.start;
94
const endOffset = offsetRange.endExclusive;
95
const startPosition = this.positionTransformer.getPosition(startOffset);
96
const endPosition = this.positionTransformer.getPosition(endOffset);
97
98
// Remove the lines we've added for the cell marker and block comments
99
const extraLinesAddedAtStart = this.cell.kind === NotebookCellKind.Markup ? 2 : 1;
100
const extraLinesAddedAtEnd = this.cell.kind === NotebookCellKind.Markup ? 1 : 0;
101
102
const startLine = Math.max(startPosition.line - extraLinesAddedAtStart, 0);
103
const lastLineIndex = (this.lineCount - extraLinesAddedAtEnd) - 1;
104
let endLine = endPosition.line;
105
let endLineEndColumn = endPosition.character;
106
if (endLine > lastLineIndex) {
107
endLineEndColumn = endLineEndColumn === 0 ? endLineEndColumn : -1;
108
endLine = lastLineIndex - extraLinesAddedAtStart;
109
} else {
110
endLine = Math.max(endPosition.line - extraLinesAddedAtStart, 0);
111
}
112
if (endLine === (lastLineIndex - extraLinesAddedAtStart)) {
113
if (endLineEndColumn !== 0 && endLineEndColumn === -1 || this.lastLineLength < endLineEndColumn) {
114
endLineEndColumn = this.lastLineLength;
115
}
116
}
117
// If the original start was in a line that part of the prefix, then we need to start from line 0, character 0.
118
const startCharacter = startPosition.line - extraLinesAddedAtStart >= 0 ? startPosition.character : 0;
119
return new Range(startLine, startCharacter, endLine, endLineEndColumn);
120
}
121
public fromAltRange(range: Range): Range {
122
// Remove the lines we've added for the cell marker and block comments
123
const extraLinesAdded = this.cell.kind === NotebookCellKind.Markup ? 2 : 1;
124
const extraLinesAddedAtEnd = this.cell.kind === NotebookCellKind.Markup ? 1 : 0;
125
126
const startLine = Math.max(range.start.line - extraLinesAdded, 0);
127
const isInvalidStartLine = extraLinesAdded ? (range.start.line + 1) <= extraLinesAdded : false;
128
const startCharacter = isInvalidStartLine ? 0 : range.start.character;
129
const isEndLineInvalid = extraLinesAddedAtEnd > 0 && (range.end.line === this.lineCount - 1);
130
const endLine = isEndLineInvalid ? (this.lineCount - extraLinesAdded - extraLinesAddedAtEnd - 1) : Math.max(range.end.line - extraLinesAdded, 0);
131
const lastLineIndex = (this.lineCount - extraLinesAdded - extraLinesAddedAtEnd) - 1;
132
const endLineCharacter = isEndLineInvalid ? this.lastLineLength : (endLine === lastLineIndex) ? Math.min(range.end.character, this.lastLineLength) : range.end.character;
133
return new Range(startLine, startCharacter, endLine, endLineCharacter);
134
}
135
}
136
137
function buildAlternativeCells<T>(cellItems: readonly T[], altCelBuilder: (cellItem: T) => AlternativeNotebookCellSnapshot) {
138
let lineCount = 0;
139
let offset = 0;
140
return cellItems.map(item => {
141
const altCell = altCelBuilder(item);
142
const startLine = lineCount;
143
const startOffset = offset;
144
lineCount += altCell.lineCount;
145
offset += altCell.altText.length + EOL.length; // EOL is added between cells
146
return { altCell, startLine, startOffset };
147
});
148
}
149
150
type AltCellInfo = {
151
altCell: AlternativeNotebookCellSnapshot;
152
/** Line number at which this cell starts within the Alternative Notebook */
153
startLine: number;
154
/** Character offset at which this cell starts within the Alternative Notebook */
155
startOffset: number;
156
};
157
158
abstract class AbstractAlternativeNotebookDocument {
159
private readonly cellTextDocuments = new Map<TextDocument, NotebookCell>();
160
public constructor(public readonly notebook: NotebookDocument,
161
public readonly excludeMarkdownCells: boolean,
162
public readonly blockComment: [string, string],
163
public readonly lineCommentStart: string,
164
public readonly cells: readonly AltCellInfo[]) {
165
for (const { altCell } of this.cells) {
166
this.cellTextDocuments.set(altCell.cell.document, altCell.cell);
167
}
168
}
169
170
/**
171
* Get the cell associated with a text document.
172
* @param textDocument The text document to find the cell for.
173
* @returns The notebook cell associated with the text document, or undefined if not found.
174
* If a cell was inserted into the notebook and this instance hasn't been updated yet, it will return undefined.
175
*/
176
public getCell(textDocument: TextDocument): NotebookCell | undefined {
177
return this.cellTextDocuments.get(textDocument);
178
}
179
180
public getText(range?: OffsetRange): string {
181
const altText = this.cells.map(cell => cell.altCell.altText).join(EOL);
182
return range ? range.substring(altText) : altText;
183
}
184
185
public fromAltRange(range: Range): [NotebookCell, Range][] {
186
const firstIdx = findLastIdxMonotonous(this.cells, c => c.startLine <= range.start.line);
187
if (firstIdx === -1) {
188
return [];
189
}
190
const cells: [NotebookCell, Range][] = [];
191
192
for (let i = firstIdx; i < this.cells.length; i++) {
193
const { altCell, startLine } = this.cells[i];
194
if (i === firstIdx) {
195
const cellStartLine = range.start.line - startLine;
196
const cellEndLine = range.end.line - startLine;
197
const cellEnd = cellEndLine <= (altCell.lineCount - 1) ? cellEndLine : altCell.lineCount - 1;
198
let cellEndChar = range.end.character;
199
if (cellEnd !== cellEndLine) {
200
cellEndChar = altCell.altRange.end.character;
201
}
202
const cellRange = new Range(cellStartLine, range.start.character, cellEnd, cellEndChar);
203
cells.push([altCell.cell, altCell.fromAltRange(cellRange)]);
204
} else if (startLine + altCell.lineCount <= range.end.line) {
205
const cellRange = new Range(0, 0, altCell.altRange.end.line, altCell.altRange.end.character);
206
cells.push([altCell.cell, altCell.fromAltRange(cellRange)]);
207
} else if (startLine < range.end.line) {
208
const cellRange = new Range(0, 0, range.end.line - startLine, range.end.character);
209
cells.push([altCell.cell, altCell.fromAltRange(cellRange)]);
210
}
211
}
212
213
return cells;
214
}
215
216
public fromAltOffsetRange(offsetRange: OffsetRange): [NotebookCell, Range][] {
217
const firstIdx = findLastIdxMonotonous(this.cells, c => c.startOffset <= offsetRange.start);
218
if (firstIdx === -1) {
219
return [];
220
}
221
const cells: [NotebookCell, Range][] = [];
222
223
for (let i = firstIdx; i < this.cells.length; i++) {
224
const { altCell, startOffset } = this.cells[i];
225
if (i === firstIdx) {
226
const endOffset = offsetRange.endExclusive > (startOffset + altCell.altText.length) ? (startOffset + altCell.altText.length) : offsetRange.endExclusive;
227
const offset = new OffsetRange(offsetRange.start - startOffset, endOffset - startOffset);
228
cells.push([altCell.cell, altCell.fromAltOffsetRange(offset)]);
229
} else if ((startOffset + altCell.altText.length) < offsetRange.endExclusive) {
230
const offset = new OffsetRange(0, altCell.altText.length);
231
cells.push([altCell.cell, altCell.fromAltOffsetRange(offset)]);
232
} else if (startOffset < offsetRange.endExclusive) {
233
const offset = new OffsetRange(0, offsetRange.endExclusive - startOffset);
234
cells.push([altCell.cell, altCell.fromAltOffsetRange(offset)]);
235
}
236
}
237
238
return cells;
239
}
240
241
public toAltOffset(cell: NotebookCell, position: Position): number | undefined {
242
const altCell = this.cells.find(c => c.altCell.cell === cell);
243
if (altCell) {
244
return altCell.altCell.toAltOffset(position);
245
} else {
246
return undefined;
247
}
248
}
249
250
public toAltOffsetRange(cell: NotebookCell, ranges: readonly Range[]): OffsetRange[] {
251
let offset = 0;
252
for (const { altCell } of this.cells) {
253
if (altCell.cell === cell) {
254
return ranges.map(range => {
255
const offsetRange = altCell.toAltOffsetRange(range);
256
const adjustedRange = new OffsetRange(offset + offsetRange.start, offset + offsetRange.endExclusive);
257
return adjustedRange;
258
});
259
} else {
260
offset += altCell.altText.length + EOL.length; // EOL is added between cells
261
}
262
}
263
return [];
264
}
265
266
public toAltRange(cell: NotebookCell, ranges: readonly Range[]): Range[] {
267
let offset = 0;
268
for (const { altCell, startLine } of this.cells) {
269
if (altCell.cell === cell) {
270
return ranges.map(range => {
271
const altCellRange = altCell.toAltRange(range);
272
const adjustedRange = new Range(altCellRange.start.line + startLine, altCellRange.start.character, altCellRange.end.line + startLine, altCellRange.end.character);
273
return adjustedRange;
274
});
275
} else {
276
offset += altCell.altText.length + EOL.length; // EOL is added between cells
277
}
278
}
279
return [];
280
}
281
}
282
283
export interface IAlternativeNotebookDocumentSnapshot extends AbstractAlternativeNotebookDocument {
284
withNotebookChanges(events: readonly NotebookDocumentContentChange[]): AlternativeNotebookDocumentSnapshot;
285
withCellChanges(cellTextDoc: TextDocument, edit: readonly TextDocumentContentChangeEvent[]): AlternativeNotebookDocumentSnapshot;
286
}
287
288
class AlternativeNotebookDocumentSnapshot extends AbstractAlternativeNotebookDocument implements IAlternativeNotebookDocumentSnapshot {
289
public static create(notebook: NotebookDocument, excludeMarkdownCells: boolean): AlternativeNotebookDocumentSnapshot {
290
const blockComment = getBlockComment(notebook);
291
const lineCommentStart = getLineCommentStart(notebook);
292
const notebookCells = notebook.getCells().filter(cell => !excludeMarkdownCells || cell.kind !== NotebookCellKind.Markup);
293
const altCells = buildAlternativeCells(notebookCells, cell => AlternativeNotebookCellSnapshot.fromNotebookCell(cell, blockComment, lineCommentStart));
294
295
return new AlternativeNotebookDocumentSnapshot(notebook, excludeMarkdownCells, blockComment, lineCommentStart, altCells);
296
}
297
constructor(notebook: NotebookDocument,
298
excludeMarkdownCells: boolean,
299
blockComment: [string, string],
300
lineCommentStart: string,
301
altCells: readonly AltCellInfo[]) {
302
super(notebook, excludeMarkdownCells, blockComment, lineCommentStart, altCells);
303
}
304
305
public withNotebookChanges(events: readonly NotebookDocumentContentChange[]): AlternativeNotebookDocumentSnapshot {
306
const cells = withNotebookChangesAndEdit(this.cells, this.blockComment, this.lineCommentStart, events, this.excludeMarkdownCells)[0];
307
return new AlternativeNotebookDocumentSnapshot(this.notebook, this.excludeMarkdownCells, this.blockComment, this.lineCommentStart, cells);
308
}
309
310
public withCellChanges(cellTextDoc: TextDocument, edit: readonly TextDocumentContentChangeEvent[]): AlternativeNotebookDocumentSnapshot {
311
if (edit instanceof StringEdit ? edit.isEmpty() : edit.length === 0) {
312
return this;
313
}
314
const [altCells,] = withCellChangesAndEdit(this.cells, cellTextDoc, edit) || [undefined, undefined] as const;
315
if (!altCells) {
316
return this;
317
}
318
return new AlternativeNotebookDocumentSnapshot(this.notebook, this.excludeMarkdownCells, this.blockComment, this.lineCommentStart, altCells);
319
}
320
}
321
322
export interface IAlternativeNotebookDocument extends AbstractAlternativeNotebookDocument {
323
applyNotebookChanges(events: readonly NotebookDocumentContentChange[]): void;
324
applyCellChanges(cellTextDoc: TextDocument, edit: readonly TextDocumentContentChangeEvent[]): void;
325
}
326
327
328
class AlternativeNotebookDocument extends AbstractAlternativeNotebookDocument implements IAlternativeNotebookDocument {
329
public static create(notebook: NotebookDocument, excludeMarkdownCells: boolean): AlternativeNotebookDocument {
330
const blockComment = getBlockComment(notebook);
331
const lineCommentStart = getLineCommentStart(notebook);
332
const notebookCells = notebook.getCells().filter(cell => !excludeMarkdownCells || cell.kind !== NotebookCellKind.Markup);
333
const altCells = buildAlternativeCells(notebookCells, cell => AlternativeNotebookCellSnapshot.fromNotebookCell(cell, blockComment, lineCommentStart));
334
335
return new AlternativeNotebookDocument(notebook, excludeMarkdownCells, blockComment, lineCommentStart, altCells);
336
}
337
constructor(notebook: NotebookDocument,
338
excludeMarkdownCells: boolean,
339
blockComment: [string, string],
340
lineCommentStart: string,
341
public override cells: AltCellInfo[]) {
342
super(notebook, excludeMarkdownCells, blockComment, lineCommentStart, cells);
343
}
344
345
private updateCells(cells: readonly AltCellInfo[]) {
346
this.cells.splice(0, this.cells.length, ...cells);
347
}
348
public applyNotebookChanges(events: readonly NotebookDocumentContentChange[]) {
349
const cells = withNotebookChangesAndEdit(this.cells, this.blockComment, this.lineCommentStart, events, this.excludeMarkdownCells)[0];
350
this.updateCells(cells);
351
}
352
353
public applyCellChanges(cellTextDoc: TextDocument, edit: readonly TextDocumentContentChangeEvent[]) {
354
if (edit instanceof StringEdit ? edit.isEmpty() : edit.length === 0) {
355
return;
356
}
357
const [cells,] = withCellChangesAndEdit(this.cells, cellTextDoc, edit) || [undefined, undefined] as const;
358
if (!cells) {
359
return;
360
}
361
this.updateCells(cells);
362
}
363
}
364
365
function withCellChangesAndEdit(cells: readonly AltCellInfo[], cellTextDoc: TextDocument, edit: readonly TextDocumentContentChangeEvent[]) {
366
if (edit instanceof StringEdit ? edit.isEmpty() : edit.length === 0) {
367
return undefined;
368
}
369
const cell = cells.find(c => c.altCell.cell.document === cellTextDoc);
370
if (!cell) {
371
return undefined;
372
}
373
const cellEdit = edit instanceof StringEdit ? edit : stringEditFromTextContentChange(cell.altCell.normalizeEdits(edit));
374
const altCells = buildAlternativeCells(cells, cell => cell.altCell.cell.document === cellTextDoc ? cell.altCell.withTextEdit(cellEdit) : cell.altCell);
375
return [altCells, edit] as const;
376
}
377
378
function withNotebookChangesAndEdit(cells: readonly AltCellInfo[], blockComment: [string, string], lineCommentStart: string, events: readonly NotebookDocumentContentChange[], excludeMarkdownCells: boolean): [readonly AltCellInfo[], StringEdit | undefined] {
379
if (!events.length) {
380
return [cells, undefined];
381
}
382
// If we've only added md cells, then its a noop.
383
if (events.every(e => e.removedCells.length === 0 && e.addedCells.every(c => c.kind === NotebookCellKind.Markup))) {
384
return [cells, undefined];
385
}
386
let altCells = cells.slice();
387
let edit = StringEdit.empty;
388
for (const event of events) {
389
const newCells = event.addedCells.filter(c => excludeMarkdownCells ? c.kind === NotebookCellKind.Code : true).map(cell => ({ altCell: AlternativeNotebookCellSnapshot.fromNotebookCell(cell, blockComment, lineCommentStart), startLine: 0, startOffset: 0 }));
390
391
const removedCells = altCells.slice(event.range.start, event.range.end);
392
let firstUnChangedCellIndex = -1;
393
if (event.range.isEmpty) {
394
firstUnChangedCellIndex = event.range.start === 0 ? -1 : event.range.start - 1;
395
} else {
396
firstUnChangedCellIndex = event.range.start === 0 ? -1 : event.range.start - 1;
397
}
398
const startOffset = firstUnChangedCellIndex === -1 ? 0 : altCells[firstUnChangedCellIndex].startOffset + altCells[firstUnChangedCellIndex].altCell.altText.length + EOL.length;
399
let offsetLength = removedCells.map((cell) => cell.altCell.altText).join(EOL).length;
400
let newCellsContent = newCells.map((cell) => cell.altCell.altText).join(EOL);
401
if (startOffset !== 0) {
402
if (!(event.range.end < altCells.length)) {
403
newCellsContent = `${EOL}${newCellsContent}`;
404
}
405
}
406
// if we have some cells after the insertion, then we need to insert an EOL at the end.
407
if (event.range.end < altCells.length) {
408
if (newCellsContent) {
409
newCellsContent += EOL;
410
}
411
if (offsetLength) {
412
offsetLength += EOL.length;
413
}
414
}
415
edit = edit.compose(StringEdit.replace(new OffsetRange(startOffset, startOffset + offsetLength), newCellsContent));
416
417
altCells.splice(event.range.start, event.range.end - event.range.start, ...newCells);
418
altCells = buildAlternativeCells(altCells, cell => cell.altCell);
419
}
420
421
return [altCells, edit];
422
}
423
424
/**
425
* Represents the Notebook as a alternative text (Jupytext like) document that is mutable.
426
* Not to be used when dealing with agents for editing or reading notebooks.
427
* Use only with NES or other exceptional cases.
428
*/
429
export function createAlternativeNotebookDocument(notebook: NotebookDocument, excludeMarkdownCells: boolean = true): IAlternativeNotebookDocument {
430
return AlternativeNotebookDocument.create(notebook, excludeMarkdownCells);
431
}
432
433
/**
434
* Represents the Notebook as an alternative text (Jupytext like) document that is immutable.
435
* Not to be used when dealing with agents for editing or reading notebooks.
436
* Use only with NES or other exceptional cases.
437
*/
438
export function createAlternativeNotebookDocumentSnapshot(notebook: NotebookDocument, excludeMarkdownCells: boolean = true): IAlternativeNotebookDocumentSnapshot {
439
return AlternativeNotebookDocumentSnapshot.create(notebook, excludeMarkdownCells);
440
}
441
442
export function toAltNotebookCellChangeEdit(notebook: AbstractAlternativeNotebookDocument, cellTextDocument: TextDocument, events: readonly TextDocumentContentChangeEvent[]): StringEdit {
443
const replacementsInApplicationOrder = toAltCellTextDocumentContentChangeEvents(notebook, cellTextDocument, events);
444
return stringEditFromTextContentChange(replacementsInApplicationOrder);
445
}
446
447
export function toAltNotebookChangeEdit(notebook: AbstractAlternativeNotebookDocument, events: readonly NotebookDocumentContentChange[]): StringEdit | undefined {
448
return withNotebookChangesAndEdit(notebook.cells, notebook.blockComment, notebook.lineCommentStart, events, notebook.excludeMarkdownCells)[1];
449
}
450
451
function toAltCellTextDocumentContentChangeEvents(notebook: AbstractAlternativeNotebookDocument, cellTextDocument: TextDocument, events: readonly TextDocumentContentChangeEvent[]): TextDocumentContentChangeEvent[] {
452
return coalesce(events.map(e => {
453
const cell = notebook.getCell(cellTextDocument);
454
if (!cell) {
455
return undefined;
456
}
457
const ranges = notebook.toAltRange(cell, [e.range]);
458
const rangeOffsets = notebook.toAltOffsetRange(cell, [e.range]);
459
if (!ranges.length || !rangeOffsets.length) {
460
return undefined;
461
}
462
const range = ranges[0];
463
const rangeOffset = rangeOffsets[0];
464
return {
465
range,
466
rangeLength: rangeOffset.endExclusive - rangeOffset.start,
467
rangeOffset: rangeOffset.start,
468
text: e.text.replace(/\r\n|\n/g, EOL), // Normalize line endings to EOL
469
} as typeof e;
470
}));
471
}
472
473