/* * Ricochet - A minimalist CLI web browser for fast, distraction-free navigation. * Copyright (C) 2026 Richard Qin * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . */ #pragma once #include "paint.hh" #include "text.hh" #include #include namespace ricochet::tui::detail { enum class EditorKind : std::uint8_t { SingleLine, MultiLine, Password }; struct EditorOptions { EditorKind kind { EditorKind::SingleLine }; // The owner supplies history; password and multiline editors never request it. bool history { false }; }; enum class EditorCommand : std::uint8_t { Left, Right, Up, Down, LineStart, LineEnd, PreviousWord, NextWord, Backspace, Delete, KillToStart, KillToEnd, KillWord, Yank, BeginSelection, SelectAll, CancelSelection, Clear, Newline, Undo, Redo }; // Enter/Tab/Escape are context decisions, never implicit submit/finish operations. // The owner may translate Enter into Newline for a multiline field. // Unhandled keys stay with the owning editor context, never fall through to Browse. enum class EditorIntent : std::uint8_t { Consumed, Unhandled, Enter, Tab, BackTab, Escape, Cancel, Menu, Redraw, HistoryPrevious, HistoryNext }; // One editing session. Owners recreate it when changing fields; its text, undo // history and kill buffer are never shared with another editor or a view snapshot. class TextEditor { public: TextEditor( std::string value, EditorOptions options, const TextService& text, ScreenSize viewport ); TextEditor( const TextEditor& ) = delete; TextEditor& operator=( const TextEditor& ) = delete; TextEditor( TextEditor&& ) = delete; TextEditor& operator=( TextEditor&& ) = delete; ~TextEditor() = default; // Business value for the owning field; rendering and copying use the methods below. const std::string& value() const; std::uint64_t revision() const; std::size_t cursor() const; ByteRange selection() const; std::optional copy_selection(); std::optional prefix_deadline() const; void resize( ScreenSize viewport ); void insert( std::u32string_view input ); // Host history replacement is one undoable edit, not a new editing session. void replace_value( std::string value ); void execute( EditorCommand command, bool extend = false ); EditorIntent handle( const InputEvent& event, std::chrono::steady_clock::time_point now = std::chrono::steady_clock::now() ); // Region-local mouse positioning, with extend=true while selecting/dragging. void place( CellPoint point, bool extend ); void draw( Frame& frame, CellPoint origin ) const; private: struct State { std::string value {}; std::size_t cursor { 0 }; std::optional anchor {}; bool selecting { false }; }; struct Point { std::size_t row { 0 }; std::int64_t column { 0 }; }; struct Glyph { Point point {}; int columns { 0 }; }; std::string normalize( std::string_view value ) const; std::size_t unit_index() const; std::size_t boundary( std::size_t index ) const; std::size_t line_start() const; std::size_t line_end() const; std::size_t nearest( Point point ) const; void analyze(); void layout(); void reveal(); void move( std::size_t destination, bool extend, bool preserve_column ); void replace( ByteRange range, std::string_view replacement ); void kill( ByteRange range ); void undo( bool redo ); EditorIntent key( const Key& key, std::chrono::steady_clock::time_point now ); EditorOptions options_; const TextService& text_; ScreenSize viewport_; State state_ {}; TextAnalysis analysis_ {}; std::vector glyphs_ {}; std::vector carets_ {}; std::size_t top_row_ { 0 }; std::int64_t left_column_ { 0 }; std::int64_t desired_column_ { 0 }; std::vector past_ {}; std::vector future_ {}; std::string killed_ {}; bool killing_ { false }; std::uint64_t revision_ { 0 }; std::optional prefix_ {}; }; } // namespace ricochet::tui::detail