irongit

feat: session based focus list

huncholanehuncholaneauthored
parent eeae321commit 0ead49ba478b757ac01808a8486394882866559eBrowse files

3 files changed, +67 -17

+30-17src/app.rs
@@ -259,6 +259,7 @@ impl JvApp {
259259 };
260260 if let Some(loaded) = &app.current_session {
261261 app.applied_threshold = loaded.session.jaccard_threshold;
262+ app.browser_view.load_focus_list(&loaded.session.focus_list);
262263 }
263264 tracing::info!("UI loaded");
264265 app
@@ -351,6 +352,7 @@ impl JvApp {
351352 if ui.selectable_label(name == &current_name, name).clicked() {
352353 let session = self.session_manager.sessions[i].clone();
353354 self.session_manager.save_last_session_id(&session.id);
355+ self.browser_view.load_focus_list(&session.focus_list);
354356 self.current_session = Some(LoadedSession::new(session));
355357 self.selected_file_index = 0;
356358 self.rebuild_schema();
@@ -889,27 +891,38 @@ impl JvApp {
889891 return;
890892 }
891893
892- let loaded = self.current_session.as_ref().unwrap();
893- let file_idx = self.selected_file_index.min(loaded.parsed_files.len().saturating_sub(1));
894- let file_count = loaded.parsed_files.len();
895-
896- // Invalidate only when files are added/removed (not on selection change)
897- if file_count != self.last_file_count {
898- self.browser_view.invalidate();
899- self.code_view.invalidate();
900- self.schema_diagram_view.invalidate();
901- self.last_file_count = file_count;
894+ {
895+ let loaded = self.current_session.as_ref().unwrap();
896+ let file_idx = self.selected_file_index.min(loaded.parsed_files.len().saturating_sub(1));
897+ let file_count = loaded.parsed_files.len();
898+
899+ if file_count != self.last_file_count {
900+ self.browser_view.invalidate();
901+ self.code_view.invalidate();
902+ self.schema_diagram_view.invalidate();
903+ self.last_file_count = file_count;
904+ }
905+ self.last_file_index = file_idx;
906+
907+ self.browser_view.show(ui, &loaded.parsed_files);
902908 }
903- self.last_file_index = file_idx;
904909
905- self.browser_view.show(ui, &loaded.parsed_files);
910+ // Save focus list if it changed
911+ if self.browser_view.take_focus_dirty() {
912+ if let Some(loaded) = &mut self.current_session {
913+ loaded.session.focus_list = self.browser_view.save_focus_list();
914+ self.session_manager.update_session(&loaded.session);
915+ }
916+ }
906917
907918 // Sync sidebar selection from browser's current file
908- if let Some(file_key) = self.browser_view.current_file_key(&loaded.parsed_files) {
909- if let Some(idx) = loaded.parsed_files.iter().position(|(n, _)| {
910- n.strip_suffix(".json").unwrap_or(n) == file_key
911- }) {
912- self.selected_file_index = idx;
919+ if let Some(loaded) = &self.current_session {
920+ if let Some(file_key) = self.browser_view.current_file_key(&loaded.parsed_files) {
921+ if let Some(idx) = loaded.parsed_files.iter().position(|(n, _)| {
922+ n.strip_suffix(".json").unwrap_or(n) == file_key
923+ }) {
924+ self.selected_file_index = idx;
925+ }
913926 }
914927 }
915928 }
+3-0src/session.rs
@@ -27,6 +27,8 @@ pub struct Session {
2727 pub enum_conversions: Vec<EnumConversion>,
2828 #[serde(default)]
2929 pub hidden_fields: Vec<String>, // "StructName.field_name" format
30+ #[serde(default)]
31+ pub focus_list: Vec<Vec<String>>, // saved focus paths for jv browser
3032 }
3133
3234 fn default_jaccard() -> f32 {
@@ -141,6 +143,7 @@ impl Session {
141143 jaccard_threshold: default_jaccard(),
142144 enum_conversions: Vec::new(),
143145 hidden_fields: Vec::new(),
146+ focus_list: Vec::new(),
144147 }
145148 }
146149 }
+34-0src/views/browser.rs
@@ -54,6 +54,7 @@ pub struct BrowserView {
5454 // Focus list: full paths to pinned entries (e.g. ["activity_Aug.responseData.status"])
5555 focused: Vec<Vec<PathSegment>>,
5656 focus_mode: bool,
57+ focus_dirty: bool,
5758 }
5859
5960 impl BrowserView {
@@ -73,6 +74,7 @@ impl BrowserView {
7374 cache_key: 0,
7475 focused: Vec::new(),
7576 focus_mode: false,
77+ focus_dirty: false,
7678 }
7779 }
7880
@@ -112,6 +114,37 @@ impl BrowserView {
112114 self.sync_jq_from_path();
113115 }
114116
117+ /// Load focus list from session data.
118+ pub fn load_focus_list(&mut self, paths: &[Vec<String>]) {
119+ self.focused = paths.iter().map(|segs| {
120+ segs.iter().map(|s| {
121+ if let Some(stripped) = s.strip_prefix('[').and_then(|s| s.strip_suffix(']')) {
122+ if let Ok(i) = stripped.parse::<usize>() {
123+ return PathSegment::Index(i);
124+ }
125+ }
126+ PathSegment::Key(s.clone())
127+ }).collect()
128+ }).collect();
129+ }
130+
131+ /// Save focus list as session-serializable data.
132+ pub fn save_focus_list(&self) -> Vec<Vec<String>> {
133+ self.focused.iter().map(|path| {
134+ path.iter().map(|seg| match seg {
135+ PathSegment::Key(k) => k.clone(),
136+ PathSegment::Index(i) => format!("[{}]", i),
137+ }).collect()
138+ }).collect()
139+ }
140+
141+ /// Returns true if the focus list was modified since last check, and clears the flag.
142+ pub fn take_focus_dirty(&mut self) -> bool {
143+ let dirty = self.focus_dirty;
144+ self.focus_dirty = false;
145+ dirty
146+ }
147+
115148 pub fn invalidate(&mut self) {
116149 self.cache_key = 0;
117150 self.path.clear();
@@ -322,6 +355,7 @@ impl BrowserView {
322355 } else {
323356 self.focused.push(full_path);
324357 }
358+ self.focus_dirty = true;
325359 }
326360 }
327361 // 'F' (shift-f) toggles focus mode