From a1428c803e97701dda4c2e167048bb86142968ea Mon Sep 17 00:00:00 2001 From: Erik Darling <2136037+erikdarlingdata@users.noreply.github.com> Date: Mon, 6 Apr 2026 09:52:15 -0400 Subject: [PATCH] Add 'Open in Query Editor' to plan viewer statements grid (#165) Right-click a statement in the plan viewer grid to open its query text in the editor. From file mode, creates a new query tab. From editor mode, loads the text into the existing editor and switches to it. Closes #165. Co-Authored-By: Claude Opus 4.6 (1M context) --- src/PlanViewer.App/Controls/PlanViewerControl.axaml | 1 + src/PlanViewer.App/Controls/PlanViewerControl.axaml.cs | 10 ++++++++++ .../Controls/QuerySessionControl.axaml.cs | 10 ++++++++++ src/PlanViewer.App/MainWindow.axaml.cs | 10 ++++++++++ 4 files changed, 31 insertions(+) diff --git a/src/PlanViewer.App/Controls/PlanViewerControl.axaml b/src/PlanViewer.App/Controls/PlanViewerControl.axaml index db9d492..ef2ed43 100644 --- a/src/PlanViewer.App/Controls/PlanViewerControl.axaml +++ b/src/PlanViewer.App/Controls/PlanViewerControl.axaml @@ -228,6 +228,7 @@ + diff --git a/src/PlanViewer.App/Controls/PlanViewerControl.axaml.cs b/src/PlanViewer.App/Controls/PlanViewerControl.axaml.cs index debb21c..0adcfd2 100644 --- a/src/PlanViewer.App/Controls/PlanViewerControl.axaml.cs +++ b/src/PlanViewer.App/Controls/PlanViewerControl.axaml.cs @@ -171,6 +171,7 @@ public ServerMetadata? Metadata public event EventHandler? HumanAdviceRequested; public event EventHandler? RobotAdviceRequested; public event EventHandler? CopyReproRequested; + public event EventHandler? OpenInEditorRequested; /// /// Navigates to a specific plan node by ID: selects it, zooms to show it, @@ -3269,6 +3270,15 @@ private async void CopyStatementText_Click(object? sender, RoutedEventArgs e) await topLevel.Clipboard.SetTextAsync(text); } + private void OpenInEditor_Click(object? sender, RoutedEventArgs e) + { + if (StatementsGrid.SelectedItem is not StatementRow row) return; + var text = row.Statement.StatementText; + if (string.IsNullOrEmpty(text)) return; + + OpenInEditorRequested?.Invoke(this, text); + } + private static void CollectNodeWarnings(PlanNode node, List warnings) { warnings.AddRange(node.Warnings); diff --git a/src/PlanViewer.App/Controls/QuerySessionControl.axaml.cs b/src/PlanViewer.App/Controls/QuerySessionControl.axaml.cs index 49c0c57..ef556d4 100644 --- a/src/PlanViewer.App/Controls/QuerySessionControl.axaml.cs +++ b/src/PlanViewer.App/Controls/QuerySessionControl.axaml.cs @@ -590,6 +590,13 @@ private static string BracketName(string name) return $"[{name}]"; } + private void OnOpenInEditorRequested(object? sender, string queryText) + { + QueryEditor.Text = queryText; + SubTabControl.SelectedIndex = 0; // Switch to the editor tab + QueryEditor.Focus(); + } + private void OnKeyDown(object? sender, KeyEventArgs e) { // F5 or Ctrl+E → Execute (actual plan) @@ -1067,6 +1074,7 @@ private async Task CaptureAndShowPlan(bool estimated, string? queryTextOverride SetStatus($"{planType} plan captured ({sw.Elapsed.TotalSeconds:F1}s)"); var viewer = new PlanViewerControl(); viewer.Metadata = _serverMetadata; + viewer.OpenInEditorRequested += OnOpenInEditorRequested; viewer.LoadPlan(planXml, tabLabel, queryText); loadingTab.Content = viewer; HumanAdviceButton.IsEnabled = true; @@ -1148,6 +1156,7 @@ private void AddPlanTab(string planXml, string queryText, bool estimated, string var viewer = new PlanViewerControl(); viewer.Metadata = _serverMetadata; + viewer.OpenInEditorRequested += OnOpenInEditorRequested; viewer.LoadPlan(planXml, label, queryText); // Build tab header with close button and right-click rename @@ -1836,6 +1845,7 @@ private async void GetActualPlan_Click(object? sender, RoutedEventArgs e) SetStatus($"Actual plan captured ({sw.Elapsed.TotalSeconds:F1}s)"); var actualViewer = new PlanViewerControl(); actualViewer.Metadata = _serverMetadata; + actualViewer.OpenInEditorRequested += OnOpenInEditorRequested; actualViewer.LoadPlan(actualPlanXml, tabLabel, queryText); loadingTab.Content = actualViewer; } diff --git a/src/PlanViewer.App/MainWindow.axaml.cs b/src/PlanViewer.App/MainWindow.axaml.cs index 0a055f9..92de531 100644 --- a/src/PlanViewer.App/MainWindow.axaml.cs +++ b/src/PlanViewer.App/MainWindow.axaml.cs @@ -530,6 +530,16 @@ private DockPanel CreatePlanTabContent(PlanViewerControl viewer) viewer.HumanAdviceRequested += (_, _) => showHumanAdvice(); viewer.RobotAdviceRequested += (_, _) => showRobotAdvice(); viewer.CopyReproRequested += async (_, _) => await copyRepro(); + viewer.OpenInEditorRequested += (_, queryText) => + { + _queryCounter++; + var session = new QuerySessionControl(_credentialService, _connectionStore); + session.QueryEditor.Text = queryText; + var tab = CreateTab($"Query {_queryCounter}", session); + MainTabControl.Items.Add(tab); + MainTabControl.SelectedItem = tab; + UpdateEmptyOverlay(); + }; var getActualPlanBtn = new Button {