Skip to content

Commit d1b2204

Browse files
authored
Make gather an optional component (microsoft#9755)
* Update .npmrc to get latest python * Update to latest analysis and fix gather icon * Use sliceLastExecution and fix cell executionId for gather. * Fix import of python analysis * Update to python-analysis 0.4.12 * get gathered text from cell.textSlice * Updates to fix gather logging * All updates to using new nb owned gather. * Working cell execution count codelenses. * Rename gather* to match existing classes and interfaces * Update to latest python analysis * lock python analysis ver * Fix MockJupyterNotebook * Fix casing of getgatherProvider * Private Gather support #1 Dynamically look for python-program-analysis. If not there gather is unsupported. * Ensure cellHashProvider can be found * Revert back to uri for NBExecutionActivated * Update new jupyterServerWrapper * Workaround fix for screwed up cellhashes Being fixed by Ian in a separate branch. * Gracefully fail if python-pgm-analysis is absent * Resolve obvious unit test problems. * Fixes to make sure that gather.ts is loadable. * Optional gather build, editing, running works. * Remove unecessary getAll of IGatherProviders. * Code cleanup * Fix gather notebook header - Also remove reference to python-program-analysis in package.json * Gather icon not only on hover * Enable gather only in insiders * Init gather only if enabled. * Add link to survey * Fix spacing in initialization.yml * Make linter and prettier happy * Make webpack happy with regard to gather * Don't define ENABLE_GATHER env var * Couple minor issues found in test * Fix a few tests * Everything but dataScienceIocContainer.ts * More fixes * Whoops, fix IW functional test back to original * Temporarily include datascienceioccontainer in 120 character line length. * Make linter finally happy. * Fix Gather functional tests * A bit more cleanup * React to PR feedback * Couple more PR review changes. * Tweak * More minor cleanup * Fix provider finders. * Actually fix the provider get problem. * Fix smoke test * Fix unit test
1 parent 499d4a7 commit d1b2204

File tree

77 files changed

+20459
-315
lines changed

Some content is hidden

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

77 files changed

+20459
-315
lines changed

.npmrc

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1 +1 @@
1-
@types:registry=https://registry.npmjs.org
1+
@types:registry=https://registry.npmjs.org

.prettierrc.js

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,12 @@ module.exports = {
88
options: {
99
tabWidth: 2
1010
}
11+
},
12+
{
13+
files: ['**/datascience/serviceRegistry.ts'],
14+
options: {
15+
printWidth: 240
16+
}
1117
}
1218
]
1319
};

build/ci/templates/steps/initialization.yml

Lines changed: 11 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@ parameters:
1818
compile: 'true'
1919
sqlite: 'false'
2020
installVSCEorNPX: 'true'
21+
enableGather: 'false'
2122

2223
steps:
2324
- bash: |
@@ -92,6 +93,15 @@ steps:
9293
verbose: true
9394
customCommand: ci
9495

96+
- task: Npm@1
97+
displayName: "Install optional python-program-analysis"
98+
condition: and(succeeded(), eq('${{ parameters.enableGather }}', 'true'))
99+
inputs:
100+
workingDir: ${{ parameters.workingDirectory }}
101+
command: custom
102+
verbose: true
103+
customCommand: "install @msrvida/python-program-analysis"
104+
95105
# On Mac, the command `node` doesn't always point to the current node version.
96106
# Debugger tests use the variable process.env.NODE_PATH
97107
- script: |
@@ -101,7 +111,7 @@ steps:
101111
displayName: "Setup NODE_PATH for extension (Debugger Tests)"
102112
condition: and(succeeded(), eq(variables['agent.os'], 'Darwin'))
103113
104-
# Install vsce
114+
# Install vsce
105115
- bash: |
106116
npm install -g vsce
107117
displayName: "Install vsce"

build/webpack/webpack.extension.config.js

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,9 @@ const configFileName = path.join(constants.ExtensionRootDir, 'tsconfig.extension
1313
const existingModulesInOutDir = common.getListOfExistingModulesInOutDir();
1414
// tslint:disable-next-line:no-var-requires no-require-imports
1515
const FileManagerPlugin = require('filemanager-webpack-plugin');
16+
// If ENABLE_GATHER variable is defined, don't exclude the python-program-analysis pacakge.
17+
// See externals, below.
18+
const ppaPackageList = process.env.ENABLE_GATHER ? [] : ['@msrvida/python-program-analysis'];
1619
const config = {
1720
mode: 'production',
1821
target: 'node',
@@ -56,7 +59,10 @@ const config = {
5659
{ enforce: 'post', test: /linebreak[\/\\]src[\/\\]linebreaker.js/, loader: 'transform-loader?brfs' }
5760
]
5861
},
59-
externals: ['vscode', 'commonjs', ...existingModulesInOutDir],
62+
// Packages listed in externals keeps webpack from trying to package them.
63+
// The ppaPackageList variable is set to non-empty if the build pipeline has been
64+
//authenticated to install @msrvida/python-program-analysis.
65+
externals: ['vscode', 'commonjs', ...ppaPackageList, ...existingModulesInOutDir],
6066
plugins: [
6167
...common.getDefaultPlugins('extension'),
6268
// Copy pdfkit bits after extension builds. webpack can't handle pdfkit.

package-lock.json

Lines changed: 0 additions & 5 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

package.json

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1833,13 +1833,13 @@
18331833
"python.dataScience.enableGather": {
18341834
"type": "boolean",
18351835
"default": false,
1836-
"description": "Enable code gather for executed cells. For a gathered cell, that cell and only the code it depends on will be exported to a new notebook.",
1836+
"description": "Enable experimental code gathering for executed cells. For a gathered cell, that cell and only the code it depends on will be exported to a new notebook.",
18371837
"scope": "resource"
18381838
},
18391839
"python.dataScience.gatherToScript": {
18401840
"type": "boolean",
18411841
"default": true,
1842-
"description": "Gather code to a python script rather than a notebook.",
1842+
"description": "If experimental code gather is enabled, gather code to a python script rather than a notebook.",
18431843
"scope": "resource"
18441844
},
18451845
"python.dataScience.codeLenses": {
@@ -2868,7 +2868,6 @@
28682868
"@jupyterlab/services": "^4.2.0",
28692869
"@koa/cors": "^3.0.0",
28702870
"@loadable/component": "^5.12.0",
2871-
"@msrvida/python-program-analysis": "^0.4.1",
28722871
"ansi-regex": "^4.1.0",
28732872
"arch": "^2.1.0",
28742873
"azure-storage": "^2.10.3",

package.nls.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -418,7 +418,7 @@
418418
"DataScience.findJupyterCommandProgressCheckInterpreter": "Checking {0}.",
419419
"DataScience.findJupyterCommandProgressSearchCurrentPath": "Searching current path.",
420420
"DataScience.gatheredScriptDescription": "# This file contains only the code required to produce the results of the gathered cell.\n",
421-
"DataScience.gatheredNotebookDescriptionInMarkdown": "# Gathered Notebook\nGenerated from ```{0}```\n\nThis notebook contains only the code and cells required to produce the same results as the gathered cell.\n\nPlease note that the python analysis is quite conservative, so if it is unsure whether a line of code is necessary for execution, it will err on the side of including it.",
421+
"DataScience.gatheredNotebookDescriptionInMarkdown": "## Gathered Notebook\nGenerated from ```{0}```\n\nThis notebook contains only the code and cells required to produce the same results as the gathered cell.\n\nPlease note that the python analysis is quite conservative, so if it is unsure whether a line of code is necessary for execution, it will err on the side of including it.\n\nAs this is an experimental feature, please let us know how well Gather works for you at [https://aka.ms/gathersurvey](https://aka.ms/gathersurvey)",
422422
"DataScience.savePngTitle": "Save Image",
423423
"DataScience.jupyterSelectURIQuickPickTitle": "Pick how to connect to Jupyter",
424424
"DataScience.jupyterSelectURIQuickPickPlaceholder": "Choose an option",

src/client/common/utils/localize.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -726,7 +726,7 @@ export namespace DataScience {
726726
);
727727
export const gatheredNotebookDescriptionInMarkdown = localize(
728728
'DataScience.gatheredNotebookDescriptionInMarkdown',
729-
'# Gathered Notebook\nGenerated from ```{0}```\n\nThis notebook contains only the code and cells required to produce the same results as the gathered cell.\n\nPlease note that the python analysis is quite conservative, so if it is unsure whether a line of code is necessary for execution, it will err on the side of including it.'
729+
'## Gathered Notebook\nGenerated from ```{0}```\n\nThis notebook contains only the code and cells required to produce the same results as the gathered cell.\n\nPlease note that the python analysis is quite conservative, so if it is unsure whether a line of code is necessary for execution, it will err on the side of including it.\n\nAs this is an experimental feature, please let us know how well Gather works for you at [https://aka.ms/gathersurvey](https://aka.ms/gathersurvey)'
730730
);
731731
export const savePngTitle = localize('DataScience.savePngTitle', 'Save Image');
732732
export const fallbackToUseActiveInterpeterAsKernel = localize(
Lines changed: 48 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,48 @@
1+
// Copyright (c) Microsoft Corporation. All rights reserved.
2+
// Licensed under the MIT License.
3+
'use strict';
4+
import { inject, injectable } from 'inversify';
5+
6+
import { traceError } from '../../common/logger';
7+
import { noop } from '../../common/utils/misc';
8+
import { Identifiers } from '../constants';
9+
import { ICell, ICellHashLogger, ICellHashProvider } from '../types';
10+
import { CellHashProvider } from './cellhashprovider';
11+
12+
// This class provides hashes for debugging jupyter cells. Call getHashes just before starting debugging to compute all of the
13+
// hashes for cells.
14+
@injectable()
15+
export class CellHashLogger implements ICellHashLogger {
16+
constructor(@inject(ICellHashProvider) private provider: ICellHashProvider) {}
17+
18+
public async preExecute(cell: ICell, silent: boolean): Promise<void> {
19+
const providerObj: CellHashProvider = this.provider as CellHashProvider;
20+
21+
try {
22+
if (!silent) {
23+
// Don't log empty cells
24+
const stripped = providerObj.extractExecutableLines(cell);
25+
if (stripped.length > 0 && stripped.find(s => s.trim().length > 0)) {
26+
// When the user adds new code, we know the execution count is increasing
27+
providerObj.incExecutionCount();
28+
29+
// Skip hash on unknown file though
30+
if (cell.file !== Identifiers.EmptyFileName) {
31+
await providerObj.addCellHash(cell, providerObj.getExecutionCount());
32+
}
33+
}
34+
}
35+
} catch (exc) {
36+
// Don't let exceptions in a preExecute mess up normal operation
37+
traceError(exc);
38+
}
39+
}
40+
41+
public async postExecute(_cell: ICell, _silent: boolean): Promise<void> {
42+
noop();
43+
}
44+
45+
public getCellHashProvider(): ICellHashProvider {
46+
return this.provider;
47+
}
48+
}

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

Lines changed: 62 additions & 55 deletions
Original file line numberDiff line numberDiff line change
@@ -21,8 +21,7 @@ import {
2121
ICellHashListener,
2222
ICellHashProvider,
2323
IFileHashes,
24-
IInteractiveWindowListener,
25-
INotebookExecutionLogger
24+
IInteractiveWindowListener
2625
} from '../types';
2726

2827
interface IRangedCellHash extends ICellHash {
@@ -36,16 +35,16 @@ interface IRangedCellHash extends ICellHash {
3635
// This class provides hashes for debugging jupyter cells. Call getHashes just before starting debugging to compute all of the
3736
// hashes for cells.
3837
@injectable()
39-
export class CellHashProvider implements ICellHashProvider, IInteractiveWindowListener, INotebookExecutionLogger {
38+
export class CellHashProvider implements ICellHashProvider, IInteractiveWindowListener {
4039
// tslint:disable-next-line: no-any
4140
private postEmitter: EventEmitter<{ message: string; payload: any }> = new EventEmitter<{
4241
message: string;
4342
// tslint:disable-next-line: no-any
4443
payload: any;
4544
}>();
4645
// Map of file to Map of start line to actual hash
47-
private hashes: Map<string, IRangedCellHash[]> = new Map<string, IRangedCellHash[]>();
4846
private executionCount: number = 0;
47+
private hashes: Map<string, IRangedCellHash[]> = new Map<string, IRangedCellHash[]>();
4948
private updateEventEmitter: EventEmitter<void> = new EventEmitter<void>();
5049

5150
constructor(
@@ -127,19 +126,7 @@ export class CellHashProvider implements ICellHashProvider, IInteractiveWindowLi
127126
noop();
128127
}
129128

130-
private onChangedDocument(e: TextDocumentChangeEvent) {
131-
// See if the document is in our list of docs to watch
132-
const perFile = this.hashes.get(e.document.fileName);
133-
if (perFile) {
134-
// Apply the content changes to the file's cells.
135-
const docText = e.document.getText();
136-
e.contentChanges.forEach(c => {
137-
this.handleContentChange(docText, c, perFile);
138-
});
139-
}
140-
}
141-
142-
private extractExecutableLines(cell: ICell): string[] {
129+
public extractExecutableLines(cell: ICell): string[] {
143130
const cellMatcher = new CellMatcher(this.configService.getSettings(getCellResource(cell)).datascience);
144131
const lines = splitMultilineString(cell.data.source);
145132
// Only strip this off the first line. Otherwise we want the markers in the code.
@@ -149,44 +136,7 @@ export class CellHashProvider implements ICellHashProvider, IInteractiveWindowLi
149136
return lines;
150137
}
151138

152-
private handleContentChange(docText: string, c: TextDocumentContentChangeEvent, hashes: IRangedCellHash[]) {
153-
// First compute the number of lines that changed
154-
const lineDiff = c.range.start.line - c.range.end.line + c.text.split('\n').length - 1;
155-
const offsetDiff = c.text.length - c.rangeLength;
156-
157-
// Compute the inclusive offset that is changed by the cell.
158-
const endChangedOffset = c.rangeLength <= 0 ? c.rangeOffset : c.rangeOffset + c.rangeLength - 1;
159-
160-
hashes.forEach(h => {
161-
// See how this existing cell compares to the change
162-
if (h.endOffset < c.rangeOffset) {
163-
// No change. This cell is entirely before the change
164-
} else if (h.startOffset > endChangedOffset) {
165-
// This cell is after the text that got replaced. Adjust its start/end lines
166-
h.line += lineDiff;
167-
h.endLine += lineDiff;
168-
h.startOffset += offsetDiff;
169-
h.endOffset += offsetDiff;
170-
} else if (h.startOffset === endChangedOffset) {
171-
// Cell intersects but exactly, might be a replacement or an insertion
172-
if (h.deleted || c.rangeLength > 0 || lineDiff === 0) {
173-
// Replacement
174-
h.deleted = docText.substr(h.startOffset, h.endOffset - h.startOffset) !== h.realCode;
175-
} else {
176-
// Insertion
177-
h.line += lineDiff;
178-
h.endLine += lineDiff;
179-
h.startOffset += offsetDiff;
180-
h.endOffset += offsetDiff;
181-
}
182-
} else {
183-
// Intersection, delete if necessary
184-
h.deleted = docText.substr(h.startOffset, h.endOffset - h.startOffset) !== h.realCode;
185-
}
186-
});
187-
}
188-
189-
private async addCellHash(cell: ICell, expectedCount: number): Promise<void> {
139+
public async addCellHash(cell: ICell, expectedCount: number): Promise<void> {
190140
// Find the text document that matches. We need more information than
191141
// the add code gives us
192142
const doc = this.documentManager.textDocuments.find(d => this.fileSystem.arePathsSame(d.fileName, cell.file));
@@ -284,6 +234,63 @@ export class CellHashProvider implements ICellHashProvider, IInteractiveWindowLi
284234
}
285235
}
286236

237+
public getExecutionCount(): number {
238+
return this.executionCount;
239+
}
240+
241+
public incExecutionCount(): void {
242+
this.executionCount += 1;
243+
}
244+
245+
private onChangedDocument(e: TextDocumentChangeEvent) {
246+
// See if the document is in our list of docs to watch
247+
const perFile = this.hashes.get(e.document.fileName);
248+
if (perFile) {
249+
// Apply the content changes to the file's cells.
250+
const docText = e.document.getText();
251+
e.contentChanges.forEach(c => {
252+
this.handleContentChange(docText, c, perFile);
253+
});
254+
}
255+
}
256+
257+
private handleContentChange(docText: string, c: TextDocumentContentChangeEvent, hashes: IRangedCellHash[]) {
258+
// First compute the number of lines that changed
259+
const lineDiff = c.range.start.line - c.range.end.line + c.text.split('\n').length - 1;
260+
const offsetDiff = c.text.length - c.rangeLength;
261+
262+
// Compute the inclusive offset that is changed by the cell.
263+
const endChangedOffset = c.rangeLength <= 0 ? c.rangeOffset : c.rangeOffset + c.rangeLength - 1;
264+
265+
hashes.forEach(h => {
266+
// See how this existing cell compares to the change
267+
if (h.endOffset < c.rangeOffset) {
268+
// No change. This cell is entirely before the change
269+
} else if (h.startOffset > endChangedOffset) {
270+
// This cell is after the text that got replaced. Adjust its start/end lines
271+
h.line += lineDiff;
272+
h.endLine += lineDiff;
273+
h.startOffset += offsetDiff;
274+
h.endOffset += offsetDiff;
275+
} else if (h.startOffset === endChangedOffset) {
276+
// Cell intersects but exactly, might be a replacement or an insertion
277+
if (h.deleted || c.rangeLength > 0 || lineDiff === 0) {
278+
// Replacement
279+
h.deleted = docText.substr(h.startOffset, h.endOffset - h.startOffset) !== h.realCode;
280+
} else {
281+
// Insertion
282+
h.line += lineDiff;
283+
h.endLine += lineDiff;
284+
h.startOffset += offsetDiff;
285+
h.endOffset += offsetDiff;
286+
}
287+
} else {
288+
// Intersection, delete if necessary
289+
h.deleted = docText.substr(h.startOffset, h.endOffset - h.startOffset) !== h.realCode;
290+
}
291+
});
292+
}
293+
287294
private adjustRuntimeForDebugging(
288295
cell: ICell,
289296
source: string[],

0 commit comments

Comments
 (0)