Improve cell language detection and add support for language magics (#4081)

* Move to using notebook language by default, with override in cell
* Update cell language on kernel change
* Tweak language logic so that it prefers code mirror mode, then falls back since this was failing some notebooks
* Add new package.json contribution to define language magics. These result in cell language changing. Language is cleared out on removing the language magic
* Added support for executing Python, R and Java in the SQL Kernel to prove this out. It converts to the sp_execute_external_script format

TODO in future PR:

* Need to hook up completion item support for magics (issue #4078)
* Should add indicator at the bottom of a cell when an alternate language has been detected (issue #4079)
* On executing Python, R or Java, should add some output showing the generated code (issue #4080)
This commit is contained in:
Kevin Cunnane
2019-02-19 17:05:56 -08:00
committed by GitHub
parent 0205d0afb5
commit 1f501f4553
16 changed files with 400 additions and 131 deletions

View File

@@ -6,7 +6,6 @@ import 'vs/css!./code';
import { OnInit, Component, Input, Inject, forwardRef, ElementRef, ChangeDetectorRef, ViewChild, Output, EventEmitter, OnChanges, SimpleChange } from '@angular/core';
import { CommonServiceInterface } from 'sql/services/common/commonServiceInterface.service';
import { AngularDisposable } from 'sql/base/node/lifecycle';
import { QueryTextEditor } from 'sql/parts/modelComponents/queryTextEditor';
import { CellToggleMoreActions } from 'sql/parts/notebook/cellToggleMoreActions';
@@ -26,12 +25,12 @@ import { UntitledEditorInput } from 'vs/workbench/common/editor/untitledEditorIn
import * as DOM from 'vs/base/browser/dom';
import { IModeService } from 'vs/editor/common/services/modeService';
import { IModelService } from 'vs/editor/common/services/modelService';
import { IContextMenuService, IContextViewService } from 'vs/platform/contextview/browser/contextView';
import { INotificationService } from 'vs/platform/notification/common/notification';
import { IContextMenuService } from 'vs/platform/contextview/browser/contextView';
import { IConfigurationService } from 'vs/platform/configuration/common/configuration';
import { Emitter, debounceEvent } from 'vs/base/common/event';
import { CellTypes } from 'sql/parts/notebook/models/contracts';
import { OVERRIDE_EDITOR_THEMING_SETTING } from 'sql/workbench/services/notebook/common/notebookService';
import * as notebookUtils from 'sql/parts/notebook/notebookUtils';
export const CODE_SELECTOR: string = 'code-component';
const MARKDOWN_CLASS = 'markdown';
@@ -61,6 +60,12 @@ export class CodeComponent extends AngularDisposable implements OnInit, OnChange
@Input() set model(value: NotebookModel) {
this._model = value;
this._register(value.kernelChanged(() => {
// On kernel change, need to reevaluate the language for each cell
// Refresh based on the cell magic (since this is kernel-dependent) and then update using notebook language
this.checkForLanguageMagics();
this.updateLanguageMode();
}));
}
@Input() set activeCellId(value: string) {
@@ -88,21 +93,17 @@ export class CodeComponent extends AngularDisposable implements OnInit, OnChange
private _layoutEmitter = new Emitter<void>();
constructor(
@Inject(forwardRef(() => CommonServiceInterface)) private _bootstrapService: CommonServiceInterface,
@Inject(forwardRef(() => ChangeDetectorRef)) private _changeRef: ChangeDetectorRef,
@Inject(IWorkbenchThemeService) private themeService: IWorkbenchThemeService,
@Inject(IInstantiationService) private _instantiationService: IInstantiationService,
@Inject(IModelService) private _modelService: IModelService,
@Inject(IModeService) private _modeService: IModeService,
@Inject(IContextMenuService) private contextMenuService: IContextMenuService,
@Inject(IContextViewService) private contextViewService: IContextViewService,
@Inject(INotificationService) private notificationService: INotificationService,
@Inject(IConfigurationService) private _configurationService: IConfigurationService
) {
super();
this._cellToggleMoreActions = this._instantiationService.createInstance(CellToggleMoreActions);
debounceEvent(this._layoutEmitter.event, (l, e) => e, 250, /*leading=*/false)
(() => this.layout());
this._register(debounceEvent(this._layoutEmitter.event, (l, e) => e, 250, /*leading=*/false)
(() => this.layout()));
}
@@ -180,6 +181,7 @@ export class CodeComponent extends AngularDisposable implements OnInit, OnChange
this._editor.setHeightToScrollHeight();
this.cellModel.source = this._editorModel.getValue();
this.onContentChanged.emit();
this.checkForLanguageMagics();
// TODO see if there's a better way to handle reassessing size.
setTimeout(() => this._layoutEmitter.fire(), 250);
}));
@@ -220,7 +222,31 @@ export class CodeComponent extends AngularDisposable implements OnInit, OnChange
}
}
private updateLanguageMode() {
private checkForLanguageMagics(): void {
try {
if (!this.cellModel || this.cellModel.cellType !== CellTypes.Code) {
return;
}
if (this._editorModel && this._editor && this._editorModel.getLineCount() > 1) {
// Only try to match once we've typed past the first line
let magicName = notebookUtils.tryMatchCellMagic(this._editorModel.getLineContent(1));
if (magicName) {
let kernelName = this._model.clientSession && this._model.clientSession.kernel ? this._model.clientSession.kernel.name : undefined;
let magic = this._model.notebookOptions.cellMagicMapper.toLanguageMagic(magicName, kernelName);
if (magic && this.cellModel.language !== magic.language) {
this.cellModel.setOverrideLanguage(magic.language);
this.updateLanguageMode();
}
} else {
this.cellModel.setOverrideLanguage(undefined);
}
}
} catch (err) {
// No-op for now. Should we log?
}
}
private updateLanguageMode(): void {
if (this._editorModel && this._editor) {
this._modeService.getOrCreateMode(this.cellModel.language).then((modeValue) => {
this._modelService.setMode(this._editorModel, modeValue);