mirror of
https://github.com/ckaczor/azuredatastudio.git
synced 2026-02-17 11:01:37 -05:00
Compare commits
18 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
f74080c963 | ||
|
|
32cd41d076 | ||
|
|
0d7d1cf375 | ||
|
|
8b826063b4 | ||
|
|
6a500715a7 | ||
|
|
5e02486ace | ||
|
|
16dbef978e | ||
|
|
f9081773dd | ||
|
|
b3799d4506 | ||
|
|
2ab9e4b861 | ||
|
|
a1d8bae654 | ||
|
|
b265c53f9c | ||
|
|
ac7d19133a | ||
|
|
ab3895a4c2 | ||
|
|
a230e24af0 | ||
|
|
8354ccd76d | ||
|
|
3d60ee9030 | ||
|
|
5faf2d9cb2 |
@@ -21,7 +21,7 @@
|
|||||||
"buffer-stream-reader": "^0.1.1",
|
"buffer-stream-reader": "^0.1.1",
|
||||||
"bytes": "^3.1.0",
|
"bytes": "^3.1.0",
|
||||||
"clipboardy": "^1.2.3",
|
"clipboardy": "^1.2.3",
|
||||||
"dataprotocol-client": "github:Microsoft/sqlops-dataprotocolclient#0.2.15",
|
"dataprotocol-client": "github:Microsoft/sqlops-dataprotocolclient#0.2.16",
|
||||||
"error-ex": "^1.3.2",
|
"error-ex": "^1.3.2",
|
||||||
"figures": "^2.0.0",
|
"figures": "^2.0.0",
|
||||||
"opener": "^1.4.3",
|
"opener": "^1.4.3",
|
||||||
|
|||||||
@@ -172,19 +172,27 @@ function saveProfileAndCreateNotebook(profile: azdata.IConnectionProfile): Promi
|
|||||||
return handleNewNotebookTask(undefined, profile);
|
return handleNewNotebookTask(undefined, profile);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function findNextUntitledEditorName(): string {
|
||||||
|
let nextVal = untitledCounter;
|
||||||
|
// Note: this will go forever if it's coded wrong, or you have inifinite Untitled notebooks!
|
||||||
|
while (true) {
|
||||||
|
let title = `Notebook-${nextVal++}`;
|
||||||
|
let hasTextDoc = vscode.workspace.textDocuments.findIndex(doc => doc.isUntitled && doc.fileName === title) > -1;
|
||||||
|
let hasNotebookDoc = azdata.nb.notebookDocuments.findIndex(doc => doc.isUntitled && doc.fileName === title) > -1;
|
||||||
|
if (!hasTextDoc && !hasNotebookDoc) {
|
||||||
|
untitledCounter = nextVal;
|
||||||
|
return title;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
async function handleNewNotebookTask(oeContext?: azdata.ObjectExplorerContext, profile?: azdata.IConnectionProfile): Promise<void> {
|
async function handleNewNotebookTask(oeContext?: azdata.ObjectExplorerContext, profile?: azdata.IConnectionProfile): Promise<void> {
|
||||||
// Ensure we get a unique ID for the notebook. For now we're using a different prefix to the built-in untitled files
|
// Ensure we get a unique ID for the notebook. For now we're using a different prefix to the built-in untitled files
|
||||||
// to handle this. We should look into improving this in the future
|
// to handle this. We should look into improving this in the future
|
||||||
let untitledUri = vscode.Uri.parse(`untitled:Notebook-${untitledCounter++}`);
|
let title = findNextUntitledEditorName();
|
||||||
|
let untitledUri = vscode.Uri.parse(`untitled:${title}`);
|
||||||
let editor = await azdata.nb.showNotebookDocument(untitledUri, {
|
let editor = await azdata.nb.showNotebookDocument(untitledUri, {
|
||||||
connectionProfile: profile,
|
connectionProfile: profile,
|
||||||
providerId: jupyterNotebookProviderId,
|
preview: false
|
||||||
preview: false,
|
|
||||||
defaultKernel: {
|
|
||||||
name: 'pyspark3kernel',
|
|
||||||
display_name: 'PySpark3',
|
|
||||||
language: 'python'
|
|
||||||
}
|
|
||||||
});
|
});
|
||||||
if (oeContext && oeContext.nodeInfo && oeContext.nodeInfo.nodePath) {
|
if (oeContext && oeContext.nodeInfo && oeContext.nodeInfo.nodePath) {
|
||||||
// Get the file path after '/HDFS'
|
// Get the file path after '/HDFS'
|
||||||
@@ -221,7 +229,6 @@ async function handleOpenNotebookTask(profile: azdata.IConnectionProfile): Promi
|
|||||||
} else {
|
} else {
|
||||||
await azdata.nb.showNotebookDocument(fileUri, {
|
await azdata.nb.showNotebookDocument(fileUri, {
|
||||||
connectionProfile: profile,
|
connectionProfile: profile,
|
||||||
providerId: jupyterNotebookProviderId,
|
|
||||||
preview: false
|
preview: false
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -213,11 +213,12 @@ export class WebHDFS {
|
|||||||
|
|
||||||
request(requestParams, (error, response, body) => {
|
request(requestParams, (error, response, body) => {
|
||||||
if (!callback) { return; }
|
if (!callback) { return; }
|
||||||
if (this.isSuccess(response)) {
|
|
||||||
callback(undefined, response);
|
if (error || this.isError(response)) {
|
||||||
} else if (error || this.isError(response)) {
|
|
||||||
let hdfsError = this.parseError(response, body, error);
|
let hdfsError = this.parseError(response, body, error);
|
||||||
callback(hdfsError, response);
|
callback(hdfsError, response);
|
||||||
|
} else if (this.isSuccess(response)) {
|
||||||
|
callback(undefined, response);
|
||||||
} else {
|
} else {
|
||||||
let hdfsError = new HdfsError(
|
let hdfsError = new HdfsError(
|
||||||
localize('webhdfs.unexpectedRedirect', 'Unexpected Redirect'),
|
localize('webhdfs.unexpectedRedirect', 'Unexpected Redirect'),
|
||||||
|
|||||||
@@ -174,9 +174,9 @@ dashdash@^1.12.0:
|
|||||||
dependencies:
|
dependencies:
|
||||||
assert-plus "^1.0.0"
|
assert-plus "^1.0.0"
|
||||||
|
|
||||||
"dataprotocol-client@github:Microsoft/sqlops-dataprotocolclient#0.2.15":
|
"dataprotocol-client@github:Microsoft/sqlops-dataprotocolclient#0.2.16":
|
||||||
version "0.2.15"
|
version "0.2.16"
|
||||||
resolved "https://codeload.github.com/Microsoft/sqlops-dataprotocolclient/tar.gz/a2cd2db109de882f0959f7b6421c86afa585f460"
|
resolved "https://codeload.github.com/Microsoft/sqlops-dataprotocolclient/tar.gz/3c4e70405022ab5333cf1505deddd643223db3de"
|
||||||
dependencies:
|
dependencies:
|
||||||
vscode-languageclient "3.5.1"
|
vscode-languageclient "3.5.1"
|
||||||
|
|
||||||
|
|||||||
@@ -20,7 +20,7 @@ const JUPYTER_NOTEBOOK_PROVIDER = 'jupyter';
|
|||||||
const msgSampleCodeDataFrame = localize('msgSampleCodeDataFrame', 'This sample code loads the file into a data frame and shows the first 10 results.');
|
const msgSampleCodeDataFrame = localize('msgSampleCodeDataFrame', 'This sample code loads the file into a data frame and shows the first 10 results.');
|
||||||
const noNotebookVisible = localize('noNotebookVisible', 'No notebook editor is active');
|
const noNotebookVisible = localize('noNotebookVisible', 'No notebook editor is active');
|
||||||
|
|
||||||
let counter = 0;
|
let untitledCounter = 0;
|
||||||
|
|
||||||
export let controller: JupyterController;
|
export let controller: JupyterController;
|
||||||
|
|
||||||
@@ -54,7 +54,7 @@ export function activate(extensionContext: vscode.ExtensionContext) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
function newNotebook(connectionProfile: azdata.IConnectionProfile) {
|
function newNotebook(connectionProfile: azdata.IConnectionProfile) {
|
||||||
let title = `Untitled-${counter++}`;
|
let title = findNextUntitledEditorName();
|
||||||
let untitledUri = vscode.Uri.parse(`untitled:${title}`);
|
let untitledUri = vscode.Uri.parse(`untitled:${title}`);
|
||||||
let options: azdata.nb.NotebookShowOptions = connectionProfile ? {
|
let options: azdata.nb.NotebookShowOptions = connectionProfile ? {
|
||||||
viewColumn: null,
|
viewColumn: null,
|
||||||
@@ -71,6 +71,20 @@ function newNotebook(connectionProfile: azdata.IConnectionProfile) {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function findNextUntitledEditorName(): string {
|
||||||
|
let nextVal = untitledCounter;
|
||||||
|
// Note: this will go forever if it's coded wrong, or you have infinite Untitled notebooks!
|
||||||
|
while (true) {
|
||||||
|
let title = `Notebook-${nextVal++}`;
|
||||||
|
let hasTextDoc = vscode.workspace.textDocuments.findIndex(doc => doc.isUntitled && doc.fileName === title) > -1;
|
||||||
|
let hasNotebookDoc = azdata.nb.notebookDocuments.findIndex(doc => doc.isUntitled && doc.fileName === title) > -1;
|
||||||
|
if (!hasTextDoc && !hasNotebookDoc) {
|
||||||
|
untitledCounter = nextVal;
|
||||||
|
return title;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
async function openNotebook(): Promise<void> {
|
async function openNotebook(): Promise<void> {
|
||||||
try {
|
try {
|
||||||
let filter = {};
|
let filter = {};
|
||||||
@@ -123,7 +137,7 @@ async function addCell(cellType: azdata.nb.CellType): Promise<void> {
|
|||||||
async function analyzeNotebook(oeContext?: azdata.ObjectExplorerContext): Promise<void> {
|
async function analyzeNotebook(oeContext?: azdata.ObjectExplorerContext): Promise<void> {
|
||||||
// Ensure we get a unique ID for the notebook. For now we're using a different prefix to the built-in untitled files
|
// Ensure we get a unique ID for the notebook. For now we're using a different prefix to the built-in untitled files
|
||||||
// to handle this. We should look into improving this in the future
|
// to handle this. We should look into improving this in the future
|
||||||
let untitledUri = vscode.Uri.parse(`untitled:Notebook-${counter++}`);
|
let untitledUri = vscode.Uri.parse(`untitled:Notebook-${untitledCounter++}`);
|
||||||
|
|
||||||
let editor = await azdata.nb.showNotebookDocument(untitledUri, {
|
let editor = await azdata.nb.showNotebookDocument(untitledUri, {
|
||||||
connectionProfile: oeContext ? oeContext.connectionProfile : undefined,
|
connectionProfile: oeContext ? oeContext.connectionProfile : undefined,
|
||||||
|
|||||||
@@ -62,7 +62,7 @@ export class LocalJupyterServerManager implements nb.ServerManager, vscode.Dispo
|
|||||||
this._onServerStarted.fire();
|
this._onServerStarted.fire();
|
||||||
|
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
this.apiWrapper.showErrorMessage(localize('startServerFailed', 'Starting local Notebook server failed with error: {0}', utils.getErrorMessage(error)));
|
// this is caught and notified up the stack, no longer showing a message here
|
||||||
throw error;
|
throw error;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "azuredatastudio",
|
"name": "azuredatastudio",
|
||||||
"version": "1.5.1",
|
"version": "1.5.2",
|
||||||
"distro": "8c3e97e3425cc9814496472ab73e076de2ba99ee",
|
"distro": "8c3e97e3425cc9814496472ab73e076de2ba99ee",
|
||||||
"author": {
|
"author": {
|
||||||
"name": "Microsoft Corporation"
|
"name": "Microsoft Corporation"
|
||||||
|
|||||||
@@ -140,14 +140,7 @@ export class HeightMap {
|
|||||||
|
|
||||||
let viewItem = this.heightMap[i];
|
let viewItem = this.heightMap[i];
|
||||||
|
|
||||||
let delta = viewItem.height - size;
|
|
||||||
|
|
||||||
viewItem.height = size;
|
viewItem.height = size;
|
||||||
|
|
||||||
// update all items after this item
|
|
||||||
for (let j = i + 1; j < this.heightMap.length; j++) {
|
|
||||||
this.heightMap[j].top -= delta;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
protected updateTop(item: string, top: number): void {
|
protected updateTop(item: string, top: number): void {
|
||||||
|
|||||||
@@ -63,6 +63,7 @@ interface ISashEvent {
|
|||||||
|
|
||||||
interface IViewItem extends HeightIViewItem {
|
interface IViewItem extends HeightIViewItem {
|
||||||
view: IView;
|
view: IView;
|
||||||
|
size: number;
|
||||||
container: HTMLElement;
|
container: HTMLElement;
|
||||||
disposable: IDisposable;
|
disposable: IDisposable;
|
||||||
layout(): void;
|
layout(): void;
|
||||||
@@ -188,6 +189,7 @@ export class ScrollableSplitView extends HeightMap implements IDisposable {
|
|||||||
this.scrollable = new ScrollableElement(this.el, { vertical: options.verticalScrollbarVisibility });
|
this.scrollable = new ScrollableElement(this.el, { vertical: options.verticalScrollbarVisibility });
|
||||||
debounceEvent(this.scrollable.onScroll, (l, e) => e, types.isNumber(this.options.scrollDebounce) ? this.options.scrollDebounce : 25)(e => {
|
debounceEvent(this.scrollable.onScroll, (l, e) => e, types.isNumber(this.options.scrollDebounce) ? this.options.scrollDebounce : 25)(e => {
|
||||||
this.render(e.scrollTop, e.height);
|
this.render(e.scrollTop, e.height);
|
||||||
|
this.relayout();
|
||||||
this._onScroll.fire(e.scrollTop);
|
this._onScroll.fire(e.scrollTop);
|
||||||
});
|
});
|
||||||
let domNode = this.scrollable.getDomNode();
|
let domNode = this.scrollable.getDomNode();
|
||||||
@@ -247,12 +249,12 @@ export class ScrollableSplitView extends HeightMap implements IDisposable {
|
|||||||
const onRemove = view.onRemove ? () => view.onRemove() : () => { };
|
const onRemove = view.onRemove ? () => view.onRemove() : () => { };
|
||||||
|
|
||||||
const layoutContainer = this.orientation === Orientation.VERTICAL
|
const layoutContainer = this.orientation === Orientation.VERTICAL
|
||||||
? () => item.container.style.height = `${item.height}px`
|
? () => item.container.style.height = `${item.size}px`
|
||||||
: () => item.container.style.width = `${item.height}px`;
|
: () => item.container.style.width = `${item.size}px`;
|
||||||
|
|
||||||
const layout = () => {
|
const layout = () => {
|
||||||
layoutContainer();
|
layoutContainer();
|
||||||
item.view.layout(item.height, this.orientation);
|
item.view.layout(item.size, this.orientation);
|
||||||
};
|
};
|
||||||
|
|
||||||
let viewSize: number;
|
let viewSize: number;
|
||||||
@@ -265,7 +267,7 @@ export class ScrollableSplitView extends HeightMap implements IDisposable {
|
|||||||
viewSize = view.minimumSize;
|
viewSize = view.minimumSize;
|
||||||
}
|
}
|
||||||
|
|
||||||
const item: IViewItem = { onAdd, onRemove, view, container, layout, disposable, height: viewSize, top: 0, width: 0 };
|
const item: IViewItem = { onAdd, onRemove, view, container, size: viewSize, layout, disposable, height: viewSize, top: 0, width: 0 };
|
||||||
this.viewItems.splice(currentIndex, 0, item);
|
this.viewItems.splice(currentIndex, 0, item);
|
||||||
|
|
||||||
this.onInsertItems(new ArrayIterator([item]), currentIndex > 0 ? this.viewItems[currentIndex - 1].view.id : undefined);
|
this.onInsertItems(new ArrayIterator([item]), currentIndex > 0 ? this.viewItems[currentIndex - 1].view.id : undefined);
|
||||||
@@ -314,6 +316,14 @@ export class ScrollableSplitView extends HeightMap implements IDisposable {
|
|||||||
if (!types.isArray(sizes) && sizes.type === 'distribute') {
|
if (!types.isArray(sizes) && sizes.type === 'distribute') {
|
||||||
this.distributeViewSizes();
|
this.distributeViewSizes();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Re-render the views. Set lastRenderTop and lastRenderHeight to undefined since
|
||||||
|
// this isn't actually scrolling up or down
|
||||||
|
let scrollTop = this.lastRenderTop;
|
||||||
|
let viewHeight = this.lastRenderHeight;
|
||||||
|
this.lastRenderTop = undefined;
|
||||||
|
this.lastRenderHeight = undefined;
|
||||||
|
this.render(scrollTop, viewHeight);
|
||||||
}
|
}
|
||||||
|
|
||||||
addView(view: IView, size: number | Sizing, index = this.viewItems.length): void {
|
addView(view: IView, size: number | Sizing, index = this.viewItems.length): void {
|
||||||
@@ -342,12 +352,12 @@ export class ScrollableSplitView extends HeightMap implements IDisposable {
|
|||||||
const onRemove = view.onRemove ? () => view.onRemove() : () => { };
|
const onRemove = view.onRemove ? () => view.onRemove() : () => { };
|
||||||
|
|
||||||
const layoutContainer = this.orientation === Orientation.VERTICAL
|
const layoutContainer = this.orientation === Orientation.VERTICAL
|
||||||
? () => item.container.style.height = `${item.height}px`
|
? () => item.container.style.height = `${item.size}px`
|
||||||
: () => item.container.style.width = `${item.height}px`;
|
: () => item.container.style.width = `${item.size}px`;
|
||||||
|
|
||||||
const layout = () => {
|
const layout = () => {
|
||||||
layoutContainer();
|
layoutContainer();
|
||||||
item.view.layout(item.height, this.orientation);
|
item.view.layout(item.size, this.orientation);
|
||||||
};
|
};
|
||||||
|
|
||||||
let viewSize: number;
|
let viewSize: number;
|
||||||
@@ -360,7 +370,7 @@ export class ScrollableSplitView extends HeightMap implements IDisposable {
|
|||||||
viewSize = view.minimumSize;
|
viewSize = view.minimumSize;
|
||||||
}
|
}
|
||||||
|
|
||||||
const item: IViewItem = { onAdd, onRemove, view, container, layout, disposable, height: viewSize, top: 0, width: 0 };
|
const item: IViewItem = { onAdd, onRemove, view, container, size: viewSize, layout, disposable, height: viewSize, top: 0, width: 0 };
|
||||||
this.viewItems.splice(index, 0, item);
|
this.viewItems.splice(index, 0, item);
|
||||||
|
|
||||||
this.onInsertItems(new ArrayIterator([item]), index > 0 ? this.viewItems[index - 1].view.id : undefined);
|
this.onInsertItems(new ArrayIterator([item]), index > 0 ? this.viewItems[index - 1].view.id : undefined);
|
||||||
@@ -470,7 +480,7 @@ export class ScrollableSplitView extends HeightMap implements IDisposable {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private relayout(lowPriorityIndex?: number, highPriorityIndex?: number): void {
|
private relayout(lowPriorityIndex?: number, highPriorityIndex?: number): void {
|
||||||
const contentSize = this.viewItems.reduce((r, i) => r + i.height, 0);
|
const contentSize = this.viewItems.reduce((r, i) => r + i.size, 0);
|
||||||
|
|
||||||
this.resize(this.viewItems.length - 1, this.size - contentSize, undefined, lowPriorityIndex, highPriorityIndex);
|
this.resize(this.viewItems.length - 1, this.size - contentSize, undefined, lowPriorityIndex, highPriorityIndex);
|
||||||
this.distributeEmptySpace();
|
this.distributeEmptySpace();
|
||||||
@@ -494,7 +504,7 @@ export class ScrollableSplitView extends HeightMap implements IDisposable {
|
|||||||
} else {
|
} else {
|
||||||
for (let i = 0; i < this.viewItems.length; i++) {
|
for (let i = 0; i < this.viewItems.length; i++) {
|
||||||
const item = this.viewItems[i];
|
const item = this.viewItems[i];
|
||||||
this.updateSize(item.view.id, clamp(Math.round(this.proportions[i] * size), item.view.minimumSize, item.view.maximumSize));
|
item.size = clamp(Math.round(this.proportions[i] * size), item.view.minimumSize, item.view.maximumSize);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -504,7 +514,7 @@ export class ScrollableSplitView extends HeightMap implements IDisposable {
|
|||||||
|
|
||||||
private saveProportions(): void {
|
private saveProportions(): void {
|
||||||
if (this.contentSize > 0) {
|
if (this.contentSize > 0) {
|
||||||
this.proportions = this.viewItems.map(i => i.height / this.contentSize);
|
this.proportions = this.viewItems.map(i => i.size / this.contentSize);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -518,7 +528,7 @@ export class ScrollableSplitView extends HeightMap implements IDisposable {
|
|||||||
]);
|
]);
|
||||||
|
|
||||||
const resetSashDragState = (start: number, alt: boolean) => {
|
const resetSashDragState = (start: number, alt: boolean) => {
|
||||||
const sizes = this.viewItems.map(i => i.height);
|
const sizes = this.viewItems.map(i => i.size);
|
||||||
let minDelta = Number.NEGATIVE_INFINITY;
|
let minDelta = Number.NEGATIVE_INFINITY;
|
||||||
let maxDelta = Number.POSITIVE_INFINITY;
|
let maxDelta = Number.POSITIVE_INFINITY;
|
||||||
|
|
||||||
@@ -534,12 +544,12 @@ export class ScrollableSplitView extends HeightMap implements IDisposable {
|
|||||||
|
|
||||||
if (isLastSash) {
|
if (isLastSash) {
|
||||||
const viewItem = this.viewItems[index];
|
const viewItem = this.viewItems[index];
|
||||||
minDelta = (viewItem.view.minimumSize - viewItem.height) / 2;
|
minDelta = (viewItem.view.minimumSize - viewItem.size) / 2;
|
||||||
maxDelta = (viewItem.view.maximumSize - viewItem.height) / 2;
|
maxDelta = (viewItem.view.maximumSize - viewItem.size) / 2;
|
||||||
} else {
|
} else {
|
||||||
const viewItem = this.viewItems[index + 1];
|
const viewItem = this.viewItems[index + 1];
|
||||||
minDelta = (viewItem.height - viewItem.view.maximumSize) / 2;
|
minDelta = (viewItem.size - viewItem.view.maximumSize) / 2;
|
||||||
maxDelta = (viewItem.height - viewItem.view.minimumSize) / 2;
|
maxDelta = (viewItem.size - viewItem.view.minimumSize) / 2;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -558,11 +568,11 @@ export class ScrollableSplitView extends HeightMap implements IDisposable {
|
|||||||
|
|
||||||
if (alt) {
|
if (alt) {
|
||||||
const isLastSash = index === this.sashItems.length - 1;
|
const isLastSash = index === this.sashItems.length - 1;
|
||||||
const newSizes = this.viewItems.map(i => i.height);
|
const newSizes = this.viewItems.map(i => i.size);
|
||||||
const viewItemIndex = isLastSash ? index : index + 1;
|
const viewItemIndex = isLastSash ? index : index + 1;
|
||||||
const viewItem = this.viewItems[viewItemIndex];
|
const viewItem = this.viewItems[viewItemIndex];
|
||||||
const newMinDelta = viewItem.height - viewItem.view.maximumSize;
|
const newMinDelta = viewItem.size - viewItem.view.maximumSize;
|
||||||
const newMaxDelta = viewItem.height - viewItem.view.minimumSize;
|
const newMaxDelta = viewItem.size - viewItem.view.minimumSize;
|
||||||
const resizeIndex = isLastSash ? index - 1 : index + 1;
|
const resizeIndex = isLastSash ? index - 1 : index + 1;
|
||||||
|
|
||||||
this.resize(resizeIndex, -newDelta, newSizes, undefined, undefined, newMinDelta, newMaxDelta);
|
this.resize(resizeIndex, -newDelta, newSizes, undefined, undefined, newMinDelta, newMaxDelta);
|
||||||
@@ -585,23 +595,23 @@ export class ScrollableSplitView extends HeightMap implements IDisposable {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
size = typeof size === 'number' ? size : item.height;
|
size = typeof size === 'number' ? size : item.size;
|
||||||
size = clamp(size, item.view.minimumSize, item.view.maximumSize);
|
size = clamp(size, item.view.minimumSize, item.view.maximumSize);
|
||||||
|
|
||||||
if (this.inverseAltBehavior && index > 0) {
|
if (this.inverseAltBehavior && index > 0) {
|
||||||
// In this case, we want the view to grow or shrink both sides equally
|
// In this case, we want the view to grow or shrink both sides equally
|
||||||
// so we just resize the "left" side by half and let `resize` do the clamping magic
|
// so we just resize the "left" side by half and let `resize` do the clamping magic
|
||||||
this.resize(index - 1, Math.floor((item.height - size) / 2));
|
this.resize(index - 1, Math.floor((item.size - size) / 2));
|
||||||
this.distributeEmptySpace();
|
this.distributeEmptySpace();
|
||||||
this.layoutViews();
|
this.layoutViews();
|
||||||
} else {
|
} else {
|
||||||
|
item.size = size;
|
||||||
this.updateSize(item.view.id, size);
|
this.updateSize(item.view.id, size);
|
||||||
this.updateSize(item.view.id, size);
|
let top = item.top + item.size;
|
||||||
let top = item.top + item.height;
|
|
||||||
for (let i = index + 1; i < this.viewItems.length; i++) {
|
for (let i = index + 1; i < this.viewItems.length; i++) {
|
||||||
let currentItem = this.viewItems[i];
|
let currentItem = this.viewItems[i];
|
||||||
this.updateTop(currentItem.view.id, top);
|
this.updateTop(currentItem.view.id, top);
|
||||||
top += currentItem.height;
|
top += currentItem.size;
|
||||||
}
|
}
|
||||||
this.relayout(index);
|
this.relayout(index);
|
||||||
}
|
}
|
||||||
@@ -621,12 +631,12 @@ export class ScrollableSplitView extends HeightMap implements IDisposable {
|
|||||||
const item = this.viewItems[index];
|
const item = this.viewItems[index];
|
||||||
size = Math.round(size);
|
size = Math.round(size);
|
||||||
size = clamp(size, item.view.minimumSize, item.view.maximumSize);
|
size = clamp(size, item.view.minimumSize, item.view.maximumSize);
|
||||||
let delta = size - item.height;
|
let delta = size - item.size;
|
||||||
|
|
||||||
if (delta !== 0 && index < this.viewItems.length - 1) {
|
if (delta !== 0 && index < this.viewItems.length - 1) {
|
||||||
const downIndexes = range(index + 1, this.viewItems.length);
|
const downIndexes = range(index + 1, this.viewItems.length);
|
||||||
const collapseDown = downIndexes.reduce((r, i) => r + (this.viewItems[i].height - this.viewItems[i].view.minimumSize), 0);
|
const collapseDown = downIndexes.reduce((r, i) => r + (this.viewItems[i].size - this.viewItems[i].view.minimumSize), 0);
|
||||||
const expandDown = downIndexes.reduce((r, i) => r + (this.viewItems[i].view.maximumSize - this.viewItems[i].height), 0);
|
const expandDown = downIndexes.reduce((r, i) => r + (this.viewItems[i].view.maximumSize - this.viewItems[i].size), 0);
|
||||||
const deltaDown = clamp(delta, -expandDown, collapseDown);
|
const deltaDown = clamp(delta, -expandDown, collapseDown);
|
||||||
|
|
||||||
this.resize(index, deltaDown);
|
this.resize(index, deltaDown);
|
||||||
@@ -635,8 +645,8 @@ export class ScrollableSplitView extends HeightMap implements IDisposable {
|
|||||||
|
|
||||||
if (delta !== 0 && index > 0) {
|
if (delta !== 0 && index > 0) {
|
||||||
const upIndexes = range(index - 1, -1);
|
const upIndexes = range(index - 1, -1);
|
||||||
const collapseUp = upIndexes.reduce((r, i) => r + (this.viewItems[i].height - this.viewItems[i].view.minimumSize), 0);
|
const collapseUp = upIndexes.reduce((r, i) => r + (this.viewItems[i].size - this.viewItems[i].view.minimumSize), 0);
|
||||||
const expandUp = upIndexes.reduce((r, i) => r + (this.viewItems[i].view.maximumSize - this.viewItems[i].height), 0);
|
const expandUp = upIndexes.reduce((r, i) => r + (this.viewItems[i].view.maximumSize - this.viewItems[i].size), 0);
|
||||||
const deltaUp = clamp(-delta, -collapseUp, expandUp);
|
const deltaUp = clamp(-delta, -collapseUp, expandUp);
|
||||||
|
|
||||||
this.resize(index - 1, deltaUp);
|
this.resize(index - 1, deltaUp);
|
||||||
@@ -661,7 +671,7 @@ export class ScrollableSplitView extends HeightMap implements IDisposable {
|
|||||||
return -1;
|
return -1;
|
||||||
}
|
}
|
||||||
|
|
||||||
return this.viewItems[index].height;
|
return this.viewItems[index].size;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@@ -756,7 +766,7 @@ export class ScrollableSplitView extends HeightMap implements IDisposable {
|
|||||||
private resize(
|
private resize(
|
||||||
index: number,
|
index: number,
|
||||||
delta: number,
|
delta: number,
|
||||||
sizes = this.viewItems.map(i => i.height),
|
sizes = this.viewItems.map(i => i.size),
|
||||||
lowPriorityIndex?: number,
|
lowPriorityIndex?: number,
|
||||||
highPriorityIndex?: number,
|
highPriorityIndex?: number,
|
||||||
overloadMinDelta: number = Number.NEGATIVE_INFINITY,
|
overloadMinDelta: number = Number.NEGATIVE_INFINITY,
|
||||||
@@ -800,10 +810,8 @@ export class ScrollableSplitView extends HeightMap implements IDisposable {
|
|||||||
const viewDelta = size - upSizes[i];
|
const viewDelta = size - upSizes[i];
|
||||||
|
|
||||||
deltaUp -= viewDelta;
|
deltaUp -= viewDelta;
|
||||||
this.updateSize(item.view.id, size);
|
item.size = size;
|
||||||
this.dirtyState = true;
|
this.dirtyState = true;
|
||||||
this.lastRenderTop = 0;
|
|
||||||
this.lastRenderHeight = 0;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
for (let i = 0, deltaDown = delta; i < downItems.length; i++) {
|
for (let i = 0, deltaDown = delta; i < downItems.length; i++) {
|
||||||
@@ -812,32 +820,30 @@ export class ScrollableSplitView extends HeightMap implements IDisposable {
|
|||||||
const viewDelta = size - downSizes[i];
|
const viewDelta = size - downSizes[i];
|
||||||
|
|
||||||
deltaDown += viewDelta;
|
deltaDown += viewDelta;
|
||||||
this.updateSize(item.view.id, size);
|
item.size = size;
|
||||||
this.dirtyState = true;
|
this.dirtyState = true;
|
||||||
this.lastRenderTop = 0;
|
|
||||||
this.lastRenderHeight = 0;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return delta;
|
return delta;
|
||||||
}
|
}
|
||||||
|
|
||||||
private distributeEmptySpace(): void {
|
private distributeEmptySpace(): void {
|
||||||
let contentSize = this.viewItems.reduce((r, i) => r + i.height, 0);
|
let contentSize = this.viewItems.reduce((r, i) => r + i.size, 0);
|
||||||
let emptyDelta = this.size - contentSize;
|
let emptyDelta = this.size - contentSize;
|
||||||
|
|
||||||
for (let i = this.viewItems.length - 1; emptyDelta !== 0 && i >= 0; i--) {
|
for (let i = this.viewItems.length - 1; emptyDelta !== 0 && i >= 0; i--) {
|
||||||
const item = this.viewItems[i];
|
const item = this.viewItems[i];
|
||||||
const size = clamp(item.height + emptyDelta, item.view.minimumSize, item.view.maximumSize);
|
const size = clamp(item.size + emptyDelta, item.view.minimumSize, item.view.maximumSize);
|
||||||
const viewDelta = size - item.height;
|
const viewDelta = size - item.size;
|
||||||
|
|
||||||
emptyDelta -= viewDelta;
|
emptyDelta -= viewDelta;
|
||||||
this.updateSize(item.view.id, size);
|
item.size = size;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private layoutViews(): void {
|
private layoutViews(): void {
|
||||||
// Save new content size
|
// Save new content size
|
||||||
this.contentSize = this.viewItems.reduce((r, i) => r + i.height, 0);
|
this.contentSize = this.viewItems.reduce((r, i) => r + i.size, 0);
|
||||||
|
|
||||||
if (this.dirtyState) {
|
if (this.dirtyState) {
|
||||||
for (let i = this.indexAt(this.lastRenderTop); i <= this.indexAfter(this.lastRenderTop + this.lastRenderHeight) - 1; i++) {
|
for (let i = this.indexAt(this.lastRenderTop); i <= this.indexAfter(this.lastRenderTop + this.lastRenderHeight) - 1; i++) {
|
||||||
@@ -859,7 +865,7 @@ export class ScrollableSplitView extends HeightMap implements IDisposable {
|
|||||||
let position = 0;
|
let position = 0;
|
||||||
|
|
||||||
for (let i = 0; i < this.sashItems.length; i++) {
|
for (let i = 0; i < this.sashItems.length; i++) {
|
||||||
position += this.viewItems[i].height;
|
position += this.viewItems[i].size;
|
||||||
|
|
||||||
if (this.sashItems[i].sash === sash) {
|
if (this.sashItems[i].sash === sash) {
|
||||||
return position;
|
return position;
|
||||||
|
|||||||
@@ -154,11 +154,13 @@ export class SelectBox extends vsSelectBox {
|
|||||||
public showMessage(message: IMessage): void {
|
public showMessage(message: IMessage): void {
|
||||||
this.message = message;
|
this.message = message;
|
||||||
|
|
||||||
dom.removeClass(this.element, 'idle');
|
if (this.element) {
|
||||||
dom.removeClass(this.element, 'info');
|
dom.removeClass(this.element, 'idle');
|
||||||
dom.removeClass(this.element, 'warning');
|
dom.removeClass(this.element, 'info');
|
||||||
dom.removeClass(this.element, 'error');
|
dom.removeClass(this.element, 'warning');
|
||||||
dom.addClass(this.element, this.classForType(message.type));
|
dom.removeClass(this.element, 'error');
|
||||||
|
dom.addClass(this.element, this.classForType(message.type));
|
||||||
|
}
|
||||||
|
|
||||||
// ARIA Support
|
// ARIA Support
|
||||||
let alertText: string;
|
let alertText: string;
|
||||||
@@ -213,8 +215,6 @@ export class SelectBox extends vsSelectBox {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public hideMessage(): void {
|
public hideMessage(): void {
|
||||||
this.message = null;
|
|
||||||
|
|
||||||
dom.removeClass(this.element, 'info');
|
dom.removeClass(this.element, 'info');
|
||||||
dom.removeClass(this.element, 'warning');
|
dom.removeClass(this.element, 'warning');
|
||||||
dom.removeClass(this.element, 'error');
|
dom.removeClass(this.element, 'error');
|
||||||
@@ -222,10 +222,12 @@ export class SelectBox extends vsSelectBox {
|
|||||||
|
|
||||||
this._hideMessage();
|
this._hideMessage();
|
||||||
this.applyStyles();
|
this.applyStyles();
|
||||||
|
|
||||||
|
this.message = null;
|
||||||
}
|
}
|
||||||
|
|
||||||
private _hideMessage(): void {
|
private _hideMessage(): void {
|
||||||
if (this.contextViewProvider) {
|
if (this.message && this.contextViewProvider) {
|
||||||
this.contextViewProvider.hideContextView();
|
this.contextViewProvider.hideContextView();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -51,6 +51,7 @@
|
|||||||
|
|
||||||
.account-view .list-row {
|
.account-view .list-row {
|
||||||
padding: 12px;
|
padding: 12px;
|
||||||
|
line-height: 18px !important;
|
||||||
}
|
}
|
||||||
|
|
||||||
.account-view .list-row .icon {
|
.account-view .list-row .icon {
|
||||||
|
|||||||
@@ -13,6 +13,7 @@ import { MimeModel } from 'sql/parts/notebook/outputs/common/mimemodel';
|
|||||||
import * as outputProcessor from 'sql/parts/notebook/outputs/common/outputProcessor';
|
import * as outputProcessor from 'sql/parts/notebook/outputs/common/outputProcessor';
|
||||||
import { RenderMimeRegistry } from 'sql/parts/notebook/outputs/registry';
|
import { RenderMimeRegistry } from 'sql/parts/notebook/outputs/registry';
|
||||||
import 'vs/css!sql/parts/notebook/outputs/style/index';
|
import 'vs/css!sql/parts/notebook/outputs/style/index';
|
||||||
|
import { IThemeService } from 'vs/platform/theme/common/themeService';
|
||||||
|
|
||||||
export const OUTPUT_SELECTOR: string = 'output-component';
|
export const OUTPUT_SELECTOR: string = 'output-component';
|
||||||
|
|
||||||
@@ -31,7 +32,8 @@ export class OutputComponent extends AngularDisposable implements OnInit {
|
|||||||
|
|
||||||
|
|
||||||
constructor(
|
constructor(
|
||||||
@Inject(INotebookService) private _notebookService: INotebookService
|
@Inject(INotebookService) private _notebookService: INotebookService,
|
||||||
|
@Inject(IThemeService) private _themeService: IThemeService
|
||||||
) {
|
) {
|
||||||
super();
|
super();
|
||||||
this.registry = _notebookService.getMimeRegistry();
|
this.registry = _notebookService.getMimeRegistry();
|
||||||
@@ -49,6 +51,7 @@ export class OutputComponent extends AngularDisposable implements OnInit {
|
|||||||
let node = this.outputElement.nativeElement;
|
let node = this.outputElement.nativeElement;
|
||||||
let output = this.cellOutput;
|
let output = this.cellOutput;
|
||||||
let options = outputProcessor.getBundleOptions({ value: output, trusted: this.trustedMode });
|
let options = outputProcessor.getBundleOptions({ value: output, trusted: this.trustedMode });
|
||||||
|
options.themeService = this._themeService;
|
||||||
// TODO handle safe/unsafe mapping
|
// TODO handle safe/unsafe mapping
|
||||||
this.createRenderedMimetype(options, node);
|
this.createRenderedMimetype(options, node);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -9,9 +9,9 @@
|
|||||||
'use strict';
|
'use strict';
|
||||||
|
|
||||||
import { nb } from 'azdata';
|
import { nb } from 'azdata';
|
||||||
import * as nls from 'vs/nls';
|
|
||||||
import { URI } from 'vs/base/common/uri';
|
import { URI } from 'vs/base/common/uri';
|
||||||
import { Event, Emitter } from 'vs/base/common/event';
|
import { Event, Emitter } from 'vs/base/common/event';
|
||||||
|
import { localize } from 'vs/nls';
|
||||||
|
|
||||||
import { IClientSession, IKernelPreference, IClientSessionOptions } from './modelInterfaces';
|
import { IClientSession, IKernelPreference, IClientSessionOptions } from './modelInterfaces';
|
||||||
import { Deferred } from 'sql/base/common/promise';
|
import { Deferred } from 'sql/base/common/promise';
|
||||||
@@ -70,12 +70,14 @@ export class ClientSession implements IClientSession {
|
|||||||
await this.initializeSession();
|
await this.initializeSession();
|
||||||
await this.updateCachedKernelSpec();
|
await this.updateCachedKernelSpec();
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
this._errorMessage = notebookUtils.getErrorMessage(err);
|
this._errorMessage = notebookUtils.getErrorMessage(err) || localize('clientSession.unknownError', "An error occurred while starting the notebook session");
|
||||||
}
|
}
|
||||||
// Always resolving for now. It's up to callers to check for error case
|
// Always resolving for now. It's up to callers to check for error case
|
||||||
this._isReady = true;
|
this._isReady = true;
|
||||||
this._ready.resolve();
|
this._ready.resolve();
|
||||||
this._kernelChangeCompleted.resolve();
|
if (!this.isInErrorState && this._session && this._session.kernel) {
|
||||||
|
await this.notifyKernelChanged(undefined, this._session.kernel);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private async startServer(): Promise<void> {
|
private async startServer(): Promise<void> {
|
||||||
@@ -83,7 +85,7 @@ export class ClientSession implements IClientSession {
|
|||||||
if (serverManager && !serverManager.isStarted) {
|
if (serverManager && !serverManager.isStarted) {
|
||||||
await serverManager.startServer();
|
await serverManager.startServer();
|
||||||
if (!serverManager.isStarted) {
|
if (!serverManager.isStarted) {
|
||||||
throw new Error(nls.localize('ServerNotStarted', 'Server did not start for unknown reason'));
|
throw new Error(localize('ServerNotStarted', "Server did not start for unknown reason"));
|
||||||
}
|
}
|
||||||
this.isServerStarted = serverManager.isStarted;
|
this.isServerStarted = serverManager.isStarted;
|
||||||
} else {
|
} else {
|
||||||
@@ -116,7 +118,7 @@ export class ClientSession implements IClientSession {
|
|||||||
} catch (err) {
|
} catch (err) {
|
||||||
// TODO move registration
|
// TODO move registration
|
||||||
if (err && err.response && err.response.status === 501) {
|
if (err && err.response && err.response.status === 501) {
|
||||||
this.options.notificationService.warn(nls.localize('kernelRequiresConnection', 'Kernel {0} was not found. The default kernel will be used instead.', kernelName));
|
this.options.notificationService.warn(localize('kernelRequiresConnection', "Kernel {0} was not found. The default kernel will be used instead.", kernelName));
|
||||||
session = await this.notebookManager.sessionManager.startNew({
|
session = await this.notebookManager.sessionManager.startNew({
|
||||||
path: this.notebookUri.fsPath,
|
path: this.notebookUri.fsPath,
|
||||||
kernelName: undefined
|
kernelName: undefined
|
||||||
@@ -246,6 +248,11 @@ export class ClientSession implements IClientSession {
|
|||||||
this._isReady = kernel.isReady;
|
this._isReady = kernel.isReady;
|
||||||
await this.updateCachedKernelSpec();
|
await this.updateCachedKernelSpec();
|
||||||
// Send resolution events to listeners
|
// Send resolution events to listeners
|
||||||
|
await this.notifyKernelChanged(oldKernel, newKernel);
|
||||||
|
return kernel;
|
||||||
|
}
|
||||||
|
|
||||||
|
private async notifyKernelChanged(oldKernel: nb.IKernel, newKernel: nb.IKernel): Promise<void> {
|
||||||
let changeArgs: nb.IKernelChangedArgs = {
|
let changeArgs: nb.IKernelChangedArgs = {
|
||||||
oldValue: oldKernel,
|
oldValue: oldKernel,
|
||||||
newValue: newKernel
|
newValue: newKernel
|
||||||
@@ -255,7 +262,6 @@ export class ClientSession implements IClientSession {
|
|||||||
// Wait on connection configuration to complete before resolving full kernel change
|
// Wait on connection configuration to complete before resolving full kernel change
|
||||||
this._kernelChangeCompleted.resolve();
|
this._kernelChangeCompleted.resolve();
|
||||||
this._kernelChangedEmitter.fire(changeArgs);
|
this._kernelChangedEmitter.fire(changeArgs);
|
||||||
return kernel;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private async updateCachedKernelSpec(): Promise<void> {
|
private async updateCachedKernelSpec(): Promise<void> {
|
||||||
|
|||||||
@@ -354,7 +354,7 @@ export class NotebookModel extends Disposable implements INotebookModel {
|
|||||||
cellIndex: index
|
cellIndex: index
|
||||||
});
|
});
|
||||||
} else {
|
} else {
|
||||||
this.notifyError(localize('deleteCellFailed', 'Failed to delete cell.'));
|
this.notifyError(localize('deleteCellFailed', "Failed to delete cell."));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -392,7 +392,7 @@ export class NotebookModel extends Disposable implements INotebookModel {
|
|||||||
this._onErrorEmitter.fire({ message: error, severity: Severity.Error });
|
this._onErrorEmitter.fire({ message: error, severity: Severity.Error });
|
||||||
}
|
}
|
||||||
|
|
||||||
public async startSession(manager: INotebookManager, displayName?: string): Promise<void> {
|
public async startSession(manager: INotebookManager, displayName?: string, setErrorStateOnFail?: boolean): Promise<void> {
|
||||||
if (displayName) {
|
if (displayName) {
|
||||||
let standardKernel = this._notebookOptions.standardKernels.find(kernel => kernel.displayName === displayName);
|
let standardKernel = this._notebookOptions.standardKernels.find(kernel => kernel.displayName === displayName);
|
||||||
this._defaultKernel = displayName ? { name: standardKernel.name, display_name: standardKernel.displayName } : this._defaultKernel;
|
this._defaultKernel = displayName ? { name: standardKernel.name, display_name: standardKernel.displayName } : this._defaultKernel;
|
||||||
@@ -406,7 +406,6 @@ export class NotebookModel extends Disposable implements INotebookModel {
|
|||||||
});
|
});
|
||||||
if (!this._activeClientSession) {
|
if (!this._activeClientSession) {
|
||||||
this.updateActiveClientSession(clientSession);
|
this.updateActiveClientSession(clientSession);
|
||||||
|
|
||||||
}
|
}
|
||||||
let profile = new ConnectionProfile(this._notebookOptions.capabilitiesService, this.connectionProfile);
|
let profile = new ConnectionProfile(this._notebookOptions.capabilitiesService, this.connectionProfile);
|
||||||
|
|
||||||
@@ -420,16 +419,31 @@ export class NotebookModel extends Disposable implements INotebookModel {
|
|||||||
this._activeConnection = undefined;
|
this._activeConnection = undefined;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
clientSession.onKernelChanging(async (e) => {
|
||||||
|
await this.loadActiveContexts(e);
|
||||||
|
});
|
||||||
|
clientSession.statusChanged(async (session) => {
|
||||||
|
this._kernelsChangedEmitter.fire(session.kernel);
|
||||||
|
});
|
||||||
await clientSession.initialize();
|
await clientSession.initialize();
|
||||||
// By somehow we have to wait for ready, otherwise may not be called for some cases.
|
// By somehow we have to wait for ready, otherwise may not be called for some cases.
|
||||||
await clientSession.ready;
|
await clientSession.ready;
|
||||||
if (clientSession.isInErrorState) {
|
if (clientSession.kernel) {
|
||||||
this.setErrorState(clientSession.errorMessage);
|
await clientSession.kernel.ready;
|
||||||
} else {
|
await this.updateKernelInfoOnKernelChange(clientSession.kernel);
|
||||||
this._onClientSessionReady.fire(clientSession);
|
|
||||||
// Once session is loaded, can use the session manager to retrieve useful info
|
|
||||||
this.loadKernelInfo(clientSession, this.defaultKernel.display_name);
|
|
||||||
}
|
}
|
||||||
|
if (clientSession.isInErrorState) {
|
||||||
|
if (setErrorStateOnFail) {
|
||||||
|
this.setErrorState(clientSession.errorMessage);
|
||||||
|
} else {
|
||||||
|
throw new Error(clientSession.errorMessage);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
this._onClientSessionReady.fire(clientSession);
|
||||||
|
this._kernelChangedEmitter.fire({
|
||||||
|
oldValue: undefined,
|
||||||
|
newValue: clientSession.kernel
|
||||||
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -552,10 +566,63 @@ export class NotebookModel extends Disposable implements INotebookModel {
|
|||||||
this.doChangeKernel(displayName, true);
|
this.doChangeKernel(displayName, true);
|
||||||
}
|
}
|
||||||
|
|
||||||
public async doChangeKernel(displayName: string, mustSetProvider: boolean = true): Promise<void> {
|
private async doChangeKernel(displayName: string, mustSetProvider: boolean = true, restoreOnFail: boolean = true): Promise<void> {
|
||||||
if (mustSetProvider) {
|
if (!displayName) {
|
||||||
await this.setProviderIdAndStartSession(displayName);
|
// Can't change to an undefined kernel
|
||||||
|
return;
|
||||||
}
|
}
|
||||||
|
let oldDisplayName = this._activeClientSession && this._activeClientSession.kernel ? this._activeClientSession.kernel.name : undefined;
|
||||||
|
try {
|
||||||
|
let changeKernelNeeded = true;
|
||||||
|
if (mustSetProvider) {
|
||||||
|
let providerChanged = await this.tryStartSessionByChangingProviders(displayName);
|
||||||
|
// If provider was changed, a new session with new kernel is already created. We can skip calling changeKernel.
|
||||||
|
changeKernelNeeded = !providerChanged;
|
||||||
|
}
|
||||||
|
if (changeKernelNeeded) {
|
||||||
|
let spec = this.findSpec(displayName);
|
||||||
|
if (this._activeClientSession && this._activeClientSession.isReady) {
|
||||||
|
let kernel = await this._activeClientSession.changeKernel(spec, this._oldKernel);
|
||||||
|
try {
|
||||||
|
await kernel.ready;
|
||||||
|
await this.updateKernelInfoOnKernelChange(kernel);
|
||||||
|
} catch (err2) {
|
||||||
|
// TODO should we handle this in any way?
|
||||||
|
console.log(`doChangeKernel: ignoring error ${notebookUtils.getErrorMessage(err2)}`);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} catch (err) {
|
||||||
|
if (oldDisplayName && restoreOnFail) {
|
||||||
|
this.notifyError(localize('changeKernelFailedRetry', "Failed to change kernel. Kernel {0} will be used. Error was: {1}", oldDisplayName, notebookUtils.getErrorMessage(err)));
|
||||||
|
// Clear out previous kernel
|
||||||
|
let failedProviderId = this.tryFindProviderForKernel(displayName, true);
|
||||||
|
let oldProviderId = this.tryFindProviderForKernel(oldDisplayName, true);
|
||||||
|
if (failedProviderId !== oldProviderId) {
|
||||||
|
// We need to clear out the old kernel information so we switch providers. Otherwise in the SQL -> Jupyter -> SQL failure case,
|
||||||
|
// we would never reset the providers
|
||||||
|
this._oldKernel = undefined;
|
||||||
|
}
|
||||||
|
return this.doChangeKernel(oldDisplayName, mustSetProvider, false);
|
||||||
|
} else {
|
||||||
|
this.notifyError(localize('changeKernelFailed', "Failed to change kernel due to error: {0}", notebookUtils.getErrorMessage(err)));
|
||||||
|
this._kernelChangedEmitter.fire({
|
||||||
|
newValue: undefined,
|
||||||
|
oldValue: undefined
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// Else no need to do anything
|
||||||
|
}
|
||||||
|
|
||||||
|
private async updateKernelInfoOnKernelChange(kernel: nb.IKernel) {
|
||||||
|
await this.updateKernelInfo(kernel);
|
||||||
|
if (kernel.info) {
|
||||||
|
this.updateLanguageInfo(kernel.info.language_info);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private findSpec(displayName: string) {
|
||||||
let spec = this.getKernelSpecFromDisplayName(displayName);
|
let spec = this.getKernelSpecFromDisplayName(displayName);
|
||||||
if (spec) {
|
if (spec) {
|
||||||
// Ensure that the kernel we try to switch to is a valid kernel; if not, use the default
|
// Ensure that the kernel we try to switch to is a valid kernel; if not, use the default
|
||||||
@@ -563,24 +630,11 @@ export class NotebookModel extends Disposable implements INotebookModel {
|
|||||||
if (kernelSpecs && kernelSpecs.length > 0 && kernelSpecs.findIndex(k => k.display_name === spec.display_name) < 0) {
|
if (kernelSpecs && kernelSpecs.length > 0 && kernelSpecs.findIndex(k => k.display_name === spec.display_name) < 0) {
|
||||||
spec = kernelSpecs.find(spec => spec.name === this.notebookManager.sessionManager.specs.defaultKernel);
|
spec = kernelSpecs.find(spec => spec.name === this.notebookManager.sessionManager.specs.defaultKernel);
|
||||||
}
|
}
|
||||||
} else {
|
}
|
||||||
|
else {
|
||||||
spec = notebookConstants.sqlKernelSpec;
|
spec = notebookConstants.sqlKernelSpec;
|
||||||
}
|
}
|
||||||
if (this._activeClientSession && this._activeClientSession.isReady) {
|
return spec;
|
||||||
return this._activeClientSession.changeKernel(spec, this._oldKernel)
|
|
||||||
.then((kernel) => {
|
|
||||||
this.updateKernelInfo(kernel);
|
|
||||||
kernel.ready.then(() => {
|
|
||||||
if (kernel.info) {
|
|
||||||
this.updateLanguageInfo(kernel.info.language_info);
|
|
||||||
}
|
|
||||||
}, err => undefined);
|
|
||||||
}).catch((err) => {
|
|
||||||
this.notifyError(localize('changeKernelFailed', 'Failed to change kernel: {0}', notebookUtils.getErrorMessage(err)));
|
|
||||||
// TODO should revert kernels dropdown
|
|
||||||
});
|
|
||||||
}
|
|
||||||
return Promise.resolve();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public async changeContext(server: string, newConnection?: IConnectionProfile, hideErrorMessage?: boolean): Promise<void> {
|
public async changeContext(server: string, newConnection?: IConnectionProfile, hideErrorMessage?: boolean): Promise<void> {
|
||||||
@@ -613,7 +667,7 @@ export class NotebookModel extends Disposable implements INotebookModel {
|
|||||||
});
|
});
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
let msg = notebookUtils.getErrorMessage(err);
|
let msg = notebookUtils.getErrorMessage(err);
|
||||||
this.notifyError(localize('changeContextFailed', 'Changing context failed: {0}', msg));
|
this.notifyError(localize('changeContextFailed', "Changing context failed: {0}", msg));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -631,17 +685,6 @@ export class NotebookModel extends Disposable implements INotebookModel {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private loadKernelInfo(clientSession: IClientSession, displayName: string): void {
|
|
||||||
clientSession.onKernelChanging(async (e) => {
|
|
||||||
await this.loadActiveContexts(e);
|
|
||||||
});
|
|
||||||
clientSession.statusChanged(async (session) => {
|
|
||||||
this._kernelsChangedEmitter.fire(session.kernel);
|
|
||||||
});
|
|
||||||
|
|
||||||
this.doChangeKernel(displayName, false);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Get default language if saved in notebook file
|
// Get default language if saved in notebook file
|
||||||
// Otherwise, default to python
|
// Otherwise, default to python
|
||||||
private getDefaultLanguageInfo(notebook: nb.INotebookContents): nb.ILanguageInfo {
|
private getDefaultLanguageInfo(notebook: nb.INotebookContents): nb.ILanguageInfo {
|
||||||
@@ -703,7 +746,7 @@ export class NotebookModel extends Disposable implements INotebookModel {
|
|||||||
|
|
||||||
private setErrorState(errMsg: string): void {
|
private setErrorState(errMsg: string): void {
|
||||||
this._inErrorState = true;
|
this._inErrorState = true;
|
||||||
let msg = localize('startSessionFailed', 'Could not start session: {0}', errMsg);
|
let msg = localize('startSessionFailed', "Could not start session: {0}", errMsg);
|
||||||
this.notifyError(msg);
|
this.notifyError(msg);
|
||||||
|
|
||||||
}
|
}
|
||||||
@@ -730,7 +773,7 @@ export class NotebookModel extends Disposable implements INotebookModel {
|
|||||||
}
|
}
|
||||||
await this.shutdownActiveSession();
|
await this.shutdownActiveSession();
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
this.notifyError(localize('shutdownError', 'An error occurred when closing the notebook: {0}', err));
|
this.notifyError(localize('shutdownError', "An error occurred when closing the notebook: {0}", err));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -740,7 +783,7 @@ export class NotebookModel extends Disposable implements INotebookModel {
|
|||||||
await this._activeClientSession.ready;
|
await this._activeClientSession.ready;
|
||||||
}
|
}
|
||||||
catch (err) {
|
catch (err) {
|
||||||
this.notifyError(localize('shutdownClientSessionError', 'A client session error occurred when closing the notebook: {0}', err));
|
this.notifyError(localize('shutdownClientSessionError', "A client session error occurred when closing the notebook: {0}", err));
|
||||||
}
|
}
|
||||||
await this._activeClientSession.shutdown();
|
await this._activeClientSession.shutdown();
|
||||||
this.clearClientSessionListeners();
|
this.clearClientSessionListeners();
|
||||||
@@ -794,46 +837,39 @@ export class NotebookModel extends Disposable implements INotebookModel {
|
|||||||
* Set _providerId and start session if it is new provider
|
* Set _providerId and start session if it is new provider
|
||||||
* @param displayName Kernel dispay name
|
* @param displayName Kernel dispay name
|
||||||
*/
|
*/
|
||||||
private async setProviderIdAndStartSession(displayName: string): Promise<void> {
|
private async tryStartSessionByChangingProviders(displayName: string): Promise<boolean> {
|
||||||
if (displayName) {
|
if (displayName) {
|
||||||
if (this._activeClientSession && this._activeClientSession.isReady) {
|
if (this._activeClientSession && this._activeClientSession.isReady) {
|
||||||
this._oldKernel = this._activeClientSession.kernel;
|
this._oldKernel = this._activeClientSession.kernel;
|
||||||
let providerId = this.tryFindProviderForKernel(displayName);
|
}
|
||||||
|
let providerId = this.tryFindProviderForKernel(displayName);
|
||||||
|
|
||||||
if (providerId) {
|
if (providerId && providerId !== this._providerId) {
|
||||||
if (providerId !== this._providerId) {
|
this._providerId = providerId;
|
||||||
this._providerId = providerId;
|
this._onProviderIdChanged.fire(this._providerId);
|
||||||
this._onProviderIdChanged.fire(this._providerId);
|
|
||||||
|
|
||||||
await this.shutdownActiveSession();
|
await this.shutdownActiveSession();
|
||||||
|
let manager = this.getNotebookManager(providerId);
|
||||||
try {
|
if (manager) {
|
||||||
let manager = this.getNotebookManager(providerId);
|
await this.startSession(manager, displayName, false);
|
||||||
if (manager) {
|
} else {
|
||||||
await this.startSession(manager, displayName);
|
throw new Error(localize('ProviderNoManager', "Can't find notebook manager for provider {0}", providerId));
|
||||||
} else {
|
|
||||||
throw new Error(localize('ProviderNoManager', "Can't find notebook manager for provider {0}", providerId));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
catch (err) {
|
|
||||||
console.log(err);
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
console.log(`No provider found supporting the kernel: ${displayName}`);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
return true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
private tryFindProviderForKernel(displayName: string) {
|
private tryFindProviderForKernel(displayName: string, alwaysReturnId: boolean = false): string {
|
||||||
if (!displayName) {
|
if (!displayName) {
|
||||||
return undefined;
|
return undefined;
|
||||||
}
|
}
|
||||||
let standardKernel = this.getStandardKernelFromDisplayName(displayName);
|
let standardKernel = this.getStandardKernelFromDisplayName(displayName);
|
||||||
if (standardKernel && this._oldKernel && this._oldKernel.name !== standardKernel.name) {
|
if (standardKernel) {
|
||||||
if (this._kernelDisplayNameToNotebookProviderIds.has(displayName)) {
|
let providerId = this._kernelDisplayNameToNotebookProviderIds.get(displayName);
|
||||||
return this._kernelDisplayNameToNotebookProviderIds.get(displayName);
|
if (alwaysReturnId || (!this._oldKernel || this._oldKernel.name !== standardKernel.name)) {
|
||||||
|
return providerId;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return undefined;
|
return undefined;
|
||||||
@@ -883,7 +919,7 @@ export class NotebookModel extends Disposable implements INotebookModel {
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
onCellChange(cell: CellModel, change: NotebookChangeType): void {
|
onCellChange(cell: ICellModel, change: NotebookChangeType): void {
|
||||||
let changeInfo: NotebookContentChange = {
|
let changeInfo: NotebookContentChange = {
|
||||||
changeType: change,
|
changeType: change,
|
||||||
cells: [cell]
|
cells: [cell]
|
||||||
|
|||||||
@@ -248,7 +248,7 @@ export class NotebookComponent extends AngularDisposable implements OnInit, OnDe
|
|||||||
this._model = this._register(model);
|
this._model = this._register(model);
|
||||||
this.updateToolbarComponents(this._model.trustedMode);
|
this.updateToolbarComponents(this._model.trustedMode);
|
||||||
this._modelRegisteredDeferred.resolve(this._model);
|
this._modelRegisteredDeferred.resolve(this._model);
|
||||||
await model.startSession(this.model.notebookManager);
|
await model.startSession(this.model.notebookManager, undefined, true);
|
||||||
this.detectChanges();
|
this.detectChanges();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -235,18 +235,24 @@ export class KernelsDropdown extends SelectBox {
|
|||||||
this._register(this.model.kernelChanged((changedArgs: azdata.nb.IKernelChangedArgs) => {
|
this._register(this.model.kernelChanged((changedArgs: azdata.nb.IKernelChangedArgs) => {
|
||||||
this.updateKernel(changedArgs.newValue);
|
this.updateKernel(changedArgs.newValue);
|
||||||
}));
|
}));
|
||||||
|
let kernel = this.model.clientSession && this.model.clientSession.kernel;
|
||||||
|
this.updateKernel(kernel);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Update SelectBox values
|
// Update SelectBox values
|
||||||
public updateKernel(kernel: azdata.nb.IKernel) {
|
public updateKernel(kernel: azdata.nb.IKernel) {
|
||||||
if (kernel) {
|
let kernels: string[] = this.model.standardKernelsDisplayName();
|
||||||
|
if (kernel && kernel.isReady) {
|
||||||
let standardKernel = this.model.getStandardKernelFromName(kernel.name);
|
let standardKernel = this.model.getStandardKernelFromName(kernel.name);
|
||||||
|
|
||||||
let kernels: string[] = this.model.standardKernelsDisplayName();
|
|
||||||
if (kernels && standardKernel) {
|
if (kernels && standardKernel) {
|
||||||
let index = kernels.findIndex((kernel => kernel === standardKernel.displayName));
|
let index = kernels.findIndex((kernel => kernel === standardKernel.displayName));
|
||||||
this.setOptions(kernels, index);
|
this.setOptions(kernels, index);
|
||||||
}
|
}
|
||||||
|
} else if (this.model.clientSession.isInErrorState) {
|
||||||
|
let noKernelName = localize('noKernel', "No Kernel");
|
||||||
|
kernels.unshift(noKernelName);
|
||||||
|
this.setOptions(kernels, 0);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -283,22 +289,26 @@ export class AttachToDropdown extends SelectBox {
|
|||||||
public updateModel(model: INotebookModel): void {
|
public updateModel(model: INotebookModel): void {
|
||||||
this.model = model as NotebookModel;
|
this.model = model as NotebookModel;
|
||||||
this._register(model.contextsChanged(() => {
|
this._register(model.contextsChanged(() => {
|
||||||
let kernelDisplayName: string = this.getKernelDisplayName();
|
this.handleContextsChanged();
|
||||||
if (kernelDisplayName) {
|
|
||||||
this.loadAttachToDropdown(this.model, kernelDisplayName);
|
|
||||||
}
|
|
||||||
}));
|
}));
|
||||||
this._register(this.model.contextsLoading(() => {
|
this._register(this.model.contextsLoading(() => {
|
||||||
this.setOptions([msgLoadingContexts], 0);
|
this.setOptions([msgLoadingContexts], 0);
|
||||||
}));
|
}));
|
||||||
|
this.handleContextsChanged();
|
||||||
|
}
|
||||||
|
|
||||||
|
private handleContextsChanged(showSelectConnection?: boolean) {
|
||||||
|
let kernelDisplayName: string = this.getKernelDisplayName();
|
||||||
|
if (kernelDisplayName) {
|
||||||
|
this.loadAttachToDropdown(this.model, kernelDisplayName, showSelectConnection);
|
||||||
|
} else if (this.model.clientSession.isInErrorState) {
|
||||||
|
this.setOptions([localize('noContextAvailable', "None")], 0);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private updateAttachToDropdown(model: INotebookModel): void {
|
private updateAttachToDropdown(model: INotebookModel): void {
|
||||||
model.onValidConnectionSelected(validConnection => {
|
model.onValidConnectionSelected(validConnection => {
|
||||||
let kernelDisplayName: string = this.getKernelDisplayName();
|
this.handleContextsChanged(!validConnection);
|
||||||
if (kernelDisplayName) {
|
|
||||||
this.loadAttachToDropdown(this.model, kernelDisplayName, !validConnection);
|
|
||||||
}
|
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -101,7 +101,8 @@ export class NotebookEditorModel extends EditorModel {
|
|||||||
let content = JSON.stringify(notebookModel.toJSON(), undefined, ' ');
|
let content = JSON.stringify(notebookModel.toJSON(), undefined, ' ');
|
||||||
let model = this.textEditorModel.textEditorModel;
|
let model = this.textEditorModel.textEditorModel;
|
||||||
let endLine = model.getLineCount();
|
let endLine = model.getLineCount();
|
||||||
let endCol = model.getLineLength(endLine);
|
let endCol = model.getLineMaxColumn(endLine);
|
||||||
|
|
||||||
this.textEditorModel.textEditorModel.applyEdits([{
|
this.textEditorModel.textEditorModel.applyEdits([{
|
||||||
range: new Range(1, 1, endLine, endCol),
|
range: new Range(1, 1, endLine, endCol),
|
||||||
text: content
|
text: content
|
||||||
|
|||||||
@@ -4,6 +4,7 @@
|
|||||||
|----------------------------------------------------------------------------*/
|
|----------------------------------------------------------------------------*/
|
||||||
import { IRenderMime } from './renderMimeInterfaces';
|
import { IRenderMime } from './renderMimeInterfaces';
|
||||||
import { ReadonlyJSONObject } from '../../models/jsonext';
|
import { ReadonlyJSONObject } from '../../models/jsonext';
|
||||||
|
import { IThemeService } from 'vs/platform/theme/common/themeService';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The default mime model implementation.
|
* The default mime model implementation.
|
||||||
@@ -17,6 +18,7 @@ export class MimeModel implements IRenderMime.IMimeModel {
|
|||||||
this._data = options.data || {};
|
this._data = options.data || {};
|
||||||
this._metadata = options.metadata || {};
|
this._metadata = options.metadata || {};
|
||||||
this._callback = options.callback;
|
this._callback = options.callback;
|
||||||
|
this._themeService = options.themeService;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -38,6 +40,10 @@ export class MimeModel implements IRenderMime.IMimeModel {
|
|||||||
return this._metadata;
|
return this._metadata;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
get themeService(): IThemeService {
|
||||||
|
return this._themeService;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Set the data associated with the model.
|
* Set the data associated with the model.
|
||||||
*
|
*
|
||||||
@@ -54,6 +60,7 @@ export class MimeModel implements IRenderMime.IMimeModel {
|
|||||||
private _callback: (options: IRenderMime.ISetDataOptions) => void;
|
private _callback: (options: IRenderMime.ISetDataOptions) => void;
|
||||||
private _data: ReadonlyJSONObject;
|
private _data: ReadonlyJSONObject;
|
||||||
private _metadata: ReadonlyJSONObject;
|
private _metadata: ReadonlyJSONObject;
|
||||||
|
private _themeService: IThemeService;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -83,5 +90,10 @@ export namespace MimeModel {
|
|||||||
* The initial mime metadata.
|
* The initial mime metadata.
|
||||||
*/
|
*/
|
||||||
metadata?: ReadonlyJSONObject;
|
metadata?: ReadonlyJSONObject;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Theme service used to react to theme change events
|
||||||
|
*/
|
||||||
|
themeService?: IThemeService;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -4,6 +4,7 @@
|
|||||||
| Distributed under the terms of the Modified BSD License.
|
| Distributed under the terms of the Modified BSD License.
|
||||||
|----------------------------------------------------------------------------*/
|
|----------------------------------------------------------------------------*/
|
||||||
import { ReadonlyJSONObject } from '../../models/jsonext';
|
import { ReadonlyJSONObject } from '../../models/jsonext';
|
||||||
|
import { IThemeService } from 'vs/platform/theme/common/themeService';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* A namespace for rendermime associated interfaces.
|
* A namespace for rendermime associated interfaces.
|
||||||
@@ -37,6 +38,11 @@ export namespace IRenderMime {
|
|||||||
* containing the new data.
|
* containing the new data.
|
||||||
*/
|
*/
|
||||||
setData(options: ISetDataOptions): void;
|
setData(options: ISetDataOptions): void;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Theme service used to react to theme change events
|
||||||
|
*/
|
||||||
|
readonly themeService: IThemeService;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|||||||
@@ -9,6 +9,8 @@ import { textFormatter } from 'sql/parts/grid/services/sharedServices';
|
|||||||
import { RowNumberColumn } from 'sql/base/browser/ui/table/plugins/rowNumberColumn.plugin';
|
import { RowNumberColumn } from 'sql/base/browser/ui/table/plugins/rowNumberColumn.plugin';
|
||||||
import { escape } from 'sql/base/common/strings';
|
import { escape } from 'sql/base/common/strings';
|
||||||
import { IDataResource } from 'sql/workbench/services/notebook/sql/sqlSessionManager';
|
import { IDataResource } from 'sql/workbench/services/notebook/sql/sqlSessionManager';
|
||||||
|
import { attachTableStyler } from 'sql/platform/theme/common/styler';
|
||||||
|
import { IThemeService } from 'vs/platform/theme/common/themeService';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Render DataResource as a grid into a host node.
|
* Render DataResource as a grid into a host node.
|
||||||
@@ -64,6 +66,7 @@ export function renderDataResource(
|
|||||||
// Set the height dynamically if the grid's height is < 500px high; otherwise, set height to 500px
|
// Set the height dynamically if the grid's height is < 500px high; otherwise, set height to 500px
|
||||||
tableContainer.style.height = rowsHeight >= 500 ? '500px' : rowsHeight.toString() + 'px';
|
tableContainer.style.height = rowsHeight >= 500 ? '500px' : rowsHeight.toString() + 'px';
|
||||||
|
|
||||||
|
attachTableStyler(detailTable, options.themeService);
|
||||||
host.appendChild(tableContainer);
|
host.appendChild(tableContainer);
|
||||||
detailTable.resizeCanvas();
|
detailTable.resizeCanvas();
|
||||||
|
|
||||||
@@ -116,5 +119,10 @@ export namespace renderDataResource {
|
|||||||
* The DataResource source to render.
|
* The DataResource source to render.
|
||||||
*/
|
*/
|
||||||
source: string;
|
source: string;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Theme service used to react to theme change events
|
||||||
|
*/
|
||||||
|
themeService?: IThemeService;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -8,6 +8,7 @@ import * as renderers from './renderers';
|
|||||||
import { IRenderMime } from './common/renderMimeInterfaces';
|
import { IRenderMime } from './common/renderMimeInterfaces';
|
||||||
import { ReadonlyJSONObject } from '../models/jsonext';
|
import { ReadonlyJSONObject } from '../models/jsonext';
|
||||||
import * as tableRenderers from 'sql/parts/notebook/outputs/tableRenderers';
|
import * as tableRenderers from 'sql/parts/notebook/outputs/tableRenderers';
|
||||||
|
import { IThemeService } from 'vs/platform/theme/common/themeService';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* A common base class for mime renderers.
|
* A common base class for mime renderers.
|
||||||
@@ -372,7 +373,8 @@ export class RenderedDataResource extends RenderedCommon {
|
|||||||
render(model: IRenderMime.IMimeModel): Promise<void> {
|
render(model: IRenderMime.IMimeModel): Promise<void> {
|
||||||
return tableRenderers.renderDataResource({
|
return tableRenderers.renderDataResource({
|
||||||
host: this.node,
|
host: this.node,
|
||||||
source: JSON.stringify(model.data[this.mimeType])
|
source: JSON.stringify(model.data[this.mimeType]),
|
||||||
|
themeService: model.themeService
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -181,7 +181,10 @@ export class ServerTreeActionProvider extends ContributableActionProvider {
|
|||||||
|
|
||||||
this.addScriptingActions(context, actions);
|
this.addScriptingActions(context, actions);
|
||||||
|
|
||||||
if (isAvailableDatabaseNode) {
|
let serverInfo = this._connectionManagementService.getServerInfo(context.profile.id);
|
||||||
|
let isCloud = serverInfo && serverInfo.isCloud;
|
||||||
|
|
||||||
|
if (isAvailableDatabaseNode && !isCloud) {
|
||||||
this.addBackupAction(context, actions);
|
this.addBackupAction(context, actions);
|
||||||
this.addRestoreAction(context, actions);
|
this.addRestoreAction(context, actions);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -35,7 +35,7 @@ import { IThemeService } from 'vs/platform/theme/common/themeService';
|
|||||||
import { ViewletPanel, IViewletPanelOptions } from 'vs/workbench/browser/parts/views/panelViewlet';
|
import { ViewletPanel, IViewletPanelOptions } from 'vs/workbench/browser/parts/views/panelViewlet';
|
||||||
import { isUndefinedOrNull } from 'vs/base/common/types';
|
import { isUndefinedOrNull } from 'vs/base/common/types';
|
||||||
import { range } from 'vs/base/common/arrays';
|
import { range } from 'vs/base/common/arrays';
|
||||||
import { Orientation, Sizing } from 'vs/base/browser/ui/splitview/splitview';
|
import { Orientation } from 'vs/base/browser/ui/splitview/splitview';
|
||||||
import { Disposable, IDisposable, dispose } from 'vs/base/common/lifecycle';
|
import { Disposable, IDisposable, dispose } from 'vs/base/common/lifecycle';
|
||||||
import { generateUuid } from 'vs/base/common/uuid';
|
import { generateUuid } from 'vs/base/common/uuid';
|
||||||
import { TPromise } from 'vs/base/common/winjs.base';
|
import { TPromise } from 'vs/base/common/winjs.base';
|
||||||
@@ -145,7 +145,6 @@ export class GridPanel extends ViewletPanel {
|
|||||||
@IInstantiationService private instantiationService: IInstantiationService
|
@IInstantiationService private instantiationService: IInstantiationService
|
||||||
) {
|
) {
|
||||||
super(options, keybindingService, contextMenuService, configurationService);
|
super(options, keybindingService, contextMenuService, configurationService);
|
||||||
this.maximumBodySize = 0;
|
|
||||||
this.splitView = new ScrollableSplitView(this.container, { enableResizing: false, verticalScrollbarVisibility: ScrollbarVisibility.Visible });
|
this.splitView = new ScrollableSplitView(this.container, { enableResizing: false, verticalScrollbarVisibility: ScrollbarVisibility.Visible });
|
||||||
this.splitView.onScroll(e => {
|
this.splitView.onScroll(e => {
|
||||||
if (this.state && this.splitView.length !== 0) {
|
if (this.state && this.splitView.length !== 0) {
|
||||||
@@ -196,6 +195,9 @@ export class GridPanel extends ViewletPanel {
|
|||||||
}
|
}
|
||||||
return p;
|
return p;
|
||||||
}, []));
|
}, []));
|
||||||
|
this.maximumBodySize = this.tables.reduce((p, c) => {
|
||||||
|
return p + c.maximumSize;
|
||||||
|
}, 0);
|
||||||
|
|
||||||
if (this.state && this.state.scrollPosition) {
|
if (this.state && this.state.scrollPosition) {
|
||||||
this.splitView.setScrollPosition(this.state.scrollPosition);
|
this.splitView.setScrollPosition(this.state.scrollPosition);
|
||||||
@@ -218,6 +220,10 @@ export class GridPanel extends ViewletPanel {
|
|||||||
t.state.canBeMaximized = this.tables.length > 1;
|
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) {
|
if (this.state && this.state.scrollPosition) {
|
||||||
this.splitView.setScrollPosition(this.state.scrollPosition);
|
this.splitView.setScrollPosition(this.state.scrollPosition);
|
||||||
}
|
}
|
||||||
@@ -244,6 +250,9 @@ export class GridPanel extends ViewletPanel {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const sizeChanges = () => {
|
const sizeChanges = () => {
|
||||||
|
this.maximumBodySize = this.tables.reduce((p, c) => {
|
||||||
|
return p + c.maximumSize;
|
||||||
|
}, 0);
|
||||||
|
|
||||||
if (this.state && this.state.scrollPosition) {
|
if (this.state && this.state.scrollPosition) {
|
||||||
this.splitView.setScrollPosition(this.state.scrollPosition);
|
this.splitView.setScrollPosition(this.state.scrollPosition);
|
||||||
@@ -270,9 +279,6 @@ export class GridPanel extends ViewletPanel {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private addResultSet(resultSet: azdata.ResultSetSummary[]) {
|
private addResultSet(resultSet: azdata.ResultSetSummary[]) {
|
||||||
if (resultSet.length > 0) {
|
|
||||||
this.maximumBodySize = Number.POSITIVE_INFINITY;
|
|
||||||
}
|
|
||||||
let tables: GridTable<any>[] = [];
|
let tables: GridTable<any>[] = [];
|
||||||
|
|
||||||
for (let set of resultSet) {
|
for (let set of resultSet) {
|
||||||
@@ -302,7 +308,7 @@ export class GridPanel extends ViewletPanel {
|
|||||||
// possible to need a sort?
|
// possible to need a sort?
|
||||||
|
|
||||||
if (isUndefinedOrNull(this.maximizedGrid)) {
|
if (isUndefinedOrNull(this.maximizedGrid)) {
|
||||||
this.splitView.addViews(tables, Sizing.Distribute, this.splitView.length);
|
this.splitView.addViews(tables, tables.map(i => i.minimumSize), this.splitView.length);
|
||||||
}
|
}
|
||||||
|
|
||||||
this.tables = this.tables.concat(tables);
|
this.tables = this.tables.concat(tables);
|
||||||
@@ -316,12 +322,15 @@ export class GridPanel extends ViewletPanel {
|
|||||||
for (let i = this.splitView.length - 1; i >= 0; i--) {
|
for (let i = this.splitView.length - 1; i >= 0; i--) {
|
||||||
this.splitView.removeView(i);
|
this.splitView.removeView(i);
|
||||||
}
|
}
|
||||||
this.maximumBodySize = 0;
|
|
||||||
dispose(this.tables);
|
dispose(this.tables);
|
||||||
dispose(this.tableDisposable);
|
dispose(this.tableDisposable);
|
||||||
this.tableDisposable = [];
|
this.tableDisposable = [];
|
||||||
this.tables = [];
|
this.tables = [];
|
||||||
this.maximizedGrid = undefined;
|
this.maximizedGrid = undefined;
|
||||||
|
|
||||||
|
this.maximumBodySize = this.tables.reduce((p, c) => {
|
||||||
|
return p + c.maximumSize;
|
||||||
|
}, 0);
|
||||||
}
|
}
|
||||||
|
|
||||||
private maximizeTable(tableid: string): void {
|
private maximizeTable(tableid: string): void {
|
||||||
@@ -345,7 +354,7 @@ export class GridPanel extends ViewletPanel {
|
|||||||
this.maximizedGrid.state.maximized = false;
|
this.maximizedGrid.state.maximized = false;
|
||||||
this.maximizedGrid = undefined;
|
this.maximizedGrid = undefined;
|
||||||
this.splitView.removeView(0);
|
this.splitView.removeView(0);
|
||||||
this.splitView.addViews(this.tables, Sizing.Distribute);
|
this.splitView.addViews(this.tables, this.tables.map(i => i.minimumSize));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -716,7 +725,7 @@ class GridTable<T> extends Disposable implements IView {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public get maximumSize(): number {
|
public get maximumSize(): number {
|
||||||
return Number.POSITIVE_INFINITY;
|
return Math.max(this.maxSize, ACTIONBAR_HEIGHT + BOTTOM_PADDING);
|
||||||
}
|
}
|
||||||
|
|
||||||
private loadData(offset: number, count: number): Thenable<T[]> {
|
private loadData(offset: number, count: number): Thenable<T[]> {
|
||||||
|
|||||||
@@ -468,8 +468,7 @@ export class ConnectionManagementService extends Disposable implements IConnecti
|
|||||||
// The connected succeeded so add it to our active connections now, optionally adding it to the MRU based on
|
// The connected succeeded so add it to our active connections now, optionally adding it to the MRU based on
|
||||||
// the options.saveTheConnection setting
|
// the options.saveTheConnection setting
|
||||||
let connectionMgmtInfo = this._connectionStatusManager.findConnection(uri);
|
let connectionMgmtInfo = this._connectionStatusManager.findConnection(uri);
|
||||||
let activeConnection = connectionMgmtInfo.connectionProfile;
|
this.tryAddActiveConnection(connectionMgmtInfo, connection, options.saveTheConnection);
|
||||||
this.tryAddActiveConnection(connectionMgmtInfo, activeConnection, options.saveTheConnection);
|
|
||||||
|
|
||||||
if (callbacks.onConnectSuccess) {
|
if (callbacks.onConnectSuccess) {
|
||||||
callbacks.onConnectSuccess(options.params);
|
callbacks.onConnectSuccess(options.params);
|
||||||
|
|||||||
@@ -81,23 +81,27 @@ export class ExtHostNotebook implements ExtHostNotebookShape {
|
|||||||
|
|
||||||
$startNewSession(managerHandle: number, options: azdata.nb.ISessionOptions): Thenable<INotebookSessionDetails> {
|
$startNewSession(managerHandle: number, options: azdata.nb.ISessionOptions): Thenable<INotebookSessionDetails> {
|
||||||
return this._withSessionManager(managerHandle, async (sessionManager) => {
|
return this._withSessionManager(managerHandle, async (sessionManager) => {
|
||||||
let session = await sessionManager.startNew(options);
|
try {
|
||||||
let sessionId = this._addNewAdapter(session);
|
let session = await sessionManager.startNew(options);
|
||||||
let kernelDetails: INotebookKernelDetails = undefined;
|
let sessionId = this._addNewAdapter(session);
|
||||||
if (session.kernel) {
|
let kernelDetails: INotebookKernelDetails = undefined;
|
||||||
kernelDetails = this.saveKernel(session.kernel);
|
if (session.kernel) {
|
||||||
|
kernelDetails = this.saveKernel(session.kernel);
|
||||||
|
}
|
||||||
|
let details: INotebookSessionDetails = {
|
||||||
|
sessionId: sessionId,
|
||||||
|
id: session.id,
|
||||||
|
path: session.path,
|
||||||
|
name: session.name,
|
||||||
|
type: session.type,
|
||||||
|
status: session.status,
|
||||||
|
canChangeKernels: session.canChangeKernels,
|
||||||
|
kernelDetails: kernelDetails
|
||||||
|
};
|
||||||
|
return details;
|
||||||
|
} catch (error) {
|
||||||
|
throw typeof(error) === 'string' ? new Error(error) : error;
|
||||||
}
|
}
|
||||||
let details: INotebookSessionDetails = {
|
|
||||||
sessionId: sessionId,
|
|
||||||
id: session.id,
|
|
||||||
path: session.path,
|
|
||||||
name: session.name,
|
|
||||||
type: session.type,
|
|
||||||
status: session.status,
|
|
||||||
canChangeKernels: session.canChangeKernels,
|
|
||||||
kernelDetails: kernelDetails
|
|
||||||
};
|
|
||||||
return details;
|
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -267,7 +271,16 @@ export class ExtHostNotebook implements ExtHostNotebookShape {
|
|||||||
if (manager === undefined) {
|
if (manager === undefined) {
|
||||||
return TPromise.wrapError<R>(new Error(localize('errNoManager', 'No Manager found')));
|
return TPromise.wrapError<R>(new Error(localize('errNoManager', 'No Manager found')));
|
||||||
}
|
}
|
||||||
return TPromise.wrap(callback(manager));
|
return TPromise.wrap(this.callbackWithErrorWrap<R>(callback, manager));
|
||||||
|
}
|
||||||
|
|
||||||
|
private async callbackWithErrorWrap<R>(callback: (manager: NotebookManagerAdapter) => R | PromiseLike<R>, manager: NotebookManagerAdapter): Promise<R> {
|
||||||
|
try {
|
||||||
|
let value = await callback(manager);
|
||||||
|
return value;
|
||||||
|
} catch (error) {
|
||||||
|
throw typeof(error) === 'string' ? new Error(error) : error;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private _withServerManager<R>(handle: number, callback: (manager: azdata.nb.ServerManager) => R | PromiseLike<R>): TPromise<R> {
|
private _withServerManager<R>(handle: number, callback: (manager: azdata.nb.ServerManager) => R | PromiseLike<R>): TPromise<R> {
|
||||||
|
|||||||
@@ -5,6 +5,7 @@
|
|||||||
'use strict';
|
'use strict';
|
||||||
|
|
||||||
import * as azdata from 'azdata';
|
import * as azdata from 'azdata';
|
||||||
|
import * as path from 'path';
|
||||||
import { extHostNamedCustomer } from 'vs/workbench/api/electron-browser/extHostCustomers';
|
import { extHostNamedCustomer } from 'vs/workbench/api/electron-browser/extHostCustomers';
|
||||||
import { Disposable, IDisposable } from 'vs/base/common/lifecycle';
|
import { Disposable, IDisposable } from 'vs/base/common/lifecycle';
|
||||||
import { URI, UriComponents } from 'vs/base/common/uri';
|
import { URI, UriComponents } from 'vs/base/common/uri';
|
||||||
@@ -32,7 +33,6 @@ import { NotebookChangeType, CellTypes } from 'sql/parts/notebook/models/contrac
|
|||||||
import { ICapabilitiesService } from 'sql/platform/capabilities/common/capabilitiesService';
|
import { ICapabilitiesService } from 'sql/platform/capabilities/common/capabilitiesService';
|
||||||
import { IUntitledEditorService } from 'vs/workbench/services/untitled/common/untitledEditorService';
|
import { IUntitledEditorService } from 'vs/workbench/services/untitled/common/untitledEditorService';
|
||||||
import { notebookModeId } from 'sql/common/constants';
|
import { notebookModeId } from 'sql/common/constants';
|
||||||
import { UntitledEditorInput } from 'vs/workbench/common/editor/untitledEditorInput';
|
|
||||||
|
|
||||||
class MainThreadNotebookEditor extends Disposable {
|
class MainThreadNotebookEditor extends Disposable {
|
||||||
private _contentChangedEmitter = new Emitter<NotebookContentChange>();
|
private _contentChangedEmitter = new Emitter<NotebookContentChange>();
|
||||||
@@ -371,8 +371,9 @@ export class MainThreadNotebookDocumentsAndEditors extends Disposable implements
|
|||||||
};
|
};
|
||||||
let isUntitled: boolean = uri.scheme === Schemas.untitled;
|
let isUntitled: boolean = uri.scheme === Schemas.untitled;
|
||||||
|
|
||||||
const fileInput: UntitledEditorInput = isUntitled ? this._untitledEditorService.createOrGet(uri, notebookModeId) : undefined;
|
const fileInput = isUntitled ? this._untitledEditorService.createOrGet(uri, notebookModeId) :
|
||||||
let input = this._instantiationService.createInstance(NotebookInput, uri.fsPath, uri, fileInput);
|
this._editorService.createInput({resource: uri, language: notebookModeId});
|
||||||
|
let input = this._instantiationService.createInstance(NotebookInput, path.basename(uri.fsPath), uri, fileInput);
|
||||||
input.isTrusted = isUntitled;
|
input.isTrusted = isUntitled;
|
||||||
input.defaultKernel = options.defaultKernel;
|
input.defaultKernel = options.defaultKernel;
|
||||||
input.connectionProfile = new ConnectionProfile(this._capabilitiesService, options.connectionProfile);
|
input.connectionProfile = new ConnectionProfile(this._capabilitiesService, options.connectionProfile);
|
||||||
|
|||||||
@@ -90,7 +90,38 @@ export class ConnectionDialogService implements IConnectionDialogService {
|
|||||||
@IWorkspaceConfigurationService private _workspaceConfigurationService: IWorkspaceConfigurationService,
|
@IWorkspaceConfigurationService private _workspaceConfigurationService: IWorkspaceConfigurationService,
|
||||||
@IClipboardService private _clipboardService: IClipboardService,
|
@IClipboardService private _clipboardService: IClipboardService,
|
||||||
@ICommandService private _commandService: ICommandService
|
@ICommandService private _commandService: ICommandService
|
||||||
) { }
|
) {
|
||||||
|
this.initializeConnectionProviders();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Set the initial value for the connection provider and listen to the provider change event
|
||||||
|
*/
|
||||||
|
private initializeConnectionProviders() {
|
||||||
|
this.setConnectionProviders();
|
||||||
|
if (this._capabilitiesService) {
|
||||||
|
this._capabilitiesService.onCapabilitiesRegistered(() => {
|
||||||
|
this.setConnectionProviders();
|
||||||
|
if (this._connectionDialog) {
|
||||||
|
this._connectionDialog.updateConnectionProviders(this._providerTypes, this._providerNameToDisplayNameMap);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Update the available provider types using the values from capabilities service
|
||||||
|
*/
|
||||||
|
private setConnectionProviders() {
|
||||||
|
if (this._capabilitiesService) {
|
||||||
|
this._providerTypes = [];
|
||||||
|
this._providerNameToDisplayNameMap = {};
|
||||||
|
entries(this._capabilitiesService.providers).forEach(p => {
|
||||||
|
this._providerTypes.push(p[1].connection.displayName);
|
||||||
|
this._providerNameToDisplayNameMap[p[0]] = p[1].connection.displayName;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Gets the default provider with the following actions
|
* Gets the default provider with the following actions
|
||||||
@@ -351,13 +382,6 @@ export class ConnectionDialogService implements IConnectionDialogService {
|
|||||||
this._inputModel = model;
|
this._inputModel = model;
|
||||||
|
|
||||||
return new Promise<void>((resolve, reject) => {
|
return new Promise<void>((resolve, reject) => {
|
||||||
// only create the provider maps first time the dialog gets called
|
|
||||||
if (this._providerTypes.length === 0) {
|
|
||||||
entries(this._capabilitiesService.providers).forEach(p => {
|
|
||||||
this._providerTypes.push(p[1].connection.displayName);
|
|
||||||
this._providerNameToDisplayNameMap[p[0]] = p[1].connection.displayName;
|
|
||||||
});
|
|
||||||
}
|
|
||||||
this.updateModelServerCapabilities(model);
|
this.updateModelServerCapabilities(model);
|
||||||
// If connecting from a query editor set "save connection" to false
|
// If connecting from a query editor set "save connection" to false
|
||||||
if (params && params.input && params.connectionType === ConnectionType.editor) {
|
if (params && params.input && params.connectionType === ConnectionType.editor) {
|
||||||
|
|||||||
@@ -102,6 +102,17 @@ export class ConnectionDialogWidget extends Modal {
|
|||||||
super(localize('connection', 'Connection'), TelemetryKeys.Connection, _partService, telemetryService, clipboardService, _workbenchThemeService, contextKeyService, { hasSpinner: true, hasErrors: true });
|
super(localize('connection', 'Connection'), TelemetryKeys.Connection, _partService, telemetryService, clipboardService, _workbenchThemeService, contextKeyService, { hasSpinner: true, hasErrors: true });
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Update the available connection providers, this is called when new providers are registered
|
||||||
|
* So that the connection type dropdown always has up to date values
|
||||||
|
*/
|
||||||
|
public updateConnectionProviders(providerTypeOptions: string[],
|
||||||
|
providerNameToDisplayNameMap: { [providerDisplayName: string]: string }) {
|
||||||
|
this.providerTypeOptions = providerTypeOptions;
|
||||||
|
this.providerNameToDisplayNameMap = providerNameToDisplayNameMap;
|
||||||
|
this.refresh();
|
||||||
|
}
|
||||||
|
|
||||||
public refresh(): void {
|
public refresh(): void {
|
||||||
let filteredProviderTypes = this.providerTypeOptions;
|
let filteredProviderTypes = this.providerTypeOptions;
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user