Skip to content

Commit 7c32673

Browse files
committed
Updates
1 parent 8b8fbbf commit 7c32673

File tree

14 files changed

+271
-33
lines changed

14 files changed

+271
-33
lines changed

package.json

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3344,6 +3344,16 @@
33443344
"extensions": [
33453345
".ipynb"
33463346
]
3347+
},
3348+
{
3349+
"id": "julia",
3350+
"aliases": [
3351+
"Julia",
3352+
"julia"
3353+
],
3354+
"extensions": [
3355+
".jl"
3356+
]
33473357
}
33483358
],
33493359
"grammars": [

src/client/common/application/notebook.ts

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -52,6 +52,11 @@ export class VSCodeNotebook implements IVSCodeNotebook {
5252
? this.notebook.onDidCloseNotebookDocument
5353
: new EventEmitter<NotebookDocument>().event;
5454
}
55+
public get onDidSaveNotebookDocument(): Event<NotebookDocument> {
56+
return this.canUseNotebookApi
57+
? this.notebook.onDidSaveNotebookDocument
58+
: new EventEmitter<NotebookDocument>().event;
59+
}
5560
public get notebookDocuments(): ReadonlyArray<NotebookDocument> {
5661
return this.canUseNotebookApi ? this.notebook.notebookDocuments : [];
5762
}

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/jupyter/kernels/helpers.ts

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@ import { JupyterKernelSpec } from './jupyterKernelSpec';
1010
const NamedRegexp = require('named-js-regexp') as typeof import('named-js-regexp');
1111

1212
// tslint:disable-next-line: no-require-imports
13+
import { nbformat } from '@jupyterlab/coreutils';
1314
import cloneDeep = require('lodash/cloneDeep');
1415
import { PYTHON_LANGUAGE } from '../../../common/constants';
1516
import { ReadWrite } from '../../../common/types';
@@ -133,6 +134,18 @@ export function getKernelConnectionLanguage(kernelConnection?: KernelConnectionM
133134
: undefined;
134135
return model?.language || kernelSpec?.language;
135136
}
137+
export function getLanguageInNotebookMetadata(metadata?: nbformat.INotebookMetadata): string | undefined {
138+
if (!metadata) {
139+
return;
140+
}
141+
// If kernel spec is defined & we have a language in that, then use that information.
142+
// tslint:disable-next-line: no-any
143+
const kernelSpec: IJupyterKernelSpec | undefined = metadata.kernelspec as any;
144+
if (kernelSpec?.language) {
145+
return kernelSpec.language;
146+
}
147+
return metadata.language_info?.name;
148+
}
136149
// Create a default kernelspec with the given display name
137150
export function createDefaultKernelSpec(interpreter?: PythonEnvironment): IJupyterKernelSpec {
138151
// This creates a default kernel spec. When launched, 'python' argument will map to using the interpreter

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: 68 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,68 @@
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 { getLanguageInNotebookMetadata, isPythonKernelConnection } from '../jupyter/kernels/helpers';
18+
import { IJupyterKernelSpec } from '../types';
19+
import { getNotebookMetadata } from './helpers/helpers';
20+
// tslint:disable-next-line: no-var-requires no-require-imports
21+
const vscodeNotebookEnums = require('vscode') as typeof import('vscode-proposed');
22+
23+
const LastSavedNotebookCellLanguage = 'DATASCIENCE.LAST_SAVED_CELL_LANGUAGE';
24+
/**
25+
* Responsible for determining the default language of a cell for new notebooks.
26+
* It should not always be `Python`, not all data scientists or users of notebooks use Python.
27+
*/
28+
export class NotebookCellLanguageService implements IExtensionSingleActivationService {
29+
constructor(
30+
@inject(IVSCodeNotebook) private readonly vscNotebook: IVSCodeNotebook,
31+
@inject(IDisposableRegistry) private readonly disposables: IDisposableRegistry,
32+
@inject(GLOBAL_MEMENTO) private readonly globalMemento: Memento
33+
) {}
34+
/**
35+
* Gets the language to be used for the default cell in an empty notebook.
36+
* Give preference to `python` when we don't know what to use.
37+
*/
38+
public getPreferredLanguage(metadata?: nbformat.INotebookMetadata) {
39+
const jupyterLanguage =
40+
metadata?.language_info?.name || (metadata?.kernelspec as IJupyterKernelSpec | undefined)?.language;
41+
return translateKernelLanguageToMonaco(jupyterLanguage || PYTHON_LANGUAGE);
42+
}
43+
public async activate() {
44+
this.vscNotebook.onDidSaveNotebookDocument(this.onDidSaveNotebookDocument, this, this.disposables);
45+
}
46+
private get lastSavedNotebookCellLanguage(): string | undefined {
47+
return this.globalMemento.get<string | undefined>(LastSavedNotebookCellLanguage);
48+
}
49+
@swallowExceptions('Saving last saved cell language')
50+
private async onDidSaveNotebookDocument(doc: NotebookDocument) {
51+
const language = this.getLanguageOfFirstCodeCell(doc);
52+
if (language && language !== this.lastSavedNotebookCellLanguage) {
53+
await this.globalMemento.update(LastSavedNotebookCellLanguage, language);
54+
}
55+
}
56+
private getLanguageOfFirstCodeCell(doc: NotebookDocument) {
57+
// If the document has been closed, accessing cell information can fail.
58+
// Ignore such exceptions.
59+
try {
60+
// Give preference to the language information in the metadata.
61+
const language = getLanguageInNotebookMetadata(getNotebookMetadata(doc));
62+
// Fall back to the language of the first code cell in the notebook.
63+
return language || doc.cells.find((cell) => cell.cellKind === vscodeNotebookEnums.CellKind.Code)?.language;
64+
} catch (ex) {
65+
traceWarning('Failed to determine language of first cell', ex);
66+
}
67+
}
68+
}

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';
@@ -112,17 +112,16 @@ export function updateKernelInfoInNotebookMetadata(
112112
/**
113113
* Converts a NotebookModel into VSCode friendly format.
114114
*/
115-
export function notebookModelToVSCNotebookData(model: VSCodeNotebookModel): NotebookData {
115+
export function notebookModelToVSCNotebookData(model: VSCodeNotebookModel, preferredLanguage: string): NotebookData {
116116
const cells = model.cells
117-
.map(createVSCNotebookCellDataFromCell.bind(undefined, model))
117+
.map(createVSCNotebookCellDataFromCell.bind(undefined, model, preferredLanguage))
118118
.filter((item) => !!item)
119119
.map((item) => item!);
120120

121-
const defaultLanguage = getDefaultCodeLanguage(model);
122121
if (cells.length === 0 && isUntitledFile(model.file)) {
123122
cells.push({
124123
cellKind: vscodeNotebookEnums.CellKind.Code,
125-
language: defaultLanguage,
124+
language: preferredLanguage,
126125
metadata: {},
127126
outputs: [],
128127
source: ''
@@ -216,12 +215,6 @@ export function getCustomNotebookCellMetadata(cell: ICell): Record<string, unkno
216215
return custom;
217216
}
218217

219-
export function getDefaultCodeLanguage(model: INotebookModel) {
220-
return model.metadata?.language_info?.name &&
221-
model.metadata?.language_info?.name.toLowerCase() !== PYTHON_LANGUAGE.toLowerCase()
222-
? model.metadata?.language_info?.name
223-
: PYTHON_LANGUAGE;
224-
}
225218
function createRawCellFromVSCNotebookCell(cell: NotebookCell): nbformat.IRawCell {
226219
const rawCell: nbformat.IRawCell = {
227220
cell_type: 'raw',
@@ -277,10 +270,13 @@ function createVSCNotebookCellDataFromMarkdownCell(model: INotebookModel, cell:
277270
outputs: []
278271
};
279272
}
280-
function createVSCNotebookCellDataFromCodeCell(model: INotebookModel, cell: ICell): NotebookCellData {
273+
function createVSCNotebookCellDataFromCodeCell(
274+
model: INotebookModel,
275+
cell: ICell,
276+
cellLanguage: string
277+
): NotebookCellData {
281278
// tslint:disable-next-line: no-any
282279
const outputs = createVSCCellOutputsFromOutputs(cell.data.outputs as any);
283-
const defaultCodeLanguage = getDefaultCodeLanguage(model);
284280
// If we have an execution count & no errors, then success state.
285281
// If we have an execution count & errors, then error state.
286282
// Else idle state.
@@ -335,7 +331,7 @@ function createVSCNotebookCellDataFromCodeCell(model: INotebookModel, cell: ICel
335331
}
336332
return {
337333
cellKind: vscodeNotebookEnums.CellKind.Code,
338-
language: defaultCodeLanguage,
334+
language: cellLanguage,
339335
metadata: notebookCellMetadata,
340336
source: concatMultilineString(cell.data.source),
341337
outputs
@@ -435,7 +431,11 @@ function createCodeCellFromVSCNotebookCell(cell: NotebookCell): nbformat.ICodeCe
435431
metadata
436432
};
437433
}
438-
export function createVSCNotebookCellDataFromCell(model: INotebookModel, cell: ICell): NotebookCellData | undefined {
434+
export function createVSCNotebookCellDataFromCell(
435+
model: INotebookModel,
436+
cellLanguage: string,
437+
cell: ICell
438+
): NotebookCellData | undefined {
439439
switch (cell.data.cell_type) {
440440
case 'raw': {
441441
return createVSCNotebookCellDataFromRawCell(model, cell);
@@ -444,7 +444,7 @@ export function createVSCNotebookCellDataFromCell(model: INotebookModel, cell: I
444444
return createVSCNotebookCellDataFromMarkdownCell(model, cell);
445445
}
446446
case 'code': {
447-
return createVSCNotebookCellDataFromCodeCell(model, cell);
447+
return createVSCNotebookCellDataFromCodeCell(model, cell, cellLanguage);
448448
}
449449
default: {
450450
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
}

0 commit comments

Comments
 (0)