diff --git a/.github/classifier.yml b/.github/classifier.yml index bfe866776c..8e1bea9393 100644 --- a/.github/classifier.yml +++ b/.github/classifier.yml @@ -1,49 +1,49 @@ { - perform: true, - alwaysRequireAssignee: false, - labelsRequiringAssignee: [], - autoAssignees: { - accessibility: [], - acquisition: [], - agent: [], - azure: [], - backup: [], - bcdr: [], - 'chart viewer': [], - connection: [], - dacfx: [], - dashboard: [], - 'data explorer': [], - documentation: [], - 'edit data': [], - export: [], - extensibility: [], - extensionManager: [], - globalization: [], - grid: [], - import: [], - insights: [], - intellisense: [], - localization: [], - 'managed instance': [], - notebooks: [], - 'object explorer': [], - performance: [], - profiler: [], - 'query editor': [], - 'query execution': [], - reliability: [], - restore: [], - scripting: [], - 'server group': [], - settings: [], - setup: [], - shell: [], - showplan: [], - snippet: [], - sql2019Preview: [], - sqldw: [], - supportability: [], - ux: [] - } + perform: true, + alwaysRequireAssignee: false, + labelsRequiringAssignee: [], + autoAssignees: { + accessibility: [], + acquisition: [], + agent: [], + azure: [], + backup: [], + bcdr: [], + 'chart viewer': [], + connection: [], + dacfx: [], + dashboard: [], + 'data explorer': [], + documentation: [], + 'edit data': [], + export: [], + extensibility: [], + extensionManager: [], + globalization: [], + grid: [], + import: [], + insights: [], + intellisense: [], + localization: [], + 'managed instance': [], + notebooks: [], + 'object explorer': [], + performance: [], + profiler: [], + 'query editor': [], + 'query execution': [], + reliability: [], + restore: [], + scripting: [], + 'server group': [], + settings: [], + setup: [], + shell: [], + showplan: [], + snippet: [], + sql2019Preview: [], + sqldw: [], + supportability: [], + ux: [] + } } diff --git a/.github/commands.yml b/.github/commands.yml index b395715e13..482f005732 100644 --- a/.github/commands.yml +++ b/.github/commands.yml @@ -1,12 +1,12 @@ { - perform: false, - commands: [ - { - type: 'label', - name: 'duplicate', - allowTriggerByBot: true, - action: 'close', - comment: "Thanks for creating this issue! We figured it's covering the same as another one we already have. Thus, we closed this one as a duplicate. You can search for existing issues [here](https://aka.ms/vscodeissuesearch). See also our [issue reporting](https://aka.ms/vscodeissuereporting) guidelines.\n\nHappy Coding!" - } - ] + perform: false, + commands: [ + { + type: 'label', + name: 'duplicate', + allowTriggerByBot: true, + action: 'close', + comment: "Thanks for creating this issue! We figured it's covering the same as another one we already have. Thus, we closed this one as a duplicate. You can search for existing issues [here](https://aka.ms/vscodeissuesearch). See also our [issue reporting](https://aka.ms/vscodeissuereporting) guidelines.\n\nHappy Coding!" + } + ] } diff --git a/.github/locker.yml b/.github/locker.yml index a9dab936b9..c8c737ca08 100644 --- a/.github/locker.yml +++ b/.github/locker.yml @@ -1,6 +1,6 @@ { - daysAfterClose: 45, - daysSinceLastUpdate: 3, - ignoredLabels: [], - perform: false + daysAfterClose: 45, + daysSinceLastUpdate: 3, + ignoredLabels: [], + perform: true } diff --git a/.github/needs_more_info.yml b/.github/needs_more_info.yml index 143c53ba53..5bab0d7be1 100644 --- a/.github/needs_more_info.yml +++ b/.github/needs_more_info.yml @@ -1,6 +1,6 @@ { - daysUntilClose: 7, - needsMoreInfoLabel: 'more info', - perform: false, - closeComment: "This issue has been closed automatically because it needs more information and has not had recent activity. See also our [issue reporting](https://aka.ms/vscodeissuereporting) guidelines.\n\nHappy Coding!" + daysUntilClose: 7, + needsMoreInfoLabel: 'more info', + perform: false, + closeComment: "This issue has been closed automatically because it needs more information and has not had recent activity. See also our [issue reporting](https://aka.ms/vscodeissuereporting) guidelines.\n\nHappy Coding!" } diff --git a/.github/new_release.yml b/.github/new_release.yml index b7111864fd..de8617c981 100644 --- a/.github/new_release.yml +++ b/.github/new_release.yml @@ -1,6 +1,6 @@ { - newReleaseLabel: 'new-release', - newReleaseColor: '006b75', - daysAfterRelease: 5, - perform: true + newReleaseLabel: 'new-release', + newReleaseColor: '006b75', + daysAfterRelease: 5, + perform: true } diff --git a/.github/similarity.yml b/.github/similarity.yml index 8a7eac1eff..802fd8cd7b 100644 --- a/.github/similarity.yml +++ b/.github/similarity.yml @@ -1,5 +1,5 @@ { - perform: false, - whenCreatedByTeam: true, - comment: "Thanks for submitting this issue. Please also check if it is already covered by an existing one, like:\n${potentialDuplicates}" + perform: true, + whenCreatedByTeam: true, + comment: "Thanks for submitting this issue. Please also check if it is already covered by an existing one, like:\n${potentialDuplicates}" } diff --git a/extensions/notebook/src/dialog/configurePythonDialog.ts b/extensions/notebook/src/dialog/configurePythonDialog.ts index 539430050a..7bc2c89451 100644 --- a/extensions/notebook/src/dialog/configurePythonDialog.ts +++ b/extensions/notebook/src/dialog/configurePythonDialog.ts @@ -11,8 +11,9 @@ import * as azdata from 'azdata'; import * as fs from 'fs'; import * as utils from '../common/utils'; -import { AppContext } from '../common/appContext'; import JupyterServerInstallation from '../jupyter/jupyterServerInstallation'; +import { ApiWrapper } from '../common/apiWrapper'; +import { Deferred } from '../common/promise'; const localize = nls.loadMessageBundle(); @@ -31,27 +32,44 @@ export class ConfigurePythonDialog { private pythonLocationTextBox: azdata.InputBoxComponent; private browseButton: azdata.ButtonComponent; - constructor(private appContext: AppContext, private outputChannel: vscode.OutputChannel, private jupyterInstallation: JupyterServerInstallation) { + private _setupComplete: Deferred; + + constructor(private apiWrapper: ApiWrapper, private outputChannel: vscode.OutputChannel, private jupyterInstallation: JupyterServerInstallation) { + this._setupComplete = new Deferred(); } - public async showDialog() { + /** + * Opens a dialog to configure python installation for notebooks. + * @param rejectOnCancel Specifies whether an error should be thrown after clicking Cancel. + * @returns A promise that is resolved when the python installation completes. + */ + public showDialog(rejectOnCancel: boolean = false): Promise { this.dialog = azdata.window.createModelViewDialog(this.DialogTitle); this.initializeContent(); this.dialog.okButton.label = this.OkButtonText; this.dialog.cancelButton.label = this.CancelButtonText; + this.dialog.cancelButton.onClick(() => { + if (rejectOnCancel) { + this._setupComplete.reject(localize('pythonInstallDeclined', 'Python installation was declined.')); + } else { + this._setupComplete.resolve(); + } + }); this.dialog.registerCloseValidator(() => this.handleInstall()); azdata.window.openDialog(this.dialog); + + return this._setupComplete.promise; } - private initializeContent() { + private initializeContent(): void { this.dialog.registerContent(async view => { this.pythonLocationTextBox = view.modelBuilder.inputBox() .withProperties({ - value: JupyterServerInstallation.getPythonInstallPath(this.appContext.apiWrapper), + value: JupyterServerInstallation.getPythonInstallPath(this.apiWrapper), width: '100%' }).component(); @@ -106,14 +124,18 @@ export class ConfigurePythonDialog { return false; } } catch (err) { - this.appContext.apiWrapper.showErrorMessage(utils.getErrorMessage(err)); + this.apiWrapper.showErrorMessage(utils.getErrorMessage(err)); return false; } // Don't wait on installation, since there's currently no Cancel functionality - this.jupyterInstallation.startInstallProcess(pythonLocation).catch(err => { - this.appContext.apiWrapper.showErrorMessage(utils.getErrorMessage(err)); - }); + this.jupyterInstallation.startInstallProcess(pythonLocation) + .then(() => { + this._setupComplete.resolve(); + }) + .catch(err => { + this._setupComplete.reject(utils.getErrorMessage(err)); + }); return true; } @@ -149,20 +171,13 @@ export class ConfigurePythonDialog { openLabel: this.SelectFileLabel }; - let fileUris: vscode.Uri[] = await this.appContext.apiWrapper.showOpenDialog(options); + let fileUris: vscode.Uri[] = await this.apiWrapper.showOpenDialog(options); if (fileUris && fileUris[0]) { this.pythonLocationTextBox.value = fileUris[0].fsPath; } } - private showInfoMessage(message: string) { - this.dialog.message = { - text: message, - level: azdata.window.MessageLevel.Information - }; - } - - private showErrorMessage(message: string) { + private showErrorMessage(message: string): void { this.dialog.message = { text: message, level: azdata.window.MessageLevel.Error diff --git a/extensions/notebook/src/integrationTest/notebookIntegration.test.ts b/extensions/notebook/src/integrationTest/notebookIntegration.test.ts index bd9d5d7f47..d55aadb14a 100644 --- a/extensions/notebook/src/integrationTest/notebookIntegration.test.ts +++ b/extensions/notebook/src/integrationTest/notebookIntegration.test.ts @@ -127,6 +127,6 @@ function writeNotebookToFile(pythonNotebook: INotebook): vscode.Uri { async function ensureJupyterInstalled(): Promise { let jupterControllerExports = vscode.extensions.getExtension('Microsoft.sql-vnext').exports; let jupyterController = jupterControllerExports.getJupterController() as JupyterController; - await jupyterController.jupyterInstallation; + await jupyterController.jupyterInstallation.installReady; } diff --git a/extensions/notebook/src/jupyter/jupyterController.ts b/extensions/notebook/src/jupyter/jupyterController.ts index 860702bce0..75a95ce7ba 100644 --- a/extensions/notebook/src/jupyter/jupyterController.ts +++ b/extensions/notebook/src/jupyter/jupyterController.ts @@ -30,7 +30,7 @@ import CodeAdapter from '../prompts/adapter'; let untitledCounter = 0; export class JupyterController implements vscode.Disposable { - private _jupyterInstallation: Promise; + private _jupyterInstallation: JupyterServerInstallation; private _notebookInstances: IServerInstance[] = []; private outputChannel: vscode.OutputChannel; @@ -55,31 +55,11 @@ export class JupyterController implements vscode.Disposable { // PUBLIC METHODS ////////////////////////////////////////////////////// public async activate(): Promise { - // Prompt for install if the python installation path is not defined - let jupyterInstaller = new JupyterServerInstallation( + this._jupyterInstallation = new JupyterServerInstallation( this.extensionContext.extensionPath, this.outputChannel, this.apiWrapper); - if (JupyterServerInstallation.isPythonInstalled(this.apiWrapper)) { - this._jupyterInstallation = Promise.resolve(jupyterInstaller); - } else { - this._jupyterInstallation = new Promise(resolve => { - jupyterInstaller.onInstallComplete(err => { - if (!err) { - resolve(jupyterInstaller); - } - }); - }); - } - let notebookProvider = undefined; - - notebookProvider = this.registerNotebookProvider(); - azdata.nb.onDidOpenNotebookDocument(notebook => { - if (!JupyterServerInstallation.isPythonInstalled(this.apiWrapper)) { - this.doConfigurePython(jupyterInstaller); - } - }); // Add command/task handlers this.apiWrapper.registerTaskHandler(constants.jupyterOpenNotebookTask, (profile: azdata.IConnectionProfile) => { return this.handleOpenNotebookTask(profile); @@ -96,11 +76,12 @@ export class JupyterController implements vscode.Disposable { this.apiWrapper.registerCommand(constants.jupyterReinstallDependenciesCommand, () => { return this.handleDependenciesReinstallation(); }); this.apiWrapper.registerCommand(constants.jupyterInstallPackages, () => { return this.doManagePackages(); }); - this.apiWrapper.registerCommand(constants.jupyterConfigurePython, () => { return this.doConfigurePython(jupyterInstaller); }); + this.apiWrapper.registerCommand(constants.jupyterConfigurePython, () => { return this.doConfigurePython(this._jupyterInstallation); }); let supportedFileFilter: vscode.DocumentFilter[] = [ { scheme: 'untitled', language: '*' } ]; + let notebookProvider = this.registerNotebookProvider(); this.extensionContext.subscriptions.push(this.apiWrapper.registerCompletionItemProvider(supportedFileFilter, new NotebookCompletionItemProvider(notebookProvider))); return true; @@ -195,7 +176,7 @@ export class JupyterController implements vscode.Disposable { private async handleDependenciesReinstallation(): Promise { if (await this.confirmReinstall()) { - this._jupyterInstallation = JupyterServerInstallation.getInstallation( + this._jupyterInstallation = await JupyterServerInstallation.getInstallation( this.extensionContext.extensionPath, this.outputChannel, this.apiWrapper, @@ -225,14 +206,11 @@ export class JupyterController implements vscode.Disposable { } } - public async doConfigurePython(jupyterInstaller: JupyterServerInstallation): Promise { - try { - let pythonDialog = new ConfigurePythonDialog(this.appContext, this.outputChannel, jupyterInstaller); - await pythonDialog.showDialog(); - } catch (error) { - let message = utils.getErrorMessage(error); - this.apiWrapper.showErrorMessage(message); - } + public doConfigurePython(jupyterInstaller: JupyterServerInstallation): void { + let pythonDialog = new ConfigurePythonDialog(this.apiWrapper, this.outputChannel, jupyterInstaller); + pythonDialog.showDialog().catch(err => { + this.apiWrapper.showErrorMessage(utils.getErrorMessage(err)); + }); } public getTextToSendToTerminal(shellType: any): string { diff --git a/extensions/notebook/src/jupyter/jupyterServerInstallation.ts b/extensions/notebook/src/jupyter/jupyterServerInstallation.ts index cc3a1a5d08..95fa6d672c 100644 --- a/extensions/notebook/src/jupyter/jupyterServerInstallation.ts +++ b/extensions/notebook/src/jupyter/jupyterServerInstallation.ts @@ -16,7 +16,9 @@ import * as request from 'request'; import { ApiWrapper } from '../common/apiWrapper'; import * as constants from '../common/constants'; import * as utils from '../common/utils'; -import { OutputChannel, ConfigurationTarget, Event, EventEmitter, window } from 'vscode'; +import { OutputChannel, ConfigurationTarget, window } from 'vscode'; +import { Deferred } from '../common/promise'; +import { ConfigurePythonDialog } from '../dialog/configurePythonDialog'; const localize = nls.loadMessageBundle(); const msgPythonInstallationProgress = localize('msgPythonInstallationProgress', 'Python installation is in progress'); @@ -53,7 +55,7 @@ export default class JupyterServerInstallation { private static readonly DefaultPythonLocation = path.join(utils.getUserHome(), 'azuredatastudio-python'); - private _installCompleteEmitter = new EventEmitter(); + private _installReady: Deferred; constructor(extensionPath: string, outputChannel: OutputChannel, apiWrapper: ApiWrapper, pythonInstallationPath?: string, forceInstall?: boolean) { this.extensionPath = extensionPath; @@ -63,10 +65,15 @@ export default class JupyterServerInstallation { this._forceInstall = !!forceInstall; this.configurePackagePaths(); + + this._installReady = new Deferred(); + if (JupyterServerInstallation.isPythonInstalled(this.apiWrapper)) { + this._installReady.resolve(); + } } - public get onInstallComplete(): Event { - return this._installCompleteEmitter.event; + public get installReady(): Promise { + return this._installReady.promise; } public static async getInstallation( @@ -232,7 +239,7 @@ export default class JupyterServerInstallation { }; } - public async startInstallProcess(pythonInstallationPath?: string): Promise { + public startInstallProcess(pythonInstallationPath?: string): Promise { if (pythonInstallationPath) { this._pythonInstallationPath = pythonInstallationPath; this.configurePackagePaths(); @@ -249,23 +256,31 @@ export default class JupyterServerInstallation { operation: op => { this.installDependencies(op) .then(() => { - this._installCompleteEmitter.fire(); + this._installReady.resolve(); updateConfig(); }) .catch(err => { let errorMsg = msgDependenciesInstallationFailed(err); op.updateStatus(azdata.TaskStatus.Failed, errorMsg); this.apiWrapper.showErrorMessage(errorMsg); - this._installCompleteEmitter.fire(errorMsg); + this._installReady.reject(errorMsg); }); } }); } else { // Python executable already exists, but the path setting wasn't defined, // so update it here - this._installCompleteEmitter.fire(); + this._installReady.resolve(); updateConfig(); } + return this._installReady.promise; + } + + public async promptForPythonInstall(): Promise { + if (!JupyterServerInstallation.isPythonInstalled(this.apiWrapper)) { + let pythonDialog = new ConfigurePythonDialog(this.apiWrapper, this.outputChannel, this); + return pythonDialog.showDialog(true); + } } private async installJupyterProsePackage(): Promise { diff --git a/extensions/notebook/src/jupyter/jupyterServerManager.ts b/extensions/notebook/src/jupyter/jupyterServerManager.ts index 6cbfd41824..f5ae01455c 100644 --- a/extensions/notebook/src/jupyter/jupyterServerManager.ts +++ b/extensions/notebook/src/jupyter/jupyterServerManager.ts @@ -20,7 +20,7 @@ import { PerNotebookServerInstance, IInstanceOptions } from './serverInstance'; export interface IServerManagerOptions { documentPath: string; - jupyterInstallation: Promise; + jupyterInstallation: JupyterServerInstallation; extensionContext: vscode.ExtensionContext; apiWrapper?: ApiWrapper; factory?: ServerInstanceFactory; @@ -62,7 +62,7 @@ export class LocalJupyterServerManager implements nb.ServerManager, vscode.Dispo this._onServerStarted.fire(); } catch (error) { - this.apiWrapper.showErrorMessage(localize('startServerFailed', 'Starting local Notebook server failed with error {0}', utils.getErrorMessage(error))); + this.apiWrapper.showErrorMessage(localize('startServerFailed', 'Starting local Notebook server failed with error: {0}', utils.getErrorMessage(error))); throw error; } } @@ -102,7 +102,8 @@ export class LocalJupyterServerManager implements nb.ServerManager, vscode.Dispo } private async doStartServer(): Promise { // We can't find or create servers until the installation is complete - let installation = await this.options.jupyterInstallation; + let installation = this.options.jupyterInstallation; + await installation.promptForPythonInstall(); // Calculate the path to use as the notebook-dir for Jupyter based on the path of the uri of the // notebook to open. This will be the workspace folder if the notebook uri is inside a workspace diff --git a/extensions/notebook/src/test/model/serverManager.test.ts b/extensions/notebook/src/test/model/serverManager.test.ts index 3f5ab6eae1..4c88fc8d8c 100644 --- a/extensions/notebook/src/test/model/serverManager.test.ts +++ b/extensions/notebook/src/test/model/serverManager.test.ts @@ -23,7 +23,7 @@ import { MockExtensionContext } from '../common/stubs'; describe('Local Jupyter Server Manager', function (): void { let expectedPath = 'my/notebook.ipynb'; let serverManager: LocalJupyterServerManager; - let deferredInstall: Deferred; + let deferredInstall: Deferred; let mockApiWrapper: TypeMoq.IMock; let mockExtensionContext: MockExtensionContext; let mockFactory: TypeMoq.IMock; @@ -33,10 +33,14 @@ describe('Local Jupyter Server Manager', function (): void { mockApiWrapper.setup(a => a.showErrorMessage(TypeMoq.It.isAny())); mockApiWrapper.setup(a => a.getWorkspacePathFromUri(TypeMoq.It.isAny())).returns(() => undefined); mockFactory = TypeMoq.Mock.ofType(ServerInstanceFactory); - deferredInstall = new Deferred(); + + deferredInstall = new Deferred(); + let mockInstall = TypeMoq.Mock.ofType(JupyterServerInstallation, undefined, undefined, '/root'); + mockInstall.setup(j => j.promptForPythonInstall()).returns(() => deferredInstall.promise); + serverManager = new LocalJupyterServerManager({ documentPath: expectedPath, - jupyterInstallation: deferredInstall.promise, + jupyterInstallation: mockInstall.object, extensionContext: mockExtensionContext, apiWrapper: mockApiWrapper.object, factory: mockFactory.object @@ -58,8 +62,8 @@ describe('Local Jupyter Server Manager', function (): void { it('Should configure and start install', async function (): Promise { // Given an install and instance that start with no issues let expectedUri = vscode.Uri.parse('http://localhost:1234?token=abcdefghijk'); - let [mockInstall, mockServerInstance] = initInstallAndInstance(expectedUri); - deferredInstall.resolve(mockInstall.object); + let mockServerInstance = initInstallAndInstance(expectedUri); + deferredInstall.resolve(); // When I start the server let notified = false; @@ -83,9 +87,9 @@ describe('Local Jupyter Server Manager', function (): void { it('Should call stop on server instance', async function (): Promise { // Given an install and instance that start with no issues let expectedUri = vscode.Uri.parse('http://localhost:1234?token=abcdefghijk'); - let [mockInstall, mockServerInstance] = initInstallAndInstance(expectedUri); + let mockServerInstance = initInstallAndInstance(expectedUri); mockServerInstance.setup(s => s.stop()).returns(() => Promise.resolve()); - deferredInstall.resolve(mockInstall.object); + deferredInstall.resolve(); // When I start and then the server await serverManager.startServer(); @@ -98,9 +102,9 @@ describe('Local Jupyter Server Manager', function (): void { it('Should call stop when extension is disposed', async function (): Promise { // Given an install and instance that start with no issues let expectedUri = vscode.Uri.parse('http://localhost:1234?token=abcdefghijk'); - let [mockInstall, mockServerInstance] = initInstallAndInstance(expectedUri); + let mockServerInstance = initInstallAndInstance(expectedUri); mockServerInstance.setup(s => s.stop()).returns(() => Promise.resolve()); - deferredInstall.resolve(mockInstall.object); + deferredInstall.resolve(); // When I start and then dispose the extension await serverManager.startServer(); @@ -111,13 +115,12 @@ describe('Local Jupyter Server Manager', function (): void { mockServerInstance.verify(s => s.stop(), TypeMoq.Times.once()); }); - function initInstallAndInstance(uri: vscode.Uri): [TypeMoq.IMock, TypeMoq.IMock] { - let mockInstall = TypeMoq.Mock.ofType(JupyterServerInstallation, undefined, undefined, '/root'); + function initInstallAndInstance(uri: vscode.Uri): TypeMoq.IMock { let mockServerInstance = TypeMoq.Mock.ofType(JupyterServerInstanceStub); mockFactory.setup(f => f.createInstance(TypeMoq.It.isAny())).returns(() => mockServerInstance.object); mockServerInstance.setup(s => s.configure()).returns(() => Promise.resolve()); mockServerInstance.setup(s => s.start()).returns(() => Promise.resolve()); mockServerInstance.setup(s => s.uri).returns(() => uri); - return [mockInstall, mockServerInstance]; + return mockServerInstance; } }); diff --git a/src/sql/parts/notebook/cellViews/output.component.ts b/src/sql/parts/notebook/cellViews/output.component.ts index 5e534b7d0d..d7366cb1f8 100644 --- a/src/sql/parts/notebook/cellViews/output.component.ts +++ b/src/sql/parts/notebook/cellViews/output.component.ts @@ -7,6 +7,7 @@ import 'vs/css!./code'; import { OnInit, Component, Input, Inject, ElementRef, ViewChild } from '@angular/core'; import { AngularDisposable } from 'sql/base/node/lifecycle'; import { nb } from 'azdata'; +import { ICellModel } from 'sql/parts/notebook/models/modelInterfaces'; import { INotebookService } from 'sql/workbench/services/notebook/common/notebookService'; import { MimeModel } from 'sql/parts/notebook/outputs/common/mimemodel'; import * as outputProcessor from 'sql/parts/notebook/outputs/common/outputProcessor'; @@ -22,6 +23,7 @@ export const OUTPUT_SELECTOR: string = 'output-component'; export class OutputComponent extends AngularDisposable implements OnInit { @ViewChild('output', { read: ElementRef }) private outputElement: ElementRef; @Input() cellOutput: nb.ICellOutput; + @Input() cellModel: ICellModel; private _trusted: boolean; private _initialized: boolean = false; private readonly _minimumHeight = 30; @@ -38,6 +40,9 @@ export class OutputComponent extends AngularDisposable implements OnInit { ngOnInit() { this.renderOutput(); this._initialized = true; + this.cellModel.notebookModel.layoutChanged(() => { + this.renderOutput(); + }); } private renderOutput() { diff --git a/src/sql/parts/notebook/cellViews/outputArea.component.html b/src/sql/parts/notebook/cellViews/outputArea.component.html index 8669d54d75..fddff39107 100644 --- a/src/sql/parts/notebook/cellViews/outputArea.component.html +++ b/src/sql/parts/notebook/cellViews/outputArea.component.html @@ -6,7 +6,7 @@ -->
- +
\ No newline at end of file diff --git a/src/sql/parts/notebook/models/cell.ts b/src/sql/parts/notebook/models/cell.ts index a1f3563d34..75d60f4e59 100644 --- a/src/sql/parts/notebook/models/cell.ts +++ b/src/sql/parts/notebook/models/cell.ts @@ -129,6 +129,10 @@ export class CellModel implements ICellModel { return this._cellUri; } + public get notebookModel(): NotebookModel { + return this.options.notebook; + } + public set cellUri(value: URI) { this._cellUri = value; } @@ -208,12 +212,12 @@ export class CellModel implements ICellModel { // TODO update source based on editor component contents let content = this.source; if (content) { - this.fireExecutionStateChanged(); let future = await kernel.requestExecute({ code: content, stop_on_error: true }, false); this.setFuture(future as FutureInternal); + this.fireExecutionStateChanged(); // For now, await future completion. Later we should just track and handle cancellation based on model notifications let result: nb.IExecuteReplyMsg = await future.done; if (result && result.content) { diff --git a/src/sql/parts/notebook/models/modelInterfaces.ts b/src/sql/parts/notebook/models/modelInterfaces.ts index 0148b75f39..d1f7679ae0 100644 --- a/src/sql/parts/notebook/models/modelInterfaces.ts +++ b/src/sql/parts/notebook/models/modelInterfaces.ts @@ -23,6 +23,7 @@ import { IStandardKernelWithProvider } from 'sql/parts/notebook/notebookUtils'; import { ConnectionProfile } from 'sql/platform/connection/common/connectionProfile'; import { ICapabilitiesService } from 'sql/platform/capabilities/common/capabilitiesService'; import { localize } from 'vs/nls'; +import { NotebookModel } from 'sql/parts/notebook/models/notebookModel'; export interface IClientSessionOptions { notebookUri: URI; @@ -449,8 +450,9 @@ export interface ICellModel { readonly outputs: ReadonlyArray; readonly onOutputsChanged: Event>; readonly onExecutionStateChange: Event; - setFuture(future: FutureInternal): void; readonly executionState: CellExecutionState; + readonly notebookModel: NotebookModel; + setFuture(future: FutureInternal): void; runCell(notificationService?: INotificationService, connectionManagementService?: IConnectionManagementService): Promise; setOverrideLanguage(language: string); equals(cellModel: ICellModel): boolean; diff --git a/src/sql/parts/notebook/notebook.component.ts b/src/sql/parts/notebook/notebook.component.ts index e324fb9f45..61b6f8a0d8 100644 --- a/src/sql/parts/notebook/notebook.component.ts +++ b/src/sql/parts/notebook/notebook.component.ts @@ -24,7 +24,7 @@ import { VIEWLET_ID, IExtensionsViewlet } from 'vs/workbench/parts/extensions/co import { CommonServiceInterface } from 'sql/services/common/commonServiceInterface.service'; import { AngularDisposable } from 'sql/base/node/lifecycle'; import { CellTypes, CellType } from 'sql/parts/notebook/models/contracts'; -import { ICellModel, IModelFactory, INotebookModel, NotebookContentChange, notebookConstants } from 'sql/parts/notebook/models/modelInterfaces'; +import { ICellModel, IModelFactory, INotebookModel, NotebookContentChange } from 'sql/parts/notebook/models/modelInterfaces'; import { IConnectionManagementService } from 'sql/platform/connection/common/connectionManagement'; import { INotebookService, INotebookParams, INotebookManager, INotebookEditor, DEFAULT_NOTEBOOK_PROVIDER, SQL_NOTEBOOK_PROVIDER } from 'sql/workbench/services/notebook/common/notebookService'; import { IBootstrapParams } from 'sql/services/bootstrap/bootstrapService'; @@ -38,8 +38,6 @@ import { KernelsDropdown, AttachToDropdown, AddCellAction, TrustedAction } from import { IObjectExplorerService } from 'sql/workbench/services/objectExplorer/common/objectExplorerService'; import * as TaskUtilities from 'sql/workbench/common/taskUtilities'; import { ISingleNotebookEditOperation } from 'sql/workbench/api/common/sqlExtHostTypes'; -import { IUntitledEditorService } from 'vs/workbench/services/untitled/common/untitledEditorService'; -import { IEditorGroupsService } from 'vs/workbench/services/group/common/editorGroupsService'; import { IConnectionDialogService } from 'sql/workbench/services/connection/common/connectionDialogService'; import { ICapabilitiesService } from 'sql/platform/capabilities/common/capabilitiesService'; import { CellMagicMapper } from 'sql/parts/notebook/models/cellMagicMapper'; @@ -254,8 +252,9 @@ export class NotebookComponent extends AngularDisposable implements OnInit, OnDe this.detectChanges(); } - private async setNotebookManager() { - for (let providerId of this._notebookParams.providers) { + private async setNotebookManager(): Promise { + let providerInfo = await this._notebookParams.providerInfo; + for (let providerId of providerInfo.providers) { let notebookManager = await this.notebookService.getOrCreateNotebookManager(providerId, this._notebookParams.notebookUri); this.notebookManagers.push(notebookManager); } @@ -265,12 +264,14 @@ export class NotebookComponent extends AngularDisposable implements OnInit, OnDe // Wait on registration for now. Long-term would be good to cache and refresh await this.notebookService.registrationComplete; // Refresh the provider if we had been using default - if (DEFAULT_NOTEBOOK_PROVIDER === this._notebookParams.providerId) { + let providerInfo = await this._notebookParams.providerInfo; + + if (DEFAULT_NOTEBOOK_PROVIDER === providerInfo.providerId) { let providers = notebookUtils.getProvidersForFileName(this._notebookParams.notebookUri.fsPath, this.notebookService); let tsqlProvider = providers.find(provider => provider === SQL_NOTEBOOK_PROVIDER); - this._notebookParams.providerId = tsqlProvider ? SQL_NOTEBOOK_PROVIDER : providers[0]; + providerInfo.providerId = tsqlProvider ? SQL_NOTEBOOK_PROVIDER : providers[0]; } - if (DEFAULT_NOTEBOOK_PROVIDER === this._notebookParams.providerId) { + if (DEFAULT_NOTEBOOK_PROVIDER === providerInfo.providerId) { // If it's still the default, warn them they should install an extension this.notificationService.prompt(Severity.Warning, localize('noKernelInstalled', 'Please install the SQL Server 2019 extension to run cells'), diff --git a/src/sql/parts/notebook/notebookEditor.ts b/src/sql/parts/notebook/notebookEditor.ts index f87535bc82..051de26f64 100644 --- a/src/sql/parts/notebook/notebookEditor.ts +++ b/src/sql/parts/notebook/notebookEditor.ts @@ -15,7 +15,7 @@ import { CancellationToken } from 'vs/base/common/cancellation'; import { NotebookInput } from 'sql/parts/notebook/notebookInput'; import { NotebookModule } from 'sql/parts/notebook/notebook.module'; import { NOTEBOOK_SELECTOR } from 'sql/parts/notebook/notebook.component'; -import { INotebookParams, DEFAULT_NOTEBOOK_PROVIDER } from 'sql/workbench/services/notebook/common/notebookService'; +import { INotebookParams } from 'sql/workbench/services/notebook/common/notebookService'; import { IStorageService } from 'vs/platform/storage/common/storage'; import { $ } from 'sql/base/browser/builder'; @@ -92,8 +92,7 @@ export class NotebookEditor extends BaseEditor { let params: INotebookParams = { notebookUri: input.notebookUri, input: input, - providerId: input.providerId ? input.providerId : DEFAULT_NOTEBOOK_PROVIDER, - providers: input.providers ? input.providers : [DEFAULT_NOTEBOOK_PROVIDER], + providerInfo: input.getProviderInfo(), isTrusted: input.isTrusted, profile: input.connectionProfile }; diff --git a/src/sql/parts/notebook/notebookInput.ts b/src/sql/parts/notebook/notebookInput.ts index 262afe02ed..200ef01205 100644 --- a/src/sql/parts/notebook/notebookInput.ts +++ b/src/sql/parts/notebook/notebookInput.ts @@ -15,7 +15,7 @@ import * as resources from 'vs/base/common/resources'; import * as azdata from 'azdata'; import { IStandardKernelWithProvider, getProvidersForFileName, getStandardKernelsForProvider } from 'sql/parts/notebook/notebookUtils'; -import { INotebookService, DEFAULT_NOTEBOOK_PROVIDER } from 'sql/workbench/services/notebook/common/notebookService'; +import { INotebookService, DEFAULT_NOTEBOOK_PROVIDER, IProviderInfo } from 'sql/workbench/services/notebook/common/notebookService'; import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; import { ITextModelService } from 'vs/editor/common/services/resolverService'; import { INotebookModel, IContentManager } from 'sql/parts/notebook/models/modelInterfaces'; @@ -29,6 +29,7 @@ import { ITextFileService, ISaveOptions } from 'vs/workbench/services/textfile/c import { LocalContentManager } from 'sql/workbench/services/notebook/node/localContentManager'; import { IConnectionProfile } from 'sql/platform/connection/common/interfaces'; import { UntitledEditorInput } from 'vs/workbench/common/editor/untitledEditorInput'; +import { IExtensionService } from 'vs/workbench/services/extensions/common/extensions'; export type ModeViewSaveHandler = (handle: number) => Thenable; @@ -140,6 +141,7 @@ export class NotebookInput extends EditorInput { private _model: NotebookEditorModel; private _untitledEditorService: IUntitledEditorService; private _contentManager: IContentManager; + private _providersLoaded: Promise; constructor(private _title: string, private resource: URI, @@ -147,13 +149,14 @@ export class NotebookInput extends EditorInput { @ITextModelService private textModelService: ITextModelService, @IUntitledEditorService untitledEditorService: IUntitledEditorService, @IInstantiationService private instantiationService: IInstantiationService, - @INotebookService private notebookService: INotebookService + @INotebookService private notebookService: INotebookService, + @IExtensionService private extensionService: IExtensionService ) { super(); this._untitledEditorService = untitledEditorService; this.resource = resource; this._standardKernels = []; - this.assignProviders(); + this._providersLoaded = this.assignProviders(); } public get textInput(): UntitledEditorInput { @@ -186,14 +189,13 @@ export class NotebookInput extends EditorInput { return this._title; } - public get providerId(): string { - return this._providerId; + public async getProviderInfo(): Promise { + await this._providersLoaded; + return { + providerId: this._providerId ? this._providerId : DEFAULT_NOTEBOOK_PROVIDER, + providers: this._providers ? this._providers : [DEFAULT_NOTEBOOK_PROVIDER] + }; } - - public set providerId(value: string) { - this._providerId = value; - } - public get isTrusted(): boolean { return this._isTrusted; } @@ -214,14 +216,6 @@ export class NotebookInput extends EditorInput { return this._standardKernels; } - public get providers(): string[] { - return this._providers; - } - - public set providers(value: string[]) { - this._providers = value; - } - public save(): TPromise { let options: ISaveOptions = { force: false }; return this._model.save(options); @@ -280,7 +274,8 @@ export class NotebookInput extends EditorInput { } } - private assignProviders(): void { + private async assignProviders(): Promise { + await this.extensionService.whenInstalledExtensionsRegistered(); let providerIds: string[] = getProvidersForFileName(this._title, this.notebookService); if (providerIds && providerIds.length > 0) { this._providerId = providerIds.filter(provider => provider !== DEFAULT_NOTEBOOK_PROVIDER)[0]; diff --git a/src/sql/parts/notebook/notebookStyles.ts b/src/sql/parts/notebook/notebookStyles.ts index 08574b1c8c..bf16992651 100644 --- a/src/sql/parts/notebook/notebookStyles.ts +++ b/src/sql/parts/notebook/notebookStyles.ts @@ -42,6 +42,10 @@ export function registerNotebookThemes(overrideEditorThemeSetting: boolean): IDi box-shadow: 0; } + .notebookEditor .notebook-cell.active:hover { + border-color: ${activeBorder}; + } + .notebookEditor .notebook-cell:hover:not(.active) { box-shadow: ${lightBoxShadow}; } @@ -98,13 +102,14 @@ export function registerNotebookThemes(overrideEditorThemeSetting: boolean): IDi // Standard notebook cell behavior collector.addRule(` .notebookEditor .notebook-cell { - border-color: ${inactiveBorder}; + border-color: transparent; border-width: 1px; } .notebookEditor .notebook-cell.active { border-width: 1px; } .notebookEditor .notebook-cell:hover { + border-color: ${inactiveBorder}; border-width: 1px; } `); diff --git a/src/sql/parts/objectExplorer/viewlet/serverTreeView.ts b/src/sql/parts/objectExplorer/viewlet/serverTreeView.ts index 8947bf13cd..453e54f2f2 100644 --- a/src/sql/parts/objectExplorer/viewlet/serverTreeView.ts +++ b/src/sql/parts/objectExplorer/viewlet/serverTreeView.ts @@ -32,6 +32,7 @@ import { TreeNode, TreeItemCollapsibleState } from 'sql/parts/objectExplorer/com import { SERVER_GROUP_CONFIG, SERVER_GROUP_AUTOEXPAND_CONFIG } from 'sql/parts/objectExplorer/serverGroupDialog/serverGroup.contribution'; import { IErrorMessageService } from 'sql/platform/errorMessage/common/errorMessageService'; import { ServerTreeActionProvider } from 'sql/parts/objectExplorer/viewlet/serverTreeActionProvider'; +import { ICapabilitiesService } from 'sql/platform/capabilities/common/capabilitiesService'; const $ = builder.$; @@ -54,7 +55,8 @@ export class ServerTreeView { @IObjectExplorerService private _objectExplorerService: IObjectExplorerService, @IThemeService private _themeService: IThemeService, @IErrorMessageService private _errorMessageService: IErrorMessageService, - @IConfigurationService private _configurationService: IConfigurationService + @IConfigurationService private _configurationService: IConfigurationService, + @ICapabilitiesService capabilitiesService: ICapabilitiesService ) { this._activeConnectionsFilterAction = this._instantiationService.createInstance( ActiveConnectionsFilterAction, @@ -64,6 +66,12 @@ export class ServerTreeView { this._treeSelectionHandler = this._instantiationService.createInstance(TreeSelectionHandler); this._onSelectionOrFocusChange = new Emitter(); this._actionProvider = this._instantiationService.createInstance(ServerTreeActionProvider); + capabilitiesService.onCapabilitiesRegistered(() => { + if (this._connectionManagementService.hasRegisteredServers()) { + this.refreshTree(); + this._treeSelectionHandler.onTreeActionStateChange(false); + } + }); } /** diff --git a/src/sql/workbench/api/node/mainThreadNotebookDocumentsAndEditors.ts b/src/sql/workbench/api/node/mainThreadNotebookDocumentsAndEditors.ts index b26053b327..8532c4c4fc 100644 --- a/src/sql/workbench/api/node/mainThreadNotebookDocumentsAndEditors.ts +++ b/src/sql/workbench/api/node/mainThreadNotebookDocumentsAndEditors.ts @@ -22,8 +22,8 @@ import { SqlMainContext, MainThreadNotebookDocumentsAndEditorsShape, SqlExtHostContext, ExtHostNotebookDocumentsAndEditorsShape, INotebookDocumentsAndEditorsDelta, INotebookEditorAddData, INotebookShowOptions, INotebookModelAddedData, INotebookModelChangedData } from 'sql/workbench/api/node/sqlExtHost.protocol'; -import { NotebookInput, NotebookEditorModel } from 'sql/parts/notebook/notebookInput'; -import { INotebookService, INotebookEditor, DEFAULT_NOTEBOOK_PROVIDER } from 'sql/workbench/services/notebook/common/notebookService'; +import { NotebookInput } from 'sql/parts/notebook/notebookInput'; +import { INotebookService, INotebookEditor, IProviderInfo } from 'sql/workbench/services/notebook/common/notebookService'; import { TPromise } from 'vs/base/common/winjs.base'; import { ISingleNotebookEditOperation } from 'sql/workbench/api/common/sqlExtHostTypes'; import { disposed } from 'vs/base/common/errors'; @@ -37,6 +37,7 @@ import { UntitledEditorInput } from 'vs/workbench/common/editor/untitledEditorIn class MainThreadNotebookEditor extends Disposable { private _contentChangedEmitter = new Emitter(); public readonly contentChanged: Event = this._contentChangedEmitter.event; + private _providerInfo: IProviderInfo; constructor(public readonly editor: INotebookEditor) { super(); @@ -49,6 +50,9 @@ class MainThreadNotebookEditor extends Disposable { this._contentChangedEmitter.fire(changeEvent); })); }); + editor.notebookParams.providerInfo.then(info => { + this._providerInfo = info; + }); } public get uri(): URI { @@ -64,11 +68,11 @@ class MainThreadNotebookEditor extends Disposable { } public get providerId(): string { - return this.editor.notebookParams.providerId; + return this._providerInfo ? this._providerInfo.providerId : undefined; } public get providers(): string[] { - return this.editor.notebookParams.providers; + return this._providerInfo ? this._providerInfo.providers : []; } public get cells(): ICellModel[] { diff --git a/src/sql/workbench/services/notebook/common/notebookService.ts b/src/sql/workbench/services/notebook/common/notebookService.ts index 1ccb4ee099..636b57a322 100644 --- a/src/sql/workbench/services/notebook/common/notebookService.ts +++ b/src/sql/workbench/services/notebook/common/notebookService.ts @@ -85,11 +85,14 @@ export interface INotebookManager { readonly serverManager: azdata.nb.ServerManager; } +export interface IProviderInfo { + providerId: string; + providers: string[]; +} export interface INotebookParams extends IBootstrapParams { notebookUri: URI; input: NotebookInput; - providerId: string; - providers: string[]; + providerInfo: Promise; isTrusted: boolean; profile?: IConnectionProfile; modelFactory?: ModelFactory; diff --git a/src/sqltest/parts/connection/connectionTreeActions.test.ts b/src/sqltest/parts/connection/connectionTreeActions.test.ts index e512234405..949b68243a 100644 --- a/src/sqltest/parts/connection/connectionTreeActions.test.ts +++ b/src/sqltest/parts/connection/connectionTreeActions.test.ts @@ -205,7 +205,7 @@ suite('SQL Connection Tree Action tests', () => { return new TPromise((resolve) => resolve({})); }); - let serverTreeView = TypeMoq.Mock.ofType(ServerTreeView, TypeMoq.MockBehavior.Strict, undefined, instantiationService.object, undefined, undefined, undefined); + let serverTreeView = TypeMoq.Mock.ofType(ServerTreeView, TypeMoq.MockBehavior.Strict, undefined, instantiationService.object, undefined, undefined, undefined, undefined, capabilitiesService); serverTreeView.setup(x => x.showFilteredTree(TypeMoq.It.isAnyString())); serverTreeView.setup(x => x.refreshTree()); let connectionTreeAction: ActiveConnectionsFilterAction = new ActiveConnectionsFilterAction(ActiveConnectionsFilterAction.ID, ActiveConnectionsFilterAction.LABEL, serverTreeView.object, connectionManagementService.object); @@ -222,7 +222,7 @@ suite('SQL Connection Tree Action tests', () => { return new TPromise((resolve) => resolve({})); }); - let serverTreeView = TypeMoq.Mock.ofType(ServerTreeView, TypeMoq.MockBehavior.Strict, undefined, instantiationService.object, undefined, undefined, undefined); + let serverTreeView = TypeMoq.Mock.ofType(ServerTreeView, TypeMoq.MockBehavior.Strict, undefined, instantiationService.object, undefined, undefined, undefined, undefined, capabilitiesService); serverTreeView.setup(x => x.showFilteredTree(TypeMoq.It.isAnyString())); serverTreeView.setup(x => x.refreshTree()); let connectionTreeAction: ActiveConnectionsFilterAction = new ActiveConnectionsFilterAction(ActiveConnectionsFilterAction.ID, ActiveConnectionsFilterAction.LABEL, serverTreeView.object, connectionManagementService.object); @@ -240,7 +240,7 @@ suite('SQL Connection Tree Action tests', () => { return new TPromise((resolve) => resolve({})); }); - let serverTreeView = TypeMoq.Mock.ofType(ServerTreeView, TypeMoq.MockBehavior.Strict, undefined, instantiationService.object, undefined, undefined, undefined); + let serverTreeView = TypeMoq.Mock.ofType(ServerTreeView, TypeMoq.MockBehavior.Strict, undefined, instantiationService.object, undefined, undefined, undefined, undefined, capabilitiesService); serverTreeView.setup(x => x.showFilteredTree(TypeMoq.It.isAnyString())); serverTreeView.setup(x => x.refreshTree()); let connectionTreeAction: RecentConnectionsFilterAction = new RecentConnectionsFilterAction(RecentConnectionsFilterAction.ID, RecentConnectionsFilterAction.LABEL, serverTreeView.object, connectionManagementService.object); @@ -257,7 +257,7 @@ suite('SQL Connection Tree Action tests', () => { return new TPromise((resolve) => resolve({})); }); - let serverTreeView = TypeMoq.Mock.ofType(ServerTreeView, TypeMoq.MockBehavior.Strict, undefined, instantiationService.object, undefined, undefined, undefined); + let serverTreeView = TypeMoq.Mock.ofType(ServerTreeView, TypeMoq.MockBehavior.Strict, undefined, instantiationService.object, undefined, undefined, undefined, undefined, capabilitiesService); serverTreeView.setup(x => x.showFilteredTree(TypeMoq.It.isAnyString())); serverTreeView.setup(x => x.refreshTree()); let connectionTreeAction: RecentConnectionsFilterAction = new RecentConnectionsFilterAction(RecentConnectionsFilterAction.ID, RecentConnectionsFilterAction.LABEL, serverTreeView.object, connectionManagementService.object); diff --git a/src/sqltest/parts/registeredServer/viewlet/serverTreeView.test.ts b/src/sqltest/parts/registeredServer/viewlet/serverTreeView.test.ts index b24c927e3d..1cd2e8c273 100644 --- a/src/sqltest/parts/registeredServer/viewlet/serverTreeView.test.ts +++ b/src/sqltest/parts/registeredServer/viewlet/serverTreeView.test.ts @@ -14,18 +14,21 @@ import { TestInstantiationService } from 'vs/platform/instantiation/test/common/ import { TestStorageService } from 'vs/workbench/test/workbenchTestServices'; import * as TypeMoq from 'typemoq'; +import { CapabilitiesTestService } from 'sqltest/stubs/capabilitiesTestService'; suite('ServerTreeView onAddConnectionProfile handler tests', () => { let serverTreeView: ServerTreeView; let mockTree: TypeMoq.Mock; let mockRefreshTreeMethod: TypeMoq.Mock; + let capabilitiesService = new CapabilitiesTestService(); setup(() => { let instantiationService = new TestInstantiationService(); let mockConnectionManagementService = TypeMoq.Mock.ofType(ConnectionManagementService, TypeMoq.MockBehavior.Strict, {}, {}, new TestStorageService()); mockConnectionManagementService.setup(x => x.getConnectionGroups()).returns(x => []); - serverTreeView = new ServerTreeView(mockConnectionManagementService.object, instantiationService, undefined, undefined, undefined, undefined); + mockConnectionManagementService.setup(x => x.hasRegisteredServers()).returns(() => true); + serverTreeView = new ServerTreeView(mockConnectionManagementService.object, instantiationService, undefined, undefined, undefined, undefined, capabilitiesService); let tree = { clearSelection() { }, getSelection() { }, @@ -91,4 +94,9 @@ suite('ServerTreeView onAddConnectionProfile handler tests', () => { mockTree.verify(x => x.clearSelection(), TypeMoq.Times.never()); mockTree.verify(x => x.select(TypeMoq.It.isAny()), TypeMoq.Times.never()); }); + + test('The tree refreshes when new capabilities are registered', () => { + capabilitiesService.fireCapabilitiesRegistered(undefined); + mockRefreshTreeMethod.verify(x => x(), TypeMoq.Times.once()); + }); }); diff --git a/src/sqltest/stubs/capabilitiesTestService.ts b/src/sqltest/stubs/capabilitiesTestService.ts index d0ca7ae377..4c7eb386a0 100644 --- a/src/sqltest/stubs/capabilitiesTestService.ts +++ b/src/sqltest/stubs/capabilitiesTestService.ts @@ -138,6 +138,10 @@ export class CapabilitiesTestService implements ICapabilitiesService { return Promise.resolve(null); } + public fireCapabilitiesRegistered(providerFeatures: ProviderFeatures): void { + this._onCapabilitiesRegistered.fire(providerFeatures); + } + private _onCapabilitiesRegistered = new Emitter(); public readonly onCapabilitiesRegistered = this._onCapabilitiesRegistered.event; } diff --git a/src/vs/workbench/browser/parts/titlebar/menubarControl.ts b/src/vs/workbench/browser/parts/titlebar/menubarControl.ts index bcab028d0a..724748f53c 100644 --- a/src/vs/workbench/browser/parts/titlebar/menubarControl.ts +++ b/src/vs/workbench/browser/parts/titlebar/menubarControl.ts @@ -203,38 +203,40 @@ export class MenubarControl extends Disposable { } private detectAndRecommendCustomTitlebar(): void { - if (!isLinux) { - return; - } + // {{SQL CARBON EDIT}} - Disable the custom titlebar recommendation + // if (!isLinux) { + // return; + // } - if (!this.storageService.getBoolean('menubar/electronFixRecommended', StorageScope.GLOBAL, false)) { - if (this.currentMenubarVisibility === 'hidden' || this.currentTitlebarStyleSetting === 'custom') { - // Issue will not arise for user, abort notification - return; - } + // if (!this.storageService.getBoolean('menubar/electronFixRecommended', StorageScope.GLOBAL, false)) { + // if (this.currentMenubarVisibility === 'hidden' || this.currentTitlebarStyleSetting === 'custom') { + // // Issue will not arise for user, abort notification + // return; + // } - const message = nls.localize('menubar.electronFixRecommendation', "If you experience hard to read text in the menu bar, we recommend trying out the custom title bar."); - this.notificationService.prompt(Severity.Info, message, [ - { - label: nls.localize('goToSetting', "Open Settings"), - run: () => { - return this.preferencesService.openGlobalSettings(undefined, { query: 'window.titleBarStyle' }); - } - }, - { - label: nls.localize('moreInfo', "More Info"), - run: () => { - window.open('https://go.microsoft.com/fwlink/?linkid=2038566'); - } - }, - { - label: nls.localize('neverShowAgain', "Don't Show Again"), - run: () => { - this.storageService.store('menubar/electronFixRecommended', true, StorageScope.GLOBAL); - } - } - ]); - } + // const message = nls.localize('menubar.electronFixRecommendation', "If you experience hard to read text in the menu bar, we recommend trying out the custom title bar."); + // this.notificationService.prompt(Severity.Info, message, [ + // { + // label: nls.localize('goToSetting', "Open Settings"), + // run: () => { + // return this.preferencesService.openGlobalSettings(undefined, { query: 'window.titleBarStyle' }); + // } + // }, + // { + // label: nls.localize('moreInfo', "More Info"), + // run: () => { + // window.open('https://go.microsoft.com/fwlink/?linkid=2038566'); + // } + // }, + // { + // label: nls.localize('neverShowAgain', "Don't Show Again"), + // run: () => { + // this.storageService.store('menubar/electronFixRecommended', true, StorageScope.GLOBAL); + // } + // } + // ]); + // } + // {{SQL CARBON EDIT}} - End } private registerListeners(): void {