irongit

fix: jq bar is usable

huncholanehuncholaneauthored
parent a83eec5commit 72773015894229e7a04afb692564ac574f880a3cBrowse files

2 files changed, +96 -53

+40-12src/views/browser.rs
@@ -204,6 +204,11 @@ impl BrowserView {
204204 self.go_up();
205205 }
206206
207+ // '/' focuses the jq bar
208+ if ui.input(|i| i.key_pressed(egui::Key::Slash)) {
209+ self.jq_bar.focus();
210+ }
211+
207212 // Copy selected value: c or Ctrl+C
208213 let copy = ui.input(|i| i.key_pressed(egui::Key::C))
209214 || ui.input_mut(|i| i.consume_key(egui::Modifiers::CTRL, egui::Key::C));
@@ -289,21 +294,46 @@ impl BrowserView {
289294 self.jq_error = None;
290295 }
291296
292- if resp.completion_applied {
293- if let Some(path) = jq_path_to_segments(&self.jq_bar.query) {
294- self.path = path;
297+ // Build a full path from jq segments by prepending the current file segment
298+ let file_seg = self.path.first().cloned();
299+ let make_full_path = |jq_segs: Vec<PathSegment>| -> Vec<PathSegment> {
300+ let mut full = Vec::with_capacity(jq_segs.len() + 1);
301+ if let Some(ref seg) = file_seg {
302+ full.push(seg.clone());
303+ }
304+ full.extend(jq_segs);
305+ full
306+ };
307+
308+ // Cycling through completions — preview the path without committing
309+ if resp.previewing {
310+ if let Some(jq_segs) = jq_path_to_segments(&self.jq_bar.query) {
311+ if resolve_path(root, &jq_segs).is_some() {
312+ self.path = make_full_path(jq_segs);
313+ self.selection = 0;
314+ self.jq_synced = true;
315+ self.jq_result = None;
316+ self.jq_error = None;
317+ self.scroll_to_selection = true;
318+ }
319+ }
320+ }
321+
322+ // Final acceptance — Enter/Tab/click on a completion
323+ if resp.accepted {
324+ if let Some(jq_segs) = jq_path_to_segments(&self.jq_bar.query) {
325+ self.path = make_full_path(jq_segs);
295326 self.selection = 0;
296327 self.jq_synced = true;
297328 self.jq_result = None;
298329 self.jq_error = None;
330+ self.scroll_to_selection = true;
299331 }
300- }
301-
302- if resp.run {
303- // First try as navigation path
304- if let Some(path) = jq_path_to_segments(&self.jq_bar.query) {
305- if resolve_path(root, &path).is_some() {
306- self.path = path;
332+ } else if resp.run {
333+ // Manual Enter (no completion) — try as path, then as jq query
334+ if let Some(jq_segs) = jq_path_to_segments(&self.jq_bar.query) {
335+ if resolve_path(root, &jq_segs).is_some() {
336+ self.path = make_full_path(jq_segs);
307337 self.selection = 0;
308338 self.jq_synced = true;
309339 self.jq_result = None;
@@ -312,7 +342,6 @@ impl BrowserView {
312342 return;
313343 }
314344 }
315- // Otherwise run as jq query
316345 let result = JqEngine::execute(&self.jq_bar.query, root);
317346 if let Some(err) = &result.error {
318347 self.jq_error = Some(err.clone());
@@ -323,7 +352,6 @@ impl BrowserView {
323352 }
324353 }
325354
326- // Show jq error inline
327355 if let Some(err) = &self.jq_error {
328356 ui.label(
329357 RichText::new(err)
+56-41src/widgets/jq_bar.rs
@@ -4,14 +4,16 @@ use crate::theme::CatppuccinMocha;
44
55 /// Response from the jq bar — tells the caller what happened this frame.
66 pub struct JqBarResponse {
7- /// Enter was pressed (caller should execute or navigate)
7+ /// Enter/Tab/click accepted a completion — navigate to the path
8+ pub accepted: bool,
9+ /// Enter with no completions — execute as jq query
810 pub run: bool,
9- /// Escape was pressed (caller should cancel/reset)
11+ /// Escape was pressed — cancel and reset
1012 pub escaped: bool,
11- /// Query text was edited by the user
13+ /// Query text was edited by the user (typing)
1214 pub changed: bool,
13- /// A completion was accepted (Tab or click)
14- pub completion_applied: bool,
15+ /// Cycling through completions — preview the path but don't commit
16+ pub previewing: bool,
1517 }
1618
1719 /// Reusable jq filter bar with fuzzy autocompletion.
@@ -35,26 +37,34 @@ impl JqBar {
3537 }
3638
3739 /// The egui Id used for the text input (for focus checks).
38- pub fn input_id(ui: &Ui) -> egui::Id {
39- ui.id().with("jq_bar_input")
40+ /// Uses a stable global id so callers can check focus from any ui context.
41+ pub fn input_id() -> egui::Id {
42+ egui::Id::new("jq_bar_input_global")
4043 }
4144
4245 /// Returns true if the jq bar input currently has focus.
4346 pub fn has_focus(ui: &Ui) -> bool {
44- let id = Self::input_id(ui);
47+ let id = Self::input_id();
4548 ui.ctx().memory(|m| m.focused().map_or(false, |f| f == id))
4649 }
4750
51+ /// Request focus on the jq bar input next frame.
52+ pub fn focus(&mut self) {
53+ self.refocus = true;
54+ }
55+
4856 /// Render the jq bar. Caller provides `root` for autocompletion.
4957 pub fn show(&mut self, ui: &mut Ui, root: &serde_json::Value) -> JqBarResponse {
5058 let mut response = JqBarResponse {
59+ accepted: false,
5160 run: false,
5261 escaped: false,
5362 changed: false,
54- completion_applied: false,
63+ previewing: false,
5564 };
5665
5766 let mut accepted_completion: Option<String> = None;
67+ let suppress_completions = self.refocus; // completion was just applied
5868
5969 ui.horizontal(|ui| {
6070 ui.label(
@@ -63,7 +73,7 @@ impl JqBar {
6373 .size(14.0),
6474 );
6575
66- let input_id = Self::input_id(ui);
76+ let input_id = Self::input_id();
6777 let text_response = ui.add(
6878 egui::TextEdit::singleline(&mut self.query)
6979 .id(input_id)
@@ -84,22 +94,50 @@ impl JqBar {
8494 self.refocus = false;
8595 }
8696
97+ // Check focus — Enter causes lost_focus, so check both
8798 let has_focus = text_response.has_focus();
99+ let just_lost_focus = text_response.lost_focus();
88100
89- if has_focus {
90- if text_response.changed() {
101+ if has_focus || just_lost_focus {
102+ if has_focus && text_response.changed() && !suppress_completions {
91103 response.changed = true;
92104 self.rebuild_completions(root);
93105 }
94106
95107 let tab = ui.input_mut(|i| i.consume_key(egui::Modifiers::NONE, egui::Key::Tab));
108+ let enter = ui.input(|i| i.key_pressed(egui::Key::Enter));
96109 let ctrl_space =
97110 ui.input_mut(|i| i.consume_key(egui::Modifiers::CTRL, egui::Key::Space));
98111
99- if tab && self.show_completions && !self.completions.is_empty() {
100- accepted_completion = Some(self.completions[self.completion_index].clone());
112+ if self.show_completions && !self.completions.is_empty() {
113+ // Arrow keys / Ctrl-N/P cycle and live-preview the selection
114+ let down = ui.input(|i| i.key_pressed(egui::Key::ArrowDown))
115+ || ui.input_mut(|i| i.consume_key(egui::Modifiers::CTRL, egui::Key::N));
116+ let up = ui.input(|i| i.key_pressed(egui::Key::ArrowUp))
117+ || ui.input_mut(|i| i.consume_key(egui::Modifiers::CTRL, egui::Key::P));
118+ if down {
119+ self.completion_index =
120+ (self.completion_index + 1).min(self.completions.len() - 1);
121+ self.apply_completion(&self.completions[self.completion_index].clone());
122+ self.refocus = true;
123+ response.previewing = true;
124+ }
125+ if up {
126+ self.completion_index = self.completion_index.saturating_sub(1);
127+ self.apply_completion(&self.completions[self.completion_index].clone());
128+ self.refocus = true;
129+ response.previewing = true;
130+ }
131+
132+ // Enter/Tab: accept current selection and close
133+ if tab || enter {
134+ accepted_completion = Some(self.completions[self.completion_index].clone());
135+ }
136+ } else if enter {
137+ response.run = true;
101138 }
102- if tab {
139+
140+ if tab && !self.show_completions {
103141 text_response.request_focus();
104142 }
105143
@@ -116,38 +154,15 @@ impl JqBar {
116154 self.rebuild_completions(root);
117155 }
118156 }
119-
120- // Enter: signal caller to execute/navigate
121- if text_response.lost_focus()
122- && ui.input(|i| i.key_pressed(egui::Key::Enter))
123- {
124- response.run = true;
125- }
126-
127- // Navigate completions
128- if self.show_completions && !self.completions.is_empty() {
129- let down = ui.input(|i| i.key_pressed(egui::Key::ArrowDown));
130- let up = ui.input(|i| i.key_pressed(egui::Key::ArrowUp));
131- if down {
132- self.completion_index =
133- (self.completion_index + 1).min(self.completions.len() - 1);
134- }
135- if up {
136- self.completion_index = self.completion_index.saturating_sub(1);
137- }
138- if ui.input(|i| i.key_pressed(egui::Key::Enter)) {
139- accepted_completion = Some(self.completions[self.completion_index].clone());
140- }
141- }
142157 }
143158 });
144159
145- // Apply completion
160+ // Apply completion — sets the query, closes suggestions, signals caller to navigate
146161 if let Some(comp) = accepted_completion {
147162 self.apply_completion(&comp);
148163 self.show_completions = false;
149164 self.refocus = true;
150- response.completion_applied = true;
165+ response.accepted = true;
151166 }
152167
153168 // Show completion popup
@@ -197,7 +212,7 @@ impl JqBar {
197212 self.apply_completion(&c);
198213 self.show_completions = false;
199214 self.refocus = true;
200- response.completion_applied = true;
215+ response.accepted = true;
201216 }
202217 }
203218