From fbe7a234dadb35a7853b4af05ea47657e7214508 Mon Sep 17 00:00:00 2001 From: Ravi Chande Date: Wed, 19 Dec 2018 11:05:04 -0800 Subject: [PATCH 1/5] Detect when files move to empty directories Augment the IFileSystemWatcher to allow watching a directory recursively. This enables OmniSharp to watch a project's entire directory tree (as oppposed to the specific subdirectories that contain documents) --- .../FileWatching/IFileSystemWatcher.cs | 8 +++++ .../FileWatching/ManualFileSystemWatcher.cs | 35 +++++++++++++++++++ src/OmniSharp.MSBuild/ProjectManager.cs | 3 ++ .../FilesChangedFacts.cs | 31 ++++++++++++++++ 4 files changed, 77 insertions(+) diff --git a/src/OmniSharp.Abstractions/FileWatching/IFileSystemWatcher.cs b/src/OmniSharp.Abstractions/FileWatching/IFileSystemWatcher.cs index 90bccf5196..33c220f5e7 100644 --- a/src/OmniSharp.Abstractions/FileWatching/IFileSystemWatcher.cs +++ b/src/OmniSharp.Abstractions/FileWatching/IFileSystemWatcher.cs @@ -1,4 +1,5 @@ using OmniSharp.Models.FilesChanged; +using System.IO; namespace OmniSharp.FileWatching { @@ -12,5 +13,12 @@ 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); + + /// + /// Call to watch a a directory path (and child directory paths) for changes + /// + /// The directory to watch. + /// The callback that will be invoked when a change occurs in the directory. + void WatchRecursively(DirectoryInfo directory, FileSystemNotificationCallback callback); } } diff --git a/src/OmniSharp.Host/FileWatching/ManualFileSystemWatcher.cs b/src/OmniSharp.Host/FileWatching/ManualFileSystemWatcher.cs index 0a485eb27e..2095515221 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,10 +9,12 @@ internal partial class ManualFileSystemWatcher : IFileSystemWatcher, IFileSystem { private readonly object _gate = new object(); private readonly Dictionary _callbacksMap; + private readonly Dictionary _recursiveCallbacksMap; public ManualFileSystemWatcher() { _callbacksMap = new Dictionary(StringComparer.OrdinalIgnoreCase); + _recursiveCallbacksMap = new Dictionary(); } public void Notify(string filePath, FileChangeType changeType = FileChangeType.Unspecified) @@ -29,6 +32,14 @@ public void Notify(string filePath, FileChangeType changeType = FileChangeType.U directoryCallbacks.Invoke(filePath, changeType); } + var parentDirectory = _recursiveCallbacksMap.Keys.FirstOrDefault( + k => filePath.StartsWith(k.FullName)); + + if (parentDirectory != null) + { + _recursiveCallbacksMap[parentDirectory].Invoke(filePath, changeType); + } + var extension = Path.GetExtension(filePath); if (!string.IsNullOrEmpty(extension) && _callbacksMap.TryGetValue(extension, out var extensionCallbacks)) @@ -61,5 +72,29 @@ public void Watch(string pathOrExtension, FileSystemNotificationCallback callbac callbacks.Add(callback); } } + + public void WatchRecursively(DirectoryInfo directory, FileSystemNotificationCallback callback) + { + if (directory == null) + { + throw new ArgumentNullException(nameof(directory)); + } + + if (callback == null) + { + throw new ArgumentNullException(nameof(callback)); + } + + lock (_gate) + { + if (!_recursiveCallbacksMap.TryGetValue(directory, out var callbacks)) + { + callbacks = new Callbacks(); + _recursiveCallbacksMap.Add(directory, callbacks); + } + + callbacks.Add(callback); + } + } } } diff --git a/src/OmniSharp.MSBuild/ProjectManager.cs b/src/OmniSharp.MSBuild/ProjectManager.cs index 259bbd7cc5..32b3bbb856 100644 --- a/src/OmniSharp.MSBuild/ProjectManager.cs +++ b/src/OmniSharp.MSBuild/ProjectManager.cs @@ -443,6 +443,9 @@ private void UpdateSourceFiles(Project project, IList sourceFiles) { _workspace.RemoveDocument(currentDocument.Value); } + + // Finally, recursively watch the project's directory for file changes + _fileSystemWatcher.WatchRecursively(new DirectoryInfo(Path.GetDirectoryName(project.FilePath)), _onDirectoryFileChanged); } private void OnDirectoryFileChanged(string path, FileChangeType changeType) diff --git a/tests/OmniSharp.Roslyn.CSharp.Tests/FilesChangedFacts.cs b/tests/OmniSharp.Roslyn.CSharp.Tests/FilesChangedFacts.cs index 7e63762e79..0f7fb8a351 100644 --- a/tests/OmniSharp.Roslyn.CSharp.Tests/FilesChangedFacts.cs +++ b/tests/OmniSharp.Roslyn.CSharp.Tests/FilesChangedFacts.cs @@ -36,6 +36,37 @@ public async Task TestFileAddedToMSBuildWorkspaceOnCreation() } } + [Fact] + public async Task TestFileMovedToPreviouslyEmptyDirectory() + { + using (var testProject = await TestAssets.Instance.GetTestProjectAsync("ProjectAndSolution")) + using (var host = CreateOmniSharpHost(testProject.Directory)) + { + 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"); + var handler = GetRequestHandler(host); + await handler.Handle(new[] { new FilesChangedRequest() { FileName = filePath, ChangeType = FileChangeType.Create } }); + + Assert.Contains(host.Workspace.CurrentSolution.Projects.First().Documents, d => d.Name == filePath); + + var nestedDirectory = Path.Combine(projectDirectory, "Nested"); + Directory.CreateDirectory(nestedDirectory); + + var destinationPath = Path.Combine(nestedDirectory, filename); + 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 } }); + + Assert.Contains(host.Workspace.CurrentSolution.Projects.First().Documents, d => d.Name == destinationPath); + Assert.DoesNotContain(host.Workspace.CurrentSolution.Projects.First().Documents, d => d.Name == filePath); + } + } + [Fact] public void TestMultipleDirectoryWatchers() { From 4bba299244464a2959c2162d878bd978ee93a55a Mon Sep 17 00:00:00 2001 From: Ravi Chande Date: Wed, 19 Dec 2018 12:56:11 -0800 Subject: [PATCH 2/5] Watch all .cs files --- src/OmniSharp.MSBuild/ProjectFile/ProjectFileInfo.cs | 2 ++ src/OmniSharp.MSBuild/ProjectManager.cs | 7 ++----- 2 files changed, 4 insertions(+), 5 deletions(-) diff --git a/src/OmniSharp.MSBuild/ProjectFile/ProjectFileInfo.cs b/src/OmniSharp.MSBuild/ProjectFile/ProjectFileInfo.cs index dc7d484a78..49d8d4df77 100644 --- a/src/OmniSharp.MSBuild/ProjectFile/ProjectFileInfo.cs +++ b/src/OmniSharp.MSBuild/ProjectFile/ProjectFileInfo.cs @@ -49,6 +49,8 @@ internal partial class ProjectFileInfo public ImmutableArray PackageReferences => _data.PackageReferences; public ImmutableArray Analyzers => _data.Analyzers; + public string RootNamespace { get; internal set; } + private ProjectFileInfo( ProjectId id, string filePath, diff --git a/src/OmniSharp.MSBuild/ProjectManager.cs b/src/OmniSharp.MSBuild/ProjectManager.cs index 32b3bbb856..6e415ca378 100644 --- a/src/OmniSharp.MSBuild/ProjectManager.cs +++ b/src/OmniSharp.MSBuild/ProjectManager.cs @@ -275,6 +275,8 @@ private void ProcessQueue(CancellationToken cancellationToken) { _processingQueue = false; } + + _fileSystemWatcher.Watch("*.cs", _onDirectoryFileChanged); } private (ProjectFileInfo, ProjectLoadedEventArgs) LoadProject(string projectFilePath) @@ -421,8 +423,6 @@ private void UpdateSourceFiles(Project project, IList sourceFiles) // Add source files to the project. foreach (var sourceFile in sourceFiles) { - _fileSystemWatcher.Watch(Path.GetDirectoryName(sourceFile), _onDirectoryFileChanged); - // If a document for this source file already exists in the project, carry on. if (currentDocuments.Remove(sourceFile)) { @@ -443,9 +443,6 @@ private void UpdateSourceFiles(Project project, IList sourceFiles) { _workspace.RemoveDocument(currentDocument.Value); } - - // Finally, recursively watch the project's directory for file changes - _fileSystemWatcher.WatchRecursively(new DirectoryInfo(Path.GetDirectoryName(project.FilePath)), _onDirectoryFileChanged); } private void OnDirectoryFileChanged(string path, FileChangeType changeType) From 3d954924be8bdebd5e25ecae40edeb7f7f449cdf Mon Sep 17 00:00:00 2001 From: Ravi Chande Date: Wed, 9 Jan 2019 12:28:01 -0800 Subject: [PATCH 3/5] Fix tests --- .../FileWatching/IFileSystemWatcher.cs | 7 ----- .../FileWatching/ManualFileSystemWatcher.cs | 26 ------------------- src/OmniSharp.MSBuild/ProjectManager.cs | 2 +- 3 files changed, 1 insertion(+), 34 deletions(-) diff --git a/src/OmniSharp.Abstractions/FileWatching/IFileSystemWatcher.cs b/src/OmniSharp.Abstractions/FileWatching/IFileSystemWatcher.cs index 33c220f5e7..d5d2c47d63 100644 --- a/src/OmniSharp.Abstractions/FileWatching/IFileSystemWatcher.cs +++ b/src/OmniSharp.Abstractions/FileWatching/IFileSystemWatcher.cs @@ -13,12 +13,5 @@ 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); - - /// - /// Call to watch a a directory path (and child directory paths) for changes - /// - /// The directory to watch. - /// The callback that will be invoked when a change occurs in the directory. - void WatchRecursively(DirectoryInfo directory, FileSystemNotificationCallback callback); } } diff --git a/src/OmniSharp.Host/FileWatching/ManualFileSystemWatcher.cs b/src/OmniSharp.Host/FileWatching/ManualFileSystemWatcher.cs index 2095515221..1dc5ebcc8d 100644 --- a/src/OmniSharp.Host/FileWatching/ManualFileSystemWatcher.cs +++ b/src/OmniSharp.Host/FileWatching/ManualFileSystemWatcher.cs @@ -9,12 +9,10 @@ internal partial class ManualFileSystemWatcher : IFileSystemWatcher, IFileSystem { private readonly object _gate = new object(); private readonly Dictionary _callbacksMap; - private readonly Dictionary _recursiveCallbacksMap; public ManualFileSystemWatcher() { _callbacksMap = new Dictionary(StringComparer.OrdinalIgnoreCase); - _recursiveCallbacksMap = new Dictionary(); } public void Notify(string filePath, FileChangeType changeType = FileChangeType.Unspecified) @@ -72,29 +70,5 @@ public void Watch(string pathOrExtension, FileSystemNotificationCallback callbac callbacks.Add(callback); } } - - public void WatchRecursively(DirectoryInfo directory, FileSystemNotificationCallback callback) - { - if (directory == null) - { - throw new ArgumentNullException(nameof(directory)); - } - - if (callback == null) - { - throw new ArgumentNullException(nameof(callback)); - } - - lock (_gate) - { - if (!_recursiveCallbacksMap.TryGetValue(directory, out var callbacks)) - { - callbacks = new Callbacks(); - _recursiveCallbacksMap.Add(directory, callbacks); - } - - callbacks.Add(callback); - } - } } } diff --git a/src/OmniSharp.MSBuild/ProjectManager.cs b/src/OmniSharp.MSBuild/ProjectManager.cs index 6e415ca378..3b37808548 100644 --- a/src/OmniSharp.MSBuild/ProjectManager.cs +++ b/src/OmniSharp.MSBuild/ProjectManager.cs @@ -276,7 +276,7 @@ private void ProcessQueue(CancellationToken cancellationToken) _processingQueue = false; } - _fileSystemWatcher.Watch("*.cs", _onDirectoryFileChanged); + _fileSystemWatcher.Watch(".cs", _onDirectoryFileChanged); } private (ProjectFileInfo, ProjectLoadedEventArgs) LoadProject(string projectFilePath) From acca41eddc899aeaa422599cb626273d5d9cb6f7 Mon Sep 17 00:00:00 2001 From: Ravi Chande Date: Wed, 9 Jan 2019 12:46:41 -0800 Subject: [PATCH 4/5] revert changes to mnual file watcher --- .../FileWatching/ManualFileSystemWatcher.cs | 9 --------- 1 file changed, 9 deletions(-) diff --git a/src/OmniSharp.Host/FileWatching/ManualFileSystemWatcher.cs b/src/OmniSharp.Host/FileWatching/ManualFileSystemWatcher.cs index 1dc5ebcc8d..0a485eb27e 100644 --- a/src/OmniSharp.Host/FileWatching/ManualFileSystemWatcher.cs +++ b/src/OmniSharp.Host/FileWatching/ManualFileSystemWatcher.cs @@ -1,7 +1,6 @@ using System; using System.Collections.Generic; using System.IO; -using System.Linq; namespace OmniSharp.FileWatching { @@ -30,14 +29,6 @@ public void Notify(string filePath, FileChangeType changeType = FileChangeType.U directoryCallbacks.Invoke(filePath, changeType); } - var parentDirectory = _recursiveCallbacksMap.Keys.FirstOrDefault( - k => filePath.StartsWith(k.FullName)); - - if (parentDirectory != null) - { - _recursiveCallbacksMap[parentDirectory].Invoke(filePath, changeType); - } - var extension = Path.GetExtension(filePath); if (!string.IsNullOrEmpty(extension) && _callbacksMap.TryGetValue(extension, out var extensionCallbacks)) From 6535cbb058642cc591c913940f39be4113396b5d Mon Sep 17 00:00:00 2001 From: Ravi Chande Date: Wed, 9 Jan 2019 14:35:30 -0800 Subject: [PATCH 5/5] clean up --- src/OmniSharp.Abstractions/FileWatching/IFileSystemWatcher.cs | 1 - src/OmniSharp.MSBuild/ProjectFile/ProjectFileInfo.cs | 2 -- 2 files changed, 3 deletions(-) diff --git a/src/OmniSharp.Abstractions/FileWatching/IFileSystemWatcher.cs b/src/OmniSharp.Abstractions/FileWatching/IFileSystemWatcher.cs index d5d2c47d63..90bccf5196 100644 --- a/src/OmniSharp.Abstractions/FileWatching/IFileSystemWatcher.cs +++ b/src/OmniSharp.Abstractions/FileWatching/IFileSystemWatcher.cs @@ -1,5 +1,4 @@ using OmniSharp.Models.FilesChanged; -using System.IO; namespace OmniSharp.FileWatching { diff --git a/src/OmniSharp.MSBuild/ProjectFile/ProjectFileInfo.cs b/src/OmniSharp.MSBuild/ProjectFile/ProjectFileInfo.cs index 49d8d4df77..dc7d484a78 100644 --- a/src/OmniSharp.MSBuild/ProjectFile/ProjectFileInfo.cs +++ b/src/OmniSharp.MSBuild/ProjectFile/ProjectFileInfo.cs @@ -49,8 +49,6 @@ internal partial class ProjectFileInfo public ImmutableArray PackageReferences => _data.PackageReferences; public ImmutableArray Analyzers => _data.Analyzers; - public string RootNamespace { get; internal set; } - private ProjectFileInfo( ProjectId id, string filePath,