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);
                }
            }
        }
Example #3
0
        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();
        }
Example #4
0
        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);
            }
        }
Example #6
0
        // 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);
            }
Example #8
0
        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);
        }
Example #12
0
        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);
            });
        }
Example #13
0
        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);
        }
Example #14
0
        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);
            });
        }
Example #15
0
        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);
        }
Example #16
0
        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);
        }
Example #17
0
        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);
        }
Example #19
0
        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);
        }