sql-cli 1.69.2

SQL query tool for CSV/JSON with both interactive TUI and non-interactive CLI modes - perfect for exploration and automation
Documentation
# Buffer State Snapshot Design

## Goal
Enable true buffer switching where switching back to a buffer restores the EXACT state as if you never left - cursor position, viewport, mode, search state, everything.

## Key Insight
The Buffer becomes a complete "memento" that contains a snapshot of all view state. The state manager coordinates saving/restoring this snapshot transparently.

## Architecture

### 1. Buffer as State Container
```rust
pub struct Buffer {
    // ... existing data fields ...
    
    // State snapshot - populated when switching away
    state_snapshot: Option<BufferStateSnapshot>,
}

pub struct BufferStateSnapshot {
    // Mode and navigation
    mode: AppMode,
    cursor_position: (usize, usize),
    viewport_offset: (usize, usize),
    cursor_lock: bool,
    viewport_lock: bool,
    
    // Search state
    search_state: SearchStateSnapshot,
    
    // Filter state
    filter_pattern: Option<String>,
    fuzzy_filter_pattern: Option<String>,
    filter_active: bool,
    fuzzy_filter_active: bool,
    
    // Column state
    hidden_columns: Vec<String>,
    pinned_columns: Vec<String>,
    column_order: Vec<String>,
    sort_column: Option<usize>,
    sort_ascending: bool,
    
    // Input state (for command mode)
    input_text: String,
    input_cursor: usize,
    
    // Timestamp for debugging
    saved_at: std::time::Instant,
}

pub struct SearchStateSnapshot {
    vim_search: Option<VimSearchState>,
    column_search: Option<ColumnSearchState>,
    search_matches: Vec<(usize, usize)>,
    current_match: usize,
}
```

### 2. State Manager as Coordinator

The ShadowStateManager becomes responsible for:

```rust
impl ShadowStateManager {
    /// Save current state to buffer before switching away
    pub fn save_to_buffer(&self, buffer: &mut Buffer, viewport_manager: &ViewportManager) {
        let snapshot = BufferStateSnapshot {
            mode: self.get_mode(),
            cursor_position: viewport_manager.get_cursor_position(),
            viewport_offset: viewport_manager.get_viewport_offset(),
            cursor_lock: viewport_manager.is_cursor_locked(),
            viewport_lock: viewport_manager.is_viewport_locked(),
            // ... collect all state ...
            saved_at: std::time::Instant::now(),
        };
        
        buffer.set_state_snapshot(Some(snapshot));
        info!("Saved state snapshot to buffer");
    }
    
    /// Restore state from buffer when switching to it
    pub fn restore_from_buffer(&mut self, buffer: &Buffer, viewport_manager: &mut ViewportManager) {
        if let Some(snapshot) = buffer.get_state_snapshot() {
            // Restore mode
            self.set_state_from_mode(snapshot.mode);
            
            // Restore viewport
            viewport_manager.set_cursor_position(snapshot.cursor_position);
            viewport_manager.set_viewport_offset(snapshot.viewport_offset);
            viewport_manager.set_cursor_lock(snapshot.cursor_lock);
            viewport_manager.set_viewport_lock(snapshot.viewport_lock);
            
            // Restore search state
            if let Some(vim_search) = &snapshot.search_state.vim_search {
                self.restore_vim_search(vim_search);
            }
            
            // ... restore all state ...
            
            info!("Restored state snapshot from buffer (age: {:?})", snapshot.saved_at.elapsed());
        } else {
            // No snapshot - initialize default state for this buffer
            self.initialize_default_state(buffer);
        }
    }
}
```

### 3. TUI Integration (Transparent)

The TUI doesn't need to know about snapshots:

```rust
impl EnhancedTUI {
    /// Switch to a different buffer
    pub fn switch_to_buffer(&mut self, buffer_id: usize) {
        // Save current state to current buffer
        if let Some(current_buffer) = self.get_current_buffer_mut() {
            self.shadow_state.borrow().save_to_buffer(
                current_buffer,
                &self.viewport_manager.borrow()
            );
        }
        
        // Switch buffer
        self.set_current_buffer(buffer_id);
        
        // Restore state from new buffer
        if let Some(new_buffer) = self.get_current_buffer() {
            self.shadow_state.borrow_mut().restore_from_buffer(
                new_buffer,
                &mut self.viewport_manager.borrow_mut()
            );
        }
    }
}
```

## Implementation Phases

### Phase 1: Define Snapshot Structure
1. Create `BufferStateSnapshot` struct
2. Add `state_snapshot` field to Buffer
3. Implement getters/setters

### Phase 2: Implement Save Logic
1. Add `save_to_buffer` to ShadowStateManager
2. Collect state from all sources:
   - Shadow state itself
   - ViewportManager
   - AppStateContainer (search states)
   - DataView (column states)

### Phase 3: Implement Restore Logic
1. Add `restore_from_buffer` to ShadowStateManager
2. Distribute state to all targets:
   - Update shadow state
   - Update ViewportManager
   - Update AppStateContainer
   - Update DataView

### Phase 4: Wire Up Buffer Switching
1. Add buffer switching commands (e.g., Ctrl+Tab, :bnext, :bprev)
2. Implement switch_to_buffer in TUI
3. Test state preservation

## Benefits

1. **Perfect State Restoration**: Every aspect of the view is preserved
2. **Transparent to TUI**: TUI just calls switch_to_buffer()
3. **Centralized Logic**: State manager handles all complexity
4. **Debugging**: Can inspect saved snapshots
5. **Future: Undo/Redo**: Snapshots can be used for undo stack

## Considerations

### What State to Save?

**Always Save:**
- Mode
- Cursor position
- Viewport position
- Active filters
- Active searches
- Column visibility/order
- Sort state

**Maybe Save:**
- Undo history (could be large)
- Clipboard/yank buffer (might want global)
- Error messages (probably clear on switch)

**Don't Save:**
- Transient UI state (animations, tooltips)
- Debug overlays
- Performance metrics

### Memory Usage

Each snapshot might be ~1-2KB. With 100 buffers, that's only ~200KB - negligible.

### Snapshot Timing

Save snapshot when:
- Switching to another buffer
- Before executing a new query (save "Results" view)
- Optionally: Periodically for recovery

## Migration Path

Since we're already migrating to shadow state as the source of truth, we can:

1. First: Complete shadow state migration (current work)
2. Then: Add snapshot capability to shadow state
3. Finally: Implement buffer switching with state restoration

This means continuing our current path is the right approach - making shadow state authoritative prepares us for this snapshot system.

## Example User Experience

```
1. User runs query: SELECT * FROM users
   - Results shown, user navigates to row 50, column 3
   - Applies filter for "active" status
   - Sorts by created_date
   
2. User runs new query: SELECT * FROM orders  
   - State snapshot saved to buffer #1
   - New buffer #2 created
   - User browses orders
   
3. User switches back to buffer #1 (Ctrl+Tab)
   - State restored: Same row 50, column 3
   - Filter still active
   - Sort still applied
   - Feels like they never left!
```

## Next Steps

1. Continue current shadow state migration (make it authoritative)
2. Design BufferStateSnapshot struct with all needed fields
3. Implement save/restore methods
4. Add buffer switching commands
5. Test with multiple complex buffers