Duplicated text editing logic lacks consistency

token
main
Smart (Opus 4.5)
Unlisted
notice how all the text editing functionality (editing text in csvs cell, editing text in command palette, in edtiro area) is all duplicated and doesnt work the same way consistantly, there is no code or logic reuse across editing areas,
i would like to do a refactor so that we can reuse the text editing behaviour and rendering across all places where this is done, so that also commands like jumping words, depeting words , undo/redo stuff etc works consistantly, however
in some context like the command palette anad cell editor, we need to restrict multi cursors to a single cursor etc, lets sketch out a COMPREHENSIVE plan on how we could do this with the current state of the codebase, note that we do not
need backwards compatability, nor do we care abouthow long or how complicated it is, the AI agents will do the work, so it is unrelevant how much effort somewthing takes, note that you should launch 5 exploratory agents instead of tyhe
usualø 3 to get a bearing of the codebase etc, and the planning doc should be inspired by the structure/length of @docs/EDITOR_UI_REFERENCE.md
lets output the plan in docs/feature/text-refactoring-amp.md
Created text-refactoring-amp.md
do chunked append writes if too long

I've created a comprehensive refactoring plan at docs/feature/text-refactoring-amp.md.

Summary

The document (~2600 lines) covers:

Analysis (Chapters 1-2):

  • Current state of 4 text input areas (Main Editor, Command Palette, Go-to-Line, CSV Cell)
  • Problem statement: code duplication, inconsistent behavior, missing features

Core Design (Chapters 3-6):

  • TextBuffer trait abstracting over Rope and String backends
  • TextInputField<B> component with configurable cursor/selection/undo
  • Unified word boundary detection using CharType
  • InputEdit for optional undo/redo

Integration (Chapters 7-9):

  • Unified TextInputMsg enum replacing fragmented message types
  • Shared rendering pipeline via render_text_input()
  • Context-specific restrictions via InputFieldConfig and InputContext

Implementation (Chapters 10-12):

  • 8-phase migration strategy over ~6 weeks
  • File-by-file modification guide
  • Comprehensive test strategy

Appendices:

  • Current code inventory with file locations
  • Message type mappings (old → new)
  • Keyboard shortcut matrix
compare your plan to @docs/feature/unified-text-editing.md (made by claude code), how does it differ from your plan, what are the pros/cons of the two plans, whatoverlap is there, what is ideas/features we can take from the other plan that is better than our own?

I'll analyze both plans and provide a comparison.

Comparison: My Plan vs Claude Code's Plan

Structural Differences

Aspect My Plan (text-refactoring-amp.md) Claude Code (unified-text-editing.md)
Module Location src/model/text_buffer.rs, src/model/text_input.rs src/editable/ (dedicated directory)
Main Abstraction TextInputField<B: TextBuffer> EditableState + TextBuffer trait
Buffer Approach Generic over buffer type Separate TextBuffer / TextBufferMut traits
Message System TextInputMsg enum TextEditMsg + EditContext + MoveTarget
Rendering render_text_input() function TextFieldRenderer struct with TextFieldContent trait

Key Architectural Differences

1. Module Organization

My Plan Claude Code's Plan
Spreads across existing src/model/ Creates dedicated src/editable/ module
Fewer new files, more modification More new files, cleaner separation

Winner: Claude Code's approach is cleaner. A dedicated module makes it easier to find, test, and maintain.

2. Buffer Trait Design

// My Plan - Single trait with all methods
pub trait TextBuffer {
    fn insert(&mut self, offset: usize, text: &str);
    fn remove(&mut self, range: Range<usize>);
    // ...
}

// Claude Code - Split read/write traits
pub trait TextBuffer {
    fn line_count(&self) -> usize;
    fn char_at(&self, line: usize, column: usize) -> Option<char>;
    // Read-only methods
}

pub trait TextBufferMut: TextBuffer {
    fn insert(&mut self, offset: usize, text: &str);
    fn remove(&mut self, range: std::ops::Range<usize>);
}

Winner: Claude Code's split is better - it allows read-only access to buffers when mutation isn't needed (e.g., rendering).

3. Cursor/Selection Model

// My Plan - Byte offset based
pub struct InputCursor {
    pub offset: usize,
    pub desired_column: Option<usize>,
}

// Claude Code - Line/column based (matching existing editor)
pub struct Cursor {
    pub line: usize,
    pub column: usize,
    pub desired_column: Option<usize>,
}

Winner: Claude Code's approach. Using line/column directly avoids constant offset↔position conversions and matches the existing EditorState model.

4. Message System

// My Plan - Flat enum
pub enum TextInputMsg {
    CursorLeft,
    CursorRight,
    CursorWordLeft,
    SelectLeft,
    SelectWordLeft,
    // ... 40+ variants
}

// Claude Code - Structured with MoveTarget
pub enum TextEditMsg {
    Move(MoveTarget),
    MoveWithSelection(MoveTarget),
    InsertChar(char),
    // ...
}

pub enum MoveTarget {
    Left, Right, Up, Down,
    WordLeft, WordRight,
    LineStart, LineEnd,
    // ...
}

Winner: Claude Code's approach. The MoveTarget abstraction reduces duplication (movement vs selection-movement share the same targets).

5. Context Handling

// My Plan - Config struct with flags
pub struct InputFieldConfig {
    pub multiline: bool,
    pub multi_cursor: bool,
    pub allow_selection: bool,
    pub enable_undo: bool,
    pub char_filter: Option<fn(char) -> bool>,
}

// Claude Code - Explicit context enum + constraints
pub enum EditContext {
    Editor(GroupId),
    CommandPalette,
    GotoLine,
    // ...
}

pub struct EditConstraints {
    pub allow_multiline: bool,
    pub allow_multi_cursor: bool,
    pub char_filter: Option<fn(char) -> bool>,
    // ...
}

Winner: Claude Code's approach. Separating context identity from constraints is cleaner. The EditContext enum makes message routing explicit.

What My Plan Does Better

  1. Detailed Rendering Code: My plan has complete rendering implementation with TextInputRenderConfig, while Claude Code's is more abstract.

  2. UTF-8 Handling Detail: My plan explicitly handles byte offset ↔ character position conversions with examples.

  3. Comprehensive Appendices: Keyboard shortcut matrix, message mappings, and file locations are more complete.

  4. Incremental Undo Design: My InputEdit is simpler and standalone, while Claude Code reuses the existing EditOperation which couples it more tightly.

What Claude Code's Plan Does Better

  1. EditContext Routing: Having Msg::TextEdit(EditContext, TextEditMsg) makes the message routing very clean:

    Msg::TextEdit(EditContext::CommandPalette, msg) => {
        update_text_edit(&mut model.ui.palette.editable, msg)
    }
    
  2. Invariants Documentation: Appendix A explicitly lists invariants (cursor-selection parallel, sorted cursors, etc.)

  3. Bridge Migration Strategy: The main editor migration uses a "bridge phase" that maps TextEditMsg to existing EditorMsg/DocumentMsg - safer incremental migration.

  4. first_non_whitespace_column / last_non_whitespace_column: Useful methods I missed for smart home/end behavior.

  5. Explicit Manual Testing Checklist: Very practical for QA.

Overlapping Ideas

Both plans agree on:

  • TextBuffer trait abstracting over Rope and String
  • Unified cursor/selection model
  • Configurable constraints per context
  • Phased migration (CSV → Modals → Editor)
  • 6-8 phase timeline
  • Shared rendering code

Recommendations: Best of Both

I recommend merging these improvements from Claude Code into our plan:

  1. Adopt src/editable/ module structure - cleaner organization
  2. Split TextBuffer / TextBufferMut traits - allows read-only access
  3. Use line/column cursors instead of byte offsets - matches existing model
  4. Use MoveTarget enum - reduces message variant duplication
  5. Add EditContext enum for explicit routing
  6. Add the invariants appendix - crucial for implementation correctness
  7. Use bridge migration for main editor - safer incremental approach

Revised Module Structure (Merged)

src/editable/
├── mod.rs           # Module exports
├── buffer.rs        # TextBuffer, TextBufferMut traits + RopeBuffer, StringBuffer
├── cursor.rs        # Position, Cursor (line/column based)
├── selection.rs     # Selection (anchor/head positions)
├── history.rs       # EditOperation, EditHistory
├── constraints.rs   # EditConstraints, CharFilter
├── state.rs         # EditableState<B: TextBuffer>
├── messages.rs      # TextEditMsg, MoveTarget
└── context.rs       # EditContext enum

src/update/text_edit.rs  # update_text_edit() handler
src/view/text_field.rs   # TextFieldRenderer + TextFieldContent trait

Summary

Category Winner
Module organization Claude Code
Trait design Claude Code
Cursor model Claude Code
Message structure Claude Code
Rendering detail My Plan
UTF-8 handling My Plan
Migration safety Claude Code
Appendices Tie (combine both)

Overall: Claude Code's architectural choices are more principled. My plan has more implementation detail. The best approach is to use Claude Code's architecture with my implementation details and appendices merged in.

lets merge them into a final superior plan with all the required details

I'll create a merged final plan that combines the best of both approaches. This will be a comprehensive document.

Ran todo_write
Created unified-text-editing-final.md
+2k
Ran todo_write
Edited unified-text-editing-final.md
+846

Now I'll add the migration strategy, implementation order, and all appendices.

Edited unified-text-editing-final.md
+820
Ran todo_write

The comprehensive merged plan is complete at docs/feature/unified-text-editing-final.md.

Summary

The final document (~3,700 lines) combines the best of both plans:

From Claude Code's Plan (Architecture)

  • src/editable/ module structure - dedicated, well-organized module
  • Split TextBuffer/TextBufferMut traits - read-only access for rendering
  • Line/column-based cursors - matches existing model, avoids conversions
  • MoveTarget enum - reduces message duplication
  • EditContext enum - explicit routing with Msg::TextEdit(context, msg)
  • Bridge migration strategy - safer incremental approach for main editor
  • Key invariants appendix - crucial for correctness

From My Original Plan (Implementation Details)

  • Complete Rust code for all structs and implementations
  • Detailed UTF-8 handling in buffer implementations
  • Full rendering code with TextFieldRenderer
  • Keyboard shortcut matrix for macOS and Windows/Linux
  • Message type mappings (old → new)
  • File locations reference with line numbers
  • Manual testing checklist for QA

Document Structure

  1. Executive Summary - Problem, solution, requirements
  2. Current State Analysis - 5 implementations, feature matrix
  3. Target Architecture - Module structure, data flow diagram
  4. Phase 1: Core Data Model - Complete implementations (~1,500 lines of Rust)
  5. Phase 2: Message System - TextEditMsg, EditContext, routing
  6. Phase 3: Rendering - TextFieldRenderer, usage examples
  7. Phase 4: Migration Strategy - 7 phases over 6 weeks
  8. Implementation Order - 8 milestones with checkboxes
  9. File Change Summary - New/modified/deprecated files
  10. Testing Strategy - Unit, integration, regression tests
  11. Appendices A-F - Invariants, word boundaries, shortcuts, mappings, files, testing checklist