openerService.js 12 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253
  1. /*---------------------------------------------------------------------------------------------
  2. * Copyright (c) Microsoft Corporation. All rights reserved.
  3. * Licensed under the MIT License. See License.txt in the project root for license information.
  4. *--------------------------------------------------------------------------------------------*/
  5. var __decorate = (this && this.__decorate) || function (decorators, target, key, desc) {
  6. var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
  7. if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
  8. else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
  9. return c > 3 && r && Object.defineProperty(target, key, r), r;
  10. };
  11. var __param = (this && this.__param) || function (paramIndex, decorator) {
  12. return function (target, key) { decorator(target, key, paramIndex); }
  13. };
  14. var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
  15. function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
  16. return new (P || (P = Promise))(function (resolve, reject) {
  17. function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
  18. function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
  19. function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
  20. step((generator = generator.apply(thisArg, _arguments || [])).next());
  21. });
  22. };
  23. import * as dom from '../../../base/browser/dom.js';
  24. import { CancellationToken } from '../../../base/common/cancellation.js';
  25. import { LinkedList } from '../../../base/common/linkedList.js';
  26. import { ResourceMap } from '../../../base/common/map.js';
  27. import { parse } from '../../../base/common/marshalling.js';
  28. import { Schemas } from '../../../base/common/network.js';
  29. import { normalizePath } from '../../../base/common/resources.js';
  30. import { URI } from '../../../base/common/uri.js';
  31. import { ICodeEditorService } from './codeEditorService.js';
  32. import { ICommandService } from '../../../platform/commands/common/commands.js';
  33. import { EditorOpenContext } from '../../../platform/editor/common/editor.js';
  34. import { matchesScheme, matchesSomeScheme } from '../../../platform/opener/common/opener.js';
  35. let CommandOpener = class CommandOpener {
  36. constructor(_commandService) {
  37. this._commandService = _commandService;
  38. }
  39. open(target, options) {
  40. return __awaiter(this, void 0, void 0, function* () {
  41. if (!matchesScheme(target, Schemas.command)) {
  42. return false;
  43. }
  44. if (!(options === null || options === void 0 ? void 0 : options.allowCommands)) {
  45. // silently ignore commands when command-links are disabled, also
  46. // surpress other openers by returning TRUE
  47. return true;
  48. }
  49. // run command or bail out if command isn't known
  50. if (typeof target === 'string') {
  51. target = URI.parse(target);
  52. }
  53. // execute as command
  54. let args = [];
  55. try {
  56. args = parse(decodeURIComponent(target.query));
  57. }
  58. catch (_a) {
  59. // ignore and retry
  60. try {
  61. args = parse(target.query);
  62. }
  63. catch (_b) {
  64. // ignore error
  65. }
  66. }
  67. if (!Array.isArray(args)) {
  68. args = [args];
  69. }
  70. yield this._commandService.executeCommand(target.path, ...args);
  71. return true;
  72. });
  73. }
  74. };
  75. CommandOpener = __decorate([
  76. __param(0, ICommandService)
  77. ], CommandOpener);
  78. let EditorOpener = class EditorOpener {
  79. constructor(_editorService) {
  80. this._editorService = _editorService;
  81. }
  82. open(target, options) {
  83. return __awaiter(this, void 0, void 0, function* () {
  84. if (typeof target === 'string') {
  85. target = URI.parse(target);
  86. }
  87. let selection = undefined;
  88. const match = /^L?(\d+)(?:,(\d+))?/.exec(target.fragment);
  89. if (match) {
  90. // support file:///some/file.js#73,84
  91. // support file:///some/file.js#L73
  92. selection = {
  93. startLineNumber: parseInt(match[1]),
  94. startColumn: match[2] ? parseInt(match[2]) : 1
  95. };
  96. // remove fragment
  97. target = target.with({ fragment: '' });
  98. }
  99. if (target.scheme === Schemas.file) {
  100. target = normalizePath(target); // workaround for non-normalized paths (https://github.com/microsoft/vscode/issues/12954)
  101. }
  102. yield this._editorService.openCodeEditor({
  103. resource: target,
  104. options: Object.assign({ selection, context: (options === null || options === void 0 ? void 0 : options.fromUserGesture) ? EditorOpenContext.USER : EditorOpenContext.API }, options === null || options === void 0 ? void 0 : options.editorOptions)
  105. }, this._editorService.getFocusedCodeEditor(), options === null || options === void 0 ? void 0 : options.openToSide);
  106. return true;
  107. });
  108. }
  109. };
  110. EditorOpener = __decorate([
  111. __param(0, ICodeEditorService)
  112. ], EditorOpener);
  113. let OpenerService = class OpenerService {
  114. constructor(editorService, commandService) {
  115. this._openers = new LinkedList();
  116. this._validators = new LinkedList();
  117. this._resolvers = new LinkedList();
  118. this._resolvedUriTargets = new ResourceMap(uri => uri.with({ path: null, fragment: null, query: null }).toString());
  119. this._externalOpeners = new LinkedList();
  120. // Default external opener is going through window.open()
  121. this._defaultExternalOpener = {
  122. openExternal: (href) => __awaiter(this, void 0, void 0, function* () {
  123. // ensure to open HTTP/HTTPS links into new windows
  124. // to not trigger a navigation. Any other link is
  125. // safe to be set as HREF to prevent a blank window
  126. // from opening.
  127. if (matchesSomeScheme(href, Schemas.http, Schemas.https)) {
  128. dom.windowOpenNoOpener(href);
  129. }
  130. else {
  131. window.location.href = href;
  132. }
  133. return true;
  134. })
  135. };
  136. // Default opener: any external, maito, http(s), command, and catch-all-editors
  137. this._openers.push({
  138. open: (target, options) => __awaiter(this, void 0, void 0, function* () {
  139. if ((options === null || options === void 0 ? void 0 : options.openExternal) || matchesSomeScheme(target, Schemas.mailto, Schemas.http, Schemas.https, Schemas.vsls)) {
  140. // open externally
  141. yield this._doOpenExternal(target, options);
  142. return true;
  143. }
  144. return false;
  145. })
  146. });
  147. this._openers.push(new CommandOpener(commandService));
  148. this._openers.push(new EditorOpener(editorService));
  149. }
  150. registerOpener(opener) {
  151. const remove = this._openers.unshift(opener);
  152. return { dispose: remove };
  153. }
  154. registerValidator(validator) {
  155. const remove = this._validators.push(validator);
  156. return { dispose: remove };
  157. }
  158. registerExternalUriResolver(resolver) {
  159. const remove = this._resolvers.push(resolver);
  160. return { dispose: remove };
  161. }
  162. setDefaultExternalOpener(externalOpener) {
  163. this._defaultExternalOpener = externalOpener;
  164. }
  165. registerExternalOpener(opener) {
  166. const remove = this._externalOpeners.push(opener);
  167. return { dispose: remove };
  168. }
  169. open(target, options) {
  170. var _a;
  171. return __awaiter(this, void 0, void 0, function* () {
  172. // check with contributed validators
  173. const targetURI = typeof target === 'string' ? URI.parse(target) : target;
  174. // validate against the original URI that this URI resolves to, if one exists
  175. const validationTarget = (_a = this._resolvedUriTargets.get(targetURI)) !== null && _a !== void 0 ? _a : target;
  176. for (const validator of this._validators) {
  177. if (!(yield validator.shouldOpen(validationTarget))) {
  178. return false;
  179. }
  180. }
  181. // check with contributed openers
  182. for (const opener of this._openers) {
  183. const handled = yield opener.open(target, options);
  184. if (handled) {
  185. return true;
  186. }
  187. }
  188. return false;
  189. });
  190. }
  191. resolveExternalUri(resource, options) {
  192. return __awaiter(this, void 0, void 0, function* () {
  193. for (const resolver of this._resolvers) {
  194. try {
  195. const result = yield resolver.resolveExternalUri(resource, options);
  196. if (result) {
  197. if (!this._resolvedUriTargets.has(result.resolved)) {
  198. this._resolvedUriTargets.set(result.resolved, resource);
  199. }
  200. return result;
  201. }
  202. }
  203. catch (_a) {
  204. // noop
  205. }
  206. }
  207. throw new Error('Could not resolve external URI: ' + resource.toString());
  208. });
  209. }
  210. _doOpenExternal(resource, options) {
  211. return __awaiter(this, void 0, void 0, function* () {
  212. //todo@jrieken IExternalUriResolver should support `uri: URI | string`
  213. const uri = typeof resource === 'string' ? URI.parse(resource) : resource;
  214. let externalUri;
  215. try {
  216. externalUri = (yield this.resolveExternalUri(uri, options)).resolved;
  217. }
  218. catch (_a) {
  219. externalUri = uri;
  220. }
  221. let href;
  222. if (typeof resource === 'string' && uri.toString() === externalUri.toString()) {
  223. // open the url-string AS IS
  224. href = resource;
  225. }
  226. else {
  227. // open URI using the toString(noEncode)+encodeURI-trick
  228. href = encodeURI(externalUri.toString(true));
  229. }
  230. if (options === null || options === void 0 ? void 0 : options.allowContributedOpeners) {
  231. const preferredOpenerId = typeof (options === null || options === void 0 ? void 0 : options.allowContributedOpeners) === 'string' ? options === null || options === void 0 ? void 0 : options.allowContributedOpeners : undefined;
  232. for (const opener of this._externalOpeners) {
  233. const didOpen = yield opener.openExternal(href, {
  234. sourceUri: uri,
  235. preferredOpenerId,
  236. }, CancellationToken.None);
  237. if (didOpen) {
  238. return true;
  239. }
  240. }
  241. }
  242. return this._defaultExternalOpener.openExternal(href, { sourceUri: uri }, CancellationToken.None);
  243. });
  244. }
  245. dispose() {
  246. this._validators.clear();
  247. }
  248. };
  249. OpenerService = __decorate([
  250. __param(0, ICodeEditorService),
  251. __param(1, ICommandService)
  252. ], OpenerService);
  253. export { OpenerService };