-
Notifications
You must be signed in to change notification settings - Fork 28k
/
opener.ts
175 lines (146 loc) · 5.84 KB
/
opener.ts
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import { CancellationToken } from 'vs/base/common/cancellation';
import { Disposable, IDisposable } from 'vs/base/common/lifecycle';
import { equalsIgnoreCase, startsWithIgnoreCase } from 'vs/base/common/strings';
import { URI } from 'vs/base/common/uri';
import { IEditorOptions, ITextEditorSelection } from 'vs/platform/editor/common/editor';
import { createDecorator } from 'vs/platform/instantiation/common/instantiation';
export const IOpenerService = createDecorator<IOpenerService>('openerService');
export type OpenInternalOptions = {
/**
* Signals that the intent is to open an editor to the side
* of the currently active editor.
*/
readonly openToSide?: boolean;
/**
* Extra editor options to apply in case an editor is used to open.
*/
readonly editorOptions?: IEditorOptions;
/**
* Signals that the editor to open was triggered through a user
* action, such as keyboard or mouse usage.
*/
readonly fromUserGesture?: boolean;
/**
* Allow command links to be handled.
*/
readonly allowCommands?: boolean;
};
export type OpenExternalOptions = {
readonly openExternal?: boolean;
readonly allowTunneling?: boolean;
readonly allowContributedOpeners?: boolean | string;
readonly fromWorkspace?: boolean;
};
export type OpenOptions = OpenInternalOptions & OpenExternalOptions;
export type ResolveExternalUriOptions = { readonly allowTunneling?: boolean };
export interface IResolvedExternalUri extends IDisposable {
resolved: URI;
}
export interface IOpener {
open(resource: URI | string, options?: OpenInternalOptions | OpenExternalOptions): Promise<boolean>;
}
export interface IExternalOpener {
openExternal(href: string, ctx: { sourceUri: URI; preferredOpenerId?: string }, token: CancellationToken): Promise<boolean>;
dispose?(): void;
}
export interface IValidator {
shouldOpen(resource: URI | string, openOptions?: OpenOptions): Promise<boolean>;
}
export interface IExternalUriResolver {
resolveExternalUri(resource: URI, options?: OpenOptions): Promise<{ resolved: URI; dispose(): void } | undefined>;
}
export interface IOpenerService {
readonly _serviceBrand: undefined;
/**
* Register a participant that can handle the open() call.
*/
registerOpener(opener: IOpener): IDisposable;
/**
* Register a participant that can validate if the URI resource be opened.
* Validators are run before openers.
*/
registerValidator(validator: IValidator): IDisposable;
/**
* Register a participant that can resolve an external URI resource to be opened.
*/
registerExternalUriResolver(resolver: IExternalUriResolver): IDisposable;
/**
* Sets the handler for opening externally. If not provided,
* a default handler will be used.
*/
setDefaultExternalOpener(opener: IExternalOpener): void;
/**
* Registers a new opener external resources openers.
*/
registerExternalOpener(opener: IExternalOpener): IDisposable;
/**
* Opens a resource, like a webaddress, a document uri, or executes command.
*
* @param resource A resource
* @return A promise that resolves when the opening is done.
*/
open(resource: URI | string, options?: OpenInternalOptions | OpenExternalOptions): Promise<boolean>;
/**
* Resolve a resource to its external form.
* @throws whenever resolvers couldn't resolve this resource externally.
*/
resolveExternalUri(resource: URI, options?: ResolveExternalUriOptions): Promise<IResolvedExternalUri>;
}
export const NullOpenerService = Object.freeze({
_serviceBrand: undefined,
registerOpener() { return Disposable.None; },
registerValidator() { return Disposable.None; },
registerExternalUriResolver() { return Disposable.None; },
setDefaultExternalOpener() { },
registerExternalOpener() { return Disposable.None; },
async open() { return false; },
async resolveExternalUri(uri: URI) { return { resolved: uri, dispose() { } }; },
} as IOpenerService);
export function matchesScheme(target: URI | string, scheme: string): boolean {
if (URI.isUri(target)) {
return equalsIgnoreCase(target.scheme, scheme);
} else {
return startsWithIgnoreCase(target, scheme + ':');
}
}
export function matchesSomeScheme(target: URI | string, ...schemes: string[]): boolean {
return schemes.some(scheme => matchesScheme(target, scheme));
}
/**
* Encodes selection into the `URI`.
*
* IMPORTANT: you MUST use `extractSelection` to separate the selection
* again from the original `URI` before passing the `URI` into any
* component that is not aware of selections.
*/
export function withSelection(uri: URI, selection: ITextEditorSelection): URI {
return uri.with({ fragment: `${selection.startLineNumber},${selection.startColumn}${selection.endLineNumber ? `-${selection.endLineNumber}${selection.endColumn ? `,${selection.endColumn}` : ''}` : ''}` });
}
/**
* file:///some/file.js#73
* file:///some/file.js#L73
* file:///some/file.js#73,84
* file:///some/file.js#L73,84
* file:///some/file.js#73-83
* file:///some/file.js#L73-L83
* file:///some/file.js#73,84-83,52
* file:///some/file.js#L73,84-L83,52
*/
export function extractSelection(uri: URI): { selection: ITextEditorSelection | undefined; uri: URI } {
let selection: ITextEditorSelection | undefined = undefined;
const match = /^L?(\d+)(?:,(\d+))?(-L?(\d+)(?:,(\d+))?)?/.exec(uri.fragment);
if (match) {
selection = {
startLineNumber: parseInt(match[1]),
startColumn: match[2] ? parseInt(match[2]) : 1,
endLineNumber: match[4] ? parseInt(match[4]) : undefined,
endColumn: match[4] ? (match[5] ? parseInt(match[5]) : 1) : undefined
};
uri = uri.with({ fragment: '' });
}
return { selection, uri };
}