Compare commits

..

46 Commits
1.3.7 ... 1.3.9

Author SHA1 Message Date
kisantia
cacf0777c7 Moving onValidityChanged listener to showPage() so that it gets added to pages that are added to the wizard after the initial start up (#3691) 2019-01-14 15:01:54 -08:00
Karl Burtram
cb433fbeac Bump Azure Data Studio to 1.3.9 2019-01-14 14:24:28 -08:00
Kevin Cunnane
8f6736df5e Fix #3736 Notebook: cannot connect to SQL big data cluster due to empty config.json file (#3738)
- Writing the config file in the core for now, will look to move to the extension in Feb release
2019-01-14 14:19:45 -08:00
Anthony Dresser
ef76d730e3 fix html formatting in grid (#3722) 2019-01-14 14:15:25 -08:00
Karl Burtram
aabbeeffa9 Add connection dialog icon dark theme and HC styles (#3721) 2019-01-14 14:13:43 -08:00
Anthony Dresser
7a513de543 Duplicate Result sets (fix merge conflicts) 2019-01-14 14:12:29 -08:00
Karl Burtram
abbd5ec037 Add Idera extension to recommendation list (#3709) 2019-01-14 14:09:08 -08:00
Karl Burtram
84009f65ec Save grid selection/vertical scroll when switching tabs 2019-01-08 15:37:20 -08:00
kisantia
191648df0a Fix database not getting set correctly in DacFx wizard deploy scenario (#3641)
* fix db not getting set correctly for deploy scenario if coming from import page

* removed space so that comments go directly after //
2019-01-07 12:06:20 -08:00
Matt Irvine
1265a56ff4 Update edit data for result set streaming changes (#3634) 2019-01-07 12:06:11 -08:00
Aditya Bist
2171d44922 removed potentially PII (#3619) 2018-12-12 14:13:50 -08:00
Anthony Dresser
812f315e69 Account for different situations for stream setting (#3615)
* add cases for different situation

* default streaming setting false
2018-12-12 12:12:18 -08:00
Karl Burtram
d48258a0fb Turn off "something went wrong" message (#3606) 2018-12-11 16:19:23 -08:00
Karl Burtram
8ec78f67af Merge branch 'master' into release/1.3 2018-12-11 15:10:32 -08:00
Anthony Dresser
0ac0175bb1 update table size on result set update (#3604) 2018-12-11 15:05:28 -08:00
Anthony Dresser
f39007cd2d wrong variable name (#3603) 2018-12-11 15:05:11 -08:00
Karl Burtram
348b03327e Merge branch 'master' into release/1.3 2018-12-11 11:29:01 -08:00
Yurong He
2349aa4df8 Fixed #3596 by change URI.parse to URI.file to get the path (#3597) 2018-12-11 11:23:29 -08:00
Karl Burtram
6497bbcc38 Merge branch 'master' into release/1.3 2018-12-10 17:39:23 -08:00
Yurong He
a93a173183 The CSS class is overwritten by the previous change. Add it back (#3583) 2018-12-10 17:36:58 -08:00
Karl Burtram
a1abca26df Remove a style that is breaking the "Clear MRU" list (#3587) 2018-12-10 17:36:55 -08:00
Anthony Dresser
42e55dd2dd Result Streaming settings (#3537)
* add setting control for result streaming

* change default result streaming to true
2018-12-10 17:36:35 -08:00
Raj
ca3146d38f Filetype while prmopting for save #3552 (#3575) 2018-12-10 17:02:16 -08:00
Alan Ren
7f6cd514a5 Alanren/profiler search (#3525)
* further improve the search experience for profiler

* change the default value for parameter
2018-12-10 16:43:58 -08:00
Aditya Bist
88e24e92b5 Agent: features and suggestions (#3512)
* removed row highlighting overlap with scrollbars

* fixed more styling suggestions

* made async calls parallel and improved UI

* cleared style
2018-12-10 16:36:41 -08:00
Anthony Dresser
8b447e361f change cancelation in the async data loader to correctly cancel requests (#3516) 2018-12-10 16:29:02 -08:00
Chris LaFreniere
a92dd2d4e4 Fix for PySpark3 not being selected by default (#3554) 2018-12-10 16:26:57 -08:00
Karl Burtram
852ec44567 Fix DataTier wizard null ref looking up provider with no active connection (#3528) 2018-12-10 16:26:06 -08:00
Raj
b6e32cdeb4 Rename notebook editor (fixes #3521) (#3536)
* Rename notebook editor #3521

* Review comments #3521
2018-12-10 13:26:29 -08:00
Alan Yu
4bd264d9be Add feature request template (#3487)
* Add feature request template

Github recently added feature for issue templates to also automatically assign a label. By setting this template, we can have some guidance for users who want to ask a feature request instead of an issue.

We can also have this be the default within the product when a user clicks "request a missing feature" on the smiley face button

* update label
2018-12-10 11:40:39 -08:00
Karl Burtram
4a4b8574d0 Update Azure Data Studio to 1.3.8 2018-12-10 11:30:20 -08:00
Yurong He
ded073edd9 Added clear output to ToggleMoreAction and added it to markdown preview (#3535)
* Added toggleMoreActions to Markdown Preview only.
When it is in editor mode, only editor display ToggleMoreActions.

* Added clear output back
2018-12-10 11:17:35 -08:00
Alan Yu
568f95e7a3 Update SQL Server Import readme extension (#3519)
Added DacFx wizard text
2018-12-10 10:42:13 -08:00
Chris LaFreniere
5adcabc8de Add back Notebook Completion List IntelliSense (#3520)
* Static notebook intellisense working

* Intellisense dynamic cells working

* Remove launch.json erroneous change

* PR comments #1

* PR feedback to change a minor condition
2018-12-07 18:04:32 -08:00
Kevin Cunnane
e3bce7172c Handle delayed Notebook provider registration (#3526)
* Handle delayed Notebook provider registration
- Fixes #3197 Notebooks: builtin provider always used on reopen with notebook file visible
- Fixes #3414 Can't refresh kernel after connect to big data cluster

There are 3 parts to this fix:
- If no notebook provider other than the default is installed, we warn users and prompt to install the SQL2019 extension
- We wait on the extension host registration to complete before determining which provider to use
- We know that the extension registration of the provider instance will be after package.json is read, so if we wait after registration for 10 seconds to give this a chance to happen before returning a provider to the front end

* Remove launch.json change that was added accidentally

* Fix timeout not being the expected value

* Removed console log left in during debugging

* Remove unnecessary whitespace

* Fix unit test failure

* Name the registration better, and remove outdated comments
2018-12-07 17:56:21 -08:00
Raj
96fb618390 Notebook saves are broken #3432 (#3478)
* Notebook saves are broken #3432

* Misc change

* Save notebook uri to This

* Untitled notebook save including review comments #3432

* Cleanup

* Misc changes
2018-12-07 16:27:23 -08:00
Alan Yu
2d4fdcb661 Updated SQL Server Import extension readme
Added Data-Tier Application Wizard
2018-12-07 15:26:48 -08:00
Yurong He
7a84cff5b4 Fixed #3508 by removing the fixed height of toolbar (#3518) 2018-12-07 15:16:53 -08:00
Yurong He
2af627b704 Fixed #3497 (#3517) 2018-12-07 14:16:00 -08:00
Alan Ren
77fdf18686 improve the visual effect for selected card (#3509)
* improve the visual effect for selected card

* remove shadow for unselected card as per Smitha's suggestion

* fix the issue of status icon not changing when new theme selected
2018-12-07 14:13:52 -08:00
Yurong He
944a77fe42 Fixed #3287 adding loading-spinner to markdown cell (#3505) 2018-12-07 13:07:00 -08:00
Chris LaFreniere
049678b32e Change notebook width to 100 (#3423) 2018-12-07 12:22:19 -08:00
Kevin Cunnane
3325e4d854 Fix #3422 Notebooks opened from within ADS should be Trusted by default. (#3498) 2018-12-07 12:14:57 -08:00
Kevin Cunnane
1e90e88d4b Fix #3481 Notebook: Markdown coloring appears incorrect (#3499)
- Set markdown as language for markdown cell
- Fix issue where after loading language from cell metadata, always override it. This made the "language" feature irrelevant in the cell.
- Fixed tests with new behavior (assumption: cell-level language overrides notebook-level definition) and added new test to cover this too
2018-12-07 12:14:43 -08:00
Kevin Cunnane
8aeb33c98c Fix #3470 Notebook: Switching between Servers and File Explorer opens a duplicate notebook (#3500)
- We missed implementing matches functionality. This is required in order to skip reopening of the notebook
2018-12-07 12:14:26 -08:00
Yurong He
3b08721835 Fixed #3415 add padding-left/right 8px (#3462)
* Fixed #3415 add padding-left/right 8px

* We will keep CSS consistent for review and editing mode. So removed codes not used.
2018-12-07 09:02:20 -08:00
64 changed files with 1388 additions and 602 deletions

View File

@@ -1,6 +1,10 @@
---
name: Bug report
about: Create a report to help us improve
title: ''
labels: ''
assignees: ''
---
<!-- Please search existing issues to avoid creating duplicates. -->

View File

@@ -0,0 +1,20 @@
---
name: Feature request
about: Suggest an idea for this project
title: ''
labels: feature request
assignees: ''
---
**Is your feature request related to a problem? Please describe.**
A clear and concise description of what the problem is. Ex. I'm always frustrated when [...]
**Describe the solution or feature you'd like**
A clear and concise description of what you want to happen.
**Describe alternatives you've considered**
A clear and concise description of any alternative solutions or features you've considered.
**Additional context**
Add any other context or screenshots about the feature request here.

View File

@@ -69,7 +69,7 @@ export class AlertData implements IAgentDialogData {
this.eventDescriptionKeyword = alertInfo.eventDescriptionKeyword;
this.eventSource = alertInfo.eventSource;
this.hasNotification = alertInfo.hasNotification;
this.includeEventDescription = alertInfo.includeEventDescription.toString();
this.includeEventDescription = alertInfo.includeEventDescription ? alertInfo.includeEventDescription.toString() : null;
this.isEnabled = alertInfo.isEnabled;
this.jobId = alertInfo.jobId;
this.lastOccurrenceDate = alertInfo.lastOccurrenceDate;
@@ -82,7 +82,7 @@ export class AlertData implements IAgentDialogData {
this.databaseName = alertInfo.databaseName;
this.countResetDate = alertInfo.countResetDate;
this.categoryName = alertInfo.categoryName;
this.alertType = alertInfo.alertType.toString();
this.alertType = alertInfo.alertType ? alertInfo.alertType.toString() : null;
this.wmiEventNamespace = alertInfo.wmiEventNamespace;
this.wmiEventQuery = alertInfo.wmiEventQuery;
}

View File

@@ -1,9 +1,50 @@
# Microsoft SQL Server Import for Azure Data Studio
Microsoft SQL Server Import for Azure Data Studio is a simple way to copy data from a flat file (.csv, .txt, .json) to a SQL Server table. Checkout below the reasons for using the Import Flat File wizard, how to find this wizard, and a simple example.
Microsoft SQL Server Import for Azure Data Studio includes two wizards:
- [Import Flat File Wizard](#import-flat-file-wizard-preview)
- [Data-tier Application Wizard.](#data-tier-application-wizard-preview)
## Import Flat File Wizard *(preview)*
**The Import Flat File Wizard** is a simple way to copy data from a flat file (.csv, .txt, .json) to a SQL Server table. Checkout below the reasons for using the Import Flat File wizard, how to find this wizard, and a simple example.
This experience is currently in its initial preview. Please report issues and feature requests [here.](https://github.com/microsoft/azuredatastudio/issues)
<img src="https://user-images.githubusercontent.com/30873802/43433347-c958ed28-942b-11e8-8bbc-f4f2529c3978.png" width="800px" />
### Requirements
* This wizard requires an active connection to a SQL Server instance to start.
* This wizard only works on .txt and .csv files.
### How do I start the Import Flat File wizard?
* The main entry point for the wizard is to right click a database in the Object Explorer, and click **Import wizard**.
* If a user is connected to a SQL Server instance, the user can also press **Ctrl**+**I** to start the wizard.
### Why would I use the Import Flat File wizard?
This wizard was created to improve the current import experience leveraging an intelligent framework known as Program Synthesis using Examples ([PROSE](https://microsoft.github.io/prose/)). For a user without specialized domain knowledge, importing data can often be a complex, error prone, and tedious task. This wizard streamlines the import process as simple as selecting an input file and unique table name, and the PROSE framework handles the rest.
PROSE analyzes data patterns in your input file to infer column names, types, delimiters, and more. This framework learns the structure of the file and does all of the hard work so users don't have to.
Please note that the PROSE binary components used by this extension are licensed under the [MICROSOFT SQL TOOLS IMPORT FLAT FILE EULA](https://raw.githubusercontent.com/Microsoft/azuredatastudio/master/extensions/import/Microsoft_SQL_Server_Import_Extension_and_Tools_Import_Flat_File_Preview.docx).
## Data-tier Application Wizard *(preview)*
**The Data-tier Application Wizard** provides an easy to use experience to deploy and extract .dacpac files and import and export .bacpac files.
This experience is currently in its initial preview. Please report issues and feature requests [here.](https://github.com/microsoft/azuredatastudio/issues)
<img src="https://user-images.githubusercontent.com/30873802/49676289-f2df6880-fa2d-11e8-8bfa-6213b7734075.png" width="800px" />
### Requirements
* This wizard requires an active connection to a SQL Server instance to start.
### How do I start the Data-tier Application wizard?
* The main entry point for the wizard is to right click a database in the Object Explorer, and click **Data-tier Application wizard**.
* If a user is connected to a SQL Server instance, the user can also start the wizard from the command palette (Ctrl+Shift+P) by searching for **Data-tier Application wizard.**
### Why would I use the Data-tier Application wizard?
This wizard was created to add the ability to extract and deploy .dacpac files and import and export .bacpac files in Azure Data Studio.
To learn more about Data-Tier Applications and working with dacpac and bacpac files, [you can read more here.](https://docs.microsoft.com/en-us/sql/relational-databases/data-tier-applications/data-tier-applications?view=sql-server-2017)
## License
Copyright (c) Microsoft Corporation. All rights reserved.
@@ -12,21 +53,6 @@ Licensed under the [MICROSOFT SQL SERVER IMPORT EXTENSION EULA](https://raw.gith
> Note: Microsoft SQL Server Import for Azure Data Studio extension contains the Microsoft SQL Tools Import Flat File component which is also licensed under the above EULA.
## Requirements
* This wizard requires an active connection to a SQL Server instance to start.
* This wizard only works on .txt and .csv files.
## How do I start the Flat File Import wizard?
* The main entry point for the wizard is to right click a database in the Object Explorer, and click **Import wizard**.
* If a user is connected to a SQL Server instance, the user can also press **Ctrl**+**I** to start the wizard.
## Why would I use the Flat File Import wizard?
This wizard was created to improve the current import experience leveraging an intelligent framework known as Program Synthesis using Examples ([PROSE](https://microsoft.github.io/prose/)). For a user without specialized domain knowledge, importing data can often be a complex, error prone, and tedious task. This wizard streamlines the import process as simple as selecting an input file and unique table name, and the PROSE framework handles the rest.
PROSE analyzes data patterns in your input file to infer column names, types, delimiters, and more. This framework learns the structure of the file and does all of the hard work so users don't have to.
Please note that the PROSE binary components used by this extension are licensed under the [MICROSOFT SQL TOOLS IMPORT FLAT FILE EULA](https://raw.githubusercontent.com/Microsoft/azuredatastudio/master/extensions/import/Microsoft_SQL_Server_Import_Extension_and_Tools_Import_Flat_File_Preview.docx).
## Code of Conduct
This project has adopted the [Microsoft Open Source Code of Conduct](https://opensource.microsoft.com/codeofconduct/). For more information see the [Code of Conduct FAQ](https://opensource.microsoft.com/codeofconduct/faq/) or contact [opencode@microsoft.com](mailto:opencode@microsoft.com) with any additional questions or comments.

View File

@@ -202,7 +202,7 @@ export class DataTierApplicationWizard {
}
private async deploy() {
let service = await DataTierApplicationWizard.getService();
let service = await DataTierApplicationWizard.getService(this.model.server.providerName);
let ownerUri = await sqlops.connection.getUriForConnection(this.model.server.connectionId);
let result = await service.deployDacpac(this.model.filePath, this.model.database, this.model.upgradeExisting, ownerUri, sqlops.TaskExecutionMode.execute);
@@ -213,7 +213,7 @@ export class DataTierApplicationWizard {
}
private async extract() {
let service = await DataTierApplicationWizard.getService();
let service = await DataTierApplicationWizard.getService(this.model.server.providerName);
let ownerUri = await sqlops.connection.getUriForConnection(this.model.server.connectionId);
let result = await service.extractDacpac(this.model.database, this.model.filePath, this.model.database, this.model.version, ownerUri, sqlops.TaskExecutionMode.execute);
@@ -224,7 +224,7 @@ export class DataTierApplicationWizard {
}
private async export() {
let service = await DataTierApplicationWizard.getService();
let service = await DataTierApplicationWizard.getService(this.model.server.providerName);
let ownerUri = await sqlops.connection.getUriForConnection(this.model.server.connectionId);
let result = await service.exportBacpac(this.model.database, this.model.filePath, ownerUri, sqlops.TaskExecutionMode.execute);
@@ -235,7 +235,7 @@ export class DataTierApplicationWizard {
}
private async import() {
let service = await DataTierApplicationWizard.getService();
let service = await DataTierApplicationWizard.getService(this.model.server.providerName);
let ownerUri = await sqlops.connection.getUriForConnection(this.model.server.connectionId);
let result = await service.importBacpac(this.model.filePath, this.model.database, ownerUri, sqlops.TaskExecutionMode.execute);
@@ -245,9 +245,8 @@ export class DataTierApplicationWizard {
}
}
public static async getService(): Promise<sqlops.DacFxServicesProvider> {
let currentConnection = await sqlops.connection.getCurrentConnection();
let service = sqlops.dataprotocol.getProvider<sqlops.DacFxServicesProvider>(currentConnection.providerName, sqlops.DataProviderType.DacFxServicesProvider);
private static async getService(providerName: string): Promise<sqlops.DacFxServicesProvider> {
let service = sqlops.dataprotocol.getProvider<sqlops.DacFxServicesProvider>(providerName, sqlops.DataProviderType.DacFxServicesProvider);
return service;
}
}

View File

@@ -32,7 +32,7 @@ export class DeployConfigPage extends DacFxConfigPage {
}
async start(): Promise<boolean> {
let serverComponent = await this.createServerDropdown(true);
let serverComponent = await this.createServerDropdown(true);
let fileBrowserComponent = await this.createFileBrowser();
this.databaseComponent = await this.createDatabaseTextBox();
this.databaseComponent.title = localize('dacFx.databaseNameTextBox', 'Database Name');
@@ -131,7 +131,7 @@ export class DeployConfigPage extends DacFxConfigPage {
this.model.database = this.databaseTextBox.value;
});
// Initialize with upgrade existing true
//Initialize with upgrade existing true
upgradeRadioButton.checked = true;
this.model.upgradeExisting = true;
@@ -149,10 +149,10 @@ export class DeployConfigPage extends DacFxConfigPage {
}
protected async createDeployDatabaseDropdown(): Promise<sqlops.FormComponent> {
this.databaseDropdown = this.view.modelBuilder.dropDown().withProperties({
this.databaseDropdown = this.view.modelBuilder.dropDown().withProperties({
required: true
}).component();
// Handle database changes
//Handle database changes
this.databaseDropdown.onValueChanged(async () => {
this.model.database = (<sqlops.CategoryValue>this.databaseDropdown.value).name;
});
@@ -172,7 +172,8 @@ export class DeployConfigPage extends DacFxConfigPage {
}
let values = await this.getDatabaseValues();
if (this.model.database === undefined) {
//set the database to the first dropdown value if upgrading, otherwise it should get set to the textbox value
if (this.model.upgradeExisting) {
this.model.database = values[0].name;
}

View File

@@ -1,6 +1,6 @@
{
"name": "azuredatastudio",
"version": "1.3.7",
"version": "1.3.9",
"distro": "8c3e97e3425cc9814496472ab73e076de2ba99ee",
"author": {
"name": "Microsoft Corporation"

View File

@@ -42,7 +42,8 @@
"Microsoft.server-report",
"Microsoft.sql-vnext",
"Microsoft.whoisactive",
"Redgate.sql-search"
"Redgate.sql-search",
"IDERA.sqldm-performance-insights"
],
"extensionsGallery": {
"serviceUrl": "https://sqlopsextensions.blob.core.windows.net/marketplace/v1/extensionsGallery.json"

View File

@@ -4,6 +4,7 @@
*--------------------------------------------------------------------------------------------*/
import { IDisposableDataProvider } from 'sql/base/browser/ui/table/interfaces';
import { CancellationTokenSource } from 'vs/base/common/cancellation';
export interface IObservableCollection<T> {
getLength(): number;
@@ -14,16 +15,12 @@ export interface IObservableCollection<T> {
dispose(): void;
}
class LoadCancellationToken {
isCancelled: boolean;
}
class DataWindow<T> {
private _data: T[];
private _length: number = 0;
private _offsetFromDataSource: number = -1;
private lastLoadCancellationToken: LoadCancellationToken;
private cancellationToken = new CancellationTokenSource();
constructor(
private loadFunction: (offset: number, count: number) => Thenable<T[]>,
@@ -36,9 +33,7 @@ class DataWindow<T> {
this.loadFunction = undefined;
this.placeholderItemGenerator = undefined;
this.loadCompleteCallback = undefined;
if (this.lastLoadCancellationToken) {
this.lastLoadCancellationToken.isCancelled = true;
}
this.cancellationToken.cancel();
}
public getStartIndex(): number {
@@ -65,17 +60,16 @@ class DataWindow<T> {
this._length = length;
this._data = undefined;
if (this.lastLoadCancellationToken) {
this.lastLoadCancellationToken.isCancelled = true;
}
this.cancellationToken.cancel();
this.cancellationToken = new CancellationTokenSource();
const currentCancellation = this.cancellationToken;
if (length === 0) {
return;
}
this.lastLoadCancellationToken = new LoadCancellationToken();
this.loadFunction(offset, length).then(data => {
if (!this.lastLoadCancellationToken.isCancelled) {
if (!currentCancellation.token.isCancellationRequested) {
this._data = data;
this.loadCompleteCallback(this._offsetFromDataSource, this._offsetFromDataSource + this._length);
}

View File

@@ -78,7 +78,7 @@ export class TableDataView<T extends Slick.SlickData> implements IDisposableData
this._onRowCountChange.fire();
}
find(exp: string): Thenable<IFindPosition> {
find(exp: string, maxMatches: number = 0): Thenable<IFindPosition> {
if (!this._findFn) {
return TPromise.wrapError(new Error('no find function provided'));
}
@@ -87,7 +87,8 @@ export class TableDataView<T extends Slick.SlickData> implements IDisposableData
this._onFindCountChange.fire(this._findArray.length);
if (exp) {
this._findObs = Observable.create((observer: Observer<IFindPosition>) => {
this._data.forEach((item, i) => {
for (let i = 0; i < this._data.length; i++) {
let item = this._data[i];
let result = this._findFn(item, exp);
if (result) {
result.forEach(pos => {
@@ -96,8 +97,11 @@ export class TableDataView<T extends Slick.SlickData> implements IDisposableData
observer.next(index);
this._onFindCountChange.fire(this._findArray.length);
});
if (maxMatches > 0 && this._findArray.length > maxMatches) {
break;
}
}
});
}
});
return this._findObs.take(1).toPromise().then(() => {
return this._findArray[this._findIndex];

View File

@@ -5,9 +5,8 @@
import * as path from 'path';
import { Registry } from 'vs/platform/registry/common/platform';
import { EditorInput, IEditorInput } from 'vs/workbench/common/editor';
import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation';
import { IInstantiationService, ServiceIdentifier } from 'vs/platform/instantiation/common/instantiation';
import { UntitledEditorInput } from 'vs/workbench/common/editor/untitledEditorInput';
import { FileEditorInput } from 'vs/workbench/parts/files/common/editors/fileEditorInput';
import URI from 'vs/base/common/uri';
@@ -17,9 +16,9 @@ import { QueryInput } from 'sql/parts/query/common/queryInput';
import { IQueryEditorOptions } from 'sql/parts/query/common/queryEditorService';
import { QueryPlanInput } from 'sql/parts/queryPlan/queryPlanInput';
import { NotebookInput, NotebookInputModel, NotebookInputValidator } from 'sql/parts/notebook/notebookInput';
import { Extensions, INotebookProviderRegistry } from 'sql/services/notebook/notebookRegistry';
import { DEFAULT_NOTEBOOK_PROVIDER } from 'sql/services/notebook/notebookService';
import { DEFAULT_NOTEBOOK_PROVIDER, INotebookService } from 'sql/services/notebook/notebookService';
import { getProviderForFileName } from 'sql/parts/notebook/notebookUtils';
import { ResourceEditorInput } from 'vs/workbench/common/editor/resourceEditorInput';
const fs = require('fs');
@@ -30,6 +29,7 @@ export const untitledFilePrefix = 'SQLQuery';
// mode identifier for SQL mode
export const sqlModeId = 'sql';
export const notebookModeId = 'notebook';
/**
* Checks if the specified input is supported by one our custom input types, and if so convert it
@@ -59,20 +59,20 @@ export function convertEditorInput(input: EditorInput, options: IQueryEditorOpti
//Notebook
let notebookValidator = instantiationService.createInstance(NotebookInputValidator);
uri = getNotebookEditorUri(input);
if(uri && notebookValidator.isNotebookEnabled()){
//TODO: We need to pass in notebook data either through notebook input or notebook service
let fileName: string = 'untitled';
let providerId: string = DEFAULT_NOTEBOOK_PROVIDER;
if (input) {
fileName = input.getName();
providerId = getProviderForFileName(fileName);
}
let notebookInputModel = new NotebookInputModel(uri, undefined, false, undefined);
notebookInputModel.providerId = providerId;
//TO DO: Second parameter has to be the content.
let notebookInput: NotebookInput = instantiationService.createInstance(NotebookInput, fileName, notebookInputModel);
return notebookInput;
uri = getNotebookEditorUri(input, instantiationService);
if (uri && notebookValidator.isNotebookEnabled()) {
return withService<INotebookService, NotebookInput>(instantiationService, INotebookService, notebookService => {
let fileName: string = 'untitled';
let providerId: string = DEFAULT_NOTEBOOK_PROVIDER;
if (input) {
fileName = input.getName();
providerId = getProviderForFileName(fileName, notebookService);
}
let notebookInputModel = new NotebookInputModel(uri, undefined, false, undefined);
notebookInputModel.providerId = providerId;
let notebookInput: NotebookInput = instantiationService.createInstance(NotebookInput, fileName, notebookInputModel);
return notebookInput;
});
}
}
return input;
@@ -97,6 +97,13 @@ export function getSupportedInputResource(input: IEditorInput): URI {
}
}
if (input instanceof ResourceEditorInput) {
let resourceCast: ResourceEditorInput = <ResourceEditorInput>input;
if (resourceCast) {
return resourceCast.getResource();
}
}
return undefined;
}
@@ -154,34 +161,50 @@ function getQueryPlanEditorUri(input: EditorInput): URI {
return undefined;
}
/**
* If input is a supported notebook editor file (.ipynb), return it's URI. Otherwise return undefined.
* @param input The EditorInput to get the URI of.
*/
function getNotebookEditorUri(input: EditorInput): URI {
function getNotebookEditorUri(input: EditorInput, instantiationService: IInstantiationService): URI {
if (!input || !input.getName()) {
return undefined;
}
// If this editor is not already of type notebook input
if (!(input instanceof NotebookInput)) {
let uri: URI = getSupportedInputResource(input);
if (uri) {
if (hasFileExtension(getNotebookFileExtensions(), input, false)) {
if (hasFileExtension(getNotebookFileExtensions(instantiationService), input, false) || hasNotebookFileMode(input)) {
return uri;
}
}
}
return undefined;
}
function getNotebookFileExtensions() {
let notebookRegistry = Registry.as<INotebookProviderRegistry>(Extensions.NotebookProviderContribution);
return notebookRegistry.getSupportedFileExtensions();
function getNotebookFileExtensions(instantiationService: IInstantiationService): string[] {
return withService<INotebookService, string[]>(instantiationService, INotebookService, notebookService => {
return notebookService.getSupportedFileExtensions();
});
}
/**
* Checks whether the given EditorInput is set to either undefined or notebook mode
* @param input The EditorInput to check the mode of
*/
function hasNotebookFileMode(input: EditorInput): boolean {
if (input instanceof UntitledEditorInput) {
let untitledCast: UntitledEditorInput = <UntitledEditorInput>input;
return (untitledCast && untitledCast.getModeId() === notebookModeId);
}
return false;
}
function withService<TService, TResult>(instantiationService: IInstantiationService, serviceId: ServiceIdentifier<TService>, action: (service: TService) => TResult, ): TResult {
return instantiationService.invokeFunction(accessor => {
let service = accessor.get(serviceId);
return action(service);
});
}
/**
@@ -219,3 +242,17 @@ function hasFileExtension(extensions: string[], input: EditorInput, checkUntitle
return false;
}
// Returns file mode - notebookModeId or sqlModeId
export function getFileMode(instantiationService: IInstantiationService, resource: URI): string {
if (!resource) {
return sqlModeId;
}
return withService<INotebookService, string>(instantiationService, INotebookService, notebookService => {
for (const editor of notebookService.listNotebookEditors()) {
if (editor.notebookParams.notebookUri === resource) {
return notebookModeId;
}
}
return sqlModeId;
});
}

View File

@@ -90,6 +90,8 @@
margin: 0px 13px;
}
.vs-dark .connection-dialog .connection-history-actions .action-label.icon,
.hc-black .connection-dialog .connection-history-actions .action-label.icon,
.connection-dialog .connection-history-actions .action-label.icon {
display: block;
height: 20px;
@@ -105,10 +107,12 @@
background: url('clear-search-results.svg');
}
/*
.vs-dark .search-action.clear-search-results,
.hc-black .search-action.clear-search-results {
background: url('clear-search-results-dark.svg');
}
*/
.connection-details-title {
font-size: 14px;

View File

@@ -168,8 +168,7 @@ export const DashboardModule = (params, selector: string, instantiationService:
if (e instanceof NavigationEnd) {
this.navigations++;
TelemetryUtils.addTelemetry(this.telemetryService, TelemetryKeys.DashboardNavigated, {
numberOfNavigations: this.navigations,
routeUrl: e.url
numberOfNavigations: this.navigations
});
}
});

View File

@@ -48,13 +48,13 @@ export function textFormatter(row: number, cell: any, value: any, columnDef: any
if (!value.isNull) {
valueToDisplay = value.displayValue.replace(/(\r\n|\n|\r)/g, ' ');
valueToDisplay = escape(valueToDisplay.length > 250 ? valueToDisplay.slice(0, 250) + '...' : valueToDisplay);
titleValue = value.displayValue;
titleValue = valueToDisplay;
} else {
cellClasses += ' missing-value';
}
} else if (typeof value === 'string') {
valueToDisplay = escape(value.length > 250 ? value.slice(0, 250) + '...' : value);
titleValue = value;
titleValue = valueToDisplay;
}
return `<span title="${titleValue}" class="${cellClasses}">${valueToDisplay}</span>`;

View File

@@ -343,6 +343,9 @@ export class EditDataComponent extends GridParentComponent implements OnInit, On
handleResultSet(self: EditDataComponent, event: any): void {
// Clone the data before altering it to avoid impacting other subscribers
let resultSet = Object.assign({}, event.data);
if (!resultSet.complete) {
return;
}
// Add an extra 'new row'
resultSet.rowCount++;

View File

@@ -304,14 +304,6 @@ table.jobprevruns > tbody {
background-image: url('refresh_inverse.svg');
}
.agent-actionbar-container .monaco-action-bar > ul.actions-container {
padding-top: 10px;
}
jobsview-component .agent-actionbar-container {
height: 40px;
}
.agent-actionbar-container .monaco-action-bar > ul.actions-container > li.action-item {
padding-left: 20px;
}
@@ -414,4 +406,11 @@ jobsview-component .jobview-grid .slick-cell.error-row {
#proxiesDiv .proxyview-proxynameindicatordisabled {
width: 5px;
background: red;
}
#jobsDiv jobsview-component .monaco-toolbar.carbon-taskbar,
#operatorsDiv joboperatorsview-component .monaco-toolbar.carbon-taskbar,
#alertsDiv jobalertsview-component .monaco-toolbar.carbon-taskbar,
#proxiesDiv jobproxiesview-component .monaco-toolbar.carbon-taskbar {
margin: 10px 0px 10px 0px;
}

View File

@@ -156,8 +156,8 @@
</td>
</tr>
</table>
<div #jobsteps style="flex: 1 1 auto; position: relative">
<jobstepsview-component *ngIf="showSteps === true" style="position: absolute; height: 100%; width: 100%"></jobstepsview-component>
<div #jobsteps *ngIf="showSteps === true" style="flex: 1 1 auto; position: relative">
<jobstepsview-component *ngIf="showSteps === true"></jobstepsview-component>
</div>
<h3 *ngIf="showSteps === false">No Steps Available</h3>
</div>

View File

@@ -65,7 +65,6 @@ export class JobHistoryComponent extends JobManagementView implements OnInit {
private _agentJobInfo: sqlops.AgentJobInfo;
private _noJobsAvailable: boolean = false;
private static readonly INITIAL_TREE_HEIGHT: number = 780;
private static readonly HEADING_HEIGHT: number = 24;
constructor(

View File

@@ -7,7 +7,6 @@
.all-jobs {
display: inline;
font-size: 15px;
padding-bottom: 15px;
}
.overview-container .overview-tab .resultsViewCollapsible {
@@ -266,14 +265,22 @@ jobhistory-component > .jobhistory-heading-container > .icon.in-progress {
padding-left: 20px;
}
jobhistory-component > .agent-actionbar-container .monaco-action-bar > ul.actions-container {
jobhistory-component > .agent-actionbar-container {
border-top: 3px solid #f4f4f4;
}
.vs-dark jobhistory-component > .agent-actionbar-container .monaco-action-bar > ul.actions-container {
.vs-dark jobhistory-component > .agent-actionbar-container {
border-top: 3px solid #444444;
}
.hc-black jobhistory-component > .agent-actionbar-container .monaco-action-bar > ul.actions-container {
.hc-black jobhistory-component > .agent-actionbar-container {
border-top: 3px solid #2b56f2;
}
jobhistory-component .step-table.prev-run-list .monaco-tree-wrapper .monaco-tree-row {
width: 96%;
}
jobhistory-component .agent-actionbar-container > .monaco-toolbar.carbon-taskbar {
margin: 10px 0px 5px 0px;
}

View File

@@ -80,4 +80,11 @@
jobstepsview-component {
display: flex;
flex-direction: column;
position: absolute;
height: 100%;
width: 100%;
}
jobstepsview-component .steps-tree .monaco-tree-wrapper .monaco-tree-row {
width: 99.2%;
}

View File

@@ -591,7 +591,7 @@ export class JobsViewComponent extends JobManagementView implements OnInit, OnDe
private async curateJobHistory(jobs: sqlops.AgentJobInfo[], ownerUri: string) {
const self = this;
jobs.forEach(async (job) => {
await Promise.all(jobs.map(async (job) => {
await this._jobManagementService.getJobHistory(ownerUri, job.jobId, job.name).then(async(result) => {
if (result) {
self.jobSteps[job.jobId] = result.steps ? result.steps : [];
@@ -622,32 +622,23 @@ export class JobsViewComponent extends JobManagementView implements OnInit, OnDe
}
}
});
});
}));
}
private createJobChart(jobId: string, jobHistories: sqlops.AgentJobHistoryInfo[]): void {
let chartHeights = this.getChartHeights(jobHistories);
let runCharts = [];
for (let i = 0; i < jobHistories.length; i++) {
for (let i = 0; i < chartHeights.length; i++) {
let runGraph = $(`table#${jobId}.jobprevruns > tbody > tr > td > div.bar${i}`);
if (jobHistories && jobHistories.length > 0) {
runGraph.css('height', chartHeights[i]);
let bgColor = jobHistories[i].runStatus === 0 ? 'red' : 'green';
runGraph.css('background', bgColor);
runGraph.hover((e) => {
let currentTarget = e.currentTarget;
currentTarget.title = jobHistories[i].runDuration;
});
if (runGraph.get(0)) {
runCharts.push(runGraph.get(0).outerHTML);
}
} else {
runGraph.css('height', '5px');
runGraph.css('background', 'red');
runGraph.hover((e) => {
let currentTarget = e.currentTarget;
currentTarget.title = 'Job not run.';
});
runGraph.css('height', chartHeights[i]);
let bgColor = jobHistories[i].runStatus === 0 ? 'red' : 'green';
runGraph.css('background', bgColor);
runGraph.hover((e) => {
let currentTarget = e.currentTarget;
currentTarget.title = jobHistories[i].runDuration;
});
if (runGraph.get(0)) {
runCharts.push(runGraph.get(0).outerHTML);
}
}
if (runCharts.length > 0) {
@@ -658,7 +649,7 @@ export class JobsViewComponent extends JobManagementView implements OnInit, OnDe
// chart height normalization logic
private getChartHeights(jobHistories: sqlops.AgentJobHistoryInfo[]): string[] {
if (!jobHistories || jobHistories.length === 0) {
return ['5px', '5px', '5px', '5px', '5px'];
return [];
}
let maxDuration: number = 0;
jobHistories.forEach(history => {

View File

@@ -2,31 +2,32 @@
<span *ngIf="hasStatus" class="card-status">
<div class="status-content" [style.backgroundColor]="statusColor"></div>
</span>
<ng-container *ngIf="isVerticalButton">
<div class="card-vertical-button">
<div *ngIf="iconPath" class="iconContainer">
<div [class]="iconClass" [style.maxWidth]="iconWidth" [style.maxHeight]="iconHeight"></div>
</div>
<h4 class="card-label">{{label}}</h4>
<span *ngIf="showRadioButton" class="selection-indicator-container">
<div *ngIf="showAsSelected" class="selection-indicator"></div>
</span>
<ng-container *ngIf="isVerticalButton">
<div class="card-vertical-button">
<div *ngIf="iconPath" class="iconContainer">
<div [class]="iconClass" [style.maxWidth]="iconWidth" [style.maxHeight]="iconHeight"></div>
</div>
</ng-container>
<ng-container *ngIf="isDetailsCard">
<div class="card-content">
<h4 class="card-label">{{label}}</h4>
<p class="card-value">{{value}}</p>
<span *ngIf="actions">
<table class="model-table">
<tr *ngFor="let action of actions">
<td class="table-row">{{action.label}}</td>
<td *ngIf="action.actionTitle" class="table-row">
<a class="pointer prominent" (click)="onDidActionClick(action)">{{action.actionTitle}}</a>
</td>
</tr>
</table>
</span>
</div>
</ng-container>
<h4 class="card-label">{{label}}</h4>
</div>
</ng-container>
<ng-container *ngIf="isDetailsCard">
<div class="card-content">
<h4 class="card-label">{{label}}</h4>
<p class="card-value">{{value}}</p>
<span *ngIf="actions">
<table class="model-table">
<tr *ngFor="let action of actions">
<td class="table-row">{{action.label}}</td>
<td *ngIf="action.actionTitle" class="table-row">
<a class="pointer prominent" (click)="onDidActionClick(action)">{{action.actionTitle}}</a>
</td>
</tr>
</table>
</span>
</div>
</ng-container>
</div>

View File

@@ -29,7 +29,7 @@ export default class CardComponent extends ComponentWithIconBase implements ICom
private backgroundColor: string;
constructor( @Inject(forwardRef(() => ChangeDetectorRef)) changeRef: ChangeDetectorRef,
constructor(@Inject(forwardRef(() => ChangeDetectorRef)) changeRef: ChangeDetectorRef,
@Inject(forwardRef(() => ElementRef)) el: ElementRef,
@Inject(IWorkbenchThemeService) private themeService: IWorkbenchThemeService
) {
@@ -130,6 +130,14 @@ export default class CardComponent extends ComponentWithIconBase implements ICom
return this.cardType === 'VerticalButton';
}
public get showRadioButton():boolean{
return this.selectable && (this.selected || this._hasFocus)
}
public get showAsSelected(): boolean {
return this.selectable && this.selected;
}
public get actions(): ActionDescriptor[] {
return this.getPropertyOrDefault<CardProperties, ActionDescriptor[]>((props) => props.actions, []);
@@ -156,6 +164,7 @@ export default class CardComponent extends ComponentWithIconBase implements ICom
private updateTheme(theme: IColorTheme) {
this.backgroundColor = theme.getColor(colors.editorBackground, true).toString();
this._changeRef.detectChanges();
}
private onDidActionClick(action: ActionDescriptor): void {

View File

@@ -1,4 +1,3 @@
.model-card {
position: relative;
display: inline-block;
@@ -7,23 +6,18 @@
margin: 15px;
border-width: 1px;
border-style: solid;
text-align: left;
vertical-align: top;
box-shadow: rgba(120, 120, 120, 0.75) 0px 0px 6px;
}
.model-card.selected {
border-color: darkblue
}
.vs-dark .monaco-workbench .model-card.selected,
.hc-black .monaco-workbench .model-card.selected {
border-color: darkblue
border-color: rgb(0, 120, 215);
box-shadow: rgba(0, 120, 215, 0.75) 0px 0px 6px;
}
.model-card.unselected {
border-color: rgb(214, 214, 214);
box-shadow: none;
}
@@ -102,21 +96,43 @@
text-align: center;
}
.model-card .selection-indicator-container {
position: absolute;
top: 5px;
right: 5px;
overflow: hidden;
width: 16px;
height: 16px;
border-radius: 50%;
background-color: white;
border-width: 1px;
border-color: rgb(0, 120, 215);
border-style: solid;
}
.model-card .selection-indicator {
margin: 4px;
width: 8px;
height: 8px;
border-radius: 50%;
background-color: rgb(0, 120, 215);
}
.model-card .model-table {
border-spacing: 5px;
}
.model-table .table-row {
width: auto;
clear: both;
width: auto;
clear: both;
}
.model-table .table-cell {
vertical-align: top;
padding: 7px;
vertical-align: top;
padding: 7px;
}
.model-table a {
cursor: pointer;
text-decoration: underline
}
}

View File

@@ -0,0 +1,127 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the Source EULA. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import { ElementRef } from '@angular/core';
import { nb } from 'sqlops';
import { localize } from 'vs/nls';
import { Action } from 'vs/base/common/actions';
import { ActionBar, ActionsOrientation } from 'vs/base/browser/ui/actionbar/actionbar';
import { getErrorMessage } from 'vs/base/common/errors';
import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation';
import { INotificationService, Severity } from 'vs/platform/notification/common/notification';
import { ICellModel } from 'sql/parts/notebook/models/modelInterfaces';
import { CellContext, CellActionBase } from 'sql/parts/notebook/cellViews/codeActions';
import { NotebookModel } from 'sql/parts/notebook/models/notebookModel';
import { ToggleMoreWidgetAction } from 'sql/parts/dashboard/common/actions';
import { CellTypes, CellType } from 'sql/parts/notebook/models/contracts';
import { CellModel } from 'sql/parts/notebook/models/cell';
export class CellToggleMoreActions {
private _actions: Action[] = [];
private _moreActions: ActionBar;
constructor(
@IInstantiationService private instantiationService: IInstantiationService) {
this._actions.push(
instantiationService.createInstance(DeleteCellAction, 'delete', localize('delete', 'Delete')),
instantiationService.createInstance(AddCellFromContextAction,'codeBefore', localize('codeBefore', 'Insert Code before'), CellTypes.Code, false),
instantiationService.createInstance(AddCellFromContextAction, 'codeAfter', localize('codeAfter', 'Insert Code after'), CellTypes.Code, true),
instantiationService.createInstance(AddCellFromContextAction, 'markdownBefore', localize('markdownBefore', 'Insert Markdown before'), CellTypes.Markdown, false),
instantiationService.createInstance(AddCellFromContextAction, 'markdownAfter', localize('markdownAfter', 'Insert Markdown after'), CellTypes.Markdown, true),
instantiationService.createInstance(ClearCellOutputAction, 'clear', localize('clear', 'Clear output'))
);
}
public toggle(showIcon: boolean, elementRef: ElementRef, model: NotebookModel, cellModel: ICellModel) {
let context = new CellContext(model,cellModel);
let moreActionsElement = <HTMLElement>elementRef.nativeElement;
if (showIcon) {
if (moreActionsElement.childNodes.length > 0) {
moreActionsElement.removeChild(moreActionsElement.childNodes[0]);
}
this._moreActions = new ActionBar(moreActionsElement, { orientation: ActionsOrientation.VERTICAL });
this._moreActions.context = { target: moreActionsElement };
this._moreActions.push(this.instantiationService.createInstance(ToggleMoreWidgetAction, this._actions, context), { icon: showIcon, label: false });
}
else if (moreActionsElement.childNodes.length > 0) {
moreActionsElement.removeChild(moreActionsElement.childNodes[0]);
}
}
}
export class AddCellFromContextAction extends CellActionBase {
constructor(
id: string, label: string, private cellType: CellType, private isAfter: boolean,
@INotificationService notificationService: INotificationService
) {
super(id, label, undefined, notificationService);
}
runCellAction(context: CellContext): Promise<void> {
try {
let model = context.model;
let index = model.cells.findIndex((cell) => cell.id === context.cell.id);
if (index !== undefined && this.isAfter) {
index += 1;
}
model.addCell(this.cellType, index);
} catch (error) {
let message = getErrorMessage(error);
this.notificationService.notify({
severity: Severity.Error,
message: message
});
}
return Promise.resolve();
}
}
export class DeleteCellAction extends CellActionBase {
constructor(id: string, label: string,
@INotificationService notificationService: INotificationService
) {
super(id, label, undefined, notificationService);
}
runCellAction(context: CellContext): Promise<void> {
try {
context.model.deleteCell(context.cell);
} catch (error) {
let message = getErrorMessage(error);
this.notificationService.notify({
severity: Severity.Error,
message: message
});
}
return Promise.resolve();
}
}
export class ClearCellOutputAction extends CellActionBase {
constructor(id: string, label: string,
@INotificationService notificationService: INotificationService
) {
super(id, label, undefined, notificationService);
}
runCellAction(context: CellContext): Promise<void> {
try {
(context.model.activeCell as CellModel).clearOutputs();
} catch (error) {
let message = getErrorMessage(error);
this.notificationService.notify({
severity: Severity.Error,
message: message
});
}
return Promise.resolve();
}
}

View File

@@ -9,6 +9,6 @@
</div>
<div #editor class="editor" style="flex: 1 1 auto; overflow: hidden;">
</div>
<div #moreactions class="toolbar" style="flex: 0 0 auto; display: flex; flex-flow:column; width: 20px; min-height: 20px; max-height: 20px; padding-top: 0px; orientation: portrait">
<div #moreactions class="moreActions" style="flex: 0 0 auto; display: flex; flex-flow:column;width: 20px; min-height: 20px; max-height: 20px; padding-top: 0px; orientation: portrait">
</div>
</div>

View File

@@ -4,11 +4,16 @@
*--------------------------------------------------------------------------------------------*/
import 'vs/css!./code';
import { OnInit, Component, Input, Inject, forwardRef, ElementRef, ChangeDetectorRef, OnDestroy, ViewChild, Output, EventEmitter, OnChanges, SimpleChange } from '@angular/core';
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/common/lifecycle';
import { QueryTextEditor } from 'sql/parts/modelComponents/queryTextEditor';
import { CellToggleMoreActions } from 'sql/parts/notebook/cellToggleMoreActions';
import { ICellModel } from 'sql/parts/notebook/models/modelInterfaces';
import { Taskbar } from 'sql/base/browser/ui/taskbar/taskbar';
import { RunCellAction, CellContext } from 'sql/parts/notebook/cellViews/codeActions';
import { NotebookModel } from 'sql/parts/notebook/models/notebookModel';
import { IColorTheme, IWorkbenchThemeService } from 'vs/workbench/services/themes/common/workbenchThemeService';
import * as themeColors from 'vs/workbench/common/theme';
@@ -19,20 +24,11 @@ import { IInstantiationService } from 'vs/platform/instantiation/common/instanti
import { ITextModel } from 'vs/editor/common/model';
import { UntitledEditorInput } from 'vs/workbench/common/editor/untitledEditorInput';
import URI from 'vs/base/common/uri';
import { localize } from 'vs/nls';
import { Action } from 'vs/base/common/actions';
import { ActionBar, ActionsOrientation } from 'vs/base/browser/ui/actionbar/actionbar';
import { Schemas } from 'vs/base/common/network';
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 { ICellModel } from 'sql/parts/notebook/models/modelInterfaces';
import { Taskbar } from 'sql/base/browser/ui/taskbar/taskbar';
import { RunCellAction, DeleteCellAction, AddCellAction, CellContext } from 'sql/parts/notebook/cellViews/codeActions';
import { NotebookModel } from 'sql/parts/notebook/models/notebookModel';
import { ToggleMoreWidgetAction } from 'sql/parts/dashboard/common/actions';
import { CellTypes } from 'sql/parts/notebook/models/contracts';
import { INotificationService } from 'vs/platform/notification/common/notification';
export const CODE_SELECTOR: string = 'code-component';
@@ -59,15 +55,14 @@ export class CodeComponent extends AngularDisposable implements OnInit, OnChange
}
protected _actionBar: Taskbar;
protected _moreActions: ActionBar;
private readonly _minimumHeight = 30;
private _editor: QueryTextEditor;
private _editorInput: UntitledEditorInput;
private _editorModel: ITextModel;
private _uri: string;
private _model: NotebookModel;
private _actions: Action[] = [];
private _activeCellId: string;
private _cellToggleMoreActions: CellToggleMoreActions;
constructor(
@Inject(forwardRef(() => CommonServiceInterface)) private _bootstrapService: CommonServiceInterface,
@@ -81,13 +76,7 @@ export class CodeComponent extends AngularDisposable implements OnInit, OnChange
@Inject(INotificationService) private notificationService: INotificationService,
) {
super();
this._actions.push(
this._instantiationService.createInstance(AddCellAction, 'codeBefore', localize('codeBefore', 'Insert Code before'), CellTypes.Code, false),
this._instantiationService.createInstance(AddCellAction, 'codeAfter', localize('codeAfter', 'Insert Code after'), CellTypes.Code, true),
this._instantiationService.createInstance(AddCellAction, 'markdownBefore', localize('markdownBefore', 'Insert Markdown before'), CellTypes.Markdown, false),
this._instantiationService.createInstance(AddCellAction, 'markdownAfter', localize('markdownAfter', 'Insert Markdown after'), CellTypes.Markdown, true),
this._instantiationService.createInstance(DeleteCellAction, 'delete', localize('delete', 'Delete'))
);
this._cellToggleMoreActions = this._instantiationService.createInstance(CellToggleMoreActions);
}
ngOnInit() {
@@ -105,10 +94,10 @@ export class CodeComponent extends AngularDisposable implements OnInit, OnChange
if (propName === 'activeCellId') {
let changedProp = changes[propName];
if (this.cellModel.id === changedProp.currentValue) {
this.toggleMoreActions(true);
this._cellToggleMoreActions.toggle(true, this.moreActionsElementRef, this.model, this.cellModel);
}
else {
this.toggleMoreActions(false);
this._cellToggleMoreActions.toggle(false, this.moreActionsElementRef, this.model, this.cellModel);
}
break;
}
@@ -173,21 +162,6 @@ export class CodeComponent extends AngularDisposable implements OnInit, OnChange
]);
}
private toggleMoreActions(showIcon: boolean) {
let context = new CellContext(this.model, this.cellModel);
let moreActionsElement = <HTMLElement>this.moreActionsElementRef.nativeElement;
if (showIcon) {
if (moreActionsElement.childNodes.length > 0) {
moreActionsElement.removeChild(moreActionsElement.childNodes[0]);
}
this._moreActions = new ActionBar(moreActionsElement, { orientation: ActionsOrientation.VERTICAL });
this._moreActions.context = { target: moreActionsElement };
this._moreActions.push(this._instantiationService.createInstance(ToggleMoreWidgetAction, this._actions, context), { icon: showIcon, label: false });
}
else if (moreActionsElement.childNodes.length > 0) {
moreActionsElement.removeChild(moreActionsElement.childNodes[0]);
}
}
private createUri(): URI {
let uri = URI.from({ scheme: Schemas.untitled, path: `notebook-editor-${this.cellModel.id}` });

View File

@@ -45,10 +45,6 @@ code-component .carbon-taskbar .icon {
width: 40px;
}
code-component .action-label.icon.toggle-more {
height: 20px;
width: 20px;
}
code-component .carbon-taskbar.monaco-toolbar .monaco-action-bar.animated .actions-container
{

View File

@@ -40,7 +40,7 @@ export class CellContext {
}
}
abstract class CellActionBase extends Action {
export abstract class CellActionBase extends Action {
constructor(id: string, label: string, icon: string, protected notificationService: INotificationService) {
super(id, label, icon);
@@ -135,53 +135,3 @@ export class RunCellAction extends ToggleableAction {
return clientSession.kernel;
}
}
export class AddCellAction extends CellActionBase {
constructor(
id: string, label: string, private cellType: CellType, private isAfter: boolean,
@INotificationService notificationService: INotificationService
) {
super(id, label, undefined, notificationService);
}
runCellAction(context: CellContext): Promise<void> {
try {
let model = context.model;
let index = model.cells.findIndex((cell) => cell.id === context.cell.id);
if (index !== undefined && this.isAfter) {
index += 1;
}
model.addCell(this.cellType, index);
} catch (error) {
let message = getErrorMessage(error);
this.notificationService.notify({
severity: Severity.Error,
message: message
});
}
return Promise.resolve();
}
}
export class DeleteCellAction extends CellActionBase {
constructor(id: string, label: string,
@INotificationService notificationService: INotificationService
) {
super(id, label, undefined, notificationService);
}
runCellAction(context: CellContext): Promise<void> {
try {
context.model.deleteCell(context.cell);
} catch (error) {
let message = getErrorMessage(error);
this.notificationService.notify({
severity: Severity.Error,
message: message
});
}
return Promise.resolve();
}
}

View File

@@ -5,10 +5,15 @@
*--------------------------------------------------------------------------------------------*/
-->
<div style="overflow: hidden; width: 100%; height: 100%; display: flex; flex-flow: column">
<loading-spinner [loading]="isLoading"></loading-spinner>
<div class="notebook-text" style="flex: 0 0 auto;">
<code-component *ngIf="isEditMode" [cellModel]="cellModel" (onContentChanged)="handleContentChanged()" [model]="model" [activeCellId]="activeCellId" [hideVerticalToolbar]=true>
</code-component>
</div>
<div #preview style="flex: 0 0 auto;" (dblclick)="toggleEditMode()">
<div style="overflow: hidden; width: 100%; height: 100%; display: flex; flex-flow: row">
<div #preview class ="notebook-preview" style="flex: 1 1 auto; user-select: initial;" (dblclick)="toggleEditMode()">
</div>
<div #moreactions class="moreActions" style="flex: 0 0 auto; display: flex; flex-flow:column;width: 20px; min-height: 20px; max-height: 20px; padding-top: 0px; orientation: portrait">
</div>
</div>
</div>

View File

@@ -6,16 +6,21 @@ import 'vs/css!./textCell';
import { OnInit, Component, Input, Inject, forwardRef, ElementRef, ChangeDetectorRef, OnDestroy, ViewChild, OnChanges, SimpleChange } from '@angular/core';
import { CommonServiceInterface } from 'sql/services/common/commonServiceInterface.service';
import { CellView } from 'sql/parts/notebook/cellViews/interfaces';
import { localize } from 'vs/nls';
import { IColorTheme, IWorkbenchThemeService } from 'vs/workbench/services/themes/common/workbenchThemeService';
import * as themeColors from 'vs/workbench/common/theme';
import { ICommandService } from 'vs/platform/commands/common/commands';
import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation';
import { Emitter } from 'vs/base/common/event';
import URI from 'vs/base/common/uri';
import { IOpenerService } from 'vs/platform/opener/common/opener';
import { CommonServiceInterface } from 'sql/services/common/commonServiceInterface.service';
import { CellView } from 'sql/parts/notebook/cellViews/interfaces';
import { ICellModel } from 'sql/parts/notebook/models/modelInterfaces';
import { ISanitizer, defaultSanitizer } from 'sql/parts/notebook/outputs/sanitizer';
import { localize } from 'vs/nls';
import { NotebookModel } from 'sql/parts/notebook/models/notebookModel';
import { CellToggleMoreActions } from 'sql/parts/notebook/cellToggleMoreActions';
export const TEXT_SELECTOR: string = 'text-cell-component';
@@ -25,6 +30,7 @@ export const TEXT_SELECTOR: string = 'text-cell-component';
})
export class TextCellComponent extends CellView implements OnInit, OnChanges {
@ViewChild('preview', { read: ElementRef }) private output: ElementRef;
@ViewChild('moreactions', { read: ElementRef }) private moreActionsElementRef: ElementRef;
@Input() cellModel: ICellModel;
@Input() set model(value: NotebookModel) {
@@ -38,18 +44,25 @@ export class TextCellComponent extends CellView implements OnInit, OnChanges {
private _content: string;
private isEditMode: boolean;
private _sanitizer: ISanitizer;
private _previewCssApplied: boolean = false;
private _model: NotebookModel;
private _activeCellId: string;
private readonly _onDidClickLink = this._register(new Emitter<URI>());
public readonly onDidClickLink = this._onDidClickLink.event;
protected isLoading: boolean;
private _cellToggleMoreActions: CellToggleMoreActions;
constructor(
@Inject(forwardRef(() => CommonServiceInterface)) private _bootstrapService: CommonServiceInterface,
@Inject(forwardRef(() => ChangeDetectorRef)) private _changeRef: ChangeDetectorRef,
@Inject(IInstantiationService) private _instantiationService: IInstantiationService,
@Inject(IWorkbenchThemeService) private themeService: IWorkbenchThemeService,
@Inject(ICommandService) private _commandService: ICommandService
@Inject(ICommandService) private _commandService: ICommandService,
@Inject(IOpenerService) private readonly openerService: IOpenerService,
) {
super();
this.isEditMode = false;
this.isLoading = true;
this._cellToggleMoreActions = this._instantiationService.createInstance(CellToggleMoreActions);
}
//Gets sanitizer from ISanitizer interface
@@ -68,8 +81,14 @@ export class TextCellComponent extends CellView implements OnInit, OnChanges {
return this._activeCellId;
}
private setLoading(isLoading: boolean): void {
this.isLoading = isLoading;
this._changeRef.detectChanges();
}
ngOnInit() {
this.updatePreview();
this.setLoading(false);
this._register(this.themeService.onDidColorThemeChange(this.updateTheme, this));
this.updateTheme(this.themeService.getColorTheme());
this.cellModel.onOutputsChanged(e => {
@@ -124,32 +143,24 @@ export class TextCellComponent extends CellView implements OnInit, OnChanges {
private updateTheme(theme: IColorTheme): void {
let outputElement = <HTMLElement>this.output.nativeElement;
outputElement.style.borderTopColor = theme.getColor(themeColors.SIDE_BAR_BACKGROUND, true).toString();
let moreActionsEl = <HTMLElement>this.moreActionsElementRef.nativeElement;
moreActionsEl.style.borderRightColor = theme.getColor(themeColors.SIDE_BAR_BACKGROUND, true).toString();
}
public handleContentChanged(): void {
if (!this._previewCssApplied) {
this.updatePreviewCssClass();
}
this.updatePreview();
}
public toggleEditMode(editMode?: boolean): void {
this.isEditMode = editMode !== undefined? editMode : !this.isEditMode;
this.updatePreviewCssClass();
if (!this.isEditMode && this.cellModel.id === this._activeCellId) {
this._cellToggleMoreActions.toggle(true, this.moreActionsElementRef, this.model, this.cellModel);
}
else {
this._cellToggleMoreActions.toggle(false, this.moreActionsElementRef, this.model, this.cellModel);
}
this.updatePreview();
this._changeRef.detectChanges();
}
// Updates the css class to preview 'div' based on edit mode
private updatePreviewCssClass() {
let outputElement = <HTMLElement>this.output.nativeElement;
if (this.isEditMode && this.cellModel.source) {
outputElement.className = 'notebook-preview';
this._previewCssApplied = true;
}
else {
outputElement.className = '';
this._previewCssApplied = false;
}
}
}

View File

@@ -11,4 +11,6 @@ text-cell-component .notebook-preview {
border-top-width: 1px;
border-top-style: solid;
user-select: initial;
padding-left: 8px;
padding-right: 8px;
}

View File

@@ -38,15 +38,15 @@ export class CellModel implements ICellModel {
constructor(private factory: IModelFactory, cellData?: nb.ICellContents, private _options?: ICellModelOptions) {
this.id = `${modelId++}`;
CellModel.CreateLanguageMappings();
// Do nothing for now
if (cellData) {
// Read in contents if available
this.fromJSON(cellData);
} else {
this._cellType = CellTypes.Code;
this._source = '';
}
this._isEditMode = this._cellType !== CellTypes.Markdown;
this.setDefaultLanguage();
this.ensureDefaultLanguage();
if (_options && _options.isTrusted) {
this._isTrusted = true;
} else {
@@ -284,7 +284,7 @@ export class CellModel implements ICellModel {
}
this._cellType = cell.cell_type;
this._source = Array.isArray(cell.source) ? cell.source.join('') : cell.source;
this._language = (cell.metadata && cell.metadata.language) ? cell.metadata.language : 'python';
this.setLanguageFromContents(cell);
if (cell.outputs) {
for (let output of cell.outputs) {
// For now, we're assuming it's OK to save these as-is with no modification
@@ -293,6 +293,15 @@ export class CellModel implements ICellModel {
}
}
private setLanguageFromContents(cell: nb.ICellContents): void {
if (cell.cell_type === CellTypes.Markdown) {
this._language = 'markdown';
} else if (cell.metadata && cell.metadata.language) {
this._language = cell.metadata.language;
}
// else skip, we set default language anyhow
}
private addOutput(output: nb.ICellOutput) {
this._normalize(output);
this._outputs.push(output);
@@ -327,8 +336,32 @@ export class CellModel implements ICellModel {
return undefined;
}
private setDefaultLanguage(): void {
this._language = 'python';
/**
* Ensures there is a default language set, if none was already defined.
* Will read information from the overall Notebook (passed as options to the model), or
* if all else fails default back to python.
*
*/
private ensureDefaultLanguage(): void {
// See if language is already set / is known based on cell type
if (this.hasLanguage()) {
return;
}
if (this._cellType === CellTypes.Markdown) {
this._language = 'markdown';
return;
}
// try set it based on overall Notebook language
this.trySetLanguageFromLangInfo();
// fallback to python
if (!this._language) {
this._language = 'python';
}
}
private trySetLanguageFromLangInfo() {
// In languageInfo, set the language to the "name" property
// If the "name" property isn't defined, check the "mimeType" property
// Otherwise, default to python as the language
@@ -338,16 +371,25 @@ export class CellModel implements ICellModel {
// check the LanguageMapping to determine if a mapping is necessary (example 'pyspark' -> 'python')
if (CellModel.LanguageMapping[languageInfo.name]) {
this._language = CellModel.LanguageMapping[languageInfo.name];
} else {
}
else {
this._language = languageInfo.name;
}
} else if (languageInfo.mimetype) {
}
else if (languageInfo.mimetype) {
this._language = languageInfo.mimetype;
}
}
let mimeTypePrefix = 'x-';
if (this._language.includes(mimeTypePrefix)) {
this._language = this._language.replace(mimeTypePrefix, '');
if (this._language) {
let mimeTypePrefix = 'x-';
if (this._language.includes(mimeTypePrefix)) {
this._language = this._language.replace(mimeTypePrefix, '');
}
}
}
private hasLanguage(): boolean {
return !!this._language;
}
}

View File

@@ -288,6 +288,12 @@ export interface INotebookModel {
*/
readonly contexts: IDefaultConnection | undefined;
/**
* Event fired on first initialization of the cells and
* on subsequent change events
*/
readonly contentChanged: Event<NotebookContentChange>;
/**
* The trusted mode of the Notebook
*/
@@ -339,6 +345,29 @@ export interface INotebookModel {
pushEditOperations(edits: ISingleNotebookEditOperation[]): void;
}
export interface NotebookContentChange {
/**
* The type of change that occurred
*/
changeType: NotebookChangeType;
/**
* Optional cells that were changed
*/
cells?: ICellModel | ICellModel[];
/**
* Optional index of the change, indicating the cell at which an insert or
* delete occurred
*/
cellIndex?: number;
/**
* Optional value indicating if the notebook is in a dirty or clean state after this change
*
* @type {boolean}
* @memberof NotebookContentChange
*/
isDirty?: boolean;
}
export interface ICellModelOptions {
notebook: INotebookModel;
isTrusted: boolean;

View File

@@ -12,7 +12,7 @@ import { Event, Emitter } from 'vs/base/common/event';
import { Disposable } from 'vs/base/common/lifecycle';
import { CellModel } from './cell';
import { IClientSession, INotebookModel, IDefaultConnection, INotebookModelOptions, ICellModel, notebookConstants } from './modelInterfaces';
import { IClientSession, INotebookModel, IDefaultConnection, INotebookModelOptions, ICellModel, notebookConstants, NotebookContentChange } from './modelInterfaces';
import { NotebookChangeType, CellTypes, CellType } from 'sql/parts/notebook/models/contracts';
import { nbversion } from '../notebookConstants';
import * as notebookUtils from '../notebookUtils';
@@ -22,6 +22,7 @@ import { IConnectionProfile } from 'sql/parts/connection/common/interfaces';
import { NotebookConnection } from 'sql/parts/notebook/models/notebookConnection';
import { INotification, Severity } from 'vs/platform/notification/common/notification';
import { Schemas } from 'vs/base/common/network';
import URI from 'vs/base/common/uri';
import { ISingleNotebookEditOperation } from 'sql/workbench/api/common/sqlExtHostTypes';
/*
@@ -38,28 +39,6 @@ export class ErrorInfo {
constructor(public readonly message: string, public readonly severity: MessageLevel) {
}
}
export interface NotebookContentChange {
/**
* What was the change that occurred?
*/
changeType: NotebookChangeType;
/**
* Optional cells that were changed
*/
cells?: ICellModel | ICellModel[];
/**
* Optional index of the change, indicating the cell at which an insert or
* delete occurred
*/
cellIndex?: number;
/**
* Optional value indicating if the notebook is in a dirty or clean state after this change
*
* @type {boolean}
* @memberof NotebookContentChange
*/
isDirty?: boolean;
}
export class NotebookModel extends Disposable implements INotebookModel {
private _contextsChangedEmitter = new Emitter<void>();
@@ -97,6 +76,13 @@ export class NotebookModel extends Disposable implements INotebookModel {
return this.notebookOptions.notebookManager;
}
public get notebookUri() : URI {
return this.notebookOptions.notebookUri;
}
public set notebookUri(value : URI) {
this.notebookOptions.notebookUri = value;
}
public get hasServerManager(): boolean {
// If the service has a server manager, then we can show the start button
return !!this.notebookManager.serverManager;
@@ -364,7 +350,7 @@ export class NotebookModel extends Disposable implements INotebookModel {
if (!newConnection && this._activeContexts.defaultConnection.options['host'] === host) {
newConnection = this._activeContexts.defaultConnection;
}
SparkMagicContexts.configureContext(this.notebookOptions);
SparkMagicContexts.configureContext();
this._hadoopConnection = new NotebookConnection(newConnection);
this.refreshConnections(newConnection);
this._clientSession.updateConnection(this._hadoopConnection);

View File

@@ -8,7 +8,6 @@
import * as path from 'path';
import { nb } from 'sqlops';
import * as json from 'vs/base/common/json';
import * as pfs from 'vs/base/node/pfs';
import { localize } from 'vs/nls';
import { IConnectionProfile } from 'sql/parts/connection/common/interfaces';
@@ -17,11 +16,48 @@ import * as notebookUtils from '../notebookUtils';
import { INotificationService } from 'vs/platform/notification/common/notification';
import { IConnectionManagementService } from 'sql/parts/connection/common/connectionManagement';
const configBase = {
'kernel_python_credentials': {
'url': ''
},
'kernel_scala_credentials': {
'url': ''
},
'kernel_r_credentials': {
'url': ''
},
'ignore_ssl_errors': true,
'logging_config': {
'version': 1,
'formatters': {
'magicsFormatter': {
'format': '%(asctime)s\t%(levelname)s\t%(message)s',
'datefmt': ''
}
},
'handlers': {
'magicsHandler': {
'class': 'hdijupyterutils.filehandler.MagicsFileHandler',
'formatter': 'magicsFormatter',
'home_path': ''
}
},
'loggers': {
'magicsLogger': {
'handlers': ['magicsHandler'],
'level': 'DEBUG',
'propagate': 0
}
}
}
};
export class SparkMagicContexts {
public static get DefaultContext(): IDefaultConnection {
// TODO NOTEBOOK REFACTOR fix default connection handling
let defaultConnection: IConnectionProfile = <any> {
let defaultConnection: IConnectionProfile = <any>{
providerName: notebookConstants.hadoopKnoxProviderName,
id: '-1',
options:
@@ -47,7 +83,7 @@ export class SparkMagicContexts {
let connections: IDefaultConnection = this.DefaultContext;
if (!profile) {
if (!kernelChangedArgs || !kernelChangedArgs.newValue ||
(kernelChangedArgs.oldValue && kernelChangedArgs.newValue.id === kernelChangedArgs.oldValue.id)) {
(kernelChangedArgs.oldValue && kernelChangedArgs.newValue.id === kernelChangedArgs.oldValue.id)) {
// nothing to do, kernels are the same or new kernel is undefined
return connections;
}
@@ -55,7 +91,7 @@ export class SparkMagicContexts {
if (kernelChangedArgs && kernelChangedArgs.newValue && kernelChangedArgs.newValue.name) {
switch (kernelChangedArgs.newValue.name) {
case (notebookConstants.python3):
// python3 case, use this.DefaultContext for the only connection
// python3 case, use this.DefaultContext for the only connection
break;
//TO DO: Handle server connections based on kernel type. Right now, we call the same method for all kernel types.
default:
@@ -76,13 +112,13 @@ export class SparkMagicContexts {
let defaultConnection: IConnectionProfile = SparkMagicContexts.DefaultContext.defaultConnection;
let activeConnections: IConnectionProfile[] = await connectionService.getActiveConnections();
// If no connections exist, only show 'n/a'
if (activeConnections && activeConnections.length > 0) {
// Remove all non-Spark connections
activeConnections = activeConnections.filter(conn => conn.providerName === notebookConstants.hadoopKnoxProviderName);
}
if (activeConnections.length === 0) {
return SparkMagicContexts.DefaultContext;
}
if (activeConnections && activeConnections.length > 0) {
// Remove all non-Spark connections
activeConnections = activeConnections.filter(conn => conn.providerName === notebookConstants.hadoopKnoxProviderName);
}
if (activeConnections.length === 0) {
return SparkMagicContexts.DefaultContext;
}
// If launched from the right click or server dashboard, connection profile data exists, so use that as default
if (profile && profile.options) {
@@ -95,7 +131,7 @@ export class SparkMagicContexts {
defaultConnection = activeConnections[0];
} else {
// TODO NOTEBOOK REFACTOR change this so it's no longer incompatible with IConnectionProfile
defaultConnection = <IConnectionProfile> <any>{
defaultConnection = <IConnectionProfile><any>{
providerName: notebookConstants.hadoopKnoxProviderName,
id: '-1',
options:
@@ -107,31 +143,28 @@ export class SparkMagicContexts {
}
}
return {
otherConnections: activeConnections,
defaultConnection: defaultConnection
otherConnections: activeConnections,
defaultConnection: defaultConnection
};
}
public static async configureContext(options: INotebookModelOptions): Promise<object> {
public static async configureContext(): Promise<object> {
let sparkmagicConfDir = path.join(notebookUtils.getUserHome(), '.sparkmagic');
// TODO NOTEBOOK REFACTOR re-enable this or move to extension. Requires config files to be available in order to work
// await notebookUtils.mkDir(sparkmagicConfDir);
await notebookUtils.mkDir(sparkmagicConfDir);
// // Default to localhost in config file.
// let creds: ICredentials = {
// 'url': 'http://localhost:8088'
// };
// Default to localhost in config file.
let creds: ICredentials = {
'url': 'http://localhost:8088'
};
// let configPath = notebookUtils.getTemplatePath(options.extensionContext.extensionPath, path.join('jupyter_config', 'sparkmagic_config.json'));
// let fileBuffer: Buffer = await pfs.readFile(configPath);
// let fileContents: string = fileBuffer.toString();
// let config: ISparkMagicConfig = json.parse(fileContents);
// SparkMagicContexts.updateConfig(config, creds, sparkmagicConfDir);
let config: ISparkMagicConfig = Object.assign({}, configBase);
SparkMagicContexts.updateConfig(config, creds, sparkmagicConfDir);
// let configFilePath = path.join(sparkmagicConfDir, 'config.json');
// await pfs.writeFile(configFilePath, JSON.stringify(config));
let configFilePath = path.join(sparkmagicConfDir, 'config.json');
await pfs.writeFile(configFilePath, JSON.stringify(config));
return {'SPARKMAGIC_CONF_DIR': sparkmagicConfDir};
return { 'SPARKMAGIC_CONF_DIR': sparkmagicConfDir };
}
/**
*
@@ -149,10 +182,10 @@ export class SparkMagicContexts {
}
}
let profile = connectionInfo as IConnectionProfile;
if (foundSavedKernelInSpecs && specs && connectionInfo && profile.providerName === notebookConstants.hadoopKnoxProviderName) {
if (specs && connectionInfo && profile.providerName === notebookConstants.hadoopKnoxProviderName) {
// set default kernel to default spark kernel if profile exists
// otherwise, set default to kernel info loaded from existing file
defaultKernel = !savedKernelInfo ? specs.kernels.find((spec) => spec.name === notebookConstants.defaultSparkKernel) : savedKernelInfo;
defaultKernel = !foundSavedKernelInSpecs ? specs.kernels.find((spec) => spec.name === notebookConstants.defaultSparkKernel) : foundSavedKernelInSpecs;
} else {
// Handle kernels
if (savedKernelInfo && savedKernelInfo.name.toLowerCase().indexOf('spark') > -1) {
@@ -186,10 +219,13 @@ interface ISparkMagicConfig {
kernel_python_credentials: ICredentials;
kernel_scala_credentials: ICredentials;
kernel_r_credentials: ICredentials;
ignore_ssl_errors?: boolean;
logging_config: {
handlers: {
magicsHandler: {
home_path: string;
class?: string;
formatter?: string
}
}
};

View File

@@ -5,7 +5,7 @@
*--------------------------------------------------------------------------------------------*/
-->
<div style="overflow: hidden; width: 100%; height: 100%; display: flex; flex-flow: column">
<div #toolbar class="editor-toolbar actionbar-container" style="flex: 0 0 auto; display: flex; flex-flow: row; width: 100%; align-items: center; height: 36px">
<div #toolbar class="editor-toolbar actionbar-container" style="flex: 0 0 auto; display: flex; flex-flow: row; width: 100%; align-items: center;">
</div>
<div class="scrollable" style="flex: 1 1 auto; position: relative" (click)="unselectActiveCell()">
<loading-spinner [loading]="isLoading"></loading-spinner>

View File

@@ -5,42 +5,50 @@
import './notebookStyles';
import { nb } from 'sqlops';
import { OnInit, Component, Inject, forwardRef, ElementRef, ChangeDetectorRef, ViewChild, OnDestroy } from '@angular/core';
import { IColorTheme, IWorkbenchThemeService } from 'vs/workbench/services/themes/common/workbenchThemeService';
import * as themeColors from 'vs/workbench/common/theme';
import { INotificationService, INotification } from 'vs/platform/notification/common/notification';
import { INotificationService, INotification, Severity } from 'vs/platform/notification/common/notification';
import { localize } from 'vs/nls';
import { CommonServiceInterface } from 'sql/services/common/commonServiceInterface.service';
import { AngularDisposable } from 'sql/base/common/lifecycle';
import { CellTypes, CellType } from 'sql/parts/notebook/models/contracts';
import { ICellModel, IModelFactory, notebookConstants } from 'sql/parts/notebook/models/modelInterfaces';
import { IConnectionManagementService, IConnectionDialogService } from 'sql/parts/connection/common/connectionManagement';
import { INotebookService, INotebookParams, INotebookManager, INotebookEditor } from 'sql/services/notebook/notebookService';
import { IBootstrapParams } from 'sql/services/bootstrap/bootstrapService';
import { NotebookModel, NotebookContentChange } from 'sql/parts/notebook/models/notebookModel';
import { ModelFactory } from 'sql/parts/notebook/models/modelFactory';
import * as notebookUtils from './notebookUtils';
import { Deferred } from 'sql/base/common/promise';
import { IConnectionProfile } from 'sql/parts/connection/common/interfaces';
import { Taskbar } from 'sql/base/browser/ui/taskbar/taskbar';
import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation';
import { IContextMenuService, IContextViewService } from 'vs/platform/contextview/browser/contextView';
import { KernelsDropdown, AttachToDropdown, AddCellAction, TrustedAction, SaveNotebookAction } from 'sql/parts/notebook/notebookActions';
import { IEditorService } from 'vs/workbench/services/editor/common/editorService';
import { attachSelectBoxStyler } from 'vs/platform/theme/common/styler';
import { MenuId, IMenuService, MenuItemAction } from 'vs/platform/actions/common/actions';
import { IAction, Action, IActionItem } from 'vs/base/common/actions';
import { IContextKeyService } from 'vs/platform/contextkey/common/contextkey';
import { IKeybindingService } from 'vs/platform/keybinding/common/keybinding';
import { fillInActions, LabeledMenuItemActionItem } from 'vs/platform/actions/browser/menuItemActionItem';
import { Schemas } from 'vs/base/common/network';
import URI from 'vs/base/common/uri';
import { IHistoryService } from 'vs/workbench/services/history/common/history';
import * as paths from 'vs/base/common/paths';
import { IWindowService } from 'vs/platform/windows/common/windows';
import { TPromise } from 'vs/base/common/winjs.base';
import { IViewletService } from 'vs/workbench/services/viewlet/browser/viewlet';
import { VIEWLET_ID, IExtensionsViewlet } from 'vs/workbench/parts/extensions/common/extensions';
import { CommonServiceInterface } from 'sql/services/common/commonServiceInterface.service';
import { AngularDisposable } from 'sql/base/common/lifecycle';
import { CellTypes, CellType } from 'sql/parts/notebook/models/contracts';
import { ICellModel, IModelFactory, notebookConstants, INotebookModel, NotebookContentChange } from 'sql/parts/notebook/models/modelInterfaces';
import { IConnectionManagementService, IConnectionDialogService } from 'sql/parts/connection/common/connectionManagement';
import { INotebookService, INotebookParams, INotebookManager, INotebookEditor, DEFAULT_NOTEBOOK_FILETYPE, DEFAULT_NOTEBOOK_PROVIDER } from 'sql/services/notebook/notebookService';
import { IBootstrapParams } from 'sql/services/bootstrap/bootstrapService';
import { NotebookModel } from 'sql/parts/notebook/models/notebookModel';
import { ModelFactory } from 'sql/parts/notebook/models/modelFactory';
import * as notebookUtils from 'sql/parts/notebook/notebookUtils';
import { Deferred } from 'sql/base/common/promise';
import { IConnectionProfile } from 'sql/parts/connection/common/interfaces';
import { Taskbar } from 'sql/base/browser/ui/taskbar/taskbar';
import { KernelsDropdown, AttachToDropdown, AddCellAction, TrustedAction, SaveNotebookAction } from 'sql/parts/notebook/notebookActions';
import { IObjectExplorerService } from 'sql/parts/objectExplorer/common/objectExplorerService';
import * as TaskUtilities from 'sql/workbench/common/taskUtilities';
import { IEditorService } from 'vs/workbench/services/editor/common/editorService';
import { ISingleNotebookEditOperation } from 'sql/workbench/api/common/sqlExtHostTypes';
import { IResourceInput } from 'vs/platform/editor/common/editor';
import { IUntitledEditorService } from 'vs/workbench/services/untitled/common/untitledEditorService';
import { IEditorGroupsService } from 'vs/workbench/services/group/common/editorGroupsService';
export const NOTEBOOK_SELECTOR: string = 'notebook-component';
@@ -81,7 +89,12 @@ export class NotebookComponent extends AngularDisposable implements OnInit, OnDe
@Inject(IConnectionDialogService) private connectionDialogService: IConnectionDialogService,
@Inject(IContextKeyService) private contextKeyService: IContextKeyService,
@Inject(IMenuService) private menuService: IMenuService,
@Inject(IKeybindingService) private keybindingService: IKeybindingService
@Inject(IKeybindingService) private keybindingService: IKeybindingService,
@Inject(IHistoryService) private historyService: IHistoryService,
@Inject(IWindowService) private windowService: IWindowService,
@Inject(IViewletService) private viewletService: IViewletService,
@Inject(IUntitledEditorService) private untitledEditorService: IUntitledEditorService,
@Inject(IEditorGroupsService) private editorGroupService: IEditorGroupsService
) {
super();
this.updateProfile();
@@ -132,7 +145,7 @@ export class NotebookComponent extends AngularDisposable implements OnInit, OnDe
return this._modelRegisteredDeferred.promise;
}
protected get cells(): ReadonlyArray<ICellModel> {
public get cells(): ICellModel[] {
return this._model ? this._model.cells : [];
}
@@ -222,6 +235,7 @@ export class NotebookComponent extends AngularDisposable implements OnInit, OnDe
}
private async loadModel(): Promise<void> {
await this.awaitNonDefaultProvider();
this.notebookManager = await this.notebookService.getOrCreateNotebookManager(this._notebookParams.providerId, this._notebookParams.notebookUri);
let model = new NotebookModel({
factory: this.modelFactory,
@@ -241,6 +255,34 @@ export class NotebookComponent extends AngularDisposable implements OnInit, OnDe
this._changeRef.detectChanges();
}
private async awaitNonDefaultProvider(): Promise<void> {
// 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) {
this._notebookParams.providerId = notebookUtils.getProviderForFileName(this._notebookParams.notebookUri.fsPath, this.notebookService);
}
if (DEFAULT_NOTEBOOK_PROVIDER === this._notebookParams.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'),
[{
label: localize('installSql2019Extension', 'Install Extension'),
run: () => this.openExtensionGallery()
}]);
}
}
private async openExtensionGallery(): Promise<void> {
try {
let viewlet = await this.viewletService.openViewlet(VIEWLET_ID, true) as IExtensionsViewlet;
viewlet.search('sql-vnext');
viewlet.focus();
} catch (error) {
this.notificationService.error(error.message);
}
}
// Updates toolbar components
private updateToolbarComponents(isTrusted: boolean)
{
@@ -332,7 +374,78 @@ export class NotebookComponent extends AngularDisposable implements OnInit, OnDe
}
}
// Gets file path from recent workspace in local
private getLastActiveFilePath(untitledResource: URI): string {
let fileName = untitledResource.path + '.' + DEFAULT_NOTEBOOK_FILETYPE.toLocaleLowerCase();
let lastActiveFile = this.historyService.getLastActiveFile();
if (lastActiveFile) {
return URI.file(paths.join(paths.dirname(lastActiveFile.fsPath), fileName)).fsPath;
}
let lastActiveFolder = this.historyService.getLastActiveWorkspaceRoot('file');
if (lastActiveFolder) {
return URI.file(paths.join(lastActiveFolder.fsPath, fileName)).fsPath;
}
return fileName;
}
promptForPath(defaultPath: string): TPromise<string> {
return this.windowService.showSaveDialog({
defaultPath: defaultPath,
filters: [{ name: localize('notebookFile', 'Notebook'), extensions: ['ipynb']}]
});
}
// Entry point to save notebook
public async save(): Promise<boolean> {
let self = this;
let notebookUri = this.notebookParams.notebookUri;
if (notebookUri.scheme === Schemas.untitled) {
let dialogPath = this.getLastActiveFilePath(notebookUri);
return this.promptForPath(dialogPath).then(path => {
if (path) {
let target = URI.file(path);
let resource = self._model.notebookUri;
self._model.notebookUri = target;
this.saveNotebook().then(result => {
if(result)
{
return this.replaceUntitledNotebookEditor(resource, target);
}
return result;
});
}
return false; // User clicks cancel
});
}
else {
return await this.saveNotebook();
}
}
// Replaces untitled notebook editor with the saved file name
private async replaceUntitledNotebookEditor(resource: URI, target: URI): Promise<boolean> {
let encodingOfSource = this.untitledEditorService.getEncoding(resource);
const replacement: IResourceInput = {
resource: target,
encoding: encodingOfSource,
options: {
pinned: true
}
};
return TPromise.join(this.editorGroupService.groups.map(g =>
this.editorService.replaceEditors([{
editor: { resource },
replacement
}], g))).then(() => {
this.notebookService.renameNotebookEditor(resource, target, this);
return true;
});
}
private async saveNotebook(): Promise<boolean> {
try {
let saved = await this._model.saveModel();
if (saved) {
@@ -368,6 +481,10 @@ export class NotebookComponent extends AngularDisposable implements OnInit, OnDe
return this._notebookParams.notebookUri.toString();
}
public get modelReady(): Promise<INotebookModel> {
return this._modelReadyDeferred.promise;
}
isActive(): boolean {
return this.editorService.activeEditor === this.notebookParams.input;
}

View File

@@ -37,7 +37,7 @@
}
.notebookEditor .monaco-select-box {
min-width: 150px;
min-width: 100px;
}
.notebookEditor .notebook-button.icon-add{
@@ -74,4 +74,9 @@
.vs-dark .notebookEditor .notebook-button.icon-save,
.hc-black .notebookEditor .notebook-button.icon-save{
background-image: url("./media/dark/save_inverse.svg");
}
.moreActions .action-label.icon.toggle-more {
height: 20px;
width: 20px;
}

View File

@@ -26,7 +26,6 @@ const attachToLabel: string = localize('AttachTo', 'Attach to: ');
const msgLoadingContexts = localize('loadingContexts', 'Loading contexts...');
const msgAddNewConnection = localize('addNewConnection', 'Add new connection');
const msgSelectConnection = localize('selectConnection', 'Select connection');
const msgConnectionNotApplicable = localize('connectionNotSupported', 'n/a');
const msgLocalHost = localize('localhost', 'Localhost');
// Action to add a cell to notebook based on cell type(code/markdown).
@@ -65,8 +64,6 @@ export class SaveNotebookAction extends Action {
let saved = await context.save();
if (saved) {
this._notificationService.notify({ severity: Severity.Info, message: SaveNotebookAction.notebookSavedMsg, actions });
} else {
this._notificationService.error(SaveNotebookAction.notebookFailedSaveMsg);
}
return saved;
}

View File

@@ -177,4 +177,20 @@ export class NotebookInput extends EditorInput {
setDirty(isDirty: boolean): void {
this._model.setDirty(isDirty);
}
public matches(otherInput: any): boolean {
if (super.matches(otherInput) === true) {
return true;
}
if (otherInput instanceof NotebookInput) {
const otherNotebookEditorInput = <NotebookInput>otherInput;
// Compare by resource
return otherNotebookEditorInput.notebookUri.toString() === this.notebookUri.toString();
}
return false;
}
}

View File

@@ -11,9 +11,7 @@ import * as os from 'os';
import * as pfs from 'vs/base/node/pfs';
import { localize } from 'vs/nls';
import { IOutputChannel } from 'vs/workbench/parts/output/common/output';
import { Registry } from 'vs/platform/registry/common/platform';
import { INotebookProviderRegistry, Extensions } from 'sql/services/notebook/notebookRegistry';
import { DEFAULT_NOTEBOOK_PROVIDER, DEFAULT_NOTEBOOK_FILETYPE } from 'sql/services/notebook/notebookService';
import { DEFAULT_NOTEBOOK_PROVIDER, DEFAULT_NOTEBOOK_FILETYPE, INotebookService } from 'sql/services/notebook/notebookService';
/**
@@ -41,18 +39,17 @@ export async function mkDir(dirPath: string, outputChannel?: IOutputChannel): Pr
}
}
export function getProviderForFileName(fileName: string): string {
export function getProviderForFileName(fileName: string, notebookService: INotebookService): string {
let fileExt = path.extname(fileName);
let provider: string;
let notebookRegistry = Registry.as<INotebookProviderRegistry>(Extensions.NotebookProviderContribution);
// First try to get provider for actual file type
if (fileExt && fileExt.startsWith('.')) {
fileExt = fileExt.slice(1,fileExt.length);
provider = notebookRegistry.getProviderForFileType(fileExt);
provider = notebookService.getProviderForFileType(fileExt);
}
// Fallback to provider for default file type (assume this is a global handler)
if (!provider) {
provider = notebookRegistry.getProviderForFileType(DEFAULT_NOTEBOOK_FILETYPE);
provider = notebookService.getProviderForFileType(DEFAULT_NOTEBOOK_FILETYPE);
}
// Finally if all else fails, use the built-in handler
if (!provider) {

View File

@@ -22,7 +22,7 @@ import { Widget } from 'vs/base/browser/ui/widget';
import { Sash, IHorizontalSashLayoutProvider, ISashEvent, Orientation } from 'vs/base/browser/ui/sash/sash';
import { IKeybindingService } from 'vs/platform/keybinding/common/keybinding';
import { IOverlayWidget, IOverlayWidgetPosition, OverlayWidgetPositionPreference } from 'vs/editor/browser/editorBrowser';
import { FIND_IDS, MATCHES_LIMIT, CONTEXT_FIND_INPUT_FOCUSED } from 'vs/editor/contrib/find/findModel';
import { FIND_IDS, CONTEXT_FIND_INPUT_FOCUSED } from 'vs/editor/contrib/find/findModel';
import { FindReplaceState, FindReplaceStateChangedEvent } from 'vs/editor/contrib/find/findState';
import { IContextKeyService, IContextKey } from 'vs/platform/contextkey/common/contextkey';
import { ITheme, registerThemingParticipant, IThemeService } from 'vs/platform/theme/common/themeService';
@@ -36,7 +36,7 @@ const NLS_FIND_INPUT_PLACEHOLDER = nls.localize('placeholder.find', "Find");
const NLS_PREVIOUS_MATCH_BTN_LABEL = nls.localize('label.previousMatchButton', "Previous match");
const NLS_NEXT_MATCH_BTN_LABEL = nls.localize('label.nextMatchButton', "Next match");
const NLS_CLOSE_BTN_LABEL = nls.localize('label.closeButton', "Close");
const NLS_MATCHES_COUNT_LIMIT_TITLE = nls.localize('title.matchesCountLimit', "Only the first 999 results are highlighted, but all find operations work on the entire text.");
const NLS_MATCHES_COUNT_LIMIT_TITLE = nls.localize('title.matchesCountLimit', "Your search returned a large number of results, only the first 999 matches will be highlighted.");
const NLS_MATCHES_LOCATION = nls.localize('label.matchesLocation', "{0} of {1}");
const NLS_NO_RESULTS = nls.localize('label.noResults', "No Results");
@@ -46,6 +46,8 @@ const FIND_INPUT_AREA_WIDTH = PART_WIDTH - 54;
let MAX_MATCHES_COUNT_WIDTH = 69;
export const PROFILER_MAX_MATCHES = 999;
export const ACTION_IDS = {
FIND_NEXT: 'findNext',
FIND_PREVIOUS: 'findPrev'
@@ -86,6 +88,8 @@ export class FindWidget extends Widget implements IOverlayWidget, IHorizontalSas
private _resizeSash: Sash;
private searchTimeoutHandle: number;
constructor(
tableController: ITableController,
state: FindReplaceState,
@@ -213,7 +217,7 @@ export class FindWidget extends Widget implements IOverlayWidget, IHorizontalSas
private _updateMatchesCount(): void {
this._matchesCount.style.minWidth = MAX_MATCHES_COUNT_WIDTH + 'px';
if (this._state.matchesCount >= MATCHES_LIMIT) {
if (this._state.matchesCount >= PROFILER_MAX_MATCHES) {
this._matchesCount.title = NLS_MATCHES_COUNT_LIMIT_TITLE;
} else {
this._matchesCount.title = '';
@@ -227,8 +231,8 @@ export class FindWidget extends Widget implements IOverlayWidget, IHorizontalSas
let label: string;
if (this._state.matchesCount > 0) {
let matchesCount: string = String(this._state.matchesCount);
if (this._state.matchesCount >= MATCHES_LIMIT) {
matchesCount += '+';
if (this._state.matchesCount >= PROFILER_MAX_MATCHES) {
matchesCount = PROFILER_MAX_MATCHES + '+';
}
let matchesPosition: string = String(this._state.matchesPosition);
if (matchesPosition === '0') {
@@ -401,7 +405,14 @@ export class FindWidget extends Widget implements IOverlayWidget, IHorizontalSas
this._findInput.setWholeWords(!!this._state.wholeWord);
this._register(this._findInput.onKeyDown((e) => this._onFindInputKeyDown(e)));
this._register(this._findInput.onInput(() => {
this._state.change({ searchString: this._findInput.getValue() }, true);
let self = this;
if (self.searchTimeoutHandle) {
clearTimeout(self.searchTimeoutHandle);
}
this.searchTimeoutHandle = setTimeout(function () {
self._state.change({ searchString: self._findInput.getValue() }, true);
}, 300);
}));
this._register(this._findInput.onDidOptionChange(() => {
this._state.change({

View File

@@ -27,6 +27,7 @@ import { Event, Emitter } from 'vs/base/common/event';
import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation';
import { Dimension } from 'vs/base/browser/dom';
import { textFormatter } from 'sql/parts/grid/services/sharedServices';
import { PROFILER_MAX_MATCHES } from 'sql/parts/profiler/editor/controller/profilerFindWidget';
export interface ProfilerTableViewState {
scrollTop: number;
@@ -214,7 +215,7 @@ export class ProfilerTableEditor extends BaseEditor implements IProfilerControll
if (e.searchString) {
if (this._input && this._input.data) {
if (this._findState.searchString) {
this._input.data.find(this._findState.searchString).then(p => {
this._input.data.find(this._findState.searchString, PROFILER_MAX_MATCHES).then(p => {
if (p) {
this._profilerTable.setActiveCell(p.row, p.col);
this._updateFinderMatchState();

View File

@@ -305,6 +305,11 @@ let registryProperties = {
'description': localize('sql.saveAsCsv.encoding', '[Optional] File encoding used when saving results as CSV'),
'default': 'utf-8'
},
'sql.results.streaming': {
'type': 'boolean',
'description': localize('sql.results.streaming', 'Enable results streaming; contains few minor visual issues'),
'default': false
},
'sql.copyIncludeHeaders': {
'type': 'boolean',
'description': localize('sql.copyIncludeHeaders', '[Optional] Configuration options for copying results from the Results View'),

View File

@@ -88,7 +88,8 @@ export class GridTableState extends Disposable {
private _canBeMaximized: boolean;
/* The top row of the current scroll */
public scrollPosition = 0;
public scrollPositionY = 0;
public scrollPositionX = 0;
public selection: Slick.Range[];
public activeCell: Slick.Cell;
@@ -145,7 +146,7 @@ export class GridPanel extends ViewletPanel {
super(options, keybindingService, contextMenuService, configurationService);
this.splitView = new ScrollableSplitView(this.container, { enableResizing: false, verticalScrollbarVisibility: ScrollbarVisibility.Visible });
this.splitView.onScroll(e => {
if (this.state) {
if (this.state && this.splitView.length !== 0) {
this.state.scrollPosition = e;
}
});
@@ -185,62 +186,97 @@ export class GridPanel extends ViewletPanel {
}
this.reset();
}));
this.addResultSet(this.runner.batchSets.reduce<sqlops.ResultSetSummary[]>((p, e) => {
if (this.configurationService.getValue<boolean>('sql.results.streaming')) {
p = p.concat(e.resultSetSummaries);
} else {
p = p.concat(e.resultSetSummaries.filter(c => c.complete));
}
return p;
}, []));
this.maximumBodySize = this.tables.reduce((p, c) => {
return p + c.maximumSize;
}, 0);
if (this.state && this.state.scrollPosition) {
this.splitView.setScrollPosition(this.state.scrollPosition);
}
}
private onResultSet(resultSet: sqlops.ResultSetSummary | sqlops.ResultSetSummary[]) {
this.addResultSet(resultSet);
this.tables.map(t => {
t.state.canBeMaximized = this.tables.length > 1;
});
this.maximumBodySize = this.tables.reduce((p, c) => {
return p + c.maximumSize;
}, 0);
if (this.state && this.state.scrollPosition) {
this.splitView.setScrollPosition(this.state.scrollPosition);
}
}
private updateResultSet(resultSet: sqlops.ResultSetSummary | sqlops.ResultSetSummary[]) {
let resultsToUpdate: sqlops.ResultSetSummary[];
if (!Array.isArray(resultSet)) {
resultsToUpdate = [resultSet];
} else {
resultsToUpdate = resultSet;
}
for (let set of resultsToUpdate) {
let table = this.tables.find(t => t.resultSet.batchId === set.batchId && t.resultSet.id === set.id);
if (table) {
table.updateResult(set);
} else {
warn('Got result set update request for non-existant table');
}
}
this.maximumBodySize = this.tables.reduce((p, c) => {
return p + c.maximumSize;
}, 0);
if (this.state && this.state.scrollPosition) {
this.splitView.setScrollPosition(this.state.scrollPosition);
}
}
private addResultSet(resultSet: sqlops.ResultSetSummary | sqlops.ResultSetSummary[]) {
let resultsToAdd: sqlops.ResultSetSummary[];
if (!Array.isArray(resultSet)) {
resultsToAdd = [resultSet];
} else {
resultsToAdd = resultSet;
resultsToAdd = resultSet.splice(0);
}
const sizeChanges = () => {
this.tables.map(t => {
t.state.canBeMaximized = this.tables.length > 1;
});
this.maximumBodySize = this.tables.reduce((p, c) => {
return p + c.maximumSize;
}, 0);
if (this.state && this.state.scrollPosition) {
this.splitView.setScrollPosition(this.state.scrollPosition);
}
};
if (this.configurationService.getValue<boolean>('sql.results.streaming')) {
this.addResultSet(resultsToAdd);
sizeChanges();
} else {
resultsToAdd = resultsToAdd.filter(e => e.complete);
if (resultsToAdd.length > 0) {
this.addResultSet(resultsToAdd);
}
sizeChanges();
}
}
private updateResultSet(resultSet: sqlops.ResultSetSummary | sqlops.ResultSetSummary[]) {
let resultsToUpdate: sqlops.ResultSetSummary[];
if (!Array.isArray(resultSet)) {
resultsToUpdate = [resultSet];
} else {
resultsToUpdate = resultSet.splice(0);
}
const sizeChanges = () => {
this.maximumBodySize = this.tables.reduce((p, c) => {
return p + c.maximumSize;
}, 0);
if (this.state && this.state.scrollPosition) {
this.splitView.setScrollPosition(this.state.scrollPosition);
}
};
if (this.configurationService.getValue<boolean>('sql.results.streaming')) {
for (let set of resultsToUpdate) {
let table = this.tables.find(t => t.resultSet.batchId === set.batchId && t.resultSet.id === set.id);
if (table) {
table.updateResult(set);
} else {
warn('Got result set update request for non-existant table');
}
}
sizeChanges();
} else {
resultsToUpdate = resultsToUpdate.filter(e => e.complete);
if (resultsToUpdate.length > 0) {
this.addResultSet(resultsToUpdate);
}
sizeChanges();
}
}
private addResultSet(resultSet: sqlops.ResultSetSummary[]) {
let tables: GridTable<any>[] = [];
for (let set of resultsToAdd) {
for (let set of resultSet) {
let tableState: GridTableState;
if (this._state) {
tableState = this.state.tableStates.find(e => e.batchId === set.batchId && e.resultId === set.id);
@@ -420,6 +456,7 @@ class GridTable<T> extends Disposable implements IView {
});
this.dataProvider.dataRows = collection;
this.table.updateRowCount();
this.setupState();
}
public onRemove() {
@@ -513,12 +550,18 @@ class GridTable<T> extends Disposable implements IView {
});
this.table.grid.onScroll.subscribe((e, data) => {
if (!this.scrolled && this.state.scrollPosition && isInDOM(this.container)) {
if (!this.visible) {
// If the grid is not set up yet it can get scroll events resetting the top to 0px,
// so ignore those events
return;
}
if (!this.scrolled && (this.state.scrollPositionY || this.state.scrollPositionX) && isInDOM(this.container)) {
this.scrolled = true;
this.table.grid.scrollTo(this.state.scrollPosition);
this.restoreScrollState();
}
if (this.state && isInDOM(this.container)) {
this.state.scrollPosition = data.scrollTop;
this.state.scrollPositionY = data.scrollTop;
this.state.scrollPositionX = data.scrollLeft;
}
});
@@ -527,8 +570,13 @@ class GridTable<T> extends Disposable implements IView {
this.state.activeCell = this.table.grid.getActiveCell();
}
});
}
this.setupState();
private restoreScrollState() {
if (this.state.scrollPositionX || this.state.scrollPositionY) {
this.table.grid.scrollTo(this.state.scrollPositionY);
this.table.grid.getContainerNode().children[3].scrollLeft = this.state.scrollPositionX;
}
}
private setupState() {
@@ -537,20 +585,18 @@ class GridTable<T> extends Disposable implements IView {
this._register(this.state.onCanBeMaximizedChange(this.rebuildActionBar, this));
if (this.state.scrollPosition) {
// most of the time this won't do anything
this.table.grid.scrollTo(this.state.scrollPosition);
// the problem here is that the scrolling state slickgrid uses
// doesn't work with it offDOM.
}
this.restoreScrollState();
if (this.state.selection) {
this.selectionModel.setSelectedRanges(this.state.selection);
}
// Setting the active cell resets the selection so save it here
let savedSelection = this.state.selection;
if (this.state.activeCell) {
this.table.setActiveCell(this.state.activeCell.row, this.state.activeCell.cell);
}
if (savedSelection) {
this.selectionModel.setSelectedRanges(savedSelection);
}
}
public get state(): GridTableState {
@@ -601,6 +647,7 @@ class GridTable<T> extends Disposable implements IView {
this.table.updateRowCount();
}
this.rowNumberColumn.updateRowCount(resultSet.rowCount);
this._onDidChange.fire();
}
private generateContext(cell?: Slick.Cell): IGridActionContext {

View File

@@ -144,6 +144,7 @@ export class MessagePanel extends ViewletPanel {
this.reset();
this.queryRunnerDisposables.push(runner.onQueryStart(() => this.reset()));
this.queryRunnerDisposables.push(runner.onMessage(e => this.onMessage(e)));
this.onMessage(runner.messages);
}
private onMessage(message: IResultMessage | IResultMessage[]) {

View File

@@ -387,8 +387,12 @@ export class QueryModelService implements IQueryModelService {
// We do not have a query runner for this editor, so create a new one
// and map it to the results uri
queryRunner = this._instantiationService.createInstance(QueryRunner, ownerUri);
const resultSetEventType = 'resultSet';
queryRunner.addListener(QREvents.RESULT_SET, resultSet => {
this._fireQueryEvent(ownerUri, 'resultSet', resultSet);
this._fireQueryEvent(ownerUri, resultSetEventType, resultSet);
});
queryRunner.onResultSetUpdate(resultSetSummary => {
this._fireQueryEvent(ownerUri, resultSetEventType, resultSetSummary);
});
queryRunner.addListener(QREvents.BATCH_START, batch => {
let link = undefined;

View File

@@ -13,6 +13,7 @@ import { IQueryManagementService } from 'sql/parts/query/common/queryManagement'
import * as Utils from 'sql/parts/connection/common/utils';
import { SaveFormat } from 'sql/parts/grid/common/interfaces';
import { echo, debounceEvent } from 'sql/base/common/event';
import { Deferred } from 'sql/base/common/promise';
import Severity from 'vs/base/common/severity';
import { IWorkspaceConfigurationService } from 'vs/workbench/services/configuration/common/configuration';
@@ -26,7 +27,6 @@ import { Emitter, Event } from 'vs/base/common/event';
import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation';
import { ResultSerializer } from 'sql/parts/query/common/resultSerializer';
import { TPromise } from 'vs/base/common/winjs.base';
import { Deferred } from 'sql/base/common/promise';
export interface IEditSessionReadyEvent {
ownerUri: string;
@@ -69,6 +69,7 @@ export default class QueryRunner extends Disposable {
private _isExecuting: boolean = false;
private _hasCompleted: boolean = false;
private _batchSets: sqlops.BatchSummary[] = [];
private _messages: sqlops.IResultMessage[] = [];
private _eventEmitter = new EventEmitter();
private _isQueryPlan: boolean;
@@ -77,40 +78,13 @@ export default class QueryRunner extends Disposable {
public get planXml(): Thenable<string> { return this._planXml.promise; }
private _onMessage = this._register(new Emitter<sqlops.IResultMessage>());
private _debouncedMessage = debounceEvent<sqlops.IResultMessage, sqlops.IResultMessage[]>(this._onMessage.event, (l, e) => {
// on first run
if (types.isUndefinedOrNull(l)) {
return [e];
} else {
return l.concat(e);
}
});
private _echoedMessages = echo(this._debouncedMessage.event);
public readonly onMessage = this._echoedMessages.event;
public readonly onMessage = this._onMessage.event;
private _onResultSet = this._register(new Emitter<sqlops.ResultSetSummary>());
private _debouncedResultSet = debounceEvent<sqlops.ResultSetSummary, sqlops.ResultSetSummary[]>(this._onResultSet.event, (l, e) => {
// on first run
if (types.isUndefinedOrNull(l)) {
return [e];
} else {
return l.concat(e);
}
});
private _echoedResultSet = echo(this._debouncedResultSet.event);
public readonly onResultSet = this._echoedResultSet.event;
public readonly onResultSet = this._onResultSet.event;
private _onResultSetUpdate = this._register(new Emitter<sqlops.ResultSetSummary>());
private _debouncedResultSetUpdate = debounceEvent<sqlops.ResultSetSummary, sqlops.ResultSetSummary[]>(this._onResultSetUpdate.event, (l, e) => {
// on first run
if (types.isUndefinedOrNull(l)) {
return [e];
} else {
return l.concat(e);
}
});
private _echoedResultSetUpdate = echo(this._debouncedResultSetUpdate.event);
public readonly onResultSetUpdate = this._echoedResultSetUpdate.event;
public readonly onResultSetUpdate = this._onResultSetUpdate.event;
private _onQueryStart = this._register(new Emitter<void>());
public readonly onQueryStart: Event<void> = this._onQueryStart.event;
@@ -153,8 +127,18 @@ export default class QueryRunner extends Disposable {
return this._hasCompleted;
}
get batchSets(): sqlops.BatchSummary[] {
return this._batchSets;
/**
* For public use only, for private use, directly access the member
*/
public get batchSets(): sqlops.BatchSummary[] {
return this._batchSets.slice(0);
}
/**
* For public use only, for private use, directly access the member
*/
public get messages(): sqlops.IResultMessage[] {
return this._messages.slice(0);
}
// PUBLIC METHODS ======================================================
@@ -202,10 +186,6 @@ export default class QueryRunner extends Disposable {
if (this.isExecuting) {
return TPromise.as(undefined);
}
this._echoedMessages.clear();
this._echoedResultSet.clear();
this._debouncedMessage.clear();
this._debouncedResultSet.clear();
this._planXml = new Deferred<string>();
this._batchSets = [];
this._hasCompleted = false;
@@ -274,7 +254,7 @@ export default class QueryRunner extends Disposable {
this._hasCompleted = true;
this._batchSets = result.batchSummaries ? result.batchSummaries : [];
this.batchSets.map(batch => {
this._batchSets.map(batch => {
if (batch.selection) {
batch.selection.startLine = batch.selection.startLine + this._resultLineOffset;
batch.selection.endLine = batch.selection.endLine + this._resultLineOffset;
@@ -291,6 +271,7 @@ export default class QueryRunner extends Disposable {
isError: false,
time: undefined
};
this._messages.push(message);
this._onQueryEnd.fire(timeStamp);
this._onMessage.fire(message);
@@ -312,7 +293,7 @@ export default class QueryRunner extends Disposable {
batch.resultSetSummaries = [];
// Store the batch
this.batchSets[batch.id] = batch;
this._batchSets[batch.id] = batch;
let message = {
// account for index by 1
@@ -321,6 +302,7 @@ export default class QueryRunner extends Disposable {
selection: batch.selection,
isError: false
};
this._messages.push(message);
this._eventEmitter.emit(EventType.BATCH_START, batch);
this._onMessage.fire(message);
this._onBatchStart.fire(batch);
@@ -333,7 +315,7 @@ export default class QueryRunner extends Disposable {
let batch: sqlops.BatchSummary = result.batchSummary;
// Store the batch again to get the rest of the data
this.batchSets[batch.id] = batch;
this._batchSets[batch.id] = batch;
let executionTime = <number>(Utils.parseTimeString(batch.executionElapsed) || 0);
this._totalElapsedMilliseconds += executionTime;
if (executionTime > 0) {
@@ -355,8 +337,8 @@ export default class QueryRunner extends Disposable {
if (!resultSet.batchId) {
// Missing the batchId. In this case, default to always using the first batch in the list
// or create one in the case the DMP extension didn't obey the contract perfectly
if (this.batchSets.length > 0) {
batchSet = this.batchSets[0];
if (this._batchSets.length > 0) {
batchSet = this._batchSets[0];
} else {
batchSet = <sqlops.BatchSummary>{
id: 0,
@@ -364,10 +346,10 @@ export default class QueryRunner extends Disposable {
hasError: false,
resultSetSummaries: []
};
this.batchSets[0] = batchSet;
this._batchSets[0] = batchSet;
}
} else {
batchSet = this.batchSets[resultSet.batchId];
batchSet = this._batchSets[resultSet.batchId];
}
// handle getting queryPlanxml if we need too
if (this.isQueryPlan) {
@@ -392,7 +374,7 @@ export default class QueryRunner extends Disposable {
if (result && result.resultSetSummary) {
let resultSet = result.resultSetSummary;
let batchSet: sqlops.BatchSummary;
batchSet = this.batchSets[resultSet.batchId];
batchSet = this._batchSets[resultSet.batchId];
// handle getting queryPlanxml if we need too
if (this.isQueryPlan) {
// check if this result has show plan, this needs work, it won't work for any other provider
@@ -415,6 +397,7 @@ export default class QueryRunner extends Disposable {
public handleMessage(obj: sqlops.QueryExecuteMessageParams): void {
let message = obj.message;
message.time = new Date(message.time).toLocaleTimeString();
this._messages.push(message);
// Send the message to the results pane
this._eventEmitter.emit(EventType.MESSAGE, message);
@@ -434,10 +417,10 @@ export default class QueryRunner extends Disposable {
};
return this._queryManagementService.getQueryRows(rowData).then(r => r, error => {
this._notificationService.notify({
severity: Severity.Error,
message: nls.localize('query.gettingRowsFailedError', 'Something went wrong getting more rows: {0}', error)
});
// this._notificationService.notify({
// severity: Severity.Error,
// message: nls.localize('query.gettingRowsFailedError', 'Something went wrong getting more rows: {0}', error)
// });
return error;
});
}
@@ -492,11 +475,11 @@ export default class QueryRunner extends Disposable {
}
resolve(result);
}, error => {
let errorMessage = nls.localize('query.moreRowsFailedError', 'Something went wrong getting more rows:');
self._notificationService.notify({
severity: Severity.Error,
message: `${errorMessage} ${error}`
});
// let errorMessage = nls.localize('query.moreRowsFailedError', 'Something went wrong getting more rows:');
// self._notificationService.notify({
// severity: Severity.Error,
// message: `${errorMessage} ${error}`
// });
reject(error);
});
});
@@ -636,7 +619,7 @@ export default class QueryRunner extends Disposable {
private getColumnHeaders(batchId: number, resultId: number, range: Slick.Range): string[] {
let headers: string[] = undefined;
let batchSummary: sqlops.BatchSummary = this.batchSets[batchId];
let batchSummary: sqlops.BatchSummary = this._batchSets[batchId];
if (batchSummary !== undefined) {
let resultSetSummary = batchSummary.resultSetSummaries[resultId];
headers = resultSetSummary.columnInfo.slice(range.fromCell, range.toCell + 1).map((info, i) => {
@@ -669,6 +652,7 @@ export default class QueryRunner extends Disposable {
time: undefined,
isError: false
};
this._messages.push(message);
// Send the message to the results pane
this._onMessage.fire(message);
}

View File

@@ -97,15 +97,6 @@ export class WizardModal extends Modal {
messageChangeHandler(this._wizard.message);
this._wizard.onMessageChange(message => messageChangeHandler(message));
this._wizard.pages.forEach((page, index) => {
page.onValidityChanged(valid => {
if (index === this._wizard.currentPage) {
this._nextButton.enabled = this._wizard.nextButton.enabled && page.valid;
this._doneButton.enabled = this._wizard.doneButton.enabled && page.valid;
}
});
});
}
private addDialogButton(button: DialogButton, onSelect: () => void = () => undefined, registerClickEvent: boolean = true, requirePageValid: boolean = false): Button {
@@ -198,6 +189,13 @@ export class WizardModal extends Modal {
let currentPageValid = this._wizard.pages[this._wizard.currentPage].valid;
this._nextButton.enabled = this._wizard.nextButton.enabled && currentPageValid;
this._doneButton.enabled = this._wizard.doneButton.enabled && currentPageValid;
pageToShow.onValidityChanged(valid => {
if (index === this._wizard.currentPage) {
this._nextButton.enabled = this._wizard.nextButton.enabled && pageToShow.valid;
this._doneButton.enabled = this._wizard.doneButton.enabled && pageToShow.valid;
}
});
}
private setButtonsForPage(index: number) {

View File

@@ -9,12 +9,13 @@ import { IJSONSchema } from 'vs/base/common/jsonSchema';
import { ExtensionsRegistry, IExtensionPointUser } from 'vs/workbench/services/extensions/common/extensionsRegistry';
import { localize } from 'vs/nls';
import * as platform from 'vs/platform/registry/common/platform';
import { Event, Emitter } from 'vs/base/common/event';
export const Extensions = {
NotebookProviderContribution: 'notebook.providers'
};
export interface NotebookProviderDescription {
export interface NotebookProviderRegistration {
provider: string;
fileExtensions: string | string[];
}
@@ -54,42 +55,28 @@ let notebookContrib: IJSONSchema = {
};
export interface INotebookProviderRegistry {
registerNotebookProvider(provider: NotebookProviderDescription): void;
getSupportedFileExtensions(): string[];
getProviderForFileType(fileType: string): string;
readonly registrations: NotebookProviderRegistration[];
readonly onNewRegistration: Event<{ id: string, registration: NotebookProviderRegistration }>;
registerNotebookProvider(registration: NotebookProviderRegistration): void;
}
class NotebookProviderRegistry implements INotebookProviderRegistry {
private providerIdToProviders = new Map<string, NotebookProviderDescription>();
private fileToProviders = new Map<string, NotebookProviderDescription>();
private providerIdToRegistration = new Map<string, NotebookProviderRegistration>();
private _onNewRegistration = new Emitter<{ id: string, registration: NotebookProviderRegistration }>();
public readonly onNewRegistration: Event<{ id: string, registration: NotebookProviderRegistration }> = this._onNewRegistration.event;
registerNotebookProvider(provider: NotebookProviderDescription): void {
registerNotebookProvider(registration: NotebookProviderRegistration): void {
// Note: this method intentionally overrides default provider for a file type.
// This means that any built-in provider will be overridden by registered extensions
this.providerIdToProviders.set(provider.provider, provider);
if (provider.fileExtensions) {
if (Array.isArray<string>(provider.fileExtensions)) {
for (let fileType of provider.fileExtensions) {
this.addFileProvider(fileType, provider);
}
} else {
this.addFileProvider(provider.fileExtensions, provider);
}
}
this.providerIdToRegistration.set(registration.provider, registration);
this._onNewRegistration.fire( { id: registration.provider, registration: registration });
}
private addFileProvider(fileType: string, provider: NotebookProviderDescription) {
this.fileToProviders.set(fileType.toUpperCase(), provider);
}
getSupportedFileExtensions(): string[] {
return Array.from(this.fileToProviders.keys());
}
getProviderForFileType(fileType: string): string {
fileType = fileType.toUpperCase();
let provider = this.fileToProviders.get(fileType);
return provider ? provider.provider : undefined;
public get registrations(): NotebookProviderRegistration[] {
let registrationArray: NotebookProviderRegistration[] = [];
this.providerIdToRegistration.forEach(p => registrationArray.push(p));
return registrationArray;
}
}
@@ -97,15 +84,15 @@ const notebookProviderRegistry = new NotebookProviderRegistry();
platform.Registry.add(Extensions.NotebookProviderContribution, notebookProviderRegistry);
ExtensionsRegistry.registerExtensionPoint<NotebookProviderDescription | NotebookProviderDescription[]>(Extensions.NotebookProviderContribution, [], notebookContrib).setHandler(extensions => {
ExtensionsRegistry.registerExtensionPoint<NotebookProviderRegistration | NotebookProviderRegistration[]>(Extensions.NotebookProviderContribution, [], notebookContrib).setHandler(extensions => {
function handleExtension(contrib: NotebookProviderDescription, extension: IExtensionPointUser<any>) {
function handleExtension(contrib: NotebookProviderRegistration, extension: IExtensionPointUser<any>) {
notebookProviderRegistry.registerNotebookProvider(contrib);
}
for (let extension of extensions) {
const { value } = extension;
if (Array.isArray<NotebookProviderDescription>(value)) {
if (Array.isArray<NotebookProviderRegistration>(value)) {
for (let command of value) {
handleExtension(command, extension);
}

View File

@@ -16,6 +16,7 @@ import { ModelFactory } from 'sql/parts/notebook/models/modelFactory';
import { IConnectionProfile } from 'sql/parts/connection/common/interfaces';
import { NotebookInput } from 'sql/parts/notebook/notebookInput';
import { ISingleNotebookEditOperation } from 'sql/workbench/api/common/sqlExtHostTypes';
import { ICellModel, INotebookModel } from 'sql/parts/notebook/models/modelInterfaces';
export const SERVICE_ID = 'notebookService';
export const INotebookService = createDecorator<INotebookService>(SERVICE_ID);
@@ -26,9 +27,12 @@ export const DEFAULT_NOTEBOOK_FILETYPE = 'IPYNB';
export interface INotebookService {
_serviceBrand: any;
onNotebookEditorAdd: Event<INotebookEditor>;
onNotebookEditorRemove: Event<INotebookEditor>;
readonly onNotebookEditorAdd: Event<INotebookEditor>;
readonly onNotebookEditorRemove: Event<INotebookEditor>;
onNotebookEditorRename: Event<INotebookEditor>;
readonly isRegistrationComplete: boolean;
readonly registrationComplete: Promise<void>;
/**
* Register a metadata provider
*/
@@ -39,6 +43,10 @@ export interface INotebookService {
*/
unregisterProvider(providerId: string): void;
getSupportedFileExtensions(): string[];
getProviderForFileType(fileType: string): string;
/**
* Initializes and returns a Notebook manager that can handle all important calls to open, display, and
* run cells in a notebook.
@@ -57,6 +65,8 @@ export interface INotebookService {
shutdown(): void;
getMimeRegistry(): RenderMimeRegistry;
renameNotebookEditor(oldUri: URI, newUri: URI, currentEditor: INotebookEditor): void;
}
export interface INotebookProvider {
@@ -84,6 +94,8 @@ export interface INotebookParams extends IBootstrapParams {
export interface INotebookEditor {
readonly notebookParams: INotebookParams;
readonly id: string;
readonly cells?: ICellModel[];
readonly modelReady: Promise<INotebookModel>;
isDirty(): boolean;
isActive(): boolean;
isVisible(): boolean;

View File

@@ -18,41 +18,143 @@ import { RenderMimeRegistry } from 'sql/parts/notebook/outputs/registry';
import { standardRendererFactories } from 'sql/parts/notebook/outputs/factories';
import { LocalContentManager } from 'sql/services/notebook/localContentManager';
import { SessionManager } from 'sql/services/notebook/sessionManager';
import { Extensions, INotebookProviderRegistry } from 'sql/services/notebook/notebookRegistry';
import { Extensions, INotebookProviderRegistry, NotebookProviderRegistration } from 'sql/services/notebook/notebookRegistry';
import { Emitter, Event } from 'vs/base/common/event';
import { Memento } from 'vs/workbench/common/memento';
import { IStorageService } from 'vs/platform/storage/common/storage';
import { IExtensionService } from 'vs/workbench/services/extensions/common/extensions';
import { IExtensionManagementService, IExtensionIdentifier } from 'vs/platform/extensionManagement/common/extensionManagement';
import { Disposable } from 'vs/base/common/lifecycle';
import { getIdFromLocalExtensionId } from 'vs/platform/extensionManagement/common/extensionManagementUtil';
import { Deferred } from 'sql/base/common/promise';
export interface NotebookProviderProperties {
provider: string;
fileExtensions: string[];
}
export class NotebookService implements INotebookService {
interface NotebookProviderCache {
[id: string]: NotebookProviderProperties;
}
interface NotebookProvidersMemento {
notebookProviderCache: NotebookProviderCache;
}
const notebookRegistry = Registry.as<INotebookProviderRegistry>(Extensions.NotebookProviderContribution);
class ProviderDescriptor {
private _instanceReady = new Deferred<INotebookProvider>();
constructor(private providerId: string, private _instance?: INotebookProvider) {
if (_instance) {
this._instanceReady.resolve(_instance);
}
}
public get instanceReady(): Promise<INotebookProvider> {
return this._instanceReady.promise;
}
public get instance(): INotebookProvider {
return this._instance;
}
public set instance(value: INotebookProvider) {
this._instance = value;
this._instanceReady.resolve(value);
}
}
export class NotebookService extends Disposable implements INotebookService {
_serviceBrand: any;
private _memento = new Memento('notebookProviders');
private _mimeRegistry: RenderMimeRegistry;
private _providers: Map<string, INotebookProvider> = new Map();
private _providers: Map<string, ProviderDescriptor> = new Map();
private _managers: Map<string, INotebookManager> = new Map();
private _onNotebookEditorAdd = new Emitter<INotebookEditor>();
private _onNotebookEditorRemove = new Emitter<INotebookEditor>();
private _onCellChanged = new Emitter<INotebookEditor>();
private _onNotebookEditorRename = new Emitter<INotebookEditor>();
private _editors = new Map<string, INotebookEditor>();
private _fileToProviders = new Map<string, NotebookProviderRegistration>();
private _registrationComplete = new Deferred<void>();
private _isRegistrationComplete = false;
constructor() {
constructor(
@IStorageService private _storageService: IStorageService,
@IExtensionService extensionService: IExtensionService,
@IExtensionManagementService extensionManagementService: IExtensionManagementService
) {
super();
this._register(notebookRegistry.onNewRegistration(this.updateRegisteredProviders, this));
this.registerDefaultProvider();
if (extensionService) {
extensionService.whenInstalledExtensionsRegistered().then(() => {
this.cleanupProviders();
this._isRegistrationComplete = true;
this._registrationComplete.resolve();
});
}
if (extensionManagementService) {
this._register(extensionManagementService.onDidUninstallExtension(({ identifier }) => this.removeContributedProvidersFromCache(identifier, extensionService)));
}
}
private registerDefaultProvider() {
let defaultProvider = new BuiltinProvider();
this.registerProvider(defaultProvider.providerId, defaultProvider);
let registry = Registry.as<INotebookProviderRegistry>(Extensions.NotebookProviderContribution);
registry.registerNotebookProvider({
provider: defaultProvider.providerId,
fileExtensions: DEFAULT_NOTEBOOK_FILETYPE
});
private updateRegisteredProviders(p: { id: string; registration: NotebookProviderRegistration; }) {
let registration = p.registration;
if (!this._providers.has(p.id)) {
this._providers.set(p.id, new ProviderDescriptor(p.id));
}
if (registration.fileExtensions) {
if (Array.isArray<string>(registration.fileExtensions)) {
for (let fileType of registration.fileExtensions) {
this.addFileProvider(fileType, registration);
}
}
else {
this.addFileProvider(registration.fileExtensions, registration);
}
}
}
registerProvider(providerId: string, provider: INotebookProvider): void {
this._providers.set(providerId, provider);
registerProvider(providerId: string, instance: INotebookProvider): void {
let providerDescriptor = this._providers.get(providerId);
if (providerDescriptor) {
// Update, which will resolve the promise for anyone waiting on the instance to be registered
providerDescriptor.instance = instance;
} else {
this._providers.set(providerId, new ProviderDescriptor(providerId, instance));
}
}
unregisterProvider(providerId: string): void {
this._providers.delete(providerId);
}
get isRegistrationComplete(): boolean {
return this._isRegistrationComplete;
}
get registrationComplete(): Promise<void> {
return this._registrationComplete.promise;
}
private addFileProvider(fileType: string, provider: NotebookProviderRegistration) {
this._fileToProviders.set(fileType.toUpperCase(), provider);
}
getSupportedFileExtensions(): string[] {
return Array.from(this._fileToProviders.keys());
}
getProviderForFileType(fileType: string): string {
fileType = fileType.toUpperCase();
let provider = this._fileToProviders.get(fileType);
return provider ? provider.provider : undefined;
}
public shutdown(): void {
this._managers.forEach(manager => {
if (manager.serverManager) {
@@ -83,6 +185,13 @@ export class NotebookService implements INotebookService {
get onNotebookEditorRemove(): Event<INotebookEditor> {
return this._onNotebookEditorRemove.event;
}
get onCellChanged(): Event<INotebookEditor> {
return this._onCellChanged.event;
}
get onNotebookEditorRename(): Event<INotebookEditor> {
return this._onNotebookEditorRename.event;
}
addNotebookEditor(editor: INotebookEditor): void {
this._editors.set(editor.id, editor);
@@ -103,32 +212,70 @@ export class NotebookService implements INotebookService {
return editors;
}
private sendNotebookCloseToProvider(editor: INotebookEditor) {
renameNotebookEditor(oldUri: URI, newUri: URI, currentEditor: INotebookEditor): void {
let oldUriKey = oldUri.toString();
if(this._editors.has(oldUriKey))
{
this._editors.delete(oldUriKey);
currentEditor.notebookParams.notebookUri = newUri;
this._editors.set(newUri.toString(), currentEditor);
this._onNotebookEditorRename.fire(currentEditor);
}
}
private sendNotebookCloseToProvider(editor: INotebookEditor): void {
let notebookUri = editor.notebookParams.notebookUri;
let uriString = notebookUri.toString();
let manager = this._managers.get(uriString);
if (manager) {
// As we have a manager, we can assume provider is ready
this._managers.delete(uriString);
let provider = this._providers.get(manager.providerId);
provider.handleNotebookClosed(notebookUri);
provider.instance.handleNotebookClosed(notebookUri);
}
}
// PRIVATE HELPERS /////////////////////////////////////////////////////
private doWithProvider<T>(providerId: string, op: (provider: INotebookProvider) => Thenable<T>): Thenable<T> {
private async doWithProvider<T>(providerId: string, op: (provider: INotebookProvider) => Thenable<T>): Promise<T> {
// Make sure the provider exists before attempting to retrieve accounts
let provider: INotebookProvider;
if (this._providers.has(providerId)) {
provider = this._providers.get(providerId);
}
else {
provider = this._providers.get(DEFAULT_NOTEBOOK_PROVIDER);
let provider: INotebookProvider = await this.getProviderInstance(providerId);
return op(provider);
}
private async getProviderInstance(providerId: string, timeout?: number): Promise<INotebookProvider> {
let providerDescriptor = this._providers.get(providerId);
let instance: INotebookProvider;
// Try get from actual provider, waiting on its registration
if (providerDescriptor) {
if (!providerDescriptor.instance) {
instance = await this.waitOnProviderAvailability(providerDescriptor);
} else {
instance = providerDescriptor.instance;
}
}
if (!provider) {
return Promise.reject(new Error(localize('notebookServiceNoProvider', 'Notebook provider does not exist'))).then();
// Fall back to default if this failed
if (!instance) {
providerDescriptor = this._providers.get(DEFAULT_NOTEBOOK_PROVIDER);
instance = providerDescriptor ? providerDescriptor.instance : undefined;
}
return op(provider);
// Should never happen, but if default wasn't registered we should throw
if (!instance) {
throw new Error(localize('notebookServiceNoProvider', 'Notebook provider does not exist'));
}
return instance;
}
private waitOnProviderAvailability(providerDescriptor: ProviderDescriptor, timeout?: number): Promise<INotebookProvider> {
// Wait up to 10 seconds for the provider to be registered
timeout = timeout || 10000;
let promises: Promise<INotebookProvider>[] = [
providerDescriptor.instanceReady,
new Promise<INotebookProvider>((resolve, reject) => setTimeout(() => resolve(), timeout))
];
return Promise.race(promises);
}
//Returns an instantiation of RenderMimeRegistry class
@@ -140,6 +287,41 @@ export class NotebookService implements INotebookService {
}
return this._mimeRegistry;
}
private get providersMemento(): NotebookProvidersMemento {
return this._memento.getMemento(this._storageService) as NotebookProvidersMemento;
}
private cleanupProviders(): void {
let knownProviders = Object.keys(notebookRegistry.registrations);
let cache = this.providersMemento.notebookProviderCache;
for (let key in cache) {
if (!knownProviders.includes(key)) {
this._providers.delete(key);
delete cache[key];
}
}
}
private registerDefaultProvider() {
let defaultProvider = new BuiltinProvider();
this.registerProvider(defaultProvider.providerId, defaultProvider);
notebookRegistry.registerNotebookProvider({
provider: defaultProvider.providerId,
fileExtensions: DEFAULT_NOTEBOOK_FILETYPE
});
}
private removeContributedProvidersFromCache(identifier: IExtensionIdentifier, extensionService: IExtensionService) {
let extensionid = getIdFromLocalExtensionId(identifier.id);
extensionService.getExtensions().then(i => {
let extension = i.find(c => c.id === extensionid);
if (extension && extension.contributes['notebookProvider']) {
let id = extension.contributes['notebookProvider'].providerId;
delete this.providersMemento.notebookProviderCache[id];
}
});
}
}
export class BuiltinProvider implements INotebookProvider {

View File

@@ -1548,6 +1548,7 @@ declare module 'sqlops' {
export interface NotebookCell {
contents: ICellContents;
uri?: vscode.Uri;
}
export interface NotebookShowOptions {
@@ -1593,7 +1594,7 @@ declare module 'sqlops' {
/**
* The new value for the [notebook documents's cells](#NotebookDocument.cells).
*/
cell: NotebookCell[];
cells: NotebookCell[];
/**
* The [change kind](#TextEditorSelectionChangeKind) which has triggered this
* event. Can be `undefined`.

View File

@@ -24,11 +24,6 @@ export class ExtHostNotebook implements ExtHostNotebookShape {
private readonly _proxy: MainThreadNotebookShape;
private _adapters = new Map<number, Adapter>();
private _onDidOpenNotebook = new Emitter<sqlops.nb.NotebookDocument>();
private _onDidChangeNotebookCell = new Emitter<sqlops.nb.NotebookCellChangeEvent>();
public readonly onDidOpenNotebookDocument: Event<sqlops.nb.NotebookDocument> = this._onDidOpenNotebook.event;
public readonly onDidChangeNotebookCell: Event<sqlops.nb.NotebookCellChangeEvent> = this._onDidChangeNotebookCell.event;
// Notebook URI to manager lookup.
constructor(_mainContext: IMainContext) {

View File

@@ -12,22 +12,20 @@ import { ok } from 'vs/base/common/assert';
import { Schemas } from 'vs/base/common/network';
import { TPromise } from 'vs/base/common/winjs.base';
import { MainThreadNotebookDocumentsAndEditorsShape } from 'sql/workbench/api/node/sqlExtHost.protocol';
import { MainThreadNotebookDocumentsAndEditorsShape, INotebookModelChangedData } from 'sql/workbench/api/node/sqlExtHost.protocol';
import { CellRange } from 'sql/workbench/api/common/sqlExtHostTypes';
export class ExtHostNotebookDocumentData implements IDisposable {
private _document: sqlops.nb.NotebookDocument;
private _cells: sqlops.nb.NotebookCell[];
private _isDisposed: boolean = false;
constructor(private readonly _proxy: MainThreadNotebookDocumentsAndEditorsShape,
private readonly _uri: URI,
private readonly _providerId: string,
private _isDirty: boolean
private _providerId: string,
private _isDirty: boolean,
private _cells: sqlops.nb.NotebookCell[]
) {
// TODO add cell mapping support
this._cells = [];
}
dispose(): void {
@@ -66,6 +64,14 @@ export class ExtHostNotebookDocumentData implements IDisposable {
}
public onModelChanged(data: INotebookModelChangedData) {
if (data) {
this._isDirty = data.isDirty;
this._cells = data.cells;
this._providerId = data.providerId;
}
}
// ---- range math
private _validateRange(range: sqlops.nb.CellRange): sqlops.nb.CellRange {

View File

@@ -9,7 +9,7 @@ import * as vscode from 'vscode';
import { Event, Emitter } from 'vs/base/common/event';
import { dispose } from 'vs/base/common/lifecycle';
import URI from 'vs/base/common/uri';
import URI, { UriComponents } from 'vs/base/common/uri';
import { Disposable } from 'vs/workbench/api/node/extHostTypes';
import * as typeConverters from 'vs/workbench/api/node/extHostTypeConverters';
import { IMainContext } from 'vs/workbench/api/node/extHost.protocol';
@@ -17,7 +17,7 @@ import { ok } from 'vs/base/common/assert';
import {
SqlMainContext, INotebookDocumentsAndEditorsDelta, ExtHostNotebookDocumentsAndEditorsShape,
MainThreadNotebookDocumentsAndEditorsShape, INotebookShowOptions
MainThreadNotebookDocumentsAndEditorsShape, INotebookShowOptions, INotebookModelChangedData
} from 'sql/workbench/api/node/sqlExtHost.protocol';
import { ExtHostNotebookDocumentData } from 'sql/workbench/api/node/extHostNotebookDocumentData';
import { ExtHostNotebookEditor } from 'sql/workbench/api/node/extHostNotebookEditor';
@@ -33,15 +33,16 @@ export class ExtHostNotebookDocumentsAndEditors implements ExtHostNotebookDocume
private readonly _editors = new Map<string, ExtHostNotebookEditor>();
private readonly _documents = new Map<string, ExtHostNotebookDocumentData>();
private readonly _onDidAddDocuments = new Emitter<ExtHostNotebookDocumentData[]>();
private readonly _onDidRemoveDocuments = new Emitter<ExtHostNotebookDocumentData[]>();
private readonly _onDidChangeVisibleNotebookEditors = new Emitter<ExtHostNotebookEditor[]>();
private readonly _onDidChangeActiveNotebookEditor = new Emitter<ExtHostNotebookEditor>();
private _onDidOpenNotebook = new Emitter<sqlops.nb.NotebookDocument>();
private _onDidChangeNotebookCell = new Emitter<sqlops.nb.NotebookCellChangeEvent>();
readonly onDidAddDocuments: Event<ExtHostNotebookDocumentData[]> = this._onDidAddDocuments.event;
readonly onDidRemoveDocuments: Event<ExtHostNotebookDocumentData[]> = this._onDidRemoveDocuments.event;
readonly onDidChangeVisibleNotebookEditors: Event<ExtHostNotebookEditor[]> = this._onDidChangeVisibleNotebookEditors.event;
readonly onDidChangeActiveNotebookEditor: Event<ExtHostNotebookEditor> = this._onDidChangeActiveNotebookEditor.event;
readonly onDidOpenNotebookDocument: Event<sqlops.nb.NotebookDocument> = this._onDidOpenNotebook.event;
readonly onDidChangeNotebookCell: Event<sqlops.nb.NotebookCellChangeEvent> = this._onDidChangeNotebookCell.event;
constructor(
private readonly _mainContext: IMainContext,
@@ -81,7 +82,8 @@ export class ExtHostNotebookDocumentsAndEditors implements ExtHostNotebookDocume
this._proxy,
resource,
data.providerId,
data.isDirty
data.isDirty,
data.cells
);
this._documents.set(resource.toString(), documentData);
addedDocuments.push(documentData);
@@ -122,11 +124,11 @@ export class ExtHostNotebookDocumentsAndEditors implements ExtHostNotebookDocume
dispose(removedEditors);
// now that the internal state is complete, fire events
if (delta.removedDocuments) {
this._onDidRemoveDocuments.fire(removedDocuments);
if (removedDocuments) {
// TODO add doc close event
}
if (delta.addedDocuments) {
this._onDidAddDocuments.fire(addedDocuments);
if (addedDocuments) {
addedDocuments.forEach(d => this._onDidOpenNotebook.fire(d.document));
}
if (delta.removedEditors || delta.addedEditors) {
@@ -136,6 +138,21 @@ export class ExtHostNotebookDocumentsAndEditors implements ExtHostNotebookDocume
this._onDidChangeActiveNotebookEditor.fire(this.getActiveEditor());
}
}
$acceptModelChanged(uriComponents: UriComponents, e: INotebookModelChangedData): void {
const uri = URI.revive(uriComponents);
const strURL = uri.toString();
let data = this._documents.get(strURL);
if (data) {
data.onModelChanged(e);
this._onDidChangeNotebookCell.fire({
cells: data.document.cells,
notebook: data.document,
kind: undefined
});
}
}
//#endregion
//#region Extension accessible methods

View File

@@ -5,19 +5,22 @@
'use strict';
import * as sqlops from 'sqlops';
import * as util from 'util';
import { extHostNamedCustomer } from 'vs/workbench/api/electron-browser/extHostCustomers';
import { Disposable } from 'vs/base/common/lifecycle';
import { Disposable, IDisposable } from 'vs/base/common/lifecycle';
import URI, { UriComponents } from 'vs/base/common/uri';
import { Event, Emitter } from 'vs/base/common/event';
import { IExtHostContext, IUndoStopOptions } from 'vs/workbench/api/node/extHost.protocol';
import { IEditorService } from 'vs/workbench/services/editor/common/editorService';
import { IEditorGroupsService } from 'vs/workbench/services/group/common/editorGroupsService';
import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation';
import { ITextEditorOptions } from 'vs/platform/editor/common/editor';
import { viewColumnToEditorGroup } from 'vs/workbench/api/shared/editor';
import { Schemas } from 'vs/base/common/network';
import {
SqlMainContext, MainThreadNotebookDocumentsAndEditorsShape, SqlExtHostContext, ExtHostNotebookDocumentsAndEditorsShape,
INotebookDocumentsAndEditorsDelta, INotebookEditorAddData, INotebookShowOptions, INotebookModelAddedData
INotebookDocumentsAndEditorsDelta, INotebookEditorAddData, INotebookShowOptions, INotebookModelAddedData, INotebookModelChangedData
} from 'sql/workbench/api/node/sqlExtHost.protocol';
import { NotebookInputModel, NotebookInput } from 'sql/parts/notebook/notebookInput';
import { INotebookService, INotebookEditor } from 'sql/services/notebook/notebookService';
@@ -25,11 +28,17 @@ import { TPromise } from 'vs/base/common/winjs.base';
import { getProviderForFileName } from 'sql/parts/notebook/notebookUtils';
import { ISingleNotebookEditOperation } from 'sql/workbench/api/common/sqlExtHostTypes';
import { disposed } from 'vs/base/common/errors';
import { ICellModel, NotebookContentChange } from 'sql/parts/notebook/models/modelInterfaces';
class MainThreadNotebookEditor extends Disposable {
private _contentChangedEmitter = new Emitter<NotebookContentChange>();
public readonly contentChanged: Event<NotebookContentChange> = this._contentChangedEmitter.event;
constructor(public readonly editor: INotebookEditor) {
super();
editor.modelReady.then(model => {
this._register(model.contentChanged((e) => this._contentChangedEmitter.fire(e)));
});
}
public get uri(): URI {
@@ -48,6 +57,10 @@ class MainThreadNotebookEditor extends Disposable {
return this.editor.notebookParams.providerId;
}
public get cells(): ICellModel[] {
return this.editor.cells;
}
public save(): Thenable<boolean> {
return this.editor.save();
}
@@ -205,6 +218,7 @@ class MainThreadNotebookDocumentAndEditorStateComputer extends Disposable {
this._register(this._editorService.onDidVisibleEditorsChange(this._updateState, this));
this._register(this._notebookService.onNotebookEditorAdd(this._onDidAddEditor, this));
this._register(this._notebookService.onNotebookEditorRemove(this._onDidRemoveEditor, this));
this._register(this._notebookService.onNotebookEditorRename(this._onDidRenameEditor, this));
this._updateState();
}
@@ -219,6 +233,11 @@ class MainThreadNotebookDocumentAndEditorStateComputer extends Disposable {
this._updateState();
}
private _onDidRenameEditor(e: INotebookEditor): void {
this._updateState();
//TODO: Close editor and open it
}
private _updateState(): void {
// editor
const editors = new Map<string, INotebookEditor>();
@@ -246,12 +265,13 @@ export class MainThreadNotebookDocumentsAndEditors extends Disposable implements
private _proxy: ExtHostNotebookDocumentsAndEditorsShape;
private _notebookEditors = new Map<string, MainThreadNotebookEditor>();
private _modelToDisposeMap = new Map<string, IDisposable>();
constructor(
extHostContext: IExtHostContext,
@IInstantiationService private _instantiationService: IInstantiationService,
@IEditorService private _editorService: IEditorService,
@IEditorGroupsService private _editorGroupService: IEditorGroupsService
@IEditorGroupsService private _editorGroupService: IEditorGroupsService,
@INotebookService private readonly _notebookService: INotebookService
) {
super();
if (extHostContext) {
@@ -293,12 +313,13 @@ export class MainThreadNotebookDocumentsAndEditors extends Disposable implements
preserveFocus: options.preserveFocus,
pinned: !options.preview
};
let model = new NotebookInputModel(uri, undefined, false, undefined);
let trusted = uri.scheme === Schemas.untitled;
let model = new NotebookInputModel(uri, undefined, trusted, undefined);
let providerId = options.providerId;
if(!providerId)
{
// Ensure there is always a sensible provider ID for this file type
providerId = getProviderForFileName(uri.fsPath);
providerId = getProviderForFileName(uri.fsPath, this._notebookService);
}
model.providerId = providerId;
@@ -389,8 +410,34 @@ export class MainThreadNotebookDocumentsAndEditors extends Disposable implements
if (!empty) {
this._proxy.$acceptDocumentsAndEditorsDelta(extHostDelta);
this.processRemovedDocs(removedDocuments);
this.processAddedDocs(addedEditors);
}
}
processRemovedDocs(removedDocuments: URI[]): void {
if (!removedDocuments) {
return;
}
removedDocuments.forEach(removedDoc => {
let listener = this._modelToDisposeMap.get(removedDoc.toString());
if (listener) {
listener.dispose();
this._modelToDisposeMap.delete(removedDoc.toString());
}
});
}
processAddedDocs(addedEditors: MainThreadNotebookEditor[]): any {
if (!addedEditors) {
return;
}
addedEditors.forEach(editor => {
let modelUrl = editor.uri;
this._modelToDisposeMap.set(editor.uri.toString(), editor.contentChanged((e) => {
this._proxy.$acceptModelChanged(modelUrl, this._toNotebookChangeData(e, editor));
}));
});
}
private _toNotebookEditorAddData(editor: MainThreadNotebookEditor): INotebookEditorAddData {
let addData: INotebookEditorAddData = {
@@ -405,8 +452,54 @@ export class MainThreadNotebookDocumentsAndEditors extends Disposable implements
let addData: INotebookModelAddedData = {
uri: editor.uri,
isDirty: editor.isDirty,
providerId: editor.providerId
providerId: editor.providerId,
cells: this.convertCellModelToNotebookCell(editor.cells)
};
return addData;
}
private _toNotebookChangeData(e: NotebookContentChange, editor: MainThreadNotebookEditor): INotebookModelChangedData {
let changeData: INotebookModelChangedData = {
// Note: we just send all cells for now, not a diff
cells: this.convertCellModelToNotebookCell(editor.cells),
isDirty: e.isDirty,
providerId: editor.providerId,
uri: editor.uri
};
return changeData;
}
private convertCellModelToNotebookCell(cells: ICellModel | ICellModel[]): sqlops.nb.NotebookCell[] {
let notebookCells: sqlops.nb.NotebookCell[] = [];
if (Array.isArray(cells)) {
for (let cell of cells) {
notebookCells.push({
uri: cell.cellUri,
contents: {
cell_type: cell.cellType,
execution_count: undefined,
metadata: {
language: cell.language
},
source: undefined
}
});
}
}
else {
notebookCells.push({
uri: cells.cellUri,
contents: {
cell_type: cells.cellType,
execution_count: undefined,
metadata: {
language: cells.language
},
source: undefined
}
});
}
return notebookCells;
}
}

View File

@@ -432,10 +432,10 @@ export function createApiFactory(
return extHostNotebookDocumentsAndEditors.getAllEditors();
},
get onDidOpenNotebookDocument() {
return extHostNotebook.onDidOpenNotebookDocument;
return extHostNotebookDocumentsAndEditors.onDidOpenNotebookDocument;
},
get onDidChangeNotebookCell() {
return extHostNotebook.onDidChangeNotebookCell;
return extHostNotebookDocumentsAndEditors.onDidChangeNotebookCell;
},
showNotebookDocument(uri: vscode.Uri, showOptions: sqlops.nb.NotebookShowOptions) {
return extHostNotebookDocumentsAndEditors.showNotebookDocument(uri, showOptions);

View File

@@ -797,6 +797,14 @@ export interface INotebookModelAddedData {
uri: UriComponents;
providerId: string;
isDirty: boolean;
cells: sqlops.nb.NotebookCell[];
}
export interface INotebookModelChangedData {
uri: UriComponents;
providerId: string;
isDirty: boolean;
cells: sqlops.nb.NotebookCell[];
}
export interface INotebookEditorAddData {
@@ -815,6 +823,7 @@ export interface INotebookShowOptions {
export interface ExtHostNotebookDocumentsAndEditorsShape {
$acceptDocumentsAndEditorsDelta(delta: INotebookDocumentsAndEditorsDelta): void;
$acceptModelChanged(strURL: UriComponents, e: INotebookModelChangedData);
}
export interface MainThreadNotebookDocumentsAndEditorsShape extends IDisposable {

View File

@@ -8,7 +8,7 @@
import { nb, IConnectionProfile } from 'sqlops';
import { Event, Emitter } from 'vs/base/common/event';
import { INotebookModel, ICellModel, IClientSession, IDefaultConnection } from 'sql/parts/notebook/models/modelInterfaces';
import { INotebookModel, ICellModel, IClientSession, IDefaultConnection, NotebookContentChange } from 'sql/parts/notebook/models/modelInterfaces';
import { NotebookChangeType, CellType } from 'sql/parts/notebook/models/contracts';
import { INotebookManager } from 'sql/services/notebook/notebookService';
import { ISingleNotebookEditOperation } from 'sql/workbench/api/common/sqlExtHostTypes';
@@ -44,6 +44,9 @@ export class NotebookModelStub implements INotebookModel {
get contextsChanged(): Event<void> {
throw new Error('method not implemented.');
}
get contentChanged(): Event<NotebookContentChange> {
throw new Error('method not implemented.');
}
get specs(): nb.IAllKernels {
throw new Error('method not implemented.');
}

View File

@@ -93,7 +93,7 @@ describe('Cell Model', function (): void {
let cellData: nb.ICellContents = {
cell_type: CellTypes.Code,
source: 'print(\'1\')',
metadata: { language: 'python'},
metadata: { },
execution_count: 1
};
@@ -105,6 +105,22 @@ describe('Cell Model', function (): void {
let cell = factory.createCell(cellData, { notebook: notebookModel, isTrusted: false });
should(cell.language).equal('scala');
});
it('Should keep cell language as python if cell has language override', async function (): Promise<void> {
let cellData: nb.ICellContents = {
cell_type: CellTypes.Code,
source: 'print(\'1\')',
metadata: { language: 'python'},
execution_count: 1
};
let notebookModel = new NotebookModelStub({
name: 'scala',
version: '',
mimetype: ''
});
let cell = factory.createCell(cellData, { notebook: notebookModel, isTrusted: false });
should(cell.language).equal('python');
});
it('Should set cell language to python if no language defined', async function (): Promise<void> {
let cellData: nb.ICellContents = {
@@ -127,7 +143,7 @@ describe('Cell Model', function (): void {
let cellData: nb.ICellContents = {
cell_type: CellTypes.Code,
source: 'std::cout << "hello world";',
metadata: { language: 'python'},
metadata: { },
execution_count: 1
};
@@ -144,7 +160,7 @@ describe('Cell Model', function (): void {
let cellData: nb.ICellContents = {
cell_type: CellTypes.Code,
source: 'print(\'1\')',
metadata: { language: 'python'},
metadata: { },
execution_count: 1
};

View File

@@ -24,7 +24,7 @@ export function resolveCommonProperties(commit: string, version: string, machine
result['sessionID'] = '';
// __GDPR__COMMON__ "commitHash" : { "classification": "SystemMetaData", "purpose": "FeatureInsight" }
result['commitHash'] = commit;
result['commitHash'] = '';
// __GDPR__COMMON__ "version" : { "classification": "SystemMetaData", "purpose": "FeatureInsight" }
result['version'] = version;
// __GDPR__COMMON__ "common.platformVersion" : { "classification": "SystemMetaData", "purpose": "FeatureInsight" }

View File

@@ -29,7 +29,8 @@ import { coalesce } from 'vs/base/common/arrays';
import { isCodeEditor, isDiffEditor, ICodeEditor, IDiffEditor } from 'vs/editor/browser/editorBrowser';
import { IEditorGroupView, IEditorOpeningEvent, EditorGroupsServiceImpl, EditorServiceImpl } from 'vs/workbench/browser/parts/editor/editor';
import { IUriDisplayService } from 'vs/platform/uriDisplay/common/uriDisplay';
import { convertEditorInput } from 'sql/parts/common/customInputConverter';
//{{ SQL CARBON EDIT }}
import { convertEditorInput, getFileMode } from 'sql/parts/common/customInputConverter';
type ICachedEditorInput = ResourceEditorInput | IFileEditorInput | DataUriEditorInput;
@@ -67,7 +68,7 @@ export class EditorService extends Disposable implements EditorServiceImpl {
@IInstantiationService private instantiationService: IInstantiationService,
@IUriDisplayService private uriDisplayService: IUriDisplayService,
@IFileService private fileService: IFileService,
@IConfigurationService private configurationService: IConfigurationService
@IConfigurationService private configurationService: IConfigurationService,
) {
super();
@@ -495,9 +496,11 @@ export class EditorService extends Disposable implements EditorServiceImpl {
const untitledInput = <IUntitledResourceInput>input;
if (!untitledInput.resource || typeof untitledInput.filePath === 'string' || (untitledInput.resource instanceof URI && untitledInput.resource.scheme === Schemas.untitled)) {
// {{SQL CARBON EDIT}}
let mode: string = getFileMode( this.instantiationService, untitledInput.resource);
return convertEditorInput(this.untitledEditorService.createOrGet(
untitledInput.filePath ? URI.file(untitledInput.filePath) : untitledInput.resource,
'sql',
mode,
untitledInput.contents,
untitledInput.encoding
), undefined, this.instantiationService);