Skip to content

Chapter 15: Editor and Text Views

Previous: Chapter 14 — Palettes and Color Selection


Overview

This chapter covers Turbo Vision's text editing components, from simple terminal views to full-featured editors with syntax highlighting, search/replace, and file operations.

The Terminal View

The Terminal type provides a write-only scrolling view for displaying text output. It's most useful for debugging, logging, or monitoring text streams. Terminal views are defined in src/terminal/mod.rs.

Basic Terminal Usage

Terminal objects manage text display with automatic buffering and rendering. The terminal maintains an internal buffer of cells (character + color attribute pairs) and efficiently updates only the changed portions of the screen.

Key Terminal Features

  • Character Cell Buffer: Each position stores a character and color attributes
  • Efficient Rendering: Only changed cells are redrawn
  • Event Handling: Processes keyboard and mouse input
  • Clipping Regions: Supports nested clipping for view hierarchies

Terminal Example

use turbo_vision::app::Application;
use turbo_vision::terminal::Terminal;

fn main() -> std::io::Result<()> {
    let mut app = Application::new()?;
    // Terminal is created and managed by Application
    // Views write to it via the draw() method
    app.run()
}

The Editor Object

The Editor struct in src/views/editor.rs implements a powerful multi-line text editor with:

  • Unlimited undo/redo (configurable history size)
  • Clipboard operations (cut, copy, paste)
  • Text selection with keyboard and mouse
  • Find and replace with options
  • Auto-indent and tab handling
  • Insert/overwrite modes
  • Syntax highlighting support
  • Scroll bars and status indicators

How the Editor Works

Unlike traditional fixed-size buffers, the Rust editor stores text as a Vec<String> where each string represents one line. This approach:

  • Handles files of any size (limited only by memory)
  • Supports UTF-8 text natively
  • Simplifies line-based operations
  • Integrates naturally with Rust's string handling

Editor Buffer Structure

The editor maintains these key fields (see src/views/editor.rs:79-103):

pub struct Editor {
    lines: Vec<String>,           // Text content, one line per entry
    cursor: Point,                 // Current cursor position (x=col, y=line)
    delta: Point,                  // Scroll offset
    selection_start: Option<Point>, // Selection anchor
    undo_stack: Vec<EditAction>,   // Undo history
    redo_stack: Vec<EditAction>,   // Redo history
    insert_mode: bool,             // Insert vs overwrite
    auto_indent: bool,             // Auto-indent on newline
    // ... other fields
}

Creating an Editor

use turbo_vision::views::editor::Editor;
use turbo_vision::core::geometry::Rect;

// Basic editor
let editor = Editor::new(Rect::new(0, 0, 80, 24));

// Editor with scrollbars and indicator
let editor = Editor::new(Rect::new(0, 0, 80, 24))
    .with_scrollbars_and_indicator();

Setting Editor Options

// Configure editor behavior
editor.set_tab_size(4);
editor.set_auto_indent(true);
editor.set_read_only(false);

// Enable syntax highlighting
use turbo_vision::views::syntax::RustHighlighter;
editor.set_highlighter(Box::new(RustHighlighter::new()));

Undo and Redo

The editor maintains full undo/redo support through an action stack (see src/views/editor.rs:53-76):

// Undo last edit
editor.undo();

// Redo last undone edit
editor.redo();

Edit Actions: Each editing operation (insert character, delete text, etc.) is recorded as an EditAction enum variant. The undo system:

  • Stores up to MAX_UNDO_HISTORY actions (default: 100)
  • Clears the redo stack when a new edit is made
  • Supports inverting any action for undo

Text Selection

Selection is managed through selection_start and cursor points:

// Select all text
editor.select_all();

// Get selected text
if let Some(text) = editor.get_selection() {
    println!("Selected: {}", text);
}

// Clear selection
editor.selection_start = None;

Stream vs. Block Selection

The editor supports two selection shapes, tracked by SelectionMode:

  • Stream (the default): a continuous character range.
  • Block (rectangular / column): a column band [min_x, max_x) applied to every row in the range. Copying a block yields each row's column slice joined by newlines; deleting a block removes the band from every row and undoes in a single step.

Both shapes are extended the same way, with Shift+arrows or a mouse drag. Which one you get is decided by the global block-edit mode, not by a keyboard modifier, because terminals disagree on whether they deliver Alt/Option with cursor keys and mouse drags. An application reads and writes the flag through the Application API:

app.set_block_edit_mode(true);      // selections become rectangular
let on = app.block_edit_mode();     // query
app.toggle_block_edit_mode();       // flip, returns the new value

Application also handles the CM_TOGGLE_BLOCK_MODE command, so a menu or status-line item can flip the mode with no extra code. MenuItem::flag makes a checkable item whose check mark tracks the flag on every draw:

MenuItem::flag(
    "Bloc~k~ mode",
    CM_TOGGLE_BLOCK_MODE,
    0,
    HC_EDITOR,
    turbo_vision::core::state::block_edit_mode,
)

For a persistent on-screen hint, StatusLine::set_right_indicator (and its MenuBar twin) draws a right-aligned marker at the far end of the bar, re-queried on every draw:

status_line.set_right_indicator(|| {
    turbo_vision::core::state::block_edit_mode().then(|| "▭ Block".to_string())
});

The mode is fixed at the moment a selection starts, so toggling block-edit mode does not reshape a selection already in progress; a plain movement or click clears the selection and the next one picks up the current mode.

Clipboard Operations

The editor integrates with the system clipboard via src/core/clipboard.rs:

use turbo_vision::core::clipboard;

// Copy selection to clipboard
if let Some(selection) = editor.get_selection() {
    clipboard::set_clipboard(&selection);
}

// Paste from clipboard
let text = clipboard::get_clipboard();
if !text.is_empty() {
    editor.insert_text(&text);
}

Keyboard Shortcuts: - Ctrl+C: Copy - Ctrl+X: Cut - Ctrl+V: Paste - Ctrl+A: Select All - Ctrl+Z: Undo - Ctrl+Y: Redo

Search and Replace

The editor provides comprehensive search and replace functionality matching the original Turbo Vision API (see src/views/editor.rs:296-470).

Search Options

use turbo_vision::views::editor::SearchOptions;

let options = SearchOptions {
    case_sensitive: false,
    whole_words_only: false,
    backwards: false,
};

Finding Text

// Find first occurrence
if let Some(pos) = editor.find("search_term", options) {
    println!("Found at {:?}", pos);
}

// Find next occurrence
if let Some(pos) = editor.find_next() {
    println!("Next match at {:?}", pos);
}

The search automatically: - Wraps around to the beginning when reaching the end - Highlights matches by setting selection - Scrolls the view to make matches visible

Replace Operations

// Replace current selection
if editor.replace_selection("new_text") {
    println!("Replaced");
}

// Replace next occurrence
if editor.replace_next("find", "replace", options) {
    println!("Replaced next");
}

// Replace all occurrences
let count = editor.replace_all("find", "replace", options);
println!("Replaced {} occurrences", count);

Scroll Bars and Indicators

Editors can have optional scrollbars and status indicators:

// Scrollbars automatically track:
// - Content size (lines and max line length)
// - Viewport size (editor dimensions)
// - Current position (delta/scroll offset)

// Indicators show:
// - Current line and column
// - Modified flag
// - Insert/overwrite mode

The editor automatically updates scrollbars and indicators when: - Text is edited - Cursor moves - View is resized

Syntax Highlighting

The editor supports pluggable syntax highlighting via the SyntaxHighlighter trait (see src/views/syntax.rs).

Using Syntax Highlighting

use turbo_vision::views::syntax::RustHighlighter;

let mut editor = Editor::new(bounds)
    .with_scrollbars_and_indicator();

// Enable Rust syntax highlighting
editor.set_highlighter(Box::new(RustHighlighter::new()));

// Disable highlighting
editor.clear_highlighter();

Implementing Custom Highlighters

Create a custom highlighter by implementing the SyntaxHighlighter trait:

use turbo_vision::views::syntax::{SyntaxHighlighter, Token, TokenType};

struct MyHighlighter;

impl SyntaxHighlighter for MyHighlighter {
    fn highlight_line(&self, line: &str, _line_num: usize) -> Vec<Token> {
        // Return tokens with positions and types
        // The editor will color them appropriately
        vec![
            Token {
                start: 0,
                end: line.len(),
                token_type: TokenType::Text,
            }
        ]
    }
}

The Memo Control

The Memo struct (src/views/memo.rs) is a simplified editor designed for use in dialog boxes. It provides basic text editing without undo/redo or advanced features.

Memo vs Editor

Memo is designed for: - Simple text input in dialogs - Limited editing needs - Smaller memory footprint - Integration with data transfer

Editor is designed for: - Full-featured text editing - Undo/redo support - Syntax highlighting - File editing

Using a Memo

use turbo_vision::views::memo::Memo;

// Create memo with optional scrollbars
let mut memo = Memo::new(Rect::new(5, 3, 45, 10))
    .with_scrollbars(true);

// Set properties
memo.set_max_length(Some(500));  // Limit characters
memo.set_read_only(false);
memo.set_tab_size(4);

// Get/set text
memo.set_text("Initial content");
let text = memo.get_text();

File Editors

The FileEditor struct (src/views/file_editor.rs) extends Editor with file management:

  • File name tracking
  • Load/save operations
  • Modified flag handling
  • Save prompt on close

Using File Editor

use turbo_vision::views::file_editor::FileEditor;
use std::path::PathBuf;

// Create file editor
let mut editor = FileEditor::new(Rect::new(0, 0, 80, 24));

// Load a file
editor.load_file(PathBuf::from("example.rs"))?;

// Make edits...
editor.editor_mut().set_text("Modified content");

// Save
if editor.is_modified() {
    editor.save()?;  // Save to current file

    // Or save as new file
    editor.save_as(PathBuf::from("new_file.rs"))?;
}

File Name and Title

// Get filename
if let Some(path) = editor.filename() {
    println!("Editing: {:?}", path);
}

// Get display title ("Untitled" if new file)
let title = editor.get_title();

Save Confirmation

The FileEditor::valid() method prompts for save confirmation when closing with unsaved changes:

use turbo_vision::core::command::CM_CLOSE;

// Check if close is allowed (prompts if modified)
if editor.valid(app, CM_CLOSE) {
    // User chose to save, discard, or already saved
    // Safe to close
} else {
    // User cancelled
}

Editor Key Bindings

The editor supports these keyboard shortcuts (see src/views/editor.rs:1174-1305):

  • Arrow keys: Move cursor
  • Home: Move to line start
  • End: Move to line end
  • PgUp/PgDn: Scroll page up/down
  • Shift + navigation: Extend selection

Editing

  • Enter: Insert newline (with auto-indent if enabled)
  • Backspace: Delete character before cursor
  • Delete: Delete character at cursor
  • Tab: Insert tab (spaces)
  • Insert: Toggle insert/overwrite mode (if supported)

Clipboard

  • Ctrl+X: Cut selection
  • Ctrl+C: Copy selection
  • Ctrl+V: Paste
  • Ctrl+A: Select all

Undo/Redo

  • Ctrl+Z: Undo last action
  • Ctrl+Y: Redo last undone action

Editor Configuration

Read-Only Mode

editor.set_read_only(true);
// User can view and select but not edit

Tab Size

// Set number of spaces for tab
editor.set_tab_size(2);  // 2 spaces
editor.set_tab_size(4);  // 4 spaces (default)

Auto-Indent

// Enable auto-indent on newline
editor.set_auto_indent(true);
// Pressing Enter indents to match previous line

Modified Flag

// Check if modified
if editor.is_modified() {
    println!("File has unsaved changes");
}

// Clear flag (after save)
editor.clear_modified();

Drawing and Rendering

Editors implement the View trait and handle drawing automatically (see src/views/editor.rs:1030-1172):

impl View for Editor {
    fn draw(&mut self, terminal: &mut Terminal) {
        // Renders visible portion of text
        // Applies syntax highlighting if enabled
        // Draws selection highlight
        // Shows cursor
        // Updates scrollbars and indicator
    }
}

The rendering process: 1. Calculates visible text area (content area minus scrollbars/indicator) 2. Renders visible lines with syntax highlighting 3. Applies selection highlighting 4. Draws cursor if focused 5. Updates child views (scrollbars, indicator)

Complete Example

Here's a complete example combining all editor features:

use turbo_vision::app::Application;
use turbo_vision::views::{
    window::Window,
    editor::Editor,
    syntax::RustHighlighter,
    view::View,
};
use turbo_vision::core::geometry::Rect;

fn main() -> std::io::Result<()> {
    let mut app = Application::new()?;

    // Create window using the builder pattern
    let mut window = WindowBuilder::new()
        .bounds(Rect::new(5, 2, 75, 22))
        .title("Editor Demo")
        .build();

    // Create editor with all features
    let mut editor = Editor::new(Rect::new(1, 1, 68, 18))
        .with_scrollbars_and_indicator();

    // Configure editor
    editor.set_tab_size(4);
    editor.set_auto_indent(true);
    editor.set_highlighter(Box::new(RustHighlighter::new()));

    // Set initial content
    editor.set_text(
        "fn main() {\n\
         \    println!(\"Hello, Turbo Vision!\");\n\
         }\n"
    );

    // Add to window
    window.add(Box::new(editor));

    // Show window
    app.insert_window(Box::new(window));

    // Run application
    app.run()
}

See the examples/editor_demo.rs file for a comprehensive demonstration of all editor features including search, replace, file operations, and syntax highlighting.

Summary

Turbo Vision provides a complete hierarchy of text editing components:

  • Terminal: Low-level display and event handling
  • Memo: Simple text input for dialogs
  • Editor: Full-featured text editor with undo/redo, search/replace, and syntax highlighting
  • FileEditor: Editor with file management and save prompts

All components: - Support UTF-8 text natively - Integrate with the clipboard - Handle keyboard and mouse input - Work within Turbo Vision's view hierarchy - Follow Rust ownership and borrowing rules

For file editing, use FileEditor. For text input in dialogs, use Memo. For custom text display, work directly with Terminal and the drawing system.


Next: Chapter 16 — Collections and Streams