From 52cdbd1439beb8c690f3b6b4d2a868321b730395 Mon Sep 17 00:00:00 2001 From: Savpek Date: Fri, 5 Jun 2020 12:03:30 +0300 Subject: [PATCH 01/10] Initial working version --- .../FileWatching/FileChangeType.cs | 3 ++- .../FileWatching/IFileSystemWatcher.cs | 1 + .../FileWatching/ManualFileSystemWatcher.cs | 17 +++++++++++++++++ src/OmniSharp.Roslyn/OmniSharpWorkspace.cs | 19 +++++++++++++++++++ 4 files changed, 39 insertions(+), 1 deletion(-) diff --git a/src/OmniSharp.Abstractions/FileWatching/FileChangeType.cs b/src/OmniSharp.Abstractions/FileWatching/FileChangeType.cs index 218513e78b..85c1721739 100644 --- a/src/OmniSharp.Abstractions/FileWatching/FileChangeType.cs +++ b/src/OmniSharp.Abstractions/FileWatching/FileChangeType.cs @@ -5,6 +5,7 @@ public enum FileChangeType Unspecified = 0, Change, Create, - Delete + Delete, + FolderDelete } } diff --git a/src/OmniSharp.Abstractions/FileWatching/IFileSystemWatcher.cs b/src/OmniSharp.Abstractions/FileWatching/IFileSystemWatcher.cs index 90bccf5196..c0b2700446 100644 --- a/src/OmniSharp.Abstractions/FileWatching/IFileSystemWatcher.cs +++ b/src/OmniSharp.Abstractions/FileWatching/IFileSystemWatcher.cs @@ -12,5 +12,6 @@ public interface IFileSystemWatcher /// The file path, directory path or file extension to watch. /// The callback that will be invoked when a change occurs in the watched file or directory. void Watch(string pathOrExtension, FileSystemNotificationCallback callback); + void WatchFolders(FileSystemNotificationCallback callback); } } diff --git a/src/OmniSharp.Host/FileWatching/ManualFileSystemWatcher.cs b/src/OmniSharp.Host/FileWatching/ManualFileSystemWatcher.cs index 0a485eb27e..f0d60e633c 100644 --- a/src/OmniSharp.Host/FileWatching/ManualFileSystemWatcher.cs +++ b/src/OmniSharp.Host/FileWatching/ManualFileSystemWatcher.cs @@ -1,6 +1,7 @@ using System; using System.Collections.Generic; using System.IO; +using System.Linq; namespace OmniSharp.FileWatching { @@ -8,6 +9,7 @@ internal partial class ManualFileSystemWatcher : IFileSystemWatcher, IFileSystem { private readonly object _gate = new object(); private readonly Dictionary _callbacksMap; + private readonly Callbacks _folderCallbacks = new Callbacks(); public ManualFileSystemWatcher() { @@ -18,6 +20,16 @@ public void Notify(string filePath, FileChangeType changeType = FileChangeType.U { lock (_gate) { + if(changeType == FileChangeType.FolderDelete) + { + foreach(var matchingCallback in _callbacksMap.AsEnumerable().Where(x => x.Key.StartsWith(filePath))) + { + matchingCallback.Value.Invoke(matchingCallback.Key, FileChangeType.Delete); + } + + _folderCallbacks.Invoke(filePath, FileChangeType.FolderDelete); + } + if (_callbacksMap.TryGetValue(filePath, out var fileCallbacks)) { fileCallbacks.Invoke(filePath, changeType); @@ -38,6 +50,11 @@ public void Notify(string filePath, FileChangeType changeType = FileChangeType.U } } + public void WatchFolders(FileSystemNotificationCallback callback) + { + _folderCallbacks.Add(callback); + } + public void Watch(string pathOrExtension, FileSystemNotificationCallback callback) { if (pathOrExtension == null) diff --git a/src/OmniSharp.Roslyn/OmniSharpWorkspace.cs b/src/OmniSharp.Roslyn/OmniSharpWorkspace.cs index 4c878a069b..da480ddc96 100644 --- a/src/OmniSharp.Roslyn/OmniSharpWorkspace.cs +++ b/src/OmniSharp.Roslyn/OmniSharpWorkspace.cs @@ -41,10 +41,29 @@ public OmniSharpWorkspace(HostServicesAggregator aggregator, ILoggerFactory logg { BufferManager = new BufferManager(this, fileSystemWatcher); _logger = loggerFactory.CreateLogger(); + fileSystemWatcher.WatchFolders(OnDirectoryRemoved); } public override bool CanOpenDocuments => true; + + private void OnDirectoryRemoved(string path, FileChangeType changeType) + { + if(changeType == FileChangeType.FolderDelete) + { + var docs = CurrentSolution + .Projects + .SelectMany(x => x.Documents) + .Where(x => x.FilePath.StartsWith(path, StringComparison.OrdinalIgnoreCase)) + .Select(x => x.Id); + + foreach(var doc in docs) + { + OnDocumentRemoved(doc); + } + } + } + public void AddWaitForProjectModelReadyHandler(Func handler) { _waitForProjectModelReadyHandlers.Add(handler); From 20d4023f1412e526aab78d4934597b18959971c9 Mon Sep 17 00:00:00 2001 From: Savpek Date: Fri, 5 Jun 2020 12:47:01 +0300 Subject: [PATCH 02/10] Refactoring for onDirectoryRemove method --- src/OmniSharp.Roslyn/OmniSharpWorkspace.cs | 9 +++------ 1 file changed, 3 insertions(+), 6 deletions(-) diff --git a/src/OmniSharp.Roslyn/OmniSharpWorkspace.cs b/src/OmniSharp.Roslyn/OmniSharpWorkspace.cs index da480ddc96..c670e2a8cb 100644 --- a/src/OmniSharp.Roslyn/OmniSharpWorkspace.cs +++ b/src/OmniSharp.Roslyn/OmniSharpWorkspace.cs @@ -51,15 +51,12 @@ private void OnDirectoryRemoved(string path, FileChangeType changeType) { if(changeType == FileChangeType.FolderDelete) { - var docs = CurrentSolution - .Projects - .SelectMany(x => x.Documents) - .Where(x => x.FilePath.StartsWith(path, StringComparison.OrdinalIgnoreCase)) - .Select(x => x.Id); + var docs = CurrentSolution.Projects.SelectMany(x => x.Documents) + .Where(x => x.FilePath.StartsWith(path, StringComparison.OrdinalIgnoreCase)); foreach(var doc in docs) { - OnDocumentRemoved(doc); + OnDocumentRemoved(doc.Id); } } } From aa7896a76cd9139b4965bebe0f873b3ab4570930 Mon Sep 17 00:00:00 2001 From: Savpek Date: Fri, 5 Jun 2020 12:50:01 +0300 Subject: [PATCH 03/10] Refactored folder -> directory --- src/OmniSharp.Abstractions/FileWatching/FileChangeType.cs | 2 +- .../FileWatching/IFileSystemWatcher.cs | 2 +- src/OmniSharp.Host/FileWatching/ManualFileSystemWatcher.cs | 6 +++--- src/OmniSharp.Roslyn/OmniSharpWorkspace.cs | 4 ++-- 4 files changed, 7 insertions(+), 7 deletions(-) diff --git a/src/OmniSharp.Abstractions/FileWatching/FileChangeType.cs b/src/OmniSharp.Abstractions/FileWatching/FileChangeType.cs index 85c1721739..ca5d487cda 100644 --- a/src/OmniSharp.Abstractions/FileWatching/FileChangeType.cs +++ b/src/OmniSharp.Abstractions/FileWatching/FileChangeType.cs @@ -6,6 +6,6 @@ public enum FileChangeType Change, Create, Delete, - FolderDelete + DirectoryDelete } } diff --git a/src/OmniSharp.Abstractions/FileWatching/IFileSystemWatcher.cs b/src/OmniSharp.Abstractions/FileWatching/IFileSystemWatcher.cs index c0b2700446..200146cea4 100644 --- a/src/OmniSharp.Abstractions/FileWatching/IFileSystemWatcher.cs +++ b/src/OmniSharp.Abstractions/FileWatching/IFileSystemWatcher.cs @@ -12,6 +12,6 @@ public interface IFileSystemWatcher /// The file path, directory path or file extension to watch. /// The callback that will be invoked when a change occurs in the watched file or directory. void Watch(string pathOrExtension, FileSystemNotificationCallback callback); - void WatchFolders(FileSystemNotificationCallback callback); + void WatchDirectories(FileSystemNotificationCallback callback); } } diff --git a/src/OmniSharp.Host/FileWatching/ManualFileSystemWatcher.cs b/src/OmniSharp.Host/FileWatching/ManualFileSystemWatcher.cs index f0d60e633c..0ff09ff401 100644 --- a/src/OmniSharp.Host/FileWatching/ManualFileSystemWatcher.cs +++ b/src/OmniSharp.Host/FileWatching/ManualFileSystemWatcher.cs @@ -20,14 +20,14 @@ public void Notify(string filePath, FileChangeType changeType = FileChangeType.U { lock (_gate) { - if(changeType == FileChangeType.FolderDelete) + if(changeType == FileChangeType.DirectoryDelete) { foreach(var matchingCallback in _callbacksMap.AsEnumerable().Where(x => x.Key.StartsWith(filePath))) { matchingCallback.Value.Invoke(matchingCallback.Key, FileChangeType.Delete); } - _folderCallbacks.Invoke(filePath, FileChangeType.FolderDelete); + _folderCallbacks.Invoke(filePath, FileChangeType.DirectoryDelete); } if (_callbacksMap.TryGetValue(filePath, out var fileCallbacks)) @@ -50,7 +50,7 @@ public void Notify(string filePath, FileChangeType changeType = FileChangeType.U } } - public void WatchFolders(FileSystemNotificationCallback callback) + public void WatchDirectories(FileSystemNotificationCallback callback) { _folderCallbacks.Add(callback); } diff --git a/src/OmniSharp.Roslyn/OmniSharpWorkspace.cs b/src/OmniSharp.Roslyn/OmniSharpWorkspace.cs index c670e2a8cb..e494091754 100644 --- a/src/OmniSharp.Roslyn/OmniSharpWorkspace.cs +++ b/src/OmniSharp.Roslyn/OmniSharpWorkspace.cs @@ -41,7 +41,7 @@ public OmniSharpWorkspace(HostServicesAggregator aggregator, ILoggerFactory logg { BufferManager = new BufferManager(this, fileSystemWatcher); _logger = loggerFactory.CreateLogger(); - fileSystemWatcher.WatchFolders(OnDirectoryRemoved); + fileSystemWatcher.WatchDirectories(OnDirectoryRemoved); } public override bool CanOpenDocuments => true; @@ -49,7 +49,7 @@ public OmniSharpWorkspace(HostServicesAggregator aggregator, ILoggerFactory logg private void OnDirectoryRemoved(string path, FileChangeType changeType) { - if(changeType == FileChangeType.FolderDelete) + if(changeType == FileChangeType.DirectoryDelete) { var docs = CurrentSolution.Projects.SelectMany(x => x.Documents) .Where(x => x.FilePath.StartsWith(path, StringComparison.OrdinalIgnoreCase)); From de8484a80aafa713fdb5400d09ce18a07e894646 Mon Sep 17 00:00:00 2001 From: Savpek Date: Fri, 5 Jun 2020 13:11:56 +0300 Subject: [PATCH 04/10] Added path directory char to make sure only files under directories are matched --- src/OmniSharp.Roslyn/OmniSharpWorkspace.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/OmniSharp.Roslyn/OmniSharpWorkspace.cs b/src/OmniSharp.Roslyn/OmniSharpWorkspace.cs index e494091754..fe0e39bd08 100644 --- a/src/OmniSharp.Roslyn/OmniSharpWorkspace.cs +++ b/src/OmniSharp.Roslyn/OmniSharpWorkspace.cs @@ -52,7 +52,7 @@ private void OnDirectoryRemoved(string path, FileChangeType changeType) if(changeType == FileChangeType.DirectoryDelete) { var docs = CurrentSolution.Projects.SelectMany(x => x.Documents) - .Where(x => x.FilePath.StartsWith(path, StringComparison.OrdinalIgnoreCase)); + .Where(x => x.FilePath.StartsWith(path + Path.DirectorySeparatorChar, StringComparison.OrdinalIgnoreCase)); foreach(var doc in docs) { From f7f423f11620fa7b02f91f0aa5ee0e3e03256351 Mon Sep 17 00:00:00 2001 From: Savpek Date: Fri, 5 Jun 2020 14:09:16 +0300 Subject: [PATCH 05/10] Added path separator to make sure only files under folder --- src/OmniSharp.Host/FileWatching/ManualFileSystemWatcher.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/OmniSharp.Host/FileWatching/ManualFileSystemWatcher.cs b/src/OmniSharp.Host/FileWatching/ManualFileSystemWatcher.cs index 0ff09ff401..ca7658715f 100644 --- a/src/OmniSharp.Host/FileWatching/ManualFileSystemWatcher.cs +++ b/src/OmniSharp.Host/FileWatching/ManualFileSystemWatcher.cs @@ -22,7 +22,7 @@ public void Notify(string filePath, FileChangeType changeType = FileChangeType.U { if(changeType == FileChangeType.DirectoryDelete) { - foreach(var matchingCallback in _callbacksMap.AsEnumerable().Where(x => x.Key.StartsWith(filePath))) + foreach(var matchingCallback in _callbacksMap.AsEnumerable().Where(x => x.Key.StartsWith(filePath + Path.DirectorySeparatorChar, StringComparison.OrdinalIgnoreCase))) { matchingCallback.Value.Invoke(matchingCallback.Key, FileChangeType.Delete); } From 10c3668770de78fe64da4f8f2e56b8424840464b Mon Sep 17 00:00:00 2001 From: Savpek Date: Fri, 5 Jun 2020 14:48:01 +0300 Subject: [PATCH 06/10] Added test for removed dir --- .../FilesChangedFacts.cs | 37 +++++++++++++++++-- 1 file changed, 33 insertions(+), 4 deletions(-) diff --git a/tests/OmniSharp.Roslyn.CSharp.Tests/FilesChangedFacts.cs b/tests/OmniSharp.Roslyn.CSharp.Tests/FilesChangedFacts.cs index ace7d9d098..460d86b10d 100644 --- a/tests/OmniSharp.Roslyn.CSharp.Tests/FilesChangedFacts.cs +++ b/tests/OmniSharp.Roslyn.CSharp.Tests/FilesChangedFacts.cs @@ -45,6 +45,7 @@ public async Task TestFileMovedToPreviouslyEmptyDirectory() var watcher = host.GetExport(); var projectDirectory = Path.GetDirectoryName(host.Workspace.CurrentSolution.Projects.First().FilePath); + const string filename = "FileName.cs"; var filePath = Path.Combine(projectDirectory, filename); File.WriteAllText(filePath, "text"); @@ -68,7 +69,7 @@ public async Task TestFileMovedToPreviouslyEmptyDirectory() } [Fact] - public void TestMultipleDirectoryWatchers() + public async Task TestMultipleDirectoryWatchers() { using (var host = CreateEmptyOmniSharpHost()) { @@ -80,7 +81,7 @@ public void TestMultipleDirectoryWatchers() watcher.Watch("", (path, changeType) => { secondWatcherCalled = true; }); var handler = GetRequestHandler(host); - handler.Handle(new[] { new FilesChangedRequest() { FileName = "FileName.cs", ChangeType = FileChangeType.Create } }); + await handler.Handle(new[] { new FilesChangedRequest() { FileName = "FileName.cs", ChangeType = FileChangeType.Create } }); Assert.True(firstWatcherCalled); Assert.True(secondWatcherCalled); @@ -88,7 +89,7 @@ public void TestMultipleDirectoryWatchers() } [Fact] - public void TestFileExtensionWatchers() + public async Task TestFileExtensionWatchers() { using (var host = CreateEmptyOmniSharpHost()) { @@ -98,10 +99,38 @@ public void TestFileExtensionWatchers() watcher.Watch(".cs", (path, changeType) => { extensionWatcherCalled = true; }); var handler = GetRequestHandler(host); - handler.Handle(new[] { new FilesChangedRequest() { FileName = "FileName.cs", ChangeType = FileChangeType.Create } }); + await handler.Handle(new[] { new FilesChangedRequest() { FileName = "FileName.cs", ChangeType = FileChangeType.Create } }); Assert.True(extensionWatcherCalled); } } + + [Fact] + // This is specifically added to workaround VScode broken file remove notifications on folder removals/moves/renames. + // It's by design at VsCode and will probably not get fixed any time soon if ever. + public async Task TestThatOnFolderRemovalFilesUnderFolderAreRemoved() + { + using (var testProject = await TestAssets.Instance.GetTestProjectAsync("ProjectAndSolution")) + using (var host = CreateOmniSharpHost(testProject.Directory)) + { + var watcher = host.GetExport(); + + var filePath = await AddFile(host); + + await GetRequestHandler(host).Handle(new[] { new FilesChangedRequest() { FileName = Path.GetDirectoryName(filePath), ChangeType = FileChangeType.DirectoryDelete } }); + + Assert.DoesNotContain(host.Workspace.CurrentSolution.Projects.First().Documents, d => d.FilePath == filePath && d.Name == Path.GetFileName(filePath)); + } + } + + private async Task AddFile(OmniSharpTestHost host) + { + var projectDirectory = Path.GetDirectoryName(host.Workspace.CurrentSolution.Projects.First().FilePath); + const string filename = "FileName.cs"; + var filePath = Path.Combine(projectDirectory, filename); + File.WriteAllText(filePath, "text"); + await GetRequestHandler(host).Handle(new[] { new FilesChangedRequest() { FileName = filePath, ChangeType = FileChangeType.Create } }); + return filePath; + } } } From 92f41d9300409d41837057cd4f317191016590e5 Mon Sep 17 00:00:00 2001 From: Savpek Date: Fri, 5 Jun 2020 14:50:39 +0300 Subject: [PATCH 07/10] Small refactoring for tests --- .../FilesChangedFacts.cs | 18 +++++------------- 1 file changed, 5 insertions(+), 13 deletions(-) diff --git a/tests/OmniSharp.Roslyn.CSharp.Tests/FilesChangedFacts.cs b/tests/OmniSharp.Roslyn.CSharp.Tests/FilesChangedFacts.cs index 460d86b10d..623ef1c38e 100644 --- a/tests/OmniSharp.Roslyn.CSharp.Tests/FilesChangedFacts.cs +++ b/tests/OmniSharp.Roslyn.CSharp.Tests/FilesChangedFacts.cs @@ -26,11 +26,7 @@ public async Task TestFileAddedToMSBuildWorkspaceOnCreation() { var watcher = host.GetExport(); - var path = Path.GetDirectoryName(host.Workspace.CurrentSolution.Projects.First().FilePath); - var filePath = Path.Combine(path, "FileName.cs"); - File.WriteAllText(filePath, "text"); - var handler = GetRequestHandler(host); - await handler.Handle(new[] { new FilesChangedRequest() { FileName = filePath, ChangeType = FileChangeType.Create } }); + var filePath = await AddFile(host); Assert.Contains(host.Workspace.CurrentSolution.Projects.First().Documents, d => d.FilePath == filePath && d.Name == "FileName.cs"); } @@ -46,22 +42,18 @@ public async Task TestFileMovedToPreviouslyEmptyDirectory() var projectDirectory = Path.GetDirectoryName(host.Workspace.CurrentSolution.Projects.First().FilePath); - const string filename = "FileName.cs"; - var filePath = Path.Combine(projectDirectory, filename); - File.WriteAllText(filePath, "text"); - var handler = GetRequestHandler(host); - await handler.Handle(new[] { new FilesChangedRequest() { FileName = filePath, ChangeType = FileChangeType.Create } }); + var filePath = await AddFile(host); Assert.Contains(host.Workspace.CurrentSolution.Projects.First().Documents, d => d.FilePath == filePath && d.Name == "FileName.cs"); var nestedDirectory = Path.Combine(projectDirectory, "Nested"); Directory.CreateDirectory(nestedDirectory); - var destinationPath = Path.Combine(nestedDirectory, filename); + var destinationPath = Path.Combine(nestedDirectory, Path.GetFileName(filePath)); File.Move(filePath, destinationPath); - await handler.Handle(new[] { new FilesChangedRequest() { FileName = filePath, ChangeType = FileChangeType.Delete } }); - await handler.Handle(new[] { new FilesChangedRequest() { FileName = destinationPath, ChangeType = FileChangeType.Create } }); + await GetRequestHandler(host).Handle(new[] { new FilesChangedRequest() { FileName = filePath, ChangeType = FileChangeType.Delete } }); + await GetRequestHandler(host).Handle(new[] { new FilesChangedRequest() { FileName = destinationPath, ChangeType = FileChangeType.Create } }); Assert.Contains(host.Workspace.CurrentSolution.Projects.First().Documents, d => d.FilePath == destinationPath && d.Name == "FileName.cs"); Assert.DoesNotContain(host.Workspace.CurrentSolution.Projects.First().Documents, d => d.FilePath == filePath && d.Name == "FileName.cs"); From a62d7d6e1341db9368503bb04b27f21faeaa724f Mon Sep 17 00:00:00 2001 From: Savpek Date: Fri, 5 Jun 2020 17:17:33 +0300 Subject: [PATCH 08/10] Testfixes --- tests/OmniSharp.Cake.Tests/LineIndexHelperFacts.cs | 14 +++++++++++++- 1 file changed, 13 insertions(+), 1 deletion(-) diff --git a/tests/OmniSharp.Cake.Tests/LineIndexHelperFacts.cs b/tests/OmniSharp.Cake.Tests/LineIndexHelperFacts.cs index 3dc6f031f4..4446050a52 100644 --- a/tests/OmniSharp.Cake.Tests/LineIndexHelperFacts.cs +++ b/tests/OmniSharp.Cake.Tests/LineIndexHelperFacts.cs @@ -7,6 +7,7 @@ using Microsoft.CodeAnalysis.Text; using Microsoft.Extensions.Logging; using OmniSharp.Cake.Utilities; +using OmniSharp.FileWatching; using OmniSharp.Services; using OmniSharp.Utilities; using Xunit; @@ -59,7 +60,7 @@ private static OmniSharpWorkspace CreateSimpleWorkspace(string fileName, string var workspace = new OmniSharpWorkspace( new HostServicesAggregator( Enumerable.Empty(), new LoggerFactory()), - new LoggerFactory(), null); + new LoggerFactory(), new DummyFileSystemWatcher()); var projectInfo = ProjectInfo.Create(ProjectId.CreateNewId(), VersionStamp.Create(), "ProjectNameVal", "AssemblyNameVal", LanguageNames.CSharp); @@ -147,5 +148,16 @@ public async Task TranslateFromGenerated_Should_Translate_To_Negative_If_Outside Assert.Equal(expected, actualIndex); } + + private class DummyFileSystemWatcher : IFileSystemWatcher + { + public void Watch(string pathOrExtension, FileSystemNotificationCallback callback) + { + } + + public void WatchDirectories(FileSystemNotificationCallback callback) + { + } + } } } From e90ea72d72e6b107785d2b11459dda1b75de9936 Mon Sep 17 00:00:00 2001 From: Savpek Date: Sat, 6 Jun 2020 09:16:23 +0300 Subject: [PATCH 09/10] Review fix --- src/OmniSharp.Host/FileWatching/ManualFileSystemWatcher.cs | 5 ----- 1 file changed, 5 deletions(-) diff --git a/src/OmniSharp.Host/FileWatching/ManualFileSystemWatcher.cs b/src/OmniSharp.Host/FileWatching/ManualFileSystemWatcher.cs index ca7658715f..ce7c22ec81 100644 --- a/src/OmniSharp.Host/FileWatching/ManualFileSystemWatcher.cs +++ b/src/OmniSharp.Host/FileWatching/ManualFileSystemWatcher.cs @@ -22,11 +22,6 @@ public void Notify(string filePath, FileChangeType changeType = FileChangeType.U { if(changeType == FileChangeType.DirectoryDelete) { - foreach(var matchingCallback in _callbacksMap.AsEnumerable().Where(x => x.Key.StartsWith(filePath + Path.DirectorySeparatorChar, StringComparison.OrdinalIgnoreCase))) - { - matchingCallback.Value.Invoke(matchingCallback.Key, FileChangeType.Delete); - } - _folderCallbacks.Invoke(filePath, FileChangeType.DirectoryDelete); } From b5f39526d9fc2b402e430ed8c3e3bd02381e7888 Mon Sep 17 00:00:00 2001 From: Savpek Date: Sat, 6 Jun 2020 09:59:51 +0300 Subject: [PATCH 10/10] Rebuild