@@ -47,9 +47,9 @@ export async function activate(context: ExtensionContext) {
4747 } )
4848 ) ;
4949
50- // We're returning a Promise from this function that will start the Ruby
51- // subprocess .
52- await startLanguageServer ( ) ;
50+ // If there's an open folder, use it as cwd when spawning commands
51+ // to promote correct package & language versioning .
52+ const getCWD = ( ) => workspace . workspaceFolders ?. [ 0 ] ?. uri ?. fsPath || process . cwd ( ) ;
5353
5454 // This function is called when the extension is activated or when the
5555 // language server is restarted.
@@ -82,27 +82,27 @@ export async function activate(context: ExtensionContext) {
8282 args . push ( `--plugins=${ Array . from ( plugins ) . join ( "," ) } ` ) ;
8383 }
8484
85- outputChannel . appendLine ( `Starting language server with ${ plugins . size } plugin(s)...` ) ;
86- let run : ServerOptions = { command : "stree" , args } ;
87-
8885 // There's a bit of complexity here. Basically, if there's an open folder,
89- // then w 're going to check if the syntax_tree gem is inside the bundle. If
86+ // then we 're going to check if the syntax_tree gem is inside the bundle. If
9087 // it is, then we'll run bundle exec stree. This is good, because it'll
9188 // ensure that we get the correct version of the gem. If it's not in the
9289 // bundle or there is no bundle, then we'll just run the global stree. This
9390 // might be correct in the end if the right environment variables are set,
9491 // but it's a bit of a prayer.
95- if ( workspace . workspaceFolders ) {
96- const cwd = workspace . workspaceFolders ! [ 0 ] . uri . fsPath ;
97-
98- try {
99- await promiseExec ( "bundle show syntax_tree" , { cwd } ) ;
100- run = { command : "bundle" , args : [ "exec" , "stree" , "lsp" ] , options : { cwd } } ;
101- } catch {
102- outputChannel . appendLine ( "No bundled syntax_tree, running global stree." ) ;
103- }
92+ const cwd = getCWD ( ) ;
93+ let run : ServerOptions = { command : "stree" , args } ;
94+ let where = 'global' ;
95+
96+ try {
97+ await promiseExec ( "bundle show syntax_tree" , { cwd } ) ;
98+ run = { command : "bundle" , args : [ "exec" , "stree" , "lsp" ] , options : { cwd } } ;
99+ where = 'bundled' ;
100+ } catch {
101+ // No-op (just keep using the global stree)
104102 }
105103
104+ outputChannel . appendLine ( `Starting language server with ${ where } stree and ${ plugins . size } plugin(s)...` ) ;
105+
106106 // Here, we instantiate the language client. This is the object that is
107107 // responsible for the communication and management of the Ruby subprocess.
108108 languageClient = new LanguageClient ( "Syntax Tree" , { run, debug : run } , {
@@ -112,16 +112,39 @@ export async function activate(context: ExtensionContext) {
112112 outputChannel
113113 } ) ;
114114
115- // Here we're going to wait for the language server to start.
116- await languageClient . start ( ) ;
115+ try {
116+ // Here we're going to wait for the language server to start.
117+ await languageClient . start ( ) ;
118+ // Finally, now that the language server has been properly started, we can
119+ // add the various features to the extension. Each of them in turn
120+ // implements Disposable so that they clean up their own resources.
121+ visualizer = new Visualize ( languageClient , outputChannel ) ;
122+ context . subscriptions . push (
123+ visualizer
124+ ) ;
125+ } catch ( e : any ) {
126+ languageClient = null ;
127+ const items = [ 'Restart' ]
128+ let msg = 'Something went wrong.' ;
129+ if ( typeof e === 'string' ) {
130+ if ( / E N O E N T / . test ( e ) ) {
131+ msg = 'Command not found. Is the syntax_tree RubyGem installed?' ;
132+ items . unshift ( 'Install Gem' ) ;
133+ }
134+ }
117135
118- // Finally, now that the language server has been properly started, we can
119- // add the various features to the extension. Each of them in turn
120- // implements Disposable so that they clean up their own resources.
121- visualizer = new Visualize ( languageClient , outputChannel ) ;
122- context . subscriptions . push (
123- visualizer
124- ) ;
136+ const action = await window . showErrorMessage ( msg , ...items ) ;
137+ switch ( action ) {
138+ case 'Install Gem' :
139+ installGem ( ) ;
140+ break ;
141+ case 'Restart' :
142+ startLanguageServer ( ) ;
143+ break ;
144+ }
145+ if ( action === 'Restart' ) {
146+ }
147+ }
125148 }
126149
127150 // This function is called as part of the shutdown or restart process. It's
@@ -142,6 +165,22 @@ export async function activate(context: ExtensionContext) {
142165 await stopLanguageServer ( ) ;
143166 await startLanguageServer ( ) ;
144167 }
168+
169+ // This function is called when the user wants to recover from ENOENT
170+ // on start. It starts the language server afterward.
171+ async function installGem ( ) {
172+ const cwd = getCWD ( ) ;
173+ try {
174+ await promiseExec ( "gem install syntax_tree" , { cwd } ) ;
175+ startLanguageServer ( ) ;
176+ } catch ( e ) {
177+ outputChannel . appendLine ( "Error installing gem: " + e ) ;
178+ }
179+ }
180+
181+ // We're returning a Promise from this function that will start the Ruby
182+ // subprocess.
183+ await startLanguageServer ( ) ;
145184}
146185
147186// This is the expected top-level export that is called by VSCode when the
0 commit comments