mirror of
https://github.com/ckaczor/azuredatastudio.git
synced 2026-02-16 10:58:30 -05:00
Add notebook open protocol handler (#6093)
Adds a protocol handler for notebook open, which can be used from browsers Uses extension-based handler support so all URIs must start with `azuredatastudio://microsoft.notebook` Adds 2 actions: - `/new` opens a new empty notebook - `/open` opens a HTTP/S file as an untitled notebook or text document Sample URL: ``` azuredatastudio://microsoft.notebook/open?url=https%3A%2F%2Fraw.githubusercontent.com%2Fkevcunnane%2Fmsbuild_ads_demo%2Fmaster%2F0_YoAzdata.ipynb ```
This commit is contained in:
131
extensions/notebook/src/protocol/notebookUriHandler.ts
Normal file
131
extensions/notebook/src/protocol/notebookUriHandler.ts
Normal file
@@ -0,0 +1,131 @@
|
||||
/*---------------------------------------------------------------------------------------------
|
||||
* 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 azdata from 'azdata';
|
||||
|
||||
import * as request from 'request';
|
||||
import * as path from 'path';
|
||||
import * as querystring from 'querystring';
|
||||
import * as nls from 'vscode-nls';
|
||||
const localize = nls.loadMessageBundle();
|
||||
import { IQuestion, QuestionTypes } from '../prompts/question';
|
||||
import CodeAdapter from '../prompts/adapter';
|
||||
import { getErrorMessage, isEditorTitleFree } from '../common/utils';
|
||||
|
||||
export class NotebookUriHandler implements vscode.UriHandler {
|
||||
private prompter = new CodeAdapter();
|
||||
|
||||
constructor() {
|
||||
}
|
||||
|
||||
handleUri(uri: vscode.Uri): vscode.ProviderResult<void> {
|
||||
switch (uri.path) {
|
||||
case '/new':
|
||||
vscode.commands.executeCommand('notebook.command.new');
|
||||
break;
|
||||
case '/open':
|
||||
this.open(uri);
|
||||
break;
|
||||
default:
|
||||
vscode.window.showErrorMessage(localize('notebook.unsupportedAction', "Action {0} is not supported for this handler", uri.path));
|
||||
}
|
||||
}
|
||||
|
||||
private open(uri: vscode.Uri): void {
|
||||
const data = querystring.parse(uri.query);
|
||||
|
||||
if (!data.url) {
|
||||
console.warn('Failed to open URI:', uri);
|
||||
}
|
||||
|
||||
this.openNotebook(data.url);
|
||||
}
|
||||
|
||||
private async openNotebook(url: string | string[]): Promise<void> {
|
||||
try {
|
||||
if (Array.isArray(url)) {
|
||||
url = url[0];
|
||||
}
|
||||
url = decodeURI(url);
|
||||
let uri = vscode.Uri.parse(url);
|
||||
switch (uri.scheme) {
|
||||
case 'http':
|
||||
case 'https':
|
||||
break;
|
||||
default:
|
||||
vscode.window.showErrorMessage(localize('unsupportedScheme', "Cannot open link {0} as only HTTP and HTTPS links are supported", url));
|
||||
return;
|
||||
}
|
||||
|
||||
let doOpen = await this.prompter.promptSingle<boolean>(<IQuestion>{
|
||||
type: QuestionTypes.confirm,
|
||||
message: localize('notebook.confirmOpen', "Download and open '{0}'?", url),
|
||||
default: true
|
||||
});
|
||||
if (!doOpen) {
|
||||
return;
|
||||
}
|
||||
|
||||
let contents = await this.download(url);
|
||||
let untitledUri = this.getUntitledUri(path.basename(uri.fsPath));
|
||||
if (path.extname(uri.fsPath) === '.ipynb') {
|
||||
await azdata.nb.showNotebookDocument(untitledUri, {
|
||||
initialContent: contents,
|
||||
preserveFocus: true
|
||||
});
|
||||
} else {
|
||||
let doc = await vscode.workspace.openTextDocument(untitledUri);
|
||||
let editor = await vscode.window.showTextDocument(doc, vscode.ViewColumn.Active, true);
|
||||
await editor.edit(builder => {
|
||||
builder.insert(new vscode.Position(0, 0), contents);
|
||||
});
|
||||
}
|
||||
} catch (err) {
|
||||
vscode.window.showErrorMessage(getErrorMessage(err));
|
||||
}
|
||||
}
|
||||
|
||||
private download(url: string): Promise<string> {
|
||||
return new Promise<string>((resolve, reject) => {
|
||||
request.get(url, { timeout: 10000 }, (error, response, body) => {
|
||||
if (error) {
|
||||
return reject(error);
|
||||
}
|
||||
|
||||
if (response.statusCode === 404) {
|
||||
return reject(localize('notebook.fileNotFound', "Could not find the specified file"));
|
||||
}
|
||||
|
||||
if (response.statusCode !== 200) {
|
||||
return reject(
|
||||
localize('notebook.fileDownloadError',
|
||||
"File open request failed with error: {0} {1}",
|
||||
response.statusCode,
|
||||
response.statusMessage));
|
||||
}
|
||||
|
||||
resolve(body);
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
private getUntitledUri(originalTitle: string): vscode.Uri {
|
||||
let title = originalTitle;
|
||||
let nextVal = 0;
|
||||
let ext = path.extname(title);
|
||||
while (!isEditorTitleFree(title)) {
|
||||
if (ext) {
|
||||
// Need it to be `Readme-0.txt` not `Readme.txt-0`
|
||||
let titleStart = originalTitle.slice(0, originalTitle.length - ext.length);
|
||||
title = `${titleStart}-${nextVal}${ext}`;
|
||||
} else {
|
||||
title = `${originalTitle}-${nextVal}`;
|
||||
}
|
||||
nextVal++;
|
||||
}
|
||||
return vscode.Uri.parse(`untitled:${title}`);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user