Turbo Vision Rust Implementation: Serialization, Resources, and Persistence¶
Executive Summary¶
The Rust implementation of Turbo Vision does NOT use any serialization frameworks like serde. Instead, it provides:
1. Resource definitions through Rust structures (Menu, MenuItem, StatusDef, StatusItem)
2. File I/O operations for editors using standard Rust std::fs
3. In-memory state management with no built-in persistence layer
4. History management for input fields (similar to Borland's THistory system)
1. Resource Files and Data Structures¶
1.1 Menu Resources (src/core/menu_data.rs)¶
Location: /Users/enzo/Code/turbo-vision/src/core/menu_data.rs (356 lines)
The menu system defines resources declaratively using Rust enums and structs:
#[derive(Clone, Debug)]
pub enum MenuItem {
Regular {
text: String,
command: CommandId,
key_code: KeyCode,
help_ctx: u16,
enabled: bool,
shortcut: Option<String>,
},
SubMenu {
text: String,
key_code: KeyCode,
help_ctx: u16,
menu: Menu,
},
Separator,
}
pub struct Menu {
pub items: Vec<MenuItem>,
pub default_index: Option<usize>,
}
Key Features: - Enum-based design for type-safe menu items - Support for regular items, submenus, and separators - Keyboard shortcuts and help context - Default item tracking - Builder pattern for fluent API (MenuBuilder)
Example Usage (from examples):
let file_menu = Menu::from_items(vec![
MenuItem::new("~O~pen", CM_OPEN, KB_F3, 0),
MenuItem::new("~S~ave", CM_SAVE, KB_F2, 0),
MenuItem::separator(),
MenuItem::new("E~x~it", CM_QUIT, KB_ALT_X, 0),
]);
1.2 Status Line Resources (src/core/status_data.rs)¶
Location: /Users/enzo/Code/turbo-vision/src/core/status_data.rs (268 lines)
Status line defines resources for the bottom line of the application:
#[derive(Clone, Debug)]
pub struct StatusItem {
pub text: String,
pub key_code: KeyCode,
pub command: CommandId,
}
#[derive(Clone, Debug)]
pub struct StatusDef {
pub min: u16, // Min command range
pub max: u16, // Max command range
pub items: Vec<StatusItem>,
}
#[derive(Clone, Debug)]
pub struct StatusLine {
pub defs: Vec<StatusDef>,
}
Key Features: - Command-range based definitions (different items for different command contexts) - Support for help text and keyboard shortcuts - Multiple definitions can apply to different command ranges - Builder pattern (StatusLineBuilder)
Example Usage:
let status = StatusLineBuilder::new()
.add_def(0, 0xFFFF, vec![
StatusItem::new("~F1~ Help", KB_F1, CM_HELP),
StatusItem::new("~Alt+X~ Exit", KB_ALT_X, CM_QUIT),
])
.build();
2. File Serialization and I/O¶
2.1 Editor File Operations (src/views/editor.rs)¶
Location: /Users/enzo/Code/turbo-vision/src/views/editor.rs
The Editor provides basic file I/O using standard Rust file operations:
pub fn load_file(&mut self, path: &str) -> std::io::Result<()> {
let content = std::fs::read_to_string(path)?;
self.set_text(&content);
self.filename = Some(path.to_string());
self.modified = false;
self.undo_stack.clear();
self.redo_stack.clear();
self.update_indicator();
Ok(())
}
pub fn save_as(&mut self, path: &str) -> std::io::Result<()> {
let content = self.get_text();
std::fs::write(path, content)?;
self.filename = Some(path.to_string());
self.modified = false;
self.update_indicator();
Ok(())
}
Key Features: - Plain text file reading/writing - Modified flag tracking - Filename tracking - Undo/redo stack management - Works with UTF-8 text
Dependencies: Uses only std::fs::read_to_string() and std::fs::write()
2.2 FileEditor Wrapper (src/views/file_editor.rs)¶
Location: /Users/enzo/Code/turbo-vision/src/views/file_editor.rs
Extends Editor with file-specific features:
pub struct FileEditor {
editor: Editor,
filename: Option<PathBuf>,
}
pub fn load_file(&mut self, path: PathBuf) -> std::io::Result<()> {
self.editor.load_file(path.to_str().unwrap())?;
self.filename = Some(path);
Ok(())
}
pub fn save(&mut self) -> std::io::Result<bool> {
if let Some(path) = self.filename.clone() {
self.editor.save_file()?;
Ok(true)
} else {
Ok(false) // Need to call save_as
}
}
2.3 EditWindow Wrapper (src/views/edit_window.rs)¶
Location: /Users/enzo/Code/turbo-vision/src/views/edit_window.rs
High-level window for editing:
pub fn load_file(&mut self, path: &str) -> std::io::Result<()> {
self.editor.load_file(path)
}
pub fn save_file(&mut self) -> std::io::Result<()> {
self.editor.save_file()
}
pub fn save_as(&mut self, path: &str) -> std::io::Result<()> {
self.editor.save_as(path)
}
2.4 ANSI Dump Utilities (src/core/ansi_dump.rs)¶
Location: /Users/enzo/Code/turbo-vision/src/core/ansi_dump.rs (80+ lines)
Specialized file I/O for debugging:
pub fn dump_buffer_to_file(
buffer: &[Vec<Cell>],
width: usize,
height: usize,
path: &str,
) -> io::Result<()> {
let mut file = File::create(path)?;
// ... writes ANSI escape codes to file
}
Purpose: Dump terminal buffers to ANSI text files for debugging
- Converts TvColor to RGB values
- Writes ANSI escape sequences
- Viewable with cat or text editors supporting ANSI codes
3. Application State Management¶
3.1 Application Structure (src/app/application.rs)¶
Location: /Users/enzo/Code/turbo-vision/src/app/application.rs (310 lines)
The Application struct holds the desktop state:
pub struct Application {
pub terminal: Terminal,
pub menu_bar: Option<MenuBar>,
pub status_line: Option<StatusLine>,
pub desktop: Desktop,
pub running: bool,
needs_redraw: bool,
}
State Management: - Menu bar state (open/closed dropdowns) - Status line state (selected item, hint text) - Desktop state (window list, z-order) - Running flag for the main loop
No Built-in Serialization: The Application struct does NOT serialize. State is held in memory during runtime.
3.2 Desktop State (src/views/desktop.rs)¶
Location: /Users/enzo/Code/turbo-vision/src/views/desktop.rs (260 lines)
Methods for State Management:
- add() - Add window to desktop
- child_count() - Get number of windows
- child_at() - Access window by index
- remove_child() - Remove window
- remove_closed_windows() - Cleanup marked for deletion
- handle_moved_windows() - Track window movement
Window Z-order:
- Windows are stored in order (last = topmost)
- bring_to_front() brings window to top
- Modal windows capture all events
3.3 History Management (src/core/history.rs)¶
Location: /Users/enzo/Code/turbo-vision/src/core/history.rs (279 lines)
In-memory history for input fields (similar to Borland's THistory):
pub struct HistoryList {
items: Vec<String>,
max_items: usize,
}
pub struct HistoryManager;
impl HistoryManager {
pub fn add(history_id: u16, item: String) { ... }
pub fn get_list(history_id: u16) -> Vec<String> { ... }
pub fn has_history(history_id: u16) -> bool { ... }
pub fn count(history_id: u16) -> usize { ... }
pub fn clear(history_id: u16) { ... }
pub fn clear_all() { ... }
}
Features: - Global singleton using OnceLock - Per-ID history lists (e.g., separate history for file path input) - Max items limit (default 20) - Most recent items at front - Thread-safe (uses Mutex)
No Persistence: - History is cleared when application exits - Not saved to disk in current implementation
4. Serialization Frameworks¶
4.1 What's NOT Used¶
The project does NOT include: - serde - No serde crate in Cargo.toml - ron - No RON (Rusty Object Notation) - toml - No TOML support - json - No JSON support - bincode - No binary serialization - protobuf - No Protocol Buffers
4.2 Cargo.toml Dependencies¶
Location: /Users/enzo/Code/turbo-vision/Cargo.toml (44 lines)
[dependencies]
crossterm = "0.27"
unicode-width = "0.1"
arboard = "3.3"
[dev-dependencies]
tempfile = "3.8"
Observations:
- crossterm - Terminal abstraction
- unicode-width - Text width calculation
- arboard - Clipboard access
- tempfile - Test fixture file creation
- No serialization crates
5. Demo Application: Rust Editor (demo/rust_editor.rs)¶
Location: /Users/enzo/Code/turbo-vision/demo/rust_editor.rs (17915 bytes)
Shows practical usage of menu/status resources and file operations:
struct EditorState {
filename: Option<PathBuf>,
}
// Menu definition
let file_menu = Menu::from_items(vec![
MenuItem::new("~N~ew", CM_NEW, 0, 0),
MenuItem::new("~O~pen", CM_OPEN, KB_F3, 0),
MenuItem::new("~S~ave", CM_SAVE, KB_F2, 0),
MenuItem::new("Save ~A~s", CMD_SAVE_AS, KB_ALT_S, 0),
MenuItem::separator(),
MenuItem::new("E~x~it", CM_QUIT, KB_ALT_X, 0),
]);
// Status definition
let status_line = StatusLine::new(
Rect::new(0, height as i16 - 1, width as i16, height as i16),
vec![
StatusItem::new("~F10~ Menu", KB_F10, 0),
StatusItem::new("~Ctrl+S~ Save", 0, CM_SAVE),
StatusItem::new("~Ctrl+F~ Find", 0, CMD_SEARCH),
],
);
// File operations
fn handle_open_file(app: &mut Application, editor: &mut Editor) -> bool {
let dialog = FileDialog::new(/* ... */);
let cmd = app.exec_view(Box::new(dialog));
if cmd == CM_OK {
if let Some(path) = selected_path {
return editor.load_file(&path).is_ok();
}
}
false
}
fn handle_save_file(app: &mut Application, editor: &mut Editor, state: &mut EditorState) {
if let Some(path) = &state.filename {
editor.save_as(path.to_str().unwrap()).ok();
} else {
// Show save dialog
}
}
6. State Persistence Patterns¶
6.1 Application-Level State (Not Persisted)¶
The demo shows application state is transient:
struct EditorState {
filename: Option<PathBuf>,
}
// In main loop:
if event.what == EventType::Command && event.command == CM_CLOSE {
// Prompt user to save
if prompt_save_if_dirty(&mut app, &mut editor_state, true) {
// Allow close
app.desktop.remove_child(0);
editor_state = EditorState::new(); // Reset state
}
}
Characteristics: - Window list rebuilt on startup - Editor state reset on new/close - No application.json or .config files - No desktop save/restore
6.2 File-Based Persistence¶
Only individual files are saved:
// Save current editor content to file
pub fn save_as(&mut self, path: &str) -> std::io::Result<()> {
let content = self.get_text();
std::fs::write(path, content)?; // Direct file write
self.filename = Some(path.to_string());
self.modified = false;
Ok(())
}
No Configuration Files: - No ~/.turbo-vision/ directory - No settings.toml - No window positions/sizes saved - No recent files list - No user preferences persisted
7. Command Architecture¶
7.1 Command Set (src/core/command_set.rs)¶
Location: /Users/enzo/Code/turbo-vision/src/core/command_set.rs
Commands are defined as constants:
pub const CM_QUIT: CommandId = 3;
pub const CM_OPEN: CommandId = 20;
pub const CM_SAVE: CommandId = 21;
pub const CM_CANCEL: CommandId = 104;
pub const CM_CLOSE: CommandId = 202;
pub const CM_NEW: CommandId = 300;
// ... more command IDs
No Serialization: - Commands are just u16 constants - Menu/status items reference commands directly - No serialization needed
8. Text Editing Features (Non-Persisted)¶
8.1 Undo/Redo Stack¶
Location: /Users/enzo/Code/turbo-vision/src/views/editor.rs
#[derive(Clone, Debug)]
enum EditAction {
InsertChar { pos: Point, ch: char },
DeleteChar { pos: Point, ch: char },
InsertText { pos: Point, text: String },
DeleteText { pos: Point, text: String },
InsertLine { line: usize, text: String },
DeleteLine { line: usize, text: String },
}
pub struct Editor {
undo_stack: Vec<EditAction>,
redo_stack: Vec<EditAction>,
modified: bool,
// ...
}
Limitations: - Undo/redo cleared on load_file() - Undo/redo NOT saved to disk - Limited to 100 actions (MAX_UNDO_HISTORY)
8.2 Find/Replace State¶
Location: /Users/enzo/Code/turbo-vision/src/views/editor.rs
pub struct SearchOptions {
pub case_sensitive: bool,
pub whole_words_only: bool,
pub backwards: bool,
}
pub struct Editor {
last_search: String,
last_search_options: SearchOptions,
// ...
}
Characteristics: - Search state kept during session - Not persisted across sessions
9. Terminal State (src/terminal/mod.rs)¶
The Terminal manages low-level display state, not application persistence:
pub struct Terminal {
// Screen buffer management
cell_buffer: Vec<Vec<Cell>>,
cursor_pos: (u16, u16),
// ...
}
No Serialization: - Terminal state is ephemeral - Rebuilt on each frame
10. Summary: Serialization Patterns¶
| Feature | Implemented? | Method | Location |
|---|---|---|---|
| Menu Resources | Yes | Rust enums/structs | core/menu_data.rs |
| Status Resources | Yes | Rust enums/structs | core/status_data.rs |
| File I/O | Yes | std::fs::read_to_string/write | views/editor.rs |
| Application State | In-memory only | struct fields | app/application.rs |
| Desktop State | In-memory only | Desktop struct | views/desktop.rs |
| Window State | In-memory only | Window/View traits | views/window.rs |
| Configuration | None | N/A | N/A |
| serde Framework | No | N/A | N/A |
| History Persistence | No | Memory only | core/history.rs |
| Undo/Redo Persistence | No | Memory only | views/editor.rs |
| Preferences File | No | N/A | N/A |
11. Recommendations for Adding Persistence¶
If you want to add serialization:
Option 1: Add serde Support¶
Then derive on structures:
#[derive(Serialize, Deserialize)]
pub struct Menu {
pub items: Vec<MenuItem>,
pub default_index: Option<usize>,
}
Option 2: Manual Serialization¶
Write custom to_string/from_str methods for each structure.
Option 3: Configuration Format¶
Use existing configs (TOML, YAML) with toml or yaml crates.
12. Key Files Summary¶
| File | Lines | Purpose |
|---|---|---|
src/core/menu_data.rs |
356 | Menu resource definitions |
src/core/status_data.rs |
268 | Status line resource definitions |
src/views/editor.rs |
1400+ | Text editing with file I/O |
src/views/file_editor.rs |
150+ | File editor wrapper |
src/views/edit_window.rs |
150+ | Edit window wrapper |
src/core/history.rs |
279 | In-memory history manager |
src/core/ansi_dump.rs |
80+ | Debug output dumping |
src/app/application.rs |
310 | Application state container |
src/views/desktop.rs |
260 | Desktop/window management |
demo/rust_editor.rs |
17915 | Complete editor example |
Conclusion¶
The Turbo Vision Rust implementation:
- Uses NO serialization frameworks - No serde, JSON, TOML, etc.
- Defines resources as Rust structures - Menu, MenuItem, StatusDef, StatusItem
- Implements basic file I/O - Editor can load/save plain text files
- Manages state in memory only - No persistence between sessions
- Provides history system - Per-ID history for input fields (not persisted)
- Matches Borland's architecture - Commands, menu bar, status line, desktop
The design is simple and idiomatic Rust, focusing on the core UI framework rather than serialization concerns. Adding persistence is straightforward if needed.