@@ -16,7 +16,10 @@ import {
1616import {
1717 CancellationToken ,
1818 Connection ,
19+ DidChangeWatchedFilesNotification ,
1920 DocumentHighlight ,
21+ FileChangeType ,
22+ FileEvent ,
2023 TextDocumentSyncKind ,
2124} from "vscode-languageserver" ;
2225import { URI , UriUtils } from "../utils/uri" ;
@@ -148,6 +151,15 @@ export function startLanguageServer(
148151 } ;
149152 } ) ;
150153 connection . onInitialized ( async ( ) => {
154+ connection . client . register ( DidChangeWatchedFilesNotification . type , {
155+ watchers : [
156+ {
157+ // Watch all changes
158+ // Includes changes to any directory or file in the workspace
159+ globPattern : "**/*" ,
160+ } ,
161+ ] ,
162+ } ) ;
151163 const promises : Promise < void > [ ] = [ ] ;
152164 for ( const folder of folders ) {
153165 promises . push (
@@ -408,21 +420,123 @@ export function startLanguageServer(
408420 ) ;
409421 } ) ;
410422 } ) ;
423+
424+ async function pluginConfigChanged ( ) {
425+ // handle changes to the .pliplugin config folder's contents
426+ const diagnosticsByUri = await workspace . config . reloadConfigurations ( ) ;
427+ publishPluginConfigDiagnostics ( diagnosticsByUri ) ;
428+
429+ // reindex reachable compilation units
430+ await compilationUnitHandler . reindex ( connection , CancellationToken . None ) ;
431+
432+ // refresh semantic tokens so syntax coloring updates immediately
433+ connection . languages . semanticTokens . refresh ( ) ;
434+ }
435+
436+ // A structural change to a lib folder (a file/dir created or deleted
437+ // inside a lib, or a directory removed that is or contains a lib)
438+ // invalidates the computed lib index, so the libs must be re-expanded.
439+ // Note: Simple file edits are not considered structural changes,
440+ // so they don't trigger a reindex.
441+ function changeAffectsLibs ( changes : readonly FileEvent [ ] ) : boolean {
442+ const libDirs = workspace . config . getLibDirectoryUris ( ) ;
443+ if ( libDirs . length === 0 ) {
444+ return false ;
445+ }
446+ for ( const change of changes ) {
447+ if ( change . type === FileChangeType . Changed ) {
448+ continue ;
449+ }
450+ const changeUri = UriUtils . toUri ( change . uri ) ;
451+ for ( const libDir of libDirs ) {
452+ // Change inside a lib (create/delete of a member) OR the change
453+ // is/contains the lib dir itself (whole lib folder gone).
454+ if (
455+ UriUtils . contains ( libDir , changeUri ) ||
456+ UriUtils . contains ( changeUri , libDir )
457+ ) {
458+ return true ;
459+ }
460+ }
461+ }
462+ return false ;
463+ }
411464 onNotification (
412465 connection ,
413- Messages . WorkspaceDidChangePluginConfigNotification ,
466+ Messages . OnDidChangePluginConfigSettingsNotification ,
414467 async ( ) => {
415- // handle changes to the .pliplugin config folder's contents
416- const diagnosticsByUri = await workspace . config . reloadConfigurations ( ) ;
417- publishPluginConfigDiagnostics ( diagnosticsByUri ) ;
418-
419- // reindex reachable compilation units
420- await compilationUnitHandler . reindex ( connection , CancellationToken . None ) ;
421-
422- // refresh semantic tokens so syntax coloring updates immediately
423- connection . languages . semanticTokens . refresh ( ) ;
468+ // Handle changes to the pli.pgm_conf and pli.proc_grps settings in vscode
469+ await pluginConfigChanged ( ) ;
424470 } ,
425471 ) ;
472+ connection . onDidChangeWatchedFiles ( async ( params ) => {
473+ // First thing: Figure out whether any of the changed files are plugin config files
474+ // If they are, we need to reindex the workspace anyway - no need to check for other changes.
475+ function isPluginConfigFile ( uri : string ) : boolean {
476+ return (
477+ uri . endsWith ( "/.pliplugin" ) ||
478+ uri . endsWith ( "/.pliplugin/pgm_conf.json" ) ||
479+ uri . endsWith ( "/.pliplugin/proc_grps.json" )
480+ ) ;
481+ }
482+ // Since we cannot know whether a created directory is a lib in advance
483+ // We always have to reindex if a directory is created, since it may contain a lib.
484+ let directoryCreated = false ;
485+ for ( const change of params . changes ) {
486+ if ( change . type === FileChangeType . Created ) {
487+ // Try to stat the changed file to see if it's a directory.
488+ const uri = UriUtils . toUri ( change . uri ) ;
489+ try {
490+ const stats = await workspace . fs . stat ( uri ) ;
491+ if ( stats . isDirectory ) {
492+ directoryCreated = true ;
493+ break ;
494+ }
495+ } catch {
496+ // Ignore errors, assume it's not a directory.
497+ }
498+ }
499+ }
500+ const pluginConfigHasChanged = params . changes . some ( ( change ) =>
501+ isPluginConfigFile ( change . uri ) ,
502+ ) ;
503+ if (
504+ directoryCreated ||
505+ pluginConfigHasChanged ||
506+ changeAffectsLibs ( params . changes )
507+ ) {
508+ await pluginConfigChanged ( ) ;
509+ } else {
510+ const compilationUnits = new Set < CompilationUnit > ( ) ;
511+ // Not a plugin config change, meaning that individual folders/files have changed.
512+ for ( const compilationUnit of compilationUnitHandler . getAllCompilationUnits ( ) ) {
513+ if ( compilationUnit . includeError ) {
514+ // If the compilation unit has an unresolved include, we need to re-run the lifecycle
515+ // to see if the include can now be resolved.
516+ compilationUnits . add ( compilationUnit ) ;
517+ // No need to change the change contents for this
518+ continue ;
519+ }
520+ changeLoop: for ( const change of params . changes ) {
521+ for ( const file of compilationUnit . services . files . keys ( ) ) {
522+ // Either equal (i.e. file has changed) or the changed dir is a parent of the file
523+ if ( UriUtils . contains ( change . uri , file ) ) {
524+ compilationUnits . add ( compilationUnit ) ;
525+ break changeLoop;
526+ }
527+ }
528+ }
529+ }
530+ // Finally, update the compilation units themselves that might be affected by the change
531+ for ( const compilationUnit of compilationUnits ) {
532+ await compilationUnitHandler . updateUri ( compilationUnit . uri ) ;
533+ }
534+ if ( compilationUnits . size > 0 ) {
535+ // refresh semantic tokens so syntax coloring updates immediately
536+ connection . languages . semanticTokens . refresh ( ) ;
537+ }
538+ }
539+ } ) ;
426540 onRequest ( connection , Messages . ExistingFile , ( uriString : string ) : boolean => {
427541 const uri = UriUtils . toUri ( uriString ) ;
428542 const compilationUnit = compilationUnitHandler . getCompilationUnit ( uri ) ;
0 commit comments