@@ -6,6 +6,8 @@ import * as path from 'path';
6
6
import * as cpp from 'child-process-promise' ;
7
7
import * as compareVersions from 'compare-versions' ;
8
8
9
+ import { ChildProcess } from "child_process" ;
10
+
9
11
import { ExtensionContext } from 'vscode' ;
10
12
import * as vscode from 'vscode' ;
11
13
import { LanguageClient , LanguageClientOptions , RevealOutputChannelOn ,
@@ -14,10 +16,16 @@ import { enableOldServerWorkaround } from './compat'
14
16
15
17
import * as worksheet from './worksheet'
16
18
19
+ import * as rpc from 'vscode-jsonrpc'
20
+ import * as sbtserver from './sbt-server'
21
+
17
22
let extensionContext : ExtensionContext
18
23
let outputChannel : vscode . OutputChannel
19
24
export let client : LanguageClient
20
25
26
+ /** The sbt process that may have been started by this extension */
27
+ let sbtProcess : ChildProcess
28
+
21
29
const sbtVersion = "1.2.3"
22
30
const sbtArtifact = `org.scala-sbt:sbt-launch:${ sbtVersion } `
23
31
const workspaceRoot = `${ vscode . workspace . rootPath } `
@@ -71,27 +79,79 @@ export function activate(context: ExtensionContext) {
71
79
} )
72
80
73
81
} else {
74
- // Check whether `.dotty-ide-artifact` exists. If it does, start the language server,
75
- // otherwise, try propose to start it if there's no build.sbt
76
- if ( fs . existsSync ( languageServerArtifactFile ) ) {
77
- runLanguageServer ( coursierPath , languageServerArtifactFile )
78
- } else if ( isUnconfiguredProject ( ) ) {
79
- vscode . window . showInformationMessage (
80
- "This looks like an unconfigured Scala project. Would you like to start the Dotty IDE?" ,
81
- "Yes" , "No"
82
+ let configuredProject : Thenable < void > = Promise . resolve ( )
83
+ if ( isUnconfiguredProject ( ) ) {
84
+ configuredProject = vscode . window . showInformationMessage (
85
+ "This looks like an unconfigured Scala project. Would you like to start the Dotty IDE?" ,
86
+ "Yes" , "No"
82
87
) . then ( choice => {
83
- if ( choice == "Yes" ) {
84
- fetchAndConfigure ( coursierPath , sbtArtifact , buildSbtFileSource , dottyPluginSbtFileSource ) . then ( ( ) => {
85
- runLanguageServer ( coursierPath , languageServerArtifactFile )
86
- } )
87
- } else {
88
+ if ( choice === "Yes" ) {
89
+ bootstrapSbtProject ( buildSbtFileSource , dottyPluginSbtFileSource )
90
+ return Promise . resolve ( )
91
+ } else if ( choice === "No" ) {
88
92
fs . appendFile ( disableDottyIDEFile , "" , _ => { } )
93
+ return Promise . reject ( )
89
94
}
90
95
} )
91
96
}
97
+
98
+ configuredProject
99
+ . then ( _ => withProgress ( "Configuring Dotty IDE..." , configureIDE ( coursierPath ) ) )
100
+ . then ( _ => runLanguageServer ( coursierPath , languageServerArtifactFile ) )
92
101
}
93
102
}
94
103
104
+ export function deactivate ( ) {
105
+ // If sbt was started by this extension, kill the process.
106
+ // FIXME: This will be a problem for other clients of this server.
107
+ if ( sbtProcess ) {
108
+ sbtProcess . kill ( )
109
+ }
110
+ }
111
+
112
+ /**
113
+ * Display a progress bar with title `title` while `op` completes.
114
+ *
115
+ * @param title The title of the progress bar
116
+ * @param op The thenable that is monitored by the progress bar.
117
+ */
118
+ function withProgress < T > ( title : string , op : Thenable < T > ) : Thenable < T > {
119
+ return vscode . window . withProgress ( {
120
+ location : vscode . ProgressLocation . Window ,
121
+ title : title
122
+ } , _ => op )
123
+ }
124
+
125
+ /** Connect to an sbt server and run `configureIDE`. */
126
+ function configureIDE ( coursierPath : string ) : Thenable < sbtserver . ExecResult > {
127
+
128
+ function offeringToRetry ( client : rpc . MessageConnection , command : string ) : Thenable < sbtserver . ExecResult > {
129
+ return sbtserver . tellSbt ( outputChannel , client , command )
130
+ . then ( success => Promise . resolve ( success ) ,
131
+ _ => {
132
+ outputChannel . show ( )
133
+ return vscode . window . showErrorMessage ( "IDE configuration failed (see logs for details)" , "Retry?" )
134
+ . then ( retry => {
135
+ if ( retry ) return offeringToRetry ( client , command )
136
+ else return Promise . reject ( )
137
+ } )
138
+ } )
139
+ }
140
+
141
+ return withSbtInstance ( outputChannel , coursierPath )
142
+ . then ( client => {
143
+ // `configureIDE` is a command, which means that upon failure, sbt won't tell us anything
144
+ // until sbt/sbt#4370 is fixed.
145
+ // We run `compile` and `test:compile` first because they're tasks (so we get feedback from sbt
146
+ // in case of failure), and we're pretty sure configureIDE will pass if they passed.
147
+ return offeringToRetry ( client , "compile" ) . then ( _ => {
148
+ return offeringToRetry ( client , "test:compile" ) . then ( _ => {
149
+ return offeringToRetry ( client , "configureIDE" )
150
+ } )
151
+ } )
152
+ } )
153
+ }
154
+
95
155
function runLanguageServer ( coursierPath : string , languageServerArtifactFile : string ) {
96
156
fs . readFile ( languageServerArtifactFile , ( err , data ) => {
97
157
if ( err ) throw err
@@ -109,10 +169,34 @@ function runLanguageServer(coursierPath: string, languageServerArtifactFile: str
109
169
} )
110
170
}
111
171
112
- function fetchAndConfigure ( coursierPath : string , sbtArtifact : string , buildSbtFileSource : string , dottyPluginSbtFileSource : string ) {
113
- return fetchWithCoursier ( coursierPath , sbtArtifact ) . then ( ( sbtClasspath ) => {
114
- return configureIDE ( sbtClasspath , buildSbtFileSource , dottyPluginSbtFileSource )
172
+ /**
173
+ * Connects to an existing sbt server, or boots up one instance and connects to it.
174
+ */
175
+ function withSbtInstance ( log : vscode . OutputChannel , coursierPath : string ) : Thenable < rpc . MessageConnection > {
176
+ const serverSocketInfo = path . join ( workspaceRoot , "project" , "target" , "active.json" )
177
+
178
+ if ( ! fs . existsSync ( serverSocketInfo ) ) {
179
+ fetchWithCoursier ( coursierPath , sbtArtifact ) . then ( ( sbtClasspath ) => {
180
+ sbtProcess = cpp . spawn ( "java" , [
181
+ "-Dsbt.log.noformat=true" ,
182
+ "-classpath" , sbtClasspath ,
183
+ "xsbt.boot.Boot"
184
+ ] ) . childProcess
185
+
186
+ // Close stdin, otherwise in case of error sbt will block waiting for the
187
+ // user input to reload or exit the build.
188
+ sbtProcess . stdin . end ( )
189
+
190
+ sbtProcess . stdout . on ( 'data' , data => {
191
+ log . appendLine ( data . toString ( ) )
192
+ } )
193
+ sbtProcess . stderr . on ( 'data' , data => {
194
+ log . appendLine ( data . toString ( ) )
195
+ } )
115
196
} )
197
+ }
198
+
199
+ return sbtserver . connectToSbtServer ( log )
116
200
}
117
201
118
202
function fetchWithCoursier ( coursierPath : string , artifact : string , extra : string [ ] = [ ] ) {
@@ -150,54 +234,12 @@ function fetchWithCoursier(coursierPath: string, artifact: string, extra: string
150
234
} )
151
235
}
152
236
153
- function configureIDE ( sbtClasspath : string ,
154
- buildSbtFileSource : string ,
155
- dottyPluginSbtFileSource : string ) {
156
-
157
- return vscode . window . withProgress ( {
158
- location : vscode . ProgressLocation . Window ,
159
- title : 'Configuring the IDE for Dotty...'
160
- } , _ => {
161
-
162
- // Bootstrap an sbt build
237
+ function bootstrapSbtProject ( buildSbtFileSource : string ,
238
+ dottyPluginSbtFileSource : string ) {
163
239
fs . mkdirSync ( sbtProjectDir )
164
240
fs . appendFileSync ( sbtBuildPropertiesFile , `sbt.version=${ sbtVersion } ` )
165
241
fs . copyFileSync ( buildSbtFileSource , sbtBuildSbtFile )
166
242
fs . copyFileSync ( dottyPluginSbtFileSource , path . join ( sbtProjectDir , "plugins.sbt" ) )
167
-
168
- // Run sbt to configure the IDE.
169
- const sbtPromise =
170
- cpp . spawn ( "java" , [
171
- "-Dsbt.log.noformat=true" ,
172
- "-classpath" , sbtClasspath ,
173
- "xsbt.boot.Boot" ,
174
- "configureIDE"
175
- ] )
176
-
177
- const sbtProc = sbtPromise . childProcess
178
- // Close stdin, otherwise in case of error sbt will block waiting for the
179
- // user input to reload or exit the build.
180
- sbtProc . stdin . end ( )
181
-
182
- sbtProc . stdout . on ( 'data' , ( data : Buffer ) => {
183
- let msg = data . toString ( ) . trim ( )
184
- outputChannel . appendLine ( msg )
185
- } )
186
- sbtProc . stderr . on ( 'data' , ( data : Buffer ) => {
187
- let msg = data . toString ( ) . trim ( )
188
- outputChannel . appendLine ( msg )
189
- } )
190
-
191
- sbtProc . on ( 'close' , ( code : number ) => {
192
- if ( code != 0 ) {
193
- const msg = "Configuring the IDE failed."
194
- outputChannel . appendLine ( msg )
195
- throw new Error ( msg )
196
- }
197
- } )
198
-
199
- return sbtPromise
200
- } )
201
243
}
202
244
203
245
function run ( serverOptions : ServerOptions , isOldServer : boolean ) {
0 commit comments