mirror of
https://github.com/ckaczor/azuredatastudio.git
synced 2026-02-17 02:51:36 -05:00
New feature: Jupyter Books (#6095)
* Initial commit * Fixed broken branch * Show notebook titles in tree view * Added README * sections showing in tree view * Multiple books in treeview * removed book extension, added to notebook * removed book from extensions.ts * addressed Chris' comments * Addressed Charles' comments * fixed spelling in readme * added comment about same filenames * adding vsix * addressed Karl's comments
This commit is contained in:
@@ -376,7 +376,7 @@ export function packageExtensionsStream(optsIn?: IPackageExtensionsOptions): Nod
|
|||||||
];
|
];
|
||||||
|
|
||||||
const localExtensionDependencies = () => gulp.src(extensionDepsSrc, { base: '.', dot: true })
|
const localExtensionDependencies = () => gulp.src(extensionDepsSrc, { base: '.', dot: true })
|
||||||
.pipe(filter(['**', '!**/package-lock.json']))
|
.pipe(filter(['**', '!**/package-lock.json']));
|
||||||
|
|
||||||
// Original code commented out here
|
// Original code commented out here
|
||||||
// const localExtensionDependencies = () => gulp.src('extensions/node_modules/**', { base: '.' });
|
// const localExtensionDependencies = () => gulp.src('extensions/node_modules/**', { base: '.' });
|
||||||
|
|||||||
@@ -2,6 +2,12 @@
|
|||||||
|
|
||||||
Welcome to the Notebook extension for Azure Data Studio! This extension supports core notebook functionality including configuration settings, actions such as New / Open Notebook, and more.
|
Welcome to the Notebook extension for Azure Data Studio! This extension supports core notebook functionality including configuration settings, actions such as New / Open Notebook, and more.
|
||||||
|
|
||||||
|
## Books in Azure Data Studio
|
||||||
|
|
||||||
|
Jupyter Book allows opening a single "Book" of related notebooks and markdown files. This feature will work if you open any Book folder in Azure Data Studio.
|
||||||
|
|
||||||
|
Download a [sample book](https://github.com/jupyter/jupyter-book) and open folder in Azure Data Studio to get started. You can learn more about Books on the [Jupyter Book homepage](https://jupyter.org/jupyter-book/intro.html).
|
||||||
|
|
||||||
## Code of Conduct
|
## Code of Conduct
|
||||||
|
|
||||||
This project has adopted the [Microsoft Open Source Code of Conduct](https://opensource.microsoft.com/codeofconduct/). For more information see the [Code of Conduct FAQ](https://opensource.microsoft.com/codeofconduct/faq/) or contact [opencode@microsoft.com](mailto:opencode@microsoft.com) with any additional questions or comments.
|
This project has adopted the [Microsoft Open Source Code of Conduct](https://opensource.microsoft.com/codeofconduct/). For more information see the [Code of Conduct FAQ](https://opensource.microsoft.com/codeofconduct/faq/) or contact [opencode@microsoft.com](mailto:opencode@microsoft.com) with any additional questions or comments.
|
||||||
|
|||||||
@@ -333,10 +333,20 @@
|
|||||||
"connectionProviderIds": []
|
"connectionProviderIds": []
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
|
},
|
||||||
|
"views": {
|
||||||
|
"explorer": [
|
||||||
|
{
|
||||||
|
"id": "bookTreeView",
|
||||||
|
"name": "Books",
|
||||||
|
"when": "bookOpened && isDevelopment"
|
||||||
|
}
|
||||||
|
]
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@jupyterlab/services": "^3.2.1",
|
"@jupyterlab/services": "^3.2.1",
|
||||||
|
"@types/js-yaml": "^3.12.1",
|
||||||
"decompress": "^4.2.0",
|
"decompress": "^4.2.0",
|
||||||
"error-ex": "^1.3.1",
|
"error-ex": "^1.3.1",
|
||||||
"figures": "^2.0.0",
|
"figures": "^2.0.0",
|
||||||
|
|||||||
24
extensions/notebook/src/book/bookTreeItem.ts
Normal file
24
extensions/notebook/src/book/bookTreeItem.ts
Normal file
@@ -0,0 +1,24 @@
|
|||||||
|
/*---------------------------------------------------------------------------------------------
|
||||||
|
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||||
|
* Licensed under the Source EULA. See License.txt in the project root for license information.
|
||||||
|
*--------------------------------------------------------------------------------------------*/
|
||||||
|
|
||||||
|
import * as vscode from 'vscode';
|
||||||
|
|
||||||
|
export class BookTreeItem extends vscode.TreeItem {
|
||||||
|
|
||||||
|
constructor(
|
||||||
|
public readonly title: string,
|
||||||
|
public readonly root: string,
|
||||||
|
public readonly tableOfContents: any[],
|
||||||
|
public readonly collapsibleState: vscode.TreeItemCollapsibleState,
|
||||||
|
public uri?: string,
|
||||||
|
public readonly type?: vscode.FileType,
|
||||||
|
public command?: vscode.Command
|
||||||
|
) {
|
||||||
|
super(title, collapsibleState);
|
||||||
|
}
|
||||||
|
|
||||||
|
contextValue = 'book';
|
||||||
|
|
||||||
|
}
|
||||||
111
extensions/notebook/src/book/bookTreeView.ts
Normal file
111
extensions/notebook/src/book/bookTreeView.ts
Normal file
@@ -0,0 +1,111 @@
|
|||||||
|
/*---------------------------------------------------------------------------------------------
|
||||||
|
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||||
|
* Licensed under the Source EULA. See License.txt in the project root for license information.
|
||||||
|
*--------------------------------------------------------------------------------------------*/
|
||||||
|
|
||||||
|
import * as vscode from 'vscode';
|
||||||
|
import * as path from 'path';
|
||||||
|
import * as fs from 'fs';
|
||||||
|
import * as yaml from 'js-yaml';
|
||||||
|
import { BookTreeItem } from './bookTreeItem';
|
||||||
|
import * as nls from 'vscode-nls';
|
||||||
|
const localize = nls.loadMessageBundle();
|
||||||
|
|
||||||
|
|
||||||
|
export class BookTreeViewProvider implements vscode.TreeDataProvider<BookTreeItem> {
|
||||||
|
|
||||||
|
private _onDidChangeTreeData: vscode.EventEmitter<BookTreeItem | undefined> = new vscode.EventEmitter<BookTreeItem | undefined>();
|
||||||
|
readonly onDidChangeTreeData: vscode.Event<BookTreeItem | undefined> = this._onDidChangeTreeData.event;
|
||||||
|
private _tableOfContentsPath: string[];
|
||||||
|
|
||||||
|
constructor(private workspaceRoot: string) {
|
||||||
|
if (workspaceRoot !== '') {
|
||||||
|
this._tableOfContentsPath = this.getTocFiles(this.workspaceRoot);
|
||||||
|
let bookOpened: boolean = this._tableOfContentsPath && this._tableOfContentsPath.length > 0;
|
||||||
|
vscode.commands.executeCommand('setContext', 'bookOpened', bookOpened);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private getTocFiles(dir: string): string[] {
|
||||||
|
let allFiles: string[] = [];
|
||||||
|
let files = fs.readdirSync(dir);
|
||||||
|
for (let i in files) {
|
||||||
|
let name = path.join(dir, files[i]);
|
||||||
|
if (fs.statSync(name).isDirectory()) {
|
||||||
|
allFiles = allFiles.concat(this.getTocFiles(name));
|
||||||
|
} else if (files[i] === 'toc.yml') {
|
||||||
|
allFiles.push(name);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return allFiles;
|
||||||
|
}
|
||||||
|
|
||||||
|
async openNotebook(resource: vscode.Uri): Promise<void> {
|
||||||
|
try {
|
||||||
|
let doc = await vscode.workspace.openTextDocument(resource);
|
||||||
|
vscode.window.showTextDocument(doc);
|
||||||
|
} catch (e) {
|
||||||
|
vscode.window.showErrorMessage(localize('openNotebookError', 'Open file {0} failed: {1}',
|
||||||
|
resource.fsPath,
|
||||||
|
e instanceof Error ? e.message : e));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
getTreeItem(element: BookTreeItem): vscode.TreeItem {
|
||||||
|
return element;
|
||||||
|
}
|
||||||
|
|
||||||
|
getChildren(element?: BookTreeItem): Thenable<BookTreeItem[]> {
|
||||||
|
if (element) {
|
||||||
|
if (element.tableOfContents) {
|
||||||
|
return Promise.resolve(this.getSections(element.tableOfContents, element.root));
|
||||||
|
} else {
|
||||||
|
return Promise.resolve([]);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
return Promise.resolve(this.getBooks());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private getBooks(): BookTreeItem[] {
|
||||||
|
let books: BookTreeItem[] = [];
|
||||||
|
for (let i in this._tableOfContentsPath) {
|
||||||
|
let root = path.dirname(path.dirname(this._tableOfContentsPath[i]));
|
||||||
|
try {
|
||||||
|
const config = yaml.safeLoad(fs.readFileSync(path.join(root, '_config.yml'), 'utf-8'));
|
||||||
|
const tableOfContents = yaml.safeLoad(fs.readFileSync(this._tableOfContentsPath[i], 'utf-8'));
|
||||||
|
let book = new BookTreeItem(config.title, root, tableOfContents, vscode.TreeItemCollapsibleState.Collapsed);
|
||||||
|
books.push(book);
|
||||||
|
} catch (e) {
|
||||||
|
vscode.window.showErrorMessage(localize('openConfigFileError', 'Open file {0} failed: {1}',
|
||||||
|
path.join(root, '_config.yml'),
|
||||||
|
e instanceof Error ? e.message : e));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return books;
|
||||||
|
}
|
||||||
|
|
||||||
|
private getSections(sec: any[], root: string): BookTreeItem[] {
|
||||||
|
let notebooks: BookTreeItem[] = [];
|
||||||
|
for (let i = 0; i < sec.length; i++) {
|
||||||
|
if (sec[i].url) {
|
||||||
|
let pathToNotebook = path.join(root, 'content', sec[i].url.concat('.ipynb'));
|
||||||
|
let pathToMarkdown = path.join(root, 'content', sec[i].url.concat('.md'));
|
||||||
|
// Note: Currently, if there is an ipynb and a md file with the same name, Jupyter Books only shows the notebook.
|
||||||
|
// Following Jupyter Books behavior for now
|
||||||
|
if (fs.existsSync(pathToNotebook)) {
|
||||||
|
let notebook = new BookTreeItem(sec[i].title, root, sec[i].sections, sec[i].sections ? vscode.TreeItemCollapsibleState.Expanded : vscode.TreeItemCollapsibleState.None, sec[i].url, vscode.FileType.File, { command: 'bookTreeView.openNotebook', title: 'Open Notebook', arguments: [pathToNotebook], });
|
||||||
|
notebooks.push(notebook);
|
||||||
|
} else if (fs.existsSync(pathToMarkdown)) {
|
||||||
|
let markdown = new BookTreeItem(sec[i].title, root, sec[i].sections, sec[i].sections ? vscode.TreeItemCollapsibleState.Expanded : vscode.TreeItemCollapsibleState.None, sec[i].url, vscode.FileType.File, { command: 'bookTreeView.openNotebook', title: 'Open Notebook', arguments: [pathToMarkdown], });
|
||||||
|
notebooks.push(markdown);
|
||||||
|
} else {
|
||||||
|
vscode.window.showErrorMessage(localize('missingFileError', 'Missing file : {0}', sec[i].title));
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// TODO: search functionality (#6160)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return notebooks;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -15,6 +15,7 @@ import { IExtensionApi } from './types';
|
|||||||
import { CellType } from './contracts/content';
|
import { CellType } from './contracts/content';
|
||||||
import { getErrorMessage, isEditorTitleFree } from './common/utils';
|
import { getErrorMessage, isEditorTitleFree } from './common/utils';
|
||||||
import { NotebookUriHandler } from './protocol/notebookUriHandler';
|
import { NotebookUriHandler } from './protocol/notebookUriHandler';
|
||||||
|
import { BookTreeViewProvider } from './book/bookTreeView';
|
||||||
|
|
||||||
const localize = nls.loadMessageBundle();
|
const localize = nls.loadMessageBundle();
|
||||||
|
|
||||||
@@ -26,6 +27,11 @@ let controller: JupyterController;
|
|||||||
type ChooseCellType = { label: string, id: CellType };
|
type ChooseCellType = { label: string, id: CellType };
|
||||||
|
|
||||||
export async function activate(extensionContext: vscode.ExtensionContext): Promise<IExtensionApi> {
|
export async function activate(extensionContext: vscode.ExtensionContext): Promise<IExtensionApi> {
|
||||||
|
|
||||||
|
const bookTreeViewProvider = new BookTreeViewProvider(vscode.workspace.rootPath || '');
|
||||||
|
vscode.window.registerTreeDataProvider('bookTreeView', bookTreeViewProvider);
|
||||||
|
vscode.commands.registerCommand('bookTreeView.openNotebook', (resource) => bookTreeViewProvider.openNotebook(resource));
|
||||||
|
|
||||||
extensionContext.subscriptions.push(vscode.commands.registerCommand('_notebook.command.new', (context?: azdata.ConnectedContext) => {
|
extensionContext.subscriptions.push(vscode.commands.registerCommand('_notebook.command.new', (context?: azdata.ConnectedContext) => {
|
||||||
let connectionProfile: azdata.IConnectionProfile = undefined;
|
let connectionProfile: azdata.IConnectionProfile = undefined;
|
||||||
if (context && context.connectionProfile) {
|
if (context && context.connectionProfile) {
|
||||||
|
|||||||
@@ -120,6 +120,11 @@
|
|||||||
"@types/minimatch" "*"
|
"@types/minimatch" "*"
|
||||||
"@types/node" "*"
|
"@types/node" "*"
|
||||||
|
|
||||||
|
"@types/js-yaml@^3.12.1":
|
||||||
|
version "3.12.1"
|
||||||
|
resolved "https://registry.yarnpkg.com/@types/js-yaml/-/js-yaml-3.12.1.tgz#5c6f4a1eabca84792fbd916f0cb40847f123c656"
|
||||||
|
integrity sha512-SGGAhXLHDx+PK4YLNcNGa6goPf9XRWQNAUUbffkwVGGXIxmDKWyGGL4inzq2sPmExu431Ekb9aEMn9BkPqEYFA==
|
||||||
|
|
||||||
"@types/minimatch@*":
|
"@types/minimatch@*":
|
||||||
version "3.0.3"
|
version "3.0.3"
|
||||||
resolved "https://registry.yarnpkg.com/@types/minimatch/-/minimatch-3.0.3.tgz#3dca0e3f33b200fc7d1139c0cd96c1268cadfd9d"
|
resolved "https://registry.yarnpkg.com/@types/minimatch/-/minimatch-3.0.3.tgz#3dca0e3f33b200fc7d1139c0cd96c1268cadfd9d"
|
||||||
|
|||||||
Reference in New Issue
Block a user