public void RazorDocumentOutputChanged(RazorFileChangeEventArgs args) { if (args == null) { throw new ArgumentNullException(nameof(args)); } _foregroundDispatcher.AssertBackgroundThread(); // This is required due to a bug in OmniSharp roslyn https://github.com/OmniSharp/omnisharp-roslyn/issues/1418 // OmniSharp doesn't directly listen for .cs file changes in the obj folder like VS windows does. Therefore // we need to play that part and force buffer updates to indirectly update their workspace to include our Razor // declaration files. lock (_refreshLock) { var projectFilePath = args.UnevaluatedProjectInstance.ProjectFileLocation.File; if (!_pendingOutputRefreshes.TryGetValue(projectFilePath, out var outputRefresh)) { outputRefresh = new OutputRefresh(); _pendingOutputRefreshes[projectFilePath] = outputRefresh; } outputRefresh.UpdateWithChange(args); if (!_deferredRefreshTasks.TryGetValue(projectFilePath, out var update) || update.IsCompleted) { _deferredRefreshTasks[projectFilePath] = RefreshAfterDelay(projectFilePath); } } }
// Internal for testing internal async Task RazorDocumentChangedAsync(RazorFileChangeEventArgs args) { if (args == null) { throw new ArgumentNullException(nameof(args)); } _foregroundDispatcher.AssertBackgroundThread(); if (args.Kind == RazorFileChangeKind.Changed) { // On save we kick off a design time build if the file saved happened to be a component. // This replicates the VS windows world SingleFileGenerator behavior. var isComponentFile = await Task.Factory.StartNew( () => IsComponentFile(args.RelativeFilePath, args.UnevaluatedProjectInstance.ProjectFileLocation.File), CancellationToken.None, TaskCreationOptions.None, _foregroundDispatcher.ForegroundScheduler); if (isComponentFile) { // Evaluation will re-generate the .razor.g.cs files which will indirectly trigger RazorDocumentOutputChanged. _projectInstanceEvaluator.Evaluate(args.UnevaluatedProjectInstance); } } }
public async Task RazorDocumentChangedAsync_Changed_PerformsProjectEvaluation() { // Arrange var projectRootElement = ProjectRootElement.Create("/path/to/project.csproj"); var projectInstance = new ProjectInstance(projectRootElement); var projectInstanceEvaluator = new Mock <ProjectInstanceEvaluator>(); projectInstanceEvaluator.Setup(evaluator => evaluator.Evaluate(It.IsAny <ProjectInstance>())) .Verifiable(); var projectManager = CreateProjectSnapshotManager(); var updateBufferDispatcher = Mock.Of <UpdateBufferDispatcher>(dispatcher => dispatcher.UpdateBufferAsync(It.IsAny <Request>()) == Task.CompletedTask); var refreshTrigger = new ComponentRefreshTrigger(Dispatcher, new FilePathNormalizer(), projectInstanceEvaluator.Object, updateBufferDispatcher, LoggerFactory); refreshTrigger.Initialize(projectManager); await RunOnForegroundAsync(() => { var hostProject = new OmniSharpHostProject(projectInstance.ProjectFileLocation.File, RazorConfiguration.Default, "TestRootNamespace"); projectManager.ProjectAdded(hostProject); var hostDocument = new OmniSharpHostDocument("file.cshtml", "file.cshtml", FileKinds.Component); projectManager.DocumentAdded(hostProject, hostDocument); }); var args = new RazorFileChangeEventArgs("file.cshtml", "file.cshtml", projectInstance, RazorFileChangeKind.Changed); // Act await refreshTrigger.RazorDocumentChangedAsync(args); // Assert projectInstanceEvaluator.VerifyAll(); }
public async Task RazorDocumentOutputChanged_BatchesUpdates() { // Arrange await RunOnForegroundAsync(() => ProjectManager.ProjectAdded(Project1)); var mre = new ManualResetEventSlim(initialState: false); var workspaceStateGenerator = new Mock <OmniSharpProjectWorkspaceStateGenerator>(MockBehavior.Strict); workspaceStateGenerator.Setup(generator => generator.Update(It.IsAny <Project>(), It.IsAny <OmniSharpProjectSnapshot>())) .Callback <Project, OmniSharpProjectSnapshot>((_, __) => { if (mre.IsSet) { throw new XunitException("Should not have been called twice."); } mre.Set(); }); var refreshTrigger = CreateRefreshTrigger(workspaceStateGenerator.Object, enqueueDelay: 10); var args = new RazorFileChangeEventArgs("/path/to/obj/file.cshtml.g.cs", (ProjectInstance)Project1Instance, RazorFileChangeKind.Added); // Act refreshTrigger.RazorDocumentOutputChanged(args); refreshTrigger.RazorDocumentOutputChanged(args); refreshTrigger.RazorDocumentOutputChanged(args); refreshTrigger.RazorDocumentOutputChanged(args); // Assert var result = mre.Wait(WaitDelay); Assert.True(result); }
// Internal for testing internal void FileSystemWatcher_RazorDocumentOutputEvent(string filePath, ProjectInstance projectInstance, RazorFileChangeKind changeKind) { var args = new RazorFileChangeEventArgs(filePath, projectInstance, changeKind); for (var i = 0; i < _documentOutputChangeListeners.Count; i++) { _documentOutputChangeListeners[i].RazorDocumentOutputChanged(args); } }
// Internal for testing internal void FileSystemWatcher_RazorDocumentEvent(string filePath, string projectDirectory, ProjectInstance projectInstance, RazorFileChangeKind changeKind) { var relativeFilePath = ResolveRelativeFilePath(filePath, projectDirectory); var args = new RazorFileChangeEventArgs(filePath, relativeFilePath, projectInstance, changeKind); for (var i = 0; i < _documentChangeListeners.Count; i++) { _documentChangeListeners[i].RazorDocumentChanged(args); } }
public void UpdateWithChange(RazorFileChangeEventArgs change) { if (change == null) { throw new ArgumentNullException(nameof(change)); } // Always take latest project instance. _projectInstance = change.UnevaluatedProjectInstance; _documentChangeInfos[change.FilePath] = new DocumentChangeInfo(change.FilePath, change.RelativeFilePath, change.Kind); }
public async Task RazorDocumentChangedAsync_AddedRemoved_Noops(RazorFileChangeKind changeKind) { // Arrange var refreshTrigger = CreateTestComponentRefreshTrigger(); var args = new RazorFileChangeEventArgs("file.cshtml", "file.cshtml", ProjectInstance, changeKind); // Act & Assert // Jump off the foreground thread await refreshTrigger.RazorDocumentChangedAsync(args); }
public async void RazorDocumentChanged(RazorFileChangeEventArgs args) { try { await RazorDocumentChangedAsync(args); } catch (Exception ex) { _logger.LogError("Unexpected error when handling file " + args.FilePath + " document changed: " + ex); } }
public void RazorDocumentOutputChanged(RazorFileChangeEventArgs args) { if (args == null) { throw new ArgumentNullException(nameof(args)); } // Razor build occurred _ = Task.Factory.StartNew( () => EnqueueUpdate(args.UnevaluatedProjectInstance.ProjectFileLocation.File), CancellationToken.None, TaskCreationOptions.None, _projectSnapshotManagerDispatcher.DispatcherScheduler).ConfigureAwait(false); }
public void RazorDocumentOutputChanged(RazorFileChangeEventArgs args) { if (args == null) { throw new ArgumentNullException(nameof(args)); } // Razor build occurred Task.Factory.StartNew( () => EnqueueUpdate(args.UnevaluatedProjectInstance.ProjectFileLocation.File), CancellationToken.None, TaskCreationOptions.None, _foregroundDispatcher.ForegroundScheduler); }
public void RazorDocumentOutputChanged_BatchesFileUpdates() { // Arrange var updateBufferRequests = new List <Request>(); var refreshTrigger = CreateTestComponentRefreshTrigger(onUpdateBuffer: (request) => updateBufferRequests.Add(request)); refreshTrigger.BlockRefreshWorkStarting = new ManualResetEventSlim(initialState: false); var file1Path = "file.razor.g.cs"; var file2Path = "anotherfile.razor.g.cs"; var file3Path = "file.razor.g.cs"; var projectRootElement = ProjectRootElement.Create("/path/to/project.csproj"); projectRootElement.AddItem("Compile", file1Path); projectRootElement.AddItem("Compile", file2Path); // Not adding file3 here to ensure it doesn't get updated. var projectInstance = new ProjectInstance(projectRootElement); var file1Args = new RazorFileChangeEventArgs(file1Path, file1Path, projectInstance, RazorFileChangeKind.Changed); var file2Args = new RazorFileChangeEventArgs(file2Path, file2Path, projectInstance, RazorFileChangeKind.Changed); var file3Args = new RazorFileChangeEventArgs(file3Path, file3Path, projectInstance, RazorFileChangeKind.Changed); // Act refreshTrigger.RazorDocumentOutputChanged(file1Args); refreshTrigger.RazorDocumentOutputChanged(file2Args); refreshTrigger.RazorDocumentOutputChanged(file3Args); // Assert refreshTrigger.BlockRefreshWorkStarting.Set(); refreshTrigger.NotifyRefreshWorkCompleting.Wait(TimeSpan.FromSeconds(1)); Assert.True(refreshTrigger.NotifyRefreshWorkCompleting.IsSet); Assert.Collection(updateBufferRequests, request => { var updateBufferRequest = Assert.IsType <UpdateBufferRequest>(request); Assert.Equal(file1Path, updateBufferRequest.FileName); Assert.True(updateBufferRequest.FromDisk); }, request => { var updateBufferRequest = Assert.IsType <UpdateBufferRequest>(request); Assert.Equal(file2Path, updateBufferRequest.FileName); Assert.True(updateBufferRequest.FromDisk); }); }
public void RazorDocumentOutputChanged_EnqueuesRefresh() { // Arrange var refreshTrigger = CreateTestComponentRefreshTrigger(); var args = new RazorFileChangeEventArgs("/path/to/file.razor.g.cs", "file.razor.g.cs", ProjectInstance, RazorFileChangeKind.Added); // Act refreshTrigger.RazorDocumentOutputChanged(args); // Assert refreshTrigger.NotifyRefreshWorkStarting.Wait(TimeSpan.FromSeconds(1)); Assert.True(refreshTrigger.NotifyRefreshWorkStarting.IsSet); // Let refresh work complete refreshTrigger.NotifyRefreshWorkCompleting.Wait(TimeSpan.FromSeconds(1)); Assert.True(refreshTrigger.NotifyRefreshWorkCompleting.IsSet); }
public void RazorDocumentOutputChanged_ClearsWorkspaceBufferOnRemove() { // Arrange var changedProjectInstance = new ProjectInstance(ProjectRootElement.Create()); var updateBufferRequests = new List <Request>(); var refreshTrigger = CreateTestComponentRefreshTrigger(onUpdateBuffer: (request) => updateBufferRequests.Add(request)); refreshTrigger.BlockRefreshWorkStarting = new ManualResetEventSlim(initialState: false); var filePath = "file.razor.g.cs"; var projectRootElement = ProjectRootElement.Create("/path/to/project.csproj"); projectRootElement.AddItem("Compile", filePath); var projectInstance = new ProjectInstance(projectRootElement); var onAddArgs = new RazorFileChangeEventArgs(filePath, filePath, projectInstance, RazorFileChangeKind.Added); var onRemoveArgs = new RazorFileChangeEventArgs(filePath, filePath, changedProjectInstance, RazorFileChangeKind.Removed); refreshTrigger.RazorDocumentOutputChanged(onAddArgs); refreshTrigger.BlockRefreshWorkStarting.Set(); refreshTrigger.NotifyRefreshWorkCompleting.Wait(TimeSpan.FromSeconds(1)); refreshTrigger.NotifyRefreshWorkCompleting.Reset(); // Act refreshTrigger.RazorDocumentOutputChanged(onRemoveArgs); // Assert refreshTrigger.BlockRefreshWorkStarting.Set(); refreshTrigger.NotifyRefreshWorkCompleting.Wait(TimeSpan.FromSeconds(1)); Assert.True(refreshTrigger.NotifyRefreshWorkCompleting.IsSet); Assert.Collection(updateBufferRequests, request => { var updateBufferRequest = Assert.IsType <UpdateBufferRequest>(request); Assert.Equal(filePath, updateBufferRequest.FileName); Assert.True(updateBufferRequest.FromDisk); }, request => { Assert.Equal(filePath, request.FileName); Assert.Equal(string.Empty, request.Buffer); }); }
public void RazorDocumentOutputChanged_MemoizesRefreshTasks() { // Arrange var refreshTrigger = CreateTestComponentRefreshTrigger(); refreshTrigger.BlockRefreshWorkStarting = new ManualResetEventSlim(initialState: false); var args = new RazorFileChangeEventArgs("/path/to/file.razor.g.cs", "file.razor.g.cs", ProjectInstance, RazorFileChangeKind.Added); // Act refreshTrigger.RazorDocumentOutputChanged(args); refreshTrigger.RazorDocumentOutputChanged(args); // Assert Assert.Single(refreshTrigger._deferredRefreshTasks); refreshTrigger.BlockRefreshWorkStarting.Set(); refreshTrigger.NotifyRefreshWorkCompleting.Wait(TimeSpan.FromSeconds(1)); Assert.True(refreshTrigger.NotifyRefreshWorkCompleting.IsSet); }
public async Task RazorDocumentOutputChanged_TriggersUpdate() { // Arrange await RunOnForegroundAsync(() => ProjectManager.ProjectAdded(Project1)); var mre = new ManualResetEventSlim(initialState: false); var workspaceStateGenerator = new Mock <OmniSharpProjectWorkspaceStateGenerator>(); workspaceStateGenerator.Setup(generator => generator.Update(It.IsAny <Project>(), It.IsAny <OmniSharpProjectSnapshot>())) .Callback <Project, OmniSharpProjectSnapshot>((_, __) => mre.Set()); var refreshTrigger = CreateRefreshTrigger(workspaceStateGenerator.Object); var args = new RazorFileChangeEventArgs("/path/to/obj/file.cshtml.g.cs", (ProjectInstance)Project1Instance, RazorFileChangeKind.Added); // Act refreshTrigger.RazorDocumentOutputChanged(args); // Assert var result = mre.Wait(WaitDelay); Assert.True(result); }
public async Task RazorDocumentChangedAsync_Changed_NotComponent_Noops() { // Arrange var projectManager = CreateProjectSnapshotManager(); var refreshTrigger = CreateTestComponentRefreshTrigger(); refreshTrigger.Initialize(projectManager); await RunOnForegroundAsync(() => { var hostProject = new OmniSharpHostProject("/path/to/project.csproj", RazorConfiguration.Default, "TestRootNamespace"); projectManager.ProjectAdded(hostProject); var hostDocument = new OmniSharpHostDocument("file.cshtml", "file.cshtml", FileKinds.Legacy); projectManager.DocumentAdded(hostProject, hostDocument); }); var args = new RazorFileChangeEventArgs("file.cshtml", "file.cshtml", ProjectInstance, RazorFileChangeKind.Changed); // Act & Assert await refreshTrigger.RazorDocumentChangedAsync(args); }
public void RazorDocumentChanged(RazorFileChangeEventArgs args) { if (args is null) { throw new ArgumentNullException(nameof(args)); } // Razor document changed _ = Task.Factory.StartNew( () => { if (IsComponentFile(args.FilePath, args.UnevaluatedProjectInstance.ProjectFileLocation.File)) { // Razor component file changed. EnqueueUpdate(args.UnevaluatedProjectInstance.ProjectFileLocation.File); } }, CancellationToken.None, TaskCreationOptions.None, _projectSnapshotManagerDispatcher.DispatcherScheduler).ConfigureAwait(false); }
public void RazorDocumentChanged(RazorFileChangeEventArgs args) { if (args is null) { throw new ArgumentNullException(nameof(args)); } // Razor document changed Task.Factory.StartNew( () => { if (IsComponentFile(args.FilePath, args.UnevaluatedProjectInstance.ProjectFileLocation.File)) { // Razor component file changed. EnqueueUpdate(args.UnevaluatedProjectInstance.ProjectFileLocation.File); } }, CancellationToken.None, TaskCreationOptions.None, _foregroundDispatcher.ForegroundScheduler); }