Wikilinks
February 17, 2026 ยท View on GitHub
Overview
Wikilinks ([[target]] and [[target|display text]]) allow linking between Markdown files using a lightweight syntax. Clicking a wikilink in rendered or split view opens the target file in a new or existing tab. Broken links (target file not found) are visually distinguished with dimmed red styling and strikethrough.
Key Files
src/markdown/parser.rs- AST node type (MarkdownNodeType::Wikilink) and post-processing extraction fromTextnodessrc/markdown/editor.rs-render_wikilink()function,WikilinkContextstruct, broken link detection, click handling viaegui::Memorysrc/markdown/mod.rs- Re-exportsWikilinkContextsrc/app/file_ops.rs-navigate_wikilink(),resolve_wikilink_target(), recursive file search with tie-breakerssrc/app/central_panel.rs- PassesWikilinkContexttoMarkdownEditor, handleswikilink_clickedoutput events
Implementation Details
Parsing
Comrak does not natively support wikilinks, so they are handled as a post-processing step on the AST. After Comrak parses the Markdown into MarkdownNode trees, extract_wikilinks() recursively walks the AST and finds Text nodes containing [[...]] patterns. These are split into sequences of Text and Wikilink nodes.
The Wikilink AST variant:
Wikilink {
target: String, // e.g. "My Note"
display: Option<String>, // e.g. Some("Click here")
}
Supported syntax:
[[target]]- links totarget.md, displays "target"[[target|display text]]- links totarget.md, displays "display text"
Edge cases handled as plain text:
[[]](empty target)[[not closed(unclosed brackets)
Rendering
Wikilinks are rendered as clickable egui::Label widgets in the rendered and split views:
- Normal links: Blue/accent colored text with hand cursor on hover
- Broken links: Dimmed red with strikethrough, indicating the target file was not found
- Tooltip: Shows target path and resolution status on hover
- Click: Stores target in
egui::MemoryunderId::new("wikilink_clicked_target"), which is read byMarkdownEditorOutput.wikilink_clicked
The WikilinkContext struct provides current directory and workspace root to the renderer via egui temporary memory, enabling lightweight file-exists checks at render time without threading filesystem state through the deep render call chain.
File Resolution
resolve_wikilink_target() in file_ops.rs implements the resolution hierarchy:
- Relative to current file's directory -
current_dir/target.md - Relative to workspace root -
workspace_root/target.md - Recursive search of workspace with tie-breakers:
- Same-folder-first priority
- Shortest path wins
- First match returned if still ambiguous
Supports spaces in filenames (e.g., [[My Note]] resolves to My Note.md).
Navigation
When a wikilink is clicked:
central_panel.rscaptures the target fromMarkdownEditorOutput.wikilink_clicked- After UI rendering completes (to avoid borrow conflicts), calls
FerriteApp::navigate_wikilink() - The method resolves the target path and calls
state.open_file()to open in a new or existing tab - If resolution fails, an error toast is displayed
Dependencies Used
No new dependencies. Uses existing:
comrak- Base Markdown parsing (wikilinks added as post-processing)egui- Rendering andMemoryfor cross-component communication
Usage
Open test_md/test_wikilinks.md in Ferrite's rendered or split view to test all wikilink scenarios. The test file includes:
- Basic wikilinks and display text variants
- Spaces in filenames
- Broken/missing link indicators
- Edge cases (empty, unclosed)
- Wikilinks in lists, blockquotes, and formatted text
Tests
7 unit tests in src/markdown/parser.rs:
test_parse_simple_wikilinktest_parse_wikilink_with_display_texttest_parse_wikilink_with_spacestest_parse_multiple_wikilinkstest_parse_wikilink_text_contenttest_parse_unclosed_wikilinktest_parse_empty_wikilink
Run with: cargo test parser::tests::test_parse