Skip to content

Commit 789c6ac

Browse files
committed
Misc
1 parent cc4431d commit 789c6ac

File tree

12 files changed

+238
-33
lines changed

12 files changed

+238
-33
lines changed

src/client/common/application/notebook.ts

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -48,6 +48,11 @@ export class VSCodeNotebook implements IVSCodeNotebook {
4848
: new EventEmitter<NotebookDocument>().event;
4949
}
5050
public get onDidCloseNotebookDocument(): Event<NotebookDocument> {
51+
return this.canUseNotebookApi
52+
? this.notebook.onDidSaveNotebookDocument
53+
: new EventEmitter<NotebookDocument>().event;
54+
}
55+
public get onDidSaveNotebookDocument(): Event<NotebookDocument> {
5156
return this.canUseNotebookApi
5257
? this.notebook.onDidCloseNotebookDocument
5358
: new EventEmitter<NotebookDocument>().event;

src/client/common/application/types.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1540,6 +1540,7 @@ export interface IVSCodeNotebook {
15401540
readonly notebookDocuments: ReadonlyArray<NotebookDocument>;
15411541
readonly onDidOpenNotebookDocument: Event<NotebookDocument>;
15421542
readonly onDidCloseNotebookDocument: Event<NotebookDocument>;
1543+
readonly onDidSaveNotebookDocument: Event<NotebookDocument>;
15431544
readonly onDidChangeActiveNotebookEditor: Event<NotebookEditor | undefined>;
15441545
readonly onDidChangeNotebookDocument: Event<NotebookCellChangedEvent>;
15451546
readonly notebookEditors: Readonly<NotebookEditor[]>;

src/client/datascience/common.ts

Lines changed: 9 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -131,11 +131,16 @@ export function traceCellResults(prefix: string, results: ICell[]) {
131131
}
132132

133133
export function translateKernelLanguageToMonaco(kernelLanguage: string): string {
134-
// The only known translation is C# to csharp at the moment
135-
if (kernelLanguage === 'C#' || kernelLanguage === 'c#') {
136-
return 'csharp';
134+
// At the moment these are the only translations.
135+
// python, julia, r, javascript, powershell, etc can be left as is.
136+
switch (kernelLanguage.toLowerCase()) {
137+
case 'c#':
138+
return 'csharp';
139+
case 'f#':
140+
return 'csharp';
141+
default:
142+
return kernelLanguage.toLowerCase();
137143
}
138-
return kernelLanguage.toLowerCase();
139144
}
140145

141146
export function generateNewNotebookUri(

src/client/datascience/notebook/contentProvider.ts

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@ import { captureTelemetry, sendTelemetryEvent, setSharedProperty } from '../../t
2121
import { Telemetry } from '../constants';
2222
import { INotebookStorageProvider } from '../notebookStorage/notebookStorageProvider';
2323
import { VSCodeNotebookModel } from '../notebookStorage/vscNotebookModel';
24+
import { NotebookCellLanguageService } from './defaultCellLanguageService';
2425
import { notebookModelToVSCNotebookData } from './helpers/helpers';
2526
import { NotebookEditorCompatibilitySupport } from './notebookEditorCompatibilitySupport';
2627
// tslint:disable-next-line: no-var-requires no-require-imports
@@ -41,6 +42,7 @@ export class NotebookContentProvider implements VSCNotebookContentProvider {
4142
}
4243
constructor(
4344
@inject(INotebookStorageProvider) private readonly notebookStorage: INotebookStorageProvider,
45+
@inject(NotebookCellLanguageService) private readonly cellLanguageService: NotebookCellLanguageService,
4446
@inject(NotebookEditorCompatibilitySupport)
4547
private readonly compatibilitySupport: NotebookEditorCompatibilitySupport
4648
) {}
@@ -77,7 +79,8 @@ export class NotebookContentProvider implements VSCNotebookContentProvider {
7779
}
7880
setSharedProperty('ds_notebookeditor', 'native');
7981
sendTelemetryEvent(Telemetry.CellCount, undefined, { count: model.cells.length });
80-
return notebookModelToVSCNotebookData(model);
82+
const preferredLanguage = this.cellLanguageService.getPreferredLanguage(model.metadata);
83+
return notebookModelToVSCNotebookData(model, preferredLanguage);
8184
}
8285
@captureTelemetry(Telemetry.Save, undefined, true)
8386
public async saveNotebook(document: NotebookDocument, cancellation: CancellationToken) {
Lines changed: 59 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,59 @@
1+
// Copyright (c) Microsoft Corporation. All rights reserved.
2+
// Licensed under the MIT License.
3+
4+
'use strict';
5+
6+
import type { nbformat } from '@jupyterlab/coreutils/lib/nbformat';
7+
import { inject } from 'inversify';
8+
import { Memento } from 'vscode';
9+
import { NotebookDocument } from '../../../../types/vscode-proposed';
10+
import { IExtensionSingleActivationService } from '../../activation/types';
11+
import { IVSCodeNotebook } from '../../common/application/types';
12+
import { PYTHON_LANGUAGE } from '../../common/constants';
13+
import { traceWarning } from '../../common/logger';
14+
import { GLOBAL_MEMENTO, IDisposableRegistry } from '../../common/types';
15+
import { swallowExceptions } from '../../common/utils/decorators';
16+
import { translateKernelLanguageToMonaco } from '../common';
17+
import { IJupyterKernelSpec } from '../types';
18+
// tslint:disable-next-line: no-var-requires no-require-imports
19+
const vscodeNotebookEnums = require('vscode') as typeof import('vscode-proposed');
20+
21+
const LastSavedNotebookCellLanguage = 'DATASCIENCE.LAST_SAVED_CELL_LANGUAGE';
22+
/**
23+
* Responsible for determining the default language of a cell for new notebooks.
24+
* It should not always be `Python`, not all data scientists or users of notebooks use Python.
25+
*/
26+
export class NotebookCellLanguageService implements IExtensionSingleActivationService {
27+
constructor(
28+
@inject(IVSCodeNotebook) private readonly vscNotebook: IVSCodeNotebook,
29+
@inject(IDisposableRegistry) private readonly disposables: IDisposableRegistry,
30+
@inject(GLOBAL_MEMENTO) private readonly globalMemento: Memento
31+
) {}
32+
public getPreferredLanguage(metadata?: nbformat.INotebookMetadata) {
33+
const jupyterLanguage =
34+
metadata?.language_info?.name || (metadata?.kernelspec as IJupyterKernelSpec | undefined)?.language;
35+
return translateKernelLanguageToMonaco(jupyterLanguage || PYTHON_LANGUAGE);
36+
}
37+
public async activate() {
38+
this.vscNotebook.onDidSaveNotebookDocument(this.onDidSaveNotebookDocument, this, this.disposables);
39+
}
40+
private get lastSavedNotebookCellLanguage(): string | undefined {
41+
return this.globalMemento.get<string | undefined>(LastSavedNotebookCellLanguage);
42+
}
43+
@swallowExceptions('Saving last saved cell language')
44+
private async onDidSaveNotebookDocument(doc: NotebookDocument) {
45+
const language = this.getLanguageOfFirstCodeCell(doc);
46+
if (language && language !== this.lastSavedNotebookCellLanguage) {
47+
await this.globalMemento.update(LastSavedNotebookCellLanguage, language);
48+
}
49+
}
50+
private getLanguageOfFirstCodeCell(doc: NotebookDocument) {
51+
// If the document has been closed, accessing cell information can fail.
52+
// Ignore such exceptions.
53+
try {
54+
return doc.cells.find((cell) => cell.cellKind === vscodeNotebookEnums.CellKind.Code)?.language;
55+
} catch (ex) {
56+
traceWarning('Failed to determine language of first cell', ex);
57+
}
58+
}
59+
}

src/client/datascience/notebook/helpers/helpers.ts

Lines changed: 16 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,7 @@ import type {
1818
} from 'vscode-proposed';
1919
import { NotebookCellRunState } from '../../../../../typings/vscode-proposed';
2020
import { concatMultilineString, splitMultilineString } from '../../../../datascience-ui/common';
21-
import { MARKDOWN_LANGUAGE, PYTHON_LANGUAGE } from '../../../common/constants';
21+
import { MARKDOWN_LANGUAGE } from '../../../common/constants';
2222
import { traceError, traceWarning } from '../../../common/logger';
2323
import { sendTelemetryEvent } from '../../../telemetry';
2424
import { Telemetry } from '../../constants';
@@ -93,17 +93,16 @@ export function updateKernelInNotebookMetadata(
9393
/**
9494
* Converts a NotebookModel into VSCode friendly format.
9595
*/
96-
export function notebookModelToVSCNotebookData(model: VSCodeNotebookModel): NotebookData {
96+
export function notebookModelToVSCNotebookData(model: VSCodeNotebookModel, preferredLanguage: string): NotebookData {
9797
const cells = model.cells
98-
.map(createVSCNotebookCellDataFromCell.bind(undefined, model))
98+
.map(createVSCNotebookCellDataFromCell.bind(undefined, model, preferredLanguage))
9999
.filter((item) => !!item)
100100
.map((item) => item!);
101101

102-
const defaultLanguage = getDefaultCodeLanguage(model);
103102
if (cells.length === 0 && isUntitledFile(model.file)) {
104103
cells.push({
105104
cellKind: vscodeNotebookEnums.CellKind.Code,
106-
language: defaultLanguage,
105+
language: preferredLanguage,
107106
metadata: {},
108107
outputs: [],
109108
source: ''
@@ -197,12 +196,6 @@ export function getCustomNotebookCellMetadata(cell: ICell): Record<string, unkno
197196
return custom;
198197
}
199198

200-
export function getDefaultCodeLanguage(model: INotebookModel) {
201-
return model.metadata?.language_info?.name &&
202-
model.metadata?.language_info?.name.toLowerCase() !== PYTHON_LANGUAGE.toLowerCase()
203-
? model.metadata?.language_info?.name
204-
: PYTHON_LANGUAGE;
205-
}
206199
function createRawCellFromVSCNotebookCell(cell: NotebookCell): nbformat.IRawCell {
207200
const rawCell: nbformat.IRawCell = {
208201
cell_type: 'raw',
@@ -258,10 +251,13 @@ function createVSCNotebookCellDataFromMarkdownCell(model: INotebookModel, cell:
258251
outputs: []
259252
};
260253
}
261-
function createVSCNotebookCellDataFromCodeCell(model: INotebookModel, cell: ICell): NotebookCellData {
254+
function createVSCNotebookCellDataFromCodeCell(
255+
model: INotebookModel,
256+
cell: ICell,
257+
cellLanguage: string
258+
): NotebookCellData {
262259
// tslint:disable-next-line: no-any
263260
const outputs = createVSCCellOutputsFromOutputs(cell.data.outputs as any);
264-
const defaultCodeLanguage = getDefaultCodeLanguage(model);
265261
// If we have an execution count & no errors, then success state.
266262
// If we have an execution count & errors, then error state.
267263
// Else idle state.
@@ -316,7 +312,7 @@ function createVSCNotebookCellDataFromCodeCell(model: INotebookModel, cell: ICel
316312
}
317313
return {
318314
cellKind: vscodeNotebookEnums.CellKind.Code,
319-
language: defaultCodeLanguage,
315+
language: cellLanguage,
320316
metadata: notebookCellMetadata,
321317
source: concatMultilineString(cell.data.source),
322318
outputs
@@ -416,7 +412,11 @@ function createCodeCellFromVSCNotebookCell(cell: NotebookCell): nbformat.ICodeCe
416412
metadata
417413
};
418414
}
419-
export function createVSCNotebookCellDataFromCell(model: INotebookModel, cell: ICell): NotebookCellData | undefined {
415+
export function createVSCNotebookCellDataFromCell(
416+
model: INotebookModel,
417+
cellLanguage: string,
418+
cell: ICell
419+
): NotebookCellData | undefined {
420420
switch (cell.data.cell_type) {
421421
case 'raw': {
422422
return createVSCNotebookCellDataFromRawCell(model, cell);
@@ -425,7 +425,7 @@ export function createVSCNotebookCellDataFromCell(model: INotebookModel, cell: I
425425
return createVSCNotebookCellDataFromMarkdownCell(model, cell);
426426
}
427427
case 'code': {
428-
return createVSCNotebookCellDataFromCodeCell(model, cell);
428+
return createVSCNotebookCellDataFromCodeCell(model, cell, cellLanguage);
429429
}
430430
default: {
431431
traceError(`Conversion of Cell into VS Code NotebookCell not supported ${cell.data.cell_type}`);

src/client/datascience/notebook/notebookEditor.ts

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,7 @@ import {
2323
InterruptResult,
2424
IStatusProvider
2525
} from '../types';
26-
import { getDefaultCodeLanguage } from './helpers/helpers';
26+
import { NotebookCellLanguageService } from './defaultCellLanguageService';
2727
// tslint:disable-next-line: no-var-requires no-require-imports
2828
const vscodeNotebookEnums = require('vscode') as typeof import('vscode-proposed');
2929

@@ -86,7 +86,8 @@ export class NotebookEditor implements INotebookEditor {
8686
private readonly applicationShell: IApplicationShell,
8787
private readonly configurationService: IConfigurationService,
8888
disposables: IDisposableRegistry,
89-
private readonly nbExtensibility: INotebookExtensibility
89+
private readonly nbExtensibility: INotebookExtensibility,
90+
private readonly cellLanguageService: NotebookCellLanguageService
9091
) {
9192
disposables.push(model.onDidEdit(() => this._modified.fire(this)));
9293
disposables.push(
@@ -132,7 +133,7 @@ export class NotebookEditor implements INotebookEditor {
132133
if (!this.vscodeNotebook.activeNotebookEditor) {
133134
return;
134135
}
135-
const defaultLanguage = getDefaultCodeLanguage(this.model);
136+
const defaultLanguage = this.cellLanguageService.getPreferredLanguage(this.model.metadata);
136137
const editor = this.vscodeNotebook.notebookEditors.find((item) => item.document === this.document);
137138
if (editor) {
138139
editor

src/client/datascience/notebook/notebookEditorProvider.ts

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,7 @@ import {
2626
IStatusProvider
2727
} from '../types';
2828
import { JupyterNotebookView } from './constants';
29+
import { NotebookCellLanguageService } from './defaultCellLanguageService';
2930
import { isJupyterNotebook } from './helpers/helpers';
3031
import { NotebookEditor } from './notebookEditor';
3132

@@ -70,7 +71,8 @@ export class NotebookEditorProvider implements INotebookEditorProvider {
7071
@inject(IStatusProvider) private readonly statusProvider: IStatusProvider,
7172
@inject(IServiceContainer) private readonly serviceContainer: IServiceContainer,
7273
@inject(IDataScienceFileSystem) private readonly fs: IDataScienceFileSystem,
73-
@inject(INotebookExtensibility) private readonly notebookExtensibility: INotebookExtensibility
74+
@inject(INotebookExtensibility) private readonly notebookExtensibility: INotebookExtensibility,
75+
@inject(NotebookCellLanguageService) private readonly cellLanguageService: NotebookCellLanguageService
7476
) {
7577
this.disposables.push(this.vscodeNotebook.onDidOpenNotebookDocument(this.onDidOpenNotebookDocument, this));
7678
this.disposables.push(this.vscodeNotebook.onDidCloseNotebookDocument(this.onDidCloseNotebookDocument, this));
@@ -165,7 +167,8 @@ export class NotebookEditorProvider implements INotebookEditorProvider {
165167
this.appShell,
166168
this.configurationService,
167169
this.disposables,
168-
this.notebookExtensibility
170+
this.notebookExtensibility,
171+
this.cellLanguageService
169172
);
170173
this.onEditorOpened(editor);
171174
}
Lines changed: 110 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,110 @@
1+
// Copyright (c) Microsoft Corporation. All rights reserved.
2+
// Licensed under the MIT License.
3+
4+
'use strict';
5+
6+
import { assert } from 'chai';
7+
import { cloneDeep } from 'lodash';
8+
import { IDisposable } from 'monaco-editor';
9+
import { anything, instance, mock, when } from 'ts-mockito';
10+
import { EventEmitter, Memento, Uri } from 'vscode';
11+
// tslint:disable-next-line: no-var-requires no-require-imports
12+
const vscodeNotebookEnums = require('vscode') as typeof import('vscode-proposed');
13+
import type { NotebookContentProvider as VSCodeNotebookContentProvider, NotebookDocument } from 'vscode-proposed';
14+
import { IVSCodeNotebook } from '../../../client/common/application/types';
15+
import { MARKDOWN_LANGUAGE, PYTHON_LANGUAGE } from '../../../client/common/constants';
16+
import { ICryptoUtils } from '../../../client/common/types';
17+
import { NotebookCellLanguageService } from '../../../client/datascience/notebook/defaultCellLanguageService';
18+
import { createNotebookModel, disposeAllDisposables } from './helper';
19+
// tslint:disable: no-any
20+
suite('DataScience - Default Cell Language Service', () => {
21+
let notebookCellLanguageService: NotebookCellLanguageService;
22+
let notebookSaveEmitter: EventEmitter<NotebookDocument>;
23+
let memento: Memento;
24+
const disposables: IDisposable[] = [];
25+
setup(async () => {
26+
notebookSaveEmitter = new EventEmitter<NotebookDocument>();
27+
const vscNotebooks = mock<IVSCodeNotebook>();
28+
when(vscNotebooks.onDidSaveNotebookDocument).thenReturn(notebookSaveEmitter.event);
29+
memento = mock<Memento>();
30+
when(memento.get(anything())).thenReturn();
31+
notebookCellLanguageService = new NotebookCellLanguageService(
32+
instance(vscNotebooks),
33+
disposables,
34+
instance(memento)
35+
);
36+
await notebookCellLanguageService.activate();
37+
});
38+
teardown(() => disposeAllDisposables(disposables));
39+
test('Return notebook with 2 cells', async () => {
40+
const model = createNotebookModel(
41+
isNotebookTrusted,
42+
Uri.file('any'),
43+
instance(mock<Memento>()),
44+
instance(mock<ICryptoUtils>()),
45+
{
46+
cells: [
47+
{
48+
cell_type: 'code',
49+
execution_count: 10,
50+
hasExecutionOrder: true,
51+
outputs: [],
52+
source: 'print(1)',
53+
metadata: {}
54+
},
55+
{
56+
cell_type: 'markdown',
57+
hasExecutionOrder: false,
58+
source: '# HEAD',
59+
metadata: {}
60+
}
61+
]
62+
}
63+
);
64+
when(storageProvider.getOrCreateModel(anything(), anything(), anything(), anything())).thenResolve(model);
65+
66+
const notebook = await contentProvider.openNotebook(fileUri, {});
67+
68+
assert.isOk(notebook);
69+
assert.deepEqual(notebook.languages, [PYTHON_LANGUAGE]);
70+
// ignore metadata we add.
71+
const cellsWithoutCustomMetadata = notebook.cells.map((cell) => {
72+
const cellToCompareWith = cloneDeep(cell);
73+
delete cellToCompareWith.metadata?.custom;
74+
return cellToCompareWith;
75+
});
76+
77+
assert.equal(notebook.metadata.cellEditable, isNotebookTrusted);
78+
assert.equal(notebook.metadata.cellRunnable, isNotebookTrusted);
79+
assert.equal(notebook.metadata.editable, isNotebookTrusted);
80+
assert.equal(notebook.metadata.runnable, isNotebookTrusted);
81+
82+
assert.deepEqual(cellsWithoutCustomMetadata, [
83+
{
84+
cellKind: (vscodeNotebookEnums as any).CellKind.Code,
85+
language: PYTHON_LANGUAGE,
86+
outputs: [],
87+
source: 'print(1)',
88+
metadata: {
89+
editable: isNotebookTrusted,
90+
executionOrder: 10,
91+
hasExecutionOrder: true,
92+
runState: (vscodeNotebookEnums as any).NotebookCellRunState.Success,
93+
runnable: isNotebookTrusted
94+
}
95+
},
96+
{
97+
cellKind: (vscodeNotebookEnums as any).CellKind.Markdown,
98+
language: MARKDOWN_LANGUAGE,
99+
outputs: [],
100+
source: '# HEAD',
101+
metadata: {
102+
editable: isNotebookTrusted,
103+
executionOrder: undefined,
104+
hasExecutionOrder: false,
105+
runnable: false
106+
}
107+
}
108+
]);
109+
});
110+
});

0 commit comments

Comments
 (0)