44 *--------------------------------------------------------------------------------------------*/
55
66import * as dom from 'vs/base/browser/dom' ;
7- import { Disposable , IDisposable } from 'vs/base/common/lifecycle' ;
7+ import { IDisposable } from 'vs/base/common/lifecycle' ;
88import { LinkedList } from 'vs/base/common/linkedList' ;
99import { parse } from 'vs/base/common/marshalling' ;
1010import { Schemas } from 'vs/base/common/network' ;
@@ -16,46 +16,123 @@ import { CommandsRegistry, ICommandService } from 'vs/platform/commands/common/c
1616import { IOpener , IOpenerService , IValidator , IExternalUriResolver , OpenOptions , ResolveExternalUriOptions , IResolvedExternalUri , IExternalOpener } from 'vs/platform/opener/common/opener' ;
1717import { EditorOpenContext } from 'vs/platform/editor/common/editor' ;
1818
19- export class OpenerService extends Disposable implements IOpenerService {
19+ function hasScheme ( target : URI | URL , scheme : string ) {
20+ if ( URI . isUri ( target ) ) {
21+ return equalsIgnoreCase ( target . scheme , scheme ) ;
22+ } else {
23+ return equalsIgnoreCase ( target . protocol , scheme + ':' ) ;
24+ }
25+ }
26+
27+ export class OpenerService implements IOpenerService {
2028
2129 _serviceBrand : undefined ;
2230
2331 private readonly _openers = new LinkedList < IOpener > ( ) ;
2432 private readonly _validators = new LinkedList < IValidator > ( ) ;
2533 private readonly _resolvers = new LinkedList < IExternalUriResolver > ( ) ;
34+
2635 private _externalOpener : IExternalOpener ;
36+ private _openerAsExternal : IOpener ;
37+ private _openerAsCommand : IOpener ;
38+ private _openerAsEditor : IOpener ;
2739
2840 constructor (
29- @ICodeEditorService private readonly _editorService : ICodeEditorService ,
30- @ICommandService private readonly _commandService : ICommandService ,
41+ @ICodeEditorService editorService : ICodeEditorService ,
42+ @ICommandService commandService : ICommandService ,
3143 ) {
32- super ( ) ;
33-
3444 // Default external opener is going through window.open()
3545 this . _externalOpener = {
3646 openExternal : href => {
3747 dom . windowOpenNoOpener ( href ) ;
38-
3948 return Promise . resolve ( true ) ;
4049 }
4150 } ;
51+
52+ // Default opener: maito, http(s), command, and catch-all-editors
53+ this . _openerAsExternal = {
54+ open : async ( target : URI | URL , options ?: OpenOptions ) => {
55+ if ( options ?. openExternal || hasScheme ( target , Schemas . mailto ) || hasScheme ( target , Schemas . http ) || hasScheme ( target , Schemas . https ) ) {
56+ // open externally
57+ await this . _doOpenExternal ( target , options ) ;
58+ return true ;
59+ }
60+ return false ;
61+ }
62+ } ;
63+
64+ this . _openerAsCommand = {
65+ open : async ( target ) => {
66+ if ( ! hasScheme ( target , Schemas . command ) ) {
67+ return false ;
68+ }
69+ // run command or bail out if command isn't known
70+ if ( ! URI . isUri ( target ) ) {
71+ target = URI . from ( target ) ;
72+ }
73+ if ( ! CommandsRegistry . getCommand ( target . path ) ) {
74+ throw new Error ( `command '${ target . path } ' NOT known` ) ;
75+ }
76+ // execute as command
77+ let args : any = [ ] ;
78+ try {
79+ args = parse ( target . query ) ;
80+ if ( ! Array . isArray ( args ) ) {
81+ args = [ args ] ;
82+ }
83+ } catch ( e ) {
84+ // ignore error
85+ }
86+ await commandService . executeCommand ( target . path , ...args ) ;
87+ return true ;
88+ }
89+ } ;
90+
91+ this . _openerAsEditor = {
92+ open : async ( target , options : OpenOptions ) => {
93+ if ( ! URI . isUri ( target ) ) {
94+ target = URI . from ( target ) ;
95+ }
96+ let selection : { startLineNumber : number ; startColumn : number ; } | undefined = undefined ;
97+ const match = / ^ L ? ( \d + ) (?: , ( \d + ) ) ? / . exec ( target . fragment ) ;
98+ if ( match ) {
99+ // support file:///some/file.js#73,84
100+ // support file:///some/file.js#L73
101+ selection = {
102+ startLineNumber : parseInt ( match [ 1 ] ) ,
103+ startColumn : match [ 2 ] ? parseInt ( match [ 2 ] ) : 1
104+ } ;
105+ // remove fragment
106+ target = target . with ( { fragment : '' } ) ;
107+ }
108+
109+ if ( target . scheme === Schemas . file ) {
110+ target = resources . normalizePath ( target ) ; // workaround for non-normalized paths (https://github.com/Microsoft/vscode/issues/12954)
111+ }
112+
113+ await editorService . openCodeEditor (
114+ { resource : target , options : { selection, context : options ?. fromUserGesture ? EditorOpenContext . USER : EditorOpenContext . API } } ,
115+ editorService . getFocusedCodeEditor ( ) ,
116+ options ?. openToSide
117+ ) ;
118+
119+ return true ;
120+ }
121+ } ;
42122 }
43123
44124 registerOpener ( opener : IOpener ) : IDisposable {
45125 const remove = this . _openers . push ( opener ) ;
46-
47126 return { dispose : remove } ;
48127 }
49128
50129 registerValidator ( validator : IValidator ) : IDisposable {
51130 const remove = this . _validators . push ( validator ) ;
52-
53131 return { dispose : remove } ;
54132 }
55133
56134 registerExternalUriResolver ( resolver : IExternalUriResolver ) : IDisposable {
57135 const remove = this . _resolvers . push ( resolver ) ;
58-
59136 return { dispose : remove } ;
60137 }
61138
@@ -86,7 +163,12 @@ export class OpenerService extends Disposable implements IOpenerService {
86163 }
87164
88165 // use default openers
89- return this . _doOpen ( resource , options ) ;
166+ for ( const opener of [ this . _openerAsExternal , this . _openerAsCommand , this . _openerAsEditor ] ) {
167+ if ( await opener . open ( resource , options ) ) {
168+ break ;
169+ }
170+ }
171+ return true ;
90172 }
91173
92174 async resolveExternalUri ( resource : URI , options ?: ResolveExternalUriOptions ) : Promise < IResolvedExternalUri > {
@@ -100,68 +182,16 @@ export class OpenerService extends Disposable implements IOpenerService {
100182 return { resolved : resource , dispose : ( ) => { } } ;
101183 }
102184
103- private async _doOpen ( resource : URI , options : OpenOptions | undefined ) : Promise < boolean > {
104- const { scheme, path, query, fragment } = resource ;
105-
106- if ( options ?. openExternal || equalsIgnoreCase ( scheme , Schemas . mailto ) || equalsIgnoreCase ( scheme , Schemas . http ) || equalsIgnoreCase ( scheme , Schemas . https ) ) {
107- // open externally
108- return this . _doOpenExternal ( resource , options ) ;
109- }
110-
111- if ( equalsIgnoreCase ( scheme , Schemas . command ) ) {
112- // run command or bail out if command isn't known
113- if ( ! CommandsRegistry . getCommand ( path ) ) {
114- throw new Error ( `command '${ path } ' NOT known` ) ;
115- }
116- // execute as command
117- let args : any = [ ] ;
118- try {
119- args = parse ( query ) ;
120- if ( ! Array . isArray ( args ) ) {
121- args = [ args ] ;
122- }
123- } catch ( e ) {
124- // ignore error
125- }
126-
127- await this . _commandService . executeCommand ( path , ...args ) ;
128-
129- return true ;
130- }
131-
132- // finally open in editor
133- let selection : { startLineNumber : number ; startColumn : number ; } | undefined = undefined ;
134- const match = / ^ L ? ( \d + ) (?: , ( \d + ) ) ? / . exec ( fragment ) ;
135- if ( match ) {
136- // support file:///some/file.js#73,84
137- // support file:///some/file.js#L73
138- selection = {
139- startLineNumber : parseInt ( match [ 1 ] ) ,
140- startColumn : match [ 2 ] ? parseInt ( match [ 2 ] ) : 1
141- } ;
142- // remove fragment
143- resource = resource . with ( { fragment : '' } ) ;
144- }
145-
146- if ( resource . scheme === Schemas . file ) {
147- resource = resources . normalizePath ( resource ) ; // workaround for non-normalized paths (https://github.com/Microsoft/vscode/issues/12954)
185+ private async _doOpenExternal ( resource : URI | URL , options : OpenOptions | undefined ) : Promise < boolean > {
186+ if ( URI . isUri ( resource ) ) {
187+ const { resolved } = await this . resolveExternalUri ( resource , options ) ;
188+ // TODO@Jo neither encodeURI nor toString(true) should be needed
189+ // once we go with URL and not URI
190+ return this . _externalOpener . openExternal ( encodeURI ( resolved . toString ( true ) ) ) ;
191+ } else {
192+ //todo@joh what about resolveExternalUri?
193+ return this . _externalOpener . openExternal ( resource . href ) ;
148194 }
149-
150- await this . _editorService . openCodeEditor (
151- { resource, options : { selection, context : options ?. fromUserGesture ? EditorOpenContext . USER : EditorOpenContext . API } } ,
152- this . _editorService . getFocusedCodeEditor ( ) ,
153- options ?. openToSide
154- ) ;
155-
156- return true ;
157- }
158-
159- private async _doOpenExternal ( resource : URI , options : OpenOptions | undefined ) : Promise < boolean > {
160- const { resolved } = await this . resolveExternalUri ( resource , options ) ;
161-
162- // TODO@Jo neither encodeURI nor toString(true) should be needed
163- // once we go with URL and not URI
164- return this . _externalOpener . openExternal ( encodeURI ( resolved . toString ( true ) ) ) ;
165195 }
166196
167197 dispose ( ) {
0 commit comments