mirror of
https://github.com/ckaczor/azuredatastudio.git
synced 2026-02-16 18:46:40 -05:00
Fix debounce issue when book toc is updated (#14392)
* pass function to debounce * remove debounce decorator and move watch methods to bookmodel * Move the vscode tree change data event to book model * address pr comments * fix book tests
This commit is contained in:
@@ -13,7 +13,7 @@ import * as fs from 'fs-extra';
|
|||||||
import * as loc from '../common/localizedConstants';
|
import * as loc from '../common/localizedConstants';
|
||||||
import { IJupyterBookToc, JupyterBookSection } from '../contracts/content';
|
import { IJupyterBookToc, JupyterBookSection } from '../contracts/content';
|
||||||
import { convertFrom, getContentPath, BookVersion } from './bookVersionHandler';
|
import { convertFrom, getContentPath, BookVersion } from './bookVersionHandler';
|
||||||
|
import { debounce } from '../common/utils';
|
||||||
const fsPromises = fileServices.promises;
|
const fsPromises = fileServices.promises;
|
||||||
const content = 'content';
|
const content = 'content';
|
||||||
|
|
||||||
@@ -32,43 +32,62 @@ export class BookModel {
|
|||||||
public readonly openAsUntitled: boolean,
|
public readonly openAsUntitled: boolean,
|
||||||
public readonly isNotebook: boolean,
|
public readonly isNotebook: boolean,
|
||||||
private _extensionContext: vscode.ExtensionContext,
|
private _extensionContext: vscode.ExtensionContext,
|
||||||
|
private _onDidChangeTreeData: vscode.EventEmitter<BookTreeItem | undefined>,
|
||||||
public readonly notebookRootPath?: string) {
|
public readonly notebookRootPath?: string) {
|
||||||
this._bookItems = [];
|
this._bookItems = [];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public unwatchTOC(): void {
|
||||||
|
fs.unwatchFile(this.tableOfContentsPath);
|
||||||
|
}
|
||||||
|
|
||||||
|
public watchTOC(): void {
|
||||||
|
fs.watchFile(this.tableOfContentsPath, async (curr, prev) => {
|
||||||
|
if (curr.mtime > prev.mtime) {
|
||||||
|
this.reinitializeContents();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
@debounce(1500)
|
||||||
|
public async reinitializeContents(): Promise<void> {
|
||||||
|
await this.initializeContents();
|
||||||
|
this._onDidChangeTreeData.fire(undefined);
|
||||||
|
}
|
||||||
|
|
||||||
public async initializeContents(): Promise<void> {
|
public async initializeContents(): Promise<void> {
|
||||||
this._bookItems = [];
|
this._bookItems = [];
|
||||||
this._allNotebooks = new Map<string, BookTreeItem>();
|
this._allNotebooks = new Map<string, BookTreeItem>();
|
||||||
if (this.isNotebook) {
|
if (this.isNotebook) {
|
||||||
this.readNotebook();
|
this.readNotebook();
|
||||||
} else {
|
} else {
|
||||||
await this.readBookStructure(this.bookPath);
|
await this.readBookStructure();
|
||||||
await this.loadTableOfContentFiles();
|
await this.loadTableOfContentFiles();
|
||||||
await this.readBooks();
|
await this.readBooks();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public async readBookStructure(folderPath: string): Promise<void> {
|
public async readBookStructure(): Promise<void> {
|
||||||
// check book structure to determine version
|
// check book structure to determine version
|
||||||
let isOlderVersion: boolean;
|
let isOlderVersion: boolean;
|
||||||
this._configPath = path.posix.join(folderPath, '_config.yml');
|
this._configPath = path.posix.join(this.bookPath, '_config.yml');
|
||||||
try {
|
try {
|
||||||
isOlderVersion = (await fs.stat(path.posix.join(folderPath, '_data'))).isDirectory() && (await fs.stat(path.posix.join(folderPath, content))).isDirectory();
|
isOlderVersion = (await fs.stat(path.posix.join(this.bookPath, '_data'))).isDirectory() && (await fs.stat(path.posix.join(this.bookPath, content))).isDirectory();
|
||||||
} catch {
|
} catch {
|
||||||
isOlderVersion = false;
|
isOlderVersion = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (isOlderVersion) {
|
if (isOlderVersion) {
|
||||||
let isTocFile = (await fs.stat(path.posix.join(folderPath, '_data', 'toc.yml'))).isFile();
|
let isTocFile = (await fs.stat(path.posix.join(this.bookPath, '_data', 'toc.yml'))).isFile();
|
||||||
if (isTocFile) {
|
if (isTocFile) {
|
||||||
this._tableOfContentsPath = path.posix.join(folderPath, '_data', 'toc.yml');
|
this._tableOfContentsPath = path.posix.join(this.bookPath, '_data', 'toc.yml');
|
||||||
}
|
}
|
||||||
this._bookVersion = BookVersion.v1;
|
this._bookVersion = BookVersion.v1;
|
||||||
this._contentFolderPath = path.posix.join(folderPath, content, '');
|
this._contentFolderPath = path.posix.join(this.bookPath, content, '');
|
||||||
this._rootPath = path.dirname(path.dirname(this._tableOfContentsPath));
|
this._rootPath = path.dirname(path.dirname(this._tableOfContentsPath));
|
||||||
} else {
|
} else {
|
||||||
this._contentFolderPath = folderPath;
|
this._contentFolderPath = this.bookPath;
|
||||||
this._tableOfContentsPath = path.posix.join(folderPath, '_toc.yml');
|
this._tableOfContentsPath = path.posix.join(this.bookPath, '_toc.yml');
|
||||||
this._rootPath = path.dirname(this._tableOfContentsPath);
|
this._rootPath = path.dirname(this._tableOfContentsPath);
|
||||||
this._bookVersion = BookVersion.v2;
|
this._bookVersion = BookVersion.v2;
|
||||||
}
|
}
|
||||||
@@ -89,6 +108,7 @@ export class BookModel {
|
|||||||
|
|
||||||
if (await fs.pathExists(this._tableOfContentsPath)) {
|
if (await fs.pathExists(this._tableOfContentsPath)) {
|
||||||
vscode.commands.executeCommand('setContext', 'bookOpened', true);
|
vscode.commands.executeCommand('setContext', 'bookOpened', true);
|
||||||
|
this.watchTOC();
|
||||||
} else {
|
} else {
|
||||||
this._errorMessage = loc.missingTocError;
|
this._errorMessage = loc.missingTocError;
|
||||||
throw new Error(loc.missingTocError);
|
throw new Error(loc.missingTocError);
|
||||||
|
|||||||
@@ -16,7 +16,7 @@ import { Deferred } from '../common/promise';
|
|||||||
import { IBookTrustManager, BookTrustManager } from './bookTrustManager';
|
import { IBookTrustManager, BookTrustManager } from './bookTrustManager';
|
||||||
import * as loc from '../common/localizedConstants';
|
import * as loc from '../common/localizedConstants';
|
||||||
import * as glob from 'fast-glob';
|
import * as glob from 'fast-glob';
|
||||||
import { debounce, getPinnedNotebooks, confirmReplace } from '../common/utils';
|
import { getPinnedNotebooks, confirmReplace } from '../common/utils';
|
||||||
import { IBookPinManager, BookPinManager } from './bookPinManager';
|
import { IBookPinManager, BookPinManager } from './bookPinManager';
|
||||||
import { BookTocManager, IBookTocManager, quickPickResults } from './bookTocManager';
|
import { BookTocManager, IBookTocManager, quickPickResults } from './bookTocManager';
|
||||||
import { CreateBookDialog } from '../dialog/createBookDialog';
|
import { CreateBookDialog } from '../dialog/createBookDialog';
|
||||||
@@ -92,14 +92,6 @@ export class BookTreeViewProvider implements vscode.TreeDataProvider<BookTreeIte
|
|||||||
this._extensionContext.globalState.update(constants.visitedNotebooksMementoKey, value);
|
this._extensionContext.globalState.update(constants.visitedNotebooksMementoKey, value);
|
||||||
}
|
}
|
||||||
|
|
||||||
setFileWatcher(book: BookModel): void {
|
|
||||||
fs.watchFile(book.tableOfContentsPath, async (curr, prev) => {
|
|
||||||
if (curr.mtime > prev.mtime) {
|
|
||||||
await this.initializeBookContents(book);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
trustBook(bookTreeItem?: BookTreeItem): void {
|
trustBook(bookTreeItem?: BookTreeItem): void {
|
||||||
let bookPathToTrust: string = bookTreeItem ? bookTreeItem.root : this.currentBook?.bookPath;
|
let bookPathToTrust: string = bookTreeItem ? bookTreeItem.root : this.currentBook?.bookPath;
|
||||||
if (bookPathToTrust) {
|
if (bookPathToTrust) {
|
||||||
@@ -216,7 +208,7 @@ export class BookTreeViewProvider implements vscode.TreeDataProvider<BookTreeIte
|
|||||||
this.bookTocManager = new BookTocManager(targetBook, sourceBook);
|
this.bookTocManager = new BookTocManager(targetBook, sourceBook);
|
||||||
// remove watch on toc file from source book.
|
// remove watch on toc file from source book.
|
||||||
if (sourceBook) {
|
if (sourceBook) {
|
||||||
fs.unwatchFile(sourceBook.tableOfContentsPath);
|
sourceBook.unwatchTOC();
|
||||||
}
|
}
|
||||||
try {
|
try {
|
||||||
await this.bookTocManager.updateBook(movingElement, updateBook, targetSection);
|
await this.bookTocManager.updateBook(movingElement, updateBook, targetSection);
|
||||||
@@ -225,16 +217,14 @@ export class BookTreeViewProvider implements vscode.TreeDataProvider<BookTreeIte
|
|||||||
vscode.window.showErrorMessage(loc.editBookError(updateBook.book.contentPath, e instanceof Error ? e.message : e));
|
vscode.window.showErrorMessage(loc.editBookError(updateBook.book.contentPath, e instanceof Error ? e.message : e));
|
||||||
} finally {
|
} finally {
|
||||||
try {
|
try {
|
||||||
await targetBook.initializeContents();
|
await targetBook.reinitializeContents();
|
||||||
} finally {
|
} finally {
|
||||||
if (sourceBook && sourceBook.bookPath !== targetBook.bookPath) {
|
if (sourceBook && sourceBook.bookPath !== targetBook.bookPath) {
|
||||||
// refresh source book model to pick up latest changes
|
// refresh source book model to pick up latest changes
|
||||||
await sourceBook.initializeContents();
|
await sourceBook.reinitializeContents();
|
||||||
}
|
}
|
||||||
this._onDidChangeTreeData.fire(undefined);
|
|
||||||
// even if it fails, we still need to watch the toc file again.
|
|
||||||
if (sourceBook) {
|
if (sourceBook) {
|
||||||
this.setFileWatcher(sourceBook);
|
sourceBook.watchTOC();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -261,17 +251,6 @@ export class BookTreeViewProvider implements vscode.TreeDataProvider<BookTreeIte
|
|||||||
}
|
}
|
||||||
|
|
||||||
TelemetryReporter.createActionEvent(BookTelemetryView, NbTelemetryActions.OpenBook).send();
|
TelemetryReporter.createActionEvent(BookTelemetryView, NbTelemetryActions.OpenBook).send();
|
||||||
// add file watcher on toc file.
|
|
||||||
if (!isNotebook) {
|
|
||||||
fs.watchFile(this.currentBook.tableOfContentsPath, async (curr, prev) => {
|
|
||||||
if (curr.mtime > prev.mtime) {
|
|
||||||
let book = this.books.find(book => book.bookPath === bookPath);
|
|
||||||
if (book) {
|
|
||||||
await this.initializeBookContents(book);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
// if there is an error remove book from context
|
// if there is an error remove book from context
|
||||||
const index = this.books.findIndex(book => book.bookPath === bookPath);
|
const index = this.books.findIndex(book => book.bookPath === bookPath);
|
||||||
@@ -297,13 +276,6 @@ export class BookTreeViewProvider implements vscode.TreeDataProvider<BookTreeIte
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@debounce(1500)
|
|
||||||
async initializeBookContents(book: BookModel): Promise<void> {
|
|
||||||
await book.initializeContents().then(() => {
|
|
||||||
this._onDidChangeTreeData.fire(undefined);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
async closeBook(book: BookTreeItem): Promise<void> {
|
async closeBook(book: BookTreeItem): Promise<void> {
|
||||||
// remove book from the saved books
|
// remove book from the saved books
|
||||||
let deletedBook: BookModel;
|
let deletedBook: BookModel;
|
||||||
@@ -324,7 +296,7 @@ export class BookTreeViewProvider implements vscode.TreeDataProvider<BookTreeIte
|
|||||||
} finally {
|
} finally {
|
||||||
// remove watch on toc file.
|
// remove watch on toc file.
|
||||||
if (deletedBook && !deletedBook.isNotebook) {
|
if (deletedBook && !deletedBook.isNotebook) {
|
||||||
fs.unwatchFile(deletedBook.tableOfContentsPath);
|
deletedBook.unwatchTOC();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -338,7 +310,7 @@ export class BookTreeViewProvider implements vscode.TreeDataProvider<BookTreeIte
|
|||||||
*/
|
*/
|
||||||
private async createAndAddBookModel(bookPath: string, isNotebook: boolean, notebookBookRoot?: string): Promise<void> {
|
private async createAndAddBookModel(bookPath: string, isNotebook: boolean, notebookBookRoot?: string): Promise<void> {
|
||||||
if (!this.books.find(x => x.bookPath === bookPath)) {
|
if (!this.books.find(x => x.bookPath === bookPath)) {
|
||||||
const book: BookModel = new BookModel(bookPath, this._openAsUntitled, isNotebook, this._extensionContext, notebookBookRoot);
|
const book: BookModel = new BookModel(bookPath, this._openAsUntitled, isNotebook, this._extensionContext, this._onDidChangeTreeData, notebookBookRoot);
|
||||||
await book.initializeContents();
|
await book.initializeContents();
|
||||||
this.books.push(book);
|
this.books.push(book);
|
||||||
if (!this.currentBook) {
|
if (!this.currentBook) {
|
||||||
|
|||||||
@@ -485,14 +485,14 @@ describe('BooksTreeViewTests', function () {
|
|||||||
|
|
||||||
if (run.it === 'v1') {
|
if (run.it === 'v1') {
|
||||||
it('should ignore toc.yml files not in _data folder', async () => {
|
it('should ignore toc.yml files not in _data folder', async () => {
|
||||||
await bookTreeViewProvider.currentBook.readBookStructure(rootFolderPath);
|
await bookTreeViewProvider.currentBook.readBookStructure();
|
||||||
await bookTreeViewProvider.currentBook.loadTableOfContentFiles();
|
await bookTreeViewProvider.currentBook.loadTableOfContentFiles();
|
||||||
let path = bookTreeViewProvider.currentBook.tableOfContentsPath;
|
let path = bookTreeViewProvider.currentBook.tableOfContentsPath;
|
||||||
should(vscode.Uri.file(path).fsPath).equal(vscode.Uri.file(run.folderPaths.tableOfContentsFile).fsPath);
|
should(vscode.Uri.file(path).fsPath).equal(vscode.Uri.file(run.folderPaths.tableOfContentsFile).fsPath);
|
||||||
});
|
});
|
||||||
} else if (run.it === 'v2') {
|
} else if (run.it === 'v2') {
|
||||||
it('should ignore toc.yml files not under the root book folder', async () => {
|
it('should ignore toc.yml files not under the root book folder', async () => {
|
||||||
await bookTreeViewProvider.currentBook.readBookStructure(rootFolderPath);
|
await bookTreeViewProvider.currentBook.readBookStructure();
|
||||||
await bookTreeViewProvider.currentBook.loadTableOfContentFiles();
|
await bookTreeViewProvider.currentBook.loadTableOfContentFiles();
|
||||||
let path = bookTreeViewProvider.currentBook.tableOfContentsPath;
|
let path = bookTreeViewProvider.currentBook.tableOfContentsPath;
|
||||||
should(vscode.Uri.file(path).fsPath).equal(vscode.Uri.file(run.folderPaths.tableOfContentsFile).fsPath);
|
should(vscode.Uri.file(path).fsPath).equal(vscode.Uri.file(run.folderPaths.tableOfContentsFile).fsPath);
|
||||||
|
|||||||
@@ -20,7 +20,6 @@ import { BookTreeViewProvider } from '../../book/bookTreeView';
|
|||||||
import { NavigationProviders } from '../../common/constants';
|
import { NavigationProviders } from '../../common/constants';
|
||||||
import * as loc from '../../common/localizedConstants';
|
import * as loc from '../../common/localizedConstants';
|
||||||
|
|
||||||
|
|
||||||
export function equalTOC(actualToc: IJupyterBookSectionV2[], expectedToc: IJupyterBookSectionV2[]): boolean {
|
export function equalTOC(actualToc: IJupyterBookSectionV2[], expectedToc: IJupyterBookSectionV2[]): boolean {
|
||||||
for (let [i, section] of actualToc.entries()) {
|
for (let [i, section] of actualToc.entries()) {
|
||||||
if (section.title !== expectedToc[i].title || section.file !== expectedToc[i].file) {
|
if (section.title !== expectedToc[i].title || section.file !== expectedToc[i].file) {
|
||||||
@@ -475,8 +474,8 @@ describe('BookTocManagerTests', function () {
|
|||||||
|
|
||||||
const mockExtensionContext = new MockExtensionContext();
|
const mockExtensionContext = new MockExtensionContext();
|
||||||
|
|
||||||
sourceBookModel = new BookModel(run.sourceBook.rootBookFolderPath, false, false, mockExtensionContext);
|
sourceBookModel = new BookModel(run.sourceBook.rootBookFolderPath, false, false, mockExtensionContext, undefined);
|
||||||
targetBookModel = new BookModel(run.targetBook.rootBookFolderPath, false, false, mockExtensionContext);
|
targetBookModel = new BookModel(run.targetBook.rootBookFolderPath, false, false, mockExtensionContext, undefined);
|
||||||
// create book model mock objects
|
// create book model mock objects
|
||||||
sinon.stub(sourceBookModel, 'bookItems').value([sectionA]);
|
sinon.stub(sourceBookModel, 'bookItems').value([sectionA]);
|
||||||
sinon.stub(targetBookModel, 'bookItems').value([targetBook]);
|
sinon.stub(targetBookModel, 'bookItems').value([targetBook]);
|
||||||
|
|||||||
Reference in New Issue
Block a user