@@ -486,7 +486,7 @@ internal void ReloadModifiedProject (MonoDevelop.Projects.Project project)
486
486
return await TryLoadSolution ( cancellationToken ) . ConfigureAwait ( false ) ;
487
487
}
488
488
489
- async Task ReloadProjects ( CancellationToken cancellationToken )
489
+ internal async Task ReloadProjects ( CancellationToken cancellationToken )
490
490
{
491
491
try {
492
492
using var cts = CancellationTokenSource . CreateLinkedTokenSource ( cancellationToken , src . Token ) ;
@@ -731,9 +731,9 @@ protected override void ApplyDocumentTextChanged (DocumentId id, SourceText text
731
731
tryApplyState_documentTextChangedTasks . Add ( ApplyDocumentTextChangedCore ( id , text ) ) ;
732
732
}
733
733
734
- async Task ApplyDocumentTextChangedCore ( DocumentId id , SourceText text )
734
+ async Task ApplyDocumentTextChangedCore ( DocumentId id , SourceText text , bool isAnalyzerConfigFile = false )
735
735
{
736
- var document = GetDocument ( id ) ;
736
+ TextDocument document = isAnalyzerConfigFile ? GetAnalyzerConfigDocument ( id ) : GetDocument ( id ) ;
737
737
if ( document == null )
738
738
return ;
739
739
@@ -750,12 +750,21 @@ async Task ApplyDocumentTextChangedCore (DocumentId id, SourceText text)
750
750
return ;
751
751
}
752
752
}
753
- var ( projection , filePath ) = Projections . Get ( document . FilePath ) ;
754
- var data = TextFileProvider . Instance . GetTextEditorData ( filePath , out bool isOpen ) ;
755
- // Guard against already done changes in linked files.
756
- // This shouldn't happen but the roslyn merging seems not to be working correctly in all cases :/
757
- if ( document . GetLinkedDocumentIds ( ) . Length > 0 && isOpen && ! ( text . GetType ( ) . FullName == "Microsoft.CodeAnalysis.Text.ChangedText" ) ) {
758
- return ;
753
+ Projection projection = null ;
754
+ FilePath filePath ;
755
+ ITextDocument data = null ;
756
+ bool isOpen ;
757
+ if ( isAnalyzerConfigFile ) {
758
+ filePath = document . FilePath ;
759
+ data = TextFileProvider . Instance . GetTextEditorData ( filePath , out isOpen ) ;
760
+ } else {
761
+ ( projection , filePath ) = Projections . Get ( document . FilePath ) ;
762
+ data = TextFileProvider . Instance . GetTextEditorData ( filePath , out isOpen ) ;
763
+ // Guard against already done changes in linked files.
764
+ // This shouldn't happen but the roslyn merging seems not to be working correctly in all cases :/
765
+ if ( ( ( Document ) document ) . GetLinkedDocumentIds ( ) . Length > 0 && isOpen && ! ( text . GetType ( ) . FullName == "Microsoft.CodeAnalysis.Text.ChangedText" ) ) {
766
+ return ;
767
+ }
759
768
}
760
769
761
770
lock ( tryApplyState_documentTextChangedContents ) {
@@ -799,20 +808,29 @@ async Task ApplyDocumentTextChangedCore (DocumentId id, SourceText text)
799
808
}
800
809
data . Save ( ) ;
801
810
if ( projection != null ) {
802
- await UpdateProjectionsDocuments ( document , data ) ;
811
+ await UpdateProjectionsDocuments ( ( Document ) document , data ) ;
812
+ } else if ( isAnalyzerConfigFile ) {
813
+ OnAnalyzerConfigDocumentTextChanged ( id , new MonoDevelopSourceText ( data ) , PreservationMode . PreserveValue ) ;
803
814
} else {
804
815
OnDocumentTextChanged ( id , new MonoDevelopSourceText ( data ) , PreservationMode . PreserveValue ) ;
805
816
}
806
817
} else {
807
818
var formatter = CodeFormatterService . GetFormatter ( data . MimeType ) ;
808
819
var documentContext = documentManager . Documents . FirstOrDefault ( d => FilePath . PathComparer . Compare ( d . FileName , filePath ) == 0 ) ? . DocumentContext ;
809
- var root = await projectChanges . NewProject . GetDocument ( id ) . GetSyntaxRootAsync ( ) ;
810
- var annotatedNode = root . DescendantNodesAndSelf ( ) . FirstOrDefault ( n => n . HasAnnotation ( typeSystemService . InsertionModeAnnotation ) ) ;
811
- SyntaxToken ? renameTokenOpt = root . GetAnnotatedNodesAndTokens ( Microsoft . CodeAnalysis . CodeActions . RenameAnnotation . Kind )
812
- . Where ( s => s . IsToken )
813
- . Select ( s => s . AsToken ( ) )
814
- . Cast < SyntaxToken ? > ( )
815
- . FirstOrDefault ( ) ;
820
+
821
+ SyntaxNode root = null ;
822
+ SyntaxNode annotatedNode = null ;
823
+ SyntaxToken ? renameTokenOpt = null ;
824
+
825
+ if ( ! isAnalyzerConfigFile ) {
826
+ root = await projectChanges . NewProject . GetDocument ( id ) . GetSyntaxRootAsync ( ) ;
827
+ annotatedNode = root . DescendantNodesAndSelf ( ) . FirstOrDefault ( n => n . HasAnnotation ( typeSystemService . InsertionModeAnnotation ) ) ;
828
+ renameTokenOpt = root . GetAnnotatedNodesAndTokens ( Microsoft . CodeAnalysis . CodeActions . RenameAnnotation . Kind )
829
+ . Where ( s => s . IsToken )
830
+ . Select ( s => s . AsToken ( ) )
831
+ . Cast < SyntaxToken ? > ( )
832
+ . FirstOrDefault ( ) ;
833
+ }
816
834
817
835
if ( documentContext != null ) {
818
836
var editor = ( TextEditor ) data ;
@@ -936,12 +954,21 @@ await Runtime.RunInMainThread (async () => {
936
954
}
937
955
938
956
if ( projection != null ) {
939
- await UpdateProjectionsDocuments ( document , data ) ;
957
+ await UpdateProjectionsDocuments ( ( Document ) document , data ) ;
958
+ } else if ( isAnalyzerConfigFile ) {
959
+ OnAnalyzerConfigDocumentTextChanged ( id , new MonoDevelopSourceText ( data ) , PreservationMode . PreserveValue ) ;
940
960
} else {
941
961
OnDocumentTextChanged ( id , new MonoDevelopSourceText ( data ) , PreservationMode . PreserveValue ) ;
942
962
}
943
963
}
944
964
}
965
+
966
+ protected override void ApplyAnalyzerConfigDocumentTextChanged ( DocumentId id , SourceText text )
967
+ {
968
+ lock ( projectModifyLock )
969
+ tryApplyState_documentTextChangedTasks . Add ( ApplyDocumentTextChangedCore ( id , text , isAnalyzerConfigFile : true ) ) ;
970
+ }
971
+
945
972
internal static Func < TextEditor , int , Task < List < InsertionPoint > > > GetInsertionPoints ;
946
973
internal static Action < TextEditor , DocumentContext , ITextSourceVersion , SyntaxToken ? > StartRenameSession ;
947
974
@@ -992,6 +1019,7 @@ static int ApplyChanges (Projection projection, ITextDocument data, List<Microso
992
1019
HashSet < MonoDevelop . Projects . Project > tryApplyState_changedProjects = new HashSet < MonoDevelop . Projects . Project > ( ) ;
993
1020
List < Task > tryApplyState_documentTextChangedTasks = new List < Task > ( ) ;
994
1021
Dictionary < string , SourceText > tryApplyState_documentTextChangedContents = new Dictionary < string , SourceText > ( ) ;
1022
+ HashSet < MonoDevelop . Projects . Project > tryApplyState_modifiedProjects = new HashSet < MonoDevelop . Projects . Project > ( ) ;
995
1023
996
1024
/// <summary>
997
1025
/// Used by tests to validate that project has been saved.
@@ -1036,17 +1064,36 @@ internal override bool TryApplyChanges (Solution newSolution, IProgressTracker p
1036
1064
tryApplyState_documentTextChangedTasks . Clear ( ) ;
1037
1065
tryApplyState_changedProjects . Clear ( ) ;
1038
1066
freezeProjectModify = false ;
1067
+ if ( tryApplyState_modifiedProjects . Count > 0 )
1068
+ NotifyProjectsModified ( ) ;
1039
1069
FileService . ThawEvents ( ) ;
1040
1070
}
1041
1071
}
1042
- }
1043
-
1072
+ }
1073
+
1074
+ void NotifyProjectsModified ( )
1075
+ {
1076
+ try {
1077
+ foreach ( var project in tryApplyState_modifiedProjects ) {
1078
+ // New .editorconfig file added so we need to update the project info.
1079
+ project . NotifyModified ( "CoreCompileFiles" ) ;
1080
+ }
1081
+ } catch ( Exception ex ) {
1082
+ LoggingService . LogError ( "tryApplyState_modifiedProjects NotifyModifed error" , ex ) ;
1083
+ } finally {
1084
+ tryApplyState_modifiedProjects . Clear ( ) ;
1085
+ }
1086
+ }
1087
+
1044
1088
public override bool CanApplyChange ( ApplyChangesKind feature )
1045
1089
{
1046
1090
switch ( feature ) {
1047
1091
case ApplyChangesKind . AddDocument :
1048
1092
case ApplyChangesKind . RemoveDocument :
1049
- case ApplyChangesKind . ChangeDocument :
1093
+ case ApplyChangesKind . ChangeDocument :
1094
+ case ApplyChangesKind . AddAnalyzerConfigDocument :
1095
+ case ApplyChangesKind . RemoveAnalyzerConfigDocument :
1096
+ case ApplyChangesKind . ChangeAnalyzerConfigDocument :
1050
1097
//HACK: we don't actually support adding and removing metadata references from project
1051
1098
//however, our MetadataReferenceCache currently depends on (incorrectly) using TryApplyChanges
1052
1099
case ApplyChangesKind . AddMetadataReference :
@@ -1068,24 +1115,88 @@ protected override void ApplyProjectChanges (ProjectChanges projectChanges)
1068
1115
1069
1116
protected override void ApplyDocumentAdded ( DocumentInfo info , SourceText text )
1070
1117
{
1071
- var id = info . Id ;
1072
- MonoDevelop . Projects . Project mdProject = null ;
1118
+ var mdProject = GetMonoProject ( info ) ;
1119
+
1120
+ var path = DetermineFilePath ( info , mdProject , true ) ;
1121
+ // If file is already part of project don't re-add it, example of this is .cshtml
1122
+ if ( mdProject ? . IsFileInProject ( path ) == true ) {
1123
+ this . OnDocumentAdded ( info ) ;
1124
+ return ;
1125
+ }
1126
+ info = info . WithFilePath ( path ) . WithTextLoader ( new MonoDevelopTextLoader ( path ) ) ;
1127
+
1128
+ FormatFile ( text , mdProject , path ) ;
1129
+
1130
+ if ( mdProject != null ) {
1131
+ var data = ProjectMap . GetData ( info . Id . ProjectId ) ;
1132
+ data . DocumentData . Add ( info . Id , path ) ;
1133
+ var file = new MonoDevelop . Projects . ProjectFile ( path ) ;
1134
+ mdProject . Files . Add ( file ) ;
1135
+ tryApplyState_changedProjects . Add ( mdProject ) ;
1136
+ }
1073
1137
1138
+ this . OnDocumentAdded ( info ) ;
1139
+ }
1140
+
1141
+ MonoDevelop . Projects . Project GetMonoProject ( DocumentInfo info )
1142
+ {
1143
+ var id = info . Id ;
1074
1144
if ( id . ProjectId != null ) {
1075
1145
var project = CurrentSolution . GetProject ( id . ProjectId ) ;
1076
- mdProject = GetMonoProject ( project ) ;
1146
+ var mdProject = GetMonoProject ( project ) ;
1077
1147
if ( mdProject == null )
1078
- LoggingService . LogWarning ( "Couldn't find project for newly generated file {0} (Project {1})." , info . Name , info . Id . ProjectId ) ;
1148
+ LoggingService . LogWarning ( "Couldn't find project for document {0} (Project {1})." , info . Name , id . ProjectId ) ;
1149
+ return mdProject ;
1079
1150
}
1151
+ return null ;
1152
+ }
1080
1153
1081
- var path = DetermineFilePath ( info . Id , info . Name , info . FilePath , info . Folders , mdProject ? . FileName . ParentDirectory , true ) ;
1082
- // If file is already part of project don't re-add it, example of this is .cshtml
1083
- if ( mdProject ? . IsFileInProject ( path ) == true ) {
1084
- this . OnDocumentAdded ( info ) ;
1154
+ protected override void ApplyAnalyzerConfigDocumentAdded ( DocumentInfo info , SourceText text )
1155
+ {
1156
+ var mdProject = GetMonoProject ( info ) ;
1157
+
1158
+ var path = DetermineFilePath ( info , mdProject , true ) ;
1159
+ // If file is already part of project don't re-add it unless it does not exist.
1160
+ if ( mdProject ? . IsFileInProject ( path ) == true && File . Exists ( path ) ) {
1161
+ OnAnalyzerConfigDocumentAdded ( info ) ;
1085
1162
return ;
1086
1163
}
1087
- info = info . WithFilePath ( path ) . WithTextLoader ( new MonoDevelopTextLoader ( path ) ) ;
1164
+ info = info . WithFilePath ( path ) . WithTextLoader ( new MonoDevelopTextLoader ( path ) ) ;
1165
+
1166
+ FormatFile ( text , mdProject , path ) ;
1088
1167
1168
+ if ( mdProject != null ) {
1169
+ var data = ProjectMap . GetData ( info . Id . ProjectId ) ;
1170
+ data . DocumentData . Add ( info . Id , path ) ;
1171
+
1172
+ var file = new MonoDevelop . Projects . ProjectFile ( path , MonoDevelop . Projects . BuildAction . None ) ;
1173
+ if ( ! file . FilePath . IsChildPathOf ( mdProject . BaseDirectory ) ) {
1174
+ // Outside project directory - add it as a solution folder item.
1175
+ var solutionFolder = GetSolutionItemsFolder ( mdProject ) ;
1176
+ if ( ! solutionFolder . Files . Contains ( path ) ) {
1177
+ solutionFolder . Files . Add ( path ) ;
1178
+ mdProject . ParentSolution . SaveAsync ( new ProgressMonitor ( ) ) . Ignore ( ) ;
1179
+ }
1180
+ }
1181
+ if ( ! mdProject . IsFileInProject ( file . FilePath ) ) {
1182
+ mdProject . Files . Add ( file ) ;
1183
+ }
1184
+ // Need to trigger Project.Modified event after TryApp is run so project is reloaded by the type system
1185
+ // service. Adding the file to the Files collection will not trigger the modified event since
1186
+ // the build action is not EditorConfigFiles. Also this event would be ignored anyway during TryApply.
1187
+ // Also handles if the link already existed. Need to refresh all projects since the document added
1188
+ // method will only be called once.
1189
+ foreach ( var affectedProject in mdProject . ParentSolution . GetAllProjects ( ) ) {
1190
+ tryApplyState_modifiedProjects . Add ( affectedProject ) ;
1191
+ }
1192
+ tryApplyState_changedProjects . Add ( mdProject ) ;
1193
+ }
1194
+
1195
+ OnAnalyzerConfigDocumentAdded ( info ) ;
1196
+ }
1197
+
1198
+ void FormatFile ( SourceText text , MonoDevelop . Projects . Project mdProject , string path )
1199
+ {
1089
1200
string formattedText ;
1090
1201
var formatter = CodeFormatterService . GetFormatter ( desktopService . GetMimeTypeForUri ( path ) ) ;
1091
1202
if ( formatter != null && mdProject != null ) {
@@ -1100,16 +1211,22 @@ protected override void ApplyDocumentAdded (DocumentInfo info, SourceText text)
1100
1211
} catch ( Exception e ) {
1101
1212
LoggingService . LogError ( "Exception while saving file to " + path , e ) ;
1102
1213
}
1214
+ }
1103
1215
1104
- if ( mdProject != null ) {
1105
- var data = ProjectMap . GetData ( id . ProjectId ) ;
1106
- data . DocumentData . Add ( info . Id , path ) ;
1107
- var file = new MonoDevelop . Projects . ProjectFile ( path ) ;
1108
- mdProject . Files . Add ( file ) ;
1109
- tryApplyState_changedProjects . Add ( mdProject ) ;
1216
+ MonoDevelop . Projects . SolutionFolder GetSolutionItemsFolder ( MonoDevelop . Projects . Project project )
1217
+ {
1218
+ string name = GettextCatalog . GetString ( "Solution Items" ) ;
1219
+ var folder = project . ParentSolution . RootFolder . Items
1220
+ . OfType < MonoDevelop . Projects . SolutionFolder > ( )
1221
+ . FirstOrDefault ( item => StringComparer . CurrentCultureIgnoreCase . Equals ( item . Name , name ) ) ;
1222
+ if ( folder != null ) {
1223
+ return folder ;
1110
1224
}
1111
1225
1112
- this . OnDocumentAdded ( info ) ;
1226
+ folder = new MonoDevelop . Projects . SolutionFolder ( ) ;
1227
+ folder . Name = name ;
1228
+ project . ParentSolution . RootFolder . Items . Add ( folder ) ;
1229
+ return folder ;
1113
1230
}
1114
1231
1115
1232
protected override void ApplyDocumentRemoved ( DocumentId documentId )
@@ -1139,16 +1256,23 @@ protected override void ApplyDocumentRemoved (DocumentId documentId)
1139
1256
tryApplyState_changedProjects . Add ( mdProject ) ;
1140
1257
}
1141
1258
1142
- string DetermineFilePath ( DocumentId id , string name , string filePath , IReadOnlyList < string > docFolders , string defaultFolder , bool createDirectory = false )
1259
+ protected override void ApplyAnalyzerConfigDocumentRemoved ( DocumentId documentId )
1260
+ {
1261
+ LoggingService . LogInfo ( "ApplyAnalyzerConfigDocumentRemoved {0}" , documentId ) ;
1262
+ base . ApplyAnalyzerConfigDocumentRemoved ( documentId ) ;
1263
+ }
1264
+
1265
+ string DetermineFilePath ( DocumentInfo info , MonoDevelop . Projects . Project mdProject , bool createDirectory = false )
1143
1266
{
1144
- var path = filePath ;
1267
+ var path = info . FilePath ;
1145
1268
1146
1269
if ( string . IsNullOrEmpty ( path ) ) {
1147
- var monoProject = GetMonoProject ( id . ProjectId ) ;
1270
+ var monoProject = GetMonoProject ( info . Id . ProjectId ) ;
1148
1271
1149
1272
// If the first namespace name matches the name of the project, then we don't want to
1150
1273
// generate a folder for that. The project is implicitly a folder with that name.
1151
1274
IEnumerable < string > folders ;
1275
+ var docFolders = info . Folders ;
1152
1276
if ( docFolders != null && monoProject != null && docFolders . FirstOrDefault ( ) == monoProject . Name ) {
1153
1277
folders = docFolders . Skip ( 1 ) ;
1154
1278
} else {
@@ -1163,9 +1287,9 @@ string DetermineFilePath (DocumentId id, string name, string filePath, IReadOnly
1163
1287
} catch ( Exception e ) {
1164
1288
LoggingService . LogError ( "Error while creating directory for a new file : " + baseDirectory , e ) ;
1165
1289
}
1166
- path = Path . Combine ( baseDirectory , name ) ;
1290
+ path = Path . Combine ( baseDirectory , info . Name ) ;
1167
1291
} else {
1168
- path = Path . Combine ( defaultFolder , name ) ;
1292
+ path = Path . Combine ( mdProject ? . FileName . ParentDirectory , info . Name ) ;
1169
1293
}
1170
1294
}
1171
1295
return path ;
0 commit comments