2222using Microsoft . CodeAnalysis . Rename ;
2323using Microsoft . CodeAnalysis . Shared . Extensions ;
2424using Microsoft . CodeAnalysis . Text ;
25+ using Microsoft . Extensions . FileSystemGlobbing ;
2526using Microsoft . Extensions . Logging ;
2627using NuGet . Frameworks ;
2728using Roslyn . LanguageServer . Protocol ;
@@ -53,6 +54,9 @@ public class RoslynAnalysis(ILogger<RoslynAnalysis> logger, BuildService buildSe
5354 private static HashSet < CodeFixProvider > _codeFixProviders = [ ] ;
5455 private static HashSet < CodeRefactoringProvider > _codeRefactoringProviders = [ ] ;
5556
57+ // Primarily used for getting the globs for a project
58+ private Dictionary < ProjectId , ProjectFileInfo > _projectFileInfoMap = new ( ) ;
59+
5660 private TaskCompletionSource _solutionLoadedTcs = null ! ;
5761 private SharpIdeSolutionModel ? _sharpIdeSolutionModel ;
5862 public void StartSolutionAnalysis ( SharpIdeSolutionModel solutionModel )
@@ -110,7 +114,8 @@ public async Task Analyse(SharpIdeSolutionModel solutionModel, CancellationToken
110114
111115 // MsBuildProjectLoader doesn't do a restore which is absolutely required for resolving PackageReferences, if they have changed. I am guessing it just reads from project.assets.json
112116 await _buildService . MsBuildAsync ( _sharpIdeSolutionModel . FilePath , BuildType . Restore , cancellationToken ) ;
113- var solutionInfo = await _msBuildProjectLoader ! . LoadSolutionInfoAsync ( _sharpIdeSolutionModel . FilePath , cancellationToken : cancellationToken ) ;
117+ var ( solutionInfo , projectFileInfos ) = await _msBuildProjectLoader ! . LoadSolutionInfoAsync ( _sharpIdeSolutionModel . FilePath , cancellationToken : cancellationToken ) ;
118+ _projectFileInfoMap = projectFileInfos ;
114119 _workspace . ClearSolution ( ) ;
115120 var solution = _workspace . AddSolution ( solutionInfo ) ;
116121 }
@@ -185,7 +190,8 @@ public async Task ReloadSolution(CancellationToken cancellationToken = default)
185190 var __ = SharpIdeOtel . Source . StartActivity ( $ "{ nameof ( RoslynAnalysis ) } .MSBuildProjectLoader.LoadSolutionInfoAsync") ;
186191 // This call is the expensive part - MSBuild is slow. There doesn't seem to be any incrementalism for solutions.
187192 // The best we could do to speed it up is do .LoadProjectInfoAsync for the single project, and somehow munge that into the existing solution
188- var newSolutionInfo = await _msBuildProjectLoader . LoadSolutionInfoAsync ( _sharpIdeSolutionModel ! . FilePath , cancellationToken : cancellationToken ) ;
193+ var ( newSolutionInfo , projectFileInfos ) = await _msBuildProjectLoader . LoadSolutionInfoAsync ( _sharpIdeSolutionModel ! . FilePath , cancellationToken : cancellationToken ) ;
194+ _projectFileInfoMap = projectFileInfos ;
189195 __ ? . Dispose ( ) ;
190196
191197 var ___ = SharpIdeOtel . Source . StartActivity ( $ "{ nameof ( RoslynAnalysis ) } .Workspace.OnSolutionReloaded") ;
@@ -219,7 +225,11 @@ public async Task ReloadProject(SharpIdeProjectModel projectModel, CancellationT
219225 // This will get all projects necessary to build this group of projects, regardless of whether those projects are actually affected by the original project change
220226 // We can potentially optimise this, but given this is the expensive part, lets just proceed with reloading them all in the solution
221227 // We potentially lose performance because Workspace/Solution caches are dropped, but lets not prematurely optimise
222- var loadedProjectInfos = await _msBuildProjectLoader . LoadProjectInfosAsync ( projectPathsToReload , null , cancellationToken : cancellationToken ) ;
228+ var ( loadedProjectInfos , projectFileInfos ) = await _msBuildProjectLoader . LoadProjectInfosAsync ( projectPathsToReload , null , cancellationToken : cancellationToken ) ;
229+ foreach ( var ( projectId , projectFileInfo ) in projectFileInfos )
230+ {
231+ _projectFileInfoMap [ projectId ] = projectFileInfo ;
232+ }
223233 __ ? . Dispose ( ) ;
224234
225235 var ___ = SharpIdeOtel . Source . StartActivity ( $ "{ nameof ( RoslynAnalysis ) } .Workspace.UpdateSolution") ;
@@ -960,12 +970,46 @@ public async Task AddDocument(SharpIdeFile fileModel, string content)
960970 Guard . Against . Null ( fileModel , nameof ( fileModel ) ) ;
961971 Guard . Against . Null ( content , nameof ( content ) ) ;
962972
963- var project = GetProjectForSharpIdeFile ( fileModel ) ;
973+ var sharpIdeProject = GetSharpIdeProjectForSharpIdeFile ( fileModel ) ;
974+ var probableProject = GetProjectForSharpIdeProjectModel ( sharpIdeProject ) ;
975+ // This file probably belongs to this project, but we need to check its path against the globs for the project to make sure
976+ var projectFileInfo = _projectFileInfoMap . GetValueOrDefault ( probableProject . Id ) ;
977+ Guard . Against . Null ( projectFileInfo ) ;
978+ var matchers = projectFileInfo . FileGlobs . Select ( glob =>
979+ {
980+ var matcher = new Matcher ( ) ;
981+ matcher . AddIncludePatterns ( glob . Includes ) ;
982+ matcher . AddExcludePatterns ( glob . Excludes ) ;
983+ matcher . AddExcludePatterns ( glob . Removes ) ;
984+ return matcher ;
985+ } ) ;
986+
987+ var belongsToProject = false ;
988+ // Check if the file path matches any of the globs in the project file.
989+ foreach ( var matcher in matchers )
990+ {
991+ // CPS re-creates the msbuild globs from the includes/excludes/removes and the project XML directory and
992+ // ignores the MSBuildGlob.FixedDirectoryPart. We'll do the same here and match using the project directory as the relative path.
993+ // See https://devdiv.visualstudio.com/DevDiv/_git/CPS?path=/src/Microsoft.VisualStudio.ProjectSystem/Build/MsBuildGlobFactory.cs
994+ var relativeDirectory = sharpIdeProject . DirectoryPath ;
995+
996+ var matches = matcher . Match ( relativeDirectory , fileModel . Path ) ;
997+ if ( matches . HasMatches )
998+ {
999+ belongsToProject = true ;
1000+ break ;
1001+ }
1002+ }
1003+
1004+ if ( belongsToProject is false )
1005+ {
1006+ return ;
1007+ }
9641008
9651009 var existingDocument = fileModel switch
9661010 {
967- { IsRazorFile : true } => project . AdditionalDocuments . SingleOrDefault ( s => s . FilePath == fileModel . Path ) ,
968- { IsCsharpFile : true } => project . Documents . SingleOrDefault ( s => s . FilePath == fileModel . Path ) ,
1011+ { IsRazorFile : true } => probableProject . AdditionalDocuments . SingleOrDefault ( s => s . FilePath == fileModel . Path ) ,
1012+ { IsCsharpFile : true } => probableProject . Documents . SingleOrDefault ( s => s . FilePath == fileModel . Path ) ,
9691013 _ => throw new InvalidOperationException ( "AddDocument failed: File is not a workspace file" )
9701014 } ;
9711015 if ( existingDocument is not null )
@@ -977,8 +1021,8 @@ public async Task AddDocument(SharpIdeFile fileModel, string content)
9771021
9781022 var newSolution = fileModel switch
9791023 {
980- { IsRazorFile : true } => _workspace . CurrentSolution . AddAdditionalDocument ( DocumentId . CreateNewId ( project . Id ) , fileModel . Name , sourceText , filePath : fileModel . Path ) ,
981- { IsCsharpFile : true } => _workspace . CurrentSolution . AddDocument ( DocumentId . CreateNewId ( project . Id ) , fileModel . Name , sourceText , filePath : fileModel . Path ) ,
1024+ { IsRazorFile : true } => _workspace . CurrentSolution . AddAdditionalDocument ( DocumentId . CreateNewId ( probableProject . Id ) , fileModel . Name , sourceText , filePath : fileModel . Path ) ,
1025+ { IsCsharpFile : true } => _workspace . CurrentSolution . AddDocument ( DocumentId . CreateNewId ( probableProject . Id ) , fileModel . Name , sourceText , filePath : fileModel . Path ) ,
9821026 _ => throw new InvalidOperationException ( "AddDocument failed: File is not in workspace" )
9831027 } ;
9841028
@@ -1037,9 +1081,15 @@ public async Task<string> GetOutputDllPathForProject(SharpIdeProjectModel projec
10371081 return outputPath ;
10381082 }
10391083
1040- private static Project GetProjectForSharpIdeFile ( SharpIdeFile sharpIdeFile )
1084+ private static SharpIdeProjectModel GetSharpIdeProjectForSharpIdeFile ( SharpIdeFile sharpIdeFile )
10411085 {
10421086 var sharpIdeProjectModel = ( ( IChildSharpIdeNode ) sharpIdeFile ) . GetNearestProjectNode ( ) ! ;
1087+ return sharpIdeProjectModel ;
1088+ }
1089+
1090+ private static Project GetProjectForSharpIdeFile ( SharpIdeFile sharpIdeFile )
1091+ {
1092+ var sharpIdeProjectModel = GetSharpIdeProjectForSharpIdeFile ( sharpIdeFile ) ;
10431093 var project = GetProjectForSharpIdeProjectModel ( sharpIdeProjectModel ) ;
10441094 return project ;
10451095 }
0 commit comments