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