@@ -22,7 +22,16 @@ let extensionContext: ExtensionContext
22
22
let outputChannel : vscode . OutputChannel
23
23
24
24
/** The sbt process that may have been started by this extension */
25
- let sbtProcess : ChildProcess
25
+ let sbtProcess : ChildProcess | undefined
26
+
27
+ /** The status bar where the show the status of sbt server */
28
+ let sbtStatusBar : vscode . StatusBarItem
29
+
30
+ /** Interval in ms to check that sbt is alive */
31
+ const sbtCheckIntervalMs = 10 * 1000
32
+
33
+ /** A command that we use to check that sbt is still alive. */
34
+ export const nopCommand = "nop"
26
35
27
36
const sbtVersion = "1.2.3"
28
37
const sbtArtifact = `org.scala-sbt:sbt-launch:${ sbtVersion } `
@@ -82,11 +91,56 @@ export function activate(context: ExtensionContext) {
82
91
}
83
92
84
93
configuredProject
85
- . then ( _ => withProgress ( "Configuring Dotty IDE..." , configureIDE ( coursierPath ) ) )
94
+ . then ( _ => connectToSbt ( coursierPath ) )
95
+ . then ( sbt => withProgress ( "Configuring Dotty IDE..." , configureIDE ( sbt ) ) )
86
96
. then ( _ => runLanguageServer ( coursierPath , languageServerArtifactFile ) )
87
97
}
88
98
}
89
99
100
+ /**
101
+ * Connect to sbt server (possibly by starting a new instance) and keep verifying that the
102
+ * connection is still alive. If it dies, restart sbt server.
103
+ */
104
+ function connectToSbt ( coursierPath : string ) : Thenable < rpc . MessageConnection > {
105
+ if ( ! sbtStatusBar ) sbtStatusBar = vscode . window . createStatusBarItem ( vscode . StatusBarAlignment . Right )
106
+ sbtStatusBar . text = "sbt server: connecting $(sync)"
107
+ sbtStatusBar . show ( )
108
+
109
+ return offeringToRetry ( ( ) => {
110
+ return withSbtInstance ( outputChannel , coursierPath ) . then ( connection => {
111
+ markSbtUp ( )
112
+ const interval = setInterval ( ( ) => checkSbt ( interval , connection , coursierPath ) , sbtCheckIntervalMs )
113
+ return connection
114
+ } )
115
+ } , "Couldn't connect to sbt server (see log for details)" )
116
+ }
117
+
118
+ /** Mark sbt server as alive in the status bar */
119
+ function markSbtUp ( timeout ?: NodeJS . Timer ) {
120
+ sbtStatusBar . text = "sbt server: up $(check)"
121
+ if ( timeout ) clearTimeout ( timeout )
122
+ }
123
+
124
+ /** Mark sbt server as dead and try to reconnect */
125
+ function markSbtDownAndReconnect ( coursierPath : string ) {
126
+ sbtStatusBar . text = "sbt server: down $(x)"
127
+ if ( sbtProcess ) {
128
+ sbtProcess . kill ( )
129
+ sbtProcess = undefined
130
+ }
131
+ connectToSbt ( coursierPath )
132
+ }
133
+
134
+ /** Check that sbt is alive, try to reconnect if it is dead. */
135
+ function checkSbt ( interval : NodeJS . Timer , connection : rpc . MessageConnection , coursierPath : string ) {
136
+ sbtserver . tellSbt ( outputChannel , connection , nopCommand )
137
+ . then ( _ => markSbtUp ( ) ,
138
+ _ => {
139
+ clearInterval ( interval )
140
+ markSbtDownAndReconnect ( coursierPath )
141
+ } )
142
+ }
143
+
90
144
export function deactivate ( ) {
91
145
// If sbt was started by this extension, kill the process.
92
146
// FIXME: This will be a problem for other clients of this server.
@@ -109,33 +163,45 @@ function withProgress<T>(title: string, op: Thenable<T>): Thenable<T> {
109
163
}
110
164
111
165
/** Connect to an sbt server and run `configureIDE`. */
112
- function configureIDE ( coursierPath : string ) : Thenable < sbtserver . ExecResult > {
113
-
114
- function offeringToRetry ( client : rpc . MessageConnection , command : string ) : Thenable < sbtserver . ExecResult > {
115
- return sbtserver . tellSbt ( outputChannel , client , command )
116
- . then ( success => Promise . resolve ( success ) ,
117
- _ => {
118
- outputChannel . show ( )
119
- return vscode . window . showErrorMessage ( "IDE configuration failed (see logs for details)" , "Retry?" )
120
- . then ( retry => {
121
- if ( retry ) return offeringToRetry ( client , command )
122
- else return Promise . reject ( )
123
- } )
124
- } )
166
+ function configureIDE ( sbt : rpc . MessageConnection ) : Thenable < sbtserver . ExecResult > {
167
+
168
+ const tellSbt = ( command : string ) => {
169
+ return ( ) => sbtserver . tellSbt ( outputChannel , sbt , command )
125
170
}
126
171
127
- return withSbtInstance ( outputChannel , coursierPath )
128
- . then ( client => {
129
- // `configureIDE` is a command, which means that upon failure, sbt won't tell us anything
130
- // until sbt/sbt#4370 is fixed.
131
- // We run `compile` and `test:compile` first because they're tasks (so we get feedback from sbt
132
- // in case of failure), and we're pretty sure configureIDE will pass if they passed.
133
- return offeringToRetry ( client , "compile" ) . then ( _ => {
134
- return offeringToRetry ( client , "test:compile" ) . then ( _ => {
135
- return offeringToRetry ( client , "configureIDE" )
136
- } )
137
- } )
172
+ const failMessage = "`configureIDE` failed (see log for details)"
173
+
174
+ // `configureIDE` is a command, which means that upon failure, sbt won't tell us anything
175
+ // until sbt/sbt#4370 is fixed.
176
+ // We run `compile` and `test:compile` first because they're tasks (so we get feedback from sbt
177
+ // in case of failure), and we're pretty sure configureIDE will pass if they passed.
178
+ return offeringToRetry ( tellSbt ( "compile" ) , failMessage ) . then ( _ => {
179
+ return offeringToRetry ( tellSbt ( "test:compile" ) , failMessage ) . then ( _ => {
180
+ return offeringToRetry ( tellSbt ( "configureIDE" ) , failMessage )
138
181
} )
182
+ } )
183
+ }
184
+
185
+ /**
186
+ * Present the user with a dialog to retry `op` after a failure, returns its result in case of
187
+ * success.
188
+ *
189
+ * @param op The operation to perform
190
+ * @param failMessage The message to display in the dialog offering to retry `op`.
191
+ * @return A promise that will either resolve to the result of `op`, or a dialog that will let
192
+ * the user retry the operation.
193
+ */
194
+ function offeringToRetry < T > ( op : ( ) => Thenable < T > , failMessage : string ) : Thenable < T > {
195
+ return op ( )
196
+ . then ( success => Promise . resolve ( success ) ,
197
+ _ => {
198
+ outputChannel . show ( )
199
+ return vscode . window . showErrorMessage ( failMessage , "Retry?" )
200
+ . then ( retry => {
201
+ if ( retry ) return offeringToRetry ( op , failMessage )
202
+ else return Promise . reject ( )
203
+ } )
204
+ } )
139
205
}
140
206
141
207
function runLanguageServer ( coursierPath : string , languageServerArtifactFile : string ) {
@@ -155,25 +221,29 @@ function runLanguageServer(coursierPath: string, languageServerArtifactFile: str
155
221
} )
156
222
}
157
223
224
+ function startNewSbtInstance ( log : vscode . OutputChannel , coursierPath : string ) {
225
+ fetchWithCoursier ( coursierPath , sbtArtifact ) . then ( ( sbtClasspath ) => {
226
+ sbtProcess = cpp . spawn ( "java" , [
227
+ "-classpath" , sbtClasspath ,
228
+ "xsbt.boot.Boot"
229
+ ] ) . childProcess
230
+ sbtProcess . stdout . on ( 'data' , data => {
231
+ log . append ( data . toString ( ) )
232
+ } )
233
+ sbtProcess . stderr . on ( 'data' , data => {
234
+ log . append ( data . toString ( ) )
235
+ } )
236
+ } )
237
+ }
238
+
158
239
/**
159
240
* Connects to an existing sbt server, or boots up one instance and connects to it.
160
241
*/
161
242
function withSbtInstance ( log : vscode . OutputChannel , coursierPath : string ) : Thenable < rpc . MessageConnection > {
162
243
const serverSocketInfo = path . join ( workspaceRoot , "project" , "target" , "active.json" )
163
244
164
245
if ( ! fs . existsSync ( serverSocketInfo ) ) {
165
- fetchWithCoursier ( coursierPath , sbtArtifact ) . then ( ( sbtClasspath ) => {
166
- sbtProcess = cpp . spawn ( "java" , [
167
- "-classpath" , sbtClasspath ,
168
- "xsbt.boot.Boot"
169
- ] ) . childProcess
170
- sbtProcess . stdout . on ( 'data' , data => {
171
- log . append ( data . toString ( ) )
172
- } )
173
- sbtProcess . stderr . on ( 'data' , data => {
174
- log . append ( data . toString ( ) )
175
- } )
176
- } )
246
+ startNewSbtInstance ( log , coursierPath )
177
247
}
178
248
179
249
return sbtserver . connectToSbtServer ( log )
0 commit comments