Nb/open book without reload (#6635)

* initial commit

* fix: added initialize method to fix the previous/next links rendering

* added checks

* open first markdown/ipynb in the book ans expand the view

* added launch book from command pallete, removed the pick folder and save option added launching the first notebook/markdown

* moved the open book command pallet action from mssql to notebooks

* open as untitled to true

* opening markdown files issue fix

* removed opening as untitled files

* open ipynb files as untitled and changes for previous&next links to work

* add books as seperate viewlet

* localize double quote issues and renamed treeCollapsibleState

* renames and added logic to conditionally show Preview command

* moved registerCommands from widget to extension contribution

* isEditorUntitled check

* async comment updates

* formatting issues.

* promisfying the async calls

* moved existsAsync to top
This commit is contained in:
Maddy
2019-08-23 11:19:35 -07:00
committed by GitHub
parent 1a39fdae24
commit 9274f223f0
9 changed files with 576 additions and 464 deletions

View File

@@ -100,6 +100,8 @@ class AzdataExtensionBookContributionProvider extends Disposable implements Book
this._onContributionsChanged.fire(this); this._onContributionsChanged.fire(this);
} }
}, undefined, this._disposables); }, undefined, this._disposables);
this.registerCommands();
} }
private readonly _onContributionsChanged = this._register(new vscode.EventEmitter<this>()); private readonly _onContributionsChanged = this._register(new vscode.EventEmitter<this>());
@@ -117,8 +119,18 @@ class AzdataExtensionBookContributionProvider extends Disposable implements Book
.map(BookContributions.fromExtension) .map(BookContributions.fromExtension)
.reduce(BookContributions.merge, []); .reduce(BookContributions.merge, []);
} }
private registerCommands(): void {
this.contributions.map(book => {
let bookName: string = path.basename(book.path);
vscode.commands.executeCommand('setContext', bookName, true);
vscode.commands.registerCommand('books.' + bookName, async (context) => {
vscode.commands.executeCommand('bookTreeView.openBook', book.path, true);
});
});
}
} }
export function getBookExtensionContributions(context: vscode.ExtensionContext): BookContributionProvider { export function getBookExtensionContributions(context: vscode.ExtensionContext): BookContributionProvider {
return new AzdataExtensionBookContributionProvider(context.extensionPath); return new AzdataExtensionBookContributionProvider(context.extensionPath);
} }

View File

@@ -5,15 +5,10 @@
import * as vscode from 'vscode'; import * as vscode from 'vscode';
import * as azdata from 'azdata'; import * as azdata from 'azdata';
import * as nls from 'vscode-nls';
import * as fs from 'fs-extra';
import * as path from 'path';
import { import {
BookContributionProvider, BookContribution BookContributionProvider
} from './bookExtensions'; } from './bookExtensions';
import * as Utils from '../utils';
const localize = nls.loadMessageBundle();
export function registerBooksWidget(bookContributionProvider: BookContributionProvider): void { export function registerBooksWidget(bookContributionProvider: BookContributionProvider): void {
azdata.ui.registerModelViewProvider('books-widget', async (view) => { azdata.ui.registerModelViewProvider('books-widget', async (view) => {
@@ -40,7 +35,8 @@ export function registerBooksWidget(bookContributionProvider: BookContributionPr
title: contribution.name title: contribution.name
}).component(); }).component();
tsgbooklink.onDidClick(() => { tsgbooklink.onDidClick(() => {
promptForFolder(contribution); let uri: vscode.Uri = vscode.Uri.file(contribution.path);
openBookViewlet(uri);
}); });
bookRow.addItem(tsgbooklink, { bookRow.addItem(tsgbooklink, {
CSSStyles: { CSSStyles: {
@@ -73,56 +69,6 @@ export function registerBooksWidget(bookContributionProvider: BookContributionPr
}); });
} }
async function promptForFolder(bookContribution: BookContribution): Promise<void> { function openBookViewlet(folderUri: vscode.Uri): void {
try { vscode.commands.executeCommand('bookTreeView.openBook', folderUri.fsPath, true);
const allFilesFilter = localize('allFiles', "All Files");
let filter = {};
filter[allFilesFilter] = '*';
let uris = await vscode.window.showOpenDialog({
filters: filter,
canSelectFiles: false,
canSelectMany: false,
canSelectFolders: true,
openLabel: localize('labelPickFolder', "Pick Folder")
});
if (uris && uris.length > 0) {
let pickedFolder = uris[0];
let destinationUri: vscode.Uri = vscode.Uri.file(path.join(pickedFolder.fsPath, bookContribution.name));
await saveBooksToFolder(destinationUri, bookContribution);
await promptToReloadWindow(destinationUri);
}
return;
} catch (error) {
vscode.window.showErrorMessage(localize('FailedDuringSaveAndPrompt', 'Failed : {0}', Utils.getErrorMessage(error)));
}
} }
async function saveBooksToFolder(folderUri: vscode.Uri, bookContribution: BookContribution): Promise<void> {
// Get book contributions
if (bookContribution && folderUri) {
//remove folder if exists
await fs.removeSync(folderUri.fsPath);
//make directory for each contribution book.
await fs.mkdirSync(folderUri.fsPath);
await fs.copy(bookContribution.path, folderUri.fsPath);
}
}
function promptToReloadWindow(folderUri: vscode.Uri): void {
const actionReload = localize('prompt.reloadInstance', "Reload");
const actionOpenNew = localize('prompt.openNewInstance', "Open new instance");
vscode.window.showInformationMessage(localize('prompt.reloadDescription', "Reload and view the Jupyter Books in the Files view."), actionReload, actionOpenNew)
.then(selectedAction => {
if (selectedAction === actionReload) {
vscode.commands.executeCommand('workbench.action.setWorkspaceAndOpen', {
forceNewWindow: false,
folderPath: folderUri
});
}
if (selectedAction === actionOpenNew) {
vscode.commands.executeCommand('workbench.action.setWorkspaceAndOpen', {
forceNewWindow: true,
folderPath: folderUri
});
}
});
}

View File

@@ -1,385 +1,402 @@
{ {
"name": "notebook", "name": "notebook",
"displayName": "%displayName%", "displayName": "%displayName%",
"description": "%description%", "description": "%description%",
"version": "0.1.0", "version": "0.1.0",
"publisher": "Microsoft", "publisher": "Microsoft",
"engines": { "engines": {
"vscode": "*", "vscode": "*",
"azdata": "*" "azdata": "*"
}, },
"main": "./out/extension", "main": "./out/extension",
"activationEvents": [ "activationEvents": [
"*" "*"
], ],
"contributes": { "contributes": {
"configuration": { "configuration": {
"type": "object", "type": "object",
"title": "%notebook.configuration.title%", "title": "%notebook.configuration.title%",
"properties": { "properties": {
"notebook.maxBookSearchDepth": { "notebook.maxBookSearchDepth": {
"type": "number", "type": "number",
"default": 5, "default": 5,
"description": "%notebook.maxBookSearchDepth.description%" "description": "%notebook.maxBookSearchDepth.description%"
}, },
"notebook.pythonPath": { "notebook.pythonPath": {
"type": "string", "type": "string",
"default": "", "default": "",
"description": "%notebook.pythonPath.description%" "description": "%notebook.pythonPath.description%"
}, },
"notebook.useExistingPython": { "notebook.useExistingPython": {
"type": "boolean", "type": "boolean",
"default": false, "default": false,
"description": "%notebook.useExistingPython.description%" "description": "%notebook.useExistingPython.description%"
}, },
"notebook.overrideEditorTheming": { "notebook.overrideEditorTheming": {
"type": "boolean", "type": "boolean",
"default": true, "default": true,
"description": "%notebook.overrideEditorTheming.description%" "description": "%notebook.overrideEditorTheming.description%"
}, },
"notebook.maxTableRows": { "notebook.maxTableRows": {
"type": "number", "type": "number",
"default": 5000, "default": 5000,
"description": "%notebook.maxTableRows.description%" "description": "%notebook.maxTableRows.description%"
} }
} }
}, },
"commands": [ "commands": [
{ {
"command": "notebook.command.analyzeNotebook", "command": "notebook.command.analyzeNotebook",
"title": "%notebook.analyzeJupyterNotebook%" "title": "%notebook.analyzeJupyterNotebook%"
}, },
{ {
"command": "_notebook.command.new", "command": "_notebook.command.new",
"title": "%notebook.command.new%", "title": "%notebook.command.new%",
"icon": { "icon": {
"dark": "resources/dark/new_notebook_inverse.svg", "dark": "resources/dark/new_notebook_inverse.svg",
"light": "resources/light/new_notebook.svg" "light": "resources/light/new_notebook.svg"
} }
}, },
{ {
"command": "notebook.command.open", "command": "notebook.command.open",
"title": "%notebook.command.open%", "title": "%notebook.command.open%",
"icon": { "icon": {
"dark": "resources/dark/open_notebook_inverse.svg", "dark": "resources/dark/open_notebook_inverse.svg",
"light": "resources/light/open_notebook.svg" "light": "resources/light/open_notebook.svg"
} }
}, },
{ {
"command": "notebook.command.runactivecell", "command": "notebook.command.runactivecell",
"title": "%notebook.command.runactivecell%", "title": "%notebook.command.runactivecell%",
"icon": "resources/dark/touchbar_run_cell.png" "icon": "resources/dark/touchbar_run_cell.png"
}, },
{ {
"command": "notebook.command.clearactivecellresult", "command": "notebook.command.clearactivecellresult",
"title": "%notebook.command.clearactivecellresult%" "title": "%notebook.command.clearactivecellresult%"
}, },
{ {
"command": "notebook.command.runallcells", "command": "notebook.command.runallcells",
"title": "%notebook.command.runallcells%" "title": "%notebook.command.runallcells%"
}, },
{ {
"command": "notebook.command.addcode", "command": "notebook.command.addcode",
"title": "%notebook.command.addcode%" "title": "%notebook.command.addcode%"
}, },
{ {
"command": "notebook.command.addtext", "command": "notebook.command.addtext",
"title": "%notebook.command.addtext%" "title": "%notebook.command.addtext%"
}, },
{ {
"command": "notebook.command.addcell", "command": "notebook.command.addcell",
"title": "%notebook.command.addcell%", "title": "%notebook.command.addcell%",
"icon": "resources/dark/touchbar_add_cell.png" "icon": "resources/dark/touchbar_add_cell.png"
}, },
{ {
"command": "jupyter.cmd.analyzeNotebook", "command": "jupyter.cmd.analyzeNotebook",
"title": "%title.analyzeJupyterNotebook%" "title": "%title.analyzeJupyterNotebook%"
}, },
{ {
"command": "jupyter.task.newNotebook", "command": "jupyter.task.newNotebook",
"title": "%title.newJupyterNotebook%", "title": "%title.newJupyterNotebook%",
"icon": { "icon": {
"dark": "resources/dark/new_notebook_inverse.svg", "dark": "resources/dark/new_notebook_inverse.svg",
"light": "resources/light/new_notebook.svg" "light": "resources/light/new_notebook.svg"
} }
}, },
{ {
"command": "jupyter.task.openNotebook", "command": "jupyter.task.openNotebook",
"title": "%title.openJupyterNotebook%", "title": "%title.openJupyterNotebook%",
"icon": { "icon": {
"dark": "resources/dark/open_notebook_inverse.svg", "dark": "resources/dark/open_notebook_inverse.svg",
"light": "resources/light/open_notebook.svg" "light": "resources/light/open_notebook.svg"
} }
}, },
{ {
"command": "jupyter.cmd.newNotebook", "command": "jupyter.cmd.newNotebook",
"title": "%title.newJupyterNotebook%", "title": "%title.newJupyterNotebook%",
"icon": { "icon": {
"dark": "resources/dark/new_notebook_inverse.svg", "dark": "resources/dark/new_notebook_inverse.svg",
"light": "resources/light/new_notebook.svg" "light": "resources/light/new_notebook.svg"
} }
}, },
{ {
"command": "jupyter.cmd.managePackages", "command": "jupyter.cmd.managePackages",
"title": "%title.managePackages%", "title": "%title.managePackages%",
"icon": { "icon": {
"dark": "resources/dark/manage_inverse.svg", "dark": "resources/dark/manage_inverse.svg",
"light": "resources/light/manage.svg" "light": "resources/light/manage.svg"
} }
}, },
{ {
"command": "jupyter.cmd.configurePython", "command": "jupyter.cmd.configurePython",
"title": "%title.configurePython%" "title": "%title.configurePython%"
}, },
{ {
"command": "jupyter.reinstallDependencies", "command": "jupyter.reinstallDependencies",
"title": "%title.reinstallNotebookDependencies%" "title": "%title.reinstallNotebookDependencies%"
} },
], {
"languages": [ "command": "books.sqlserver2019",
{ "title": "%title.SQL19PreviewBook%",
"id": "notebook", "category": "%books-preview-category%"
"extensions": [ }
".ipynb" ],
], "languages": [
"aliases": [ {
"Notebook" "id": "notebook",
] "extensions": [
} ".ipynb"
], ],
"menus": { "aliases": [
"commandPalette": [ "Notebook"
{ ]
"command": "notebook.command.analyzeNotebook", }
"when": "false" ],
}, "menus": {
{ "commandPalette": [
"command": "_notebook.command.new", {
"when": "false" "command": "notebook.command.analyzeNotebook",
}, "when": "false"
{ },
"command": "notebook.command.open" {
}, "command": "_notebook.command.new",
{ "when": "false"
"command": "notebook.command.runactivecell", },
"when": "notebookEditorVisible" {
}, "command": "notebook.command.open"
{ },
"command": "notebook.command.clearactivecellresult", {
"when": "notebookEditorVisible" "command": "notebook.command.runactivecell",
}, "when": "notebookEditorVisible"
{ },
"command": "notebook.command.runallcells", {
"when": "notebookEditorVisible" "command": "notebook.command.clearactivecellresult",
}, "when": "notebookEditorVisible"
{ },
"command": "notebook.command.addcode", {
"when": "notebookEditorVisible" "command": "notebook.command.runallcells",
}, "when": "notebookEditorVisible"
{ },
"command": "notebook.command.addtext", {
"when": "notebookEditorVisible" "command": "notebook.command.addcode",
}, "when": "notebookEditorVisible"
{ },
"command": "notebook.command.addcell", {
"when": "false" "command": "notebook.command.addtext",
}, "when": "notebookEditorVisible"
{ },
"command": "jupyter.task.newNotebook", {
"when": "false" "command": "notebook.command.addcell",
}, "when": "false"
{ },
"command": "jupyter.cmd.newNotebook", {
"when": "false" "command": "jupyter.task.newNotebook",
}, "when": "false"
{ },
"command": "jupyter.cmd.analyzeNotebook", {
"when": "false" "command": "jupyter.cmd.newNotebook",
}, "when": "false"
{ },
"command": "jupyter.task.openNotebook", {
"when": "false" "command": "jupyter.cmd.analyzeNotebook",
}, "when": "false"
{ },
"command": "jupyter.cmd.managePackages", {
"when": "false" "command": "jupyter.task.openNotebook",
} "when": "false"
], },
"touchBar": [ {
{ "command": "jupyter.cmd.managePackages",
"command": "notebook.command.runactivecell", "when": "false"
"when": "activeEditor == workbench.editor.notebookEditor", },
"group": "1_notebook@1" {
}, "command": "books.sqlserver2019",
{ "when": "sqlserver2019 && notebookQuality != stable"
"command": "notebook.command.addcell", }
"when": "activeEditor == workbench.editor.notebookEditor", ],
"group": "1_notebook@2" "touchBar": [
} {
], "command": "notebook.command.runactivecell",
"objectExplorer/item/context": [ "when": "activeEditor == workbench.editor.notebookEditor",
{ "group": "1_notebook@1"
"command": "notebook.command.analyzeNotebook", },
"when": "nodeType=~/^mssqlCluster/ && nodeLabel=~/[^\\s]+(\\.(csv|tsv|txt))$/ && nodeType == mssqlCluster:file", {
"group": "1notebook@1" "command": "notebook.command.addcell",
}, "when": "activeEditor == workbench.editor.notebookEditor",
{ "group": "1_notebook@2"
"command": "jupyter.cmd.newNotebook", }
"when": "connectionProvider == HADOOP_KNOX && nodeType && nodeType == Server", ],
"group": "1root@1" "objectExplorer/item/context": [
}, {
{ "command": "notebook.command.analyzeNotebook",
"command": "jupyter.cmd.analyzeNotebook", "when": "nodeType=~/^mssqlCluster/ && nodeLabel=~/[^\\s]+(\\.(csv|tsv|txt))$/ && nodeType == mssqlCluster:file",
"when": "nodeType=~/^hdfs/ && nodeLabel=~/[^\\s]+(\\.(csv|tsv|txt))$/ && nodeType == hdfs:file", "group": "1notebook@1"
"group": "1notebook@1" },
} {
], "command": "jupyter.cmd.newNotebook",
"notebook/toolbar": [ "when": "connectionProvider == HADOOP_KNOX && nodeType && nodeType == Server",
{ "group": "1root@1"
"command": "jupyter.cmd.managePackages", },
"when": "providerId == jupyter && notebook:pythonInstalled" {
} "command": "jupyter.cmd.analyzeNotebook",
] "when": "nodeType=~/^hdfs/ && nodeLabel=~/[^\\s]+(\\.(csv|tsv|txt))$/ && nodeType == hdfs:file",
}, "group": "1notebook@1"
"keybindings": [ }
{ ],
"command": "notebook.command.runactivecell", "notebook/toolbar": [
"key": "F5", {
"when": "activeEditor == workbench.editor.notebookEditor" "command": "jupyter.cmd.managePackages",
}, "when": "providerId == jupyter && notebook:pythonInstalled"
{ }
"command": "notebook.command.clearactivecellresult", ]
"key": "Ctrl+Shift+R", },
"when": "activeEditor == workbench.editor.notebookEditor" "keybindings": [
}, {
{ "command": "notebook.command.runactivecell",
"command": "notebook.command.runallcells", "key": "F5",
"key": "Ctrl+Shift+F5", "when": "activeEditor == workbench.editor.notebookEditor"
"when": "activeEditor == workbench.editor.notebookEditor" },
}, {
{ "command": "notebook.command.clearactivecellresult",
"command": "notebook.command.addcode", "key": "Ctrl+Shift+R",
"key": "Ctrl+Shift+C", "when": "activeEditor == workbench.editor.notebookEditor"
"when": "activeEditor == workbench.editor.notebookEditor" },
}, {
{ "command": "notebook.command.runallcells",
"command": "notebook.command.addtext", "key": "Ctrl+Shift+F5",
"key": "Ctrl+Shift+T", "when": "activeEditor == workbench.editor.notebookEditor"
"when": "activeEditor == workbench.editor.notebookEditor" },
} {
], "command": "notebook.command.addcode",
"notebook.languagemagics": [ "key": "Ctrl+Shift+C",
{ "when": "activeEditor == workbench.editor.notebookEditor"
"magic": "lang_python", },
"language": "python", {
"executionTarget": null, "command": "notebook.command.addtext",
"kernels": [ "key": "Ctrl+Shift+T",
"sql" "when": "activeEditor == workbench.editor.notebookEditor"
] }
}, ],
{ "notebook.languagemagics": [
"magic": "lang_r", {
"language": "r", "magic": "lang_python",
"executionTarget": null, "language": "python",
"kernels": [ "executionTarget": null,
"sql" "kernels": [
] "sql"
}, ]
{ },
"magic": "lang_java", {
"language": "java", "magic": "lang_r",
"executionTarget": null, "language": "r",
"kernels": [ "executionTarget": null,
"sql" "kernels": [
] "sql"
} ]
], },
"notebook.providers": { {
"provider": "jupyter", "magic": "lang_java",
"fileExtensions": [ "language": "java",
"IPYNB" "executionTarget": null,
], "kernels": [
"standardKernels": [ "sql"
{ ]
"name": "pyspark3kernel", }
"displayName": "PySpark3", ],
"connectionProviderIds": [ "notebook.providers": {
"HADOOP_KNOX", "provider": "jupyter",
"MSSQL" "fileExtensions": [
] "IPYNB"
}, ],
{ "standardKernels": [
"name": "pysparkkernel", {
"displayName": "PySpark", "name": "pyspark3kernel",
"connectionProviderIds": [ "displayName": "PySpark3",
"HADOOP_KNOX", "connectionProviderIds": [
"MSSQL" "HADOOP_KNOX",
] "MSSQL"
}, ]
{ },
"name": "sparkkernel", {
"displayName": "Spark | Scala", "name": "pysparkkernel",
"connectionProviderIds": [ "displayName": "PySpark",
"HADOOP_KNOX", "connectionProviderIds": [
"MSSQL" "HADOOP_KNOX",
] "MSSQL"
}, ]
{ },
"name": "sparkrkernel", {
"displayName": "Spark | R", "name": "sparkkernel",
"connectionProviderIds": [ "displayName": "Spark | Scala",
"HADOOP_KNOX", "connectionProviderIds": [
"MSSQL" "HADOOP_KNOX",
] "MSSQL"
}, ]
{ },
"name": "python3", {
"displayName": "Python 3", "name": "sparkrkernel",
"connectionProviderIds": [] "displayName": "Spark | R",
} "connectionProviderIds": [
] "HADOOP_KNOX",
}, "MSSQL"
"views": { ]
"explorer": [ },
{ {
"id": "bookTreeView", "name": "python3",
"name": "Books", "displayName": "Python 3",
"when": "bookOpened && notebookQuality != stable" "connectionProviderIds": []
} }
] ]
} },
}, "viewsContainers": {
"dependencies": { "activitybar": [
"@jupyterlab/services": "^3.2.1", {
"@types/js-yaml": "^3.12.1", "id": "books-explorer",
"@types/rimraf": "^2.0.2", "title": "Jupyter Books",
"decompress": "^4.2.0", "icon": "resources/dark/open_notebook_inverse.svg"
"error-ex": "^1.3.1", }
"fast-glob": "^3.0.4", ]
"figures": "^2.0.0", },
"fs-extra": "^5.0.0", "views": {
"glob": "^7.1.1", "books-explorer": [
"node-fetch": "^2.3.0", {
"request": "^2.88.0", "id": "bookTreeView",
"temp-write": "^3.4.0", "name": "Books"
"vscode-languageclient": "^5.3.0-next.1", }
"vscode-nls": "^4.0.0" ]
}, }
"devDependencies": { },
"@types/decompress": "^4.2.3", "dependencies": {
"@types/fs-extra": "^5.0.0", "@jupyterlab/services": "^3.2.1",
"@types/glob": "^7.1.1", "@types/js-yaml": "^3.12.1",
"@types/mocha": "^5.2.5", "@types/rimraf": "^2.0.2",
"@types/node": "^11.9.3", "decompress": "^4.2.0",
"@types/request": "^2.48.1", "error-ex": "^1.3.1",
"@types/temp-write": "^3.3.0", "fast-glob": "^3.0.4",
"@types/uuid": "^3.4.5", "figures": "^2.0.0",
"assert": "^1.4.1", "fs-extra": "^5.0.0",
"mocha": "^5.2.0", "glob": "^7.1.1",
"mocha-junit-reporter": "^1.17.0", "node-fetch": "^2.3.0",
"mocha-multi-reporters": "^1.1.7", "request": "^2.88.0",
"typemoq": "^2.1.0", "temp-write": "^3.4.0",
"vscode": "1.1.5" "vscode-languageclient": "^5.3.0-next.1",
}, "vscode-nls": "^4.0.0"
"enableProposedApi": true },
"devDependencies": {
"@types/decompress": "^4.2.3",
"@types/fs-extra": "^5.0.0",
"@types/glob": "^7.1.1",
"@types/mocha": "^5.2.5",
"@types/node": "^11.9.3",
"@types/request": "^2.48.1",
"@types/temp-write": "^3.3.0",
"@types/uuid": "^3.4.5",
"assert": "^1.4.1",
"mocha": "^5.2.0",
"mocha-junit-reporter": "^1.17.0",
"mocha-multi-reporters": "^1.1.7",
"typemoq": "^2.1.0",
"vscode": "1.1.5"
},
"enableProposedApi": true
} }

View File

@@ -26,5 +26,7 @@
"config.jupyter.kernelConfigValuesDescription": "Configuration options for Jupyter kernels. This is automatically managed and not recommended to be manually edited.", "config.jupyter.kernelConfigValuesDescription": "Configuration options for Jupyter kernels. This is automatically managed and not recommended to be manually edited.",
"title.reinstallNotebookDependencies": "Reinstall Notebook dependencies", "title.reinstallNotebookDependencies": "Reinstall Notebook dependencies",
"title.configurePython": "Configure Python for Notebooks", "title.configurePython": "Configure Python for Notebooks",
"title.managePackages": "Manage Packages" "title.managePackages": "Manage Packages",
"title.SQL19PreviewBook": "SQL Server 2019 Guide",
"books-preview-category": "Jupyter Books"
} }

View File

@@ -22,6 +22,7 @@ export interface BookTreeItemFormat {
tableOfContents: any[]; tableOfContents: any[];
page: any; page: any;
type: BookTreeItemType; type: BookTreeItemType;
treeItemCollapsibleState: number;
} }
export class BookTreeItem extends vscode.TreeItem { export class BookTreeItem extends vscode.TreeItem {
@@ -32,10 +33,10 @@ export class BookTreeItem extends vscode.TreeItem {
public command: vscode.Command; public command: vscode.Command;
constructor(public book: BookTreeItemFormat, icons: any) { constructor(public book: BookTreeItemFormat, icons: any) {
super(book.title, vscode.TreeItemCollapsibleState.Collapsed); super(book.title, book.treeItemCollapsibleState);
if (book.type === BookTreeItemType.Book) { if (book.type === BookTreeItemType.Book) {
this.collapsibleState = vscode.TreeItemCollapsibleState.Collapsed; this.collapsibleState = book.treeItemCollapsibleState;
this._sections = book.page; this._sections = book.page;
} else { } else {
this.setPageVariables(); this.setPageVariables();

View File

@@ -11,8 +11,12 @@ import * as yaml from 'js-yaml';
import * as glob from 'fast-glob'; import * as glob from 'fast-glob';
import { BookTreeItem, BookTreeItemType } from './bookTreeItem'; import { BookTreeItem, BookTreeItemType } from './bookTreeItem';
import { maxBookSearchDepth, notebookConfigKey } from '../common/constants'; import { maxBookSearchDepth, notebookConfigKey } from '../common/constants';
import { isEditorTitleFree } from '../common/utils';
import * as nls from 'vscode-nls'; import * as nls from 'vscode-nls';
import { promisify } from 'util';
const localize = nls.loadMessageBundle(); const localize = nls.loadMessageBundle();
const existsAsync = promisify(fs.exists);
export class BookTreeViewProvider implements vscode.TreeDataProvider<BookTreeItem>, azdata.nb.NavigationProvider { export class BookTreeViewProvider implements vscode.TreeDataProvider<BookTreeItem>, azdata.nb.NavigationProvider {
readonly providerId: string = 'BookNavigator'; readonly providerId: string = 'BookNavigator';
@@ -25,17 +29,29 @@ export class BookTreeViewProvider implements vscode.TreeDataProvider<BookTreeIte
private _throttleTimer: any; private _throttleTimer: any;
private _resource: string; private _resource: string;
private _onReadAllTOCFiles: vscode.EventEmitter<void> = new vscode.EventEmitter<void>(); private _onReadAllTOCFiles: vscode.EventEmitter<void> = new vscode.EventEmitter<void>();
private _openAsUntitled: boolean;
constructor(workspaceFolders: vscode.WorkspaceFolder[], extensionContext: vscode.ExtensionContext) { constructor(workspaceFolders: vscode.WorkspaceFolder[], extensionContext: vscode.ExtensionContext) {
this.getTableOfContentFiles(workspaceFolders).then(() => undefined, (err) => { console.log(err); }); this.initialze(workspaceFolders, null, extensionContext);
this._extensionContext = extensionContext; }
private initialze(workspaceFolders: vscode.WorkspaceFolder[], bookPath: string, context: vscode.ExtensionContext): void {
let workspacePaths: string[] = [];
if (bookPath) {
workspacePaths.push(bookPath);
}
else if (workspaceFolders) {
workspacePaths = workspaceFolders.map(a => a.uri.fsPath);
}
this.getTableOfContentFiles(workspacePaths).then(() => undefined, (err) => { console.log(err); });
this._extensionContext = context;
} }
public get onReadAllTOCFiles(): vscode.Event<void> { public get onReadAllTOCFiles(): vscode.Event<void> {
return this._onReadAllTOCFiles.event; return this._onReadAllTOCFiles.event;
} }
async getTableOfContentFiles(workspaceFolders: vscode.WorkspaceFolder[]): Promise<void> { async getTableOfContentFiles(workspacePaths: string[]): Promise<void> {
let notebookConfig = vscode.workspace.getConfiguration(notebookConfigKey); let notebookConfig = vscode.workspace.getConfiguration(notebookConfigKey);
let maxDepth = notebookConfig[maxBookSearchDepth]; let maxDepth = notebookConfig[maxBookSearchDepth];
// Use default value if user enters an invalid value // Use default value if user enters an invalid value
@@ -44,7 +60,6 @@ export class BookTreeViewProvider implements vscode.TreeDataProvider<BookTreeIte
} else if (maxDepth === 0) { // No limit of search depth if user enters 0 } else if (maxDepth === 0) { // No limit of search depth if user enters 0
maxDepth = undefined; maxDepth = undefined;
} }
let workspacePaths: string[] = workspaceFolders.map(a => a.uri.fsPath);
for (let workspacePath of workspacePaths) { for (let workspacePath of workspacePaths) {
let p = path.join(workspacePath, '**', '_data', 'toc.yml').replace(/\\/g, '/'); let p = path.join(workspacePath, '**', '_data', 'toc.yml').replace(/\\/g, '/');
let tableOfContentPaths = await glob(p, { deep: maxDepth }); let tableOfContentPaths = await glob(p, { deep: maxDepth });
@@ -55,12 +70,50 @@ export class BookTreeViewProvider implements vscode.TreeDataProvider<BookTreeIte
this._onReadAllTOCFiles.fire(); this._onReadAllTOCFiles.fire();
} }
async openBook(bookPath: string, context: vscode.ExtensionContext, openAsUntitled: boolean): Promise<void> {
try {
// Check if the book is already open in viewlet.
if (this._tableOfContentPaths.indexOf(path.join(bookPath, '_data', 'toc.yml').replace(/\\/g, '/')) > -1 && this._allNotebooks.size > 0) {
vscode.commands.executeCommand('workbench.books.action.focusBooksExplorer');
}
else {
await this.getTableOfContentFiles([bookPath]);
let bookViewer = vscode.window.createTreeView('bookTreeView', { showCollapseAll: true, treeDataProvider: this });
await vscode.commands.executeCommand('workbench.books.action.focusBooksExplorer');
this._openAsUntitled = openAsUntitled;
let books = this.getBooks();
if (books && books.length > 0) {
bookViewer.reveal(books[0], { expand: vscode.TreeItemCollapsibleState.Expanded, focus: true, select: true });
const readmeMarkdown: string = path.join(bookPath, 'content', books[0].tableOfContents[0].url.concat('.md'));
const readmeNotebook: string = path.join(bookPath, 'content', books[0].tableOfContents[0].url.concat('.ipynb'));
const markdownExists = await existsAsync(readmeMarkdown);
const notebookExists = await existsAsync(readmeNotebook);
if (markdownExists) {
vscode.commands.executeCommand('markdown.showPreview', vscode.Uri.file(readmeMarkdown));
}
else if (notebookExists) {
vscode.workspace.openTextDocument(readmeNotebook);
}
}
}
} catch (e) {
vscode.window.showErrorMessage(localize('openBookError', "Open book {0} failed: {1}",
bookPath,
e instanceof Error ? e.message : e));
}
}
async openNotebook(resource: string): Promise<void> { async openNotebook(resource: string): Promise<void> {
try { try {
let doc = await vscode.workspace.openTextDocument(resource); if (this._openAsUntitled) {
vscode.window.showTextDocument(doc); this.openNotebookAsUntitled(resource);
}
else {
let doc = await vscode.workspace.openTextDocument(resource);
vscode.window.showTextDocument(doc);
}
} catch (e) { } catch (e) {
vscode.window.showErrorMessage(localize('openNotebookError', 'Open file {0} failed: {1}', vscode.window.showErrorMessage(localize('openNotebookError', "Open file {0} failed: {1}",
resource, resource,
e instanceof Error ? e.message : e)); e instanceof Error ? e.message : e));
} }
@@ -78,6 +131,24 @@ export class BookTreeViewProvider implements vscode.TreeDataProvider<BookTreeIte
}); });
} }
openNotebookAsUntitled(resource: string): void {
try {
let untitledFileName: vscode.Uri = this.getUntitledNotebookUri(resource);
vscode.workspace.openTextDocument(resource).then((document) => {
let initialContent = document.getText();
azdata.nb.showNotebookDocument(untitledFileName, {
connectionProfile: null,
initialContent: initialContent,
initialDirtyState: false
});
});
} catch (e) {
vscode.window.showErrorMessage(localize('openUntitledNotebookError', "Open file {0} as untitled failed: {1}",
resource,
e instanceof Error ? e.message : e));
}
}
private runThrottledAction(resource: string, action: () => void) { private runThrottledAction(resource: string, action: () => void) {
const isResourceChange = resource !== this._resource; const isResourceChange = resource !== this._resource;
if (isResourceChange) { if (isResourceChange) {
@@ -101,7 +172,7 @@ export class BookTreeViewProvider implements vscode.TreeDataProvider<BookTreeIte
try { try {
vscode.env.openExternal(vscode.Uri.parse(resource)); vscode.env.openExternal(vscode.Uri.parse(resource));
} catch (e) { } catch (e) {
vscode.window.showErrorMessage(localize('openExternalLinkError', 'Open link {0} failed: {1}', vscode.window.showErrorMessage(localize('openExternalLinkError', "Open link {0} failed: {1}",
resource, resource,
e instanceof Error ? e.message : e)); e instanceof Error ? e.message : e));
} }
@@ -139,7 +210,8 @@ export class BookTreeViewProvider implements vscode.TreeDataProvider<BookTreeIte
root: root, root: root,
tableOfContents: this.flattenArray(tableOfContents), tableOfContents: this.flattenArray(tableOfContents),
page: tableOfContents, page: tableOfContents,
type: BookTreeItemType.Book type: BookTreeItemType.Book,
treeItemCollapsibleState: vscode.TreeItemCollapsibleState.Expanded,
}, },
{ {
light: this._extensionContext.asAbsolutePath('resources/light/book.svg'), light: this._extensionContext.asAbsolutePath('resources/light/book.svg'),
@@ -148,7 +220,7 @@ export class BookTreeViewProvider implements vscode.TreeDataProvider<BookTreeIte
); );
books.push(book); books.push(book);
} catch (e) { } catch (e) {
vscode.window.showErrorMessage(localize('openConfigFileError', 'Open file {0} failed: {1}', vscode.window.showErrorMessage(localize('openConfigFileError', "Open file {0} failed: {1}",
path.join(root, '_config.yml'), path.join(root, '_config.yml'),
e instanceof Error ? e.message : e)); e instanceof Error ? e.message : e));
} }
@@ -166,7 +238,8 @@ export class BookTreeViewProvider implements vscode.TreeDataProvider<BookTreeIte
root: root, root: root,
tableOfContents: tableOfContents, tableOfContents: tableOfContents,
page: sections[i], page: sections[i],
type: BookTreeItemType.ExternalLink type: BookTreeItemType.ExternalLink,
treeItemCollapsibleState: vscode.TreeItemCollapsibleState.Collapsed
}, },
{ {
light: this._extensionContext.asAbsolutePath('resources/light/link.svg'), light: this._extensionContext.asAbsolutePath('resources/light/link.svg'),
@@ -186,7 +259,8 @@ export class BookTreeViewProvider implements vscode.TreeDataProvider<BookTreeIte
root: root, root: root,
tableOfContents: tableOfContents, tableOfContents: tableOfContents,
page: sections[i], page: sections[i],
type: BookTreeItemType.Notebook type: BookTreeItemType.Notebook,
treeItemCollapsibleState: vscode.TreeItemCollapsibleState.Collapsed
}, },
{ {
light: this._extensionContext.asAbsolutePath('resources/light/notebook.svg'), light: this._extensionContext.asAbsolutePath('resources/light/notebook.svg'),
@@ -195,13 +269,17 @@ export class BookTreeViewProvider implements vscode.TreeDataProvider<BookTreeIte
); );
notebooks.push(notebook); notebooks.push(notebook);
this._allNotebooks.set(pathToNotebook, notebook); this._allNotebooks.set(pathToNotebook, notebook);
if (this._openAsUntitled) {
this._allNotebooks.set(path.basename(pathToNotebook, '.ipynb'), notebook);
}
} else if (fs.existsSync(pathToMarkdown)) { } else if (fs.existsSync(pathToMarkdown)) {
let markdown = new BookTreeItem({ let markdown = new BookTreeItem({
title: sections[i].title, title: sections[i].title,
root: root, root: root,
tableOfContents: tableOfContents, tableOfContents: tableOfContents,
page: sections[i], page: sections[i],
type: BookTreeItemType.Markdown type: BookTreeItemType.Markdown,
treeItemCollapsibleState: vscode.TreeItemCollapsibleState.Collapsed
}, },
{ {
light: this._extensionContext.asAbsolutePath('resources/light/markdown.svg'), light: this._extensionContext.asAbsolutePath('resources/light/markdown.svg'),
@@ -210,7 +288,7 @@ export class BookTreeViewProvider implements vscode.TreeDataProvider<BookTreeIte
); );
notebooks.push(markdown); notebooks.push(markdown);
} else { } else {
vscode.window.showErrorMessage(localize('missingFileError', 'Missing file : {0}', sections[i].title)); vscode.window.showErrorMessage(localize('missingFileError', "Missing file : {0}", sections[i].title));
} }
} }
} else { } else {
@@ -226,8 +304,8 @@ export class BookTreeViewProvider implements vscode.TreeDataProvider<BookTreeIte
if (notebook) { if (notebook) {
result = { result = {
hasNavigation: true, hasNavigation: true,
previous: notebook.previousUri ? vscode.Uri.file(notebook.previousUri) : undefined, previous: notebook.previousUri ? this._openAsUntitled ? vscode.Uri.parse(notebook.previousUri).with({ scheme: 'untitled' }) : vscode.Uri.file(notebook.previousUri) : undefined,
next: notebook.nextUri ? vscode.Uri.file(notebook.nextUri) : undefined next: notebook.nextUri ? this._openAsUntitled ? vscode.Uri.parse(notebook.nextUri).with({ scheme: 'untitled' }) : vscode.Uri.file(notebook.nextUri) : undefined
}; };
} else { } else {
result = { result = {
@@ -239,4 +317,28 @@ export class BookTreeViewProvider implements vscode.TreeDataProvider<BookTreeIte
return Promise.resolve(result); return Promise.resolve(result);
} }
getUntitledNotebookUri(resource: string): vscode.Uri {
let title = this.findNextUntitledFileName(resource);
let untitledFileName: vscode.Uri = vscode.Uri.parse(`untitled:${title}`);
if (!this._allNotebooks.get(untitledFileName.fsPath)) {
let notebook = this._allNotebooks.get(resource);
this._allNotebooks.set(untitledFileName.fsPath, notebook);
}
return untitledFileName;
}
findNextUntitledFileName(filePath: string): string {
const fileExtension = path.extname(filePath);
const baseName = path.basename(filePath, fileExtension);
let idx = 0;
let title = `${baseName}`;
do {
const suffix = idx === 0 ? '' : `-${idx}`;
title = `${baseName}${suffix}`;
idx++;
} while (!isEditorTitleFree(title));
return title;
}
} }

View File

@@ -30,6 +30,8 @@ export async function activate(extensionContext: vscode.ExtensionContext): Promi
const bookTreeViewProvider = new BookTreeViewProvider(vscode.workspace.workspaceFolders || [], extensionContext); const bookTreeViewProvider = new BookTreeViewProvider(vscode.workspace.workspaceFolders || [], extensionContext);
extensionContext.subscriptions.push(vscode.window.registerTreeDataProvider('bookTreeView', bookTreeViewProvider)); extensionContext.subscriptions.push(vscode.window.registerTreeDataProvider('bookTreeView', bookTreeViewProvider));
extensionContext.subscriptions.push(azdata.nb.registerNavigationProvider(bookTreeViewProvider)); extensionContext.subscriptions.push(azdata.nb.registerNavigationProvider(bookTreeViewProvider));
extensionContext.subscriptions.push(vscode.commands.registerCommand('bookTreeView.openBook', (resource, openAsReadonly) => bookTreeViewProvider.openBook(resource, extensionContext, openAsReadonly)));
extensionContext.subscriptions.push(vscode.commands.registerCommand('bookTreeView.openNotebookAsUntitled', (resource) => bookTreeViewProvider.openNotebookAsUntitled(resource)));
extensionContext.subscriptions.push(vscode.commands.registerCommand('bookTreeView.openNotebook', (resource) => bookTreeViewProvider.openNotebook(resource))); extensionContext.subscriptions.push(vscode.commands.registerCommand('bookTreeView.openNotebook', (resource) => bookTreeViewProvider.openNotebook(resource)));
extensionContext.subscriptions.push(vscode.commands.registerCommand('bookTreeView.openMarkdown', (resource) => bookTreeViewProvider.openMarkdown(resource))); extensionContext.subscriptions.push(vscode.commands.registerCommand('bookTreeView.openMarkdown', (resource) => bookTreeViewProvider.openMarkdown(resource)));
extensionContext.subscriptions.push(vscode.commands.registerCommand('bookTreeView.openExternalLink', (resource) => bookTreeViewProvider.openExternalLink(resource))); extensionContext.subscriptions.push(vscode.commands.registerCommand('bookTreeView.openExternalLink', (resource) => bookTreeViewProvider.openExternalLink(resource)));

View File

@@ -16,6 +16,7 @@ import { ITextEditorOptions } from 'vs/platform/editor/common/editor';
import { Schemas } from 'vs/base/common/network'; import { Schemas } from 'vs/base/common/network';
import { ConnectionProfile } from 'sql/platform/connection/common/connectionProfile'; import { ConnectionProfile } from 'sql/platform/connection/common/connectionProfile';
import * as types from 'vs/base/common/types'; import * as types from 'vs/base/common/types';
import * as fs from 'fs';
import { import {
SqlMainContext, MainThreadNotebookDocumentsAndEditorsShape, SqlExtHostContext, ExtHostNotebookDocumentsAndEditorsShape, SqlMainContext, MainThreadNotebookDocumentsAndEditorsShape, SqlExtHostContext, ExtHostNotebookDocumentsAndEditorsShape,
@@ -698,13 +699,25 @@ export class MainThreadNotebookDocumentsAndEditors extends Disposable implements
onNext: async (uri) => { onNext: async (uri) => {
let result = await this._proxy.$getNavigation(handle, uri); let result = await this._proxy.$getNavigation(handle, uri);
if (result) { if (result) {
this.doOpenEditor(result.next, {}); if (uri.scheme === Schemas.untitled) {
let untitledNbName: URI = URI.parse(`untitled:${path.basename(result.next.path, '.ipynb')}`);
this.doOpenEditor(untitledNbName, { initialContent: fs.readFileSync(result.next.path).toString(), initialDirtyState: false });
}
else {
this.doOpenEditor(result.next, {});
}
} }
}, },
onPrevious: async (uri) => { onPrevious: async (uri) => {
let result = await this._proxy.$getNavigation(handle, uri); let result = await this._proxy.$getNavigation(handle, uri);
if (result) { if (result) {
this.doOpenEditor(result.previous, {}); if (uri.scheme === Schemas.untitled) {
let untitledNbName: URI = URI.parse(`untitled:${path.basename(result.previous.path, '.ipynb')}`);
this.doOpenEditor(untitledNbName, { initialContent: fs.readFileSync(result.previous.path).toString(), initialDirtyState: false });
}
else {
this.doOpenEditor(result.previous, {});
}
} }
} }
}); });

View File

@@ -23,6 +23,7 @@ import { IViewletService } from 'vs/workbench/services/viewlet/browser/viewlet';
import { URI } from 'vs/base/common/uri'; import { URI } from 'vs/base/common/uri';
import { IWorkspaceEditingService } from 'vs/workbench/services/workspace/common/workspaceEditing'; import { IWorkspaceEditingService } from 'vs/workbench/services/workspace/common/workspaceEditing';
import { IWindowService } from 'vs/platform/windows/common/windows'; import { IWindowService } from 'vs/platform/windows/common/windows';
import { IViewContainersRegistry, Extensions as ViewContainerExtensions, ViewContainer } from 'vs/workbench/common/views';
import { ContextKeyExpr } from 'vs/platform/contextkey/common/contextkey'; import { ContextKeyExpr } from 'vs/platform/contextkey/common/contextkey';
import { NodeContextKey } from 'sql/workbench/parts/dataExplorer/common/nodeContext'; import { NodeContextKey } from 'sql/workbench/parts/dataExplorer/common/nodeContext';
import { MssqlNodeContext } from 'sql/workbench/parts/dataExplorer/common/mssqlNodeContext'; import { MssqlNodeContext } from 'sql/workbench/parts/dataExplorer/common/mssqlNodeContext';
@@ -157,6 +158,22 @@ configurationRegistry.registerConfiguration({
} }
}); });
/**
* Explorer viewlet id.
*/
export const VIEWLET_ID = 'bookTreeView';
/**
* Explorer viewlet container.
*/
export const VIEW_CONTAINER: ViewContainer = Registry.as<IViewContainersRegistry>(ViewContainerExtensions.ViewContainersRegistry).registerViewContainer(VIEWLET_ID);
registerAction({
id: 'workbench.books.action.focusBooksExplorer',
handler: async (accessor) => {
const viewletService = accessor.get(IViewletService);
viewletService.openViewlet('workbench.view.extension.books-explorer', true);
}
});
/* *************** Output components *************** */ /* *************** Output components *************** */
// Note: most existing types use the same component to render. In order to // Note: most existing types use the same component to render. In order to
// preserve correct rank order, we register it once for each different rank of // preserve correct rank order, we register it once for each different rank of