Skip to content

Commit

Permalink
Merge pull request #1821 from savpek/feature/attempt-to-fix-folder-de…
Browse files Browse the repository at this point in the history
…letion-1

Fix folder deletion with VSCode
  • Loading branch information
filipw authored Jun 6, 2020
2 parents 845fa01 + b5f3952 commit 2e1b1c0
Show file tree
Hide file tree
Showing 6 changed files with 82 additions and 19 deletions.
3 changes: 2 additions & 1 deletion src/OmniSharp.Abstractions/FileWatching/FileChangeType.cs
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ public enum FileChangeType
Unspecified = 0,
Change,
Create,
Delete
Delete,
DirectoryDelete
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -12,5 +12,6 @@ public interface IFileSystemWatcher
/// <param name="pathOrExtension">The file path, directory path or file extension to watch.</param>
/// <param name="callback">The callback that will be invoked when a change occurs in the watched file or directory.</param>
void Watch(string pathOrExtension, FileSystemNotificationCallback callback);
void WatchDirectories(FileSystemNotificationCallback callback);
}
}
12 changes: 12 additions & 0 deletions src/OmniSharp.Host/FileWatching/ManualFileSystemWatcher.cs
Original file line number Diff line number Diff line change
@@ -1,13 +1,15 @@
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;

namespace OmniSharp.FileWatching
{
internal partial class ManualFileSystemWatcher : IFileSystemWatcher, IFileSystemNotifier
{
private readonly object _gate = new object();
private readonly Dictionary<string, Callbacks> _callbacksMap;
private readonly Callbacks _folderCallbacks = new Callbacks();

public ManualFileSystemWatcher()
{
Expand All @@ -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);
Expand All @@ -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)
Expand Down
16 changes: 16 additions & 0 deletions src/OmniSharp.Roslyn/OmniSharpWorkspace.cs
Original file line number Diff line number Diff line change
Expand Up @@ -41,10 +41,26 @@ public OmniSharpWorkspace(HostServicesAggregator aggregator, ILoggerFactory logg
{
BufferManager = new BufferManager(this, fileSystemWatcher);
_logger = loggerFactory.CreateLogger<OmniSharpWorkspace>();
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<string, Task> handler)
{
_waitForProjectModelReadyHandlers.Add(handler);
Expand Down
14 changes: 13 additions & 1 deletion tests/OmniSharp.Cake.Tests/LineIndexHelperFacts.cs
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -59,7 +60,7 @@ private static OmniSharpWorkspace CreateSimpleWorkspace(string fileName, string
var workspace = new OmniSharpWorkspace(
new HostServicesAggregator(
Enumerable.Empty<IHostServicesProvider>(), new LoggerFactory()),
new LoggerFactory(), null);
new LoggerFactory(), new DummyFileSystemWatcher());

var projectInfo = ProjectInfo.Create(ProjectId.CreateNewId(), VersionStamp.Create(),
"ProjectNameVal", "AssemblyNameVal", LanguageNames.CSharp);
Expand Down Expand Up @@ -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)
{
}
}
}
}
55 changes: 38 additions & 17 deletions tests/OmniSharp.Roslyn.CSharp.Tests/FilesChangedFacts.cs
Original file line number Diff line number Diff line change
Expand Up @@ -26,11 +26,7 @@ public async Task TestFileAddedToMSBuildWorkspaceOnCreation()
{
var watcher = host.GetExport<IFileSystemWatcher>();

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");
}
Expand All @@ -45,30 +41,27 @@ public async Task TestFileMovedToPreviouslyEmptyDirectory()
var watcher = host.GetExport<IFileSystemWatcher>();

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");
}
}

[Fact]
public void TestMultipleDirectoryWatchers()
public async Task TestMultipleDirectoryWatchers()
{
using (var host = CreateEmptyOmniSharpHost())
{
Expand All @@ -80,15 +73,15 @@ 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);
}
}

[Fact]
public void TestFileExtensionWatchers()
public async Task TestFileExtensionWatchers()
{
using (var host = CreateEmptyOmniSharpHost())
{
Expand All @@ -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<IFileSystemWatcher>();

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<string> 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;
}
}
}

0 comments on commit 2e1b1c0

Please sign in to comment.