1
1
import fs from "fs" ;
2
2
import globby from "globby" ;
3
3
import os from "os" ;
4
+ import "reflect-metadata" ;
4
5
import { container } from "tsyringe" ;
5
6
import util from "util" ;
6
- import { Connection } from "vscode-languageserver" ;
7
7
import { URI } from "vscode-uri" ;
8
8
import Parser , { Tree } from "web-tree-sitter" ;
9
9
import { ICancellationToken } from "../cancellation" ;
@@ -17,7 +17,7 @@ import {
17
17
IPossibleImportsCache ,
18
18
PossibleImportsCache ,
19
19
} from "../util/possibleImportsCache" ;
20
- import { Settings } from "../util/settings" ;
20
+ import { getDefaultSettings , IClientSettings } from "../util/settings" ;
21
21
import { Diagnostic } from "./diagnostics" ;
22
22
import { TypeCache } from "./typeCache" ;
23
23
import {
@@ -26,7 +26,8 @@ import {
26
26
TypeChecker ,
27
27
} from "./typeChecker" ;
28
28
import chokidar from "chokidar" ;
29
- import { CommandManager } from "../commandManager" ;
29
+ import { Connection } from "vscode-languageserver" ;
30
+ import { loadParser } from "../parser" ;
30
31
31
32
const readFile = util . promisify ( fs . readFile ) ;
32
33
@@ -87,6 +88,7 @@ export interface IProgram {
87
88
sourceFile : ISourceFile ,
88
89
importableModuleName : string ,
89
90
) : ISourceFile | undefined ;
91
+ getSourceFiles ( ) : ISourceFile [ ] ;
90
92
getForest ( synchronize ?: boolean ) : IForest ;
91
93
getRootPath ( ) : URI ;
92
94
getTypeCache ( ) : TypeCache ;
@@ -135,16 +137,39 @@ interface IElmPackage extends IElmProject {
135
137
exposedModules : Set < string > ;
136
138
}
137
139
140
+ export async function createProgram (
141
+ rootPath : URI ,
142
+ progressCallback ?: ( percent : number ) => void ,
143
+ programHost ?: IProgramHost ,
144
+ settings ?: IClientSettings ,
145
+ ) : Promise < IProgram > {
146
+ const program = new Program ( rootPath , programHost , settings ) ;
147
+ await program . init (
148
+ progressCallback ??
149
+ ( ( ) : void => {
150
+ //
151
+ } ) ,
152
+ ) ;
153
+ return program ;
154
+ }
155
+
138
156
export interface IProgramHost {
139
157
readFile ( uri : string ) : Promise < string > ;
140
158
readDirectory ( uri : string ) : Promise < string [ ] > ;
141
159
watchFile ( uri : string , callback : ( ) => void ) : void ;
160
+ logger : {
161
+ info ( message : string ) : void ;
162
+ warn ( message : string ) : void ;
163
+ error ( message : string ) : void ;
164
+ } ;
165
+ handleError ( message : string ) : void ;
166
+ onServerDidRestart (
167
+ handler : ( progressReporter : ( progress : number ) => void ) => Promise < void > ,
168
+ ) : void ;
142
169
}
143
170
144
171
export class Program implements IProgram {
145
- private parser : Parser ;
146
- private connection : Connection ;
147
- private settings : Settings ;
172
+ private parser ! : Parser ;
148
173
private typeCache : TypeCache ;
149
174
private typeChecker : TypeChecker | undefined ;
150
175
private dirty = true ;
@@ -157,24 +182,27 @@ export class Program implements IProgram {
157
182
private resolvedPackageCache = new Map < string , IElmPackage > ( ) ;
158
183
private host : IProgramHost ;
159
184
private filesWatching = new Set < string > ( ) ;
185
+ private settings : IClientSettings ;
160
186
161
- constructor ( private rootPath : URI , programHost ?: IProgramHost ) {
162
- this . settings = container . resolve ( "Settings" ) ;
163
- this . connection = container . resolve ( "Connection" ) ;
164
- this . parser = container . resolve ( "Parser" ) ;
165
- this . connection . console . info (
166
- `Starting language server for folder: ${ this . rootPath . toString ( ) } ` ,
167
- ) ;
168
-
187
+ constructor (
188
+ private rootPath : URI ,
189
+ programHost ?: IProgramHost ,
190
+ settings ?: IClientSettings ,
191
+ ) {
169
192
this . typeCache = new TypeCache ( ) ;
170
193
this . possibleImportsCache = new PossibleImportsCache ( ) ;
171
194
this . operatorsCache = new Map < string , DefinitionResult > ( ) ;
172
195
this . diagnosticsCache = new Map < string , Diagnostic [ ] > ( ) ;
173
196
this . host = programHost ?? createNodeProgramHost ( ) ;
197
+ this . settings = settings ?? getDefaultSettings ( ) ;
198
+
199
+ this . host . logger . info (
200
+ `Starting language server for folder: ${ this . rootPath . toString ( ) } ` ,
201
+ ) ;
174
202
}
175
203
176
204
public async init (
177
- progressCallback : ( percent : number ) => void ,
205
+ progressCallback ? : ( percent : number ) => void ,
178
206
) : Promise < void > {
179
207
await this . initWorkspace ( progressCallback ) ;
180
208
}
@@ -215,6 +243,10 @@ export class Program implements IProgram {
215
243
}
216
244
}
217
245
246
+ public getSourceFiles ( ) : ISourceFile [ ] {
247
+ return Array . from ( this . getForest ( ) . treeMap . values ( ) ) ;
248
+ }
249
+
218
250
public getForest ( synchronize = true ) : IForest {
219
251
if ( this . dirty && synchronize ) {
220
252
this . forest . synchronize ( ) ;
@@ -238,7 +270,10 @@ export class Program implements IProgram {
238
270
this . dirty = false ;
239
271
}
240
272
241
- return this . typeChecker ?? ( this . typeChecker = createTypeChecker ( this ) ) ;
273
+ return (
274
+ this . typeChecker ??
275
+ ( this . typeChecker = createTypeChecker ( this , this . host ) )
276
+ ) ;
242
277
}
243
278
244
279
public markAsDirty ( ) : void {
@@ -323,41 +358,34 @@ export class Program implements IProgram {
323
358
}
324
359
325
360
private async initWorkspace (
326
- progressCallback : ( percent : number ) => void ,
361
+ progressCallback ? : ( percent : number ) => void ,
327
362
) : Promise < void > {
328
- const clientSettings = await this . settings . getClientSettings ( ) ;
363
+ if ( ! container . isRegistered ( "Parser" ) ) {
364
+ await loadParser ( this . host ) ;
365
+ }
366
+
367
+ this . parser = container . resolve ( "Parser" ) ;
368
+
329
369
let progress = 0 ;
330
370
let elmVersion ;
331
371
try {
332
- elmVersion = utils . getElmVersion (
333
- clientSettings ,
334
- this . rootPath ,
335
- this . connection ,
336
- ) ;
372
+ elmVersion = utils . getElmVersion ( this . settings , this . rootPath , this . host ) ;
337
373
} catch ( error ) {
338
- this . connection . console . warn (
374
+ this . host . logger . warn (
339
375
`Could not figure out elm version, this will impact how good the server works. \n ${ error . stack } ` ,
340
376
) ;
341
377
}
342
378
343
379
const pathToElmJson = path . join ( this . rootPath . fsPath , "elm.json" ) ;
344
- this . connection . console . info ( `Reading elm.json from ${ pathToElmJson } ` ) ;
380
+ this . host . logger . info ( `Reading elm.json from ${ pathToElmJson } ` ) ;
345
381
346
382
if ( ! this . filesWatching . has ( pathToElmJson ) ) {
347
383
this . host . watchFile ( pathToElmJson , ( ) => {
348
- void this . connection . window
349
- . createWorkDoneProgress ( )
350
- . then ( ( progress ) => {
351
- progress . begin ( "Restarting Elm Language Server" , 0 ) ;
352
-
353
- this . initWorkspace ( ( percent : number ) => {
354
- progress . report ( percent , `${ percent . toFixed ( 0 ) } %` ) ;
355
- } )
356
- . then ( ( ) => progress . done ( ) )
357
- . catch ( ( ) => {
358
- //
359
- } ) ;
384
+ this . host . onServerDidRestart ( async ( progressReporter ) => {
385
+ await this . initWorkspace ( ( percent : number ) => {
386
+ progressReporter ( percent ) ;
360
387
} ) ;
388
+ } ) ;
361
389
} ) ;
362
390
this . filesWatching . add ( pathToElmJson ) ;
363
391
}
@@ -371,11 +399,11 @@ export class Program implements IProgram {
371
399
// Run `elm make` to download dependencies
372
400
try {
373
401
utils . execCmdSync (
374
- clientSettings . elmPath ,
402
+ this . settings . elmPath ,
375
403
"elm" ,
376
404
{ cmdArguments : [ "make" ] } ,
377
405
this . rootPath . fsPath ,
378
- this . connection ,
406
+ this . host ,
379
407
) ;
380
408
} catch ( error ) {
381
409
// On application projects, this will give a NO INPUT error message, but will still download the dependencies
@@ -386,38 +414,38 @@ export class Program implements IProgram {
386
414
this . forest = new Forest ( this . rootProject ) ;
387
415
388
416
const elmFilePaths = await this . findElmFilesInProject ( this . rootProject ) ;
389
- this . connection . console . info (
417
+ this . host . logger . info (
390
418
`Found ${ elmFilePaths . length . toString ( ) } files to add to the project` ,
391
419
) ;
392
420
393
421
if ( elmFilePaths . every ( ( a ) => a . project !== this . rootProject ) ) {
394
- this . connection . window . showErrorMessage (
422
+ this . host . handleError (
395
423
"The path or paths you entered in the 'source-directories' field of your 'elm.json' does not contain any elm files." ,
396
424
) ;
397
425
}
398
426
399
427
const promiseList : Promise < void > [ ] = [ ] ;
400
- const PARSE_STAGES = 3 ;
428
+ const PARSE_STAGES = 2 ;
401
429
const progressDelta = 100 / ( elmFilePaths . length * PARSE_STAGES ) ;
402
430
for ( const filePath of elmFilePaths ) {
403
- progressCallback ( ( progress += progressDelta ) ) ;
431
+ if ( progressCallback ) {
432
+ progressCallback ( ( progress += progressDelta ) ) ;
433
+ }
404
434
promiseList . push (
405
435
this . readAndAddToForest ( filePath , ( ) => {
406
- progressCallback ( ( progress += progressDelta ) ) ;
436
+ if ( progressCallback ) {
437
+ progressCallback ( ( progress += progressDelta ) ) ;
438
+ }
407
439
} ) ,
408
440
) ;
409
441
}
410
442
await Promise . all ( promiseList ) ;
411
443
412
444
this . findExposedModulesOfDependencies ( this . rootProject ) ;
413
445
414
- CommandManager . initHandlers ( this . connection ) ;
415
-
416
- this . connection . console . info (
417
- `Done parsing all files for ${ pathToElmJson } ` ,
418
- ) ;
446
+ this . host . logger . info ( `Done parsing all files for ${ pathToElmJson } ` ) ;
419
447
} catch ( error ) {
420
- this . connection . console . error (
448
+ this . host . logger . error (
421
449
`Error parsing files for ${ pathToElmJson } :\n${ error . stack } ` ,
422
450
) ;
423
451
}
@@ -467,7 +495,7 @@ export class Program implements IProgram {
467
495
) ;
468
496
469
497
if ( ! solvedVersions ) {
470
- this . connection . window . showErrorMessage (
498
+ this . host . handleError (
471
499
"There is a problem with elm.json. Could not solve dependencies with the given constraints. Try running `elm make` to install missing dependencies." ,
472
500
) ;
473
501
throw new Error ( "Unsolvable package constraints" ) ;
@@ -614,7 +642,7 @@ export class Program implements IProgram {
614
642
const maintainerAndPackageName =
615
643
project . type === "package" ? project . maintainerAndPackageName : undefined ;
616
644
617
- this . connection . console . info ( `Glob ${ sourceDir } /**/*.elm` ) ;
645
+ this . host . logger . info ( `Glob ${ sourceDir } /**/*.elm` ) ;
618
646
619
647
( await this . host . readDirectory ( sourceDir ) ) . forEach ( ( matchingPath ) => {
620
648
matchingPath = normalizeUri ( matchingPath ) ;
@@ -668,7 +696,7 @@ export class Program implements IProgram {
668
696
callback : ( ) => void ,
669
697
) : Promise < void > {
670
698
try {
671
- this . connection . console . info ( `Adding ${ filePath . path . toString ( ) } ` ) ;
699
+ this . host . logger . info ( `Adding ${ filePath . path . toString ( ) } ` ) ;
672
700
const fileContent : string = await this . host . readFile (
673
701
filePath . path . toString ( ) ,
674
702
) ;
@@ -685,7 +713,7 @@ export class Program implements IProgram {
685
713
) ;
686
714
callback ( ) ;
687
715
} catch ( error ) {
688
- this . connection . console . error ( error . stack ) ;
716
+ this . host . logger . error ( error . stack ) ;
689
717
}
690
718
}
691
719
@@ -716,7 +744,7 @@ export class Program implements IProgram {
716
744
}
717
745
}
718
746
719
- export function createNodeProgramHost ( ) : IProgramHost {
747
+ export function createNodeProgramHost ( connection ?: Connection ) : IProgramHost {
720
748
return {
721
749
readFile : ( uri ) : Promise < string > =>
722
750
readFile ( uri , {
@@ -730,5 +758,22 @@ export function createNodeProgramHost(): IProgramHost {
730
758
watchFile : ( uri : string , callback : ( ) => void ) : void => {
731
759
chokidar . watch ( uri ) . on ( "change" , callback ) ;
732
760
} ,
761
+ logger : connection ?. console ?? {
762
+ info : ( ) : void => {
763
+ //
764
+ } ,
765
+ warn : ( ) : void => {
766
+ //
767
+ } ,
768
+ error : ( ) : void => {
769
+ //
770
+ } ,
771
+ } ,
772
+ handleError : ( ) : void => {
773
+ //
774
+ } ,
775
+ onServerDidRestart : ( ) : void => {
776
+ //
777
+ } ,
733
778
} ;
734
779
}
0 commit comments