mirror of
https://github.com/coder/code-server.git
synced 2026-05-09 05:47:26 +02:00
Merge commit 'be3e8236086165e5e45a5a10783823874b3f3ebd' as 'lib/vscode'
This commit is contained in:
41
lib/vscode/src/vs/platform/url/common/url.ts
Normal file
41
lib/vscode/src/vs/platform/url/common/url.ts
Normal file
@@ -0,0 +1,41 @@
|
||||
/*---------------------------------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the MIT License. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
import { URI, UriComponents } from 'vs/base/common/uri';
|
||||
import { createDecorator } from 'vs/platform/instantiation/common/instantiation';
|
||||
import { IDisposable } from 'vs/base/common/lifecycle';
|
||||
|
||||
export const IURLService = createDecorator<IURLService>('urlService');
|
||||
|
||||
export interface IOpenURLOptions {
|
||||
|
||||
/**
|
||||
* If not provided or `false`, signals that the
|
||||
* URL to open did not originate from the product
|
||||
* but outside. As such, a confirmation dialog
|
||||
* might be shown to the user.
|
||||
*/
|
||||
trusted?: boolean;
|
||||
}
|
||||
|
||||
export interface IURLHandler {
|
||||
handleURL(uri: URI, options?: IOpenURLOptions): Promise<boolean>;
|
||||
}
|
||||
|
||||
export interface IURLService {
|
||||
|
||||
readonly _serviceBrand: undefined;
|
||||
|
||||
/**
|
||||
* Create a URL that can be called to trigger IURLhandlers.
|
||||
* The URL that gets passed to the IURLHandlers carries over
|
||||
* any of the provided IURLCreateOption values.
|
||||
*/
|
||||
create(options?: Partial<UriComponents>): URI;
|
||||
|
||||
open(url: URI, options?: IOpenURLOptions): Promise<boolean>;
|
||||
|
||||
registerHandler(handler: IURLHandler): IDisposable;
|
||||
}
|
||||
71
lib/vscode/src/vs/platform/url/common/urlIpc.ts
Normal file
71
lib/vscode/src/vs/platform/url/common/urlIpc.ts
Normal file
@@ -0,0 +1,71 @@
|
||||
/*---------------------------------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the MIT License. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
import { IChannel, IServerChannel, IClientRouter, IConnectionHub, Client } from 'vs/base/parts/ipc/common/ipc';
|
||||
import { URI } from 'vs/base/common/uri';
|
||||
import { Event } from 'vs/base/common/event';
|
||||
import { IURLHandler, IOpenURLOptions } from 'vs/platform/url/common/url';
|
||||
import { CancellationToken } from 'vs/base/common/cancellation';
|
||||
|
||||
export class URLHandlerChannel implements IServerChannel {
|
||||
|
||||
constructor(private handler: IURLHandler) { }
|
||||
|
||||
listen<T>(_: unknown, event: string): Event<T> {
|
||||
throw new Error(`Event not found: ${event}`);
|
||||
}
|
||||
|
||||
call(_: unknown, command: string, arg?: any): Promise<any> {
|
||||
switch (command) {
|
||||
case 'handleURL': return this.handler.handleURL(URI.revive(arg));
|
||||
}
|
||||
|
||||
throw new Error(`Call not found: ${command}`);
|
||||
}
|
||||
}
|
||||
|
||||
export class URLHandlerChannelClient implements IURLHandler {
|
||||
|
||||
constructor(private channel: IChannel) { }
|
||||
|
||||
handleURL(uri: URI, options?: IOpenURLOptions): Promise<boolean> {
|
||||
return this.channel.call('handleURL', uri.toJSON());
|
||||
}
|
||||
}
|
||||
|
||||
export class URLHandlerRouter implements IClientRouter<string> {
|
||||
|
||||
constructor(private next: IClientRouter<string>) { }
|
||||
|
||||
async routeCall(hub: IConnectionHub<string>, command: string, arg?: any, cancellationToken?: CancellationToken): Promise<Client<string>> {
|
||||
if (command !== 'handleURL') {
|
||||
throw new Error(`Call not found: ${command}`);
|
||||
}
|
||||
|
||||
if (arg) {
|
||||
const uri = URI.revive(arg);
|
||||
|
||||
if (uri && uri.query) {
|
||||
const match = /\bwindowId=(\d+)/.exec(uri.query);
|
||||
|
||||
if (match) {
|
||||
const windowId = match[1];
|
||||
const regex = new RegExp(`window:${windowId}`);
|
||||
const connection = hub.connections.find(c => regex.test(c.ctx));
|
||||
|
||||
if (connection) {
|
||||
return connection;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return this.next.routeCall(hub, command, arg, cancellationToken);
|
||||
}
|
||||
|
||||
routeEvent(_: IConnectionHub<string>, event: string): Promise<Client<string>> {
|
||||
throw new Error(`Event not found: ${event}`);
|
||||
}
|
||||
}
|
||||
42
lib/vscode/src/vs/platform/url/common/urlService.ts
Normal file
42
lib/vscode/src/vs/platform/url/common/urlService.ts
Normal file
@@ -0,0 +1,42 @@
|
||||
/*---------------------------------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the MIT License. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
import { IURLService, IURLHandler, IOpenURLOptions } from 'vs/platform/url/common/url';
|
||||
import { URI, UriComponents } from 'vs/base/common/uri';
|
||||
import { first } from 'vs/base/common/async';
|
||||
import { toDisposable, IDisposable, Disposable } from 'vs/base/common/lifecycle';
|
||||
import product from 'vs/platform/product/common/product';
|
||||
|
||||
export abstract class AbstractURLService extends Disposable implements IURLService {
|
||||
|
||||
declare readonly _serviceBrand: undefined;
|
||||
|
||||
private handlers = new Set<IURLHandler>();
|
||||
|
||||
abstract create(options?: Partial<UriComponents>): URI;
|
||||
|
||||
open(uri: URI, options?: IOpenURLOptions): Promise<boolean> {
|
||||
const handlers = [...this.handlers.values()];
|
||||
return first(handlers.map(h => () => h.handleURL(uri, options)), undefined, false).then(val => val || false);
|
||||
}
|
||||
|
||||
registerHandler(handler: IURLHandler): IDisposable {
|
||||
this.handlers.add(handler);
|
||||
return toDisposable(() => this.handlers.delete(handler));
|
||||
}
|
||||
}
|
||||
|
||||
export class NativeURLService extends AbstractURLService {
|
||||
|
||||
create(options?: Partial<UriComponents>): URI {
|
||||
let { authority, path, query, fragment } = options ? options : { authority: undefined, path: undefined, query: undefined, fragment: undefined };
|
||||
|
||||
if (authority && path && path.indexOf('/') !== 0) {
|
||||
path = `/${path}`; // URI validation requires a path if there is an authority
|
||||
}
|
||||
|
||||
return URI.from({ scheme: product.urlProtocol, authority, path, query, fragment });
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,109 @@
|
||||
/*---------------------------------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the MIT License. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
import { Event } from 'vs/base/common/event';
|
||||
import { IEnvironmentMainService } from 'vs/platform/environment/electron-main/environmentMainService';
|
||||
import { IURLService } from 'vs/platform/url/common/url';
|
||||
import product from 'vs/platform/product/common/product';
|
||||
import { app, Event as ElectronEvent } from 'electron';
|
||||
import { URI } from 'vs/base/common/uri';
|
||||
import { IDisposable, DisposableStore, Disposable } from 'vs/base/common/lifecycle';
|
||||
import { IWindowsMainService } from 'vs/platform/windows/electron-main/windows';
|
||||
import { isWindows } from 'vs/base/common/platform';
|
||||
import { disposableTimeout } from 'vs/base/common/async';
|
||||
|
||||
function uriFromRawUrl(url: string): URI | null {
|
||||
try {
|
||||
return URI.parse(url);
|
||||
} catch (e) {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* A listener for URLs that are opened from the OS and handled by VSCode.
|
||||
* Depending on the platform, this works differently:
|
||||
* - Windows: we use `app.setAsDefaultProtocolClient()` to register VSCode with the OS
|
||||
* and additionally add the `open-url` command line argument to identify.
|
||||
* - macOS: we rely on `app.on('open-url')` to be called by the OS
|
||||
* - Linux: we have a special shortcut installed (`resources/linux/code-url-handler.desktop`)
|
||||
* that calls VSCode with the `open-url` command line argument
|
||||
* (https://github.com/microsoft/vscode/pull/56727)
|
||||
*/
|
||||
export class ElectronURLListener {
|
||||
|
||||
private uris: URI[] = [];
|
||||
private retryCount = 0;
|
||||
private flushDisposable: IDisposable = Disposable.None;
|
||||
private disposables = new DisposableStore();
|
||||
|
||||
constructor(
|
||||
initialUrisToHandle: URI[],
|
||||
private readonly urlService: IURLService,
|
||||
windowsMainService: IWindowsMainService,
|
||||
environmentService: IEnvironmentMainService
|
||||
) {
|
||||
|
||||
// the initial set of URIs we need to handle once the window is ready
|
||||
this.uris = initialUrisToHandle;
|
||||
|
||||
// Windows: install as protocol handler
|
||||
if (isWindows) {
|
||||
const windowsParameters = environmentService.isBuilt ? [] : [`"${environmentService.appRoot}"`];
|
||||
windowsParameters.push('--open-url', '--');
|
||||
app.setAsDefaultProtocolClient(product.urlProtocol, process.execPath, windowsParameters);
|
||||
}
|
||||
|
||||
// macOS: listen to `open-url` events from here on to handle
|
||||
const onOpenElectronUrl = Event.map(
|
||||
Event.fromNodeEventEmitter(app, 'open-url', (event: ElectronEvent, url: string) => ({ event, url })),
|
||||
({ event, url }) => {
|
||||
event.preventDefault(); // always prevent default and return the url as string
|
||||
return url;
|
||||
});
|
||||
|
||||
const onOpenUrl = Event.filter<URI | null, URI>(Event.map(onOpenElectronUrl, uriFromRawUrl), (uri): uri is URI => !!uri);
|
||||
onOpenUrl(this.urlService.open, this.urlService, this.disposables);
|
||||
|
||||
// Send initial links to the window once it has loaded
|
||||
const isWindowReady = windowsMainService.getWindows()
|
||||
.filter(w => w.isReady)
|
||||
.length > 0;
|
||||
|
||||
if (isWindowReady) {
|
||||
this.flush();
|
||||
} else {
|
||||
Event.once(windowsMainService.onWindowReady)(this.flush, this, this.disposables);
|
||||
}
|
||||
}
|
||||
|
||||
private async flush(): Promise<void> {
|
||||
if (this.retryCount++ > 10) {
|
||||
return;
|
||||
}
|
||||
|
||||
const uris: URI[] = [];
|
||||
|
||||
for (const uri of this.uris) {
|
||||
const handled = await this.urlService.open(uri);
|
||||
|
||||
if (!handled) {
|
||||
uris.push(uri);
|
||||
}
|
||||
}
|
||||
|
||||
if (uris.length === 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
this.uris = uris;
|
||||
this.flushDisposable = disposableTimeout(() => this.flush(), 500);
|
||||
}
|
||||
|
||||
dispose(): void {
|
||||
this.disposables.dispose();
|
||||
this.flushDisposable.dispose();
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user