Skip to content

Commit 820817d

Browse files
committed
Next steps
1 parent 9a0e6af commit 820817d

File tree

12 files changed

+305
-370
lines changed

12 files changed

+305
-370
lines changed

src/client/datascience/editor-integration/cellhashprovider.ts

Lines changed: 68 additions & 55 deletions
Original file line numberDiff line numberDiff line change
@@ -5,9 +5,17 @@ import type { KernelMessage } from '@jupyterlab/services';
55
import * as hashjs from 'hash.js';
66
import { inject, injectable, multiInject, optional } from 'inversify';
77
import stripAnsi from 'strip-ansi';
8-
import { Event, EventEmitter, Position, Range, TextDocumentChangeEvent, TextDocumentContentChangeEvent } from 'vscode';
9-
10-
import { splitMultilineString } from '../../../datascience-ui/common';
8+
import {
9+
Event,
10+
EventEmitter,
11+
Position,
12+
Range,
13+
TextDocumentChangeEvent,
14+
TextDocumentContentChangeEvent,
15+
Uri
16+
} from 'vscode';
17+
18+
import { concatMultilineString, splitMultilineString } from '../../../datascience-ui/common';
1119
import { IDebugService, IDocumentManager } from '../../common/application/types';
1220
import { traceError, traceInfo } from '../../common/logger';
1321

@@ -22,6 +30,8 @@ import {
2230
ICellHashListener,
2331
ICellHashProvider,
2432
IDataScienceFileSystem,
33+
IExecuteOptions,
34+
IExecuteResult,
2535
IFileHashes,
2636
INotebook,
2737
INotebookExecutionLogger
@@ -45,6 +55,14 @@ interface IRangedCellHash extends ICellHash {
4555
// hashes for cells.
4656
@injectable()
4757
export class CellHashProvider implements ICellHashProvider, INotebookExecutionLogger {
58+
public get updated(): Event<void> {
59+
return this.updateEventEmitter.event;
60+
}
61+
62+
// tslint:disable-next-line: no-any
63+
public get postMessage(): Event<{ message: string; payload: any }> {
64+
return this.postEmitter.event;
65+
}
4866
// tslint:disable-next-line: no-any
4967
private postEmitter: EventEmitter<{ message: string; payload: any }> = new EventEmitter<{
5068
message: string;
@@ -73,15 +91,6 @@ export class CellHashProvider implements ICellHashProvider, INotebookExecutionLo
7391
this.traceBackRegexes.clear();
7492
}
7593

76-
public get updated(): Event<void> {
77-
return this.updateEventEmitter.event;
78-
}
79-
80-
// tslint:disable-next-line: no-any
81-
public get postMessage(): Event<{ message: string; payload: any }> {
82-
return this.postEmitter.event;
83-
}
84-
8594
public getHashes(): IFileHashes[] {
8695
return [...this.hashes.entries()]
8796
.map((e) => {
@@ -100,18 +109,18 @@ export class CellHashProvider implements ICellHashProvider, INotebookExecutionLo
100109
this.updateEventEmitter.fire();
101110
}
102111

103-
public async preExecute(cell: ICell, silent: boolean): Promise<void> {
112+
public async preExecute(options: IExecuteOptions): Promise<void> {
104113
try {
105-
if (!silent) {
114+
if (!options.silent) {
106115
// Don't log empty cells
107-
const stripped = this.extractExecutableLines(cell);
116+
const stripped = this.extractExecutableLines(options.code, options.file);
108117
if (stripped.length > 0 && stripped.find((s) => s.trim().length > 0)) {
109118
// When the user adds new code, we know the execution count is increasing
110119
this.executionCount += 1;
111120

112121
// Skip hash on unknown file though
113-
if (cell.file !== Identifiers.EmptyFileName) {
114-
await this.addCellHash(cell, this.executionCount);
122+
if (!options.file && options.file !== Identifiers.EmptyFileName) {
123+
await this.addExecutionHash(options, this.executionCount);
115124
}
116125
}
117126
}
@@ -121,7 +130,7 @@ export class CellHashProvider implements ICellHashProvider, INotebookExecutionLo
121130
}
122131
}
123132

124-
public async postExecute(_cell: ICell, _silent: boolean): Promise<void> {
133+
public async postExecute(_options: IExecuteOptions, _result: IExecuteResult): Promise<void> {
125134
noop();
126135
}
127136

@@ -140,9 +149,11 @@ export class CellHashProvider implements ICellHashProvider, INotebookExecutionLo
140149
return msg;
141150
}
142151

143-
public extractExecutableLines(cell: ICell): string[] {
144-
const cellMatcher = new CellMatcher(this.configService.getSettings(getCellResource(cell)).datascience);
145-
const lines = splitMultilineString(cell.data.source);
152+
public extractExecutableLines(code: string, file?: string): string[] {
153+
const cellMatcher = new CellMatcher(
154+
this.configService.getSettings(file ? Uri.file(file) : undefined).datascience
155+
);
156+
const lines = code.splitLines({ trim: false, removeEmptyEntries: false });
146157
// Only strip this off the first line. Otherwise we want the markers in the code.
147158
if (lines.length > 0 && (cellMatcher.isCode(lines[0]) || cellMatcher.isMarkdown(lines[0]))) {
148159
return lines.slice(1);
@@ -151,23 +162,35 @@ export class CellHashProvider implements ICellHashProvider, INotebookExecutionLo
151162
}
152163

153164
public generateHashFileName(cell: ICell, expectedCount: number): string {
165+
const code = concatMultilineString(cell.data.source);
166+
const file = cell.file;
154167
// First get the true lines from the cell
155-
const { stripped } = this.extractStrippedLines(cell);
168+
const { stripped } = this.extractStrippedLines({ code, file, id: cell.id });
156169

157170
// Then use that to make a hash value
158171
const hashedCode = stripped.join('');
159172
const hash = hashjs.sha1().update(hashedCode).digest('hex').substr(0, 12);
160173
return `<ipython-input-${expectedCount}-${hash}>`;
161174
}
162175

176+
public getExecutionCount(): number {
177+
return this.executionCount;
178+
}
179+
180+
public incExecutionCount(): void {
181+
this.executionCount += 1;
182+
}
183+
163184
// tslint:disable-next-line: cyclomatic-complexity
164-
public async addCellHash(cell: ICell, expectedCount: number): Promise<void> {
185+
private async addExecutionHash(options: IExecuteOptions, expectedCount: number): Promise<void> {
165186
// Find the text document that matches. We need more information than
166187
// the add code gives us
167-
const doc = this.documentManager.textDocuments.find((d) => this.fs.areLocalPathsSame(d.fileName, cell.file));
188+
const doc = this.documentManager.textDocuments.find((d) =>
189+
this.fs.areLocalPathsSame(d.fileName, options.file || '')
190+
);
168191
if (doc) {
169192
// Compute the code that will really be sent to jupyter
170-
const { stripped, trueStartLine } = this.extractStrippedLines(cell);
193+
const { stripped, trueStartLine } = this.extractStrippedLines(options);
171194

172195
const line = doc.lineAt(trueStartLine);
173196
const endLine = doc.lineAt(Math.min(trueStartLine + stripped.length - 1, doc.lineCount - 1));
@@ -180,13 +203,15 @@ export class CellHashProvider implements ICellHashProvider, INotebookExecutionLo
180203

181204
// Use the original values however to track edits. This is what we need
182205
// to move around
183-
const startOffset = doc.offsetAt(new Position(cell.line, 0));
206+
const startOffset = doc.offsetAt(new Position(options.line || 0, 0));
184207
const endOffset = doc.offsetAt(endLine.rangeIncludingLineBreak.end);
185208

186209
// Compute the runtime line and adjust our cell/stripped source for debugging
187-
const runtimeLine = this.adjustRuntimeForDebugging(cell, stripped, startOffset, endOffset);
210+
const runtimeLine = this.adjustRuntimeForDebugging(options, stripped);
188211
const hashedCode = stripped.join('');
189-
const realCode = doc.getText(new Range(new Position(cell.line, 0), endLine.rangeIncludingLineBreak.end));
212+
const realCode = doc.getText(
213+
new Range(new Position(options.line || 0, 0), endLine.rangeIncludingLineBreak.end)
214+
);
190215

191216
const hash: IRangedCellHash = {
192217
hash: hashjs.sha1().update(hashedCode).digest('hex').substr(0, 12),
@@ -201,13 +226,14 @@ export class CellHashProvider implements ICellHashProvider, INotebookExecutionLo
201226
trimmedRightCode: stripped.map((s) => s.replace(/[ \t\r]+\n$/g, '\n')).join(''),
202227
realCode,
203228
runtimeLine,
204-
id: cell.id,
229+
id: options.id,
205230
timestamp: Date.now()
206231
};
207232

208233
traceInfo(`Adding hash for ${expectedCount} = ${hash.hash} with ${stripped.length} lines`);
209234

210-
let list = this.hashes.get(cell.file);
235+
const hashKey = options.file || '';
236+
let list = this.hashes.get(hashKey);
211237
if (!list) {
212238
list = [];
213239
}
@@ -230,15 +256,15 @@ export class CellHashProvider implements ICellHashProvider, INotebookExecutionLo
230256
if (!inserted) {
231257
list.push(hash);
232258
}
233-
this.hashes.set(cell.file, list);
259+
this.hashes.set(hashKey, list);
234260

235261
// Save a regex to find this file later when looking for
236262
// exceptions in output
237-
if (!this.traceBackRegexes.has(cell.file)) {
238-
const fileDisplayName = this.fs.getDisplayName(cell.file);
263+
if (!this.traceBackRegexes.has(hashKey)) {
264+
const fileDisplayName = this.fs.getDisplayName(hashKey);
239265
const escaped = _escapeRegExp(fileDisplayName);
240266
const fileMatchRegex = new RegExp(`\\[.*?;32m${escaped}`);
241-
this.traceBackRegexes.set(cell.file, fileMatchRegex);
267+
this.traceBackRegexes.set(hashKey, fileMatchRegex);
242268
}
243269

244270
// Tell listeners we have new hashes.
@@ -252,14 +278,6 @@ export class CellHashProvider implements ICellHashProvider, INotebookExecutionLo
252278
}
253279
}
254280

255-
public getExecutionCount(): number {
256-
return this.executionCount;
257-
}
258-
259-
public incExecutionCount(): void {
260-
this.executionCount += 1;
261-
}
262-
263281
private onChangedDocument(e: TextDocumentChangeEvent) {
264282
// See if the document is in our list of docs to watch
265283
const perFile = this.hashes.get(e.document.fileName);
@@ -272,14 +290,14 @@ export class CellHashProvider implements ICellHashProvider, INotebookExecutionLo
272290
}
273291
}
274292

275-
private extractStrippedLines(cell: ICell): { stripped: string[]; trueStartLine: number } {
293+
private extractStrippedLines(options: IExecuteOptions): { stripped: string[]; trueStartLine: number } {
276294
// Compute the code that will really be sent to jupyter
277-
const lines = splitMultilineString(cell.data.source);
278-
const stripped = this.extractExecutableLines(cell);
295+
const lines = options.code.splitLines({ trim: false, removeEmptyEntries: false });
296+
const stripped = this.extractExecutableLines(options.code, options.file);
279297

280298
// Figure out our true 'start' line. This is what we need to tell the debugger is
281299
// actually the start of the code as that's what Jupyter will be getting.
282-
let trueStartLine = cell.line;
300+
let trueStartLine = options.line || 0;
283301
for (let i = 0; i < stripped.length; i += 1) {
284302
if (stripped[i] !== lines[i]) {
285303
trueStartLine += i + 1;
@@ -355,20 +373,15 @@ export class CellHashProvider implements ICellHashProvider, INotebookExecutionLo
355373
});
356374
}
357375

358-
private adjustRuntimeForDebugging(
359-
cell: ICell,
360-
source: string[],
361-
_cellStartOffset: number,
362-
_cellEndOffset: number
363-
): number {
376+
private adjustRuntimeForDebugging(options: IExecuteOptions, source: string[]): number {
364377
if (
365378
this.debugService.activeDebugSession &&
366-
this.configService.getSettings(getCellResource(cell)).datascience.stopOnFirstLineWhileDebugging
379+
this.configService.getSettings(options.file ? Uri.file(options.file) : undefined).datascience
380+
.stopOnFirstLineWhileDebugging
367381
) {
368382
// Inject the breakpoint line
369383
source.splice(0, 0, 'breakpoint()\n');
370-
cell.data.source = source;
371-
cell.extraLines = [0];
384+
options.code = `breakpoint()\n${options.code}`;
372385

373386
// Start on the second line
374387
return 2;

src/client/datascience/editor-integration/hoverProvider.ts

Lines changed: 6 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -14,8 +14,9 @@ import { sleep } from '../../common/utils/async';
1414
import { noop } from '../../common/utils/misc';
1515
import { Identifiers } from '../constants';
1616
import {
17-
ICell,
1817
IDataScienceFileSystem,
18+
IExecuteOptions,
19+
IExecuteResult,
1920
IInteractiveWindowProvider,
2021
IJupyterVariables,
2122
INotebook,
@@ -50,11 +51,11 @@ export class HoverProvider implements INotebookExecutionLogger, vscode.HoverProv
5051
this.runFiles.clear();
5152
}
5253

53-
public async preExecute(cell: ICell, silent: boolean): Promise<void> {
54+
public async preExecute(options: IExecuteOptions): Promise<void> {
5455
try {
55-
if (!silent && cell.file && cell.file !== Identifiers.EmptyFileName) {
56+
if (!options.silent && options.file && options.file !== Identifiers.EmptyFileName) {
5657
const size = this.runFiles.size;
57-
this.runFiles.add(cell.file.toLocaleLowerCase());
58+
this.runFiles.add(options.file.toLocaleLowerCase());
5859
if (size !== this.runFiles.size) {
5960
this.initializeHoverProvider();
6061
}
@@ -65,7 +66,7 @@ export class HoverProvider implements INotebookExecutionLogger, vscode.HoverProv
6566
}
6667
}
6768

68-
public async postExecute(_cell: ICell, _silent: boolean): Promise<void> {
69+
public async postExecute(_options: IExecuteOptions, _result: IExecuteResult): Promise<void> {
6970
noop();
7071
}
7172

src/client/datascience/gather/gatherLogger.ts

Lines changed: 19 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -1,15 +1,12 @@
11
import { inject, injectable } from 'inversify';
2-
// tslint:disable-next-line: no-require-imports
3-
import cloneDeep = require('lodash/cloneDeep');
42
import { extensions } from 'vscode';
5-
import { concatMultilineString } from '../../../datascience-ui/common';
63
import { traceError } from '../../common/logger';
74
import { IConfigurationService } from '../../common/types';
85
import { noop } from '../../common/utils/misc';
96
import { sendTelemetryEvent } from '../../telemetry';
107
import { CellMatcher } from '../cellMatcher';
11-
import { GatherExtension, Telemetry } from '../constants';
12-
import { ICell as IVscCell, IGatherLogger, IGatherProvider } from '../types';
8+
import { GatherExtension, Identifiers, Telemetry } from '../constants';
9+
import { IExecuteOptions, IExecuteResult, IGatherLogger, IGatherProvider } from '../types';
1310

1411
@injectable()
1512
export class GatherLogger implements IGatherLogger {
@@ -25,25 +22,34 @@ export class GatherLogger implements IGatherLogger {
2522
noop();
2623
}
2724

28-
public async preExecute(_vscCell: IVscCell, _silent: boolean): Promise<void> {
25+
public async preExecute(_options: IExecuteOptions): Promise<void> {
2926
// This function is just implemented here for compliance with the INotebookExecutionLogger interface
3027
noop();
3128
}
3229

33-
public async postExecute(vscCell: IVscCell, _silent: boolean): Promise<void> {
30+
public async postExecute(options: IExecuteOptions, result: IExecuteResult): Promise<void> {
3431
if (this.gather) {
3532
// Don't log if vscCell.data.source is an empty string or if it was
3633
// silently executed. Original Jupyter extension also does this.
37-
if (vscCell.data.source !== '' && !_silent) {
38-
// First make a copy of this cell, as we are going to modify it
39-
const cloneCell: IVscCell = cloneDeep(vscCell);
40-
34+
if (options.code !== '' && !options.silent) {
4135
// Strip first line marker. We can't do this at JupyterServer.executeCodeObservable because it messes up hashing
4236
const cellMatcher = new CellMatcher(this.configService.getSettings().datascience);
43-
cloneCell.data.source = cellMatcher.stripFirstMarker(concatMultilineString(vscCell.data.source));
37+
const code = cellMatcher.stripFirstMarker(options.code);
4438

4539
try {
46-
this.gather.logExecution(cloneCell);
40+
this.gather.logExecution({
41+
id: options.id,
42+
file: options.file || Identifiers.EmptyFileName,
43+
line: 0,
44+
state: result.state,
45+
data: {
46+
cell_type: 'code',
47+
outputs: result.outputs,
48+
execution_count: result.execution_count,
49+
metadata: options.metadata || {},
50+
source: code
51+
}
52+
});
4753
} catch (e) {
4854
traceError('Gather: Exception at Log Execution', e);
4955
sendTelemetryEvent(Telemetry.GatherException, undefined, { exceptionType: 'log' });

0 commit comments

Comments
 (0)