refactor file update handling
This commit is contained in:
@@ -0,0 +1,19 @@
|
|||||||
|
using Microsoft.CodeAnalysis.CodeActions;
|
||||||
|
using SharpIDE.Application.Features.FileWatching;
|
||||||
|
|
||||||
|
namespace SharpIDE.Application.Features.Analysis;
|
||||||
|
|
||||||
|
public class CodeActionService(RoslynAnalysis roslynAnalysis, FileChangedService fileChangedService)
|
||||||
|
{
|
||||||
|
private readonly RoslynAnalysis _roslynAnalysis = roslynAnalysis;
|
||||||
|
private readonly FileChangedService _fileChangedService = fileChangedService;
|
||||||
|
|
||||||
|
public async Task ApplyCodeAction(CodeAction codeAction)
|
||||||
|
{
|
||||||
|
var affectedFiles = await _roslynAnalysis.GetCodeActionApplyChanges(codeAction);
|
||||||
|
foreach (var (affectedFile, updatedText) in affectedFiles)
|
||||||
|
{
|
||||||
|
await _fileChangedService.SharpIdeFileChanged(affectedFile, updatedText, FileChangeType.CodeActionChange);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -296,6 +296,7 @@ public class RoslynAnalysis
|
|||||||
{
|
{
|
||||||
using var _ = SharpIdeOtel.Source.StartActivity($"{nameof(RoslynAnalysis)}.{nameof(GetProjectDiagnosticsForFile)}");
|
using var _ = SharpIdeOtel.Source.StartActivity($"{nameof(RoslynAnalysis)}.{nameof(GetProjectDiagnosticsForFile)}");
|
||||||
await _solutionLoadedTcs.Task;
|
await _solutionLoadedTcs.Task;
|
||||||
|
if (sharpIdeFile.IsRoslynWorkspaceFile is false) return [];
|
||||||
var cancellationToken = CancellationToken.None;
|
var cancellationToken = CancellationToken.None;
|
||||||
var project = _workspace!.CurrentSolution.Projects.Single(s => s.FilePath == ((IChildSharpIdeNode)sharpIdeFile).GetNearestProjectNode()!.FilePath);
|
var project = _workspace!.CurrentSolution.Projects.Single(s => s.FilePath == ((IChildSharpIdeNode)sharpIdeFile).GetNearestProjectNode()!.FilePath);
|
||||||
var compilation = await project.GetCompilationAsync(cancellationToken);
|
var compilation = await project.GetCompilationAsync(cancellationToken);
|
||||||
@@ -316,6 +317,7 @@ public class RoslynAnalysis
|
|||||||
if (fileModel.IsRoslynWorkspaceFile is false) return [];
|
if (fileModel.IsRoslynWorkspaceFile is false) return [];
|
||||||
using var _ = SharpIdeOtel.Source.StartActivity($"{nameof(RoslynAnalysis)}.{nameof(GetDocumentDiagnostics)}");
|
using var _ = SharpIdeOtel.Source.StartActivity($"{nameof(RoslynAnalysis)}.{nameof(GetDocumentDiagnostics)}");
|
||||||
await _solutionLoadedTcs.Task;
|
await _solutionLoadedTcs.Task;
|
||||||
|
if (fileModel.IsRoslynWorkspaceFile is false) return [];
|
||||||
|
|
||||||
var document = await GetDocumentForSharpIdeFile(fileModel);
|
var document = await GetDocumentForSharpIdeFile(fileModel);
|
||||||
Guard.Against.Null(document, nameof(document));
|
Guard.Against.Null(document, nameof(document));
|
||||||
@@ -575,14 +577,15 @@ public class RoslynAnalysis
|
|||||||
return completions;
|
return completions;
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Returns the list of files modified by applying the code action
|
/// Returns the list of files that would be modified by applying the code action. Does not apply the changes to the workspace sln
|
||||||
public async Task<List<(SharpIdeFile File, string UpdatedText)>> ApplyCodeActionAsync(CodeAction codeAction)
|
public async Task<List<(SharpIdeFile File, string UpdatedText)>> GetCodeActionApplyChanges(CodeAction codeAction)
|
||||||
{
|
{
|
||||||
using var _ = SharpIdeOtel.Source.StartActivity($"{nameof(RoslynAnalysis)}.{nameof(ApplyCodeActionAsync)}");
|
using var _ = SharpIdeOtel.Source.StartActivity($"{nameof(RoslynAnalysis)}.{nameof(GetCodeActionApplyChanges)}");
|
||||||
await _solutionLoadedTcs.Task;
|
await _solutionLoadedTcs.Task;
|
||||||
var cancellationToken = CancellationToken.None;
|
var cancellationToken = CancellationToken.None;
|
||||||
var operations = await codeAction.GetOperationsAsync(cancellationToken);
|
var operations = await codeAction.GetOperationsAsync(cancellationToken);
|
||||||
var changedDocumentIds = new List<DocumentId>();
|
var changedDocumentIds = new List<DocumentId>();
|
||||||
|
var originalSolution = _workspace!.CurrentSolution;
|
||||||
foreach (var operation in operations)
|
foreach (var operation in operations)
|
||||||
{
|
{
|
||||||
if (operation is ApplyChangesOperation applyChangesOperation)
|
if (operation is ApplyChangesOperation applyChangesOperation)
|
||||||
@@ -616,6 +619,8 @@ public class RoslynAnalysis
|
|||||||
})
|
})
|
||||||
.ToListAsync(cancellationToken);
|
.ToListAsync(cancellationToken);
|
||||||
|
|
||||||
|
_workspace.TryApplyChanges(originalSolution);
|
||||||
|
|
||||||
return changedFilesWithText;
|
return changedFilesWithText;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -14,7 +14,6 @@ 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 EventWrapper<SharpIdeFile, Task> IdeFileSavedToDisk { get; } = new(_ => Task.CompletedTask);
|
|
||||||
/// A document changed, project was reloaded etc. Document changes include unsaved changes in the IDE.
|
/// A document changed, project was reloaded etc. Document changes include unsaved changes in the IDE.
|
||||||
public EventWrapper<Task> SolutionAltered { get; } = new(() => Task.CompletedTask);
|
public EventWrapper<Task> SolutionAltered { get; } = new(() => Task.CompletedTask);
|
||||||
|
|
||||||
|
|||||||
@@ -69,7 +69,6 @@ public class IdeOpenTabsFileManager(RoslynAnalysis roslynAnalysis)
|
|||||||
var text = await GetFileTextAsync(file);
|
var text = await GetFileTextAsync(file);
|
||||||
await WriteAllText(file, text);
|
await WriteAllText(file, text);
|
||||||
file.IsDirty.Value = false;
|
file.IsDirty.Value = false;
|
||||||
GlobalEvents.Instance.IdeFileSavedToDisk.InvokeParallelFireAndForget(file);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public async Task UpdateInMemoryIfOpenAndSaveAsync(SharpIdeFile file, string newText)
|
public async Task UpdateInMemoryIfOpenAndSaveAsync(SharpIdeFile file, string newText)
|
||||||
|
|||||||
@@ -0,0 +1,80 @@
|
|||||||
|
using Microsoft.VisualStudio.SolutionPersistence.Model;
|
||||||
|
using SharpIDE.Application.Features.Analysis;
|
||||||
|
using SharpIDE.Application.Features.Evaluation;
|
||||||
|
using SharpIDE.Application.Features.Events;
|
||||||
|
using SharpIDE.Application.Features.FilePersistence;
|
||||||
|
using SharpIDE.Application.Features.SolutionDiscovery;
|
||||||
|
using SharpIDE.Application.Features.SolutionDiscovery.VsPersistence;
|
||||||
|
|
||||||
|
namespace SharpIDE.Application.Features.FileWatching;
|
||||||
|
|
||||||
|
public enum FileChangeType
|
||||||
|
{
|
||||||
|
IdeSaveToDisk, // Apply to disk
|
||||||
|
IdeUnsavedChange, // Apply only in memory
|
||||||
|
ExternalChange, // Apply to disk, as well as in memory
|
||||||
|
CodeActionChange // Apply to disk, as well as in memory
|
||||||
|
}
|
||||||
|
|
||||||
|
public class FileChangedService(RoslynAnalysis roslynAnalysis, IdeOpenTabsFileManager openTabsFileManager)
|
||||||
|
{
|
||||||
|
private readonly RoslynAnalysis _roslynAnalysis = roslynAnalysis;
|
||||||
|
private readonly IdeOpenTabsFileManager _openTabsFileManager = openTabsFileManager;
|
||||||
|
|
||||||
|
public SharpIdeSolutionModel SolutionModel { get; set; } = null!;
|
||||||
|
|
||||||
|
// All file changes should go via this service
|
||||||
|
public async Task SharpIdeFileChanged(SharpIdeFile file, string newContents, FileChangeType changeType)
|
||||||
|
{
|
||||||
|
if (changeType is FileChangeType.ExternalChange)
|
||||||
|
{
|
||||||
|
// Disk is already up to date
|
||||||
|
// Update any open tabs
|
||||||
|
// update in memory
|
||||||
|
await _openTabsFileManager.UpdateFileTextInMemory(file, newContents);
|
||||||
|
file.FileContentsChangedExternally.InvokeParallelFireAndForget();
|
||||||
|
}
|
||||||
|
else if (changeType is FileChangeType.CodeActionChange)
|
||||||
|
{
|
||||||
|
// update in memory, tabs and save to disk
|
||||||
|
await _openTabsFileManager.UpdateInMemoryIfOpenAndSaveAsync(file, newContents);
|
||||||
|
file.FileContentsChangedExternally.InvokeParallelFireAndForget();
|
||||||
|
}
|
||||||
|
else if (changeType is FileChangeType.IdeSaveToDisk)
|
||||||
|
{
|
||||||
|
// save to disk
|
||||||
|
// We technically don't need to update in memory here. TODO review
|
||||||
|
await _openTabsFileManager.UpdateInMemoryIfOpenAndSaveAsync(file, newContents);
|
||||||
|
}
|
||||||
|
else if (changeType is FileChangeType.IdeUnsavedChange)
|
||||||
|
{
|
||||||
|
// update in memory only
|
||||||
|
await _openTabsFileManager.UpdateFileTextInMemory(file, newContents);
|
||||||
|
}
|
||||||
|
var afterSaveTask = (file, changeType) switch
|
||||||
|
{
|
||||||
|
({ IsRoslynWorkspaceFile: true }, _) => HandleWorkspaceFileChanged(file, newContents),
|
||||||
|
({ IsCsprojFile: true }, FileChangeType.IdeSaveToDisk or FileChangeType.ExternalChange) => HandleCsprojChanged(file),
|
||||||
|
({ IsCsprojFile: true }, _) => Task.CompletedTask,
|
||||||
|
_ => throw new InvalidOperationException("Unknown file change type.")
|
||||||
|
};
|
||||||
|
await afterSaveTask;
|
||||||
|
}
|
||||||
|
|
||||||
|
private async Task HandleCsprojChanged(SharpIdeFile file)
|
||||||
|
{
|
||||||
|
var project = SolutionModel.AllProjects.SingleOrDefault(p => p.FilePath == file.Path);
|
||||||
|
if (project is null) return;
|
||||||
|
await ProjectEvaluation.ReloadProject(file.Path);
|
||||||
|
await _roslynAnalysis.ReloadProject(project);
|
||||||
|
GlobalEvents.Instance.SolutionAltered.InvokeParallelFireAndForget();
|
||||||
|
await _roslynAnalysis.UpdateSolutionDiagnostics();
|
||||||
|
}
|
||||||
|
|
||||||
|
private async Task HandleWorkspaceFileChanged(SharpIdeFile file, string newContents)
|
||||||
|
{
|
||||||
|
await _roslynAnalysis.UpdateDocument(file, newContents);
|
||||||
|
GlobalEvents.Instance.SolutionAltered.InvokeParallelFireAndForget();
|
||||||
|
await _roslynAnalysis.UpdateSolutionDiagnostics();
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,15 +1,15 @@
|
|||||||
using SharpIDE.Application.Features.Analysis;
|
using SharpIDE.Application.Features.Events;
|
||||||
using SharpIDE.Application.Features.Evaluation;
|
|
||||||
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;
|
||||||
|
|
||||||
public class IdeFileExternalChangeHandler
|
public class IdeFileExternalChangeHandler
|
||||||
{
|
{
|
||||||
|
private readonly FileChangedService _fileChangedService;
|
||||||
public SharpIdeSolutionModel SolutionModel { get; set; } = null!;
|
public SharpIdeSolutionModel SolutionModel { get; set; } = null!;
|
||||||
public IdeFileExternalChangeHandler()
|
public IdeFileExternalChangeHandler(FileChangedService fileChangedService)
|
||||||
{
|
{
|
||||||
|
_fileChangedService = fileChangedService;
|
||||||
GlobalEvents.Instance.FileSystemWatcherInternal.FileChanged.Subscribe(OnFileChanged);
|
GlobalEvents.Instance.FileSystemWatcherInternal.FileChanged.Subscribe(OnFileChanged);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -31,8 +31,7 @@ public class IdeFileExternalChangeHandler
|
|||||||
var file = SolutionModel.AllFiles.SingleOrDefault(f => f.Path == filePath);
|
var file = SolutionModel.AllFiles.SingleOrDefault(f => f.Path == filePath);
|
||||||
if (file is not null)
|
if (file is not null)
|
||||||
{
|
{
|
||||||
await file.FileContentsChangedExternallyFromDisk.InvokeParallelAsync();
|
await _fileChangedService.SharpIdeFileChanged(file, await File.ReadAllTextAsync(file.Path), FileChangeType.ExternalChange);
|
||||||
await GlobalEvents.Instance.IdeFileSavedToDisk.InvokeParallelAsync(file);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,58 +0,0 @@
|
|||||||
using SharpIDE.Application.Features.Analysis;
|
|
||||||
using SharpIDE.Application.Features.Evaluation;
|
|
||||||
using SharpIDE.Application.Features.Events;
|
|
||||||
using SharpIDE.Application.Features.FilePersistence;
|
|
||||||
using SharpIDE.Application.Features.SolutionDiscovery;
|
|
||||||
using SharpIDE.Application.Features.SolutionDiscovery.VsPersistence;
|
|
||||||
|
|
||||||
namespace SharpIDE.Application.Features.FileWatching;
|
|
||||||
|
|
||||||
public class IdeFileSavedToDiskHandler
|
|
||||||
{
|
|
||||||
private readonly IdeOpenTabsFileManager _openTabsFileManager;
|
|
||||||
private readonly RoslynAnalysis _roslynAnalysis;
|
|
||||||
public SharpIdeSolutionModel SolutionModel { get; set; } = null!;
|
|
||||||
|
|
||||||
public IdeFileSavedToDiskHandler(IdeOpenTabsFileManager openTabsFileManager, RoslynAnalysis roslynAnalysis)
|
|
||||||
{
|
|
||||||
_openTabsFileManager = openTabsFileManager;
|
|
||||||
_roslynAnalysis = roslynAnalysis;
|
|
||||||
GlobalEvents.Instance.IdeFileSavedToDisk.Subscribe(HandleIdeFileChanged);
|
|
||||||
}
|
|
||||||
|
|
||||||
private async Task HandleIdeFileChanged(SharpIdeFile file)
|
|
||||||
{
|
|
||||||
if (file.IsCsprojFile)
|
|
||||||
{
|
|
||||||
await HandleCsprojChanged(file);
|
|
||||||
}
|
|
||||||
else if (file.IsRoslynWorkspaceFile)
|
|
||||||
{
|
|
||||||
await HandleWorkspaceFileChanged(file);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private async Task HandleCsprojChanged(SharpIdeFile file)
|
|
||||||
{
|
|
||||||
var project = SolutionModel.AllProjects.SingleOrDefault(p => p.FilePath == file.Path);
|
|
||||||
if (project is null) return;
|
|
||||||
await ProjectEvaluation.ReloadProject(file.Path);
|
|
||||||
await _roslynAnalysis.ReloadProject(project);
|
|
||||||
await _roslynAnalysis.UpdateSolutionDiagnostics();
|
|
||||||
}
|
|
||||||
|
|
||||||
private async Task HandleWorkspaceFileChanged(SharpIdeFile file)
|
|
||||||
{
|
|
||||||
// TODO: Don't reload from disk if we raised the change event ourselves (e.g. save from IDE). Cleanup this whole disaster
|
|
||||||
var wasOpenAndUpdated = await _openTabsFileManager.ReloadFileFromDiskIfOpenInEditor(file);
|
|
||||||
if (file.IsRoslynWorkspaceFile)
|
|
||||||
{
|
|
||||||
var fileText = wasOpenAndUpdated ?
|
|
||||||
await _openTabsFileManager.GetFileTextAsync(file) :
|
|
||||||
await File.ReadAllTextAsync(file.Path);
|
|
||||||
await _roslynAnalysis.UpdateDocument(file, fileText);
|
|
||||||
GlobalEvents.Instance.SolutionAltered.InvokeParallelFireAndForget();
|
|
||||||
}
|
|
||||||
await _roslynAnalysis.UpdateSolutionDiagnostics();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -19,7 +19,6 @@ public class SharpIdeFile : ISharpIdeNode, IChildSharpIdeNode
|
|||||||
public required ReactiveProperty<bool> IsDirty { get; init; }
|
public required ReactiveProperty<bool> IsDirty { get; init; }
|
||||||
public required bool SuppressDiskChangeEvents { get; set; } // probably has concurrency issues
|
public required bool SuppressDiskChangeEvents { get; set; } // probably has concurrency issues
|
||||||
public required DateTimeOffset? LastIdeWriteTime { get; set; }
|
public required DateTimeOffset? LastIdeWriteTime { get; set; }
|
||||||
public EventWrapper<Task> FileContentsChangedExternallyFromDisk { get; } = new(() => Task.CompletedTask); // Refactor to global event - this currently doesn't handle updating un-opened files
|
|
||||||
public EventWrapper<Task> FileContentsChangedExternally { get; } = new(() => Task.CompletedTask);
|
public EventWrapper<Task> FileContentsChangedExternally { get; } = new(() => Task.CompletedTask);
|
||||||
|
|
||||||
[SetsRequiredMembers]
|
[SetsRequiredMembers]
|
||||||
|
|||||||
@@ -25,7 +25,8 @@ public partial class DiAutoload : Node
|
|||||||
services.AddScoped<BuildService>();
|
services.AddScoped<BuildService>();
|
||||||
services.AddScoped<RunService>();
|
services.AddScoped<RunService>();
|
||||||
services.AddScoped<IdeFileExternalChangeHandler>();
|
services.AddScoped<IdeFileExternalChangeHandler>();
|
||||||
services.AddScoped<IdeFileSavedToDiskHandler>();
|
services.AddScoped<CodeActionService>();
|
||||||
|
services.AddScoped<FileChangedService>();
|
||||||
services.AddScoped<IdeFileWatcher>();
|
services.AddScoped<IdeFileWatcher>();
|
||||||
services.AddScoped<IdeOpenTabsFileManager>();
|
services.AddScoped<IdeOpenTabsFileManager>();
|
||||||
services.AddScoped<RoslynAnalysis>();
|
services.AddScoped<RoslynAnalysis>();
|
||||||
|
|||||||
@@ -10,6 +10,7 @@ using SharpIDE.Application.Features.Analysis;
|
|||||||
using SharpIDE.Application.Features.Debugging;
|
using SharpIDE.Application.Features.Debugging;
|
||||||
using SharpIDE.Application.Features.Events;
|
using SharpIDE.Application.Features.Events;
|
||||||
using SharpIDE.Application.Features.FilePersistence;
|
using SharpIDE.Application.Features.FilePersistence;
|
||||||
|
using SharpIDE.Application.Features.FileWatching;
|
||||||
using SharpIDE.Application.Features.Run;
|
using SharpIDE.Application.Features.Run;
|
||||||
using SharpIDE.Application.Features.SolutionDiscovery;
|
using SharpIDE.Application.Features.SolutionDiscovery;
|
||||||
using SharpIDE.Application.Features.SolutionDiscovery.VsPersistence;
|
using SharpIDE.Application.Features.SolutionDiscovery.VsPersistence;
|
||||||
@@ -44,6 +45,8 @@ public partial class SharpIdeCodeEdit : CodeEdit
|
|||||||
[Inject] private readonly IdeOpenTabsFileManager _openTabsFileManager = null!;
|
[Inject] private readonly IdeOpenTabsFileManager _openTabsFileManager = null!;
|
||||||
[Inject] private readonly RunService _runService = null!;
|
[Inject] private readonly RunService _runService = null!;
|
||||||
[Inject] private readonly RoslynAnalysis _roslynAnalysis = null!;
|
[Inject] private readonly RoslynAnalysis _roslynAnalysis = null!;
|
||||||
|
[Inject] private readonly CodeActionService _codeActionService = null!;
|
||||||
|
[Inject] private readonly FileChangedService _fileChangedService = null!;
|
||||||
|
|
||||||
public override void _Ready()
|
public override void _Ready()
|
||||||
{
|
{
|
||||||
@@ -66,6 +69,10 @@ public partial class SharpIdeCodeEdit : CodeEdit
|
|||||||
{
|
{
|
||||||
if (_currentFile is null) return;
|
if (_currentFile is null) return;
|
||||||
GD.Print("Solution altered, updating project diagnostics for current file");
|
GD.Print("Solution altered, updating project diagnostics for current file");
|
||||||
|
var documentSyntaxHighlighting = _roslynAnalysis.GetDocumentSyntaxHighlighting(_currentFile);
|
||||||
|
var razorSyntaxHighlighting = _roslynAnalysis.GetRazorDocumentSyntaxHighlighting(_currentFile);
|
||||||
|
await Task.WhenAll(documentSyntaxHighlighting, razorSyntaxHighlighting);
|
||||||
|
await this.InvokeAsync(async () => SetSyntaxHighlightingModel(await documentSyntaxHighlighting, await razorSyntaxHighlighting));
|
||||||
var documentDiagnostics = await _roslynAnalysis.GetDocumentDiagnostics(_currentFile);
|
var documentDiagnostics = await _roslynAnalysis.GetDocumentDiagnostics(_currentFile);
|
||||||
await this.InvokeAsync(() => SetDiagnostics(documentDiagnostics));
|
await this.InvokeAsync(() => SetDiagnostics(documentDiagnostics));
|
||||||
var projectDiagnostics = await _roslynAnalysis.GetProjectDiagnosticsForFile(_currentFile);
|
var projectDiagnostics = await _roslynAnalysis.GetProjectDiagnosticsForFile(_currentFile);
|
||||||
@@ -108,8 +115,7 @@ public partial class SharpIdeCodeEdit : CodeEdit
|
|||||||
|
|
||||||
public override void _ExitTree()
|
public override void _ExitTree()
|
||||||
{
|
{
|
||||||
_currentFile?.FileContentsChangedExternallyFromDisk.Unsubscribe(OnFileChangedExternallyFromDisk);
|
_currentFile?.FileContentsChangedExternally.Unsubscribe(OnFileChangedExternally);
|
||||||
_currentFile?.FileContentsChangedExternally.Unsubscribe(OnFileChangedExternallyInMemory);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private void OnBreakpointToggled(long line)
|
private void OnBreakpointToggled(long line)
|
||||||
@@ -264,30 +270,14 @@ public partial class SharpIdeCodeEdit : CodeEdit
|
|||||||
// GD.Print($"Selection changed to line {_currentLine}, start {_selectionStartCol}, end {_selectionEndCol}");
|
// GD.Print($"Selection changed to line {_currentLine}, start {_selectionStartCol}, end {_selectionEndCol}");
|
||||||
}
|
}
|
||||||
|
|
||||||
private CancellationTokenSource _textChangedCts = new();
|
|
||||||
private void OnTextChanged()
|
private void OnTextChanged()
|
||||||
{
|
{
|
||||||
var __ = SharpIdeOtel.Source.StartActivity($"{nameof(SharpIdeCodeEdit)}.{nameof(OnTextChanged)}");
|
var __ = SharpIdeOtel.Source.StartActivity($"{nameof(SharpIdeCodeEdit)}.{nameof(OnTextChanged)}");
|
||||||
_ = Task.GodotRun(async () =>
|
_ = Task.GodotRun(async () =>
|
||||||
{
|
{
|
||||||
_currentFile.IsDirty.Value = true;
|
_currentFile.IsDirty.Value = true;
|
||||||
await _openTabsFileManager.UpdateFileTextInMemory(_currentFile, Text);
|
await _fileChangedService.SharpIdeFileChanged(_currentFile, Text, FileChangeType.IdeUnsavedChange);
|
||||||
await _textChangedCts.CancelAsync(); // Currently the below methods throw, TODO Fix with suppress throwing, and handle
|
__?.Dispose();
|
||||||
_textChangedCts.Dispose();
|
|
||||||
_textChangedCts = new CancellationTokenSource();
|
|
||||||
_ = Task.GodotRun(async () =>
|
|
||||||
{
|
|
||||||
var syntaxHighlighting = _roslynAnalysis.GetDocumentSyntaxHighlighting(_currentFile, _textChangedCts.Token);
|
|
||||||
var razorSyntaxHighlighting = _roslynAnalysis.GetRazorDocumentSyntaxHighlighting(_currentFile, _textChangedCts.Token);
|
|
||||||
await Task.WhenAll(syntaxHighlighting, razorSyntaxHighlighting);
|
|
||||||
await this.InvokeAsync(async () => SetSyntaxHighlightingModel(await syntaxHighlighting, await razorSyntaxHighlighting));
|
|
||||||
__?.Dispose();
|
|
||||||
});
|
|
||||||
_ = Task.GodotRun(async () =>
|
|
||||||
{
|
|
||||||
var documentDiagnostics = await _roslynAnalysis.GetDocumentDiagnostics(_currentFile, _textChangedCts.Token);
|
|
||||||
await this.InvokeAsync(() => SetDiagnostics(documentDiagnostics));
|
|
||||||
});
|
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -300,31 +290,19 @@ public partial class SharpIdeCodeEdit : CodeEdit
|
|||||||
|
|
||||||
_ = Task.GodotRun(async () =>
|
_ = Task.GodotRun(async () =>
|
||||||
{
|
{
|
||||||
var affectedFiles = await _roslynAnalysis.ApplyCodeActionAsync(codeAction);
|
await _codeActionService.ApplyCodeAction(codeAction);
|
||||||
// TODO: This can be more efficient - we can just update in memory and proceed with highlighting etc. Save to disk in background.
|
|
||||||
foreach (var (affectedFile, updatedText) in affectedFiles)
|
|
||||||
{
|
|
||||||
await _openTabsFileManager.UpdateInMemoryIfOpenAndSaveAsync(affectedFile, updatedText);
|
|
||||||
affectedFile.FileContentsChangedExternally.InvokeParallelFireAndForget();
|
|
||||||
}
|
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
private async Task OnFileChangedExternallyInMemory()
|
private async Task OnFileChangedExternally()
|
||||||
{
|
{
|
||||||
var fileContents = await _openTabsFileManager.GetFileTextAsync(_currentFile);
|
var fileContents = await _openTabsFileManager.GetFileTextAsync(_currentFile);
|
||||||
var syntaxHighlighting = _roslynAnalysis.GetDocumentSyntaxHighlighting(_currentFile);
|
|
||||||
var razorSyntaxHighlighting = _roslynAnalysis.GetRazorDocumentSyntaxHighlighting(_currentFile);
|
|
||||||
var diagnostics = _roslynAnalysis.GetDocumentDiagnostics(_currentFile);
|
|
||||||
await Task.WhenAll(syntaxHighlighting, razorSyntaxHighlighting, diagnostics);
|
|
||||||
Callable.From(() =>
|
Callable.From(() =>
|
||||||
{
|
{
|
||||||
var currentCaretPosition = GetCaretPosition();
|
var currentCaretPosition = GetCaretPosition();
|
||||||
var vScroll = GetVScroll();
|
var vScroll = GetVScroll();
|
||||||
BeginComplexOperation();
|
BeginComplexOperation();
|
||||||
SetText(fileContents);
|
SetText(fileContents);
|
||||||
SetSyntaxHighlightingModel(syntaxHighlighting.Result, razorSyntaxHighlighting.Result);
|
|
||||||
SetDiagnostics(diagnostics.Result);
|
|
||||||
SetCaretLine(currentCaretPosition.line);
|
SetCaretLine(currentCaretPosition.line);
|
||||||
SetCaretColumn(currentCaretPosition.col);
|
SetCaretColumn(currentCaretPosition.col);
|
||||||
SetVScroll(vScroll);
|
SetVScroll(vScroll);
|
||||||
@@ -348,8 +326,7 @@ 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 = _openTabsFileManager.GetFileTextAsync(file);
|
var readFileTask = _openTabsFileManager.GetFileTextAsync(file);
|
||||||
_currentFile.FileContentsChangedExternally.Subscribe(OnFileChangedExternallyInMemory);
|
_currentFile.FileContentsChangedExternally.Subscribe(OnFileChangedExternally);
|
||||||
_currentFile.FileContentsChangedExternallyFromDisk.Subscribe(OnFileChangedExternallyFromDisk);
|
|
||||||
|
|
||||||
var syntaxHighlighting = _roslynAnalysis.GetDocumentSyntaxHighlighting(_currentFile);
|
var syntaxHighlighting = _roslynAnalysis.GetDocumentSyntaxHighlighting(_currentFile);
|
||||||
var razorSyntaxHighlighting = _roslynAnalysis.GetRazorDocumentSyntaxHighlighting(_currentFile);
|
var razorSyntaxHighlighting = _roslynAnalysis.GetRazorDocumentSyntaxHighlighting(_currentFile);
|
||||||
@@ -369,12 +346,6 @@ public partial class SharpIdeCodeEdit : CodeEdit
|
|||||||
await this.InvokeAsync(async () => SetProjectDiagnostics(await projectDiagnosticsForFile));
|
await this.InvokeAsync(async () => SetProjectDiagnostics(await projectDiagnosticsForFile));
|
||||||
}
|
}
|
||||||
|
|
||||||
private async Task OnFileChangedExternallyFromDisk()
|
|
||||||
{
|
|
||||||
await _openTabsFileManager.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())
|
||||||
@@ -429,23 +400,25 @@ public partial class SharpIdeCodeEdit : CodeEdit
|
|||||||
|
|
||||||
public override void _UnhandledKeyInput(InputEvent @event)
|
public override void _UnhandledKeyInput(InputEvent @event)
|
||||||
{
|
{
|
||||||
if (HasFocus() is false) return; // every tab is currently listening for this input. Only respond if we have focus. Consider refactoring this _UnhandledKeyInput to CodeEditorPanel
|
// Let each open tab respond to this event
|
||||||
if (@event.IsActionPressed(InputStringNames.CodeFixes))
|
if (@event.IsActionPressed(InputStringNames.SaveAllFiles))
|
||||||
{
|
|
||||||
EmitSignalCodeFixesRequested();
|
|
||||||
}
|
|
||||||
else if (@event.IsActionPressed(InputStringNames.SaveAllFiles))
|
|
||||||
{
|
{
|
||||||
_ = Task.GodotRun(async () =>
|
_ = Task.GodotRun(async () =>
|
||||||
{
|
{
|
||||||
await _openTabsFileManager.SaveAllOpenFilesAsync();
|
await _fileChangedService.SharpIdeFileChanged(_currentFile, Text, FileChangeType.IdeSaveToDisk);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
// Now we filter to only the focused tab
|
||||||
|
if (HasFocus() is false) return;
|
||||||
|
if (@event.IsActionPressed(InputStringNames.CodeFixes))
|
||||||
|
{
|
||||||
|
EmitSignalCodeFixesRequested();
|
||||||
|
}
|
||||||
else if (@event.IsActionPressed(InputStringNames.SaveFile))
|
else if (@event.IsActionPressed(InputStringNames.SaveFile))
|
||||||
{
|
{
|
||||||
_ = Task.GodotRun(async () =>
|
_ = Task.GodotRun(async () =>
|
||||||
{
|
{
|
||||||
await _openTabsFileManager.SaveFileAsync(_currentFile);
|
await _fileChangedService.SharpIdeFileChanged(_currentFile, Text, FileChangeType.IdeSaveToDisk);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -38,7 +38,7 @@ public partial class IdeRoot : Control
|
|||||||
private readonly PackedScene _runMenuItemScene = ResourceLoader.Load<PackedScene>("res://Features/Run/RunMenuItem.tscn");
|
private readonly PackedScene _runMenuItemScene = ResourceLoader.Load<PackedScene>("res://Features/Run/RunMenuItem.tscn");
|
||||||
private TaskCompletionSource _nodeReadyTcs = new TaskCompletionSource(TaskCreationOptions.RunContinuationsAsynchronously);
|
private TaskCompletionSource _nodeReadyTcs = new TaskCompletionSource(TaskCreationOptions.RunContinuationsAsynchronously);
|
||||||
|
|
||||||
[Inject] private readonly IdeFileSavedToDiskHandler _savedToDiskHandler = null!;
|
[Inject] private readonly FileChangedService _fileChangedService = null!;
|
||||||
[Inject] private readonly IdeFileExternalChangeHandler _fileExternalChangeHandler = null!;
|
[Inject] private readonly IdeFileExternalChangeHandler _fileExternalChangeHandler = null!;
|
||||||
[Inject] private readonly IdeFileWatcher _fileWatcher = null!;
|
[Inject] private readonly IdeFileWatcher _fileWatcher = null!;
|
||||||
[Inject] private readonly BuildService _buildService = null!;
|
[Inject] private readonly BuildService _buildService = null!;
|
||||||
@@ -140,7 +140,7 @@ public partial class IdeRoot : Control
|
|||||||
_searchWindow.Solution = solutionModel;
|
_searchWindow.Solution = solutionModel;
|
||||||
_searchAllFilesWindow.Solution = solutionModel;
|
_searchAllFilesWindow.Solution = solutionModel;
|
||||||
_fileExternalChangeHandler.SolutionModel = solutionModel;
|
_fileExternalChangeHandler.SolutionModel = solutionModel;
|
||||||
_savedToDiskHandler.SolutionModel = solutionModel;
|
_fileChangedService.SolutionModel = solutionModel;
|
||||||
Callable.From(_solutionExplorerPanel.RepopulateTree).CallDeferred();
|
Callable.From(_solutionExplorerPanel.RepopulateTree).CallDeferred();
|
||||||
_roslynAnalysis.StartSolutionAnalysis(solutionModel);
|
_roslynAnalysis.StartSolutionAnalysis(solutionModel);
|
||||||
_fileWatcher.StartWatching(solutionModel);
|
_fileWatcher.StartWatching(solutionModel);
|
||||||
|
|||||||
Reference in New Issue
Block a user