未验证 提交 261ef3e7 编写于 作者: J Johannes Rieken 提交者: GitHub

Merge pull request #84812 from microsoft/joh/opener

OpenerService supporting URI and URL
......@@ -50,19 +50,19 @@ export function renderMarkdown(markdown: IMarkdownString, options: MarkdownRende
const _href = function (href: string, isDomUri: boolean): string {
const data = markdown.uris && markdown.uris[href];
if (!data) {
return href;
return href; // no uri exists
}
let uri = URI.revive(data);
if (URI.parse(href).toString() === uri.toString()) {
return href; // no tranformation performed
}
if (isDomUri) {
uri = DOM.asDomUri(uri);
}
if (uri.query) {
uri = uri.with({ query: _uriMassage(uri.query) });
}
if (data) {
href = uri.toString(true);
}
return href;
return uri.toString();
};
// signal to code-block render that the
......
......@@ -4,58 +4,132 @@
*--------------------------------------------------------------------------------------------*/
import * as dom from 'vs/base/browser/dom';
import { Disposable, IDisposable } from 'vs/base/common/lifecycle';
import { IDisposable } from 'vs/base/common/lifecycle';
import { LinkedList } from 'vs/base/common/linkedList';
import { parse } from 'vs/base/common/marshalling';
import { Schemas } from 'vs/base/common/network';
import * as resources from 'vs/base/common/resources';
import { equalsIgnoreCase } from 'vs/base/common/strings';
import { normalizePath } from 'vs/base/common/resources';
import { URI } from 'vs/base/common/uri';
import { ICodeEditorService } from 'vs/editor/browser/services/codeEditorService';
import { CommandsRegistry, ICommandService } from 'vs/platform/commands/common/commands';
import { IOpener, IOpenerService, IValidator, IExternalUriResolver, OpenOptions, ResolveExternalUriOptions, IResolvedExternalUri, IExternalOpener } from 'vs/platform/opener/common/opener';
import { IOpener, IOpenerService, IValidator, IExternalUriResolver, OpenOptions, ResolveExternalUriOptions, IResolvedExternalUri, IExternalOpener, matchesScheme } from 'vs/platform/opener/common/opener';
import { EditorOpenContext } from 'vs/platform/editor/common/editor';
export class OpenerService extends Disposable implements IOpenerService {
class CommandOpener implements IOpener {
constructor(@ICommandService private readonly _commandService: ICommandService) { }
async open(target: URI | string) {
if (!matchesScheme(target, Schemas.command)) {
return false;
}
// run command or bail out if command isn't known
if (typeof target === 'string') {
target = URI.parse(target);
}
if (!CommandsRegistry.getCommand(target.path)) {
throw new Error(`command '${target.path}' NOT known`);
}
// execute as command
let args: any = [];
try {
args = parse(target.query);
if (!Array.isArray(args)) {
args = [args];
}
} catch (e) {
// ignore error
}
await this._commandService.executeCommand(target.path, ...args);
return true;
}
}
class EditorOpener implements IOpener {
constructor(@ICodeEditorService private readonly _editorService: ICodeEditorService) { }
async open(target: URI | string, options: OpenOptions) {
if (typeof target === 'string') {
target = URI.parse(target);
}
let selection: { startLineNumber: number; startColumn: number; } | undefined = undefined;
const match = /^L?(\d+)(?:,(\d+))?/.exec(target.fragment);
if (match) {
// support file:///some/file.js#73,84
// support file:///some/file.js#L73
selection = {
startLineNumber: parseInt(match[1]),
startColumn: match[2] ? parseInt(match[2]) : 1
};
// remove fragment
target = target.with({ fragment: '' });
}
if (target.scheme === Schemas.file) {
target = normalizePath(target); // workaround for non-normalized paths (https://github.com/Microsoft/vscode/issues/12954)
}
await this._editorService.openCodeEditor(
{ resource: target, options: { selection, context: options?.fromUserGesture ? EditorOpenContext.USER : EditorOpenContext.API } },
this._editorService.getFocusedCodeEditor(),
options?.openToSide
);
return true;
}
}
export class OpenerService implements IOpenerService {
_serviceBrand: undefined;
private readonly _openers = new LinkedList<IOpener>();
private readonly _validators = new LinkedList<IValidator>();
private readonly _resolvers = new LinkedList<IExternalUriResolver>();
private _externalOpener: IExternalOpener;
constructor(
@ICodeEditorService private readonly _editorService: ICodeEditorService,
@ICommandService private readonly _commandService: ICommandService,
@ICodeEditorService editorService: ICodeEditorService,
@ICommandService commandService: ICommandService,
) {
super();
// Default external opener is going through window.open()
this._externalOpener = {
openExternal: href => {
dom.windowOpenNoOpener(href);
return Promise.resolve(true);
}
};
// Default opener: maito, http(s), command, and catch-all-editors
this._openers.push({
open: async (target: URI | string, options?: OpenOptions) => {
if (options?.openExternal || matchesScheme(target, Schemas.mailto) || matchesScheme(target, Schemas.http) || matchesScheme(target, Schemas.https)) {
// open externally
await this._doOpenExternal(target, options);
return true;
}
return false;
}
});
this._openers.push(new CommandOpener(commandService));
this._openers.push(new EditorOpener(editorService));
}
registerOpener(opener: IOpener): IDisposable {
const remove = this._openers.push(opener);
const remove = this._openers.unshift(opener);
return { dispose: remove };
}
registerValidator(validator: IValidator): IDisposable {
const remove = this._validators.push(validator);
return { dispose: remove };
}
registerExternalUriResolver(resolver: IExternalUriResolver): IDisposable {
const remove = this._resolvers.push(resolver);
return { dispose: remove };
}
......@@ -63,30 +137,24 @@ export class OpenerService extends Disposable implements IOpenerService {
this._externalOpener = externalOpener;
}
async open(resource: URI, options?: OpenOptions): Promise<boolean> {
// no scheme ?!?
if (!resource.scheme) {
return Promise.resolve(false);
}
async open(target: URI | string, options?: OpenOptions): Promise<boolean> {
// check with contributed validators
for (const validator of this._validators.toArray()) {
if (!(await validator.shouldOpen(resource))) {
if (!(await validator.shouldOpen(target))) {
return false;
}
}
// check with contributed openers
for (const opener of this._openers.toArray()) {
const handled = await opener.open(resource, options);
const handled = await opener.open(target, options);
if (handled) {
return true;
}
}
// use default openers
return this._doOpen(resource, options);
return false;
}
async resolveExternalUri(resource: URI, options?: ResolveExternalUriOptions): Promise<IResolvedExternalUri> {
......@@ -100,68 +168,19 @@ export class OpenerService extends Disposable implements IOpenerService {
return { resolved: resource, dispose: () => { } };
}
private async _doOpen(resource: URI, options: OpenOptions | undefined): Promise<boolean> {
const { scheme, path, query, fragment } = resource;
private async _doOpenExternal(resource: URI | string, options: OpenOptions | undefined): Promise<boolean> {
if (options?.openExternal || equalsIgnoreCase(scheme, Schemas.mailto) || equalsIgnoreCase(scheme, Schemas.http) || equalsIgnoreCase(scheme, Schemas.https)) {
// open externally
return this._doOpenExternal(resource, options);
}
if (equalsIgnoreCase(scheme, Schemas.command)) {
// run command or bail out if command isn't known
if (!CommandsRegistry.getCommand(path)) {
throw new Error(`command '${path}' NOT known`);
}
// execute as command
let args: any = [];
try {
args = parse(query);
if (!Array.isArray(args)) {
args = [args];
}
} catch (e) {
// ignore error
}
//todo@joh IExternalUriResolver should support `uri: URI | string`
const uri = typeof resource === 'string' ? URI.parse(resource) : resource;
const { resolved } = await this.resolveExternalUri(uri, options);
await this._commandService.executeCommand(path, ...args);
return true;
if (typeof resource === 'string' && uri.toString() === resolved.toString()) {
// open the url-string AS IS
return this._externalOpener.openExternal(resource);
} else {
// open URI using the toString(noEncode)+encodeURI-trick
return this._externalOpener.openExternal(encodeURI(resolved.toString(true)));
}
// finally open in editor
let selection: { startLineNumber: number; startColumn: number; } | undefined = undefined;
const match = /^L?(\d+)(?:,(\d+))?/.exec(fragment);
if (match) {
// support file:///some/file.js#73,84
// support file:///some/file.js#L73
selection = {
startLineNumber: parseInt(match[1]),
startColumn: match[2] ? parseInt(match[2]) : 1
};
// remove fragment
resource = resource.with({ fragment: '' });
}
if (resource.scheme === Schemas.file) {
resource = resources.normalizePath(resource); // workaround for non-normalized paths (https://github.com/Microsoft/vscode/issues/12954)
}
await this._editorService.openCodeEditor(
{ resource, options: { selection, context: options?.fromUserGesture ? EditorOpenContext.USER : EditorOpenContext.API } },
this._editorService.getFocusedCodeEditor(),
options?.openToSide
);
return true;
}
private async _doOpenExternal(resource: URI, options: OpenOptions | undefined): Promise<boolean> {
const { resolved } = await this.resolveExternalUri(resource, options);
// TODO@Jo neither encodeURI nor toString(true) should be needed
// once we go with URL and not URI
return this._externalOpener.openExternal(encodeURI(resolved.toString(true)));
}
dispose() {
......
......@@ -207,7 +207,7 @@ export class ModesContentHoverWidget extends ContentHoverWidget {
private readonly _themeService: IThemeService,
private readonly _keybindingService: IKeybindingService,
private readonly _modeService: IModeService,
private readonly _openerService: IOpenerService | null = NullOpenerService,
private readonly _openerService: IOpenerService = NullOpenerService,
) {
super(ModesContentHoverWidget.ID, editor);
......
......@@ -97,7 +97,7 @@ export class ModesGlyphHoverWidget extends GlyphHoverWidget {
constructor(
editor: ICodeEditor,
modeService: IModeService,
openerService: IOpenerService | null = NullOpenerService,
openerService: IOpenerService = NullOpenerService,
) {
super(ModesGlyphHoverWidget.ID, editor);
......
......@@ -44,17 +44,9 @@ export class Link implements ILink {
return this._link.tooltip;
}
resolve(token: CancellationToken): Promise<URI> {
async resolve(token: CancellationToken): Promise<URI | string> {
if (this._link.url) {
try {
if (typeof this._link.url === 'string') {
return Promise.resolve(URI.parse(this._link.url));
} else {
return Promise.resolve(this._link.url);
}
} catch (e) {
return Promise.reject(new Error('invalid'));
}
return this._link.url;
}
if (typeof this._provider.resolveLink === 'function') {
......
......@@ -7,7 +7,6 @@ import { IMarkdownString } from 'vs/base/common/htmlContent';
import { renderMarkdown, MarkdownRenderOptions } from 'vs/base/browser/markdownRenderer';
import { IOpenerService, NullOpenerService } from 'vs/platform/opener/common/opener';
import { IModeService } from 'vs/editor/common/services/modeService';
import { URI } from 'vs/base/common/uri';
import { onUnexpectedError } from 'vs/base/common/errors';
import { tokenizeToString } from 'vs/editor/common/modes/textToHtmlTokenizer';
import { ICodeEditor } from 'vs/editor/browser/editorBrowser';
......@@ -29,7 +28,7 @@ export class MarkdownRenderer extends Disposable {
constructor(
private readonly _editor: ICodeEditor,
@IModeService private readonly _modeService: IModeService,
@optional(IOpenerService) private readonly _openerService: IOpenerService | null = NullOpenerService,
@optional(IOpenerService) private readonly _openerService: IOpenerService = NullOpenerService,
) {
super();
}
......@@ -64,15 +63,7 @@ export class MarkdownRenderer extends Disposable {
codeBlockRenderCallback: () => this._onDidRenderCodeBlock.fire(),
actionHandler: {
callback: (content) => {
let uri: URI | undefined;
try {
uri = URI.parse(content);
} catch {
// ignore
}
if (uri && this._openerService) {
this._openerService.open(uri, { fromUserGesture: true }).catch(onUnexpectedError);
}
this._openerService.open(content, { fromUserGesture: true }).catch(onUnexpectedError);
},
disposeables
}
......
......@@ -8,6 +8,7 @@ import { URI } from 'vs/base/common/uri';
import { OpenerService } from 'vs/editor/browser/services/openerService';
import { TestCodeEditorService } from 'vs/editor/test/browser/editorTestServices';
import { CommandsRegistry, ICommandService, NullCommandService } from 'vs/platform/commands/common/commands';
import { matchesScheme } from 'vs/platform/opener/common/opener';
suite('OpenerService', function () {
const editorService = new TestCodeEditorService();
......@@ -28,27 +29,27 @@ suite('OpenerService', function () {
lastCommand = undefined;
});
test('delegate to editorService, scheme:///fff', function () {
test('delegate to editorService, scheme:///fff', async function () {
const openerService = new OpenerService(editorService, NullCommandService);
openerService.open(URI.parse('another:///somepath'));
await openerService.open(URI.parse('another:///somepath'));
assert.equal(editorService.lastInput!.options!.selection, undefined);
});
test('delegate to editorService, scheme:///fff#L123', function () {
test('delegate to editorService, scheme:///fff#L123', async function () {
const openerService = new OpenerService(editorService, NullCommandService);
openerService.open(URI.parse('file:///somepath#L23'));
await openerService.open(URI.parse('file:///somepath#L23'));
assert.equal(editorService.lastInput!.options!.selection!.startLineNumber, 23);
assert.equal(editorService.lastInput!.options!.selection!.startColumn, 1);
assert.equal(editorService.lastInput!.options!.selection!.endLineNumber, undefined);
assert.equal(editorService.lastInput!.options!.selection!.endColumn, undefined);
assert.equal(editorService.lastInput!.resource.fragment, '');
openerService.open(URI.parse('another:///somepath#L23'));
await openerService.open(URI.parse('another:///somepath#L23'));
assert.equal(editorService.lastInput!.options!.selection!.startLineNumber, 23);
assert.equal(editorService.lastInput!.options!.selection!.startColumn, 1);
openerService.open(URI.parse('another:///somepath#L23,45'));
await openerService.open(URI.parse('another:///somepath#L23,45'));
assert.equal(editorService.lastInput!.options!.selection!.startLineNumber, 23);
assert.equal(editorService.lastInput!.options!.selection!.startColumn, 45);
assert.equal(editorService.lastInput!.options!.selection!.endLineNumber, undefined);
......@@ -56,17 +57,17 @@ suite('OpenerService', function () {
assert.equal(editorService.lastInput!.resource.fragment, '');
});
test('delegate to editorService, scheme:///fff#123,123', function () {
test('delegate to editorService, scheme:///fff#123,123', async function () {
const openerService = new OpenerService(editorService, NullCommandService);
openerService.open(URI.parse('file:///somepath#23'));
await openerService.open(URI.parse('file:///somepath#23'));
assert.equal(editorService.lastInput!.options!.selection!.startLineNumber, 23);
assert.equal(editorService.lastInput!.options!.selection!.startColumn, 1);
assert.equal(editorService.lastInput!.options!.selection!.endLineNumber, undefined);
assert.equal(editorService.lastInput!.options!.selection!.endColumn, undefined);
assert.equal(editorService.lastInput!.resource.fragment, '');
openerService.open(URI.parse('file:///somepath#23,45'));
await openerService.open(URI.parse('file:///somepath#23,45'));
assert.equal(editorService.lastInput!.options!.selection!.startLineNumber, 23);
assert.equal(editorService.lastInput!.options!.selection!.startColumn, 45);
assert.equal(editorService.lastInput!.options!.selection!.endLineNumber, undefined);
......@@ -74,22 +75,22 @@ suite('OpenerService', function () {
assert.equal(editorService.lastInput!.resource.fragment, '');
});
test('delegate to commandsService, command:someid', function () {
test('delegate to commandsService, command:someid', async function () {
const openerService = new OpenerService(editorService, commandService);
const id = `aCommand${Math.random()}`;
CommandsRegistry.registerCommand(id, function () { });
openerService.open(URI.parse('command:' + id));
await openerService.open(URI.parse('command:' + id));
assert.equal(lastCommand!.id, id);
assert.equal(lastCommand!.args.length, 0);
openerService.open(URI.parse('command:' + id).with({ query: '123' }));
await openerService.open(URI.parse('command:' + id).with({ query: '123' }));
assert.equal(lastCommand!.id, id);
assert.equal(lastCommand!.args.length, 1);
assert.equal(lastCommand!.args[0], '123');
openerService.open(URI.parse('command:' + id).with({ query: JSON.stringify([12, true]) }));
await openerService.open(URI.parse('command:' + id).with({ query: JSON.stringify([12, true]) }));
assert.equal(lastCommand!.id, id);
assert.equal(lastCommand!.args.length, 2);
assert.equal(lastCommand!.args[0], 12);
......@@ -199,4 +200,18 @@ suite('OpenerService', function () {
assert.equal(v1, 2);
assert.equal(v2, 0);
});
test('matchesScheme', function () {
assert.ok(matchesScheme('https://microsoft.com', 'https'));
assert.ok(matchesScheme('http://microsoft.com', 'http'));
assert.ok(matchesScheme('hTTPs://microsoft.com', 'https'));
assert.ok(matchesScheme('httP://microsoft.com', 'http'));
assert.ok(matchesScheme(URI.parse('https://microsoft.com'), 'https'));
assert.ok(matchesScheme(URI.parse('http://microsoft.com'), 'http'));
assert.ok(matchesScheme(URI.parse('hTTPs://microsoft.com'), 'https'));
assert.ok(matchesScheme(URI.parse('httP://microsoft.com'), 'http'));
assert.ok(!matchesScheme(URI.parse('https://microsoft.com'), 'http'));
assert.ok(!matchesScheme(URI.parse('htt://microsoft.com'), 'http'));
assert.ok(!matchesScheme(URI.parse('z://microsoft.com'), 'http'));
});
});
......@@ -6,6 +6,7 @@
import { URI } from 'vs/base/common/uri';
import { createDecorator } from 'vs/platform/instantiation/common/instantiation';
import { IDisposable, Disposable } from 'vs/base/common/lifecycle';
import { equalsIgnoreCase, startsWithIgnoreCase } from 'vs/base/common/strings';
export const IOpenerService = createDecorator<IOpenerService>('openerService');
......@@ -35,8 +36,7 @@ export interface IResolvedExternalUri extends IDisposable {
}
export interface IOpener {
open(resource: URI, options?: OpenInternalOptions): Promise<boolean>;
open(resource: URI, options?: OpenExternalOptions): Promise<boolean>;
open(resource: URI | string, options?: OpenInternalOptions | OpenExternalOptions): Promise<boolean>;
}
export interface IExternalOpener {
......@@ -44,7 +44,7 @@ export interface IExternalOpener {
}
export interface IValidator {
shouldOpen(resource: URI): Promise<boolean>;
shouldOpen(resource: URI | string): Promise<boolean>;
}
export interface IExternalUriResolver {
......@@ -83,8 +83,7 @@ export interface IOpenerService {
* @param resource A resource
* @return A promise that resolves when the opening is done.
*/
open(resource: URI, options?: OpenInternalOptions): Promise<boolean>;
open(resource: URI, options?: OpenExternalOptions): Promise<boolean>;
open(resource: URI | string, options?: OpenInternalOptions | OpenExternalOptions): Promise<boolean>;
/**
* Resolve a resource to its external form.
......@@ -101,3 +100,11 @@ export const NullOpenerService: IOpenerService = Object.freeze({
async open() { return false; },
async resolveExternalUri(uri: URI) { return { resolved: uri, dispose() { } }; },
});
export function matchesScheme(target: URI | string, scheme: string) {
if (URI.isUri(target)) {
return equalsIgnoreCase(target.scheme, scheme);
} else {
return startsWithIgnoreCase(target, scheme + ':');
}
}
......@@ -42,9 +42,17 @@ export class MainThreadWindow implements MainThreadWindowShape {
return Promise.resolve(this.hostService.hasFocus);
}
async $openUri(uriComponents: UriComponents, options: IOpenUriOptions): Promise<boolean> {
async $openUri(uriComponents: UriComponents, uriString: string | undefined, options: IOpenUriOptions): Promise<boolean> {
const uri = URI.from(uriComponents);
return this.openerService.open(uri, { openExternal: true, allowTunneling: options.allowTunneling });
let target: URI | string;
if (uriString && URI.parse(uriString).toString() === uri.toString()) {
// called with string and no transformation happened -> keep string
target = uriString;
} else {
// called with URI or transformed -> use uri
target = uri;
}
return this.openerService.open(target, { openExternal: true, allowTunneling: options.allowTunneling });
}
async $asExternalUri(uriComponents: UriComponents, options: IOpenUriOptions): Promise<UriComponents> {
......
......@@ -759,7 +759,7 @@ export interface IOpenUriOptions {
export interface MainThreadWindowShape extends IDisposable {
$getWindowVisibility(): Promise<boolean>;
$openUri(uri: UriComponents, options: IOpenUriOptions): Promise<boolean>;
$openUri(uri: UriComponents, uriString: string | undefined, options: IOpenUriOptions): Promise<boolean>;
$asExternalUri(uri: UriComponents, options: IOpenUriOptions): Promise<UriComponents>;
}
......
......@@ -245,9 +245,9 @@ class OpenNodeModuleFactory implements INodeModuleFactory {
return this.callOriginal(target, options);
}
if (uri.scheme === 'http' || uri.scheme === 'https') {
return mainThreadWindow.$openUri(uri, { allowTunneling: true });
return mainThreadWindow.$openUri(uri, target, { allowTunneling: true });
} else if (uri.scheme === 'mailto' || uri.scheme === this._appUriScheme) {
return mainThreadWindow.$openUri(uri, {});
return mainThreadWindow.$openUri(uri, target, {});
}
return this.callOriginal(target, options);
};
......
......@@ -39,7 +39,9 @@ export class ExtHostWindow implements ExtHostWindowShape {
}
openUri(stringOrUri: string | URI, options: IOpenUriOptions): Promise<boolean> {
let uriAsString: string | undefined;
if (typeof stringOrUri === 'string') {
uriAsString = stringOrUri;
try {
stringOrUri = URI.parse(stringOrUri);
} catch (e) {
......@@ -51,7 +53,7 @@ export class ExtHostWindow implements ExtHostWindowShape {
} else if (stringOrUri.scheme === Schemas.command) {
return Promise.reject(`Invalid scheme '${stringOrUri.scheme}'`);
}
return this._proxy.$openUri(stringOrUri, options);
return this._proxy.$openUri(stringOrUri, uriAsString, options);
}
async asExternalUri(uri: URI, options: IOpenUriOptions): Promise<URI> {
......
......@@ -8,7 +8,6 @@ import * as nls from 'vs/nls';
import { renderMarkdown } from 'vs/base/browser/markdownRenderer';
import { onUnexpectedError } from 'vs/base/common/errors';
import { IDisposable, DisposableStore } from 'vs/base/common/lifecycle';
import { URI } from 'vs/base/common/uri';
import { IOpenerService } from 'vs/platform/opener/common/opener';
import { IResourceLabel, ResourceLabels } from 'vs/workbench/browser/labels';
import { CommentNode, CommentsModel, ResourceWithCommentThreads } from 'vs/workbench/contrib/comments/common/commentModel';
......@@ -127,12 +126,7 @@ export class CommentNodeRenderer implements IListRenderer<ITreeNode<CommentNode>
inline: true,
actionHandler: {
callback: (content) => {
try {
const uri = URI.parse(content);
this.openerService.open(uri).catch(onUnexpectedError);
} catch (err) {
// ignore
}
this.openerService.open(content).catch(onUnexpectedError);
},
disposeables: disposables
}
......
......@@ -28,7 +28,6 @@ import { KeyCode } from 'vs/base/common/keyCodes';
import { dispose, IDisposable, Disposable, DisposableStore } from 'vs/base/common/lifecycle';
import { ISpliceable } from 'vs/base/common/sequence';
import { escapeRegExpCharacters, startsWith } from 'vs/base/common/strings';
import { URI } from 'vs/base/common/uri';
import { localize } from 'vs/nls';
import { IClipboardService } from 'vs/platform/clipboard/common/clipboardService';
import { ICommandService } from 'vs/platform/commands/common/commands';
......@@ -485,15 +484,7 @@ export abstract class AbstractSettingRenderer extends Disposable implements ITre
};
this._onDidClickSettingLink.fire(e);
} else {
let uri: URI | undefined;
try {
uri = URI.parse(content);
} catch (err) {
// ignore
}
if (uri) {
this._openerService.open(uri).catch(onUnexpectedError);
}
this._openerService.open(content).catch(onUnexpectedError);
}
},
disposeables
......
......@@ -266,8 +266,7 @@ export class TerminalLinkHandler {
}
private _handleHypertextLink(url: string): void {
const uri = URI.parse(url);
this._openerService.open(uri, { allowTunneling: !!(this._processManager && this._processManager.remoteAuthority) });
this._openerService.open(url, { allowTunneling: !!(this._processManager && this._processManager.remoteAuthority) });
}
private _isLinkActivationModifierDown(event: MouseEvent): boolean {
......
......@@ -5,11 +5,10 @@
import { Schemas } from 'vs/base/common/network';
import Severity from 'vs/base/common/severity';
import { equalsIgnoreCase } from 'vs/base/common/strings';
import { URI } from 'vs/base/common/uri';
import { localize } from 'vs/nls';
import { IDialogService } from 'vs/platform/dialogs/common/dialogs';
import { IOpenerService } from 'vs/platform/opener/common/opener';
import { IOpenerService, matchesScheme } from 'vs/platform/opener/common/opener';
import { IProductService } from 'vs/platform/product/common/productService';
import { IQuickInputService } from 'vs/platform/quickinput/common/quickInput';
import { IStorageService } from 'vs/platform/storage/common/storage';
......@@ -21,6 +20,7 @@ import {
import { IEditorService } from 'vs/workbench/services/editor/common/editorService';
import { IClipboardService } from 'vs/platform/clipboard/common/clipboardService';
export class OpenerValidatorContributions implements IWorkbenchContribution {
constructor(
@IOpenerService private readonly _openerService: IOpenerService,
......@@ -34,13 +34,16 @@ export class OpenerValidatorContributions implements IWorkbenchContribution {
this._openerService.registerValidator({ shouldOpen: r => this.validateLink(r) });
}
async validateLink(resource: URI): Promise<boolean> {
const { scheme, authority, path, query, fragment } = resource;
if (!equalsIgnoreCase(scheme, Schemas.http) && !equalsIgnoreCase(scheme, Schemas.https)) {
async validateLink(resource: URI | string): Promise<boolean> {
if (!matchesScheme(resource, Schemas.http) && !matchesScheme(resource, Schemas.https)) {
return true;
}
if (typeof resource === 'string') {
resource = URI.parse(resource);
}
const { scheme, authority, path, query, fragment } = resource;
const domainToOpen = `${scheme}://${authority}`;
const { defaultTrustedDomains, trustedDomains } = readTrustedDomains(this._storageService, this._productService);
const allTrustedDomains = [...defaultTrustedDomains, ...trustedDomains];
......
......@@ -8,7 +8,7 @@ import { URI, UriComponents } from 'vs/base/common/uri';
import { IMainProcessService } from 'vs/platform/ipc/electron-browser/mainProcessService';
import { URLHandlerChannel } from 'vs/platform/url/common/urlIpc';
import { URLService } from 'vs/platform/url/node/urlService';
import { IOpenerService } from 'vs/platform/opener/common/opener';
import { IOpenerService, IOpener, matchesScheme } from 'vs/platform/opener/common/opener';
import product from 'vs/platform/product/common/product';
import { registerSingleton } from 'vs/platform/instantiation/common/extensions';
import { IElectronEnvironmentService } from 'vs/workbench/services/electron/electron-browser/electronEnvironmentService';
......@@ -20,7 +20,7 @@ export interface IRelayOpenURLOptions extends IOpenURLOptions {
openExternal?: boolean;
}
export class RelayURLService extends URLService implements IURLHandler {
export class RelayURLService extends URLService implements IURLHandler, IOpener {
private urlService: IURLService;
......@@ -51,11 +51,15 @@ export class RelayURLService extends URLService implements IURLHandler {
return uri.with({ query });
}
async open(resource: URI, options?: IRelayOpenURLOptions): Promise<boolean> {
if (resource.scheme !== product.urlProtocol) {
async open(resource: URI | string, options?: IRelayOpenURLOptions): Promise<boolean> {
if (!matchesScheme(resource, product.urlProtocol)) {
return false;
}
if (typeof resource === 'string') {
resource = URI.parse(resource);
}
return await this.urlService.open(resource, options);
}
......
Markdown is supported
0% .
You are about to add 0 people to the discussion. Proceed with caution.
先完成此消息的编辑!
想要评论请 注册