diff --git a/src/SharpIDE.Application/Features/Analysis/SharpIdeFileLinePosition.cs b/src/SharpIDE.Application/Features/Analysis/SharpIdeFileLinePosition.cs new file mode 100644 index 0000000..b471185 --- /dev/null +++ b/src/SharpIDE.Application/Features/Analysis/SharpIdeFileLinePosition.cs @@ -0,0 +1,7 @@ +namespace SharpIDE.Application.Features.Analysis; + +public struct SharpIdeFileLinePosition +{ + public required int Line { get; set; } + public required int Column { get; set; } +} diff --git a/src/SharpIDE.Application/Features/Events/GlobalEvents.cs b/src/SharpIDE.Application/Features/Events/GlobalEvents.cs index fd8f86e..40842f9 100644 --- a/src/SharpIDE.Application/Features/Events/GlobalEvents.cs +++ b/src/SharpIDE.Application/Features/Events/GlobalEvents.cs @@ -31,6 +31,7 @@ public static class AsyncEventExtensions { public static void InvokeParallelFireAndForget(this MulticastDelegate @event) => FireAndForget(() => @event.InvokeParallelAsync()); public static void InvokeParallelFireAndForget(this MulticastDelegate @event, T arg) => FireAndForget(() => @event.InvokeParallelAsync(arg)); + public static void InvokeParallelFireAndForget(this MulticastDelegate @event, T arg, U arg2) => FireAndForget(() => @event.InvokeParallelAsync(arg, arg2)); private static async void FireAndForget(Func action) { @@ -53,6 +54,10 @@ public static class AsyncEventExtensions { return InvokeDelegatesAsync(@event.GetInvocationList(), del => ((Func)del)(arg)); } + public static Task InvokeParallelAsync(this MulticastDelegate @event, T arg, U arg2) + { + return InvokeDelegatesAsync(@event.GetInvocationList(), del => ((Func)del)(arg, arg2)); + } private static async Task InvokeDelegatesAsync(IEnumerable invocationList, Func delegateExecutorDelegate) { diff --git a/src/SharpIDE.Application/Features/Search/SearchResult.cs b/src/SharpIDE.Application/Features/Search/SearchResult.cs index 9d75f35..9bd86cd 100644 --- a/src/SharpIDE.Application/Features/Search/SearchResult.cs +++ b/src/SharpIDE.Application/Features/Search/SearchResult.cs @@ -5,6 +5,7 @@ namespace SharpIDE.Application.Features.Search; public class SearchResult { public required SharpIdeFile File { get; set; } - public required int LineNumber { get; set; } + public required int Line { get; set; } + public required int StartColumn { get; set; } public required string LineText { get; set; } } diff --git a/src/SharpIDE.Application/Features/Search/SearchService.cs b/src/SharpIDE.Application/Features/Search/SearchService.cs index f891475..aa546b4 100644 --- a/src/SharpIDE.Application/Features/Search/SearchService.cs +++ b/src/SharpIDE.Application/Features/Search/SearchService.cs @@ -28,7 +28,8 @@ public static class SearchService results.Add(new SearchResult { File = file, - LineNumber = index + 1, + Line = index + 1, + StartColumn = line.IndexOf(searchTerm, StringComparison.OrdinalIgnoreCase) + 1, LineText = line.Trim() }); } diff --git a/src/SharpIDE.Godot/Features/CodeEditor/CodeEditorPanel.cs b/src/SharpIDE.Godot/Features/CodeEditor/CodeEditorPanel.cs index 76e91e3..9b60c67 100644 --- a/src/SharpIDE.Godot/Features/CodeEditor/CodeEditorPanel.cs +++ b/src/SharpIDE.Godot/Features/CodeEditor/CodeEditorPanel.cs @@ -1,5 +1,6 @@ using Ardalis.GuardClauses; using Godot; +using SharpIDE.Application.Features.Analysis; using SharpIDE.Application.Features.Debugging; using SharpIDE.Application.Features.Events; using SharpIDE.Application.Features.SolutionDiscovery; @@ -48,15 +49,18 @@ public partial class CodeEditorPanel : MarginContainer tab.QueueFree(); } - public async Task SetSharpIdeFile(SharpIdeFile file) + public async Task SetSharpIdeFile(SharpIdeFile file, SharpIdeFileLinePosition? fileLinePosition) { await Task.CompletedTask.ConfigureAwait(ConfigureAwaitOptions.ForceYielding); var existingTab = await this.InvokeAsync(() => _tabContainer.GetChildren().OfType().FirstOrDefault(t => t.SharpIdeFile == file)); if (existingTab is not null) { var existingTabIndex = existingTab.GetIndex(); - if (existingTabIndex == _tabContainer.CurrentTab) return; - await this.InvokeAsync(() => _tabContainer.CurrentTab = existingTabIndex); + await this.InvokeAsync(() => + { + _tabContainer.CurrentTab = existingTabIndex; + if (fileLinePosition is not null) existingTab.SetFileLinePosition(fileLinePosition.Value); + }); return; } var newTab = _sharpIdeCodeEditScene.Instantiate(); @@ -71,6 +75,7 @@ public partial class CodeEditorPanel : MarginContainer _tabContainer.CurrentTab = newTabIndex; }); await newTab.SetSharpIdeFile(file); + if (fileLinePosition is not null) await this.InvokeAsync(() => newTab.SetFileLinePosition(fileLinePosition.Value)); } private async Task OnDebuggerExecutionStopped(ExecutionStopInfo executionStopInfo) diff --git a/src/SharpIDE.Godot/Features/CodeEditor/SharpIdeCodeEdit.cs b/src/SharpIDE.Godot/Features/CodeEditor/SharpIdeCodeEdit.cs index 8720e98..6512a39 100644 --- a/src/SharpIDE.Godot/Features/CodeEditor/SharpIdeCodeEdit.cs +++ b/src/SharpIDE.Godot/Features/CodeEditor/SharpIdeCodeEdit.cs @@ -140,6 +140,16 @@ public partial class SharpIdeCodeEdit : CodeEdit }).CallDeferred(); }); } + + public void SetFileLinePosition(SharpIdeFileLinePosition fileLinePosition) + { + var line = fileLinePosition.Line - 1; + var column = fileLinePosition.Column - 1; + SetCaretLine(line); + SetCaretColumn(column); + CenterViewportToCaret(); + GrabFocus(); + } // TODO: Ensure not running on UI thread public async Task SetSharpIdeFile(SharpIdeFile file) diff --git a/src/SharpIDE.Godot/Features/Search/SearchResultComponent.cs b/src/SharpIDE.Godot/Features/Search/SearchResultComponent.cs index 66a8be1..e9c766a 100644 --- a/src/SharpIDE.Godot/Features/Search/SearchResultComponent.cs +++ b/src/SharpIDE.Godot/Features/Search/SearchResultComponent.cs @@ -1,4 +1,5 @@ using Godot; +using SharpIDE.Application.Features.Analysis; using SharpIDE.Application.Features.Search; namespace SharpIDE.Godot.Features.Search; @@ -25,7 +26,8 @@ public partial class SearchResultComponent : MarginContainer private void OnButtonPressed() { - GodotGlobalEvents.InvokeFileExternallySelected(Result.File); + var fileLinePosition = new SharpIdeFileLinePosition { Line = Result.Line, Column = Result.StartColumn }; + GodotGlobalEvents.InvokeFileExternallySelected(Result.File, fileLinePosition); ParentSearchWindow.Hide(); } @@ -34,6 +36,6 @@ public partial class SearchResultComponent : MarginContainer if (result is null) return; _matchingLineLabel.Text = result.LineText; _fileNameLabel.Text = result.File.Name; - _lineNumberLabel.Text = result.LineNumber.ToString(); + _lineNumberLabel.Text = result.Line.ToString(); } } \ No newline at end of file diff --git a/src/SharpIDE.Godot/Features/SolutionExplorer/SolutionExplorerPanel.cs b/src/SharpIDE.Godot/Features/SolutionExplorer/SolutionExplorerPanel.cs index de794ce..83574bc 100644 --- a/src/SharpIDE.Godot/Features/SolutionExplorer/SolutionExplorerPanel.cs +++ b/src/SharpIDE.Godot/Features/SolutionExplorer/SolutionExplorerPanel.cs @@ -1,5 +1,6 @@ using Ardalis.GuardClauses; using Godot; +using SharpIDE.Application.Features.Analysis; using SharpIDE.Application.Features.SolutionDiscovery; using SharpIDE.Application.Features.SolutionDiscovery.VsPersistence; @@ -38,10 +39,10 @@ public partial class SolutionExplorerPanel : MarginContainer GodotGlobalEvents.InvokeFileSelected(sharpIdeFile); } - private async Task OnFileExternallySelected(SharpIdeFile file) + private async Task OnFileExternallySelected(SharpIdeFile file, SharpIdeFileLinePosition? fileLinePosition) { await Task.CompletedTask.ConfigureAwait(ConfigureAwaitOptions.ForceYielding); - var task = GodotGlobalEvents.InvokeFileSelectedAndWait(file); + var task = GodotGlobalEvents.InvokeFileSelectedAndWait(file, fileLinePosition); var item = FindItemRecursive(_tree.GetRoot(), file); if (item is not null) { diff --git a/src/SharpIDE.Godot/GodotGlobalEvents.cs b/src/SharpIDE.Godot/GodotGlobalEvents.cs index 0f95c6b..7d08abc 100644 --- a/src/SharpIDE.Godot/GodotGlobalEvents.cs +++ b/src/SharpIDE.Godot/GodotGlobalEvents.cs @@ -1,4 +1,5 @@ -using SharpIDE.Application.Features.Events; +using SharpIDE.Application.Features.Analysis; +using SharpIDE.Application.Features.Events; using SharpIDE.Application.Features.SolutionDiscovery; namespace SharpIDE.Godot; @@ -14,11 +15,11 @@ public static class GodotGlobalEvents public static event Func BottomPanelVisibilityChangeRequested = _ => Task.CompletedTask; public static void InvokeBottomPanelVisibilityChangeRequested(bool show) => BottomPanelVisibilityChangeRequested.InvokeParallelFireAndForget(show); - public static event Func FileSelected = _ => Task.CompletedTask; + public static event Func FileSelected = (_, _) => Task.CompletedTask; public static void InvokeFileSelected(SharpIdeFile file) => FileSelected.InvokeParallelFireAndForget(file); - public static async Task InvokeFileSelectedAndWait(SharpIdeFile file) => await FileSelected.InvokeParallelAsync(file); - public static event Func FileExternallySelected = _ => Task.CompletedTask; - public static void InvokeFileExternallySelected(SharpIdeFile file) => FileExternallySelected.InvokeParallelFireAndForget(file); + public static async Task InvokeFileSelectedAndWait(SharpIdeFile file, SharpIdeFileLinePosition? fileLinePosition) => await FileSelected.InvokeParallelAsync(file, fileLinePosition); + public static event Func FileExternallySelected = (_, _) => Task.CompletedTask; + public static void InvokeFileExternallySelected(SharpIdeFile file, SharpIdeFileLinePosition? fileLinePosition = null) => FileExternallySelected.InvokeParallelFireAndForget(file, fileLinePosition); public static async Task InvokeFileExternallySelectedAndWait(SharpIdeFile file) => await FileExternallySelected.InvokeParallelAsync(file); } diff --git a/src/SharpIDE.Godot/IdeRoot.cs b/src/SharpIDE.Godot/IdeRoot.cs index c38add8..07f04c2 100644 --- a/src/SharpIDE.Godot/IdeRoot.cs +++ b/src/SharpIDE.Godot/IdeRoot.cs @@ -66,9 +66,9 @@ public partial class IdeRoot : Control await Singletons.BuildService.MsBuildSolutionAsync(_solutionExplorerPanel.SolutionModel.FilePath); } - private async Task OnSolutionExplorerPanelOnFileSelected(SharpIdeFile file) + private async Task OnSolutionExplorerPanelOnFileSelected(SharpIdeFile file, SharpIdeFileLinePosition? fileLinePosition) { - await _codeEditorPanel.SetSharpIdeFile(file); + await _codeEditorPanel.SetSharpIdeFile(file, fileLinePosition); } private void OnSlnFileSelected(string path)