Introduce Trust Book in Book Viewlet (#9414)

This commit is contained in:
Jorge Berumen
2020-03-13 09:11:38 -07:00
committed by GitHub
parent 744e655dd3
commit d5fdec5699
21 changed files with 590 additions and 10 deletions

View File

@@ -41,6 +41,14 @@
"type": "number", "type": "number",
"default": 5000, "default": 5000,
"description": "%notebook.maxTableRows.description%" "description": "%notebook.maxTableRows.description%"
},
"notebook.trustedBooks":{
"type": "array",
"default": [],
"description": "%notebook.trustedBooks.description%",
"items": {
"type": "string"
}
} }
} }
}, },
@@ -159,6 +167,15 @@
"light": "resources/light/save.svg" "light": "resources/light/save.svg"
} }
}, },
{
"command": "notebook.command.trustBook",
"title": "%title.trustBook%",
"category": "%books-preview-category%",
"icon": {
"dark": "resources/dark/trust_inverse.svg",
"light": "resources/light/trust.svg"
}
},
{ {
"command": "notebook.command.searchBook", "command": "notebook.command.searchBook",
"title": "%title.searchJupyterBook%", "title": "%title.searchJupyterBook%",
@@ -292,6 +309,10 @@
"command": "notebook.command.searchUntitledBook", "command": "notebook.command.searchUntitledBook",
"when": "false" "when": "false"
}, },
{
"command": "notebook.command.trustBook",
"when": "view == bookTreeView && viewItem == savedBook"
},
{ {
"command": "notebook.command.closeBook", "command": "notebook.command.closeBook",
"when": "false" "when": "false"
@@ -326,6 +347,11 @@
} }
], ],
"view/item/context": [ "view/item/context": [
{
"command": "notebook.command.trustBook",
"when": "view == bookTreeView && viewItem == savedBook",
"group": "inline"
},
{ {
"command": "notebook.command.searchBook", "command": "notebook.command.searchBook",
"when": "view == bookTreeView && viewItem == savedBook || viewItem == section", "when": "view == bookTreeView && viewItem == savedBook || viewItem == section",

View File

@@ -6,6 +6,7 @@
"notebook.useExistingPython.description": "Local path to a preexisting python installation used by Notebooks.", "notebook.useExistingPython.description": "Local path to a preexisting python installation used by Notebooks.",
"notebook.overrideEditorTheming.description": "Override editor default settings in the Notebook editor. Settings include background color, current line color and border", "notebook.overrideEditorTheming.description": "Override editor default settings in the Notebook editor. Settings include background color, current line color and border",
"notebook.maxTableRows.description": "Maximum number of rows returned per table in the Notebook editor", "notebook.maxTableRows.description": "Maximum number of rows returned per table in the Notebook editor",
"notebook.trustedBooks.description": "Notebooks contained in these books will automatically be trusted.",
"notebook.maxBookSearchDepth.description": "Maximum depth of subdirectories to search for Books (Enter 0 for infinite)", "notebook.maxBookSearchDepth.description": "Maximum depth of subdirectories to search for Books (Enter 0 for infinite)",
"notebook.command.new": "New Notebook", "notebook.command.new": "New Notebook",
"notebook.command.open": "Open Notebook", "notebook.command.open": "Open Notebook",
@@ -30,6 +31,7 @@
"title.SQL19PreviewBook": "SQL Server 2019 Guide", "title.SQL19PreviewBook": "SQL Server 2019 Guide",
"books-preview-category": "Jupyter Books", "books-preview-category": "Jupyter Books",
"title.saveJupyterBook": "Save Book", "title.saveJupyterBook": "Save Book",
"title.trustBook": "Trust Book",
"title.searchJupyterBook": "Search Book", "title.searchJupyterBook": "Search Book",
"title.SavedBooks": "Saved Books", "title.SavedBooks": "Saved Books",
"title.UnsavedBooks": "Unsaved Books", "title.UnsavedBooks": "Unsaved Books",

View File

@@ -0,0 +1,3 @@
<svg width="16" height="17" viewBox="0 0 16 17" fill="none" xmlns="http://www.w3.org/2000/svg">
<path fill-rule="evenodd" clip-rule="evenodd" d="M8.26991 1.02742C8.02998 0.993516 7.777 0.976562 7.51099 0.976562C6.97897 0.976562 6.49127 1.04567 6.04791 1.1839C5.60456 1.32212 5.1612 1.53467 4.71784 1.82155C4.34229 2.06148 3.96674 2.25708 3.59119 2.40835C3.21564 2.55961 2.83488 2.67697 2.4489 2.76042C2.06292 2.84388 1.6665 2.90125 1.25966 2.93255C0.852811 2.96385 0.432925 2.97949 0 2.97949V6.98536C0 7.74689 0.0951916 8.4693 0.285574 9.15259C0.475954 9.83589 0.739362 10.484 1.07579 11.0968C1.41223 11.7097 1.80994 12.2874 2.26895 12.8299C2.72795 13.3723 3.22607 13.8822 3.76332 14.3594C4.30056 14.8367 4.8678 15.2814 5.46503 15.6934C6.06226 16.1055 6.6634 16.4862 7.26845 16.8357L7.51099 16.9765L7.75353 16.8357C8.35859 16.4862 8.95972 16.1055 9.55695 15.6934C10.1542 15.2814 10.7214 14.8367 11.2587 14.3594C11.7959 13.8822 12.294 13.3723 12.753 12.8299C13.212 12.2874 13.6098 11.7097 13.9462 11.0968C14.2826 10.484 14.546 9.83589 14.7364 9.15259C14.9268 8.4693 15.022 7.74689 15.022 6.98536V2.97949C14.5891 2.97949 14.1692 2.96385 13.7623 2.93255C13.3555 2.90125 12.9591 2.84388 12.5731 2.76042C12.1871 2.67697 11.8063 2.55961 11.4308 2.40835C11.0552 2.25708 10.6797 2.06149 10.3041 1.82155C10.0799 1.68072 9.85817 1.55684 9.6391 1.44991C9.42003 1.34299 9.19705 1.25431 8.97016 1.1839C8.74326 1.11348 8.50985 1.06132 8.26991 1.02742ZM11.7868 3.58976C12.4988 3.7984 13.2433 3.92358 14.0205 3.96531L14.0205 6.98536C14.0205 7.64778 13.9331 8.28413 13.7584 8.8944C13.5837 9.50467 13.345 10.0863 13.0425 10.6391C12.74 11.192 12.3801 11.7149 11.9628 12.2078C11.5455 12.7008 11.097 13.1637 10.6171 13.5966C10.1372 14.0295 9.63259 14.4312 9.10317 14.8015C8.57375 15.1718 8.04302 15.5109 7.51099 15.8186C6.97897 15.5109 6.44824 15.1718 5.91882 14.8015C5.3894 14.4312 4.88476 14.0295 4.40489 13.5966C3.92502 13.1637 3.47645 12.7008 3.05917 12.2078C2.64189 11.7149 2.28199 11.192 1.97946 10.6391C1.67694 10.0863 1.43831 9.50467 1.26357 8.8944C1.08884 8.28413 1.00147 7.64778 1.00147 6.98536V3.96531C1.77865 3.92358 2.52323 3.7984 3.23521 3.58976C3.94719 3.38112 4.63178 3.06556 5.28899 2.64306C5.64368 2.41356 5.99706 2.24535 6.34914 2.13842C6.70122 2.03149 7.0885 1.97803 7.51099 1.97803C7.93348 1.97803 8.32077 2.03149 8.67285 2.13842C9.02493 2.24535 9.3783 2.41356 9.73299 2.64306C10.3902 3.06556 11.0748 3.38112 11.7868 3.58976Z" fill="white"/>
</svg>

After

Width:  |  Height:  |  Size: 2.4 KiB

View File

@@ -0,0 +1,3 @@
<svg width="16" height="17" viewBox="0 0 16 17" fill="none" xmlns="http://www.w3.org/2000/svg">
<path fill-rule="evenodd" clip-rule="evenodd" d="M8.26991 1.02742C8.02998 0.993516 7.777 0.976562 7.51099 0.976562C6.97897 0.976562 6.49127 1.04567 6.04791 1.1839C5.60456 1.32212 5.1612 1.53467 4.71784 1.82155C4.34229 2.06148 3.96674 2.25708 3.59119 2.40835C3.21564 2.55961 2.83488 2.67697 2.4489 2.76042C2.06292 2.84388 1.6665 2.90125 1.25966 2.93255C0.852811 2.96385 0.432925 2.97949 0 2.97949V6.98536C0 7.74689 0.0951916 8.4693 0.285574 9.15259C0.475954 9.83589 0.739362 10.484 1.07579 11.0968C1.41223 11.7097 1.80994 12.2874 2.26895 12.8299C2.72795 13.3723 3.22607 13.8822 3.76332 14.3594C4.30056 14.8367 4.8678 15.2814 5.46503 15.6934C6.06226 16.1055 6.6634 16.4862 7.26845 16.8357L7.51099 16.9765L7.75353 16.8357C8.35859 16.4862 8.95972 16.1055 9.55695 15.6934C10.1542 15.2814 10.7214 14.8367 11.2587 14.3594C11.7959 13.8822 12.294 13.3723 12.753 12.8299C13.212 12.2874 13.6098 11.7097 13.9462 11.0968C14.2826 10.484 14.546 9.83589 14.7364 9.15259C14.9268 8.4693 15.022 7.74689 15.022 6.98536V2.97949C14.5891 2.97949 14.1692 2.96385 13.7623 2.93255C13.3555 2.90125 12.9591 2.84388 12.5731 2.76042C12.1871 2.67697 11.8063 2.55961 11.4308 2.40835C11.0552 2.25708 10.6797 2.06149 10.3041 1.82155C10.0799 1.68072 9.85817 1.55684 9.6391 1.44991C9.42003 1.34299 9.19705 1.25431 8.97016 1.1839C8.74326 1.11348 8.50985 1.06132 8.26991 1.02742ZM11.7868 3.58976C12.4988 3.7984 13.2433 3.92358 14.0205 3.96531L14.0205 6.98536C14.0205 7.64778 13.9331 8.28413 13.7584 8.8944C13.5837 9.50467 13.345 10.0863 13.0425 10.6391C12.74 11.192 12.3801 11.7149 11.9628 12.2078C11.5455 12.7008 11.097 13.1637 10.6171 13.5966C10.1372 14.0295 9.63259 14.4312 9.10317 14.8015C8.57375 15.1718 8.04302 15.5109 7.51099 15.8186C6.97897 15.5109 6.44824 15.1718 5.91882 14.8015C5.3894 14.4312 4.88476 14.0295 4.40489 13.5966C3.92502 13.1637 3.47645 12.7008 3.05917 12.2078C2.64189 11.7149 2.28199 11.192 1.97946 10.6391C1.67694 10.0863 1.43831 9.50467 1.26357 8.8944C1.08884 8.28413 1.00147 7.64778 1.00147 6.98536V3.96531C1.77865 3.92358 2.52323 3.7984 3.23521 3.58976C3.94719 3.38112 4.63178 3.06556 5.28899 2.64306C5.64368 2.41356 5.99706 2.24535 6.34914 2.13842C6.70122 2.03149 7.0885 1.97803 7.51099 1.97803C7.93348 1.97803 8.32077 2.03149 8.67285 2.13842C9.02493 2.24535 9.3783 2.41356 9.73299 2.64306C10.3902 3.06556 11.0748 3.38112 11.7868 3.58976Z" fill="black"/>
</svg>

After

Width:  |  Height:  |  Size: 2.4 KiB

View File

@@ -7,19 +7,20 @@ import * as azdata from 'azdata';
import * as vscode from 'vscode'; import * as vscode from 'vscode';
import * as path from 'path'; import * as path from 'path';
import * as fs from 'fs-extra'; import * as fs from 'fs-extra';
import * as constants from '../common/constants';
import * as fsw from 'fs'; import * as fsw from 'fs';
import { IPrompter, QuestionTypes, IQuestion } from '../prompts/question'; import { IPrompter, QuestionTypes, IQuestion } from '../prompts/question';
import CodeAdapter from '../prompts/adapter'; import CodeAdapter from '../prompts/adapter';
import { BookTreeItem } from './bookTreeItem'; import { BookTreeItem } from './bookTreeItem';
import { BookModel } from './bookModel'; import { BookModel } from './bookModel';
import { Deferred } from '../common/promise'; import { Deferred } from '../common/promise';
import { IBookTrustManager, BookTrustManager } from './bookTrustManager';
import * as loc from '../common/localizedConstants'; import * as loc from '../common/localizedConstants';
import { ApiWrapper } from '../common/apiWrapper'; import { ApiWrapper } from '../common/apiWrapper';
const Content = 'content'; const Content = 'content';
export class BookTreeViewProvider implements vscode.TreeDataProvider<BookTreeItem> { export class BookTreeViewProvider implements vscode.TreeDataProvider<BookTreeItem> {
private _onDidChangeTreeData: vscode.EventEmitter<BookTreeItem | undefined> = new vscode.EventEmitter<BookTreeItem | undefined>(); private _onDidChangeTreeData: vscode.EventEmitter<BookTreeItem | undefined> = new vscode.EventEmitter<BookTreeItem | undefined>();
readonly onDidChangeTreeData: vscode.Event<BookTreeItem | undefined> = this._onDidChangeTreeData.event; readonly onDidChangeTreeData: vscode.Event<BookTreeItem | undefined> = this._onDidChangeTreeData.event;
private _throttleTimer: any; private _throttleTimer: any;
@@ -27,21 +28,22 @@ export class BookTreeViewProvider implements vscode.TreeDataProvider<BookTreeIte
private _extensionContext: vscode.ExtensionContext; private _extensionContext: vscode.ExtensionContext;
private prompter: IPrompter; private prompter: IPrompter;
private _initializeDeferred: Deferred<void> = new Deferred<void>(); private _initializeDeferred: Deferred<void> = new Deferred<void>();
private _bookViewer: vscode.TreeView<BookTreeItem>;
private _openAsUntitled: boolean; private _openAsUntitled: boolean;
private _apiWrapper: ApiWrapper; private _bookTrustManager: IBookTrustManager;
private _bookViewer: vscode.TreeView<BookTreeItem>;
public viewId: string; public viewId: string;
public books: BookModel[]; public books: BookModel[];
public currentBook: BookModel; public currentBook: BookModel;
constructor(apiWrapper: ApiWrapper, workspaceFolders: vscode.WorkspaceFolder[], extensionContext: vscode.ExtensionContext, openAsUntitled: boolean, view: string) { constructor(private _apiWrapper: ApiWrapper, workspaceFolders: vscode.WorkspaceFolder[], extensionContext: vscode.ExtensionContext, openAsUntitled: boolean, view: string) {
this._openAsUntitled = openAsUntitled; this._openAsUntitled = openAsUntitled;
this._extensionContext = extensionContext; this._extensionContext = extensionContext;
this.books = []; this.books = [];
this.initialize(workspaceFolders).catch(e => console.error(e)); this.initialize(workspaceFolders).catch(e => console.error(e));
this.viewId = view; this.viewId = view;
this.prompter = new CodeAdapter(); this.prompter = new CodeAdapter();
this._apiWrapper = apiWrapper ? apiWrapper : new ApiWrapper(); this._bookTrustManager = new BookTrustManager(this.books, _apiWrapper);
} }
private async initialize(workspaceFolders: vscode.WorkspaceFolder[]): Promise<void> { private async initialize(workspaceFolders: vscode.WorkspaceFolder[]): Promise<void> {
@@ -60,6 +62,28 @@ export class BookTreeViewProvider implements vscode.TreeDataProvider<BookTreeIte
return this._initializeDeferred.promise; return this._initializeDeferred.promise;
} }
get _visitedNotebooks(): string[] {
return this._extensionContext.globalState.get(constants.visitedNotebooksMementoKey, []);
}
set _visitedNotebooks(value: string[]) {
this._extensionContext.globalState.update(constants.visitedNotebooksMementoKey, value);
}
trustBook(bookTreeItem?: BookTreeItem): void {
let bookPathToTrust = bookTreeItem ? bookTreeItem.root : this.currentBook?.bookPath;
if (bookPathToTrust) {
let trustChanged = this._bookTrustManager.setBookAsTrusted(bookPathToTrust);
if (trustChanged) {
this._apiWrapper.showInfoMessage(loc.msgBookTrusted);
} else {
this._apiWrapper.showInfoMessage(loc.msgBookAlreadyTrusted);
}
}
}
async openBook(bookPath: string, urlToOpen?: string): Promise<void> { async openBook(bookPath: string, urlToOpen?: string): Promise<void> {
try { try {
let books: BookModel[] = this.books.filter(book => book.bookPath === bookPath) || []; let books: BookModel[] = this.books.filter(book => book.bookPath === bookPath) || [];
@@ -157,6 +181,19 @@ export class BookTreeViewProvider implements vscode.TreeDataProvider<BookTreeIte
if (this._openAsUntitled) { if (this._openAsUntitled) {
this.openNotebookAsUntitled(resource); this.openNotebookAsUntitled(resource);
} else { } else {
// let us keep a list of already visited notebooks so that we do not trust them again, potentially
// overriding user changes
let normalizedResource = path.normalize(resource);
if (this._visitedNotebooks.indexOf(normalizedResource) === -1
&& this._bookTrustManager.isNotebookTrustedByDefault(normalizedResource)) {
let openDocumentListenerUnsubscriber = azdata.nb.onDidOpenNotebookDocument((document: azdata.nb.NotebookDocument) => {
document.setTrusted(true);
this._visitedNotebooks = this._visitedNotebooks.concat([normalizedResource]);
openDocumentListenerUnsubscriber.dispose();
});
}
let doc = await vscode.workspace.openTextDocument(resource); let doc = await vscode.workspace.openTextDocument(resource);
vscode.window.showTextDocument(doc); vscode.window.showTextDocument(doc);
} }
@@ -377,6 +414,4 @@ export class BookTreeViewProvider implements vscode.TreeDataProvider<BookTreeIte
default: false default: false
}); });
} }
} }

View File

@@ -0,0 +1,114 @@
/*---------------------------------------------------------------------------------------------
* 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 { BookModel } from './bookModel';
import { ApiWrapper } from '../common/apiWrapper';
export interface IBookTrustManager {
isNotebookTrustedByDefault(notebookUri: string): boolean;
setBookAsTrusted(bookRootPath: string): boolean;
}
enum TrustBookOperation {
Add,
Remove
}
export class BookTrustManager implements IBookTrustManager {
constructor(private books: BookModel[], private apiWrapper: ApiWrapper) { }
isNotebookTrustedByDefault(notebookUri: string): boolean {
let normalizedNotebookUri: string = path.normalize(notebookUri);
let treeBookItems: BookTreeItem[] = this.getBookTreeItems();
let trustableBookPaths = this.getTrustableBookPaths();
let hasTrustedBookPath: boolean = treeBookItems
.filter(bookItem => trustableBookPaths.some(trustableBookPath => trustableBookPath === path.join(bookItem.book.root, path.sep)))
.some(bookItem => normalizedNotebookUri.startsWith(path.join(bookItem.root, 'content', path.sep)));
let isNotebookTrusted = hasTrustedBookPath && this.books.some(bookModel => bookModel.getNotebook(normalizedNotebookUri));
return isNotebookTrusted;
}
getTrustableBookPaths() {
let trustablePaths: string[];
let bookPathsInConfig: string[] = this.getTrustedBookPathsInConfig();
if (this.hasWorkspaceFolders()) {
let workspaceFolders = this.apiWrapper.getWorkspaceFolders();
trustablePaths = bookPathsInConfig
.map(trustableBookPath => workspaceFolders
.map(workspaceFolder => path.join(workspaceFolder.uri.fsPath, trustableBookPath)))
.reduce((accumulator, currentTrustableBookPaths) => accumulator.concat(currentTrustableBookPaths), []);
} else {
trustablePaths = bookPathsInConfig;
}
return trustablePaths;
}
getBookTreeItems(): BookTreeItem[] {
return this.books
.map(book => book.bookItems) // select all the books
.reduce((accumulator, currentBookItemList) => accumulator.concat(currentBookItemList), []);
}
setBookAsTrusted(bookRootPath: string): boolean {
return this.updateTrustedBooks(bookRootPath, TrustBookOperation.Add);
}
getTrustedBookPathsInConfig(): string[] {
let config: vscode.WorkspaceConfiguration = this.apiWrapper.getConfiguration(constants.notebookConfigKey);
let trustedBookDirectories: string[] = config.get(constants.trustedBooksConfigKey);
return trustedBookDirectories;
}
setTrustedBookPathsInConfig(bookPaths: string[]) {
let config: vscode.WorkspaceConfiguration = this.apiWrapper.getConfiguration(constants.notebookConfigKey);
let storeInWorspace: boolean = this.hasWorkspaceFolders();
config.update(constants.trustedBooksConfigKey, bookPaths, storeInWorspace ? false : vscode.ConfigurationTarget.Global);
}
hasWorkspaceFolders(): boolean {
let workspaceFolders = this.apiWrapper.getWorkspaceFolders();
return workspaceFolders && workspaceFolders.length > 0;
}
updateTrustedBooks(bookPath: string, operation: TrustBookOperation) {
let modifiedTrustedBooks = false;
let bookPathToChange: string = path.join(bookPath, path.sep);
if (this.hasWorkspaceFolders()) {
let workspaceFolders = this.apiWrapper.getWorkspaceFolders();
let matchingWorkspaceFolder: vscode.WorkspaceFolder = workspaceFolders
.find(ws => bookPathToChange.startsWith(path.normalize(ws.uri.fsPath)));
// if notebook is stored in a workspace folder, then store only the relative directory
if (matchingWorkspaceFolder) {
bookPathToChange = bookPathToChange.replace(path.normalize(matchingWorkspaceFolder.uri.fsPath), '');
}
}
let trustedBooks: string[] = this.getTrustedBookPathsInConfig();
let existingBookIndex = trustedBooks.map(trustedBookPath => path.normalize(trustedBookPath)).indexOf(bookPathToChange);
if (existingBookIndex !== -1 && operation === TrustBookOperation.Remove) {
trustedBooks.splice(existingBookIndex, 1);
modifiedTrustedBooks = true;
} else if (existingBookIndex === -1 && operation === TrustBookOperation.Add) {
trustedBooks.push(bookPathToChange);
modifiedTrustedBooks = true;
}
this.setTrustedBookPathsInConfig(trustedBooks);
return modifiedTrustedBooks;
}
}

View File

@@ -12,6 +12,10 @@ import { CommandContext, BuiltInCommands } from './constants';
* this API from our code * this API from our code
*/ */
export class ApiWrapper { export class ApiWrapper {
public getWorkspaceFolders(): vscode.WorkspaceFolder[] {
return [].concat(vscode.workspace.workspaceFolders || []);
}
public createOutputChannel(name: string): vscode.OutputChannel { public createOutputChannel(name: string): vscode.OutputChannel {
return vscode.window.createOutputChannel(name); return vscode.window.createOutputChannel(name);
} }

View File

@@ -16,6 +16,7 @@ export const pythonVersion = '3.6.6';
export const pythonPathConfigKey = 'pythonPath'; export const pythonPathConfigKey = 'pythonPath';
export const existingPythonConfigKey = 'useExistingPython'; export const existingPythonConfigKey = 'useExistingPython';
export const notebookConfigKey = 'notebook'; export const notebookConfigKey = 'notebook';
export const trustedBooksConfigKey = 'trustedBooks';
export const maxBookSearchDepth = 'maxBookSearchDepth'; export const maxBookSearchDepth = 'maxBookSearchDepth';
export const winPlatform = 'win32'; export const winPlatform = 'win32';
@@ -33,6 +34,8 @@ export const localhostName = 'localhost';
export const localhostTitle = localize('managePackages.localhost', "localhost"); export const localhostTitle = localize('managePackages.localhost', "localhost");
export const PackageNotFoundError = localize('managePackages.packageNotFound', "Could not find the specified package"); export const PackageNotFoundError = localize('managePackages.packageNotFound', "Could not find the specified package");
export const visitedNotebooksMementoKey = 'notebooks.visited';
export enum BuiltInCommands { export enum BuiltInCommands {
SetContext = 'setContext' SetContext = 'setContext'
} }

View File

@@ -21,8 +21,12 @@ export const confirmReplace = localize('confirmReplace', "Folder already exists.
export const openNotebookCommand = localize('openNotebookCommand', "Open Notebook"); export const openNotebookCommand = localize('openNotebookCommand', "Open Notebook");
export const openMarkdownCommand = localize('openMarkdownCommand', "Open Markdown"); export const openMarkdownCommand = localize('openMarkdownCommand', "Open Markdown");
export const openExternalLinkCommand = localize('openExternalLinkCommand', "Open External Link"); export const openExternalLinkCommand = localize('openExternalLinkCommand', "Open External Link");
export const msgBookTrusted = localize('msgBookTrusted', "Book is now trusted in the workspace.");
export const msgBookAlreadyTrusted = localize('msgBookAlreadyTrusted', "Book is already trusted in this workspace.");
export const msgBookUntrusted = localize('msgBookUntrusted', "Book is no longer trusted in this workspace");
export const msgBookAlreadyUntrusted = localize('msgBookAlreadyUntrusted', "Book is already untrusted in this workspace.");
export const missingTocError = localize('bookInitializeFailed', "Failed to find a toc.yml."); export const missingTocError = localize('bookInitializeFailed', "Failed to find a toc.yml.");
export function missingFileError(title: string): string { return localize('missingFileError', "Missing file : {0}", title); } export function missingFileError(title: string): string { return localize('missingFileError', "Missing file : {0}", title); }
export function invalidTocFileError(): string { return localize('InvalidError.tocFile', "Invalid toc file"); } export function invalidTocFileError(): string { return localize('InvalidError.tocFile', "Invalid toc file"); }
export function invalidTocError(title: string): string { return localize('Invalid toc.yml', "Error: {0} has an incorrect toc.yml file", title); } export function invalidTocError(title: string): string { return localize('Invalid toc.yml', "Error: {0} has an incorrect toc.yml file", title); }

View File

@@ -36,6 +36,7 @@ export async function activate(extensionContext: vscode.ExtensionContext): Promi
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)));
extensionContext.subscriptions.push(vscode.commands.registerCommand('notebook.command.saveBook', () => untitledBookTreeViewProvider.saveJupyterBooks())); extensionContext.subscriptions.push(vscode.commands.registerCommand('notebook.command.saveBook', () => untitledBookTreeViewProvider.saveJupyterBooks()));
extensionContext.subscriptions.push(vscode.commands.registerCommand('notebook.command.trustBook', (resource) => bookTreeViewProvider.trustBook(resource)));
extensionContext.subscriptions.push(vscode.commands.registerCommand('notebook.command.searchBook', (item) => bookTreeViewProvider.searchJupyterBooks(item))); extensionContext.subscriptions.push(vscode.commands.registerCommand('notebook.command.searchBook', (item) => bookTreeViewProvider.searchJupyterBooks(item)));
extensionContext.subscriptions.push(vscode.commands.registerCommand('notebook.command.searchUntitledBook', () => untitledBookTreeViewProvider.searchJupyterBooks())); extensionContext.subscriptions.push(vscode.commands.registerCommand('notebook.command.searchUntitledBook', () => untitledBookTreeViewProvider.searchJupyterBooks()));
extensionContext.subscriptions.push(vscode.commands.registerCommand('notebook.command.openBook', () => bookTreeViewProvider.openNewBook())); extensionContext.subscriptions.push(vscode.commands.registerCommand('notebook.command.openBook', () => bookTreeViewProvider.openNewBook()));

View File

@@ -0,0 +1,328 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the Source EULA. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import * as should from 'should';
import * as path from 'path';
import * as TypeMoq from 'typemoq';
import * as constants from '../../common/constants';
import { IBookTrustManager, BookTrustManager } from '../../book/bookTrustManager';
import { BookTreeItem, BookTreeItemFormat, BookTreeItemType } from '../../book/bookTreeItem';
import { ApiWrapper } from '../../common/apiWrapper';
import { WorkspaceConfiguration, ConfigurationTarget } from 'vscode';
import { BookModel } from '../../book/bookModel';
describe('BookTrustManagerTests', function () {
describe('TrustingInWorkspaces', () => {
let bookTrustManager: IBookTrustManager;
let trustedSubFolders: string[];
let books: BookModel[];
beforeEach(() => {
trustedSubFolders = ['/SubFolder/'];
// Mock Workspace Configuration
let workspaceConfigurtionMock: TypeMoq.IMock<WorkspaceConfiguration> = TypeMoq.Mock.ofType<WorkspaceConfiguration>();
workspaceConfigurtionMock.setup(config => config.get(TypeMoq.It.isValue(constants.trustedBooksConfigKey))).returns(() => [].concat(trustedSubFolders));
workspaceConfigurtionMock.setup(config => config.update(TypeMoq.It.isValue(constants.trustedBooksConfigKey), TypeMoq.It.isAny(), TypeMoq.It.isValue(false))).returns((key: string, newValues: string[]) => {
trustedSubFolders.splice(0, trustedSubFolders.length, ...newValues); // Replace
return Promise.resolve();
});
// Mock Api Wrapper
let apiWrapperMock: TypeMoq.IMock<ApiWrapper> = TypeMoq.Mock.ofType<ApiWrapper>();
apiWrapperMock.setup(api => api.getWorkspaceFolders()).returns(() => [
{
// @ts-ignore - Don't need all URI properties for this tests
uri: {
fsPath: '/temp/'
},
},
{
// @ts-ignore - Don't need all URI properties for this tests
uri: {
fsPath: '/temp2/'
}
},
]);
apiWrapperMock.setup(api => api.getConfiguration(TypeMoq.It.isValue(constants.notebookConfigKey))).returns(() => workspaceConfigurtionMock.object);
// Mock Book Data
let bookTreeItemFormat1: BookTreeItemFormat = {
root: '/temp/SubFolder/',
tableOfContents: {
sections: [
{
url: path.join(path.sep, 'sample', 'notebook')
},
{
url: path.join(path.sep, 'sample', 'notebook2')
}
]
},
isUntitled: undefined,
page: undefined,
title: undefined,
treeItemCollapsibleState: undefined,
type: BookTreeItemType.Book
};
let bookTreeItemFormat2: BookTreeItemFormat = {
root: '/temp/SubFolder2/',
tableOfContents: {
sections: [
{
url: path.join(path.sep, 'sample', 'notebook')
}
]
},
isUntitled: undefined,
page: undefined,
title: undefined,
treeItemCollapsibleState: undefined,
type: BookTreeItemType.Book
};
let bookTreeItemFormat3: BookTreeItemFormat = {
root: '/temp2/SubFolder3/',
tableOfContents: {
sections: [
{
url: path.join(path.sep, 'sample', 'notebook')
}
]
},
isUntitled: undefined,
page: undefined,
title: undefined,
treeItemCollapsibleState: undefined,
type: BookTreeItemType.Book
};
let bookModel1Mock: TypeMoq.IMock<BookModel> = TypeMoq.Mock.ofType<BookModel>();
bookModel1Mock.setup(model => model.bookItems).returns(() => [new BookTreeItem(bookTreeItemFormat1, undefined), new BookTreeItem(bookTreeItemFormat2, undefined)]);
bookModel1Mock.setup(model => model.getNotebook(TypeMoq.It.isValue(path.join(path.sep,'temp','SubFolder','content','sample', 'notebook.ipynb')))).returns((uri: string) => TypeMoq.Mock.ofType<BookTreeItem>().object);
bookModel1Mock.setup(model => model.getNotebook(TypeMoq.It.isValue(path.join(path.sep,'temp','SubFolder','content','sample', 'notebook2.ipynb')))).returns((uri: string) => TypeMoq.Mock.ofType<BookTreeItem>().object);
bookModel1Mock.setup(model => model.getNotebook(TypeMoq.It.isValue(path.join(path.sep,'temp','SubFolder2','content','sample', 'notebook.ipynb')))).returns((uri: string) => TypeMoq.Mock.ofType<BookTreeItem>().object);
bookModel1Mock.setup(model => model.getNotebook(TypeMoq.It.isAnyString())).returns((uri: string) => undefined);
let bookModel2Mock: TypeMoq.IMock<BookModel> = TypeMoq.Mock.ofType<BookModel>();
bookModel2Mock.setup(model => model.bookItems).returns(() => [new BookTreeItem(bookTreeItemFormat3, undefined)]);
bookModel2Mock.setup(model => model.getNotebook(TypeMoq.It.isValue(path.join(path.sep,'temp2','SubFolder','content','sample','notebook.ipynb')))).returns((uri: string) => TypeMoq.Mock.ofType<BookTreeItem>().object);
bookModel2Mock.setup(model => model.getNotebook(TypeMoq.It.isAnyString())).returns((uri: string) => undefined);
books = [bookModel1Mock.object, bookModel2Mock.object];
bookTrustManager = new BookTrustManager(books, apiWrapperMock.object);
});
it('should trust notebooks in a trusted book within a workspace', async () => {
let notebookUri1 = path.join(path.sep,'temp','SubFolder','content','sample', 'notebook.ipynb');
let notebookUri2 = path.join(path.sep,'temp','SubFolder','content','sample', 'notebook2.ipynb');
let isNotebook1Trusted = bookTrustManager.isNotebookTrustedByDefault(notebookUri1);
let isNotebook2Trusted = bookTrustManager.isNotebookTrustedByDefault(notebookUri2);
should(isNotebook1Trusted).be.true("Notebook 1 should be trusted");
should(isNotebook2Trusted).be.true("Notebook 2 should be trusted");
});
it('should NOT trust a notebook in an untrusted book within a workspace', async () => {
let notebookUri = path.join(path.sep,'temp','SubFolder2','content', 'sample', 'notebook.ipynb');
let isNotebookTrusted = bookTrustManager.isNotebookTrustedByDefault(notebookUri);
should(isNotebookTrusted).be.false("Notebook should be trusted");
});
it('should trust notebook after book has been trusted within a workspace', async () => {
let notebookUri = path.join(path.sep,'temp','SubFolder2','content', 'sample', 'notebook.ipynb');
let isNotebookTrustedBeforeChange = bookTrustManager.isNotebookTrustedByDefault(notebookUri);
should(isNotebookTrustedBeforeChange).be.false("Notebook should NOT be trusted");
// add another book subfolder
bookTrustManager.setBookAsTrusted('/SubFolder2/');
let isNotebookTrustedAfterChange = bookTrustManager.isNotebookTrustedByDefault(notebookUri);
should(isNotebookTrustedAfterChange).be.true("Notebook should be trusted");
});
it('should NOT trust a notebook when untrusting a book within a workspace', async () => {
let notebookUri = path.join(path.sep,'temp','SubFolder','content', 'sample', 'notebook.ipynb');
let isNotebookTrusted = bookTrustManager.isNotebookTrustedByDefault(notebookUri);
should(isNotebookTrusted).be.true("Notebook should be trusted");
// remove trusted subfolders
trustedSubFolders = [];
let isNotebookTrustedAfterChange = bookTrustManager.isNotebookTrustedByDefault(notebookUri);
should(isNotebookTrustedAfterChange).be.false("Notebook should not be trusted after book removal");
});
it('should NOT trust an unknown book within a workspace', async () => {
let notebookUri = path.join(path.sep, 'randomfolder', 'randomsubfolder', 'content', 'randomnotebook.ipynb');
let isNotebookTrusted = bookTrustManager.isNotebookTrustedByDefault(notebookUri);
should(isNotebookTrusted).be.false("Random notebooks should not be trusted");
});
it('should NOT trust notebook inside trusted subfolder when absent in table of contents ', async () => {
bookTrustManager.setBookAsTrusted('/temp/SubFolder/');
let notebookUri = path.join(path.sep, 'temp', 'SubFolder', 'content', 'sample', 'notInToc.ipynb');
let isNotebookTrusted = bookTrustManager.isNotebookTrustedByDefault(notebookUri);
should(isNotebookTrusted).be.false("Notebook should NOT be trusted");
});
});
describe('TrustingInFolder', () => {
let bookTrustManager: IBookTrustManager;
let books: BookModel[];
let trustedFolders: string[] = [];
beforeEach(() => {
// Mock Workspace Configuration
let workspaceConfigurtionMock: TypeMoq.IMock<WorkspaceConfiguration> = TypeMoq.Mock.ofType<WorkspaceConfiguration>();
workspaceConfigurtionMock.setup(config => config.get(TypeMoq.It.isValue(constants.trustedBooksConfigKey))).returns(() => [].concat(trustedFolders));
workspaceConfigurtionMock.setup(config => config.update(TypeMoq.It.isValue(constants.trustedBooksConfigKey), TypeMoq.It.isAny(), TypeMoq.It.isValue(ConfigurationTarget.Global))).returns((key: string, newValues: string[], target: ConfigurationTarget) => {
trustedFolders.splice(0, trustedFolders.length, ...newValues); // Replace
return Promise.resolve();
});
// Mock Api Wrapper
let apiWrapperMock: TypeMoq.IMock<ApiWrapper> = TypeMoq.Mock.ofType<ApiWrapper>();
apiWrapperMock.setup(api => api.getWorkspaceFolders()).returns(() => []);
apiWrapperMock.setup(api => api.getConfiguration(TypeMoq.It.isValue(constants.notebookConfigKey))).returns(() => workspaceConfigurtionMock.object);
let bookTreeItemFormat1: BookTreeItemFormat = {
root: '/temp/SubFolder/',
tableOfContents: {
sections: [
{
url: path.join(path.sep, 'sample', 'notebook')
},
{
url: path.join(path.sep, 'sample', 'notebook2')
}
]
},
isUntitled: undefined,
page: undefined,
title: undefined,
treeItemCollapsibleState: undefined,
type: BookTreeItemType.Book
};
let bookTreeItemFormat2: BookTreeItemFormat = {
root: '/temp/SubFolder2/',
tableOfContents: {
sections: [
{
url: path.join(path.sep, 'sample', 'notebook')
},
{
url: path.join(path.sep, 'sample', 'notebook2')
}
]
},
isUntitled: undefined,
page: undefined,
title: undefined,
treeItemCollapsibleState: undefined,
type: BookTreeItemType.Book
};
let bookModel1Mock: TypeMoq.IMock<BookModel> = TypeMoq.Mock.ofType<BookModel>();
bookModel1Mock.setup(model => model.bookItems).returns(() => [new BookTreeItem(bookTreeItemFormat1, undefined)]);
bookModel1Mock.setup(model => model.getNotebook(TypeMoq.It.isValue(path.join(path.sep,'temp','SubFolder','content', 'sample', 'notebook.ipynb')))).returns((uri: string) => TypeMoq.Mock.ofType<BookTreeItem>().object);
bookModel1Mock.setup(model => model.getNotebook(TypeMoq.It.isValue(path.join(path.sep,'temp','SubFolder','content', 'sample', 'notebook2.ipynb')))).returns((uri: string) => TypeMoq.Mock.ofType<BookTreeItem>().object);
bookModel1Mock.setup(model => model.getNotebook(TypeMoq.It.isAnyString())).returns((uri: string) => undefined);
let bookModel2Mock: TypeMoq.IMock<BookModel> = TypeMoq.Mock.ofType<BookModel>();
bookModel2Mock.setup(model => model.bookItems).returns(() => [new BookTreeItem(bookTreeItemFormat2, undefined)]);
bookModel2Mock.setup(model => model.getNotebook(TypeMoq.It.isValue(path.join(path.sep,'temp','SubFolder2','content', 'sample', 'notebook.ipynb')))).returns((uri: string) => TypeMoq.Mock.ofType<BookTreeItem>().object);
bookModel2Mock.setup(model => model.getNotebook(TypeMoq.It.isValue(path.join(path.sep,'temp','SubFolder2','content', 'sample', 'notebook2.ipynb')))).returns((uri: string) => TypeMoq.Mock.ofType<BookTreeItem>().object);
bookModel2Mock.setup(model => model.getNotebook(TypeMoq.It.isAnyString())).returns((uri: string) => undefined);
books = [bookModel1Mock.object, bookModel2Mock.object];
bookTrustManager = new BookTrustManager(books, apiWrapperMock.object);
});
it('should trust notebooks in a trusted book in a folder', async () => {
bookTrustManager.setBookAsTrusted('/temp/SubFolder/');
let notebookUri1 = path.join(path.sep,'temp','SubFolder','content', 'sample', 'notebook.ipynb');
let notebookUri2 = path.join(path.sep,'temp','SubFolder','content', 'sample', 'notebook2.ipynb');
let isNotebook1Trusted = bookTrustManager.isNotebookTrustedByDefault(notebookUri1);
let isNotebook2Trusted = bookTrustManager.isNotebookTrustedByDefault(notebookUri2);
should(isNotebook1Trusted).be.true("Notebook 1 should be trusted");
should(isNotebook2Trusted).be.true("Notebook 2 should be trusted");
});
it('should NOT trust a notebook in an untrusted book in a folder', async () => {
let notebookUri = path.join(path.sep,'temp','SubFolder2','content', 'sample', 'notebook.ipynb');
let isNotebookTrusted = bookTrustManager.isNotebookTrustedByDefault(notebookUri);
should(isNotebookTrusted).be.false("Notebook should be trusted");
});
it('should trust notebook after book has been added to a folder', async () => {
let notebookUri = path.join(path.sep,'temp','SubFolder2','content', 'sample','notebook.ipynb');
let isNotebookTrustedBeforeChange = bookTrustManager.isNotebookTrustedByDefault(notebookUri);
should(isNotebookTrustedBeforeChange).be.false("Notebook should NOT be trusted");
bookTrustManager.setBookAsTrusted('/temp/SubFolder2/');
let isNotebookTrustedAfterChange = bookTrustManager.isNotebookTrustedByDefault(notebookUri);
should(isNotebookTrustedAfterChange).be.true("Notebook should be trusted");
});
it('should NOT trust a notebook when untrusting a book in folder', async () => {
bookTrustManager.setBookAsTrusted('/temp/SubFolder/');
let notebookUri = path.join(path.sep,'temp','SubFolder','content', 'sample', 'notebook.ipynb');
let isNotebookTrusted = bookTrustManager.isNotebookTrustedByDefault(notebookUri);
should(isNotebookTrusted).be.true("Notebook should be trusted");
trustedFolders = [];
let isNotebookTrustedAfterChange = bookTrustManager.isNotebookTrustedByDefault(notebookUri);
should(isNotebookTrustedAfterChange).be.false("Notebook should not be trusted after book removal");
});
it('should NOT trust an unknown book', async () => {
let notebookUri = path.join(path.sep, 'randomfolder', 'randomsubfolder', 'content', 'randomnotebook.ipynb');
let isNotebookTrusted = bookTrustManager.isNotebookTrustedByDefault(notebookUri);
should(isNotebookTrusted).be.false("Random notebooks should not be trusted");
});
it('should NOT trust notebook inside trusted subfolder when absent in table of contents ', async () => {
bookTrustManager.setBookAsTrusted('/temp/SubFolder/');
let notebookUri = path.join(path.sep, 'temp', 'SubFolder', 'content', 'sample', 'notInToc.ipynb');
let isNotebookTrusted = bookTrustManager.isNotebookTrustedByDefault(notebookUri);
should(isNotebookTrusted).be.false("Notebook should NOT be trusted");
});
});
});

View File

@@ -29,6 +29,14 @@ declare module 'azdata' {
export function getConnection(uri: string): Thenable<ConnectionProfile>; export function getConnection(uri: string): Thenable<ConnectionProfile>;
} }
export namespace nb {
export interface NotebookDocument {
/**
* Sets the trust mode for the notebook document.
*/
setTrusted(state: boolean);
}
}
export type SqlDbType = 'BigInt' | 'Binary' | 'Bit' | 'Char' | 'DateTime' | 'Decimal' export type SqlDbType = 'BigInt' | 'Binary' | 'Bit' | 'Char' | 'DateTime' | 'Decimal'
| 'Float' | 'Image' | 'Int' | 'Money' | 'NChar' | 'NText' | 'NVarChar' | 'Real' | 'Float' | 'Image' | 'Int' | 'Money' | 'NChar' | 'NText' | 'NVarChar' | 'Real'

View File

@@ -362,6 +362,11 @@ export class MainThreadNotebookDocumentsAndEditors extends Disposable implements
return Promise.resolve(this.doOpenEditor(resource, options)); return Promise.resolve(this.doOpenEditor(resource, options));
} }
$trySetTrusted(uriComponent: UriComponents, isTrusted: boolean): Promise<boolean> {
let uri = URI.revive(uriComponent);
return this._notebookService.setTrusted(uri, isTrusted);
}
$tryApplyEdits(id: string, modelVersionId: number, edits: ISingleNotebookEditOperation[], opts: IUndoStopOptions): Promise<boolean> { $tryApplyEdits(id: string, modelVersionId: number, edits: ISingleNotebookEditOperation[], opts: IUndoStopOptions): Promise<boolean> {
let editor = this.getEditor(id); let editor = this.getEditor(id);
if (!editor) { if (!editor) {

View File

@@ -4,7 +4,6 @@
*--------------------------------------------------------------------------------------------*/ *--------------------------------------------------------------------------------------------*/
import * as azdata from 'azdata'; import * as azdata from 'azdata';
import { IDisposable } from 'vs/base/common/lifecycle'; import { IDisposable } from 'vs/base/common/lifecycle';
import { URI } from 'vs/base/common/uri'; import { URI } from 'vs/base/common/uri';
import { ok } from 'vs/base/common/assert'; import { ok } from 'vs/base/common/assert';
@@ -50,6 +49,7 @@ export class ExtHostNotebookDocumentData implements IDisposable {
get cells() { return data._cells; }, get cells() { return data._cells; },
get kernelSpec() { return data._kernelSpec; }, get kernelSpec() { return data._kernelSpec; },
save() { return data._save(); }, save() { return data._save(); },
setTrusted(isTrusted) { data._setTrusted(isTrusted); },
validateCellRange(range) { return data._validateRange(range); }, validateCellRange(range) { return data._validateRange(range); },
}; };
} }
@@ -61,7 +61,13 @@ export class ExtHostNotebookDocumentData implements IDisposable {
return Promise.reject(new Error('Document has been closed')); return Promise.reject(new Error('Document has been closed'));
} }
return this._proxy.$trySaveDocument(this._uri); return this._proxy.$trySaveDocument(this._uri);
}
private _setTrusted(isTrusted: boolean): Thenable<boolean> {
if (this._isDisposed) {
return Promise.reject(new Error('Document has been closed'));
}
return this._proxy.$trySetTrusted(this._uri, isTrusted);
} }
public onModelChanged(data: INotebookModelChangedData) { public onModelChanged(data: INotebookModelChangedData) {

View File

@@ -82,6 +82,10 @@ export class NotebookEditorEdit {
return range; return range;
} }
setTrusted(isTrusted: boolean) {
this._document.setTrusted(isTrusted);
}
insertCell(value: Partial<azdata.nb.ICellContents>, index?: number, collapsed?: boolean): void { insertCell(value: Partial<azdata.nb.ICellContents>, index?: number, collapsed?: boolean): void {
if (index === null || index === undefined) { if (index === null || index === undefined) {
// If not specified, assume adding to end of list // If not specified, assume adding to end of list

View File

@@ -891,6 +891,7 @@ export interface ExtHostNotebookDocumentsAndEditorsShape {
} }
export interface MainThreadNotebookDocumentsAndEditorsShape extends IDisposable { export interface MainThreadNotebookDocumentsAndEditorsShape extends IDisposable {
$trySetTrusted(_uri: UriComponents, isTrusted: boolean): Thenable<boolean>;
$trySaveDocument(uri: UriComponents): Thenable<boolean>; $trySaveDocument(uri: UriComponents): Thenable<boolean>;
$tryShowNotebookDocument(resource: UriComponents, options: INotebookShowOptions): Promise<string>; $tryShowNotebookDocument(resource: UriComponents, options: INotebookShowOptions): Promise<string>;
$tryApplyEdits(id: string, modelVersionId: number, edits: ISingleNotebookEditOperation[], opts: IUndoStopOptions): Promise<boolean>; $tryApplyEdits(id: string, modelVersionId: number, edits: ISingleNotebookEditOperation[], opts: IUndoStopOptions): Promise<boolean>;

View File

@@ -58,7 +58,7 @@ export class LinkHandlerDirective {
// ignore // ignore
} }
if (uri && this.openerService && this.isSupportedLink(uri)) { if (uri && this.openerService && this.isSupportedLink(uri)) {
if (uri.fragment && uri.fragment.length > 0 && uri.fsPath === this.workbenchFilePath.fsPath) { if (uri.fragment && uri.fragment.length > 0 && uri.path === this.workbenchFilePath.path) {
this.notebookService.navigateTo(this.notebookUri, uri.fragment); this.notebookService.navigateTo(this.notebookUri, uri.fragment);
} else { } else {
this.openerService.open(uri).catch(onUnexpectedError); this.openerService.open(uri).catch(onUnexpectedError);

View File

@@ -205,6 +205,9 @@ export class NotebookServiceStub implements INotebookService {
get languageMagics(): ILanguageMagic[] { get languageMagics(): ILanguageMagic[] {
throw new Error('Method not implemented.'); throw new Error('Method not implemented.');
} }
setTrusted(notebookUri: URI, isTrusted: boolean): Promise<boolean> {
throw new Error('Method not implemented.');
}
registerProvider(providerId: string, provider: INotebookProvider): void { registerProvider(providerId: string, provider: INotebookProvider): void {
throw new Error('Method not implemented.'); throw new Error('Method not implemented.');
} }

View File

@@ -233,6 +233,7 @@ export class NotebookModel extends Disposable implements INotebookModel {
public set trustedMode(isTrusted: boolean) { public set trustedMode(isTrusted: boolean) {
this._trustedMode = isTrusted; this._trustedMode = isTrusted;
if (this._cells) { if (this._cells) {
this._cells.forEach(c => { this._cells.forEach(c => {
c.trustedMode = this._trustedMode; c.trustedMode = this._trustedMode;
@@ -290,6 +291,7 @@ export class NotebookModel extends Disposable implements INotebookModel {
public async loadContents(isTrusted: boolean = false): Promise<void> { public async loadContents(isTrusted: boolean = false): Promise<void> {
try { try {
this._trustedMode = isTrusted; this._trustedMode = isTrusted;
let contents = null; let contents = null;
if (this._notebookOptions && this._notebookOptions.contentManager) { if (this._notebookOptions && this._notebookOptions.contentManager) {

View File

@@ -112,6 +112,13 @@ export interface INotebookService {
* @param sectionId ID of the section to navigate to * @param sectionId ID of the section to navigate to
*/ */
navigateTo(notebookUri: URI, sectionId: string): void; navigateTo(notebookUri: URI, sectionId: string): void;
/**
* Sets the trusted mode for the sepcified notebook.
* @param notebookUri URI of the notebook to navigate to
* @param isTrusted True if notebook is to be set to trusted, false otherwise.
*/
setTrusted(notebookUri: URI, isTrusted: boolean): Promise<boolean>;
} }
export interface INotebookProvider { export interface INotebookProvider {

View File

@@ -606,4 +606,25 @@ export class NotebookService extends Disposable implements INotebookService {
editor.navigateToSection(sectionId); editor.navigateToSection(sectionId);
} }
} }
/**
* Trusts a notebook with the specified URI.
* @param notebookUri The notebook URI to set the trusted mode for.
* @param isTrusted True if the notebook is to be trusted, false otherwise.
*/
async setTrusted(notebookUri: URI, isTrusted: boolean): Promise<boolean> {
let editor = this.findNotebookEditor(notebookUri);
if (editor && editor.model) {
if (isTrusted) {
this._trustedCacheQueue.push(notebookUri);
} else {
this._unTrustedCacheQueue.push(notebookUri);
}
await this.updateTrustedCache();
editor.model.trustedMode = isTrusted;
}
return isTrusted;
}
} }