diff --git a/src/OmniSharp.Abstractions/FileWatching/FileChangeType.cs b/src/OmniSharp.Abstractions/FileWatching/FileChangeType.cs
index 218513e78b..ca5d487cda 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,
+ DirectoryDelete
}
}
diff --git a/src/OmniSharp.Abstractions/FileWatching/IFileSystemWatcher.cs b/src/OmniSharp.Abstractions/FileWatching/IFileSystemWatcher.cs
index 90bccf5196..200146cea4 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 WatchDirectories(FileSystemNotificationCallback callback);
}
}
diff --git a/src/OmniSharp.Host/FileWatching/ManualFileSystemWatcher.cs b/src/OmniSharp.Host/FileWatching/ManualFileSystemWatcher.cs
index 0a485eb27e..ce7c22ec81 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,11 @@ public void Notify(string filePath, FileChangeType changeType = FileChangeType.U
{
lock (_gate)
{
+ if(changeType == FileChangeType.DirectoryDelete)
+ {
+ _folderCallbacks.Invoke(filePath, FileChangeType.DirectoryDelete);
+ }
+
if (_callbacksMap.TryGetValue(filePath, out var fileCallbacks))
{
fileCallbacks.Invoke(filePath, changeType);
@@ -38,6 +45,11 @@ public void Notify(string filePath, FileChangeType changeType = FileChangeType.U
}
}
+ public void WatchDirectories(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..fe0e39bd08 100644
--- a/src/OmniSharp.Roslyn/OmniSharpWorkspace.cs
+++ b/src/OmniSharp.Roslyn/OmniSharpWorkspace.cs
@@ -41,10 +41,26 @@ public OmniSharpWorkspace(HostServicesAggregator aggregator, ILoggerFactory logg
{
BufferManager = new BufferManager(this, fileSystemWatcher);
_logger = loggerFactory.CreateLogger();
+ fileSystemWatcher.WatchDirectories(OnDirectoryRemoved);
}
public override bool CanOpenDocuments => true;
+
+ 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 + Path.DirectorySeparatorChar, StringComparison.OrdinalIgnoreCase));
+
+ foreach(var doc in docs)
+ {
+ OnDocumentRemoved(doc.Id);
+ }
+ }
+ }
+
public void AddWaitForProjectModelReadyHandler(Func handler)
{
_waitForProjectModelReadyHandlers.Add(handler);
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)
+ {
+ }
+ }
}
}
diff --git a/tests/OmniSharp.Roslyn.CSharp.Tests/FilesChangedFacts.cs b/tests/OmniSharp.Roslyn.CSharp.Tests/FilesChangedFacts.cs
index ace7d9d098..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");
}
@@ -45,22 +41,19 @@ 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");
- 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");
@@ -68,7 +61,7 @@ public async Task TestFileMovedToPreviouslyEmptyDirectory()
}
[Fact]
- public void TestMultipleDirectoryWatchers()
+ public async Task TestMultipleDirectoryWatchers()
{
using (var host = CreateEmptyOmniSharpHost())
{
@@ -80,7 +73,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 +81,7 @@ public void TestMultipleDirectoryWatchers()
}
[Fact]
- public void TestFileExtensionWatchers()
+ public async Task TestFileExtensionWatchers()
{
using (var host = CreateEmptyOmniSharpHost())
{
@@ -98,10 +91,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;
+ }
}
}