mirror of
https://github.com/ckaczor/azuredatastudio.git
synced 2026-01-13 17:22:15 -05:00
Add language picker to notebook cells. (#18601)
This commit is contained in:
@@ -14,9 +14,9 @@
|
||||
</div>
|
||||
<div style="display: flex; flex-flow: row; justify-content: flex-end;">
|
||||
<collapse-component *ngIf="cellModel.cellType === 'code' && cellModel.source && cellModel.source.length > 1" [cellModel]="cellModel" [activeCellId]="activeCellId"></collapse-component>
|
||||
<div #cellLanguage class="cellLanguage" *ngIf="cellModel.cellType === 'code' && cellModel.language">
|
||||
<button #cellLanguage title="{{cellLanguageTitle}}" class="cellLanguage" *ngIf="cellModel.cellType === 'code' && cellModel.language" (click)="onCellLanguageClick()">
|
||||
{{cellModel.displayLanguage}}
|
||||
</div>
|
||||
</button>
|
||||
</div>
|
||||
<div #parameter class="parameter" *ngIf="cellModel.cellType === 'code' && cellModel.isParameter">
|
||||
<span>{{parametersText}}</span>
|
||||
|
||||
@@ -22,7 +22,7 @@ import { IModelService } from 'vs/editor/common/services/modelService';
|
||||
import { IConfigurationService } from 'vs/platform/configuration/common/configuration';
|
||||
import { Event, Emitter } from 'vs/base/common/event';
|
||||
import { CellTypes } from 'sql/workbench/services/notebook/common/contracts';
|
||||
import { OVERRIDE_EDITOR_THEMING_SETTING } from 'sql/workbench/services/notebook/browser/notebookService';
|
||||
import { INotebookService, OVERRIDE_EDITOR_THEMING_SETTING } from 'sql/workbench/services/notebook/browser/notebookService';
|
||||
import { IConnectionManagementService } from 'sql/platform/connection/common/connectionManagement';
|
||||
import { ILogService } from 'vs/platform/log/common/log';
|
||||
import { ICodeEditor } from 'vs/editor/browser/editorBrowser';
|
||||
@@ -36,6 +36,11 @@ import { notebookConstants } from 'sql/workbench/services/notebook/browser/inter
|
||||
import { tryMatchCellMagic } from 'sql/workbench/services/notebook/browser/utils';
|
||||
import { IColorTheme } from 'vs/platform/theme/common/themeService';
|
||||
import { localize } from 'vs/nls';
|
||||
import { IQuickInputService, QuickPickInput } from 'vs/platform/quickinput/common/quickInput';
|
||||
import { onUnexpectedError } from 'vs/base/common/errors';
|
||||
import { getIconClasses } from 'vs/editor/common/services/getIconClasses';
|
||||
import { URI } from 'vs/base/common/uri';
|
||||
import { ILanguagePickInput } from 'vs/workbench/contrib/notebook/browser/contrib/coreActions';
|
||||
|
||||
export const CODE_SELECTOR: string = 'code-component';
|
||||
const MARKDOWN_CLASS = 'markdown';
|
||||
@@ -102,7 +107,9 @@ export class CodeComponent extends CellView implements OnInit, OnChanges {
|
||||
@Inject(IModeService) private _modeService: IModeService,
|
||||
@Inject(IConfigurationService) private _configurationService: IConfigurationService,
|
||||
@Inject(forwardRef(() => ChangeDetectorRef)) private _changeRef: ChangeDetectorRef,
|
||||
@Inject(ILogService) private readonly logService: ILogService
|
||||
@Inject(ILogService) private readonly logService: ILogService,
|
||||
@Inject(IQuickInputService) private _quickInputService: IQuickInputService,
|
||||
@Inject(INotebookService) private _notebookService: INotebookService,
|
||||
) {
|
||||
super();
|
||||
this._register(Event.debounce(this._layoutEmitter.event, (l, e) => e, 250, /*leading=*/false)
|
||||
@@ -145,6 +152,10 @@ export class CodeComponent extends CellView implements OnInit, OnChanges {
|
||||
return this.cellModel.cellGuid;
|
||||
}
|
||||
|
||||
get cellLanguageTitle(): string {
|
||||
return localize('selectCellLanguage', "Select Cell Language Mode");
|
||||
}
|
||||
|
||||
get parametersText(): string {
|
||||
return localize('parametersText', "Parameters");
|
||||
}
|
||||
@@ -269,6 +280,7 @@ export class CodeComponent extends CellView implements OnInit, OnChanges {
|
||||
this._register(this.cellModel.onLanguageChanged(language => {
|
||||
let nativeElement = <HTMLElement>this.languageElement.nativeElement;
|
||||
nativeElement.innerText = this.cellModel.displayLanguage;
|
||||
nativeElement.ariaLabel = this.cellModel.displayLanguage;
|
||||
this.updateLanguageMode();
|
||||
this._changeRef.detectChanges();
|
||||
}));
|
||||
@@ -446,4 +458,75 @@ export class CodeComponent extends CellView implements OnInit, OnChanges {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public onCellLanguageClick(): void {
|
||||
this._notebookService.getSupportedLanguagesForProvider(this._model.providerId, this._model.selectedKernelDisplayName)
|
||||
.then(languages => this.pickCellLanguage(languages))
|
||||
.then(selection => {
|
||||
if (selection?.languageId) {
|
||||
this._cellModel.setOverrideLanguage(selection.languageId);
|
||||
}
|
||||
})
|
||||
.catch(err => onUnexpectedError(err));
|
||||
}
|
||||
|
||||
private pickCellLanguage(languages: string[]): Promise<ILanguagePickInput | undefined> {
|
||||
if (languages.length === 0) {
|
||||
languages = [this._cellModel.language];
|
||||
}
|
||||
|
||||
const topItems: ILanguagePickInput[] = [];
|
||||
const mainItems: ILanguagePickInput[] = [];
|
||||
languages.forEach(lang => {
|
||||
let description: string;
|
||||
if (lang === this._cellModel.language) {
|
||||
description = localize('cellLanguageDescription', "({0}) - Current Language", lang);
|
||||
} else {
|
||||
description = localize('cellLanguageDescriptionConfigured', "({0})", lang);
|
||||
}
|
||||
|
||||
const languageName = this._modeService.getLanguageName(lang) ?? lang;
|
||||
const item = <ILanguagePickInput>{
|
||||
label: languageName,
|
||||
iconClasses: getIconClasses(this._modelService, this._modeService, this.getFakeResource(languageName, this._modeService)),
|
||||
description,
|
||||
languageId: lang
|
||||
};
|
||||
if (lang === this._cellModel.language) {
|
||||
topItems.push(item);
|
||||
} else {
|
||||
mainItems.push(item);
|
||||
}
|
||||
});
|
||||
|
||||
mainItems.sort((a, b) => {
|
||||
return a.description.localeCompare(b.description);
|
||||
});
|
||||
|
||||
const picks: QuickPickInput[] = [
|
||||
...topItems,
|
||||
{ type: 'separator' },
|
||||
...mainItems
|
||||
];
|
||||
return this._quickInputService.pick(picks, { placeHolder: this.cellLanguageTitle, canPickMany: false }) as Promise<ILanguagePickInput | undefined>;
|
||||
}
|
||||
|
||||
/**
|
||||
* Copied from coreActions.ts
|
||||
*/
|
||||
private getFakeResource(lang: string, modeService: IModeService): URI | undefined {
|
||||
let fakeResource: URI | undefined;
|
||||
|
||||
const extensions = modeService.getExtensions(lang);
|
||||
if (extensions?.length) {
|
||||
fakeResource = URI.file(extensions[0]);
|
||||
} else {
|
||||
const filenames = modeService.getFilenames(lang);
|
||||
if (filenames?.length) {
|
||||
fakeResource = URI.file(filenames[0]);
|
||||
}
|
||||
}
|
||||
|
||||
return fakeResource;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -118,7 +118,9 @@ code-component .cellLanguage {
|
||||
padding: 2px 15px;
|
||||
display: inline-block;
|
||||
text-align: center;
|
||||
font-size: 16px;
|
||||
font-size: 12px;
|
||||
border-style: none;
|
||||
background-color: transparent;
|
||||
}
|
||||
|
||||
code-component .parameter {
|
||||
|
||||
@@ -6,7 +6,7 @@ import 'vs/css!./notebook';
|
||||
|
||||
import { registerThemingParticipant, IColorTheme, ICssStyleCollector } from 'vs/platform/theme/common/themeService';
|
||||
import { SIDE_BAR_BACKGROUND, EDITOR_GROUP_HEADER_TABS_BACKGROUND } from 'vs/workbench/common/theme';
|
||||
import { activeContrastBorder, contrastBorder, buttonBackground, textLinkForeground, textLinkActiveForeground, textPreformatForeground, textBlockQuoteBackground, textBlockQuoteBorder, buttonForeground } from 'vs/platform/theme/common/colorRegistry';
|
||||
import { activeContrastBorder, contrastBorder, buttonBackground, textLinkForeground, textLinkActiveForeground, textPreformatForeground, textBlockQuoteBackground, textBlockQuoteBorder, buttonForeground, foreground } from 'vs/platform/theme/common/colorRegistry';
|
||||
import { editorLineHighlight, editorLineHighlightBorder } from 'vs/editor/common/view/editorColorRegistry';
|
||||
import { cellBorder, notebookToolbarIcon, notebookToolbarLines, buttonMenuArrow, dropdownArrow, markdownEditorBackground, codeEditorBackground, codeEditorBackgroundActive, codeEditorLineNumber, codeEditorToolbarIcon, codeEditorToolbarBackground, codeEditorToolbarBorder, toolbarBackground, toolbarIcon, toolbarBottomBorder, notebookToolbarSelectBackground, splitBorder, notebookCellTagBackground, notebookCellTagForeground, notebookFindMatchHighlight, notebookFindRangeHighlight } from 'sql/platform/theme/common/colorRegistry';
|
||||
import { IDisposable } from 'vs/base/common/lifecycle';
|
||||
@@ -14,6 +14,7 @@ import { IConfigurationService } from 'vs/platform/configuration/common/configur
|
||||
import { BareResultsGridInfo, getBareResultsGridInfoStyles } from 'sql/workbench/contrib/query/browser/queryResultsEditor';
|
||||
import { getZoomLevel } from 'vs/base/browser/browser';
|
||||
import * as types from 'vs/base/common/types';
|
||||
import { cellStatusBarItemHover } from 'vs/workbench/contrib/notebook/browser/notebookEditorWidget';
|
||||
|
||||
export function registerNotebookThemes(overrideEditorThemeSetting: boolean, configurationService: IConfigurationService): IDisposable {
|
||||
return registerThemingParticipant((theme: IColorTheme, collector: ICssStyleCollector) => {
|
||||
@@ -181,6 +182,16 @@ export function registerNotebookThemes(overrideEditorThemeSetting: boolean, conf
|
||||
collector.addRule(`.notebookEditor .notebook-cell.active cell-toolbar-component { background-color: ${notebookToolbarSelectBackgroundColor};}`);
|
||||
}
|
||||
|
||||
// Cell language button
|
||||
const textColor = theme.getColor(foreground);
|
||||
if (textColor) {
|
||||
collector.addRule(`code-component .cellLanguage { color: ${textColor}; }`);
|
||||
}
|
||||
const cellStatusBarHoverBg = theme.getColor(cellStatusBarItemHover);
|
||||
if (cellStatusBarHoverBg) {
|
||||
collector.addRule(`code-component .cellLanguage:hover { background-color: ${cellStatusBarHoverBg}; }`);
|
||||
}
|
||||
|
||||
// Markdown editor toolbar
|
||||
const toolbarBackgroundColor = theme.getColor(toolbarBackground);
|
||||
if (toolbarBackgroundColor) {
|
||||
|
||||
@@ -235,6 +235,9 @@ export class ServerManagerStub implements nb.ServerManager {
|
||||
}
|
||||
|
||||
export class NotebookServiceStub implements INotebookService {
|
||||
getSupportedLanguagesForProvider(provider: string, kernelDisplayName?: string): Promise<string[]> {
|
||||
throw new Error('Method not implemented.');
|
||||
}
|
||||
createNotebookInput(options: INotebookShowOptions, resource?: UriComponents): Promise<IEditorInput> {
|
||||
throw new Error('Method not implemented.');
|
||||
}
|
||||
|
||||
@@ -418,6 +418,7 @@ export class CellModel extends Disposable implements ICellModel {
|
||||
if (newLanguage !== this._language) {
|
||||
this._language = newLanguage;
|
||||
this._onLanguageChanged.fire(newLanguage);
|
||||
this.sendChangeToNotebook(NotebookChangeType.CellMetadataUpdated);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -76,6 +76,8 @@ export interface INotebookService {
|
||||
|
||||
getStandardKernelsForProvider(provider: string): Promise<azdata.nb.IStandardKernel[] | undefined>;
|
||||
|
||||
getSupportedLanguagesForProvider(provider: string, kernelDisplayName?: string): Promise<string[]>;
|
||||
|
||||
getOrCreateSerializationManager(providerId: string, uri: URI): Promise<ISerializationManager>;
|
||||
|
||||
getOrCreateExecuteManager(providerId: string, uri: URI): Thenable<IExecuteManager>;
|
||||
|
||||
@@ -494,6 +494,22 @@ export class NotebookService extends Disposable implements INotebookService {
|
||||
return kernels;
|
||||
}
|
||||
|
||||
public async getSupportedLanguagesForProvider(provider: string, kernelDisplayName?: string): Promise<string[]> {
|
||||
let languages: string[] = [];
|
||||
let kernels = await this.getStandardKernelsForProvider(provider);
|
||||
if (kernelDisplayName && kernels) {
|
||||
kernels = kernels.filter(kernel => kernel.displayName === kernelDisplayName);
|
||||
}
|
||||
kernels?.forEach(kernel => {
|
||||
if (kernel.supportedLanguages) {
|
||||
languages.push(...kernel.supportedLanguages);
|
||||
}
|
||||
});
|
||||
// Remove duplicates
|
||||
languages = [...new Set(languages)];
|
||||
return languages;
|
||||
}
|
||||
|
||||
private shutdown(): void {
|
||||
this._executeManagersMap.forEach(manager => {
|
||||
manager.forEach(m => {
|
||||
|
||||
@@ -1647,7 +1647,7 @@ registerAction2(class ClearCellOutputsAction extends NotebookCellAction {
|
||||
}
|
||||
});
|
||||
|
||||
interface ILanguagePickInput extends IQuickPickItem {
|
||||
export interface ILanguagePickInput extends IQuickPickItem { // {{SQL CARBON EDIT}} Add export
|
||||
languageId: string;
|
||||
description: string;
|
||||
}
|
||||
|
||||
@@ -160,6 +160,25 @@ export function setup(opts: minimist.ParsedArgs) {
|
||||
await verifyElementRendered(app, markdownString, imgSelector);
|
||||
});
|
||||
});
|
||||
|
||||
describe('Cell Actions', function () {
|
||||
it('can change cell language', async function () {
|
||||
const app = this.app as Application;
|
||||
await app.workbench.sqlNotebook.newUntitledNotebook();
|
||||
await app.workbench.sqlNotebook.notebookToolbar.waitForKernel('SQL');
|
||||
await app.workbench.sqlNotebook.addCellFromPlaceholder('Code');
|
||||
await app.workbench.sqlNotebook.waitForPlaceholderGone();
|
||||
|
||||
const languagePickerButton = '.notebook-cell.active .cellLanguage';
|
||||
await app.code.waitAndClick(languagePickerButton);
|
||||
|
||||
await app.workbench.quickinput.waitForQuickInputElements(names => names[0] === 'SQL');
|
||||
await app.code.waitAndClick('.quick-input-widget .quick-input-list .monaco-list-row');
|
||||
|
||||
let element = await app.code.waitForElement(languagePickerButton);
|
||||
assert.strictEqual(element.textContent?.trim(), 'SQL');
|
||||
});
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user