Pinning Notebooks on Notebooks view (#11963)

* initial commit

* added tests

* code cleanup and more tests

* add missed util test

* changes to address comments

* remove pin from resources
This commit is contained in:
Maddy
2020-08-31 08:53:11 -07:00
committed by GitHub
parent b4a3325a21
commit ae830d9e64
15 changed files with 506 additions and 26 deletions

View File

@@ -0,0 +1,89 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the Source EULA. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import * as path from 'path';
import * as vscode from 'vscode';
import * as constants from './../common/constants';
import { BookTreeItem } from './bookTreeItem';
import { getPinnedNotebooks } from '../common/utils';
export interface IBookPinManager {
pinNotebook(notebook: BookTreeItem): boolean;
unpinNotebook(notebook: BookTreeItem): boolean;
}
enum PinBookOperation {
Pin,
Unpin
}
export class BookPinManager implements IBookPinManager {
constructor() {
this.setPinnedSectionContext();
}
setPinnedSectionContext(): void {
if (getPinnedNotebooks().length > 0) {
vscode.commands.executeCommand(constants.BuiltInCommands.SetContext, constants.showPinnedBooksContextKey, true);
} else {
vscode.commands.executeCommand(constants.BuiltInCommands.SetContext, constants.showPinnedBooksContextKey, false);
}
}
isNotebookPinned(notebookPath: string): boolean {
if (getPinnedNotebooks().findIndex(x => x === notebookPath) > -1) {
return true;
}
return false;
}
pinNotebook(notebook: BookTreeItem): boolean {
return this.isNotebookPinned(notebook.book.contentPath) ? false : this.updatePinnedBooks(notebook, PinBookOperation.Pin);
}
unpinNotebook(notebook: BookTreeItem): boolean {
return this.updatePinnedBooks(notebook, PinBookOperation.Unpin);
}
getPinnedBookPathsInConfig(): string[] {
let config: vscode.WorkspaceConfiguration = vscode.workspace.getConfiguration(constants.notebookConfigKey);
let pinnedBookDirectories: string[] = config.get(constants.pinnedBooksConfigKey);
return pinnedBookDirectories;
}
setPinnedBookPathsInConfig(bookPaths: string[]) {
let config: vscode.WorkspaceConfiguration = vscode.workspace.getConfiguration(constants.notebookConfigKey);
let storeInWorspace: boolean = this.hasWorkspaceFolders();
config.update(constants.pinnedBooksConfigKey, bookPaths, storeInWorspace ? false : vscode.ConfigurationTarget.Global);
}
hasWorkspaceFolders(): boolean {
let workspaceFolders = vscode.workspace.workspaceFolders;
return workspaceFolders && workspaceFolders.length > 0;
}
updatePinnedBooks(notebook: BookTreeItem, operation: PinBookOperation) {
let modifiedPinnedBooks = false;
let bookPathToChange: string = notebook.book.contentPath;
let pinnedBooks: string[] = this.getPinnedBookPathsInConfig();
let existingBookIndex = pinnedBooks.map(pinnedBookPath => path.normalize(pinnedBookPath)).indexOf(bookPathToChange);
if (existingBookIndex !== -1 && operation === PinBookOperation.Unpin) {
pinnedBooks.splice(existingBookIndex, 1);
modifiedPinnedBooks = true;
} else if (existingBookIndex === -1 && operation === PinBookOperation.Pin) {
pinnedBooks.push(bookPathToChange);
modifiedPinnedBooks = true;
}
this.setPinnedBookPathsInConfig(pinnedBooks);
this.setPinnedSectionContext();
return modifiedPinnedBooks;
}
}

View File

@@ -8,6 +8,7 @@ import * as path from 'path';
import * as fs from 'fs';
import { IJupyterBookSection, IJupyterBookToc } from '../contracts/content';
import * as loc from '../common/localizedConstants';
import { isBookItemPinned } from '../common/utils';
export enum BookTreeItemType {
Book = 'Book',
@@ -54,6 +55,8 @@ export class BookTreeItem extends vscode.TreeItem {
} else {
this.contextValue = 'savedNotebook';
}
} else {
this.contextValue = book.type === BookTreeItemType.Notebook ? (isBookItemPinned(book.contentPath) ? 'pinnedNotebook' : 'savedNotebook') : 'section';
}
this.setPageVariables();
this.setCommand();

View File

@@ -17,7 +17,8 @@ import { IBookTrustManager, BookTrustManager } from './bookTrustManager';
import * as loc from '../common/localizedConstants';
import * as glob from 'fast-glob';
import { isNullOrUndefined } from 'util';
import { debounce } from '../common/utils';
import { debounce, getPinnedNotebooks } from '../common/utils';
import { IBookPinManager, BookPinManager } from './bookPinManager';
const Content = 'content';
@@ -34,6 +35,7 @@ export class BookTreeViewProvider implements vscode.TreeDataProvider<BookTreeIte
private _initializeDeferred: Deferred<void> = new Deferred<void>();
private _openAsUntitled: boolean;
private _bookTrustManager: IBookTrustManager;
public bookPinManager: IBookPinManager;
private _bookViewer: vscode.TreeView<BookTreeItem>;
public viewId: string;
@@ -44,8 +46,9 @@ export class BookTreeViewProvider implements vscode.TreeDataProvider<BookTreeIte
this._openAsUntitled = openAsUntitled;
this._extensionContext = extensionContext;
this.books = [];
this.initialize(workspaceFolders).catch(e => console.error(e));
this.bookPinManager = new BookPinManager();
this.viewId = view;
this.initialize(workspaceFolders).catch(e => console.error(e));
this.prompter = new CodeAdapter();
this._bookTrustManager = new BookTrustManager(this.books);
@@ -53,13 +56,24 @@ export class BookTreeViewProvider implements vscode.TreeDataProvider<BookTreeIte
}
private async initialize(workspaceFolders: vscode.WorkspaceFolder[]): Promise<void> {
await Promise.all(workspaceFolders.map(async (workspaceFolder) => {
try {
await this.loadNotebooksInFolder(workspaceFolder.uri.fsPath);
} catch {
// no-op, not all workspace folders are going to be valid books
}
}));
if (this.viewId === constants.PINNED_BOOKS_VIEWID) {
await Promise.all(getPinnedNotebooks().map(async (notebookPath) => {
try {
await this.createAndAddBookModel(notebookPath, true);
} catch {
// no-op, not all workspace folders are going to be valid books
}
}));
} else {
await Promise.all(workspaceFolders.map(async (workspaceFolder) => {
try {
await this.loadNotebooksInFolder(workspaceFolder.uri.fsPath);
} catch {
// no-op, not all workspace folders are going to be valid books
}
}));
}
this._initializeDeferred.resolve();
}
@@ -97,6 +111,26 @@ export class BookTreeViewProvider implements vscode.TreeDataProvider<BookTreeIte
}
}
async pinNotebook(bookTreeItem: BookTreeItem): Promise<void> {
let bookPathToUpdate = bookTreeItem.book?.contentPath;
if (bookPathToUpdate) {
let pinStatusChanged = this.bookPinManager.pinNotebook(bookTreeItem);
if (pinStatusChanged) {
bookTreeItem.contextValue = 'pinnedNotebook';
}
}
}
async unpinNotebook(bookTreeItem: BookTreeItem): Promise<void> {
let bookPathToUpdate = bookTreeItem.book?.contentPath;
if (bookPathToUpdate) {
let pinStatusChanged = this.bookPinManager.unpinNotebook(bookTreeItem);
if (pinStatusChanged) {
bookTreeItem.contextValue = 'savedNotebook';
}
}
}
async openBook(bookPath: string, urlToOpen?: string, showPreview?: boolean, isNotebook?: boolean): Promise<void> {
try {
// Convert path to posix style for easier comparisons
@@ -132,6 +166,20 @@ export class BookTreeViewProvider implements vscode.TreeDataProvider<BookTreeIte
}
}
async addNotebookToPinnedView(bookItem: BookTreeItem): Promise<void> {
let notebookPath: string = bookItem.book.contentPath;
if (notebookPath) {
await this.createAndAddBookModel(notebookPath, true);
}
}
async removeNotebookFromPinnedView(bookItem: BookTreeItem): Promise<void> {
let notebookPath: string = bookItem.book.contentPath;
if (notebookPath) {
await this.closeBook(bookItem);
}
}
@debounce(1500)
async fireBookRefresh(book: BookModel): Promise<void> {
await book.initializeContents().then(() => {
@@ -169,21 +217,23 @@ export class BookTreeViewProvider implements vscode.TreeDataProvider<BookTreeIte
* @param bookPath The path to the book folder to create the model for
*/
private async createAndAddBookModel(bookPath: string, isNotebook: boolean): Promise<void> {
const book: BookModel = new BookModel(bookPath, this._openAsUntitled, isNotebook, this._extensionContext);
await book.initializeContents();
this.books.push(book);
if (!this.currentBook) {
this.currentBook = book;
}
this._bookViewer = vscode.window.createTreeView(this.viewId, { showCollapseAll: true, treeDataProvider: this });
this._bookViewer.onDidChangeVisibility(e => {
let openDocument = azdata.nb.activeNotebookEditor;
let notebookPath = openDocument?.document.uri;
// call reveal only once on the correct view
if (e.visible && ((!this._openAsUntitled && notebookPath?.scheme !== 'untitled') || (this._openAsUntitled && notebookPath?.scheme === 'untitled'))) {
this.revealActiveDocumentInViewlet();
if (!this.books.find(x => x.bookPath === bookPath)) {
const book: BookModel = new BookModel(bookPath, this._openAsUntitled, isNotebook, this._extensionContext);
await book.initializeContents();
this.books.push(book);
if (!this.currentBook) {
this.currentBook = book;
}
});
this._bookViewer = vscode.window.createTreeView(this.viewId, { showCollapseAll: true, treeDataProvider: this });
this._bookViewer.onDidChangeVisibility(e => {
let openDocument = azdata.nb.activeNotebookEditor;
let notebookPath = openDocument?.document.uri;
// call reveal only once on the correct view
if (e.visible && ((!this._openAsUntitled && notebookPath?.scheme !== 'untitled') || (this._openAsUntitled && notebookPath?.scheme === 'untitled'))) {
this.revealActiveDocumentInViewlet();
}
});
}
}
async showPreviewFile(urlToOpen?: string): Promise<void> {