This feature implements a comprehensive markdown formatting system with toolbar integration and keyboard shortcuts. It provides formatting commands that work in both Raw and Rendered editor modes, with proper selection handling and formatting state reflection in the UI.
src/markdown/formatting.rs- Core formatting command model and raw-mode formatting logicsrc/ui/ribbon.rs- Toolbar integration with formatting buttonssrc/app.rs- Keyboard shortcut handling and format command applicationsrc/state.rs- Selection tracking in Tab statesrc/editor/widget.rs- Selection capture from egui TextEditsrc/markdown/editor.rs- Focus tracking for rendered mode
The MarkdownFormatCommand enum defines all supported formatting operations:
pub enum MarkdownFormatCommand {
Bold, // **text**
Italic, // *text*
InlineCode, // `code`
Strikethrough, // ~~text~~
Link, // [text](url)
Image, // 
CodeBlock, // ```code```
Heading(u8), // # to ######
BulletList, // - item
NumberedList, // 1. item
Blockquote, // > quote
}The FormattingState struct tracks current formatting at cursor position for toolbar state reflection:
pub struct FormattingState {
pub is_bold: bool,
pub is_italic: bool,
pub is_inline_code: bool,
pub heading_level: Option<HeadingLevel>,
pub is_bullet_list: bool,
pub is_numbered_list: bool,
pub is_blockquote: bool,
// ... other fields
}Raw Mode:
- Selection is captured from egui's TextEdit
cursor_range - Stored in
Tab.selectionasOption<(usize, usize)> - Keyboard shortcuts processed AFTER editor renders to ensure fresh selection
Rendered Mode:
- Focus tracking via
FocusedElementstruct - Each editable widget (heading, paragraph, list item) reports focus and selection
- Selection converted to absolute character positions in source markdown
| Shortcut | Action |
|---|---|
| Ctrl+B | Bold |
| Ctrl+I | Italic |
| Ctrl+K | Link |
| Ctrl+Shift+K | Image |
| Ctrl+` | Inline Code |
| Ctrl+Shift+C | Code Block |
| Ctrl+1 to Ctrl+6 | Headings H1-H6 |
| Ctrl+Shift+B | Bullet List |
| Ctrl+Shift+N | Numbered List |
| Ctrl+Q | Blockquote |
Critical for correct behavior:
render_ui()runs - editor widgets capture selection- Format actions from ribbon are deferred (returned from render_ui)
handle_keyboard_shortcuts()runs- Deferred ribbon format actions applied
- All formatting uses fresh selection data
- No selection: Inline formatting (bold, italic, code, link) does nothing - prevents placeholder insertion
- With selection: Selected text is wrapped with appropriate markers
- Toggle behavior: Re-applying same format to already-formatted text removes the formatting
- Multi-line: Block formats (list, blockquote, heading) affect all selected lines
egui- UI rendering and TextEdit selection handling- Existing
comrakAST - For detecting formatting state at cursor
The Ribbon UI contains a "Format" group with buttons:
- B (Bold), I (Italic),
<>(Inline Code),[~](Link) - Heading dropdown (H1-H6)
-(Bullet List),1.(Numbered List),>(Blockquote),{}(Code Block)
Buttons show active state when cursor is inside formatted text.
// Apply formatting command
let result = apply_raw_format(&content, selection, MarkdownFormatCommand::Bold);
// Detect formatting at position
let state = detect_raw_formatting_state(&content, cursor_position);Run formatting tests:
cargo test markdown::formattingKey test coverage:
- Bold/italic with and without selection
- Heading level changes and toggles
- List formatting (bullet, numbered)
- Blockquote toggle
- Code block wrapping
- Link/image formatting
- Formatting state detection
- Rendered Mode: Selection tracking works for simple elements but complex inline-formatted content uses click-to-edit approach
- AST-based Transforms: Raw mode uses text manipulation; rendered mode doesn't yet do full AST transforms
- Toggle Detection: Some edge cases with nested formatting may not toggle correctly