@@ -485,8 +485,8 @@ impl Editor { |
| 485 | } | 485 | } |
| 486 | } | 486 | } |
| 487 | | 487 | |
| 488 | - // Update diagnostics for current file | 488 | + // Update diagnostics for current file (need full path to match LSP URIs) |
| 489 | - if let Some(path) = self.filename() { | 489 | + if let Some(path) = self.current_file_path() { |
| 490 | let path_str = path.to_string_lossy(); | 490 | let path_str = path.to_string_lossy(); |
| 491 | self.lsp_state.diagnostics = self.workspace.lsp.get_diagnostics(&path_str); | 491 | self.lsp_state.diagnostics = self.workspace.lsp.get_diagnostics(&path_str); |
| 492 | } | 492 | } |
@@ -1049,6 +1049,7 @@ impl Editor { |
| 1049 | self.workspace.fuss.hints_expanded, | 1049 | self.workspace.fuss.hints_expanded, |
| 1050 | &repo_name, | 1050 | &repo_name, |
| 1051 | branch.as_deref(), | 1051 | branch.as_deref(), |
| | 1052 | + self.workspace.fuss.git_mode, |
| 1052 | )?; | 1053 | )?; |
| 1053 | } | 1054 | } |
| 1054 | } | 1055 | } |
@@ -1466,8 +1467,8 @@ impl Editor { |
| 1466 | (Key::F(12), Modifiers { shift: true, .. }) => self.lsp_find_references(), | 1467 | (Key::F(12), Modifiers { shift: true, .. }) => self.lsp_find_references(), |
| 1467 | // Hover info: F1 | 1468 | // Hover info: F1 |
| 1468 | (Key::F(1), _) => self.lsp_hover(), | 1469 | (Key::F(1), _) => self.lsp_hover(), |
| 1469 | - // Code completion: Ctrl+Space | 1470 | + // Code completion: Ctrl+N (vim-style) |
| 1470 | - (Key::Char(' '), Modifiers { ctrl: true, .. }) => self.lsp_complete(), | 1471 | + (Key::Char('n'), Modifiers { ctrl: true, .. }) => self.lsp_complete(), |
| 1471 | // Rename: F2 | 1472 | // Rename: F2 |
| 1472 | (Key::F(2), _) => self.lsp_rename(), | 1473 | (Key::F(2), _) => self.lsp_rename(), |
| 1473 | // Server manager: Alt+M | 1474 | // Server manager: Alt+M |
@@ -3138,6 +3139,11 @@ impl Editor { |
| 3138 | } | 3139 | } |
| 3139 | | 3140 | |
| 3140 | fn handle_fuss_key(&mut self, key: Key, mods: Modifiers) -> Result<()> { | 3141 | fn handle_fuss_key(&mut self, key: Key, mods: Modifiers) -> Result<()> { |
| | 3142 | + // Handle git mode separately |
| | 3143 | + if self.workspace.fuss.git_mode { |
| | 3144 | + return self.handle_fuss_git_key(key, mods); |
| | 3145 | + } |
| | 3146 | + |
| 3141 | match (&key, &mods) { | 3147 | match (&key, &mods) { |
| 3142 | // Quit: Ctrl+Q (still works in fuss mode) | 3148 | // Quit: Ctrl+Q (still works in fuss mode) |
| 3143 | (Key::Char('q'), Modifiers { ctrl: true, .. }) => { | 3149 | (Key::Char('q'), Modifiers { ctrl: true, .. }) => { |
@@ -3146,19 +3152,23 @@ impl Editor { |
| 3146 | | 3152 | |
| 3147 | // Exit fuss mode (Escape or F3) | 3153 | // Exit fuss mode (Escape or F3) |
| 3148 | (Key::Escape, _) | (Key::F(3), _) => { | 3154 | (Key::Escape, _) | (Key::F(3), _) => { |
| | 3155 | + self.workspace.fuss.filter_clear(); |
| 3149 | self.workspace.fuss.deactivate(); | 3156 | self.workspace.fuss.deactivate(); |
| 3150 | } | 3157 | } |
| 3151 | | 3158 | |
| 3152 | // Navigation | 3159 | // Navigation |
| 3153 | - (Key::Up, _) | (Key::Char('k'), Modifiers { ctrl: false, alt: false, .. }) => { | 3160 | + (Key::Up, _) => { |
| | 3161 | + self.workspace.fuss.filter_clear(); |
| 3154 | self.workspace.fuss.move_up(); | 3162 | self.workspace.fuss.move_up(); |
| 3155 | } | 3163 | } |
| 3156 | - (Key::Down, _) | (Key::Char('j'), Modifiers { ctrl: false, alt: false, .. }) => { | 3164 | + (Key::Down, _) => { |
| | 3165 | + self.workspace.fuss.filter_clear(); |
| 3157 | self.workspace.fuss.move_down(); | 3166 | self.workspace.fuss.move_down(); |
| 3158 | } | 3167 | } |
| 3159 | | 3168 | |
| 3160 | // Toggle expand/collapse directory, or collapse parent if on a file/collapsed dir | 3169 | // Toggle expand/collapse directory, or collapse parent if on a file/collapsed dir |
| 3161 | (Key::Char(' '), _) => { | 3170 | (Key::Char(' '), _) => { |
| | 3171 | + self.workspace.fuss.filter_clear(); |
| 3162 | if self.workspace.fuss.is_dir_selected() { | 3172 | if self.workspace.fuss.is_dir_selected() { |
| 3163 | // If on a directory, toggle its expand state | 3173 | // If on a directory, toggle its expand state |
| 3164 | self.workspace.fuss.toggle_expand(); | 3174 | self.workspace.fuss.toggle_expand(); |
@@ -3170,6 +3180,7 @@ impl Editor { |
| 3170 | | 3180 | |
| 3171 | // Expand directory (right arrow) | 3181 | // Expand directory (right arrow) |
| 3172 | (Key::Right, _) => { | 3182 | (Key::Right, _) => { |
| | 3183 | + self.workspace.fuss.filter_clear(); |
| 3173 | if self.workspace.fuss.is_dir_selected() { | 3184 | if self.workspace.fuss.is_dir_selected() { |
| 3174 | // Only expand if not already expanded | 3185 | // Only expand if not already expanded |
| 3175 | if let Some(ref tree) = self.workspace.fuss.tree { | 3186 | if let Some(ref tree) = self.workspace.fuss.tree { |
@@ -3185,6 +3196,7 @@ impl Editor { |
| 3185 | | 3196 | |
| 3186 | // Collapse directory or go to parent (left arrow) | 3197 | // Collapse directory or go to parent (left arrow) |
| 3187 | (Key::Left, _) => { | 3198 | (Key::Left, _) => { |
| | 3199 | + self.workspace.fuss.filter_clear(); |
| 3188 | let mut collapsed = false; | 3200 | let mut collapsed = false; |
| 3189 | if self.workspace.fuss.is_dir_selected() { | 3201 | if self.workspace.fuss.is_dir_selected() { |
| 3190 | // If on an expanded directory, collapse it | 3202 | // If on an expanded directory, collapse it |
@@ -3205,7 +3217,8 @@ impl Editor { |
| 3205 | } | 3217 | } |
| 3206 | | 3218 | |
| 3207 | // Open file or toggle directory | 3219 | // Open file or toggle directory |
| 3208 | - (Key::Enter, _) | (Key::Char('o'), Modifiers { ctrl: false, alt: false, .. }) => { | 3220 | + (Key::Enter, _) => { |
| | 3221 | + self.workspace.fuss.filter_clear(); |
| 3209 | if self.workspace.fuss.is_dir_selected() { | 3222 | if self.workspace.fuss.is_dir_selected() { |
| 3210 | self.workspace.fuss.toggle_expand(); | 3223 | self.workspace.fuss.toggle_expand(); |
| 3211 | } else if let Some(path) = self.workspace.fuss.selected_file() { | 3224 | } else if let Some(path) = self.workspace.fuss.selected_file() { |
@@ -3214,8 +3227,8 @@ impl Editor { |
| 3214 | } | 3227 | } |
| 3215 | } | 3228 | } |
| 3216 | | 3229 | |
| 3217 | - // Toggle hidden files | 3230 | + // Toggle hidden files: Alt+. |
| 3218 | - (Key::Char('.'), _) => { | 3231 | + (Key::Char('.'), Modifiers { alt: true, .. }) => { |
| 3219 | self.workspace.fuss.toggle_hidden(); | 3232 | self.workspace.fuss.toggle_hidden(); |
| 3220 | } | 3233 | } |
| 3221 | | 3234 | |
@@ -3228,13 +3241,8 @@ impl Editor { |
| 3228 | self.workspace.fuss.toggle_hints(); | 3241 | self.workspace.fuss.toggle_hints(); |
| 3229 | } | 3242 | } |
| 3230 | | 3243 | |
| 3231 | - // Also allow 'h' for hints toggle as fallback | 3244 | + // Open file in vertical split: Ctrl+V |
| 3232 | - (Key::Char('h'), Modifiers { ctrl: false, alt: false, .. }) => { | 3245 | + (Key::Char('v'), Modifiers { ctrl: true, .. }) => { |
| 3233 | - self.workspace.fuss.toggle_hints(); | | |
| 3234 | - } | | |
| 3235 | - | | |
| 3236 | - // Open file in vertical split (v) | | |
| 3237 | - (Key::Char('v'), Modifiers { ctrl: false, alt: false, .. }) => { | | |
| 3238 | if !self.workspace.fuss.is_dir_selected() { | 3246 | if !self.workspace.fuss.is_dir_selected() { |
| 3239 | if let Some(path) = self.workspace.fuss.selected_file() { | 3247 | if let Some(path) = self.workspace.fuss.selected_file() { |
| 3240 | self.open_file_in_vsplit(&path)?; | 3248 | self.open_file_in_vsplit(&path)?; |
@@ -3243,8 +3251,8 @@ impl Editor { |
| 3243 | } | 3251 | } |
| 3244 | } | 3252 | } |
| 3245 | | 3253 | |
| 3246 | - // Open file in horizontal split (s) | 3254 | + // Open file in horizontal split: Ctrl+S |
| 3247 | - (Key::Char('s'), Modifiers { ctrl: false, alt: false, .. }) => { | 3255 | + (Key::Char('s'), Modifiers { ctrl: true, .. }) => { |
| 3248 | if !self.workspace.fuss.is_dir_selected() { | 3256 | if !self.workspace.fuss.is_dir_selected() { |
| 3249 | if let Some(path) = self.workspace.fuss.selected_file() { | 3257 | if let Some(path) = self.workspace.fuss.selected_file() { |
| 3250 | self.open_file_in_hsplit(&path)?; | 3258 | self.open_file_in_hsplit(&path)?; |
@@ -3253,8 +3261,36 @@ impl Editor { |
| 3253 | } | 3261 | } |
| 3254 | } | 3262 | } |
| 3255 | | 3263 | |
| | 3264 | + // Enter git mode: Alt+G |
| | 3265 | + (Key::Char('g'), Modifiers { alt: true, .. }) => { |
| | 3266 | + self.workspace.fuss.enter_git_mode(); |
| | 3267 | + self.message = Some("Git: [a]dd [u]nstage [d]iff [m]sg [p]ush pu[l]l [f]etch [t]ag".to_string()); |
| | 3268 | + } |
| | 3269 | + |
| | 3270 | + // Backspace: remove last filter character |
| | 3271 | + (Key::Backspace, _) => { |
| | 3272 | + self.workspace.fuss.filter_pop(); |
| | 3273 | + } |
| | 3274 | + |
| | 3275 | + // Regular characters: add to filter for fuzzy jump |
| | 3276 | + (Key::Char(c), Modifiers { ctrl: false, alt: false, .. }) => { |
| | 3277 | + self.workspace.fuss.filter_push(*c); |
| | 3278 | + } |
| | 3279 | + |
| | 3280 | + _ => {} |
| | 3281 | + } |
| | 3282 | + Ok(()) |
| | 3283 | + } |
| | 3284 | + |
| | 3285 | + /// Handle keys when in git sub-mode within fuss |
| | 3286 | + fn handle_fuss_git_key(&mut self, key: Key, mods: Modifiers) -> Result<()> { |
| | 3287 | + // Any key exits git mode (after potentially doing an action) |
| | 3288 | + self.workspace.fuss.exit_git_mode(); |
| | 3289 | + self.message = None; |
| | 3290 | + |
| | 3291 | + match (&key, &mods) { |
| 3256 | // Git: Stage file (a) | 3292 | // Git: Stage file (a) |
| 3257 | - (Key::Char('a'), Modifiers { ctrl: false, alt: false, .. }) => { | 3293 | + (Key::Char('a'), _) => { |
| 3258 | if self.workspace.fuss.stage_selected() { | 3294 | if self.workspace.fuss.stage_selected() { |
| 3259 | self.message = Some("Staged".to_string()); | 3295 | self.message = Some("Staged".to_string()); |
| 3260 | } else { | 3296 | } else { |
@@ -3263,7 +3299,7 @@ impl Editor { |
| 3263 | } | 3299 | } |
| 3264 | | 3300 | |
| 3265 | // Git: Unstage file (u) | 3301 | // Git: Unstage file (u) |
| 3266 | - (Key::Char('u'), Modifiers { ctrl: false, alt: false, .. }) => { | 3302 | + (Key::Char('u'), _) => { |
| 3267 | if self.workspace.fuss.unstage_selected() { | 3303 | if self.workspace.fuss.unstage_selected() { |
| 3268 | self.message = Some("Unstaged".to_string()); | 3304 | self.message = Some("Unstaged".to_string()); |
| 3269 | } else { | 3305 | } else { |
@@ -3272,7 +3308,7 @@ impl Editor { |
| 3272 | } | 3308 | } |
| 3273 | | 3309 | |
| 3274 | // Git: Show diff (d) | 3310 | // Git: Show diff (d) |
| 3275 | - (Key::Char('d'), Modifiers { ctrl: false, alt: false, .. }) => { | 3311 | + (Key::Char('d'), _) => { |
| 3276 | if let Some((filename, diff)) = self.workspace.fuss.get_diff_for_selected() { | 3312 | if let Some((filename, diff)) = self.workspace.fuss.get_diff_for_selected() { |
| 3277 | let display_name = format!("[diff] {}", filename); | 3313 | let display_name = format!("[diff] {}", filename); |
| 3278 | self.workspace.open_content_tab(&diff, &display_name); | 3314 | self.workspace.open_content_tab(&diff, &display_name); |
@@ -3283,7 +3319,7 @@ impl Editor { |
| 3283 | } | 3319 | } |
| 3284 | | 3320 | |
| 3285 | // Git: Commit (m) - opens prompt for commit message | 3321 | // Git: Commit (m) - opens prompt for commit message |
| 3286 | - (Key::Char('m'), Modifiers { ctrl: false, alt: false, .. }) => { | 3322 | + (Key::Char('m'), _) => { |
| 3287 | self.prompt = PromptState::TextInput { | 3323 | self.prompt = PromptState::TextInput { |
| 3288 | label: "Commit message: ".to_string(), | 3324 | label: "Commit message: ".to_string(), |
| 3289 | buffer: String::new(), | 3325 | buffer: String::new(), |
@@ -3293,25 +3329,25 @@ impl Editor { |
| 3293 | } | 3329 | } |
| 3294 | | 3330 | |
| 3295 | // Git: Push (p) | 3331 | // Git: Push (p) |
| 3296 | - (Key::Char('p'), Modifiers { ctrl: false, alt: false, .. }) => { | 3332 | + (Key::Char('p'), _) => { |
| 3297 | let (_, msg) = self.workspace.fuss.git_push(); | 3333 | let (_, msg) = self.workspace.fuss.git_push(); |
| 3298 | self.message = Some(msg); | 3334 | self.message = Some(msg); |
| 3299 | } | 3335 | } |
| 3300 | | 3336 | |
| 3301 | // Git: Pull (l) | 3337 | // Git: Pull (l) |
| 3302 | - (Key::Char('l'), Modifiers { ctrl: false, alt: false, .. }) => { | 3338 | + (Key::Char('l'), _) => { |
| 3303 | let (_, msg) = self.workspace.fuss.git_pull(); | 3339 | let (_, msg) = self.workspace.fuss.git_pull(); |
| 3304 | self.message = Some(msg); | 3340 | self.message = Some(msg); |
| 3305 | } | 3341 | } |
| 3306 | | 3342 | |
| 3307 | // Git: Fetch (f) | 3343 | // Git: Fetch (f) |
| 3308 | - (Key::Char('f'), Modifiers { ctrl: false, alt: false, .. }) => { | 3344 | + (Key::Char('f'), _) => { |
| 3309 | let (_, msg) = self.workspace.fuss.git_fetch(); | 3345 | let (_, msg) = self.workspace.fuss.git_fetch(); |
| 3310 | self.message = Some(msg); | 3346 | self.message = Some(msg); |
| 3311 | } | 3347 | } |
| 3312 | | 3348 | |
| 3313 | // Git: Tag (t) - opens prompt for tag name | 3349 | // Git: Tag (t) - opens prompt for tag name |
| 3314 | - (Key::Char('t'), Modifiers { ctrl: false, alt: false, .. }) => { | 3350 | + (Key::Char('t'), _) => { |
| 3315 | self.prompt = PromptState::TextInput { | 3351 | self.prompt = PromptState::TextInput { |
| 3316 | label: "Tag name: ".to_string(), | 3352 | label: "Tag name: ".to_string(), |
| 3317 | buffer: String::new(), | 3353 | buffer: String::new(), |
@@ -3320,6 +3356,7 @@ impl Editor { |
| 3320 | self.message = Some("Enter tag name (Enter to create, Esc to cancel)".to_string()); | 3356 | self.message = Some("Enter tag name (Enter to create, Esc to cancel)".to_string()); |
| 3321 | } | 3357 | } |
| 3322 | | 3358 | |
| | 3359 | + // Escape or any other key just cancels git mode |
| 3323 | _ => {} | 3360 | _ => {} |
| 3324 | } | 3361 | } |
| 3325 | Ok(()) | 3362 | Ok(()) |