mirror of
https://github.com/ckaczor/azuredatastudio.git
synced 2026-02-17 02:51:36 -05:00
Add Open Notebook Folder functionality to Books viewlet. (#9939)
This commit is contained in:
@@ -6,15 +6,12 @@
|
||||
import * as azdata from 'azdata';
|
||||
import * as vscode from 'vscode';
|
||||
import * as yaml from 'js-yaml';
|
||||
import * as glob from 'fast-glob';
|
||||
import { BookTreeItem, BookTreeItemType } from './bookTreeItem';
|
||||
import { maxBookSearchDepth, notebookConfigKey } from '../common/constants';
|
||||
import * as path from 'path';
|
||||
import * as fileServices from 'fs';
|
||||
import * as fs from 'fs-extra';
|
||||
import * as loc from '../common/localizedConstants';
|
||||
import { IJupyterBookToc, IJupyterBookSection } from '../contracts/content';
|
||||
import { isNullOrUndefined } from 'util';
|
||||
import { ApiWrapper } from '../common/apiWrapper';
|
||||
|
||||
|
||||
@@ -23,24 +20,30 @@ const fsPromises = fileServices.promises;
|
||||
export class BookModel implements azdata.nb.NavigationProvider {
|
||||
private _bookItems: BookTreeItem[];
|
||||
private _allNotebooks = new Map<string, BookTreeItem>();
|
||||
private _tableOfContentPaths: string[] = [];
|
||||
private _tableOfContentsPath: string;
|
||||
readonly providerId: string = 'BookNavigator';
|
||||
|
||||
private _errorMessage: string;
|
||||
private apiWrapper: ApiWrapper = new ApiWrapper();
|
||||
|
||||
constructor(public bookPath: string, public openAsUntitled: boolean, private _extensionContext: vscode.ExtensionContext) {
|
||||
this.bookPath = bookPath;
|
||||
this.openAsUntitled = openAsUntitled;
|
||||
constructor(
|
||||
public readonly bookPath: string,
|
||||
public readonly openAsUntitled: boolean,
|
||||
public readonly isNotebook: boolean,
|
||||
private _extensionContext: vscode.ExtensionContext) {
|
||||
this._bookItems = [];
|
||||
this._extensionContext.subscriptions.push(azdata.nb.registerNavigationProvider(this));
|
||||
}
|
||||
|
||||
public async initializeContents(): Promise<void> {
|
||||
this._tableOfContentPaths = [];
|
||||
this._bookItems = [];
|
||||
await this.getTableOfContentFiles(this.bookPath);
|
||||
await this.readBooks();
|
||||
this._allNotebooks = new Map<string, BookTreeItem>();
|
||||
if (this.isNotebook) {
|
||||
this.readNotebook();
|
||||
} else {
|
||||
await this.loadTableOfContentFiles(this.bookPath);
|
||||
await this.readBooks();
|
||||
}
|
||||
}
|
||||
|
||||
public getAllNotebooks(): Map<string, BookTreeItem> {
|
||||
@@ -51,20 +54,14 @@ export class BookModel implements azdata.nb.NavigationProvider {
|
||||
return this._allNotebooks.get(uri);
|
||||
}
|
||||
|
||||
public async getTableOfContentFiles(folderPath: string): Promise<void> {
|
||||
let notebookConfig = vscode.workspace.getConfiguration(notebookConfigKey);
|
||||
let maxDepth = notebookConfig[maxBookSearchDepth];
|
||||
// Use default value if user enters an invalid value
|
||||
if (isNullOrUndefined(maxDepth) || maxDepth < 0) {
|
||||
maxDepth = 5;
|
||||
} else if (maxDepth === 0) { // No limit of search depth if user enters 0
|
||||
maxDepth = undefined;
|
||||
public async loadTableOfContentFiles(folderPath: string): Promise<void> {
|
||||
if (this.isNotebook) {
|
||||
return;
|
||||
}
|
||||
|
||||
let p: string = path.posix.join(glob.escapePath(folderPath.replace(/\\/g, '/')), '**', '_data', 'toc.yml');
|
||||
let tableOfContentPaths: string[] = await glob(p, { deep: maxDepth });
|
||||
if (tableOfContentPaths.length > 0) {
|
||||
this._tableOfContentPaths = this._tableOfContentPaths.concat(tableOfContentPaths);
|
||||
let tableOfContentsPath: string = path.posix.join(folderPath, '_data', 'toc.yml');
|
||||
if (await fs.pathExists(tableOfContentsPath)) {
|
||||
this._tableOfContentsPath = tableOfContentsPath;
|
||||
vscode.commands.executeCommand('setContext', 'bookOpened', true);
|
||||
} else {
|
||||
this._errorMessage = loc.missingTocError;
|
||||
@@ -72,16 +69,55 @@ export class BookModel implements azdata.nb.NavigationProvider {
|
||||
}
|
||||
}
|
||||
|
||||
public readNotebook(): BookTreeItem {
|
||||
if (!this.isNotebook) {
|
||||
return undefined;
|
||||
}
|
||||
|
||||
let pathDetails = path.parse(this.bookPath);
|
||||
let notebookItem = new BookTreeItem({
|
||||
title: pathDetails.name,
|
||||
contentPath: this.bookPath,
|
||||
root: pathDetails.dir,
|
||||
tableOfContents: { sections: undefined },
|
||||
page: { sections: undefined },
|
||||
type: BookTreeItemType.Notebook,
|
||||
treeItemCollapsibleState: vscode.TreeItemCollapsibleState.Expanded,
|
||||
isUntitled: this.openAsUntitled,
|
||||
},
|
||||
{
|
||||
light: this._extensionContext.asAbsolutePath('resources/light/notebook.svg'),
|
||||
dark: this._extensionContext.asAbsolutePath('resources/dark/notebook_inverse.svg')
|
||||
}
|
||||
);
|
||||
this._bookItems.push(notebookItem);
|
||||
if (this.openAsUntitled && !this._allNotebooks.get(pathDetails.base)) {
|
||||
this._allNotebooks.set(pathDetails.base, notebookItem);
|
||||
} else {
|
||||
// convert to URI to avoid casing issue with drive letters when getting navigation links
|
||||
let uriToNotebook: vscode.Uri = vscode.Uri.file(this.bookPath);
|
||||
if (!this._allNotebooks.get(uriToNotebook.fsPath)) {
|
||||
this._allNotebooks.set(uriToNotebook.fsPath, notebookItem);
|
||||
}
|
||||
}
|
||||
return notebookItem;
|
||||
}
|
||||
|
||||
public async readBooks(): Promise<BookTreeItem[]> {
|
||||
for (const contentPath of this._tableOfContentPaths) {
|
||||
let root: string = path.dirname(path.dirname(contentPath));
|
||||
if (this.isNotebook) {
|
||||
return undefined;
|
||||
}
|
||||
|
||||
if (this._tableOfContentsPath) {
|
||||
let root: string = path.dirname(path.dirname(this._tableOfContentsPath));
|
||||
try {
|
||||
let fileContents = await fsPromises.readFile(path.join(root, '_config.yml'), 'utf-8');
|
||||
const config = yaml.safeLoad(fileContents.toString());
|
||||
fileContents = await fsPromises.readFile(contentPath, 'utf-8');
|
||||
fileContents = await fsPromises.readFile(this._tableOfContentsPath, 'utf-8');
|
||||
const tableOfContents: any = yaml.safeLoad(fileContents.toString());
|
||||
let book: BookTreeItem = new BookTreeItem({
|
||||
title: config.title,
|
||||
contentPath: this._tableOfContentsPath,
|
||||
root: root,
|
||||
tableOfContents: { sections: this.parseJupyterSections(tableOfContents) },
|
||||
page: tableOfContents,
|
||||
@@ -114,6 +150,7 @@ export class BookModel implements azdata.nb.NavigationProvider {
|
||||
if (sections[i].external) {
|
||||
let externalLink: BookTreeItem = new BookTreeItem({
|
||||
title: sections[i].title,
|
||||
contentPath: undefined,
|
||||
root: root,
|
||||
tableOfContents: tableOfContents,
|
||||
page: sections[i],
|
||||
@@ -136,6 +173,7 @@ export class BookModel implements azdata.nb.NavigationProvider {
|
||||
if (await fs.pathExists(pathToNotebook)) {
|
||||
let notebook = new BookTreeItem({
|
||||
title: sections[i].title,
|
||||
contentPath: pathToNotebook,
|
||||
root: root,
|
||||
tableOfContents: tableOfContents,
|
||||
page: sections[i],
|
||||
@@ -165,6 +203,7 @@ export class BookModel implements azdata.nb.NavigationProvider {
|
||||
} else if (await fs.pathExists(pathToMarkdown)) {
|
||||
let markdown: BookTreeItem = new BookTreeItem({
|
||||
title: sections[i].title,
|
||||
contentPath: pathToMarkdown,
|
||||
root: root,
|
||||
tableOfContents: tableOfContents,
|
||||
page: sections[i],
|
||||
@@ -208,8 +247,8 @@ export class BookModel implements azdata.nb.NavigationProvider {
|
||||
|
||||
}
|
||||
|
||||
public get tableOfContentPaths(): string[] {
|
||||
return this._tableOfContentPaths;
|
||||
public get tableOfContentsPath(): string {
|
||||
return this._tableOfContentsPath;
|
||||
}
|
||||
|
||||
getNavigation(uri: vscode.Uri): Thenable<azdata.nb.NavigationResult> {
|
||||
|
||||
Reference in New Issue
Block a user