@@ -47,9 +47,9 @@ export async function activate(context: ExtensionContext) {
47
47
} )
48
48
) ;
49
49
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 ( ) ;
53
53
54
54
// This function is called when the extension is activated or when the
55
55
// language server is restarted.
@@ -82,27 +82,27 @@ export async function activate(context: ExtensionContext) {
82
82
args . push ( `--plugins=${ Array . from ( plugins ) . join ( "," ) } ` ) ;
83
83
}
84
84
85
- outputChannel . appendLine ( `Starting language server with ${ plugins . size } plugin(s)...` ) ;
86
- let run : ServerOptions = { command : "stree" , args } ;
87
-
88
85
// 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
90
87
// it is, then we'll run bundle exec stree. This is good, because it'll
91
88
// ensure that we get the correct version of the gem. If it's not in the
92
89
// bundle or there is no bundle, then we'll just run the global stree. This
93
90
// might be correct in the end if the right environment variables are set,
94
91
// 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)
104
102
}
105
103
104
+ outputChannel . appendLine ( `Starting language server with ${ where } stree and ${ plugins . size } plugin(s)...` ) ;
105
+
106
106
// Here, we instantiate the language client. This is the object that is
107
107
// responsible for the communication and management of the Ruby subprocess.
108
108
languageClient = new LanguageClient ( "Syntax Tree" , { run, debug : run } , {
@@ -112,16 +112,39 @@ export async function activate(context: ExtensionContext) {
112
112
outputChannel
113
113
} ) ;
114
114
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
+ }
117
135
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
+ }
125
148
}
126
149
127
150
// This function is called as part of the shutdown or restart process. It's
@@ -142,6 +165,22 @@ export async function activate(context: ExtensionContext) {
142
165
await stopLanguageServer ( ) ;
143
166
await startLanguageServer ( ) ;
144
167
}
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 ( ) ;
145
184
}
146
185
147
186
// This is the expected top-level export that is called by VSCode when the
0 commit comments