text-document-editing 0.0.10

Undoable text editing use cases for text-document
Documentation
[![crates.io](https://img.shields.io/crates/v/text-document?style=flat-square&logo=rust)](https://crates.io/crates/text-document)
[![API](https://docs.rs/text-document/badge.svg)](https://docs.rs/text-document)
![quality](https://img.shields.io/github/actions/workflow/status/jacquetc/text-document/ci.yml)
[![codecov](https://codecov.io/gh/jacquetc/text-document/branch/main/graph/badge.svg?token=S4M513A2XR)](https://codecov.io/gh/jacquetc/text-document)
[![license](https://img.shields.io/badge/license-Apache--2.0_OR_MIT-blue?style=flat-square)](#license)

# text-document

A rich text document model for Rust, inspired by Qt's QTextDocument/QTextCursor API.

Built on [Qleany](https://github.com/jacquetc/qleany)-generated Clean Architecture with redb (embedded ACID database), full undo/redo, and multi-cursor support.

## Features

- **Rich text model**: Frames, Blocks, InlineElements with `InlineContent::Text | Image`
- **Multi-cursor editing**: Qt-style cursors with automatic position adjustment
- **Full undo/redo**: Snapshot-based, with composite grouping (`begin_edit_block` / `end_edit_block`)
- **Import/Export**: Plain text, Markdown, HTML, LaTeX, DOCX
- **Search**: Find, find all, regex, replace (undoable)
- **Formatting**: Character format (`bold`, `italic`, `underline`, ...), block format (`alignment`, `heading_level`, ...), frame format
- **Event system**: Callback-based (`on_change`) and polling-based (`poll_events`)
- **Thread-safe**: `Send + Sync` throughout, `Arc<Mutex<...>>` interior mutability
- **Resources**: Image and stylesheet storage with base64 encoding

## Quick start

```rust
use text_document::{TextDocument, MoveMode, MoveOperation};

let doc = TextDocument::new();
doc.set_plain_text("Hello world").unwrap();

// Cursor-based editing
let cursor = doc.cursor();
cursor.move_position(MoveOperation::EndOfWord, MoveMode::KeepAnchor, 1);
cursor.insert_text("Goodbye").unwrap(); // replaces "Hello"

// Multiple cursors
let c1 = doc.cursor();
let c2 = doc.cursor_at(5);
c1.insert_text("A").unwrap();
// c2's position is automatically adjusted

// Undo
doc.undo().unwrap();

// Search
use text_document::FindOptions;
let matches = doc.find_all("world", &FindOptions::default()).unwrap();

// Export
let html = doc.to_html().unwrap();
let markdown = doc.to_markdown().unwrap();
```

## CLI

A command-line tool for format conversion and text processing:

```bash
# Convert between formats (detected by file extension)
text-document convert README.md output.html
text-document convert article.html article.tex

# Show document statistics
text-document stats manuscript.md

# Find text (grep-like output)
text-document find paper.md "TODO" --case-sensitive

# Find and replace
text-document replace draft.md "colour" "color" --output fixed.md

# Print to stdout in a different format
text-document cat notes.html --format plain
```

Supported formats:

| Extension | Import | Export |
|-----------|--------|--------|
| `.txt` | yes | yes |
| `.md` | yes | yes |
| `.html`/`.htm` | yes | yes |
| `.tex`/`.latex` | - | yes |
| `.docx` | - | yes |

## Document structure

```
Root
 +-- Document
     +-- Frame (root frame)
     |   +-- Block
     |   |   +-- InlineElement (Text "Hello ")
     |   |   +-- InlineElement (Text "world" with bold)
     |   |   +-- InlineElement (Image { name, width, height })
     |   +-- Block
     |       +-- InlineElement (Text "Second paragraph")
     +-- List (style: Decimal, indent: 1)
     +-- Resource (image data, stylesheets)
```

- **Frame**: contains Blocks and child Frames. `child_order` interleaves them.
- **Block**: a paragraph. Contains InlineElements. Has `document_position` for O(log n) lookup.
- **InlineElement**: either `Text(String)`, `Image { name, width, height, quality }`, or `Empty`.
- **List**: styling for list items (Disc, Decimal, LowerAlpha, ...). Blocks reference lists via weak relationship.
- **Resource**: binary data (images, stylesheets) stored as base64.

All format fields are `Option<T>` — `None` means "inherit from parent/default", `Some(value)` means "explicitly set".

## Architecture

Generated by [Qleany](https://github.com/jacquetc/qleany) v1.5.1, following Clean Architecture with Package by Feature (Vertical Slice):

```
crates/
+-- public_api/       # TextDocument, TextCursor, DocumentEvent (the public crate)
+-- cli/              # Command-line tool
+-- frontend/         # AppContext, commands, event hub client
+-- common/           # Entities, database (redb), events, undo/redo, repositories
+-- macros/           # #[uow_action] proc macro
+-- direct_access/    # Entity CRUD controllers + DTOs
+-- document_editing/ # 11 use cases (insert, delete, block, image, frame, list, fragment, ...)
+-- document_formatting/ # 4 use cases (set/merge text format, block format, frame format)
+-- document_io/      # 8 use cases (import/export plain text, markdown, HTML, LaTeX, DOCX)
+-- document_search/  # 3 use cases (find, find_all, replace)
+-- document_inspection/ # 4 use cases (stats, text at position, block at position, extract fragment)
+-- test_harness/       # Shared test setup utilities
```

Data flow: `TextDocument / TextCursor -> frontend::commands -> controllers -> use cases -> UoW -> repositories -> redb`

## License

Licensed under either of

 * Apache License, Version 2.0
   ([LICENSE-APACHE]LICENSE-APACHE or http://www.apache.org/licenses/LICENSE-2.0)
 * MIT license
   ([LICENSE-MIT]LICENSE-MIT or http://opensource.org/licenses/MIT)

at your option.

## Contribution

Unless you explicitly state otherwise, any contribution intentionally submitted
for inclusion in the work by you, as defined in the Apache-2.0 license, shall be
dual licensed as above, without any additional terms or conditions.