docs/internal/ISSUES_IMPLEMENTATION_PLAN.md
This document provides a step-by-step implementation plan for 14 bug fixes and feature improvements, following the pattern: reproduce in tmux, add e2e test, implement fix.
| # | Issue | Priority | Complexity | Key Files |
|---|---|---|---|---|
| 1 | Buffer focus history (close returns to previous) | P0 | Medium | split.rs, buffer_management.rs |
| 2 | Search not finding __ or plugin_name< | P0 | Low | render.rs |
| 3 | Multi-cursor cut not working | P0 | Medium | clipboard.rs |
| 4 | Rainbow bracket matching | P1 | High | New file, theme/types.rs |
| 5 | Async formatter (rustfmt freezing UI) | P1 | High | on_save_actions.rs, async handling |
| 6 | Indent selection cursor position | P1 | Medium | input.rs |
| 7 | Library files read-only (go-to-definition) | P1 | Low | lsp_requests.rs, buffer_management.rs |
| 8 | Tab bar scroll buttons | P1 | Medium | tabs.rs, mouse_input.rs |
| 9 | Explorer menu visibility | P2 | Low | menu.rs, render.rs |
| 10 | Shift+click selection | P1 | Low | mouse_input.rs |
| 11 | Whitespace cleanup on save | P2 | Medium | on_save_actions.rs, config.rs |
| 12 | LSP file filtering by project root | P2 | Medium | lsp_requests.rs, manager.rs |
| 13 | Virtual buffer cursor/status hiding | P2 | Low | render.rs, status bar |
| 14 | Movement keys disabled in virtual buffers | P2 | Low | input.rs, buffer_mode.rs |
When closing a buffer, the editor switches to an adjacent tab instead of the previously focused buffer.
SplitViewState in src/view/split.rs:107 has previous_buffer: Option<BufferId> - only tracks ONE previousbuffer_management.rs uses index-based replacement in close_buffer_internalFiles to modify:
/home/noam/repos/fresh/crates/fresh-editor/src/view/split.rs/home/noam/repos/fresh/crates/fresh-editor/src/app/buffer_management.rs/home/noam/repos/fresh/crates/fresh-editor/src/app/mod.rsStep 1.1: Change SplitViewState to use a focus history stack
// In split.rs, replace:
pub previous_buffer: Option<BufferId>,
// With:
pub focus_history: Vec<BufferId>, // Most recent at end
Step 1.2: Add helper methods to SplitViewState
impl SplitViewState {
pub fn push_focus(&mut self, buffer_id: BufferId) {
// Remove if already in history (LRU-style)
self.focus_history.retain(|&id| id != buffer_id);
self.focus_history.push(buffer_id);
// Limit to 50 entries
if self.focus_history.len() > 50 {
self.focus_history.remove(0);
}
}
pub fn pop_focus(&mut self) -> Option<BufferId> {
self.focus_history.pop()
}
pub fn remove_from_history(&mut self, buffer_id: BufferId) {
self.focus_history.retain(|&id| id != buffer_id);
}
}
Step 1.3: Update set_active_buffer in mod.rs to push to history before switching
Step 1.4: Update close_buffer_internal to use pop_focus() instead of index-based selection
Create tests/e2e/buffer_focus_history.rs:
#[test]
fn test_close_returns_to_previous_focused() {
// Open A, B, C
// Focus order: A -> B -> C -> A -> B
// Close B -> should return to A (most recent before B)
}
cargo test -p fresh-editor buffer_focusCtrl+F search doesn't find __ or plugin_name< even though they exist in the file.
render.rs uses regex::escape() when use_regex is falseFiles to modify:
/home/noam/repos/fresh/crates/fresh-editor/src/app/render.rsStep 2.1: Debug and verify regex escaping in perform_search()
< and _ are properly escaped_ should NOT need escaping in regex (it's literal)< should be literal in non-regex modeStep 2.2: Add debug logging if needed to trace search pattern construction
Add to tests/e2e/search.rs:
#[test]
fn test_search_double_underscore() {
let mut harness = EditorTestHarness::create(80, 24, HarnessOptions::new());
harness.type_text("def __init__(self): pass");
harness.send_key(KeyCode::Char('f'), KeyModifiers::CONTROL);
harness.type_text("__");
harness.send_key(KeyCode::Enter, KeyModifiers::NONE);
// Verify match found
}
#[test]
fn test_search_angle_bracket() {
let mut harness = EditorTestHarness::create(80, 24, HarnessOptions::new());
harness.type_text("let x: Vec<String>");
harness.send_key(KeyCode::Char('f'), KeyModifiers::CONTROL);
harness.type_text("Vec<");
harness.send_key(KeyCode::Enter, KeyModifiers::NONE);
// Verify match found
}
cargo test -p fresh-editor search__init__Using cut with multiple cursors selecting text in multiple locations doesn't work correctly.
cut_selection() in clipboard.rs handles multi-cursorFiles to modify:
/home/noam/repos/fresh/crates/fresh-editor/src/app/clipboard.rsStep 3.1: Investigate the cut flow
cut_selection() with multiple cursorsStep 3.2: Fix - likely need to:
apply_events_as_bulk_edit() for atomic operationAdd to tests/e2e/multicursor.rs:
#[test]
fn test_multicursor_cut() {
let mut harness = EditorTestHarness::create(80, 24, HarnessOptions::new());
harness.type_text("hello world hello world");
// Add cursor at both "world" positions
// Select "world" at each cursor
// Cut
harness.assert_buffer_content("hello hello ");
// Paste should give "world\nworld"
}
cargo test -p fresh-editor multicursorNo highlighting for matching parentheses, and no rainbow colors for nested brackets.
goto_matching_bracket() exists in render.rs but doesn't highlightreference_highlight_overlay.rs shows overlay patternFiles to create/modify:
/home/noam/repos/fresh/crates/fresh-editor/src/view/bracket_highlight_overlay.rs/home/noam/repos/fresh/crates/fresh-editor/src/view/theme/types.rs/home/noam/repos/fresh/crates/fresh-editor/src/config.rs/home/noam/repos/fresh/crates/fresh-editor/src/app/render.rsStep 4.1: Add rainbow colors to theme
// In types.rs
pub bracket_match_colors: Vec<Color>, // Cycle through for nested brackets
Step 4.2: Create bracket highlight overlay module
Step 4.3: Add config options
pub highlight_matching_brackets: bool,
pub rainbow_brackets: bool,
#[test]
fn test_bracket_highlight_on_cursor() {
// Create buffer with nested brackets
// Move cursor to opening bracket
// Verify overlay on matching closing bracket
}
cargo test -p fresh-editor bracketrustfmt timing out freezes the entire UI because formatter runs synchronously.
on_save_actions.rs has a blocking polling loop (10ms sleep in try_wait loop)Files to modify:
/home/noam/repos/fresh/crates/fresh-editor/src/app/on_save_actions.rs/home/noam/repos/fresh/crates/fresh-editor/src/app/async_messages.rs/home/noam/repos/fresh/crates/fresh-editor/src/app/mod.rsStep 5.1: Create async formatter task type
pub struct FormatterTask {
pub buffer_id: BufferId,
pub receiver: oneshot::Receiver<FormatterResult>,
}
Step 5.2: Move formatter execution to spawned thread
Step 5.3: Add formatter state tracking
// In Editor
pub formatting_in_progress: Option<FormatterTask>,
Step 5.4: Handle completion in event loop
#[test]
fn test_formatter_nonblocking() {
// Start format on buffer
// Immediately send input events
// Verify input events processed (UI not blocked)
}
cargo test -p fresh-editor formatAfter pressing Tab with text selected, selection moves or changes unexpectedly.
InsertTab action in input.rs indents selected linesFiles to modify:
/home/noam/repos/fresh/crates/fresh-editor/src/app/input.rs/home/noam/repos/fresh/crates/fresh-editor/src/input/buffer_mode.rsStep 6.1: Investigate current indent behavior
Step 6.2: Fix cursor adjustment
Add to tests/e2e/tab_indent_selection.rs:
#[test]
fn test_indent_preserves_relative_cursor() {
// Type multiline text
// Select multiple lines
// Record cursor position relative to line
// Indent
// Verify cursor at original_column + indent_size
}
cargo test -p fresh-editor tab_indentWhen using go-to-definition and it opens a library source file (outside project), it should be read-only.
BufferMetadata has read_only: bool fieldopen_file() without checking locationFiles to modify:
/home/noam/repos/fresh/crates/fresh-editor/src/app/lsp_requests.rs/home/noam/repos/fresh/crates/fresh-editor/src/app/buffer_management.rsStep 7.1: Add library file detection
fn is_library_file(path: &Path, project_root: &Path) -> bool {
// Check if outside project root
if !path.starts_with(project_root) {
return true;
}
// Check common library paths
let path_str = path.to_string_lossy();
path_str.contains(".cargo") ||
path_str.contains("node_modules") ||
path_str.contains("site-packages")
}
Step 7.2: Update handle_goto_definition_response to set read-only
// After opening file, if it's a library file:
if is_library_file(&path, &self.project_root) {
self.buffer_metadata_mut(buffer_id).read_only = true;
}
#[test]
fn test_goto_definition_library_readonly() {
// Mock LSP response pointing outside project
// Execute goto definition
// Verify buffer has read_only = true
}
cargo test -p fresh-editor lspLeft/right buttons on tabs bar don't work - left button overlaps separator, clicking either does nothing.
tabs.rs renders < and > scroll indicatorsFiles to modify:
/home/noam/repos/fresh/crates/fresh-editor/src/view/ui/tabs.rs/home/noam/repos/fresh/crates/fresh-editor/src/app/mouse_input.rsStep 8.1: Add scroll button hit areas to TabLayout
pub struct TabLayout {
// existing fields...
pub left_scroll_area: Option<Rect>,
pub right_scroll_area: Option<Rect>,
}
Step 8.2: Extend TabHit enum
pub enum TabHit {
// existing variants...
ScrollLeft,
ScrollRight,
}
Step 8.3: Handle clicks in mouse_input.rs
tab_scroll_offset by scroll amounttab_scroll_offset by scroll amountStep 8.4: Fix overlap with file explorer separator
#[test]
fn test_tab_scroll_buttons() {
// Open 10+ files to overflow tab bar
// Simulate click on right scroll button
// Verify tab_scroll_offset increased
// Verify different tabs now visible
}
cargo test -p fresh-editor tabs"Explorer" menu should only be visible when File Explorer is in focus.
when conditionsFiles to modify:
/home/noam/repos/fresh/crates/fresh-editor/src/view/ui/menu.rs/home/noam/repos/fresh/crates/fresh-editor/src/app/render.rsStep 9.1: Add "file_explorer_focused" to MenuContext
Step 9.2: Update Explorer menu items with when: "file_explorer_focused"
Step 9.3: Set context based on current focus in render
#[test]
fn test_explorer_menu_visibility() {
// Focus editor area
// Verify Explorer menu not visible
// Focus file explorer
// Verify Explorer menu visible
}
cargo test -p fresh-editor menuShift+click should extend selection to clicked point like other apps.
mouse_input.rs handles clicks but doesn't check Shift modifier for selectionFiles to modify:
/home/noam/repos/fresh/crates/fresh-editor/src/app/mouse_input.rsStep 10.1: In handle_mouse_click, check for Shift modifier
if modifiers.contains(KeyModifiers::SHIFT) {
// Use current cursor position as anchor
// Calculate clicked position
// Extend selection from anchor to clicked position
state.cursors.primary_mut().move_to(clicked_pos, true); // true = extend
return;
}
Add to tests/e2e/selection.rs:
#[test]
fn test_shift_click_extends_selection() {
let mut harness = EditorTestHarness::create(80, 24, HarnessOptions::new());
harness.type_text("hello world test");
// Click at position 0
harness.send_mouse_event(0, 2, MouseButton::Left, MouseEventKind::Down);
// Shift+click at position 10
harness.send_mouse_event_with_modifiers(10, 2, MouseButton::Left,
MouseEventKind::Down, KeyModifiers::SHIFT);
// Verify selection from 0 to 10
}
cargo test -p fresh-editor selectionNeed command and on-save option to clean up trailing whitespace.
Files to modify:
/home/noam/repos/fresh/crates/fresh-editor/src/app/on_save_actions.rs/home/noam/repos/fresh/crates/fresh-editor/src/config.rs/home/noam/repos/fresh/crates/fresh-editor/src/input/commands.rsStep 11.1: Add config options
// In EditorConfig
pub trim_trailing_whitespace_on_save: bool,
pub ensure_final_newline: bool,
Step 11.2: Implement cleanup function
fn trim_trailing_whitespace(&mut self) -> Vec<Event> {
// Iterate all lines
// Trim trailing whitespace from each
// Return edit events
}
Step 11.3: Add as action
Action::TrimTrailingWhitespace
Step 11.4: Call in run_on_save_actions if enabled
#[test]
fn test_trim_whitespace_on_save() {
// Create buffer with trailing spaces
// Enable config
// Save
// Verify spaces removed
}
cargo test -p fresh-editor whitespaceLSP sends irrelevant files (outside project root, wrong language).
lsp_requests.rs has filters but may not check project rootFiles to modify:
/home/noam/repos/fresh/crates/fresh-editor/src/app/lsp_requests.rs/home/noam/repos/fresh/crates/fresh-editor/src/app/types.rsStep 12.1: Add project root check in with_lsp_for_buffer
// Check if file is within project root
let file_path = metadata.file_path()?;
if !file_path.starts_with(&self.project_root) {
metadata.disable_lsp("File outside project root");
return None;
}
#[test]
fn test_lsp_disabled_outside_project() {
// Open file outside project
// Verify LSP disabled for that buffer
}
cargo test -p fresh-editor lspIn virtual buffers, line/column status should be hidden.
Files to modify:
/home/noam/repos/fresh/crates/fresh-editor/src/app/render.rsStep 13.1: In status bar rendering, check if buffer is virtual
if !metadata.is_virtual() {
// Render line:column
} else {
// Show buffer mode instead (e.g., "Diagnostics")
}
#[test]
fn test_virtual_buffer_hides_line_col() {
// Open diagnostics panel (virtual buffer)
// Verify status bar doesn't show line:col
}
In virtual buffers where cursor is hidden, movement keys should have no effect.
Files to modify:
/home/noam/repos/fresh/crates/fresh-editor/src/app/input.rs/home/noam/repos/fresh/crates/fresh-editor/src/input/buffer_mode.rsStep 14.1: Check show_cursors before handling movement
fn handle_movement_action(&mut self, action: Action) {
if !self.active_state().show_cursors {
return; // Ignore movement in virtual buffers
}
// Normal movement handling
}
#[test]
fn test_virtual_buffer_ignores_movement() {
// Create virtual buffer with hidden cursor
// Send movement keys
// Verify cursor position unchanged
}
cargo test -p fresh-editor
cargo test -p fresh-editor --test '*'
__ and < characters - verify foundPhase 1 - High Impact Bug Fixes:
Phase 2 - UX Improvements:
Phase 3 - Features:
Phase 4 - Polish: