From 7973914386a1e6ee145e07ee2bb415ebfac68842 Mon Sep 17 00:00:00 2001 From: Karthik Nadig Date: Fri, 8 Nov 2019 14:51:40 -0800 Subject: [PATCH 01/21] Ensure we run PR, CI pipelines on release branches (#8471) (#8474) --- build/ci/templates/steps/build.yml | 2 +- build/ci/templates/steps/build_compile.yml | 2 +- build/ci/vscode-python-ci.yaml | 2 +- build/ci/vscode-python-nightly-ci.yaml | 2 +- build/ci/vscode-python-nightly-flake-ci.yaml | 2 +- build/ci/vscode-python-nightly-uitest.yaml | 2 +- build/ci/vscode-python-pr-validation.yaml | 2 +- 7 files changed, 7 insertions(+), 7 deletions(-) diff --git a/build/ci/templates/steps/build.yml b/build/ci/templates/steps/build.yml index fc953c7bfb60..9a308065640c 100644 --- a/build/ci/templates/steps/build.yml +++ b/build/ci/templates/steps/build.yml @@ -43,7 +43,7 @@ steps: - bash: | npm run updateBuildNumber -- --buildNumber $BUILD_BUILDID --updateChangelog displayName: "Update release Version of Extension" - condition: and(succeeded(), eq(variables['Build.SourceBranchName'], 'release')) + condition: and(succeeded(), startsWith(variables['Build.SourceBranchName'], 'release')) - bash: | npm run package diff --git a/build/ci/templates/steps/build_compile.yml b/build/ci/templates/steps/build_compile.yml index 5abf5c0bcdaa..badf6829d94d 100644 --- a/build/ci/templates/steps/build_compile.yml +++ b/build/ci/templates/steps/build_compile.yml @@ -48,7 +48,7 @@ steps: - bash: | npm run updateBuildNumber -- --buildNumber $BUILD_BUILDID --updateChangelog displayName: "Update release Version of Extension" - condition: and(succeeded(), eq(variables['build'], 'true'), eq(variables['Build.SourceBranchName'], 'release')) + condition: and(succeeded(), eq(variables['build'], 'true'), startsWith(variables['Build.SourceBranchName'], 'release')) - bash: | npm run package diff --git a/build/ci/vscode-python-ci.yaml b/build/ci/vscode-python-ci.yaml index 187f1f2a796a..29b22c7943e5 100644 --- a/build/ci/vscode-python-ci.yaml +++ b/build/ci/vscode-python-ci.yaml @@ -4,7 +4,7 @@ name: '$(Year:yyyy).$(Month).0.$(BuildID)-ci' # on changes in the news and .vscode folders. trigger: branches: - include: ["master", "release"] + include: ["master", "release*"] paths: exclude: ["/news/1 Enhancements", "/news/2 Fixes", "/news/3 Code Health", "/.vscode"] diff --git a/build/ci/vscode-python-nightly-ci.yaml b/build/ci/vscode-python-nightly-ci.yaml index a1752c80cfb0..a465421225bb 100644 --- a/build/ci/vscode-python-nightly-ci.yaml +++ b/build/ci/vscode-python-nightly-ci.yaml @@ -15,7 +15,7 @@ schedules: branches: include: - master - - release + - release* always: true # Variables that are available for the entire pipeline. diff --git a/build/ci/vscode-python-nightly-flake-ci.yaml b/build/ci/vscode-python-nightly-flake-ci.yaml index 62b42eb2f5ae..4702ca86217b 100644 --- a/build/ci/vscode-python-nightly-flake-ci.yaml +++ b/build/ci/vscode-python-nightly-flake-ci.yaml @@ -17,7 +17,7 @@ schedules: branches: include: - master - - release + - release* # False here so we don't run these long tests over and over on release branch if the source isn't changing always: false diff --git a/build/ci/vscode-python-nightly-uitest.yaml b/build/ci/vscode-python-nightly-uitest.yaml index c43ff39ce0cd..d3cac64c46f8 100644 --- a/build/ci/vscode-python-nightly-uitest.yaml +++ b/build/ci/vscode-python-nightly-uitest.yaml @@ -9,7 +9,7 @@ schedules: branches: include: - master - - releases/* + - releases* # Variables that are available for the entire pipeline. variables: diff --git a/build/ci/vscode-python-pr-validation.yaml b/build/ci/vscode-python-pr-validation.yaml index 593947f75746..2696ec8ef280 100644 --- a/build/ci/vscode-python-pr-validation.yaml +++ b/build/ci/vscode-python-pr-validation.yaml @@ -6,7 +6,7 @@ name: '$(Year:yyyy).$(Month).0.$(BuildID)-pr' pr: autoCancel: true branches: - include: ["master", "release"] + include: ["master", "release*"] paths: exclude: ["/news/1 Enhancements", "/news/2 Fixes", "/news/3 Code Health", "/.vscode"] From e558a7c33f61a9db96b85bdf068beeaeba6c9c99 Mon Sep 17 00:00:00 2001 From: Ian Huff Date: Fri, 8 Nov 2019 15:31:41 -0800 Subject: [PATCH 02/21] Port blue icon and altair background fixes into release (#8470) * fix blue color on export icon (#8449) * default non-text mimetypes to white background (#8452) --- news/2 Fixes/8423.md | 1 + news/2 Fixes/8424.md | 1 + .../interactive-common/cellOutput.tsx | 123 +++++++++--------- .../native-editor/nativeEditor.less | 9 -- .../native-editor/nativeEditor.tsx | 2 +- .../manualTestFiles/manualTestFile.py | 24 +++- 6 files changed, 83 insertions(+), 77 deletions(-) create mode 100644 news/2 Fixes/8423.md create mode 100644 news/2 Fixes/8424.md diff --git a/news/2 Fixes/8423.md b/news/2 Fixes/8423.md new file mode 100644 index 000000000000..8879d8ae89ad --- /dev/null +++ b/news/2 Fixes/8423.md @@ -0,0 +1 @@ +Add a white backtground for most non-text mimetypes. This lets stuff like Atlair look good in dark mode. \ No newline at end of file diff --git a/news/2 Fixes/8424.md b/news/2 Fixes/8424.md new file mode 100644 index 000000000000..735307663e6d --- /dev/null +++ b/news/2 Fixes/8424.md @@ -0,0 +1 @@ +Export to python button is blue in native editor. \ No newline at end of file diff --git a/src/datascience-ui/interactive-common/cellOutput.tsx b/src/datascience-ui/interactive-common/cellOutput.tsx index 71306f9f39d7..0b8167dc4874 100644 --- a/src/datascience-ui/interactive-common/cellOutput.tsx +++ b/src/datascience-ui/interactive-common/cellOutput.tsx @@ -249,71 +249,72 @@ export class CellOutput extends React.Component { const mimeBundle = copy.data as nbformat.IMimeBundle; let data: nbformat.MultilineString | JSONObject = mimeBundle[mimeType]; - switch (mimeType) { - case 'text/plain': - case 'text/html': - return { - mimeType, - data: concatMultilineStringOutput(data as nbformat.MultilineString), - isText, - isError, - renderWithScrollbars: true, - extraButton, - doubleClick: noop + // Text based mimeTypes don't get a white background + if (/^text\//.test(mimeType)) { + return { + mimeType, + data: concatMultilineStringOutput(data as nbformat.MultilineString), + isText, + isError, + renderWithScrollbars: true, + extraButton, + doubleClick: noop + }; + } else if (mimeType === 'image/svg+xml' || mimeType === 'image/png') { + // If we have a png or svg enable the plot viewer button + // There should be two mime bundles. Well if enablePlotViewer is turned on. See if we have both + const svg = mimeBundle['image/svg+xml']; + const png = mimeBundle['image/png']; + const buttonTheme = this.props.themeMatplotlibPlots ? this.props.baseTheme : 'vscode-light'; + let doubleClick: () => void = noop; + if (svg && png) { + // Save the svg in the extra button. + const openClick = () => { + this.props.expandImage(svg.toString()); }; + extraButton = ( +
+ + + +
+ ); - case 'image/svg+xml': - case 'image/png': - // There should be two mime bundles. Well if enablePlotViewer is turned on. See if we have both - const svg = mimeBundle['image/svg+xml']; - const png = mimeBundle['image/png']; - const buttonTheme = this.props.themeMatplotlibPlots ? this.props.baseTheme : 'vscode-light'; - let doubleClick: () => void = noop; - if (svg && png) { - // Save the svg in the extra button. - const openClick = () => { - this.props.expandImage(svg.toString()); - }; - extraButton = ( -
- - - -
- ); - - // Switch the data to the png - data = png; - mimeType = 'image/png'; - - // Switch double click to do the same thing as the extra button - doubleClick = openClick; - } - - // return the image - // If not theming plots then wrap in a span - return { - mimeType, - data, - isText, - isError, - renderWithScrollbars, - extraButton, - doubleClick, - outputSpanClassName: this.props.themeMatplotlibPlots ? undefined : 'cell-output-plot-background' - }; + // Switch the data to the png + data = png; + mimeType = 'image/png'; - default: - return { - mimeType, - data, - isText, - isError, - renderWithScrollbars, - extraButton, - doubleClick: noop - }; + // Switch double click to do the same thing as the extra button + doubleClick = openClick; + } + + // return the image + // If not theming plots then wrap in a span + return { + mimeType, + data, + isText, + isError, + renderWithScrollbars, + extraButton, + doubleClick, + outputSpanClassName: this.props.themeMatplotlibPlots ? undefined : 'cell-output-plot-background' + }; + } else { + // For anything else just return it with a white plot background. This lets stuff like vega look good in + // dark mode + return { + mimeType, + data, + isText, + isError, + renderWithScrollbars, + extraButton, + doubleClick: noop, + outputSpanClassName: this.props.themeMatplotlibPlots ? undefined : 'cell-output-plot-background' + }; } + } catch (e) { return { data: e.toString(), diff --git a/src/datascience-ui/native-editor/nativeEditor.less b/src/datascience-ui/native-editor/nativeEditor.less index b7cb646ca774..f279b457301e 100644 --- a/src/datascience-ui/native-editor/nativeEditor.less +++ b/src/datascience-ui/native-editor/nativeEditor.less @@ -302,15 +302,6 @@ color: var(--override-foreground, var(--vscode-editor-foreground)); } -.save-button { - background: var(--vscode-button-background); - color: var(--vscode-button-foreground); - padding: 2px; - border: none; - cursor: pointer; - margin:2px 4px; -} - .native-button { margin-top: 3px; } diff --git a/src/datascience-ui/native-editor/nativeEditor.tsx b/src/datascience-ui/native-editor/nativeEditor.tsx index c86362942562..804382da9c1d 100644 --- a/src/datascience-ui/native-editor/nativeEditor.tsx +++ b/src/datascience-ui/native-editor/nativeEditor.tsx @@ -249,7 +249,7 @@ export class NativeEditor extends React.Component - + diff --git a/src/test/datascience/manualTestFiles/manualTestFile.py b/src/test/datascience/manualTestFiles/manualTestFile.py index 393deee6a965..feac6e1c2a8c 100644 --- a/src/test/datascience/manualTestFiles/manualTestFile.py +++ b/src/test/datascience/manualTestFiles/manualTestFile.py @@ -1,4 +1,4 @@ -# To run this file either conda or pip install the following: jupyter, numpy, matplotlib, pandas, tqdm, bokeh +# To run this file either conda or pip install the following: jupyter, numpy, matplotlib, pandas, tqdm, bokeh, vega_datasets, altair, vega # %% Basic Imports import numpy as np @@ -18,13 +18,13 @@ p.circle([1,2,3,4,5], [6,7,2,4,5], size=15, line_color="navy", fill_color="orange", fill_alpha=0.5) show(p) -#%% Progress bar +# %% Progress bar from tqdm import trange import time for i in trange(100): time.sleep(0.01) -#%% [markdown] +# %% [markdown] # # Heading # ## Sub-heading # *bold*,_italic_,`monospace` @@ -39,20 +39,32 @@ # # [Link](http://www.microsoft.com) -#%% Magics +# %% Magics %whos -#%% Some extra variable types for the variable explorer +# %% Some extra variable types for the variable explorer myNparray = np.array([['Bob', 1, 2, 3], ['Alice', 4, 5, 6], ['Gina', 7, 8, 9]]) myDataFrame = pd.DataFrame(myNparray, columns=['name', 'b', 'c', 'd']) mySeries = myDataFrame['name'] myList = [x ** 2 for x in range(0, 100000)] myString = 'testing testing testing' -#%% +# %% Latex %%latex \begin{align} \nabla \cdot \vec{\mathbf{E}} & = 4 \pi \rho \\ \nabla \times \vec{\mathbf{E}}\, +\, \frac1c\, \frac{\partial\vec{\mathbf{B}}}{\partial t} & = \vec{\mathbf{0}} \\ \nabla \cdot \vec{\mathbf{B}} & = 0 \end{align} + +# %% Altair (vega) +import altair as alt +from vega_datasets import data + +iris = data.iris() + +alt.Chart(iris).mark_point().encode( + x='petalLength', + y='petalWidth', + color='species' +) From b79a50349f9a3e04e6cc8e211bf18188d3c4e42a Mon Sep 17 00:00:00 2001 From: Rich Chiodo Date: Sat, 9 Nov 2019 08:19:29 -0800 Subject: [PATCH 03/21] =?UTF-8?q?Fix=20cells=20being=20erased=20when=20sav?= =?UTF-8?q?ing=20and=20then=20changing=20focus=20to=20another=E2=80=A6=20(?= =?UTF-8?q?#8482)=20(#8485)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * Fix cells being erased when saving and then changing focus to another cell * Make sure to use the source even if it's empty --- news/2 Fixes/8399.md | 1 + .../interactive-common/mainStateController.ts | 55 +++++++++---------- .../native-editor/nativeCell.tsx | 6 +- .../nativeEditorStateController.ts | 2 +- 4 files changed, 30 insertions(+), 34 deletions(-) create mode 100644 news/2 Fixes/8399.md diff --git a/news/2 Fixes/8399.md b/news/2 Fixes/8399.md new file mode 100644 index 000000000000..31329e4755d3 --- /dev/null +++ b/news/2 Fixes/8399.md @@ -0,0 +1 @@ +Fix cells being erased when saving and then changing focus to another cell. diff --git a/src/datascience-ui/interactive-common/mainStateController.ts b/src/datascience-ui/interactive-common/mainStateController.ts index 2f2edac75563..dad2105f645f 100644 --- a/src/datascience-ui/interactive-common/mainStateController.ts +++ b/src/datascience-ui/interactive-common/mainStateController.ts @@ -339,26 +339,21 @@ export class MainStateController implements IMessageHandler { this.clearAllSilent(); } - public updateCellSource = (cellId: string) => { - const models = monacoEditor.editor.getModels(); - const cvm = this.findCell(cellId); - if (cvm) { - const modelId = this.getMonacoId(cvm.cell.id); - if (modelId) { - const model = models.find(m => m.id === modelId); - if (model) { - cvm.cell.data.source = cvm.inputBlockText = model.getValue().replace(/\r/g, ''); - } - } - } - } - public save = () => { // We have to take the current value of each cell to make sure we have the correct text. - this.pendingState.cellVMs.forEach(c => this.updateCellSource(c.cell.id)); + const newVMs = [...this.pendingState.cellVMs]; + for (let i = 0; i < newVMs.length; i += 1) { + const text = this.getMonacoEditorContents(newVMs[i].cell.id); + if (text !== undefined) { + newVMs[i] = { ...newVMs[i], inputBlockText: text, cell: { ...newVMs[i].cell, data: { ...newVMs[i].cell.data, source: text } } }; + } + } + this.setState({ + cellVMs: newVMs + }); // Then send the save with the new state. - this.sendMessage(InteractiveWindowMessages.SaveAll, { cells: this.getNonEditCellVMs().map(cvm => cvm.cell) }); + this.sendMessage(InteractiveWindowMessages.SaveAll, { cells: newVMs.map(cvm => cvm.cell) }); } public showPlot = (imageHtml: string) => { @@ -764,6 +759,20 @@ export class MainStateController implements IMessageHandler { return this.pendingState; } + public getMonacoEditorContents(cellId: string): string | undefined { + const index = this.findCellIndex(cellId); + if (index >= 0) { + // Get the model for the monaco editor + const monacoId = this.getMonacoId(cellId); + if (monacoId) { + const model = monacoEditor.editor.getModels().find(m => m.id === monacoId); + if (model) { + return model.getValue().replace(/\r/g, ''); + } + } + } + } + // Adjust the visibility or collapsed state of a cell protected alterCellVM(cellVM: ICellViewModel, visible: boolean, expanded: boolean): ICellViewModel { if (cellVM.cell.data.cell_type === 'code') { @@ -811,20 +820,6 @@ export class MainStateController implements IMessageHandler { return cellVM; } - protected getMonacoEditorContents(cellId: string): string | undefined { - const index = this.findCellIndex(cellId); - if (index >= 0) { - // Get the model for the monaco editor - const monacoId = this.getMonacoId(cellId); - if (monacoId) { - const model = monacoEditor.editor.getModels().find(m => m.id === monacoId); - if (model) { - return model.getValue().replace(/\r/g, ''); - } - } - } - } - protected onCodeLostFocus(_cellId: string) { // Default is do nothing. } diff --git a/src/datascience-ui/native-editor/nativeCell.tsx b/src/datascience-ui/native-editor/nativeCell.tsx index bafab09f9356..08500ae84dd0 100644 --- a/src/datascience-ui/native-editor/nativeCell.tsx +++ b/src/datascience-ui/native-editor/nativeCell.tsx @@ -482,7 +482,7 @@ export class NativeCell extends React.Component { let content: string | undefined ; // If inside editor, submit this code - if (possibleContents) { + if (possibleContents !== undefined) { content = possibleContents; } else { // Outside editor, just use the cell @@ -558,8 +558,8 @@ export class NativeCell extends React.Component { private renderMiddleToolbar = () => { const cellId = this.props.cellVM.cell.id; const runCell = () => { - this.props.stateController.updateCellSource(cellId); - this.runAndMove(concatMultilineStringInput(this.props.cellVM.cell.data.source)); + const contents = this.props.stateController.getMonacoEditorContents(cellId); + this.runAndMove(contents); this.props.stateController.sendCommand(NativeCommandType.Run, 'mouse'); }; const gatherCell = () => { diff --git a/src/datascience-ui/native-editor/nativeEditorStateController.ts b/src/datascience-ui/native-editor/nativeEditorStateController.ts index a588637fd79e..b4af3930b862 100644 --- a/src/datascience-ui/native-editor/nativeEditorStateController.ts +++ b/src/datascience-ui/native-editor/nativeEditorStateController.ts @@ -286,7 +286,7 @@ export class NativeEditorStateController extends MainStateController { if (index >= 0) { // Get the model source from the monaco editor const source = this.getMonacoEditorContents(cellId); - if (source) { + if (source !== undefined) { const newVMs = [...this.getState().cellVMs]; // Update our state From 5a689a3dba5b5d482d571a27f35ae5d07c743ccf Mon Sep 17 00:00:00 2001 From: Rich Chiodo Date: Mon, 11 Nov 2019 09:29:40 -0800 Subject: [PATCH 04/21] Fix timeout to be what it was before. 10 seconds isn't long enough (#8504) --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index fdcb7b54714f..6f2edce3edd3 100644 --- a/package.json +++ b/package.json @@ -1513,7 +1513,7 @@ }, "python.dataScience.jupyterLaunchTimeout": { "type": "number", - "default": 10000, + "default": 60000, "description": "Amount of time (in ms) to wait for the Jupyter Notebook server to start.", "scope": "resource" }, From 92aeeebfa4fab8a0939b3f2badaf176aa19193ea Mon Sep 17 00:00:00 2001 From: Karthik Nadig Date: Tue, 12 Nov 2019 14:52:51 -0800 Subject: [PATCH 05/21] Ensure wheels experiment control and experiment groups uses right wheel (#8461) (#8546) * Ensure wheels control group uses no wheel ptvsd * Improve condition readability in jupyter debugger --- build/ci/templates/steps/build.yml | 1 + build/ci/templates/test_phases.yml | 1 + pythonFiles/install_ptvsd.py | 2 +- .../datascience/jupyter/jupyterDebugger.ts | 50 ++++---- .../debugger/extension/adapter/factory.ts | 22 ++-- src/client/telemetry/constants.ts | 1 + src/client/telemetry/index.ts | 11 ++ .../extension/adapter/factory.unit.test.ts | 113 +++++++++++++----- 8 files changed, 138 insertions(+), 63 deletions(-) diff --git a/build/ci/templates/steps/build.yml b/build/ci/templates/steps/build.yml index 9a308065640c..07f671750b59 100644 --- a/build/ci/templates/steps/build.yml +++ b/build/ci/templates/steps/build.yml @@ -22,6 +22,7 @@ steps: python -m pip install -U pip python -m pip --disable-pip-version-check install -t ./pythonFiles/lib/python --no-cache-dir --implementation py --no-deps --upgrade -r requirements.txt python -m pip --disable-pip-version-check install -t ./pythonFiles/lib/python/old_ptvsd --no-cache-dir --implementation py --no-deps --upgrade 'ptvsd==4.3.2' + python -m pip --disable-pip-version-check install -t ./pythonFiles/lib/python/new_ptvsd/no_wheels --no-cache-dir --implementation py --no-deps --upgrade 'ptvsd==5.0.0a7' failOnStderr: true displayName: "pip install requirements" diff --git a/build/ci/templates/test_phases.yml b/build/ci/templates/test_phases.yml index 546167a2e463..66b001c32656 100644 --- a/build/ci/templates/test_phases.yml +++ b/build/ci/templates/test_phases.yml @@ -96,6 +96,7 @@ steps: python -m pip install --upgrade -r build/test-requirements.txt python -m pip --disable-pip-version-check install -t ./pythonFiles/lib/python --no-cache-dir --implementation py --no-deps --upgrade -r requirements.txt python -m pip --disable-pip-version-check install -t ./pythonFiles/lib/python/old_ptvsd --no-cache-dir --implementation py --no-deps --upgrade 'ptvsd==4.3.2' + python -m pip --disable-pip-version-check install -t ./pythonFiles/lib/python/new_ptvsd/no_wheels --no-cache-dir --implementation py --no-deps --upgrade 'ptvsd==5.0.0a7' displayName: 'pip install system test requirements' condition: and(succeeded(), eq(variables['NeedsPythonTestReqs'], 'true')) diff --git a/pythonFiles/install_ptvsd.py b/pythonFiles/install_ptvsd.py index bfa7332a1956..8384910d6c86 100644 --- a/pythonFiles/install_ptvsd.py +++ b/pythonFiles/install_ptvsd.py @@ -6,7 +6,7 @@ EXTENSION_ROOT = os.path.dirname(os.path.dirname(os.path.abspath(__file__))) -DEBUGGER_DEST = os.path.join(EXTENSION_ROOT, "pythonFiles", "lib", "python") +DEBUGGER_DEST = os.path.join(EXTENSION_ROOT, "pythonFiles", "lib", "python", "new_ptvsd", "wheels") DEBUGGER_PACKAGE = "ptvsd" DEBUGGER_VERSION = "5.0.0a7" DEBUGGER_PYTHON_VERSIONS = ("cp37",) diff --git a/src/client/datascience/jupyter/jupyterDebugger.ts b/src/client/datascience/jupyter/jupyterDebugger.ts index 40eb56172b06..706d26755317 100644 --- a/src/client/datascience/jupyter/jupyterDebugger.ts +++ b/src/client/datascience/jupyter/jupyterDebugger.ts @@ -17,16 +17,7 @@ import { EXTENSION_ROOT_DIR } from '../../constants'; import { captureTelemetry, sendTelemetryEvent } from '../../telemetry'; import { concatMultilineStringOutput } from '../common'; import { Identifiers, Telemetry } from '../constants'; -import { - CellState, - ICell, - ICellHashListener, - IConnection, - IFileHashes, - IJupyterDebugger, - INotebook, - ISourceMapRequest -} from '../types'; +import { CellState, ICell, ICellHashListener, IConnection, IFileHashes, IJupyterDebugger, INotebook, ISourceMapRequest } from '../types'; import { JupyterDebuggerNotInstalledError } from './jupyterDebuggerNotInstalledError'; import { JupyterDebuggerRemoteNotSupported } from './jupyterDebuggerRemoteNotSupported'; import { ILiveShareHasRole } from './liveshare/types'; @@ -45,8 +36,7 @@ export class JupyterDebugger implements IJupyterDebugger, ICellHashListener { @inject(IPlatformService) private platform: IPlatformService, @inject(IWorkspaceService) private workspace: IWorkspaceService, @inject(IExperimentsManager) private readonly experimentsManager: IExperimentsManager - ) { - } + ) {} public async startDebugging(notebook: INotebook): Promise { traceInfo('start debugging'); @@ -114,9 +104,11 @@ export class JupyterDebugger implements IJupyterDebugger, ICellHashListener { public async hashesUpdated(hashes: IFileHashes[]): Promise { // Make sure that we have an active debugging session at this point if (this.debugService.activeDebugSession) { - await Promise.all(hashes.map((fileHash) => { - return this.debugService.activeDebugSession!.customRequest('setPydevdSourceMap', this.buildSourceMap(fileHash)); - })); + await Promise.all( + hashes.map(fileHash => { + return this.debugService.activeDebugSession!.customRequest('setPydevdSourceMap', this.buildSourceMap(fileHash)); + }) + ); } } @@ -184,18 +176,19 @@ export class JupyterDebugger implements IJupyterDebugger, ICellHashListener { */ private async getPtvsdPath(notebook: INotebook): Promise { const oldPtvsd = path.join(EXTENSION_ROOT_DIR, 'pythonFiles', 'lib', 'python', 'old_ptvsd'); - if (!this.experimentsManager.inExperiment(DebugAdapterDescriptorFactory.experiment) || - !this.experimentsManager.inExperiment(DebugAdapterNewPtvsd.experiment)){ + if (!this.experimentsManager.inExperiment(DebugAdapterDescriptorFactory.experiment) || !this.experimentsManager.inExperiment(DebugAdapterNewPtvsd.experiment)) { return oldPtvsd; } const pythonVersion = await this.getKernelPythonVersion(notebook); - // The new debug adapter is only supported in 3.7 + // The new debug adapter with wheels is only supported in 3.7 // Code can be found here (src/client/debugger/extension/adapter/factory.ts). - if (!pythonVersion || pythonVersion.major < 3 || pythonVersion.minor < 7){ - return oldPtvsd; + if (pythonVersion && pythonVersion.major === 3 && pythonVersion.minor === 7) { + // Return debugger with wheels + return path.join(EXTENSION_ROOT_DIR, 'pythonFiles', 'lib', 'python', 'new_ptvsd', 'wheels'); } - return path.join(EXTENSION_ROOT_DIR, 'pythonFiles', 'lib', 'python'); + // We are here so this is NOT python 3.7, return debugger without wheels + return path.join(EXTENSION_ROOT_DIR, 'pythonFiles', 'lib', 'python', 'new_ptvsd', 'no_wheels'); } private async calculatePtvsdPathList(notebook: INotebook): Promise { const extraPaths: string[] = []; @@ -311,7 +304,12 @@ export class JupyterDebugger implements IJupyterDebugger, ICellHashListener { const minor = parseInt(packageVersionMatch[2], 10); const patch = parseInt(packageVersionMatch[3], 10); return { - major, minor, patch, build: [], prerelease: [], raw: `${major}.${minor}.${patch}` + major, + minor, + patch, + build: [], + prerelease: [], + raw: `${major}.${minor}.${patch}` }; } } @@ -335,7 +333,11 @@ export class JupyterDebugger implements IJupyterDebugger, ICellHashListener { @captureTelemetry(Telemetry.PtvsdPromptToInstall) private async promptToInstallPtvsd(notebook: INotebook, oldVersion: Version | undefined): Promise { const promptMessage = oldVersion ? localize.DataScience.jupyterDebuggerInstallPtvsdUpdate() : localize.DataScience.jupyterDebuggerInstallPtvsdNew(); - const result = await this.appShell.showInformationMessage(promptMessage, localize.DataScience.jupyterDebuggerInstallPtvsdYes(), localize.DataScience.jupyterDebuggerInstallPtvsdNo()); + const result = await this.appShell.showInformationMessage( + promptMessage, + localize.DataScience.jupyterDebuggerInstallPtvsdYes(), + localize.DataScience.jupyterDebuggerInstallPtvsdNo() + ); if (result === localize.DataScience.jupyterDebuggerInstallPtvsdYes()) { await this.installPtvsd(notebook); @@ -424,7 +426,7 @@ export class JupyterDebugger implements IJupyterDebugger, ICellHashListener { const data = outputs[0].data; if (data && data.hasOwnProperty('text/plain')) { // tslint:disable-next-line:no-any - return ((data as any)['text/plain']); + return (data as any)['text/plain']; } if (outputs[0].output_type === 'stream') { const stream = outputs[0] as nbformat.IStream; diff --git a/src/client/debugger/extension/adapter/factory.ts b/src/client/debugger/extension/adapter/factory.ts index b591a7194a68..06391f8aa7db 100644 --- a/src/client/debugger/extension/adapter/factory.ts +++ b/src/client/debugger/extension/adapter/factory.ts @@ -12,6 +12,8 @@ import { traceVerbose } from '../../../common/logger'; import { IExperimentsManager } from '../../../common/types'; import { EXTENSION_ROOT_DIR } from '../../../constants'; import { IInterpreterService } from '../../../interpreter/contracts'; +import { sendTelemetryEvent } from '../../../telemetry'; +import { EventName } from '../../../telemetry/constants'; import { RemoteDebugOptions } from '../../debugAdapter/types'; import { AttachRequestArguments, LaunchRequestArguments } from '../../types'; import { IDebugAdapterDescriptorFactory } from '../types'; @@ -24,7 +26,7 @@ export class DebugAdapterDescriptorFactory implements IDebugAdapterDescriptorFac @inject(IInterpreterService) private readonly interpreterService: IInterpreterService, @inject(IApplicationShell) private readonly appShell: IApplicationShell, @inject(IExperimentsManager) private readonly experimentsManager: IExperimentsManager - ) { } + ) {} public async createDebugAdapterDescriptor(session: DebugSession, executable: DebugAdapterExecutable | undefined): Promise { const configuration = session.configuration as (LaunchRequestArguments | AttachRequestArguments); @@ -37,11 +39,17 @@ export class DebugAdapterDescriptorFactory implements IDebugAdapterDescriptorFac return new DebugAdapterServer(port, configuration.host); } else { const pythonPath = await this.getPythonPath(configuration, session.workspaceFolder); - if (await this.useNewPtvsd(pythonPath)) { - // If logToFile is set in the debug config then pass --log-dir when launching the debug adapter. - const logArgs = configuration.logToFile ? ['--log-dir', EXTENSION_ROOT_DIR] : []; - const ptvsdPathToUse = this.getPtvsdPath(); - return new DebugAdapterExecutable(pythonPath, [path.join(ptvsdPathToUse, 'adapter'), ...logArgs]); + // If logToFile is set in the debug config then pass --log-dir when launching the debug adapter. + const logArgs = configuration.logToFile ? ['--log-dir', EXTENSION_ROOT_DIR] : []; + const ptvsdPathToUse = path.join(EXTENSION_ROOT_DIR, 'pythonFiles', 'lib', 'python', 'new_ptvsd'); + if (pythonPath.length !== 0) { + if (await this.useNewPtvsd(pythonPath)) { + sendTelemetryEvent(EventName.DEBUG_ADAPTER_USING_WHEELS_PATH, undefined, { usingWheels: true }); + return new DebugAdapterExecutable(pythonPath, [path.join(ptvsdPathToUse, 'wheels', 'ptvsd', 'adapter'), ...logArgs]); + } else { + sendTelemetryEvent(EventName.DEBUG_ADAPTER_USING_WHEELS_PATH, undefined, { usingWheels: false }); + return new DebugAdapterExecutable(pythonPath, [path.join(ptvsdPathToUse, 'no_wheels', 'ptvsd', 'adapter'), ...logArgs]); + } } } } else { @@ -73,7 +81,7 @@ export class DebugAdapterDescriptorFactory implements IDebugAdapterDescriptorFac } public getPtvsdPath(): string { - return path.join(EXTENSION_ROOT_DIR, 'pythonFiles', 'lib', 'python', 'ptvsd'); + return path.join(EXTENSION_ROOT_DIR, 'pythonFiles', 'lib', 'python', 'new_ptvsd', 'no_wheels', 'ptvsd'); } public getRemotePtvsdArgs(remoteDebugOptions: RemoteDebugOptions): string[] { diff --git a/src/client/telemetry/constants.ts b/src/client/telemetry/constants.ts index a8dbbce7bb99..89e960ebfe87 100644 --- a/src/client/telemetry/constants.ts +++ b/src/client/telemetry/constants.ts @@ -39,6 +39,7 @@ export enum EventName { WORKSPACE_SYMBOLS_GO_TO = 'WORKSPACE_SYMBOLS.GO_TO', EXECUTION_CODE = 'EXECUTION_CODE', EXECUTION_DJANGO = 'EXECUTION_DJANGO', + DEBUG_ADAPTER_USING_WHEELS_PATH = 'DEBUG_ADAPTER.USING_WHEELS_PATH', DEBUG_SESSION_ERROR = 'DEBUG_SESSION.ERROR', DEBUG_SESSION_START = 'DEBUG_SESSION.START', DEBUG_SESSION_STOP = 'DEBUG_SESSION.STOP', diff --git a/src/client/telemetry/index.ts b/src/client/telemetry/index.ts index 2a8409154759..f0b720856da2 100644 --- a/src/client/telemetry/index.ts +++ b/src/client/telemetry/index.ts @@ -264,6 +264,17 @@ export interface IEventNamePropertyMapping { */ enabled: boolean; }; + /** + * Telemetry event captured when debug adapter executable is created + */ + [EventName.DEBUG_ADAPTER_USING_WHEELS_PATH]: { + /** + * Carries boolean + * - `true` if path used for the adapter is the debugger with wheels. + * - `false` if path used for the adapter is the source only version of the debugger. + */ + usingWheels: boolean; + }; /** * Telemetry captured before starting debug session. */ diff --git a/src/test/debugger/extension/adapter/factory.unit.test.ts b/src/test/debugger/extension/adapter/factory.unit.test.ts index 717c755c91eb..bc17d1e60a05 100644 --- a/src/test/debugger/extension/adapter/factory.unit.test.ts +++ b/src/test/debugger/extension/adapter/factory.unit.test.ts @@ -45,7 +45,8 @@ suite('Debugging - Adapter Factory', () => { let spiedInstance: ExperimentsManager; const nodeExecutable = { command: 'node', args: [] }; - const ptvsdAdapterPath = path.join(EXTENSION_ROOT_DIR, 'pythonFiles', 'lib', 'python', 'ptvsd', 'adapter'); + const ptvsdAdapterPathWithWheels = path.join(EXTENSION_ROOT_DIR, 'pythonFiles', 'lib', 'python', 'new_ptvsd', 'wheels', 'ptvsd', 'adapter'); + const ptvsdAdapterPathWithoutWheels = path.join(EXTENSION_ROOT_DIR, 'pythonFiles', 'lib', 'python', 'new_ptvsd', 'no_wheels', 'ptvsd', 'adapter'); const pythonPath = path.join('path', 'to', 'python', 'interpreter'); const interpreter = { architecture: Architecture.Unknown, @@ -55,6 +56,15 @@ suite('Debugging - Adapter Factory', () => { type: InterpreterType.Unknown, version: new SemVer('3.7.4-test') }; + const python36Path = path.join('path', 'to', 'active', 'interpreter'); + const interpreterPython36Details = { + architecture: Architecture.Unknown, + path: python36Path, + sysPrefix: '', + sysVersion: '', + type: InterpreterType.Unknown, + version: new SemVer('3.6.8-test') + }; const oldValueOfVSC_PYTHON_UNIT_TEST = process.env.VSC_PYTHON_UNIT_TEST; const oldValueOfVSC_PYTHON_CI_TEST = process.env.VSC_PYTHON_CI_TEST; @@ -103,11 +113,7 @@ suite('Debugging - Adapter Factory', () => { when(interpreterService.getInterpreterDetails(pythonPath)).thenResolve(interpreter); when(interpreterService.getInterpreters(anything())).thenResolve([interpreter]); - factory = new DebugAdapterDescriptorFactory( - instance(interpreterService), - instance(appShell), - experimentsManager - ); + factory = new DebugAdapterDescriptorFactory(instance(interpreterService), instance(appShell), experimentsManager); }); teardown(() => { @@ -133,7 +139,7 @@ suite('Debugging - Adapter Factory', () => { test('Return the value of configuration.pythonPath as the current python path if it exists and if we are in the experiment', async () => { const session = createSession({ pythonPath }); - const debugExecutable = new DebugAdapterExecutable(pythonPath, [ptvsdAdapterPath]); + const debugExecutable = new DebugAdapterExecutable(pythonPath, [ptvsdAdapterPathWithWheels]); when(spiedInstance.inExperiment(DebugAdapterNewPtvsd.experiment)).thenReturn(true); @@ -144,7 +150,7 @@ suite('Debugging - Adapter Factory', () => { test('Return the path of the active interpreter as the current python path if we are in the experiment, it exists and configuration.pythonPath is not defined', async () => { const session = createSession({}); - const debugExecutable = new DebugAdapterExecutable(pythonPath, [ptvsdAdapterPath]); + const debugExecutable = new DebugAdapterExecutable(pythonPath, [ptvsdAdapterPathWithWheels]); when(spiedInstance.inExperiment(DebugAdapterNewPtvsd.experiment)).thenReturn(true); when(interpreterService.getActiveInterpreter(anything())).thenResolve(interpreter); @@ -156,7 +162,7 @@ suite('Debugging - Adapter Factory', () => { test('Return the path of the first available interpreter as the current python path if we are in the experiment, configuration.pythonPath is not defined and there is no active interpreter', async () => { const session = createSession({}); - const debugExecutable = new DebugAdapterExecutable(pythonPath, [ptvsdAdapterPath]); + const debugExecutable = new DebugAdapterExecutable(pythonPath, [ptvsdAdapterPathWithWheels]); when(spiedInstance.inExperiment(DebugAdapterNewPtvsd.experiment)).thenReturn(true); @@ -213,27 +219,21 @@ suite('Debugging - Adapter Factory', () => { assert.deepEqual(descriptor, nodeExecutable); }); - test('Return old node debugger when the active interpreter is not Python 3.7', async () => { - const python36Path = path.join('path', 'to', 'active', 'interpreter'); - const interpreterPython36Details = { - architecture: Architecture.Unknown, - path: pythonPath, - sysPrefix: '', - sysVersion: '', - type: InterpreterType.Unknown, - version: new SemVer('3.6.8-test') - }; + test('Return Python debug adapter without wheels executable when the active interpreter is not Python 3.7', async () => { + const debugExecutable = new DebugAdapterExecutable(python36Path, [ptvsdAdapterPathWithoutWheels]); const session = createSession({}); + when(spiedInstance.inExperiment(DebugAdapterNewPtvsd.experiment)).thenReturn(true); + when(interpreterService.getInterpreters(anything())).thenResolve([interpreterPython36Details]); when(interpreterService.getInterpreterDetails(python36Path)).thenResolve(interpreterPython36Details); const descriptor = await factory.createDebugAdapterDescriptor(session, nodeExecutable); - assert.deepEqual(descriptor, nodeExecutable); + assert.deepEqual(descriptor, debugExecutable); }); - test('Return Python debug adapter executable when in the experiment and with the active interpreter being Python 3.7', async () => { - const debugExecutable = new DebugAdapterExecutable(pythonPath, [ptvsdAdapterPath]); + test('Return Python debug adapter with wheels executable when in the experiment and with the active interpreter being Python 3.7', async () => { + const debugExecutable = new DebugAdapterExecutable(pythonPath, [ptvsdAdapterPathWithWheels]); const session = createSession({}); when(spiedInstance.inExperiment(DebugAdapterNewPtvsd.experiment)).thenReturn(true); @@ -250,9 +250,9 @@ suite('Debugging - Adapter Factory', () => { await expect(promise).to.eventually.be.rejectedWith('Debug Adapter Executable not provided'); }); - test('Pass the --log-dir argument to PTVSD is configuration.logToFile is set', async () => { + test('Pass the --log-dir argument to PTVSD if configuration.logToFile is set, with active interpreter Python 3.7', async () => { const session = createSession({ logToFile: true }); - const debugExecutable = new DebugAdapterExecutable(pythonPath, [ptvsdAdapterPath, '--log-dir', EXTENSION_ROOT_DIR]); + const debugExecutable = new DebugAdapterExecutable(pythonPath, [ptvsdAdapterPathWithWheels, '--log-dir', EXTENSION_ROOT_DIR]); when(spiedInstance.inExperiment(DebugAdapterNewPtvsd.experiment)).thenReturn(true); @@ -261,9 +261,22 @@ suite('Debugging - Adapter Factory', () => { assert.deepEqual(descriptor, debugExecutable); }); - test('Don\'t pass the --log-dir argument to PTVSD is configuration.logToFile is not set', async () => { + test('Pass the --log-dir argument to PTVSD if configuration.logToFile is set, with active interpreter not Python 3.7', async () => { + const session = createSession({ logToFile: true }); + const debugExecutable = new DebugAdapterExecutable(python36Path, [ptvsdAdapterPathWithoutWheels, '--log-dir', EXTENSION_ROOT_DIR]); + + when(spiedInstance.inExperiment(DebugAdapterNewPtvsd.experiment)).thenReturn(true); + when(interpreterService.getInterpreters(anything())).thenResolve([interpreterPython36Details]); + when(interpreterService.getInterpreterDetails(python36Path)).thenResolve(interpreterPython36Details); + + const descriptor = await factory.createDebugAdapterDescriptor(session, nodeExecutable); + + assert.deepEqual(descriptor, debugExecutable); + }); + + test('Don\'t pass the --log-dir argument to PTVSD if configuration.logToFile is not set, with active interpreter Python 3.7', async () => { const session = createSession({}); - const debugExecutable = new DebugAdapterExecutable(pythonPath, [ptvsdAdapterPath]); + const debugExecutable = new DebugAdapterExecutable(pythonPath, [ptvsdAdapterPathWithWheels]); when(spiedInstance.inExperiment(DebugAdapterNewPtvsd.experiment)).thenReturn(true); @@ -272,25 +285,63 @@ suite('Debugging - Adapter Factory', () => { assert.deepEqual(descriptor, debugExecutable); }); - test('Don\'t pass the --log-dir argument to PTVSD is configuration.logToFile is set but false', async () => { + test('Don\'t pass the --log-dir argument to PTVSD if configuration.logToFile is not set, with active interpreter not Python 3.7', async () => { + const session = createSession({}); + const debugExecutable = new DebugAdapterExecutable(python36Path, [ptvsdAdapterPathWithoutWheels]); + + when(spiedInstance.inExperiment(DebugAdapterNewPtvsd.experiment)).thenReturn(true); + when(interpreterService.getInterpreters(anything())).thenResolve([interpreterPython36Details]); + when(interpreterService.getInterpreterDetails(python36Path)).thenResolve(interpreterPython36Details); + + const descriptor = await factory.createDebugAdapterDescriptor(session, nodeExecutable); + + assert.deepEqual(descriptor, debugExecutable); + }); + + test('Don\'t pass the --log-dir argument to PTVSD if configuration.logToFile is set but false, with active interpreter Python 3.7', async () => { + const session = createSession({ logToFile: false }); + const debugExecutable = new DebugAdapterExecutable(pythonPath, [ptvsdAdapterPathWithWheels]); + + when(spiedInstance.inExperiment(DebugAdapterNewPtvsd.experiment)).thenReturn(true); + + const descriptor = await factory.createDebugAdapterDescriptor(session, nodeExecutable); + + assert.deepEqual(descriptor, debugExecutable); + }); + + test('Don\'t pass the --log-dir argument to PTVSD if configuration.logToFile is set but false, with active interpreter not Python 3.7', async () => { const session = createSession({ logToFile: false }); - const debugExecutable = new DebugAdapterExecutable(pythonPath, [ptvsdAdapterPath]); + const debugExecutable = new DebugAdapterExecutable(python36Path, [ptvsdAdapterPathWithoutWheels]); when(spiedInstance.inExperiment(DebugAdapterNewPtvsd.experiment)).thenReturn(true); + when(interpreterService.getInterpreters(anything())).thenResolve([interpreterPython36Details]); + when(interpreterService.getInterpreterDetails(python36Path)).thenResolve(interpreterPython36Details); const descriptor = await factory.createDebugAdapterDescriptor(session, nodeExecutable); assert.deepEqual(descriptor, debugExecutable); }); - test('Send experiment group telemetry if inside the wheels experiment', async () => { + test('Send experiment group telemetry if inside the wheels experiment, with active interpreter Python 3.7', async () => { const session = createSession({}); when(spiedInstance.userExperiments).thenReturn([{ name: DebugAdapterNewPtvsd.experiment, salt: DebugAdapterNewPtvsd.experiment, min: 0, max: 0 }]); await factory.createDebugAdapterDescriptor(session, nodeExecutable); - assert.deepEqual(Reporter.eventNames, [EventName.PYTHON_EXPERIMENTS]); - assert.deepEqual(Reporter.properties, [{ expName: DebugAdapterNewPtvsd.experiment }]); + assert.deepEqual(Reporter.eventNames, [EventName.PYTHON_EXPERIMENTS, EventName.DEBUG_ADAPTER_USING_WHEELS_PATH]); + assert.deepEqual(Reporter.properties, [{ expName: DebugAdapterNewPtvsd.experiment }, { usingWheels: 'true' }]); + }); + + test('Send experiment group telemetry if inside the wheels experiment, with active interpreter not Python 3.6', async () => { + const session = createSession({}); + when(spiedInstance.userExperiments).thenReturn([{ name: DebugAdapterNewPtvsd.experiment, salt: DebugAdapterNewPtvsd.experiment, min: 0, max: 0 }]); + when(interpreterService.getInterpreters(anything())).thenResolve([interpreterPython36Details]); + when(interpreterService.getInterpreterDetails(python36Path)).thenResolve(interpreterPython36Details); + + await factory.createDebugAdapterDescriptor(session, nodeExecutable); + + assert.deepEqual(Reporter.eventNames, [EventName.PYTHON_EXPERIMENTS, EventName.DEBUG_ADAPTER_USING_WHEELS_PATH]); + assert.deepEqual(Reporter.properties, [{ expName: DebugAdapterNewPtvsd.experiment }, { usingWheels: 'false' }]); }); test('Send control group telemetry if inside the DA experiment control group', async () => { From b17619620e36d2feacd150300b4b6fdcff5d9e51 Mon Sep 17 00:00:00 2001 From: Karthik Nadig Date: Tue, 12 Nov 2019 19:25:15 -0800 Subject: [PATCH 06/21] Adjust debug adapter experiment before release (#8540) (#8553) --- experiments.json | 10 +++++----- src/client/common/experimentGroups.ts | 4 ++-- 2 files changed, 7 insertions(+), 7 deletions(-) diff --git a/experiments.json b/experiments.json index 565efa3b4df6..c6327eeba69d 100644 --- a/experiments.json +++ b/experiments.json @@ -30,16 +30,16 @@ "max": 20 }, { - "name": "DebugAdapterFactoryInsiders - control", + "name": "DebugAdapterFactory - control", "salt": "DebugAdapterFactory", "min": 0, - "max": 50 + "max": 0 }, { - "name": "DebugAdapterFactoryInsiders - experiment", + "name": "DebugAdapterFactory - experiment", "salt": "DebugAdapterFactory", - "min": 50, - "max": 100 + "min": 0, + "max": 0 }, { "name": "PtvsdWheels37 - control", diff --git a/src/client/common/experimentGroups.ts b/src/client/common/experimentGroups.ts index 0957892f564e..9ca508801ec9 100644 --- a/src/client/common/experimentGroups.ts +++ b/src/client/common/experimentGroups.ts @@ -15,8 +15,8 @@ export enum ShowExtensionSurveyPrompt { // Experiment to check whether the extension should use the new VS Code debug adapter API. export enum DebugAdapterDescriptorFactory { - control = 'DebugAdapterFactoryInsiders - control', - experiment = 'DebugAdapterFactoryInsiders - experiment' + control = 'DebugAdapterFactory - control', + experiment = 'DebugAdapterFactory - experiment' } // Experiment to check whether the ptvsd launcher should use pre-installed ptvsd wheels for debugging. From 24b60ecd8774f32497e1a82ecc2396cd63b525a9 Mon Sep 17 00:00:00 2001 From: Karthik Nadig Date: Wed, 13 Nov 2019 17:22:59 -0800 Subject: [PATCH 07/21] Release branch: updating change log and package json (#8570) * Change version numbers * Update package lock json * Update changelog --- CHANGELOG.md | 8 +++++++- news/2 Fixes/8399.md | 1 - news/2 Fixes/8423.md | 1 - news/2 Fixes/8424.md | 1 - package-lock.json | 2 +- package.json | 4 ++-- 6 files changed, 10 insertions(+), 7 deletions(-) delete mode 100644 news/2 Fixes/8399.md delete mode 100644 news/2 Fixes/8423.md delete mode 100644 news/2 Fixes/8424.md diff --git a/CHANGELOG.md b/CHANGELOG.md index 756bf52b7a9f..2fa7fa848690 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,6 +1,6 @@ # Changelog -## 2019.11.0-rc (7 November 2019) +## 2019.11.0 (13 November 2019) ### Enhancements @@ -113,6 +113,12 @@ ([#8296](https://github.com/Microsoft/vscode-python/issues/8296)) 1. Correctly transition markdown cells into code cells. ([#8386](https://github.com/Microsoft/vscode-python/issues/8386)) +1. Fix cells being erased when saving and then changing focus to another cell. + ([#8399](https://github.com/Microsoft/vscode-python/issues/8399)) +1. Add a white background for most non-text mimetypes. This lets stuff like Atlair look good in dark mode. + ([#8423](https://github.com/Microsoft/vscode-python/issues/8423)) +1. Export to python button is blue in native editor. + ([#8424](https://github.com/Microsoft/vscode-python/issues/8424)) ### Code Health diff --git a/news/2 Fixes/8399.md b/news/2 Fixes/8399.md deleted file mode 100644 index 31329e4755d3..000000000000 --- a/news/2 Fixes/8399.md +++ /dev/null @@ -1 +0,0 @@ -Fix cells being erased when saving and then changing focus to another cell. diff --git a/news/2 Fixes/8423.md b/news/2 Fixes/8423.md deleted file mode 100644 index 8879d8ae89ad..000000000000 --- a/news/2 Fixes/8423.md +++ /dev/null @@ -1 +0,0 @@ -Add a white backtground for most non-text mimetypes. This lets stuff like Atlair look good in dark mode. \ No newline at end of file diff --git a/news/2 Fixes/8424.md b/news/2 Fixes/8424.md deleted file mode 100644 index 735307663e6d..000000000000 --- a/news/2 Fixes/8424.md +++ /dev/null @@ -1 +0,0 @@ -Export to python button is blue in native editor. \ No newline at end of file diff --git a/package-lock.json b/package-lock.json index 1d58919908c7..a7d83e98faa9 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,6 +1,6 @@ { "name": "python", - "version": "2019.11.0-rc", + "version": "2019.11.0", "lockfileVersion": 1, "requires": true, "dependencies": { diff --git a/package.json b/package.json index 6f2edce3edd3..5131c177cb7a 100644 --- a/package.json +++ b/package.json @@ -2,8 +2,8 @@ "name": "python", "displayName": "Python", "description": "Linting, Debugging (multi-threaded, remote), Intellisense, code formatting, refactoring, unit tests, snippets, and more.", - "version": "2019.11.0-rc", - "languageServerVersion": "0.4.71", + "version": "2019.11.0", + "languageServerVersion": "0.4.114", "publisher": "ms-python", "author": { "name": "Microsoft Corporation" From 5fbe29e168581178cf3d1d4945aca02aa6d368cf Mon Sep 17 00:00:00 2001 From: Rich Chiodo Date: Wed, 13 Nov 2019 17:45:13 -0800 Subject: [PATCH 08/21] Fix for 7999 - CTRL+Z should not undo at the top level (#8571) --- news/2 Fixes/7999.md | 1 + .../interactiveWindowTypes.ts | 1 + .../interactive-common/mainStateController.ts | 48 ++++++++++--------- .../native-editor/nativeCell.tsx | 15 ++++-- .../native-editor/nativeEditor.tsx | 12 +++++ .../nativeEditor.functional.test.tsx | 42 +++++++++++++++- 6 files changed, 91 insertions(+), 28 deletions(-) create mode 100644 news/2 Fixes/7999.md diff --git a/news/2 Fixes/7999.md b/news/2 Fixes/7999.md new file mode 100644 index 000000000000..216f1c461491 --- /dev/null +++ b/news/2 Fixes/7999.md @@ -0,0 +1 @@ +CTRL+Z is deleting cells. It should only undo changes inside of the code for a cell. 'Z' and 'SHIFT+Z' are for undoing/redoing cell adds/moves. diff --git a/src/client/datascience/interactive-common/interactiveWindowTypes.ts b/src/client/datascience/interactive-common/interactiveWindowTypes.ts index ec9e1504f887..01cd53251700 100644 --- a/src/client/datascience/interactive-common/interactiveWindowTypes.ts +++ b/src/client/datascience/interactive-common/interactiveWindowTypes.ts @@ -97,6 +97,7 @@ export enum NativeCommandType { InsertBelow, MoveCellDown, MoveCellUp, + Redo, Run, RunAbove, RunAll, diff --git a/src/datascience-ui/interactive-common/mainStateController.ts b/src/datascience-ui/interactive-common/mainStateController.ts index dad2105f645f..8cb3e2acadae 100644 --- a/src/datascience-ui/interactive-common/mainStateController.ts +++ b/src/datascience-ui/interactive-common/mainStateController.ts @@ -267,31 +267,35 @@ export class MainStateController implements IMessageHandler { } public redo = () => { - // Pop one off of our redo stack and update our undo - const cells = this.pendingState.redoStack[this.pendingState.redoStack.length - 1]; - const redoStack = this.pendingState.redoStack.slice(0, this.pendingState.redoStack.length - 1); - const undoStack = this.pushStack(this.pendingState.undoStack, this.pendingState.cellVMs); - this.sendMessage(InteractiveWindowMessages.Redo); - this.setState({ - cellVMs: cells, - undoStack: undoStack, - redoStack: redoStack, - skipNextScroll: true - }); + if (this.pendingState.redoStack.length > 0) { + // Pop one off of our redo stack and update our undo + const cells = this.pendingState.redoStack[this.pendingState.redoStack.length - 1]; + const redoStack = this.pendingState.redoStack.slice(0, this.pendingState.redoStack.length - 1); + const undoStack = this.pushStack(this.pendingState.undoStack, this.pendingState.cellVMs); + this.sendMessage(InteractiveWindowMessages.Redo); + this.setState({ + cellVMs: cells, + undoStack: undoStack, + redoStack: redoStack, + skipNextScroll: true + }); + } } public undo = () => { - // Pop one off of our undo stack and update our redo - const cells = this.pendingState.undoStack[this.pendingState.undoStack.length - 1]; - const undoStack = this.pendingState.undoStack.slice(0, this.pendingState.undoStack.length - 1); - const redoStack = this.pushStack(this.pendingState.redoStack, this.pendingState.cellVMs); - this.sendMessage(InteractiveWindowMessages.Undo); - this.setState({ - cellVMs: cells, - undoStack: undoStack, - redoStack: redoStack, - skipNextScroll: true - }); + if (this.pendingState.undoStack.length > 0) { + // Pop one off of our undo stack and update our redo + const cells = this.pendingState.undoStack[this.pendingState.undoStack.length - 1]; + const undoStack = this.pendingState.undoStack.slice(0, this.pendingState.undoStack.length - 1); + const redoStack = this.pushStack(this.pendingState.redoStack, this.pendingState.cellVMs); + this.sendMessage(InteractiveWindowMessages.Undo); + this.setState({ + cellVMs: cells, + undoStack: undoStack, + redoStack: redoStack, + skipNextScroll: true + }); + } } public deleteCell = (cellId: string) => { diff --git a/src/datascience-ui/native-editor/nativeCell.tsx b/src/datascience-ui/native-editor/nativeCell.tsx index 08500ae84dd0..567216abeb58 100644 --- a/src/datascience-ui/native-editor/nativeCell.tsx +++ b/src/datascience-ui/native-editor/nativeCell.tsx @@ -325,10 +325,17 @@ export class NativeCell extends React.Component { } break; case 'z': - if (!this.isFocused() && this.props.stateController.canUndo()) { - e.stopPropagation(); - this.props.stateController.undo(); - this.props.stateController.sendCommand(NativeCommandType.Undo, 'keyboard'); + case 'Z': + if (!this.isFocused()) { + if (e.shiftKey && !e.ctrlKey && !e.altKey && this.props.stateController.canRedo()) { + e.stopPropagation(); + this.props.stateController.redo(); + this.props.stateController.sendCommand(NativeCommandType.Redo, 'keyboard'); + } else if (!e.shiftKey && !e.altKey && !e.ctrlKey && this.props.stateController.canUndo()) { + e.stopPropagation(); + this.props.stateController.undo(); + this.props.stateController.sendCommand(NativeCommandType.Undo, 'keyboard'); + } } break; diff --git a/src/datascience-ui/native-editor/nativeEditor.tsx b/src/datascience-ui/native-editor/nativeEditor.tsx index 804382da9c1d..666e53377ac8 100644 --- a/src/datascience-ui/native-editor/nativeEditor.tsx +++ b/src/datascience-ui/native-editor/nativeEditor.tsx @@ -365,6 +365,18 @@ export class NativeEditor extends React.Component Date: Thu, 14 Nov 2019 09:39:05 -0800 Subject: [PATCH 09/21] Fix arrowing up and down to save code too (#8574) --- news/2 Fixes/8491.md | 1 + .../interactive-common/mainStateController.ts | 12 ++++++++++++ src/datascience-ui/native-editor/nativeCell.tsx | 6 ++++++ 3 files changed, 19 insertions(+) create mode 100644 news/2 Fixes/8491.md diff --git a/news/2 Fixes/8491.md b/news/2 Fixes/8491.md new file mode 100644 index 000000000000..87fc913bf6ad --- /dev/null +++ b/news/2 Fixes/8491.md @@ -0,0 +1 @@ +Arrowing up and down through cells can lose code that was just typed. diff --git a/src/datascience-ui/interactive-common/mainStateController.ts b/src/datascience-ui/interactive-common/mainStateController.ts index 8cb3e2acadae..34c00bfd9e75 100644 --- a/src/datascience-ui/interactive-common/mainStateController.ts +++ b/src/datascience-ui/interactive-common/mainStateController.ts @@ -561,6 +561,18 @@ export class MainStateController implements IMessageHandler { } } + public updateCode = (cellId: string, code: string) => { + const index = this.pendingState.cellVMs.findIndex(c => c.cell.id === cellId); + if (index > 0) { + const cellVMs = [...this.pendingState.cellVMs]; + const current = this.pendingState.cellVMs[index]; + const newCell = { ...current, inputBlockText: code, cell: { ...current.cell, data: { ...current.cell.data, source: code } } }; + // tslint:disable-next-line: no-any + cellVMs[index] = (newCell as any); // This is because IMessageCell doesn't fit in here. But message cells can't change type + this.setState({ cellVMs }); + } + } + public changeCellType = (cellId: string, newType: 'code' | 'markdown') => { const index = this.pendingState.cellVMs.findIndex(c => c.cell.id === cellId); if (index >= 0 && this.pendingState.cellVMs[index].cell.data.cell_type !== newType) { diff --git a/src/datascience-ui/native-editor/nativeCell.tsx b/src/datascience-ui/native-editor/nativeCell.tsx index 567216abeb58..c88c6e8b8544 100644 --- a/src/datascience-ui/native-editor/nativeCell.tsx +++ b/src/datascience-ui/native-editor/nativeCell.tsx @@ -392,6 +392,9 @@ export class NativeCell extends React.Component { const prevCellId = this.getPrevCellId(); if (prevCellId) { e.stopPropagation(); + if (e.editorInfo) { + this.props.stateController.updateCode(this.getCell().id, e.editorInfo.contents); + } this.moveSelection(prevCellId, this.isFocused(), CursorPos.Bottom); } @@ -403,6 +406,9 @@ export class NativeCell extends React.Component { if (nextCellId) { e.stopPropagation(); + if (e.editorInfo) { + this.props.stateController.updateCode(this.getCell().id, e.editorInfo.contents); + } this.moveSelection(nextCellId, this.isFocused(), CursorPos.Top); } From 3676d20a1e5694bcad0a9083b312c8cfbd359ef8 Mon Sep 17 00:00:00 2001 From: Don Jayamanne Date: Thu, 14 Nov 2019 14:28:32 -0800 Subject: [PATCH 10/21] Prompt selecting a file to save once (#8590) * Prompt selecting a file to save once * Better algorithm --- news/2 Fixes/8138.md | 1 + src/client/datascience/interactive-ipynb/nativeEditor.ts | 9 +++++++++ 2 files changed, 10 insertions(+) create mode 100644 news/2 Fixes/8138.md diff --git a/news/2 Fixes/8138.md b/news/2 Fixes/8138.md new file mode 100644 index 000000000000..6826001761b5 --- /dev/null +++ b/news/2 Fixes/8138.md @@ -0,0 +1 @@ +Ensure clicking `ctrl+s` in a new `notebook` prompts the user to select a file once instead of twice. diff --git a/src/client/datascience/interactive-ipynb/nativeEditor.ts b/src/client/datascience/interactive-ipynb/nativeEditor.ts index 6fb62dd8fcb0..e6f87318e7d0 100644 --- a/src/client/datascience/interactive-ipynb/nativeEditor.ts +++ b/src/client/datascience/interactive-ipynb/nativeEditor.ts @@ -83,6 +83,7 @@ export class NativeEditor extends InteractiveBase implements INotebookEditor { private loadedPromise: Deferred = createDeferred(); private _file: Uri = Uri.file(''); private _dirty: boolean = false; + private isPromptingToSaveToDisc: boolean = false; private visibleCells: ICell[] = []; private startupTimer: StopWatch = new StopWatch(); private loadedAllCells: boolean = false; @@ -837,12 +838,18 @@ export class NativeEditor extends InteractiveBase implements INotebookEditor { @captureTelemetry(Telemetry.Save, undefined, true) private async saveToDisk(): Promise { + // If we're already in the middle of prompting the user to save, then get out of here. + // We could add a debounce decorator, unfortunately that slows saving (by waiting for no more save events to get sent). + if (this.isPromptingToSaveToDisc && this.isUntitled) { + return; + } try { let fileToSaveTo: Uri | undefined = this.file; let isDirty = this._dirty; // Ask user for a save as dialog if no title if (this.isUntitled) { + this.isPromptingToSaveToDisc = true; const filtersKey = localize.DataScience.dirtyNotebookDialogFilter(); const filtersObject: { [name: string]: string[] } = {}; filtersObject[filtersKey] = ['ipynb']; @@ -865,6 +872,8 @@ export class NativeEditor extends InteractiveBase implements INotebookEditor { } } catch (e) { traceError(e); + } finally { + this.isPromptingToSaveToDisc = false; } } From 6110d15fdb915aabea723b95c2a79c3d14e7e7df Mon Sep 17 00:00:00 2001 From: Rich Chiodo Date: Thu, 14 Nov 2019 14:28:41 -0800 Subject: [PATCH 11/21] After pasting code, arrow keys don't navigate in a cell. (#8587) --- news/2 Fixes/8495.md | 1 + src/datascience-ui/react-common/monacoEditor.tsx | 11 ++++++----- 2 files changed, 7 insertions(+), 5 deletions(-) create mode 100644 news/2 Fixes/8495.md diff --git a/news/2 Fixes/8495.md b/news/2 Fixes/8495.md new file mode 100644 index 000000000000..d4d86a50faf2 --- /dev/null +++ b/news/2 Fixes/8495.md @@ -0,0 +1 @@ +After pasting code, arrow keys don't navigate in a cell. diff --git a/src/datascience-ui/react-common/monacoEditor.tsx b/src/datascience-ui/react-common/monacoEditor.tsx index 5e3440018556..30562454dcd8 100644 --- a/src/datascience-ui/react-common/monacoEditor.tsx +++ b/src/datascience-ui/react-common/monacoEditor.tsx @@ -319,9 +319,9 @@ export class MonacoEditor extends React.Component { const match = l.style.top ? /(.+)px/.exec(l.style.top) : null; return match ? parseInt(match[0], 10) : Infinity; - }); + }).sort((a, b) => a - b); return this.lineTops; } @@ -359,7 +361,6 @@ export class MonacoEditor extends React.Component= 0) { - window.console.log(`Scrolling to line ${current}`); visibleLineDivs[current].scrollIntoView({ behavior: 'auto', block: 'nearest', inline: 'nearest' }); } } From b19eb759595a713dc7c0d909bdb94e13ad1e29ab Mon Sep 17 00:00:00 2001 From: Rich Chiodo Date: Thu, 14 Nov 2019 15:45:53 -0800 Subject: [PATCH 12/21] Don't look for a jupyter interpreter when creating blank notebook (#8596) * Creating a new blank notebook should not require a search for jupyter. * Fix bug caused by other fix. --- news/2 Fixes/8481.md | 1 + news/2 Fixes/8594.md | 1 + .../interactive-ipynb/nativeEditor.ts | 61 +++++++++++-------- .../native-editor/nativeEditor.tsx | 18 +++--- .../nativeEditor.unit.test.ts | 48 ++++++++++----- 5 files changed, 82 insertions(+), 47 deletions(-) create mode 100644 news/2 Fixes/8481.md create mode 100644 news/2 Fixes/8594.md diff --git a/news/2 Fixes/8481.md b/news/2 Fixes/8481.md new file mode 100644 index 000000000000..6361eb88d530 --- /dev/null +++ b/news/2 Fixes/8481.md @@ -0,0 +1 @@ +Creating a new blank notebook should not require a search for jupyter. diff --git a/news/2 Fixes/8594.md b/news/2 Fixes/8594.md new file mode 100644 index 000000000000..c181570a62a7 --- /dev/null +++ b/news/2 Fixes/8594.md @@ -0,0 +1 @@ +Typing 'z' in a cell causes the cell to disappear. diff --git a/src/client/datascience/interactive-ipynb/nativeEditor.ts b/src/client/datascience/interactive-ipynb/nativeEditor.ts index e6f87318e7d0..22a088487a64 100644 --- a/src/client/datascience/interactive-ipynb/nativeEditor.ts +++ b/src/client/datascience/interactive-ipynb/nativeEditor.ts @@ -164,7 +164,7 @@ export class NativeEditor extends InteractiveBase implements INotebookEditor { return this.close(); } - public get contents(): string { + public getContents(): Promise { return this.generateNotebookContent(this.visibleCells); } @@ -470,6 +470,9 @@ export class NativeEditor extends InteractiveBase implements INotebookEditor { * @memberof NativeEditor */ private async updateVersionInfoInNotebook(): Promise { + // Make sure we have notebook json + await this.ensureNotebookJson(); + // Use the active interpreter const usableInterpreter = await this.jupyterExecution.getUsableJupyterPython(); if (usableInterpreter && usableInterpreter.version && this.notebookJson.metadata && this.notebookJson.metadata.language_info) { @@ -477,24 +480,8 @@ export class NativeEditor extends InteractiveBase implements INotebookEditor { } } - private async loadContents(contents: string | undefined, forceDirty: boolean): Promise { - // tslint:disable-next-line: no-any - const json = contents ? JSON.parse(contents) as any : undefined; - - // Double check json (if we have any) - if (json && !json.cells) { - throw new InvalidNotebookFileError(this.file.fsPath); - } - - // Then compute indent. It's computed from the contents - if (contents) { - this.indentAmount = detectIndent(contents).indent; - } - - // Then save the contents. We'll stick our cells back into this format when we save - if (json) { - this.notebookJson = json; - } else { + private async ensureNotebookJson(): Promise { + if (!this.notebookJson || !this.notebookJson.metadata) { const pythonNumber = await this.extractPythonMainVersion(this.notebookJson); // Use this to build our metadata object // Use these as the defaults unless we have been given some in the options. @@ -522,6 +509,26 @@ export class NativeEditor extends InteractiveBase implements INotebookEditor { metadata: metadata }; } + } + + private async loadContents(contents: string | undefined, forceDirty: boolean): Promise { + // tslint:disable-next-line: no-any + const json = contents ? JSON.parse(contents) as any : undefined; + + // Double check json (if we have any) + if (json && !json.cells) { + throw new InvalidNotebookFileError(this.file.fsPath); + } + + // Then compute indent. It's computed from the contents + if (contents) { + this.indentAmount = detectIndent(contents).indent; + } + + // Then save the contents. We'll stick our cells back into this format when we save + if (json) { + this.notebookJson = json; + } // Extract cells from the json const cells = contents ? json.cells as (nbformat.ICodeCell | nbformat.IRawCell | nbformat.IMarkdownCell)[] : []; @@ -743,9 +750,12 @@ export class NativeEditor extends InteractiveBase implements INotebookEditor { } private async setDirty(): Promise { - // Always update storage. Don't wait for results. - this.storeContents(this.generateNotebookContent(this.visibleCells)) - .catch(ex => traceError('Failed to generate notebook content to store in state', ex)); + // Update storage if not untitled. Don't wait for results. + if (!this.isUntitled) { + this.generateNotebookContent(this.visibleCells) + .then(c => this.storeContents(c).catch(ex => traceError('Failed to generate notebook content to store in state', ex))) + .ignoreErrors(); + } // Then update dirty flag. if (!this._dirty) { @@ -827,7 +837,10 @@ export class NativeEditor extends InteractiveBase implements INotebookEditor { return usableInterpreter && usableInterpreter.version ? usableInterpreter.version.major : 3; } - private generateNotebookContent(cells: ICell[]): string { + private async generateNotebookContent(cells: ICell[]): Promise { + // Make sure we have some + await this.ensureNotebookJson(); + // Reuse our original json except for the cells. const json = { ...(this.notebookJson as nbformat.INotebookContent), @@ -863,7 +876,7 @@ export class NativeEditor extends InteractiveBase implements INotebookEditor { if (fileToSaveTo && isDirty) { // Write out our visible cells - await this.fileSystem.writeFile(fileToSaveTo.fsPath, this.generateNotebookContent(this.visibleCells)); + await this.fileSystem.writeFile(fileToSaveTo.fsPath, await this.generateNotebookContent(this.visibleCells)); // Update our file name and dirty state this._file = fileToSaveTo; diff --git a/src/datascience-ui/native-editor/nativeEditor.tsx b/src/datascience-ui/native-editor/nativeEditor.tsx index 666e53377ac8..88d5c5fc6952 100644 --- a/src/datascience-ui/native-editor/nativeEditor.tsx +++ b/src/datascience-ui/native-editor/nativeEditor.tsx @@ -367,14 +367,16 @@ export class NativeEditor extends React.Component { test('Create new editor and add some cells', async () => { const editor = createEditor(); await editor.load(baseFile, Uri.parse('file:///foo.ipynb')); - expect(editor.contents).to.be.equal(baseFile); + expect(await editor.getContents()).to.be.equal(baseFile); editor.onMessage(InteractiveWindowMessages.InsertCell, { index: 0, cell: createEmptyCell('1', 1) }); expect(editor.cells).to.be.lengthOf(4); expect(editor.isDirty).to.be.equal(true, 'Editor should be dirty'); @@ -273,7 +273,7 @@ suite('Data Science - Native Editor', () => { test('Move cells around', async () => { const editor = createEditor(); await editor.load(baseFile, Uri.parse('file:///foo.ipynb')); - expect(editor.contents).to.be.equal(baseFile); + expect(await editor.getContents()).to.be.equal(baseFile); editor.onMessage(InteractiveWindowMessages.SwapCells, { firstCellId: 'NotebookImport#0', secondCellId: 'NotebookImport#1' }); expect(editor.cells).to.be.lengthOf(3); expect(editor.isDirty).to.be.equal(true, 'Editor should be dirty'); @@ -283,7 +283,7 @@ suite('Data Science - Native Editor', () => { test('Edit/delete cells', async () => { const editor = createEditor(); await editor.load(baseFile, Uri.parse('file:///foo.ipynb')); - expect(editor.contents).to.be.equal(baseFile); + expect(await editor.getContents()).to.be.equal(baseFile); expect(editor.isDirty).to.be.equal(false, 'Editor should not be dirty'); editor.onMessage(InteractiveWindowMessages.EditCell, { changes: [{ @@ -313,7 +313,7 @@ suite('Data Science - Native Editor', () => { async function loadEditorAddCellAndWaitForMementoUpdate(file: Uri) { const editor = createEditor(); await editor.load(baseFile, file); - expect(editor.contents).to.be.equal(baseFile); + expect(await editor.getContents()).to.be.equal(baseFile); storageUpdateSpy.resetHistory(); editor.onMessage(InteractiveWindowMessages.InsertCell, { index: 0, cell: createEmptyCell('1', 1) }); expect(editor.cells).to.be.lengthOf(4); @@ -325,7 +325,7 @@ suite('Data Science - Native Editor', () => { // Confirm contents were saved. expect(storage.get(`notebook-storage-${file.toString()}`)).not.to.be.undefined; - expect(editor.contents).not.to.be.equal(baseFile); + expect(await editor.getContents()).not.to.be.equal(baseFile); return editor; } @@ -359,8 +359,8 @@ suite('Data Science - Native Editor', () => { // Verify contents are different. // Meaning it was not loaded from file, but loaded from our storage. - expect(newEditor.contents).not.to.be.equal(baseFile); - const notebook = JSON.parse(newEditor.contents); + expect(await newEditor.getContents()).not.to.be.equal(baseFile); + const notebook = JSON.parse(await newEditor.getContents()); // 4 cells (1 extra for what was added) expect(notebook.cells).to.be.lengthOf(4); }); @@ -387,8 +387,8 @@ suite('Data Science - Native Editor', () => { // Verify contents are different. // Meaning it was not loaded from file, but loaded from our storage. - expect(newEditor.contents).not.to.be.equal(baseFile); - const notebook = JSON.parse(newEditor.contents); + expect(await newEditor.getContents()).not.to.be.equal(baseFile); + const notebook = JSON.parse(await newEditor.getContents()); // 4 cells (1 extra for what was added) expect(notebook.cells).to.be.lengthOf(4); }); @@ -417,18 +417,36 @@ suite('Data Science - Native Editor', () => { // Verify contents are different. // Meaning it was not loaded from file, but loaded from our storage. - expect(newEditor.contents).to.be.equal(baseFile); + expect(await newEditor.getContents()).to.be.equal(baseFile); expect(newEditor.cells).to.be.lengthOf(3); }); + test('Python version info is not queried on creating a blank editor', async () => { + const file = Uri.parse('file:///Untitled1.ipynb'); + + // When a cell is executed, then ensure we store the python version info in the notebook data. + when(executionProvider.getUsableJupyterPython()).thenReject(); + + const editor = createEditor(); + await editor.load('', file); + + try { + await editor.getContents(); + expect(false, 'Did not throw an error'); + } catch { + // This should throw an error + noop(); + } + }); + test('Pyton version info will be updated in notebook when a cell has been executed', async () => { const file = Uri.parse('file:///foo.ipynb'); const editor = createEditor(); await editor.load(baseFile, file); - expect(editor.contents).to.be.equal(baseFile); + expect(await editor.getContents()).to.be.equal(baseFile); // At the begining version info is NOT in the file (at least not the same as what we are using to run cells). - let contents = JSON.parse(editor.contents) as nbformat.INotebookContent; + let contents = JSON.parse(await editor.getContents()) as nbformat.INotebookContent; expect(contents.metadata!.language_info!.version).to.not.equal('10.11.12'); // When a cell is executed, then ensure we store the python version info in the notebook data. @@ -453,7 +471,7 @@ suite('Data Science - Native Editor', () => { }, 5_000, 'Timeout'); // Verify the version info is in the notbook. - contents = JSON.parse(editor.contents) as nbformat.INotebookContent; + contents = JSON.parse(await editor.getContents()) as nbformat.INotebookContent; expect(contents.metadata!.language_info!.version).to.equal('10.11.12'); }); @@ -471,7 +489,7 @@ suite('Data Science - Native Editor', () => { await editor.load('', file); // It should load with that value - expect(editor.contents).to.be.equal(baseFile); + expect(await editor.getContents()).to.be.equal(baseFile); expect(editor.cells).to.be.lengthOf(3); }); @@ -496,7 +514,7 @@ suite('Data Science - Native Editor', () => { const editor = createEditor(); await editor.load(baseFile, file); - expect(editor.contents).to.be.equal(baseFile); + expect(await editor.getContents()).to.be.equal(baseFile); // Make our call to actually export editor.onMessage(InteractiveWindowMessages.Export, editor.cells); From 7ae0594dffda0ece03b3b41b808160a8b0aa0f0f Mon Sep 17 00:00:00 2001 From: Rich Chiodo Date: Fri, 15 Nov 2019 11:07:08 -0800 Subject: [PATCH 13/21] Update change log --- CHANGELOG.md | 12 ++++++++++++ news/2 Fixes/7999.md | 1 - news/2 Fixes/8138.md | 1 - news/2 Fixes/8481.md | 1 - news/2 Fixes/8491.md | 1 - news/2 Fixes/8495.md | 1 - news/2 Fixes/8594.md | 1 - 7 files changed, 12 insertions(+), 6 deletions(-) delete mode 100644 news/2 Fixes/7999.md delete mode 100644 news/2 Fixes/8138.md delete mode 100644 news/2 Fixes/8481.md delete mode 100644 news/2 Fixes/8491.md delete mode 100644 news/2 Fixes/8495.md delete mode 100644 news/2 Fixes/8594.md diff --git a/CHANGELOG.md b/CHANGELOG.md index 2fa7fa848690..7d047c3e76a1 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -119,6 +119,18 @@ ([#8423](https://github.com/Microsoft/vscode-python/issues/8423)) 1. Export to python button is blue in native editor. ([#8424](https://github.com/Microsoft/vscode-python/issues/8424)) +1. CTRL+Z is deleting cells. It should only undo changes inside of the code for a cell. 'Z' and 'SHIFT+Z' are for undoing/redoing cell adds/moves. + ([#7999](https://github.com/Microsoft/vscode-python/issues/7999)) +1. Ensure clicking `ctrl+s` in a new `notebook` prompts the user to select a file once instead of twice. + ([#8138](https://github.com/Microsoft/vscode-python/issues/8138)) +1. Creating a new blank notebook should not require a search for jupyter. + ([#8481](https://github.com/Microsoft/vscode-python/issues/8481)) +1. Arrowing up and down through cells can lose code that was just typed. + ([#8491](https://github.com/Microsoft/vscode-python/issues/8491)) +1. After pasting code, arrow keys don't navigate in a cell. + ([#8495](https://github.com/Microsoft/vscode-python/issues/8495)) +1. Typing 'z' in a cell causes the cell to disappear. + ([#8594](https://github.com/Microsoft/vscode-python/issues/8594)) ### Code Health diff --git a/news/2 Fixes/7999.md b/news/2 Fixes/7999.md deleted file mode 100644 index 216f1c461491..000000000000 --- a/news/2 Fixes/7999.md +++ /dev/null @@ -1 +0,0 @@ -CTRL+Z is deleting cells. It should only undo changes inside of the code for a cell. 'Z' and 'SHIFT+Z' are for undoing/redoing cell adds/moves. diff --git a/news/2 Fixes/8138.md b/news/2 Fixes/8138.md deleted file mode 100644 index 6826001761b5..000000000000 --- a/news/2 Fixes/8138.md +++ /dev/null @@ -1 +0,0 @@ -Ensure clicking `ctrl+s` in a new `notebook` prompts the user to select a file once instead of twice. diff --git a/news/2 Fixes/8481.md b/news/2 Fixes/8481.md deleted file mode 100644 index 6361eb88d530..000000000000 --- a/news/2 Fixes/8481.md +++ /dev/null @@ -1 +0,0 @@ -Creating a new blank notebook should not require a search for jupyter. diff --git a/news/2 Fixes/8491.md b/news/2 Fixes/8491.md deleted file mode 100644 index 87fc913bf6ad..000000000000 --- a/news/2 Fixes/8491.md +++ /dev/null @@ -1 +0,0 @@ -Arrowing up and down through cells can lose code that was just typed. diff --git a/news/2 Fixes/8495.md b/news/2 Fixes/8495.md deleted file mode 100644 index d4d86a50faf2..000000000000 --- a/news/2 Fixes/8495.md +++ /dev/null @@ -1 +0,0 @@ -After pasting code, arrow keys don't navigate in a cell. diff --git a/news/2 Fixes/8594.md b/news/2 Fixes/8594.md deleted file mode 100644 index c181570a62a7..000000000000 --- a/news/2 Fixes/8594.md +++ /dev/null @@ -1 +0,0 @@ -Typing 'z' in a cell causes the cell to disappear. From b6a8a28d84a591a83d5ca4f49abf6e87a79410d3 Mon Sep 17 00:00:00 2001 From: Karthik Nadig Date: Fri, 15 Nov 2019 17:02:24 -0800 Subject: [PATCH 14/21] Update release date in change log (#8604) --- CHANGELOG.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 7d047c3e76a1..a8103dd6cced 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,6 +1,6 @@ # Changelog -## 2019.11.0 (13 November 2019) +## 2019.11.0 (18 November 2019) ### Enhancements From e157ced96fd1ad4fabf50dca72703264e2b3ebd7 Mon Sep 17 00:00:00 2001 From: Rich Chiodo Date: Wed, 20 Nov 2019 15:25:14 -0800 Subject: [PATCH 15/21] Fix converting from a python script (#8699) * Fix converting from a python script * Fix linter error --- news/2 Fixes/8677.md | 1 + .../interactive-ipynb/nativeEditor.ts | 2 +- .../datascience/jupyter/jupyterImporter.ts | 56 ++++++++++--------- .../datascience/liveshare.functional.test.tsx | 4 +- src/test/datascience/mockJupyterManager.ts | 2 +- .../nativeEditor.functional.test.tsx | 23 ++++++-- 6 files changed, 54 insertions(+), 34 deletions(-) create mode 100644 news/2 Fixes/8677.md diff --git a/news/2 Fixes/8677.md b/news/2 Fixes/8677.md new file mode 100644 index 000000000000..62b81842a239 --- /dev/null +++ b/news/2 Fixes/8677.md @@ -0,0 +1 @@ +Converting to python script no longer working from a notebook. diff --git a/src/client/datascience/interactive-ipynb/nativeEditor.ts b/src/client/datascience/interactive-ipynb/nativeEditor.ts index 22a088487a64..c637071344a6 100644 --- a/src/client/datascience/interactive-ipynb/nativeEditor.ts +++ b/src/client/datascience/interactive-ipynb/nativeEditor.ts @@ -791,7 +791,7 @@ export class NativeEditor extends InteractiveBase implements INotebookEditor { tempFile = await this.fileSystem.createTemporaryFile('.ipynb'); // Translate the cells into a notebook - await this.fileSystem.writeFile(tempFile.filePath, this.generateNotebookContent(cells), { encoding: 'utf-8' }); + await this.fileSystem.writeFile(tempFile.filePath, await this.generateNotebookContent(cells), { encoding: 'utf-8' }); // Import this file and show it const contents = await this.importer.importFromFile(tempFile.filePath, this.file.fsPath); diff --git a/src/client/datascience/jupyter/jupyterImporter.ts b/src/client/datascience/jupyter/jupyterImporter.ts index c7db56d274bb..2c3b5921fa0b 100644 --- a/src/client/datascience/jupyter/jupyterImporter.ts +++ b/src/client/datascience/jupyter/jupyterImporter.ts @@ -1,14 +1,16 @@ // Copyright (c) Microsoft Corporation. All rights reserved. // Licensed under the MIT License. 'use strict'; +import '../../common/extensions'; + import { nbformat } from '@jupyterlab/coreutils'; import * as fs from 'fs-extra'; import { inject, injectable } from 'inversify'; import * as os from 'os'; import * as path from 'path'; -import '../../common/extensions'; import { IWorkspaceService } from '../../common/application/types'; +import { traceError } from '../../common/logger'; import { IFileSystem, IPlatformService } from '../../common/platform/types'; import { IConfigurationService, IDisposableRegistry } from '../../common/types'; import * as localize from '../../common/utils/localize'; @@ -138,35 +140,39 @@ export class JupyterImporter implements INotebookImporter { // When importing a file, calculate if we can create a %cd so that the relative paths work private async calculateDirectoryChange(notebookFile: string): Promise { let directoryChange: string | undefined; - // Make sure we don't already have an import/export comment in the file - const contents = await this.fileSystem.readFile(notebookFile); - const haveChangeAlready = contents.includes(CodeSnippits.ChangeDirectoryCommentIdentifier); - - if (!haveChangeAlready) { - const notebookFilePath = path.dirname(notebookFile); - // First see if we have a workspace open, this only works if we have a workspace root to be relative to - if (this.workspaceService.hasWorkspaceFolders) { - const workspacePath = this.workspaceService.workspaceFolders![0].uri.fsPath; - - // Make sure that we have everything that we need here - if (workspacePath && path.isAbsolute(workspacePath) && notebookFilePath && path.isAbsolute(notebookFilePath)) { - directoryChange = path.relative(workspacePath, notebookFilePath); + try { + // Make sure we don't already have an import/export comment in the file + const contents = await this.fileSystem.readFile(notebookFile); + const haveChangeAlready = contents.includes(CodeSnippits.ChangeDirectoryCommentIdentifier); + + if (!haveChangeAlready) { + const notebookFilePath = path.dirname(notebookFile); + // First see if we have a workspace open, this only works if we have a workspace root to be relative to + if (this.workspaceService.hasWorkspaceFolders) { + const workspacePath = this.workspaceService.workspaceFolders![0].uri.fsPath; + + // Make sure that we have everything that we need here + if (workspacePath && path.isAbsolute(workspacePath) && notebookFilePath && path.isAbsolute(notebookFilePath)) { + directoryChange = path.relative(workspacePath, notebookFilePath); + } } } - } - // If path.relative can't calculate a relative path, then it just returns the full second path - // so check here, we only want this if we were able to calculate a relative path, no network shares or drives - if (directoryChange && !path.isAbsolute(directoryChange)) { + // If path.relative can't calculate a relative path, then it just returns the full second path + // so check here, we only want this if we were able to calculate a relative path, no network shares or drives + if (directoryChange && !path.isAbsolute(directoryChange)) { - // Escape windows path chars so they end up in the source escaped - if (this.platform.isWindows) { - directoryChange = directoryChange.replace('\\', '\\\\'); - } + // Escape windows path chars so they end up in the source escaped + if (this.platform.isWindows) { + directoryChange = directoryChange.replace('\\', '\\\\'); + } - return directoryChange; - } else { - return undefined; + return directoryChange; + } else { + return undefined; + } + } catch (e) { + traceError(e); } } diff --git a/src/test/datascience/liveshare.functional.test.tsx b/src/test/datascience/liveshare.functional.test.tsx index 32380c0d9ed9..b46f628cd0f8 100644 --- a/src/test/datascience/liveshare.functional.test.tsx +++ b/src/test/datascience/liveshare.functional.test.tsx @@ -25,7 +25,7 @@ import { IJupyterExecution } from '../../client/datascience/types'; import { InteractivePanel } from '../../datascience-ui/history-react/interactivePanel'; -import { asyncDump } from '../common/asyncDump'; +//import { asyncDump } from '../common/asyncDump'; import { DataScienceIocContainer } from './dataScienceIocContainer'; import { createDocument } from './editor-integration/helpers'; import { waitForUpdate } from './reactHelpers'; @@ -62,7 +62,7 @@ suite('DataScience LiveShare tests', () => { }); suiteTeardown(() => { - asyncDump(); + //asyncDump(); }); function createContainer(role: vsls.Role): DataScienceIocContainer { diff --git a/src/test/datascience/mockJupyterManager.ts b/src/test/datascience/mockJupyterManager.ts index b42b97c0bbaf..ab573aa6ddd5 100644 --- a/src/test/datascience/mockJupyterManager.ts +++ b/src/test/datascience/mockJupyterManager.ts @@ -401,7 +401,7 @@ export class MockJupyterManager implements IJupyterSessionManager { this.setupPythonServiceExec(service, 'jupyter', ['nbconvert', '--version'], () => Promise.resolve({ stdout: '1.1.1.1' })); this.setupPythonServiceExec(service, 'jupyter', ['nbconvert', /.*/, '--to', 'python', '--stdout', '--template', /.*/], () => { return Promise.resolve({ - stdout: '#%%\r\nimport os\r\nos.chdir()' + stdout: '#%%\r\nimport os\r\nos.chdir()\r\n#%%\r\na=1' }); }); } diff --git a/src/test/datascience/nativeEditor.functional.test.tsx b/src/test/datascience/nativeEditor.functional.test.tsx index e3b763d209d0..f12d6798a888 100644 --- a/src/test/datascience/nativeEditor.functional.test.tsx +++ b/src/test/datascience/nativeEditor.functional.test.tsx @@ -226,17 +226,17 @@ for _ in range(50): assert.equal(afterDelete.length, 1, `Delete should NOT remove the last cell`); }, () => { return ioc; }); - runMountedTest('Export', async (wrapper) => { + runMountedTest('Convert to python', async (wrapper) => { // Export should cause the export dialog to come up. Remap appshell so we can check const dummyDisposable = { dispose: () => { return; } }; - let exportCalled = false; + let saveCalled = false; const appShell = TypeMoq.Mock.ofType(); appShell.setup(a => a.showErrorMessage(TypeMoq.It.isAnyString())).returns((e) => { throw e; }); appShell.setup(a => a.showInformationMessage(TypeMoq.It.isAny(), TypeMoq.It.isAny())).returns(() => Promise.resolve('')); appShell.setup(a => a.showSaveDialog(TypeMoq.It.isAny())).returns(() => { - exportCalled = true; + saveCalled = true; return Promise.resolve(undefined); }); appShell.setup(a => a.setStatusBarMessage(TypeMoq.It.isAny())).returns(() => dummyDisposable); @@ -247,9 +247,22 @@ for _ in range(50): await addCell(wrapper, ioc, 'a=1\na'); // Export should cause exportCalled to change to true - const exportButton = findButton(wrapper, NativeEditor, 8); + const saveButton = findButton(wrapper, NativeEditor, 8); + await waitForMessageResponse(ioc, () => saveButton!.simulate('click')); + assert.equal(saveCalled, true, 'Save should have been called'); + + // Click export and wait for a document to change + const documentChange = createDeferred(); + const docManager = ioc.get(IDocumentManager) as MockDocumentManager; + docManager.onDidChangeTextDocument(() => documentChange.resolve()); + const exportButton = findButton(wrapper, NativeEditor, 9); await waitForMessageResponse(ioc, () => exportButton!.simulate('click')); - assert.equal(exportCalled, true, 'Export should have been called'); + await waitForPromise(documentChange.promise, 3000); + // Verify the new document is valid python + const newDoc = docManager.activeTextEditor; + assert.ok(newDoc, 'New doc not created'); + assert.ok(newDoc!.document.getText().includes('a=1'), 'Export did not create a python file'); + }, () => { return ioc; }); runMountedTest('RunAllCells', async (wrapper) => { From fa999f7e19be54da54bee705a7ee08f72dfeb6f3 Mon Sep 17 00:00:00 2001 From: Rich Chiodo Date: Thu, 21 Nov 2019 08:24:17 -0800 Subject: [PATCH 16/21] Fix problem with $$ not being put around the correct items in a LaTeX equation (#8707) * First try * Add news entry and fix multiples * Add a bunch of comments --- news/2 Fixes/8673.md | 1 + .../interactive-common/latexManipulation.ts | 97 +++++++++++++------ .../latexManipulation.unit.test.ts | 60 +++++++++++- 3 files changed, 128 insertions(+), 30 deletions(-) create mode 100644 news/2 Fixes/8673.md diff --git a/news/2 Fixes/8673.md b/news/2 Fixes/8673.md new file mode 100644 index 000000000000..dd3996bb73d5 --- /dev/null +++ b/news/2 Fixes/8673.md @@ -0,0 +1 @@ +Some LaTeX equations do not print in notebooks or the interactive window. diff --git a/src/datascience-ui/interactive-common/latexManipulation.ts b/src/datascience-ui/interactive-common/latexManipulation.ts index e848ffaf1571..cde5ef974c5a 100644 --- a/src/datascience-ui/interactive-common/latexManipulation.ts +++ b/src/datascience-ui/interactive-common/latexManipulation.ts @@ -1,38 +1,81 @@ // Copyright (c) Microsoft Corporation. All rights reserved. // Licensed under the MIT License. + +// tslint:disable-next-line:no-require-imports no-var-requires +const _escapeRegExp = require('lodash/escapeRegExp') as typeof import('lodash/escapeRegExp'); + // Adds '$$' to latex formulas that don't have a '$', allowing users to input the formula directly. +// +// The general algorithm here is: +// Search for either $$ or $ or a \begin{name} item. +// If a $$ or $ is found, output up to the next dollar sign +// If a \begin{name} is found, find the matching \end{name}, wrap the section in $$ and output up to the \end. +// +// LaTeX seems to follow the pattern of \begin{name} or is escaped with $$ or $. See here for a bunch of examples: +// https://jupyter-notebook.readthedocs.io/en/stable/examples/Notebook/Typesetting%20Equations.html export function fixLatexEquations(input: string): string { - const block = '\n$$\n'; + const output: string[] = []; - const beginIndexes = getAllIndexesOfRegex(input, /\\begin\{[a-z]*\*?\}/g); - const endIndexes = getAllIndexesOfRegex(input, /\\end\{[a-z]*\*?\}/g); + // Search for begin/end pairs, outputting as we go + let start = 0; - if (beginIndexes.length === endIndexes.length) { - for (let i = 0; i < beginIndexes.length; i += 1) { - const endOfEnd = input.indexOf('}', endIndexes[i] + 1 + 8 * i); + // Loop until we run out string + while (start < input.length) { + // Check $$, $ and begin + const dollars = /\$\$/.exec(input.substr(start)); + const dollar = /\$/.exec(input.substr(start)); + const begin = /\\begin\{([a-z,\*]+)\}/.exec(input.substr(start)); + let endRegex = /\$\$/; + let endRegexLength = 2; - // Edge case, if the input starts with the latex formula we add the block at the beggining. - if (beginIndexes[i] === 0 && input[beginIndexes[i]] === '\\') { - input = block + input.slice(0, endOfEnd + 1) + block + input.slice(endOfEnd + 1, input.length); - // Normal case, if the latex formula starts with a '$' we don't do anything. - // Otherwise, we insert the block at the beginning and ending of the latex formula. - } else if (input[beginIndexes[i] - 1] !== '$') { - input = input.slice(0, beginIndexes[i] + block.length * 2 * i) + block + input.slice(beginIndexes[i] + block.length * 2 * i, endOfEnd + 1) + block + input.slice(endOfEnd + 1, input.length); - } + // Pick the first that matches + let match = dollars; + let isBeginMatch = false; + if (!match || (dollar && dollar.index < match.index)) { + match = dollar; + endRegex = /\$/; + endRegexLength = 1; + } + if (!match || (begin && begin.index < match.index)) { + match = begin; + endRegex = begin ? new RegExp(`\\\\end\\{${_escapeRegExp(begin[1])}\\}`) : /\$/; + endRegexLength = begin ? `\\end{${begin[1]}}`.length : 1; + isBeginMatch = true; } - } - - return input; -} - -function getAllIndexesOfRegex(arr: string, value: RegExp): number[] { - const indexes = []; - let result; - // tslint:disable-next-line: no-conditional-assignment - while ((result = value.exec(arr)) !== null) { - indexes.push(result.index); + // Output this match + if (match) { + if (isBeginMatch) { + // Begin match is a little more complicated. + const offset = match.index + start; + const end = endRegex.exec(input.substr(start)); + if (end) { + const prefix = input.substr(start, match.index); + const wrapped = input.substr(offset, endRegexLength + end.index - match.index); + output.push(`${prefix}\n$$\n${wrapped}\n$$\n`); + start = start + prefix.length + wrapped.length; + } else { + // Invalid, just return + return input; + } + } else { + // Output till the next $ or $$ + const offset = match.index + 1 + start; + const endDollar = endRegex.exec(input.substr(offset)); + if (endDollar) { + const length = endDollar.index + 1 + offset; + output.push(input.substr(start, length)); + start = start + length; + } else { + // Invalid, just return + return input; + } + } + } else { + // No more matches + output.push(input.substr(start)); + start = input.length; + } } - - return indexes; + return output.join(''); } diff --git a/src/test/datascience/latexManipulation.unit.test.ts b/src/test/datascience/latexManipulation.unit.test.ts index f20e044b8ec3..c582e968caaf 100644 --- a/src/test/datascience/latexManipulation.unit.test.ts +++ b/src/test/datascience/latexManipulation.unit.test.ts @@ -85,18 +85,72 @@ $$ $$ `; - test('Latex - Equations don\'t have $$', () => { + const markdown4 = ` +$$ +\\begin{equation*} +\\mathbf{V}_1 \\times \\mathbf{V}_2 = \\begin{vmatrix} +\\mathbf{i} & \\mathbf{j} & \\mathbf{k} \\ +\\frac{\partial X}{\\partial u} & \\frac{\\partial Y}{\\partial u} & 0 \\\\ +\\frac{\partial X}{\\partial v} & \\frac{\\partial Y}{\\partial v} & 0 +\\end{vmatrix} +\\end{equation*} +$$ +`; + + const markdown5 = ` +\\begin{equation*} +P(E) = {n \\choose k} p^k (1-p)^{ n-k} +\\end{equation*} + +This expression $\\sqrt{3x-1}+(1+x)^2$ is an example of a TeX inline equation in a [Markdown-formatted](https://daringfireball.net/projects/markdown/) sentence. +`; + const output5 = ` + +$$ +\\begin{equation*} +P(E) = {n \\choose k} p^k (1-p)^{ n-k} +\\end{equation*} +$$ + + +This expression $\\sqrt{3x-1}+(1+x)^2$ is an example of a TeX inline equation in a [Markdown-formatted](https://daringfireball.net/projects/markdown/) sentence. +`; + + test('Latex - Equations don\'t have \$\$', () => { const result = fixLatexEquations(markdown1); expect(result).to.be.equal(output1, 'Result is incorrect'); }); - test('Latex - Equations have $', () => { + test('Latex - Equations have \$', () => { const result = fixLatexEquations(markdown2); expect(result).to.be.equal(markdown2, 'Result is incorrect'); }); - test('Latex - Multiple equations don\'t have $$', () => { + test('Latex - Multiple equations don\'t have \$\$', () => { const result = fixLatexEquations(markdown3); expect(result).to.be.equal(output3, 'Result is incorrect'); }); + + test('Latex - All on the same line', () => { + const line = '\\begin{matrix}1 & 0\\0 & 1\\end{matrix}'; + const after = '\n$$\n\\begin{matrix}1 & 0\\0 & 1\\end{matrix}\n$$\n'; + const result = fixLatexEquations(line); + expect(result).to.be.equal(after, 'Result is incorrect'); + }); + + test('Latex - Invalid', () => { + const invalid = '\n\\begin{eq*}do stuff\\end{eq}'; + const result = fixLatexEquations(invalid); + expect(result).to.be.equal(invalid, 'Result should not have changed'); + }); + + test('Latex - \$\$ already present', () => { + const result = fixLatexEquations(markdown4); + expect(result).to.be.equal(markdown4, 'Result should not have changed'); + }); + + test('Latex - Multiple types', () => { + const result = fixLatexEquations(markdown5); + expect(result).to.be.equal(output5, 'Result is incorrect'); + }); }); From 8c5c83def6f76efa4b9ffc314fa93f8adb5ac0b5 Mon Sep 17 00:00:00 2001 From: Rich Chiodo Date: Thu, 21 Nov 2019 09:35:15 -0800 Subject: [PATCH 17/21] Modify changelog and version number --- CHANGELOG.md | 60 ++++++++++++++++++++++++++++++++++++++++++++ news/2 Fixes/8673.md | 1 - news/2 Fixes/8677.md | 1 - package.json | 2 +- 4 files changed, 61 insertions(+), 3 deletions(-) delete mode 100644 news/2 Fixes/8673.md delete mode 100644 news/2 Fixes/8677.md diff --git a/CHANGELOG.md b/CHANGELOG.md index a8103dd6cced..ac3a00cb9b81 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,65 @@ # Changelog +## 2019.11.1 (22 November 2019) + +### Fixes + +1. Some LaTeX equations do not print in notebooks or the interactive window. + ([#8673](https://github.com/Microsoft/vscode-python/issues/8673)) +1. Converting to python script no longer working from a notebook. + ([#8677](https://github.com/Microsoft/vscode-python/issues/8677)) + +### Thanks + +Thanks to the following projects which we fully rely on to provide some of +our features: +- [isort](https://pypi.org/project/isort/) +- [jedi](https://pypi.org/project/jedi/) + and [parso](https://pypi.org/project/parso/) +- [Microsoft Python Language Server](https://github.com/microsoft/python-language-server) +- [ptvsd](https://pypi.org/project/ptvsd/) +- [exuberant ctags](http://ctags.sourceforge.net/) (user-installed) +- [rope](https://pypi.org/project/rope/) (user-installed) + +Also thanks to the various projects we provide integrations with which help +make this extension useful: +- Debugging support: + [Django](https://pypi.org/project/Django/), + [Flask](https://pypi.org/project/Flask/), + [gevent](https://pypi.org/project/gevent/), + [Jinja](https://pypi.org/project/Jinja/), + [Pyramid](https://pypi.org/project/pyramid/), + [PySpark](https://pypi.org/project/pyspark/), + [Scrapy](https://pypi.org/project/Scrapy/), + [Watson](https://pypi.org/project/Watson/) +- Formatting: + [autopep8](https://pypi.org/project/autopep8/), + [black](https://pypi.org/project/black/), + [yapf](https://pypi.org/project/yapf/) +- Interpreter support: + [conda](https://conda.io/), + [direnv](https://direnv.net/), + [pipenv](https://pypi.org/project/pipenv/), + [pyenv](https://github.com/pyenv/pyenv), + [venv](https://docs.python.org/3/library/venv.html#module-venv), + [virtualenv](https://pypi.org/project/virtualenv/) +- Linting: + [bandit](https://pypi.org/project/bandit/), + [flake8](https://pypi.org/project/flake8/), + [mypy](https://pypi.org/project/mypy/), + [prospector](https://pypi.org/project/prospector/), + [pylint](https://pypi.org/project/pylint/), + [pydocstyle](https://pypi.org/project/pydocstyle/), + [pylama](https://pypi.org/project/pylama/) +- Testing: + [nose](https://pypi.org/project/nose/), + [pytest](https://pypi.org/project/pytest/), + [unittest](https://docs.python.org/3/library/unittest.html#module-unittest) + +And finally thanks to the [Python](https://www.python.org/) development team and +community for creating a fantastic programming language and community to be a +part of! + ## 2019.11.0 (18 November 2019) ### Enhancements diff --git a/news/2 Fixes/8673.md b/news/2 Fixes/8673.md deleted file mode 100644 index dd3996bb73d5..000000000000 --- a/news/2 Fixes/8673.md +++ /dev/null @@ -1 +0,0 @@ -Some LaTeX equations do not print in notebooks or the interactive window. diff --git a/news/2 Fixes/8677.md b/news/2 Fixes/8677.md deleted file mode 100644 index 62b81842a239..000000000000 --- a/news/2 Fixes/8677.md +++ /dev/null @@ -1 +0,0 @@ -Converting to python script no longer working from a notebook. diff --git a/package.json b/package.json index 5131c177cb7a..ac1ec08c5dcc 100644 --- a/package.json +++ b/package.json @@ -2,7 +2,7 @@ "name": "python", "displayName": "Python", "description": "Linting, Debugging (multi-threaded, remote), Intellisense, code formatting, refactoring, unit tests, snippets, and more.", - "version": "2019.11.0", + "version": "2019.11.1", "languageServerVersion": "0.4.114", "publisher": "ms-python", "author": { From 43f35b369818d5cc7b05d670160524ca2b3c2845 Mon Sep 17 00:00:00 2001 From: Don Jayamanne Date: Thu, 21 Nov 2019 10:31:33 -0800 Subject: [PATCH 18/21] Fixes to starting Jupyter in a Docker container. (#8715) * Ensure we generate the right args when running in docker * Fixes to starting `Jupyter` in a `Docker` container. --- news/2 Fixes/8661.md | 1 + .../datascience/jupyter/notebookStarter.ts | 2 +- src/test/datascience/execution.unit.test.ts | 29 +++++++++++++++---- 3 files changed, 26 insertions(+), 6 deletions(-) create mode 100644 news/2 Fixes/8661.md diff --git a/news/2 Fixes/8661.md b/news/2 Fixes/8661.md new file mode 100644 index 000000000000..9849402908f7 --- /dev/null +++ b/news/2 Fixes/8661.md @@ -0,0 +1 @@ +Fixes to starting `Jupyter` in a `Docker` container. diff --git a/src/client/datascience/jupyter/notebookStarter.ts b/src/client/datascience/jupyter/notebookStarter.ts index 4d64251f1a20..cb864e6c635f 100644 --- a/src/client/datascience/jupyter/notebookStarter.ts +++ b/src/client/datascience/jupyter/notebookStarter.ts @@ -194,7 +194,7 @@ export class NotebookStarter implements Disposable { if (stdout && stdout.toString().includes('(root)')) { args.push('--allow-root'); } - resolve([]); + resolve(args); }); }); } catch { diff --git a/src/test/datascience/execution.unit.test.ts b/src/test/datascience/execution.unit.test.ts index c1fd0a598a59..1ac3fd491aa0 100644 --- a/src/test/datascience/execution.unit.test.ts +++ b/src/test/datascience/execution.unit.test.ts @@ -280,6 +280,7 @@ suite('Jupyter Execution', async () => { }); teardown(() => { + reset(fileSystem); return cleanupDisposables(); }); @@ -439,7 +440,7 @@ suite('Jupyter Execution', async () => { .returns(() => result); } - function setupWorkingPythonService(service: TypeMoq.IMock, notebookStdErr?: string[]) { + function setupWorkingPythonService(service: TypeMoq.IMock, notebookStdErr?: string[], runInDocker?: boolean) { setupPythonService(service, 'ipykernel', ['--version'], Promise.resolve({ stdout: '1.1.1.1' })); setupPythonService(service, 'jupyter', ['nbconvert', '--version'], Promise.resolve({ stdout: '1.1.1.1' })); setupPythonService(service, 'jupyter', ['notebook', '--version'], Promise.resolve({ stdout: '1.1.1.1' })); @@ -462,8 +463,8 @@ suite('Jupyter Execution', async () => { const getServerInfoPath = path.join(EXTENSION_ROOT_DIR, 'pythonFiles', 'datascience', 'getServerInfo.py'); setupPythonService(service, undefined, [getServerInfoPath], Promise.resolve({ stdout: 'failure to get server infos' })); setupPythonServiceExecObservable(service, 'jupyter', ['kernelspec', 'list'], [], []); - setupPythonServiceExecObservable(service, 'jupyter', ['notebook', '--no-browser', /--notebook-dir=.*/, /--config=.*/, '--NotebookApp.iopub_data_rate_limit=10000000000.0'], [], notebookStdErr ? notebookStdErr : ['http://localhost:8888/?token=198']); - + const dockerArgs = runInDocker ? ['--ip', '127.0.0.1'] : []; + setupPythonServiceExecObservable(service, 'jupyter', ['notebook', '--no-browser', /--notebook-dir=.*/, /--config=.*/, '--NotebookApp.iopub_data_rate_limit=10000000000.0', ...dockerArgs], [], notebookStdErr ? notebookStdErr : ['http://localhost:8888/?token=198']); } function setupMissingKernelPythonService(service: TypeMoq.IMock, notebookStdErr?: string[]) { @@ -528,6 +529,9 @@ suite('Jupyter Execution', async () => { } function createExecution(activeInterpreter: PythonInterpreter, notebookStdErr?: string[], skipSearch?: boolean): JupyterExecutionFactory { + return createExecutionAndReturnProcessService(activeInterpreter, notebookStdErr, skipSearch).jupyterExecutionFactory; + } + function createExecutionAndReturnProcessService(activeInterpreter: PythonInterpreter, notebookStdErr?: string[], skipSearch?: boolean, runInDocker?: boolean): {workingPythonExecutionService: TypeMoq.IMock; jupyterExecutionFactory: JupyterExecutionFactory} { // Setup defaults when(interpreterService.onDidChangeInterpreter).thenReturn(dummyEvent.event); when(interpreterService.getActiveInterpreter()).thenResolve(activeInterpreter); @@ -536,10 +540,12 @@ suite('Jupyter Execution', async () => { when(interpreterService.getInterpreterDetails(match('/foo/baz/python.exe'))).thenResolve(missingKernelPython); when(interpreterService.getInterpreterDetails(match('/bar/baz/python.exe'))).thenResolve(missingNotebookPython); when(interpreterService.getInterpreterDetails(argThat(o => !o.includes || !o.includes('python')))).thenReject('Unknown interpreter'); - + if (runInDocker){ + when(fileSystem.readFile('/proc/self/cgroup')).thenResolve('hello docker world'); + } // Create our working python and process service. const workingService = createTypeMoq('working'); - setupWorkingPythonService(workingService, notebookStdErr); + setupWorkingPythonService(workingService, notebookStdErr, runInDocker); const missingKernelService = createTypeMoq('missingKernel'); setupMissingKernelPythonService(missingKernelService, notebookStdErr); const missingNotebookService = createTypeMoq('missingNotebook'); @@ -686,6 +692,19 @@ suite('Jupyter Execution', async () => { await assert.isFulfilled(execution.connectToNotebookServer(), 'Should be able to start a server'); }).timeout(10000); + test('Includes correct args for running in docker', async () => { + const { workingPythonExecutionService, jupyterExecutionFactory} = createExecutionAndReturnProcessService(workingPython, undefined, undefined, true); + when(executionFactory.createDaemon(deepEqual({ daemonModule: PythonDaemonModule, pythonPath: workingPython.path }))).thenResolve(workingPythonExecutionService.object); + + await assert.eventually.equal(jupyterExecutionFactory.isNotebookSupported(), true, 'Notebook not supported'); + await assert.eventually.equal(jupyterExecutionFactory.isImportSupported(), true, 'Import not supported'); + await assert.eventually.equal(jupyterExecutionFactory.isKernelSpecSupported(), true, 'Kernel Spec not supported'); + await assert.eventually.equal(jupyterExecutionFactory.isKernelCreateSupported(), true, 'Kernel Create not supported'); + const usableInterpreter = await jupyterExecutionFactory.getUsableJupyterPython(); + assert.isOk(usableInterpreter, 'Usable interpreter not found'); + await assert.isFulfilled(jupyterExecutionFactory.connectToNotebookServer(), 'Should be able to start a server'); + }).timeout(10000); + test('Failing notebook throws exception', async () => { const execution = createExecution(missingNotebookPython); when(interpreterService.getInterpreters()).thenResolve([missingNotebookPython]); From cb578c3f862adb78bcf455f54a6b3f12f9e627c5 Mon Sep 17 00:00:00 2001 From: Karthik Nadig Date: Thu, 21 Nov 2019 09:09:10 -0800 Subject: [PATCH 19/21] Ensure arguments are generated correctly for `getRemoteLauncherCommand` when in debugger experiment (#8712) * Return new debugger arguments when in experiment * Add news item --- news/2 Fixes/8685.md | 1 + src/client/debugger/extension/adapter/factory.ts | 7 +++++-- src/test/extension.unit.test.ts | 8 ++++---- 3 files changed, 10 insertions(+), 6 deletions(-) create mode 100644 news/2 Fixes/8685.md diff --git a/news/2 Fixes/8685.md b/news/2 Fixes/8685.md new file mode 100644 index 000000000000..08ecca0439e7 --- /dev/null +++ b/news/2 Fixes/8685.md @@ -0,0 +1 @@ +Ensure arguments are generated correctly for `getRemoteLauncherCommand` when in debugger experiment. diff --git a/src/client/debugger/extension/adapter/factory.ts b/src/client/debugger/extension/adapter/factory.ts index 06391f8aa7db..aa4c32a86909 100644 --- a/src/client/debugger/extension/adapter/factory.ts +++ b/src/client/debugger/extension/adapter/factory.ts @@ -7,7 +7,7 @@ import { inject, injectable } from 'inversify'; import * as path from 'path'; import { DebugAdapterDescriptor, DebugAdapterExecutable, DebugAdapterServer, DebugSession, WorkspaceFolder } from 'vscode'; import { IApplicationShell } from '../../../common/application/types'; -import { DebugAdapterNewPtvsd } from '../../../common/experimentGroups'; +import { DebugAdapterDescriptorFactory as DebugAdapterExperiment, DebugAdapterNewPtvsd } from '../../../common/experimentGroups'; import { traceVerbose } from '../../../common/logger'; import { IExperimentsManager } from '../../../common/types'; import { EXTENSION_ROOT_DIR } from '../../../constants'; @@ -26,7 +26,7 @@ export class DebugAdapterDescriptorFactory implements IDebugAdapterDescriptorFac @inject(IInterpreterService) private readonly interpreterService: IInterpreterService, @inject(IApplicationShell) private readonly appShell: IApplicationShell, @inject(IExperimentsManager) private readonly experimentsManager: IExperimentsManager - ) {} + ) { } public async createDebugAdapterDescriptor(session: DebugSession, executable: DebugAdapterExecutable | undefined): Promise { const configuration = session.configuration as (LaunchRequestArguments | AttachRequestArguments); @@ -86,6 +86,9 @@ export class DebugAdapterDescriptorFactory implements IDebugAdapterDescriptorFac public getRemotePtvsdArgs(remoteDebugOptions: RemoteDebugOptions): string[] { const waitArgs = remoteDebugOptions.waitUntilDebuggerAttaches ? ['--wait'] : []; + if (this.experimentsManager.inExperiment(DebugAdapterExperiment.experiment) && this.experimentsManager.inExperiment(DebugAdapterNewPtvsd.experiment)) { + return ['--host', remoteDebugOptions.host, '--port', remoteDebugOptions.port.toString(), ...waitArgs]; + } return ['--default', '--host', remoteDebugOptions.host, '--port', remoteDebugOptions.port.toString(), ...waitArgs]; } diff --git a/src/test/extension.unit.test.ts b/src/test/extension.unit.test.ts index d6d4f878b677..dd5a931894bd 100644 --- a/src/test/extension.unit.test.ts +++ b/src/test/extension.unit.test.ts @@ -59,14 +59,14 @@ suite('Extension API Debugger', () => { test('Test debug launcher args (no-wait and in experiment)', async () => { mockInExperiment(); - when(debugAdapterFactory.getRemotePtvsdArgs(anything())).thenReturn(['--default', '--host', 'something', '--port', '1234']); + when(debugAdapterFactory.getRemotePtvsdArgs(anything())).thenReturn(['--host', 'something', '--port', '1234']); const args = await buildApi(Promise.resolve(), instance(experimentsManager), instance(debugAdapterFactory), instance(configurationService)).debug.getRemoteLauncherCommand( 'something', 1234, false ); - const expectedArgs = [ptvsdPath, '--default', '--host', 'something', '--port', '1234']; + const expectedArgs = [ptvsdPath, '--host', 'something', '--port', '1234']; expect(args).to.be.deep.equal(expectedArgs); }); @@ -86,14 +86,14 @@ suite('Extension API Debugger', () => { test('Test debug launcher args (wait and in experiment)', async () => { mockInExperiment(); - when(debugAdapterFactory.getRemotePtvsdArgs(anything())).thenReturn(['--default', '--host', 'something', '--port', '1234', '--wait']); + when(debugAdapterFactory.getRemotePtvsdArgs(anything())).thenReturn(['--host', 'something', '--port', '1234', '--wait']); const args = await buildApi(Promise.resolve(), instance(experimentsManager), instance(debugAdapterFactory), instance(configurationService)).debug.getRemoteLauncherCommand( 'something', 1234, true ); - const expectedArgs = [ptvsdPath, '--default', '--host', 'something', '--port', '1234', '--wait']; + const expectedArgs = [ptvsdPath, '--host', 'something', '--port', '1234', '--wait']; expect(args).to.be.deep.equal(expectedArgs); }); From 6657b525c6a9dfbcd55438ca79bd6cd72c309969 Mon Sep 17 00:00:00 2001 From: Don Jayamanne Date: Thu, 21 Nov 2019 11:34:40 -0800 Subject: [PATCH 20/21] Updates to change log --- CHANGELOG.md | 4 ++++ news/2 Fixes/8661.md | 1 - news/2 Fixes/8685.md | 1 - 3 files changed, 4 insertions(+), 2 deletions(-) delete mode 100644 news/2 Fixes/8661.md delete mode 100644 news/2 Fixes/8685.md diff --git a/CHANGELOG.md b/CHANGELOG.md index ac3a00cb9b81..87f0fe007d1b 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -8,6 +8,10 @@ ([#8673](https://github.com/Microsoft/vscode-python/issues/8673)) 1. Converting to python script no longer working from a notebook. ([#8677](https://github.com/Microsoft/vscode-python/issues/8677)) +1. Fixes to starting `Jupyter` in a `Docker` container. + ([#8661](https://github.com/Microsoft/vscode-python/issues/8661)) +1. Ensure arguments are generated correctly for `getRemoteLauncherCommand` when in debugger experiment. + ([#8685](https://github.com/Microsoft/vscode-python/issues/8685)) ### Thanks diff --git a/news/2 Fixes/8661.md b/news/2 Fixes/8661.md deleted file mode 100644 index 9849402908f7..000000000000 --- a/news/2 Fixes/8661.md +++ /dev/null @@ -1 +0,0 @@ -Fixes to starting `Jupyter` in a `Docker` container. diff --git a/news/2 Fixes/8685.md b/news/2 Fixes/8685.md deleted file mode 100644 index 08ecca0439e7..000000000000 --- a/news/2 Fixes/8685.md +++ /dev/null @@ -1 +0,0 @@ -Ensure arguments are generated correctly for `getRemoteLauncherCommand` when in debugger experiment. From 0421d73548e08430863b6cb4e4d3f9ead0edfd47 Mon Sep 17 00:00:00 2001 From: Don Jayamanne Date: Thu, 21 Nov 2019 12:47:04 -0800 Subject: [PATCH 21/21] Revert tests due to incompatibilities --- src/test/datascience/execution.unit.test.ts | 28 +++------------------ 1 file changed, 4 insertions(+), 24 deletions(-) diff --git a/src/test/datascience/execution.unit.test.ts b/src/test/datascience/execution.unit.test.ts index 1ac3fd491aa0..ecda0008bb31 100644 --- a/src/test/datascience/execution.unit.test.ts +++ b/src/test/datascience/execution.unit.test.ts @@ -280,7 +280,6 @@ suite('Jupyter Execution', async () => { }); teardown(() => { - reset(fileSystem); return cleanupDisposables(); }); @@ -440,7 +439,7 @@ suite('Jupyter Execution', async () => { .returns(() => result); } - function setupWorkingPythonService(service: TypeMoq.IMock, notebookStdErr?: string[], runInDocker?: boolean) { + function setupWorkingPythonService(service: TypeMoq.IMock, notebookStdErr?: string[]) { setupPythonService(service, 'ipykernel', ['--version'], Promise.resolve({ stdout: '1.1.1.1' })); setupPythonService(service, 'jupyter', ['nbconvert', '--version'], Promise.resolve({ stdout: '1.1.1.1' })); setupPythonService(service, 'jupyter', ['notebook', '--version'], Promise.resolve({ stdout: '1.1.1.1' })); @@ -463,8 +462,7 @@ suite('Jupyter Execution', async () => { const getServerInfoPath = path.join(EXTENSION_ROOT_DIR, 'pythonFiles', 'datascience', 'getServerInfo.py'); setupPythonService(service, undefined, [getServerInfoPath], Promise.resolve({ stdout: 'failure to get server infos' })); setupPythonServiceExecObservable(service, 'jupyter', ['kernelspec', 'list'], [], []); - const dockerArgs = runInDocker ? ['--ip', '127.0.0.1'] : []; - setupPythonServiceExecObservable(service, 'jupyter', ['notebook', '--no-browser', /--notebook-dir=.*/, /--config=.*/, '--NotebookApp.iopub_data_rate_limit=10000000000.0', ...dockerArgs], [], notebookStdErr ? notebookStdErr : ['http://localhost:8888/?token=198']); + setupPythonServiceExecObservable(service, 'jupyter', ['notebook', '--no-browser', /--notebook-dir=.*/, /--config=.*/, '--NotebookApp.iopub_data_rate_limit=10000000000.0'], [], notebookStdErr ? notebookStdErr : ['http://localhost:8888/?token=198']); } function setupMissingKernelPythonService(service: TypeMoq.IMock, notebookStdErr?: string[]) { @@ -529,9 +527,6 @@ suite('Jupyter Execution', async () => { } function createExecution(activeInterpreter: PythonInterpreter, notebookStdErr?: string[], skipSearch?: boolean): JupyterExecutionFactory { - return createExecutionAndReturnProcessService(activeInterpreter, notebookStdErr, skipSearch).jupyterExecutionFactory; - } - function createExecutionAndReturnProcessService(activeInterpreter: PythonInterpreter, notebookStdErr?: string[], skipSearch?: boolean, runInDocker?: boolean): {workingPythonExecutionService: TypeMoq.IMock; jupyterExecutionFactory: JupyterExecutionFactory} { // Setup defaults when(interpreterService.onDidChangeInterpreter).thenReturn(dummyEvent.event); when(interpreterService.getActiveInterpreter()).thenResolve(activeInterpreter); @@ -540,12 +535,10 @@ suite('Jupyter Execution', async () => { when(interpreterService.getInterpreterDetails(match('/foo/baz/python.exe'))).thenResolve(missingKernelPython); when(interpreterService.getInterpreterDetails(match('/bar/baz/python.exe'))).thenResolve(missingNotebookPython); when(interpreterService.getInterpreterDetails(argThat(o => !o.includes || !o.includes('python')))).thenReject('Unknown interpreter'); - if (runInDocker){ - when(fileSystem.readFile('/proc/self/cgroup')).thenResolve('hello docker world'); - } + // Create our working python and process service. const workingService = createTypeMoq('working'); - setupWorkingPythonService(workingService, notebookStdErr, runInDocker); + setupWorkingPythonService(workingService, notebookStdErr); const missingKernelService = createTypeMoq('missingKernel'); setupMissingKernelPythonService(missingKernelService, notebookStdErr); const missingNotebookService = createTypeMoq('missingNotebook'); @@ -692,19 +685,6 @@ suite('Jupyter Execution', async () => { await assert.isFulfilled(execution.connectToNotebookServer(), 'Should be able to start a server'); }).timeout(10000); - test('Includes correct args for running in docker', async () => { - const { workingPythonExecutionService, jupyterExecutionFactory} = createExecutionAndReturnProcessService(workingPython, undefined, undefined, true); - when(executionFactory.createDaemon(deepEqual({ daemonModule: PythonDaemonModule, pythonPath: workingPython.path }))).thenResolve(workingPythonExecutionService.object); - - await assert.eventually.equal(jupyterExecutionFactory.isNotebookSupported(), true, 'Notebook not supported'); - await assert.eventually.equal(jupyterExecutionFactory.isImportSupported(), true, 'Import not supported'); - await assert.eventually.equal(jupyterExecutionFactory.isKernelSpecSupported(), true, 'Kernel Spec not supported'); - await assert.eventually.equal(jupyterExecutionFactory.isKernelCreateSupported(), true, 'Kernel Create not supported'); - const usableInterpreter = await jupyterExecutionFactory.getUsableJupyterPython(); - assert.isOk(usableInterpreter, 'Usable interpreter not found'); - await assert.isFulfilled(jupyterExecutionFactory.connectToNotebookServer(), 'Should be able to start a server'); - }).timeout(10000); - test('Failing notebook throws exception', async () => { const execution = createExecution(missingNotebookPython); when(interpreterService.getInterpreters()).thenResolve([missingNotebookPython]);