Skip to content

Commit c1d8619

Browse files
authored
Run by line partial implementation (#11608)
* Preliminary getting variables from the debugger * Implement updating as stepping * Get debugger and regular kernel to use same scripts * Put back old code and create experiment to enable/disable this * Implement experiment and begin on test for debugger * Finish fixing tests * Add news entry * Create multiplexer * Basic idea of glyph updating and control * Forget the svgs * Rework to go through interactive path * Highlight line kinda showing up * Fix variables to come out. * Fix ip line sticking around too long * Colors and icon working for ip * Implement F10 and F5 * Hover and step into icons * Remove unnecessary variables and support step into * Update frame id for variable * Fixup after merge * Exclude everything glob for just my code * Try fixing order for messages to debugger Remove exclusions as they mess up subsequent runs * Fix request problem and dark theme * Add news entry * Some extra comments and fix sonar problems * Add loc strings and make constants for code icons * Sonar problems and removing unused code * Change name to debugInfo * Try again to exclude the codicon files * Wrong base directory * Fixes from code review and run by line new icon * Fix functional test failures * Fix unit test failure (yay, caught a bug) * Fix stupid sonar warnings * Fix some more failing tests
1 parent f4c1ac3 commit c1d8619

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

60 files changed

+2102
-521
lines changed

.sonarcloud.properties

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
sonar.sources=src/client,src/datascience-ui
2+
sonar.exclusions=src/datascience-ui/**/codicon*.*
23
sonar.tests=src/test
34
sonar.cfamily.build-wrapper-output.bypass=true
45
sonar.cpd.exclusions=src/datascience-ui/**/redux/actions.ts,src/client/**/raw-kernel/rawKernel.ts,src/client/datascience/jupyter/*ariable*.ts

build/webpack/loaders/externalizeDependencies.js

Lines changed: 9 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -2,17 +2,21 @@
22
// Licensed under the MIT License.
33

44
const common = require('../common');
5-
function replaceModule(contents, moduleName, quotes) {
6-
const stringToSearch = `${quotes}${moduleName}${quotes}`;
7-
const stringToReplaceWith = `${quotes}./node_modules/${moduleName}${quotes}`;
5+
function replaceModule(prefixRegex, prefix, contents, moduleName, quotes) {
6+
const stringToSearch = `${prefixRegex}${quotes}${moduleName}${quotes}`;
7+
const stringToReplaceWith = `${prefix}${quotes}./node_modules/${moduleName}${quotes}`;
88
return contents.replace(new RegExp(stringToSearch, 'gm'), stringToReplaceWith);
99
}
1010
// tslint:disable:no-default-export no-invalid-this
1111
function default_1(source) {
1212
common.nodeModulesToReplacePaths.forEach((moduleName) => {
1313
if (source.indexOf(moduleName) > 0) {
14-
source = replaceModule(source, moduleName, '"');
15-
source = replaceModule(source, moduleName, "'");
14+
source = replaceModule('import\\(', 'import(', source, moduleName, '"');
15+
source = replaceModule('import\\(', 'import(', source, moduleName, "'");
16+
source = replaceModule('require\\(', 'require(', source, moduleName, '"');
17+
source = replaceModule('require\\(', 'require(', source, moduleName, "'");
18+
source = replaceModule('from ', 'from ', source, moduleName, '"');
19+
source = replaceModule('from ', 'from ', source, moduleName, "'");
1620
}
1721
});
1822
return source;

news/3 Code Health/11607.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
Run by line for notebook cells minimal implementation.

package.nls.json

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -489,5 +489,7 @@
489489
"DataScience.unhandledMessage": "Unhandled kernel message from a widget: {0} : {1}",
490490
"DataScience.qgridWidgetScriptVersionCompatibilityWarning": "Unable to load a compatible version of the widget 'qgrid'. Consider downgrading to version 1.1.1.",
491491
"DataScience.kernelStarted": "Started kernel {0}",
492-
"DataScience.libraryRequiredToLaunchJupyterKernelNotInstalledInterpreter": "Data Science library {1} is not installed in interpreter {0}. Install?"
492+
"DataScience.libraryRequiredToLaunchJupyterKernelNotInstalledInterpreter": "Data Science library {1} is not installed in interpreter {0}. Install?",
493+
"DataScience.runByLine": "Run by line",
494+
"DataScience.continueRunByLine": "Stop"
493495
}

src/client/common/utils/localize.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -925,6 +925,8 @@ export namespace DataScience {
925925
);
926926

927927
export const kernelStarted = localize('DataScience.kernelStarted', 'Started kernel {0}.');
928+
export const runByLine = localize('DataScience.runByLine', 'Run by line');
929+
export const continueRunByLine = localize('DataScience.continueRunByLine', 'Stop');
928930
}
929931

930932
export namespace DebugConfigStrings {

src/client/datascience/constants.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -404,6 +404,8 @@ export namespace Identifiers {
404404
export const OLD_VARIABLES = 'OLD_VARIABLES';
405405
export const KERNEL_VARIABLES = 'KERNEL_VARIABLES';
406406
export const DEBUGGER_VARIABLES = 'DEBUGGER_VARIABLES';
407+
export const MULTIPLEXING_DEBUGSERVICE = 'MULTIPLEXING_DEBUGSERVICE';
408+
export const RUN_BY_LINE_DEBUGSERVICE = 'RUN_BY_LINE_DEBUGSERVICE';
407409
}
408410

409411
export namespace CodeSnippits {

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

Lines changed: 67 additions & 34 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@ import {
2222
ICellHashListener,
2323
ICellHashProvider,
2424
IFileHashes,
25+
INotebook,
2526
INotebookExecutionLogger
2627
} from '../types';
2728

@@ -148,25 +149,25 @@ export class CellHashProvider implements ICellHashProvider, INotebookExecutionLo
148149
return lines;
149150
}
150151

152+
public generateHashFileName(cell: ICell, expectedCount: number): string {
153+
// First get the true lines from the cell
154+
const { stripped } = this.extractStrippedLines(cell);
155+
156+
// Then use that to make a hash value
157+
const hashedCode = stripped.join('');
158+
const hash = hashjs.sha1().update(hashedCode).digest('hex').substr(0, 12);
159+
return `<ipython-input-${expectedCount}-${hash}>`;
160+
}
161+
151162
// tslint:disable-next-line: cyclomatic-complexity
152163
public async addCellHash(cell: ICell, expectedCount: number): Promise<void> {
153164
// Find the text document that matches. We need more information than
154165
// the add code gives us
155166
const doc = this.documentManager.textDocuments.find((d) => this.fileSystem.arePathsSame(d.fileName, cell.file));
156167
if (doc) {
157168
// Compute the code that will really be sent to jupyter
158-
const lines = splitMultilineString(cell.data.source);
159-
const stripped = this.extractExecutableLines(cell);
160-
161-
// Figure out our true 'start' line. This is what we need to tell the debugger is
162-
// actually the start of the code as that's what Jupyter will be getting.
163-
let trueStartLine = cell.line;
164-
for (let i = 0; i < stripped.length; i += 1) {
165-
if (stripped[i] !== lines[i]) {
166-
trueStartLine += i + 1;
167-
break;
168-
}
169-
}
169+
const { stripped, trueStartLine } = this.extractStrippedLines(cell);
170+
170171
const line = doc.lineAt(trueStartLine);
171172
const endLine = doc.lineAt(Math.min(trueStartLine + stripped.length - 1, doc.lineCount - 1));
172173

@@ -181,28 +182,6 @@ export class CellHashProvider implements ICellHashProvider, INotebookExecutionLo
181182
const startOffset = doc.offsetAt(new Position(cell.line, 0));
182183
const endOffset = doc.offsetAt(endLine.rangeIncludingLineBreak.end);
183184

184-
// Jupyter also removes blank lines at the end. Make sure only one
185-
let lastLinePos = stripped.length - 1;
186-
let nextToLastLinePos = stripped.length - 2;
187-
while (nextToLastLinePos > 0) {
188-
const lastLine = stripped[lastLinePos];
189-
const nextToLastLine = stripped[nextToLastLinePos];
190-
if (
191-
(lastLine.length === 0 || lastLine === '\n') &&
192-
(nextToLastLine.length === 0 || nextToLastLine === '\n')
193-
) {
194-
stripped.splice(lastLinePos, 1);
195-
lastLinePos -= 1;
196-
nextToLastLinePos -= 1;
197-
} else {
198-
break;
199-
}
200-
}
201-
// Make sure the last line with actual content ends with a linefeed
202-
if (!stripped[lastLinePos].endsWith('\n') && stripped[lastLinePos].length > 0) {
203-
stripped[lastLinePos] = `${stripped[lastLinePos]}\n`;
204-
}
205-
206185
// Compute the runtime line and adjust our cell/stripped source for debugging
207186
const runtimeLine = this.adjustRuntimeForDebugging(cell, stripped, startOffset, endOffset);
208187
const hashedCode = stripped.join('');
@@ -291,6 +270,52 @@ export class CellHashProvider implements ICellHashProvider, INotebookExecutionLo
291270
}
292271
}
293272

273+
private extractStrippedLines(cell: ICell): { stripped: string[]; trueStartLine: number } {
274+
// Compute the code that will really be sent to jupyter
275+
const lines = splitMultilineString(cell.data.source);
276+
const stripped = this.extractExecutableLines(cell);
277+
278+
// Figure out our true 'start' line. This is what we need to tell the debugger is
279+
// actually the start of the code as that's what Jupyter will be getting.
280+
let trueStartLine = cell.line;
281+
for (let i = 0; i < stripped.length; i += 1) {
282+
if (stripped[i] !== lines[i]) {
283+
trueStartLine += i + 1;
284+
break;
285+
}
286+
}
287+
288+
// Find the first non blank line
289+
let firstNonBlankIndex = 0;
290+
while (firstNonBlankIndex < stripped.length && stripped[firstNonBlankIndex].trim().length === 0) {
291+
firstNonBlankIndex += 1;
292+
}
293+
294+
// Jupyter also removes blank lines at the end. Make sure only one
295+
let lastLinePos = stripped.length - 1;
296+
let nextToLastLinePos = stripped.length - 2;
297+
while (nextToLastLinePos > 0) {
298+
const lastLine = stripped[lastLinePos];
299+
const nextToLastLine = stripped[nextToLastLinePos];
300+
if (
301+
(lastLine.length === 0 || lastLine === '\n') &&
302+
(nextToLastLine.length === 0 || nextToLastLine === '\n')
303+
) {
304+
stripped.splice(lastLinePos, 1);
305+
lastLinePos -= 1;
306+
nextToLastLinePos -= 1;
307+
} else {
308+
break;
309+
}
310+
}
311+
// Make sure the last line with actual content ends with a linefeed
312+
if (!stripped[lastLinePos].endsWith('\n') && stripped[lastLinePos].length > 0) {
313+
stripped[lastLinePos] = `${stripped[lastLinePos]}\n`;
314+
}
315+
316+
return { stripped, trueStartLine };
317+
}
318+
294319
private handleContentChange(docText: string, c: TextDocumentContentChangeEvent, hashes: IRangedCellHash[]) {
295320
// First compute the number of lines that changed
296321
const lineDiff = c.range.start.line - c.range.end.line + c.text.split('\n').length - 1;
@@ -406,3 +431,11 @@ export class CellHashProvider implements ICellHashProvider, INotebookExecutionLo
406431
return traceFrame;
407432
}
408433
}
434+
435+
export function getCellHashProvider(notebook: INotebook): ICellHashProvider | undefined {
436+
const logger = notebook.getLoggers().find((f) => f instanceof CellHashProvider);
437+
if (logger) {
438+
// tslint:disable-next-line: no-any
439+
return (logger as any) as ICellHashProvider;
440+
}
441+
}

src/client/datascience/interactive-common/debugListener.ts

Lines changed: 8 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -3,12 +3,12 @@
33
'use strict';
44
import '../../common/extensions';
55

6-
import { inject, injectable } from 'inversify';
6+
import { inject, injectable, named } from 'inversify';
77
import { DebugSession, Event, EventEmitter } from 'vscode';
88

9-
import { IDebugService } from '../../common/application/types';
109
import { noop } from '../../common/utils/misc';
11-
import { IInteractiveWindowListener } from '../types';
10+
import { Identifiers } from '../constants';
11+
import { IInteractiveWindowListener, IJupyterDebugService } from '../types';
1212
import { InteractiveWindowMessages } from './interactiveWindowTypes';
1313

1414
// tslint:disable: no-any
@@ -18,7 +18,11 @@ export class DebugListener implements IInteractiveWindowListener {
1818
message: string;
1919
payload: any;
2020
}>();
21-
constructor(@inject(IDebugService) private debugService: IDebugService) {
21+
constructor(
22+
@inject(IJupyterDebugService)
23+
@named(Identifiers.MULTIPLEXING_DEBUGSERVICE)
24+
private debugService: IJupyterDebugService
25+
) {
2226
this.debugService.onDidChangeActiveDebugSession(this.onChangeDebugSession.bind(this));
2327
}
2428

src/client/datascience/interactive-common/intellisense/intellisenseProvider.ts

Lines changed: 36 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@
33
'use strict';
44
import '../../../common/extensions';
55

6-
import { inject, injectable } from 'inversify';
6+
import { inject, injectable, named } from 'inversify';
77
import * as monacoEditor from 'monaco-editor/esm/vs/editor/editor.api';
88
import * as path from 'path';
99
import * as uuid from 'uuid/v4';
@@ -13,6 +13,7 @@ import {
1313
CompletionItem,
1414
Event,
1515
EventEmitter,
16+
Hover,
1617
SignatureHelpContext,
1718
TextDocumentContentChangeEvent,
1819
Uri
@@ -31,7 +32,14 @@ import { HiddenFileFormatString } from '../../../constants';
3132
import { IInterpreterService, PythonInterpreter } from '../../../interpreter/contracts';
3233
import { sendTelemetryWhenDone } from '../../../telemetry';
3334
import { Identifiers, Settings, Telemetry } from '../../constants';
34-
import { ICell, IInteractiveWindowListener, INotebook, INotebookCompletion, INotebookProvider } from '../../types';
35+
import {
36+
ICell,
37+
IInteractiveWindowListener,
38+
IJupyterVariables,
39+
INotebook,
40+
INotebookCompletion,
41+
INotebookProvider
42+
} from '../../types';
3543
import {
3644
ICancelIntellisenseRequest,
3745
IInteractiveWindowMapping,
@@ -80,7 +88,8 @@ export class IntellisenseProvider implements IInteractiveWindowListener {
8088
@inject(IFileSystem) private fileSystem: IFileSystem,
8189
@inject(INotebookProvider) private notebookProvider: INotebookProvider,
8290
@inject(IInterpreterService) private interpreterService: IInterpreterService,
83-
@inject(ILanguageServerCache) private languageServerCache: ILanguageServerCache
91+
@inject(ILanguageServerCache) private languageServerCache: ILanguageServerCache,
92+
@inject(IJupyterVariables) @named(Identifiers.ALL_VARIABLES) private variableProvider: IJupyterVariables
8493
) {}
8594

8695
public dispose() {
@@ -244,16 +253,23 @@ export class IntellisenseProvider implements IInteractiveWindowListener {
244253
}
245254
protected async provideHover(
246255
position: monacoEditor.Position,
256+
wordAtPosition: string | undefined,
247257
cellId: string,
248258
token: CancellationToken
249259
): Promise<monacoEditor.languages.Hover> {
250-
const [languageServer, document] = await Promise.all([this.getLanguageServer(), this.getDocument()]);
251-
if (languageServer && document) {
260+
const [languageServer, document, variableHover] = await Promise.all([
261+
this.getLanguageServer(),
262+
this.getDocument(),
263+
this.getVariableHover(wordAtPosition)
264+
]);
265+
if (!variableHover && languageServer && document) {
252266
const docPos = document.convertToDocumentPosition(cellId, position.lineNumber, position.column);
253267
const result = await languageServer.provideHover(document, docPos, token);
254268
if (result) {
255269
return convertToMonacoHover(result);
256270
}
271+
} else if (variableHover) {
272+
return convertToMonacoHover(variableHover);
257273
}
258274

259275
return {
@@ -435,7 +451,7 @@ export class IntellisenseProvider implements IInteractiveWindowListener {
435451
const cancelSource = new CancellationTokenSource();
436452
this.cancellationSources.set(request.requestId, cancelSource);
437453
this.postTimedResponse(
438-
[this.provideHover(request.position, request.cellId, cancelSource.token)],
454+
[this.provideHover(request.position, request.wordAtPosition, request.cellId, cancelSource.token)],
439455
InteractiveWindowMessages.ProvideHoverResponse,
440456
(h) => {
441457
if (h && h[0]) {
@@ -731,4 +747,18 @@ export class IntellisenseProvider implements IInteractiveWindowListener {
731747
? this.notebookProvider.getOrCreateNotebook({ identity: this.notebookIdentity, getOnly: true })
732748
: undefined;
733749
}
750+
751+
private async getVariableHover(wordAtPosition: string | undefined): Promise<Hover | undefined> {
752+
if (wordAtPosition) {
753+
const notebook = await this.getNotebook();
754+
if (notebook) {
755+
const variable = await this.variableProvider.getMatchingVariable(notebook, wordAtPosition);
756+
if (variable && variable.value && variable.name) {
757+
return {
758+
contents: [`${variable.name} : ${variable.value}`]
759+
};
760+
}
761+
}
762+
}
763+
}
734764
}

0 commit comments

Comments
 (0)