✨ Refresh files on external change
This commit is contained in:
@@ -13,4 +13,17 @@ public class GlobalEvents
|
|||||||
public EventWrapper<SharpIdeProjectModel, Task> ProjectStartedRunning { get; } = new(_ => Task.CompletedTask);
|
public EventWrapper<SharpIdeProjectModel, Task> ProjectStartedRunning { get; } = new(_ => Task.CompletedTask);
|
||||||
public EventWrapper<SharpIdeProjectModel, Task> ProjectStoppedRunning { get; } = new(_ => Task.CompletedTask);
|
public EventWrapper<SharpIdeProjectModel, Task> ProjectStoppedRunning { get; } = new(_ => Task.CompletedTask);
|
||||||
public EventWrapper<ExecutionStopInfo, Task> DebuggerExecutionStopped { get; } = new(_ => Task.CompletedTask);
|
public EventWrapper<ExecutionStopInfo, Task> DebuggerExecutionStopped { get; } = new(_ => Task.CompletedTask);
|
||||||
|
|
||||||
|
public FileSystemWatcherInternal FileSystemWatcherInternal { get; } = new();
|
||||||
|
}
|
||||||
|
|
||||||
|
public class FileSystemWatcherInternal
|
||||||
|
{
|
||||||
|
public EventWrapper<string, Task> DirectoryCreated { get; } = new(_ => Task.CompletedTask);
|
||||||
|
public EventWrapper<string, Task> DirectoryDeleted { get; } = new(_ => Task.CompletedTask);
|
||||||
|
public EventWrapper<string, string, Task> DirectoryRenamed { get; } = new((_, _) => Task.CompletedTask);
|
||||||
|
public EventWrapper<string, Task> FileCreated { get; } = new(_ => Task.CompletedTask);
|
||||||
|
public EventWrapper<string, Task> FileDeleted { get; } = new(_ => Task.CompletedTask);
|
||||||
|
public EventWrapper<string, string, Task> FileRenamed { get; } = new((_, _) => Task.CompletedTask);
|
||||||
|
public EventWrapper<string, Task> FileChanged { get; } = new(_ => Task.CompletedTask);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -37,6 +37,20 @@ public class IdeFileManager
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public async Task ReloadFileFromDisk(SharpIdeFile file)
|
||||||
|
{
|
||||||
|
if (!_openFiles.ContainsKey(file)) throw new InvalidOperationException("File is not open in memory.");
|
||||||
|
|
||||||
|
var newTextTaskLazy = new Lazy<Task<string>>(() => File.ReadAllTextAsync(file.Path));
|
||||||
|
_openFiles[file] = newTextTaskLazy;
|
||||||
|
var textTask = newTextTaskLazy.Value;
|
||||||
|
if (file.IsRoslynWorkspaceFile)
|
||||||
|
{
|
||||||
|
var text = await textTask;
|
||||||
|
RoslynAnalysis.UpdateDocument(file, text);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
public async Task SaveFileAsync(SharpIdeFile file)
|
public async Task SaveFileAsync(SharpIdeFile file)
|
||||||
{
|
{
|
||||||
if (!_openFiles.ContainsKey(file)) throw new InvalidOperationException("File is not open in memory.");
|
if (!_openFiles.ContainsKey(file)) throw new InvalidOperationException("File is not open in memory.");
|
||||||
|
|||||||
@@ -0,0 +1,21 @@
|
|||||||
|
using SharpIDE.Application.Features.Events;
|
||||||
|
using SharpIDE.Application.Features.SolutionDiscovery.VsPersistence;
|
||||||
|
|
||||||
|
namespace SharpIDE.Application.Features.FileWatching;
|
||||||
|
|
||||||
|
public class IdeFileChangeHandler
|
||||||
|
{
|
||||||
|
public SharpIdeSolutionModel SolutionModel { get; set; } = null!;
|
||||||
|
public IdeFileChangeHandler()
|
||||||
|
{
|
||||||
|
GlobalEvents.Instance.FileSystemWatcherInternal.FileChanged.Subscribe(OnFileChanged);
|
||||||
|
}
|
||||||
|
|
||||||
|
private async Task OnFileChanged(string arg)
|
||||||
|
{
|
||||||
|
var sharpIdeFile = SolutionModel.AllFiles.SingleOrDefault(f => f.Path == arg);
|
||||||
|
if (sharpIdeFile is null) return;
|
||||||
|
// TODO: Suppress if SharpIDE changed the file
|
||||||
|
await sharpIdeFile.FileContentsChangedExternallyFromDisk.InvokeParallelAsync();
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,5 +1,6 @@
|
|||||||
using FileWatcherEx;
|
using FileWatcherEx;
|
||||||
using Microsoft.Extensions.FileSystemGlobbing;
|
using Microsoft.Extensions.FileSystemGlobbing;
|
||||||
|
using SharpIDE.Application.Features.Events;
|
||||||
using SharpIDE.Application.Features.SolutionDiscovery.VsPersistence;
|
using SharpIDE.Application.Features.SolutionDiscovery.VsPersistence;
|
||||||
|
|
||||||
namespace SharpIDE.Application.Features.FileWatching;
|
namespace SharpIDE.Application.Features.FileWatching;
|
||||||
@@ -62,7 +63,6 @@ public sealed class IdeFileWatcher : IDisposable
|
|||||||
|
|
||||||
private void HandleRenamed(string? oldFullPath, string fullPath)
|
private void HandleRenamed(string? oldFullPath, string fullPath)
|
||||||
{
|
{
|
||||||
|
|
||||||
Console.WriteLine($"FileSystemWatcher: Renamed - {oldFullPath}, {fullPath}");
|
Console.WriteLine($"FileSystemWatcher: Renamed - {oldFullPath}, {fullPath}");
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -83,8 +83,8 @@ public sealed class IdeFileWatcher : IDisposable
|
|||||||
private void HandleChanged(string fullPath)
|
private void HandleChanged(string fullPath)
|
||||||
{
|
{
|
||||||
if (Path.HasExtension(fullPath) is false) return;
|
if (Path.HasExtension(fullPath) is false) return;
|
||||||
// TODO: Handle updating the content of open files in editors
|
|
||||||
Console.WriteLine($"FileSystemWatcher: Changed - {fullPath}");
|
Console.WriteLine($"FileSystemWatcher: Changed - {fullPath}");
|
||||||
|
GlobalEvents.Instance.FileSystemWatcherInternal.FileChanged.InvokeParallelFireAndForget(fullPath);
|
||||||
}
|
}
|
||||||
|
|
||||||
public void Dispose()
|
public void Dispose()
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
using System.Collections.Concurrent;
|
using System.Collections.Concurrent;
|
||||||
using System.Diagnostics.CodeAnalysis;
|
using System.Diagnostics.CodeAnalysis;
|
||||||
using R3;
|
using R3;
|
||||||
|
using SharpIDE.Application.Features.Events;
|
||||||
using SharpIDE.Application.Features.SolutionDiscovery.VsPersistence;
|
using SharpIDE.Application.Features.SolutionDiscovery.VsPersistence;
|
||||||
|
|
||||||
namespace SharpIDE.Application.Features.SolutionDiscovery;
|
namespace SharpIDE.Application.Features.SolutionDiscovery;
|
||||||
@@ -15,6 +16,8 @@ public class SharpIdeFile : ISharpIdeNode, IChildSharpIdeNode
|
|||||||
public bool IsCsharpFile => Path.EndsWith(".cs", StringComparison.OrdinalIgnoreCase);
|
public bool IsCsharpFile => Path.EndsWith(".cs", StringComparison.OrdinalIgnoreCase);
|
||||||
public bool IsRoslynWorkspaceFile => IsCsharpFile || IsRazorFile || IsCshtmlFile;
|
public bool IsRoslynWorkspaceFile => IsCsharpFile || IsRazorFile || IsCshtmlFile;
|
||||||
public required ReactiveProperty<bool> IsDirty { get; set; }
|
public required ReactiveProperty<bool> IsDirty { get; set; }
|
||||||
|
public EventWrapper<Task> FileContentsChangedExternallyFromDisk { get; } = new(() => Task.CompletedTask);
|
||||||
|
public EventWrapper<Task> FileContentsChangedExternally { get; } = new(() => Task.CompletedTask);
|
||||||
|
|
||||||
[SetsRequiredMembers]
|
[SetsRequiredMembers]
|
||||||
internal SharpIdeFile(string fullPath, string name, IExpandableSharpIdeNode parent, ConcurrentBag<SharpIdeFile> allFiles)
|
internal SharpIdeFile(string fullPath, string name, IExpandableSharpIdeNode parent, ConcurrentBag<SharpIdeFile> allFiles)
|
||||||
|
|||||||
@@ -48,6 +48,12 @@ public partial class SharpIdeCodeEdit : CodeEdit
|
|||||||
SymbolLookup += OnSymbolLookup;
|
SymbolLookup += OnSymbolLookup;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public override void _ExitTree()
|
||||||
|
{
|
||||||
|
_currentFile.FileContentsChangedExternallyFromDisk.Unsubscribe(OnFileChangedExternallyFromDisk);
|
||||||
|
_currentFile.FileContentsChangedExternally.Unsubscribe(OnFileChangedExternallyInMemory);
|
||||||
|
}
|
||||||
|
|
||||||
private void OnBreakpointToggled(long line)
|
private void OnBreakpointToggled(long line)
|
||||||
{
|
{
|
||||||
if (_fileChangingSuppressBreakpointToggleEvent) return;
|
if (_fileChangingSuppressBreakpointToggleEvent) return;
|
||||||
@@ -118,8 +124,7 @@ public partial class SharpIdeCodeEdit : CodeEdit
|
|||||||
GD.Print($"Code fix selected: {id}");
|
GD.Print($"Code fix selected: {id}");
|
||||||
var codeAction = _currentCodeActionsInPopup[(int)id];
|
var codeAction = _currentCodeActionsInPopup[(int)id];
|
||||||
if (codeAction is null) return;
|
if (codeAction is null) return;
|
||||||
var currentCaretPosition = GetCaretPosition();
|
|
||||||
var vScroll = GetVScroll();
|
|
||||||
_ = Task.GodotRun(async () =>
|
_ = Task.GodotRun(async () =>
|
||||||
{
|
{
|
||||||
var affectedFiles = await RoslynAnalysis.ApplyCodeActionAsync(codeAction);
|
var affectedFiles = await RoslynAnalysis.ApplyCodeActionAsync(codeAction);
|
||||||
@@ -127,25 +132,35 @@ public partial class SharpIdeCodeEdit : CodeEdit
|
|||||||
foreach (var (affectedFile, updatedText) in affectedFiles)
|
foreach (var (affectedFile, updatedText) in affectedFiles)
|
||||||
{
|
{
|
||||||
await Singletons.FileManager.UpdateInMemoryIfOpenAndSaveAsync(affectedFile, updatedText);
|
await Singletons.FileManager.UpdateInMemoryIfOpenAndSaveAsync(affectedFile, updatedText);
|
||||||
|
affectedFile.FileContentsChangedExternally.InvokeParallelFireAndForget();
|
||||||
}
|
}
|
||||||
var fileContents = await Singletons.FileManager.GetFileTextAsync(_currentFile);
|
|
||||||
var syntaxHighlighting = await RoslynAnalysis.GetDocumentSyntaxHighlighting(_currentFile);
|
|
||||||
var razorSyntaxHighlighting = await RoslynAnalysis.GetRazorDocumentSyntaxHighlighting(_currentFile);
|
|
||||||
var diagnostics = await RoslynAnalysis.GetDocumentDiagnostics(_currentFile);
|
|
||||||
Callable.From(() =>
|
|
||||||
{
|
|
||||||
BeginComplexOperation();
|
|
||||||
SetText(fileContents);
|
|
||||||
SetSyntaxHighlightingModel(syntaxHighlighting, razorSyntaxHighlighting);
|
|
||||||
SetDiagnosticsModel(diagnostics);
|
|
||||||
SetCaretLine(currentCaretPosition.line);
|
|
||||||
SetCaretColumn(currentCaretPosition.col);
|
|
||||||
SetVScroll(vScroll);
|
|
||||||
EndComplexOperation();
|
|
||||||
}).CallDeferred();
|
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private async Task OnFileChangedExternallyInMemory()
|
||||||
|
{
|
||||||
|
var fileContents = await Singletons.FileManager.GetFileTextAsync(_currentFile);
|
||||||
|
var syntaxHighlighting = RoslynAnalysis.GetDocumentSyntaxHighlighting(_currentFile);
|
||||||
|
var razorSyntaxHighlighting = RoslynAnalysis.GetRazorDocumentSyntaxHighlighting(_currentFile);
|
||||||
|
var diagnostics = RoslynAnalysis.GetDocumentDiagnostics(_currentFile);
|
||||||
|
var slnDiagnostics = RoslynAnalysis.UpdateSolutionDiagnostics();
|
||||||
|
await Task.WhenAll(syntaxHighlighting, razorSyntaxHighlighting, diagnostics);
|
||||||
|
Callable.From(() =>
|
||||||
|
{
|
||||||
|
var currentCaretPosition = GetCaretPosition();
|
||||||
|
var vScroll = GetVScroll();
|
||||||
|
BeginComplexOperation();
|
||||||
|
SetText(fileContents);
|
||||||
|
SetSyntaxHighlightingModel(syntaxHighlighting.Result, razorSyntaxHighlighting.Result);
|
||||||
|
SetDiagnosticsModel(diagnostics.Result);
|
||||||
|
SetCaretLine(currentCaretPosition.line);
|
||||||
|
SetCaretColumn(currentCaretPosition.col);
|
||||||
|
SetVScroll(vScroll);
|
||||||
|
EndComplexOperation();
|
||||||
|
}).CallDeferred();
|
||||||
|
await slnDiagnostics;
|
||||||
|
}
|
||||||
|
|
||||||
public void SetFileLinePosition(SharpIdeFileLinePosition fileLinePosition)
|
public void SetFileLinePosition(SharpIdeFileLinePosition fileLinePosition)
|
||||||
{
|
{
|
||||||
var line = fileLinePosition.Line - 1;
|
var line = fileLinePosition.Line - 1;
|
||||||
@@ -162,6 +177,8 @@ public partial class SharpIdeCodeEdit : CodeEdit
|
|||||||
await Task.CompletedTask.ConfigureAwait(ConfigureAwaitOptions.ForceYielding); // get off the UI thread
|
await Task.CompletedTask.ConfigureAwait(ConfigureAwaitOptions.ForceYielding); // get off the UI thread
|
||||||
_currentFile = file;
|
_currentFile = file;
|
||||||
var readFileTask = Singletons.FileManager.GetFileTextAsync(file);
|
var readFileTask = Singletons.FileManager.GetFileTextAsync(file);
|
||||||
|
_currentFile.FileContentsChangedExternally.Subscribe(OnFileChangedExternallyInMemory);
|
||||||
|
_currentFile.FileContentsChangedExternallyFromDisk.Subscribe(OnFileChangedExternallyFromDisk);
|
||||||
|
|
||||||
var syntaxHighlighting = RoslynAnalysis.GetDocumentSyntaxHighlighting(_currentFile);
|
var syntaxHighlighting = RoslynAnalysis.GetDocumentSyntaxHighlighting(_currentFile);
|
||||||
var razorSyntaxHighlighting = RoslynAnalysis.GetRazorDocumentSyntaxHighlighting(_currentFile);
|
var razorSyntaxHighlighting = RoslynAnalysis.GetRazorDocumentSyntaxHighlighting(_currentFile);
|
||||||
@@ -177,6 +194,12 @@ public partial class SharpIdeCodeEdit : CodeEdit
|
|||||||
SetDiagnosticsModel(await diagnostics);
|
SetDiagnosticsModel(await diagnostics);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private async Task OnFileChangedExternallyFromDisk()
|
||||||
|
{
|
||||||
|
await Singletons.FileManager.ReloadFileFromDisk(_currentFile);
|
||||||
|
await OnFileChangedExternallyInMemory();
|
||||||
|
}
|
||||||
|
|
||||||
public void UnderlineRange(int line, int caretStartCol, int caretEndCol, Color color, float thickness = 1.5f)
|
public void UnderlineRange(int line, int caretStartCol, int caretEndCol, Color color, float thickness = 1.5f)
|
||||||
{
|
{
|
||||||
if (line < 0 || line >= GetLineCount())
|
if (line < 0 || line >= GetLineCount())
|
||||||
|
|||||||
@@ -47,6 +47,7 @@ public partial class IdeRoot : Control
|
|||||||
Singletons.FileWatcher?.Dispose();
|
Singletons.FileWatcher?.Dispose();
|
||||||
Singletons.FileWatcher = new IdeFileWatcher();
|
Singletons.FileWatcher = new IdeFileWatcher();
|
||||||
Singletons.FileManager = new IdeFileManager();
|
Singletons.FileManager = new IdeFileManager();
|
||||||
|
Singletons.FileChangeHandler = new IdeFileChangeHandler();
|
||||||
}
|
}
|
||||||
|
|
||||||
public override void _Ready()
|
public override void _Ready()
|
||||||
@@ -121,6 +122,7 @@ public partial class IdeRoot : Control
|
|||||||
_codeEditorPanel.Solution = solutionModel;
|
_codeEditorPanel.Solution = solutionModel;
|
||||||
_bottomPanelManager.Solution = solutionModel;
|
_bottomPanelManager.Solution = solutionModel;
|
||||||
_searchWindow.Solution = solutionModel;
|
_searchWindow.Solution = solutionModel;
|
||||||
|
Singletons.FileChangeHandler.SolutionModel = solutionModel;
|
||||||
Callable.From(_solutionExplorerPanel.RepopulateTree).CallDeferred();
|
Callable.From(_solutionExplorerPanel.RepopulateTree).CallDeferred();
|
||||||
RoslynAnalysis.StartSolutionAnalysis(solutionModel);
|
RoslynAnalysis.StartSolutionAnalysis(solutionModel);
|
||||||
Singletons.FileWatcher.StartWatching(solutionModel);
|
Singletons.FileWatcher.StartWatching(solutionModel);
|
||||||
|
|||||||
@@ -12,5 +12,6 @@ public static class Singletons
|
|||||||
public static BuildService BuildService { get; set; } = null!;
|
public static BuildService BuildService { get; set; } = null!;
|
||||||
public static IdeFileWatcher FileWatcher { get; set; } = null!;
|
public static IdeFileWatcher FileWatcher { get; set; } = null!;
|
||||||
public static IdeFileManager FileManager { get; set; } = null!;
|
public static IdeFileManager FileManager { get; set; } = null!;
|
||||||
|
public static IdeFileChangeHandler FileChangeHandler { get; set; } = null!;
|
||||||
public static AppState AppState { get; set; } = null!;
|
public static AppState AppState { get; set; } = null!;
|
||||||
}
|
}
|
||||||
Reference in New Issue
Block a user