Skip to content

Commit 6cda30b

Browse files
authored
Fix disco colors when modifying documents during project load (#10354)
Fixes an issue Andrew found in speedometer tests. When opening files and modifying them, before we migrate the document to the "real" project, things get out of sync. Also matches some stacks on PRISM, but I think this bug was only introduced in the last two weeks, so I don't think its the only cause thats for sure.
2 parents 1a1d246 + 35b302e commit 6cda30b

File tree

2 files changed

+89
-10
lines changed

2 files changed

+89
-10
lines changed

src/Razor/src/Microsoft.AspNetCore.Razor.LanguageServer/ProjectSystem/RazorProjectService.cs

Lines changed: 22 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -391,10 +391,15 @@ private void UpdateProjectDocuments(
391391

392392
_logger.LogTrace($"Updating document '{newHostDocument.FilePath}''s file kind to '{newHostDocument.FileKind}' and target path to '{newHostDocument.TargetPath}'.");
393393

394-
var remoteTextLoader = _remoteTextLoaderFactory.Create(newFilePath);
394+
// If the physical file name hasn't changed, we use the current document snapshot as the source of truth for text, in case
395+
// it has received text change info from LSP. eg, if someone changes the TargetPath of the file while its open in the editor
396+
// with unsaved changes, we don't want to reload it from disk.
397+
var textLoader = FilePathComparer.Instance.Equals(currentHostDocument.FilePath, newHostDocument.FilePath)
398+
? new DocumentSnapshotTextLoader(documentSnapshot)
399+
: _remoteTextLoaderFactory.Create(newFilePath);
395400

396401
updater.DocumentRemoved(currentProjectKey, currentHostDocument);
397-
updater.DocumentAdded(currentProjectKey, newHostDocument, remoteTextLoader);
402+
updater.DocumentAdded(currentProjectKey, newHostDocument, textLoader);
398403
}
399404

400405
project = _projectManager.GetLoadedProject(project.Key);
@@ -493,18 +498,27 @@ private void TryMigrateMiscellaneousDocumentsToProject(ProjectSnapshotManager.Up
493498
}
494499

495500
// Remove from miscellaneous project
496-
var defaultMiscProject = miscellaneousProject;
497-
498-
updater.DocumentRemoved(defaultMiscProject.Key, documentSnapshot.State.HostDocument);
501+
updater.DocumentRemoved(miscellaneousProject.Key, documentSnapshot.State.HostDocument);
499502

500503
// Add to new project
501504

502505
var textLoader = new DocumentSnapshotTextLoader(documentSnapshot);
503-
var defaultProject = projectSnapshot;
504-
var newHostDocument = new HostDocument(documentSnapshot.FilePath, documentSnapshot.TargetPath);
506+
507+
// If we're moving from the misc files project to a real project, then target path will be the full path to the file
508+
// and the next update to the project will update it to be a relative path. To save a bunch of busy work if that is
509+
// the only change necessary, we can proactively do that work here. This also means that when we later find out about
510+
// this document the "real" way, it will be equal to the one we already know about, and we won't lose content
511+
var projectDirectory = FilePathNormalizer.GetNormalizedDirectoryName(projectSnapshot.FilePath);
512+
var newTargetPath = documentSnapshot.TargetPath;
513+
if (FilePathNormalizer.Normalize(newTargetPath).StartsWith(projectDirectory))
514+
{
515+
newTargetPath = newTargetPath[projectDirectory.Length..];
516+
}
517+
518+
var newHostDocument = new HostDocument(documentSnapshot.FilePath, newTargetPath, documentSnapshot.FileKind);
505519
_logger.LogInformation($"Migrating '{documentFilePath}' from the '{miscellaneousProject.Key}' project to '{projectSnapshot.Key}' project.");
506520

507-
updater.DocumentAdded(defaultProject.Key, newHostDocument, textLoader);
521+
updater.DocumentAdded(projectSnapshot.Key, newHostDocument, textLoader);
508522
}
509523
}
510524

src/Razor/test/Microsoft.AspNetCore.Razor.LanguageServer.Test/RazorProjectServiceTest.cs

Lines changed: 67 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22
// Licensed under the MIT license. See License.txt in the project root for license information.
33

44
using System;
5+
using System.Collections.Immutable;
56
using System.Linq;
67
using System.Threading;
78
using System.Threading.Tasks;
@@ -1137,13 +1138,77 @@ await _projectManager.UpdateAsync(updater =>
11371138
x => x.DocumentRemoved(DocumentFilePath2, miscProject.Key));
11381139
}
11391140

1140-
private static TextLoader CreateEmptyTextLoader()
1141+
[Fact]
1142+
public async Task AddProject_MigratesMiscellaneousDocumentsToNewOwnerProject_FixesTargetPath()
1143+
{
1144+
// Arrange
1145+
const string ProjectFilePath = "C:/path/to/project.csproj";
1146+
const string IntermediateOutputPath = "C:/path/to/obj";
1147+
const string DocumentFilePath1 = "C:/path/to/document1.cshtml";
1148+
const string DocumentFilePath2 = "C:/path/to/document2.cshtml";
1149+
1150+
var miscProject = _snapshotResolver.GetMiscellaneousProject();
1151+
1152+
await _projectManager.UpdateAsync(updater =>
1153+
{
1154+
updater.DocumentAdded(miscProject.Key,
1155+
new HostDocument(DocumentFilePath1, "C:/path/to/document1.cshtml"), CreateEmptyTextLoader());
1156+
updater.DocumentAdded(miscProject.Key,
1157+
new HostDocument(DocumentFilePath2, "C:/path/to/document2.cshtml"), CreateEmptyTextLoader());
1158+
});
1159+
1160+
// Act
1161+
var newProjectKey = await _projectService.AddProjectAsync(
1162+
ProjectFilePath, IntermediateOutputPath, RazorConfiguration.Default, rootNamespace: null, displayName: null, DisposalToken);
1163+
1164+
// Assert
1165+
var newProject = _projectManager.GetLoadedProject(newProjectKey);
1166+
Assert.Equal("document1.cshtml", newProject.GetDocument(DocumentFilePath1)!.TargetPath);
1167+
Assert.Equal("document2.cshtml", newProject.GetDocument(DocumentFilePath2)!.TargetPath);
1168+
}
1169+
1170+
[Fact]
1171+
public async Task AddOrUpdateProjectAsync_MigratesMiscellaneousDocumentsToNewOwnerProject_MaintainsTextState()
1172+
{
1173+
// Arrange
1174+
const string ProjectFilePath = "C:/path/to/project.csproj";
1175+
const string IntermediateOutputPath = "C:/path/to/obj";
1176+
const string DocumentFilePath1 = "C:/path/to/document1.cshtml";
1177+
1178+
var miscProject = _snapshotResolver.GetMiscellaneousProject();
1179+
1180+
await _projectManager.UpdateAsync(updater =>
1181+
{
1182+
updater.DocumentAdded(miscProject.Key,
1183+
new HostDocument(DocumentFilePath1, "other/document1.cshtml"), CreateTextLoader(SourceText.From("Hello")));
1184+
});
1185+
1186+
var projectKey = new ProjectKey(IntermediateOutputPath);
1187+
1188+
var documentHandles = ImmutableArray.Create(new DocumentSnapshotHandle(DocumentFilePath1, "document1.cshtml", "mvc"));
1189+
1190+
// Act
1191+
await _projectService.AddOrUpdateProjectAsync(
1192+
projectKey, ProjectFilePath, RazorConfiguration.Default, rootNamespace: null, displayName: null, ProjectWorkspaceState.Default, documentHandles, DisposalToken);
1193+
1194+
// Assert
1195+
var newProject = _projectManager.GetLoadedProject(projectKey);
1196+
var documentText = await newProject.GetDocument(DocumentFilePath1)!.GetTextAsync();
1197+
Assert.Equal("Hello", documentText.ToString());
1198+
}
1199+
1200+
private static TextLoader CreateTextLoader(SourceText text)
11411201
{
11421202
var textLoaderMock = new StrictMock<TextLoader>();
11431203
textLoaderMock
11441204
.Setup(x => x.LoadTextAndVersionAsync(It.IsAny<LoadTextOptions>(), It.IsAny<CancellationToken>()))
1145-
.ReturnsAsync(TextAndVersion.Create(s_emptyText, VersionStamp.Create()));
1205+
.ReturnsAsync(TextAndVersion.Create(text, VersionStamp.Create()));
11461206

11471207
return textLoaderMock.Object;
11481208
}
1209+
1210+
private static TextLoader CreateEmptyTextLoader()
1211+
{
1212+
return CreateTextLoader(s_emptyText);
1213+
}
11491214
}

0 commit comments

Comments
 (0)