diff --git a/Directory.Packages.props b/Directory.Packages.props
index bee1208..f657780 100644
--- a/Directory.Packages.props
+++ b/Directory.Packages.props
@@ -2,8 +2,8 @@
true
true
-
- $(NoWarn);NU1507
+
+ $(NoWarn);NU1507
@@ -12,6 +12,7 @@
+
@@ -24,6 +25,7 @@
+
@@ -46,14 +48,14 @@
-
-
-
-
-
-
+
+
+
+
+
+
-
+
\ No newline at end of file
diff --git a/src/SharpIDE.Application/Features/FileWatching/IdeFileWatcher.cs b/src/SharpIDE.Application/Features/FileWatching/IdeFileWatcher.cs
new file mode 100644
index 0000000..88b204d
--- /dev/null
+++ b/src/SharpIDE.Application/Features/FileWatching/IdeFileWatcher.cs
@@ -0,0 +1,93 @@
+using FileWatcherEx;
+using Microsoft.Extensions.FileSystemGlobbing;
+using SharpIDE.Application.Features.SolutionDiscovery.VsPersistence;
+
+namespace SharpIDE.Application.Features.FileWatching;
+
+public sealed class IdeFileWatcher : IDisposable
+{
+ private Matcher? _matcher;
+ private FileSystemWatcherEx? _fileWatcher;
+ private SharpIdeSolutionModel? _solution;
+
+ public void StartWatching(SharpIdeSolutionModel solution)
+ {
+ _solution = solution;
+
+ var matcher = new Matcher();
+ //matcher.AddIncludePatterns(["**/*.cs", "**/*.csproj", "**/*.sln"]);
+ matcher.AddIncludePatterns(["**/*"]);
+ matcher.AddExcludePatterns(["**/bin", "**/obj", "**/node_modules", "**/.vs", "**/.git", "**/.idea", "**/.vscode"]);
+ _matcher = matcher;
+
+ var fileWatcher = new FileSystemWatcherEx();
+ fileWatcher.FolderPath = solution.DirectoryPath;
+ //fileWatcher.Filters.AddRange(["*"]);
+ fileWatcher.IncludeSubdirectories = true;
+ fileWatcher.NotifyFilter = NotifyFilters.LastWrite | NotifyFilters.FileName | NotifyFilters.DirectoryName;
+ fileWatcher.OnChanged += OnEvent;
+ fileWatcher.OnCreated += OnEvent;
+ fileWatcher.OnDeleted += OnEvent;
+ fileWatcher.OnRenamed += OnEvent;
+
+ fileWatcher.Start();
+ _fileWatcher = fileWatcher;
+ }
+
+ public void StopWatching()
+ {
+ if (_fileWatcher is not null)
+ {
+ _fileWatcher.Stop();
+ _fileWatcher.Dispose();
+ _fileWatcher = null!;
+ }
+ }
+
+ // TODO: Put events on a queue and process them in the background to avoid filling the buffer? FileSystemWatcherEx might already handle this
+ private void OnEvent(object? sender, FileChangedEvent e)
+ {
+ var matchResult = _matcher!.Match(_solution!.DirectoryPath, e.FullPath);
+ if (!matchResult.HasMatches) return;
+ switch (e.ChangeType)
+ {
+ case ChangeType.CHANGED: HandleChanged(e.FullPath); break;
+ case ChangeType.CREATED: HandleCreated(e.FullPath); break;
+ case ChangeType.DELETED: HandleDeleted(e.FullPath); break;
+ case ChangeType.RENAMED: HandleRenamed(e.OldFullPath, e.FullPath); break;
+ default: throw new ArgumentOutOfRangeException();
+ }
+ }
+
+ private void HandleRenamed(string? oldFullPath, string fullPath)
+ {
+
+ Console.WriteLine($"FileSystemWatcher: Renamed - {oldFullPath}, {fullPath}");
+ }
+
+ private void HandleDeleted(string fullPath)
+ {
+ Console.WriteLine($"FileSystemWatcher: Deleted - {fullPath}");
+ }
+
+ private void HandleCreated(string fullPath)
+ {
+ Console.WriteLine($"FileSystemWatcher: Created - {fullPath}");
+ }
+
+ // The only changed event we care about is files, not directories
+ // We will naively assume that if the file name does not have an extension, it's a directory
+ // This may not always be true, but it lets us avoid reading the file system to check
+ // TODO: Make a note to users that they should not use files without extensions
+ private void HandleChanged(string fullPath)
+ {
+ if (Path.HasExtension(fullPath) is false) return;
+ // TODO: Handle updating the content of open files in editors
+ Console.WriteLine($"FileSystemWatcher: Changed - {fullPath}");
+ }
+
+ public void Dispose()
+ {
+ StopWatching();
+ }
+}
diff --git a/src/SharpIDE.Application/Features/SolutionDiscovery/VsPersistence/SharpIdeModels.cs b/src/SharpIDE.Application/Features/SolutionDiscovery/VsPersistence/SharpIdeModels.cs
index 47e4aef..0bda2c8 100644
--- a/src/SharpIDE.Application/Features/SolutionDiscovery/VsPersistence/SharpIdeModels.cs
+++ b/src/SharpIDE.Application/Features/SolutionDiscovery/VsPersistence/SharpIdeModels.cs
@@ -34,6 +34,7 @@ public class SharpIdeSolutionModel : ISharpIdeNode, IExpandableSharpIdeNode
{
public required string Name { get; set; }
public required string FilePath { get; set; }
+ public required string DirectoryPath { get; set; }
public required List Projects { get; set; }
public required List Folders { get; set; }
public required HashSet AllProjects { get; set; }
@@ -48,6 +49,7 @@ public class SharpIdeSolutionModel : ISharpIdeNode, IExpandableSharpIdeNode
var allFiles = new ConcurrentBag();
Name = solutionName;
FilePath = solutionFilePath;
+ DirectoryPath = Path.GetDirectoryName(solutionFilePath)!;
Projects = intermediateModel.Projects.Select(s => new SharpIdeProjectModel(s, allProjects, allFiles, this)).ToList();
Folders = intermediateModel.SolutionFolders.Select(s => new SharpIdeSolutionFolder(s, allProjects, allFiles, this)).ToList();
AllProjects = allProjects.ToHashSet();
diff --git a/src/SharpIDE.Application/SharpIDE.Application.csproj b/src/SharpIDE.Application/SharpIDE.Application.csproj
index fdf55ef..9b3149b 100644
--- a/src/SharpIDE.Application/SharpIDE.Application.csproj
+++ b/src/SharpIDE.Application/SharpIDE.Application.csproj
@@ -19,7 +19,9 @@
+
+
diff --git a/src/SharpIDE.Godot/IdeRoot.cs b/src/SharpIDE.Godot/IdeRoot.cs
index 5dbc4b7..228a27d 100644
--- a/src/SharpIDE.Godot/IdeRoot.cs
+++ b/src/SharpIDE.Godot/IdeRoot.cs
@@ -4,6 +4,8 @@ using Microsoft.Extensions.Hosting;
using SharpIDE.Application.Features.Analysis;
using SharpIDE.Application.Features.Build;
using SharpIDE.Application.Features.Events;
+using SharpIDE.Application.Features.FileWatching;
+using SharpIDE.Application.Features.Run;
using SharpIDE.Application.Features.SolutionDiscovery;
using SharpIDE.Application.Features.SolutionDiscovery.VsPersistence;
using SharpIDE.Godot.Features.BottomPanel;
@@ -39,6 +41,10 @@ public partial class IdeRoot : Control
{
GodotGlobalEvents.Instance = new GodotGlobalEvents();
GlobalEvents.Instance = new GlobalEvents();
+ Singletons.RunService = new RunService();
+ Singletons.BuildService = new BuildService();
+ Singletons.FileWatcher?.Dispose();
+ Singletons.FileWatcher = new IdeFileWatcher();
}
public override void _Ready()
diff --git a/src/SharpIDE.Godot/Singletons.cs b/src/SharpIDE.Godot/Singletons.cs
index e130ff1..618cc1a 100644
--- a/src/SharpIDE.Godot/Singletons.cs
+++ b/src/SharpIDE.Godot/Singletons.cs
@@ -1,4 +1,5 @@
using SharpIDE.Application.Features.Build;
+using SharpIDE.Application.Features.FileWatching;
using SharpIDE.Application.Features.Run;
using SharpIDE.Godot.Features.IdeSettings;
@@ -6,7 +7,8 @@ namespace SharpIDE.Godot;
public static class Singletons
{
- public static RunService RunService { get; } = new RunService();
- public static BuildService BuildService { get; } = new BuildService();
+ public static RunService RunService { get; set; } = null!;
+ public static BuildService BuildService { get; set; } = null!;
+ public static IdeFileWatcher FileWatcher { get; set; } = null!;
public static AppState AppState { get; set; } = null!;
}
\ No newline at end of file