Thank you for your interest in contributing to Marco. This document explains how the project is organized, how it works at a high level, and where to find the main integration points you might work with when adding features or fixing bugs.
Marco is developed cross-platform, but the day-to-day workflow is native per OS (Linux builds on Linux, Windows builds on Windows).
We welcome contributions of all sizes. Typical contributions include bug fixes, new editor features, additional themes, documentation improvements, and core parser enhancements.
- Open an issue describing the change or bug you want to address.
- Fork the repository and create a feature branch.
- Add tests where appropriate and keep changes small and focused.
- If modifying the core grammar, test with various markdown samples.
- Run
cargo buildandcargo testlocally. - Open a pull request describing the change and link the related issue.
This repo includes two VS Code workspace files. Use the one that matches your native OS:
- Linux:
marco-linux.code-workspace- Uses Rust Analyzer +
clippyon save.
- Uses Rust Analyzer +
- Windows (MSVC):
marco-windows.code-workspace- Configures Rust Analyzer to use the
x86_64-pc-windows-msvctarget.
- Configures Rust Analyzer to use the
Note: We intentionally avoid a "Windows GNU cross-compile from Linux" workspace because GTK/Glib dependencies require a full cross sysroot +
pkg-configsetup. If you point Rust Analyzer atx86_64-pc-windows-gnuon Linux you will likely seeglib-sys/pkg-configcross-compilation errors and cascading "can't find crate ..." diagnostics.
- Keep UI code in
marco/src/components/andmarco/src/ui/. - Keep pure business logic in
core/src/logic/(no GTK dependencies). - Follow Rust idioms and project patterns (use
Result<T, E>, avoid panics in library code, document public APIs). - Add unit tests under the appropriate module and integration tests under
tests/.
Marco uses a Cargo workspace with three crates:
- core — Pure Rust library with nom-based parser, AST builder, HTML renderer, LSP features, and core logic (buffer management, settings, paths, cache, logging). No GTK dependencies. Located in
core/. - marco — Full-featured GTK4 editor binary with SourceView5 text editing and WebKit6 preview. Depends on core. Located in
marco/. - polo — Lightweight viewer-only binary with WebKit6 preview but no text editing (no SourceView5). Depends on core. Located in
polo/.
Assets (themes, fonts, icons, settings) are centralized at the workspace root in assets/.
polo/ is the viewer-focused sibling of Marco. It is intended to be a smaller GTK app that:
- Renders Markdown via
core(parser + HTML renderer) - Displays the result in a WebKit-based preview
- Does not include a full editor surface (no SourceView)
Key files:
- Entry point:
polo/src/main.rs - UI components:
polo/src/components/
Good contribution areas for Polo:
- Preview performance improvements (incremental refresh, caching)
- Theme parity with Marco (HTML preview themes)
- File opening / reload behavior
- Cross-platform windowing and webview integration (keeping UI code isolated in
polo/and core logic incore/)
The core/ crate is organized into several key modules:
grammar/— nom-based grammar parsers for block and inline Markdown elementsparser/— AST building from grammar output (includesast.rs,block_parser.rs,inline_parser.rs,position.rs)render/— HTML renderer with entity escaping and syntax highlighting supportlsp/— LSP features: syntax highlighting, diagnostics, completion, hoverlogic/— Pure Rust business logic: buffer management, settings, paths, cache, logging
Marco uses a three-layer design:
- main — application entry and glue (in
marco/src/main.rs), responsible for initializing GTK, the theme manager, and wiring UI to logic. - components — GTK widgets, layout, and event wiring (in
marco/src/components/). The primary editor component is created viacreate_editor_with_preview_and_buffer. - logic — document buffer management, file operations, and settings.
- Core logic lives in
core/src/logic/(pure Rust, no GTK). - UI-specific logic lives in
marco/src/logic/(GTK-dependent signal management and menu handlers).
- Core logic lives in
The core library handles markdown parsing and HTML rendering using a nom-based parser. The editor is a split-pane composed of a SourceView-based text buffer and a WebKit6-based HTML preview. Changes in the buffer trigger live re-rendering using core's parser for Markdown-to-HTML conversion with proper image path resolution. The parser uses nom combinators in core/src/grammar/ to build an AST which is then rendered to HTML by core/src/render/.
These functions are useful when embedding the editor widget or integrating with Marco programmatically. See the corresponding source files for details and type signatures.
-
create_editor_with_preview_and_buffer(preview_theme_filename, preview_theme_dir, theme_manager, theme_mode, document_buffer)- Returns:
(Paned, WebView, css_rc, refresh_preview, update_editor_theme, update_preview_theme, buffer, insert_mode_state, set_view_mode) - Notes: Add the returned
Panedto your window. Callrefresh_preview()to re-render andupdate_editor_theme(scheme_id)/update_preview_theme(scheme_id)to change themes at runtime. Thedocument_bufferparameter should be aDocumentBufferfor file path management and WebKit6 base URI support.
- Returns:
-
render_editor_with_view(style_scheme, font_family, font_size_pt)- Returns:
(container, buffer, source_view) - Notes: Useful for embedding the editor view without the WebView preview.
- Returns:
-
wire_footer_updates(buffer, labels, insert_mode_state)- Notes: Attaches debounced footer updates that compute cursor position, word/char counts, and syntax information using the core AST parser.
If you add public utilities, document small examples for how to call them from main.rs or tests.
File locations used during development:
- Themes and assets:
assets/themes/at workspace root. - Settings template (packaging / reference):
assets/settings_org.ron. - Settings used at runtime: resolved via
core::paths.- Dev mode uses
tests/settings/settings.ron. - Installed builds use the per-OS config directory (for example
~/.config/marco/settings.ronon Linux; on Windows this may be%APPDATA%\marco\settings.ronor a portableconfig\settings.ronnext to the executable).
- Dev mode uses
- Languages:
assets/language/for localization files. - Core library:
core/src/contains the nom-based markdown parser (grammar/,parser/), HTML renderer (render/), and LSP features (lsp/).
- Locale files live in
assets/language/{code}.tomlwhere{code}is ISO 639-1 (two lowercase letters, e.g.en,de). - Use
assets/language/en.tomlas the template; translate values but keep keys unchanged. - Update the implementation matrix:
assets/language/language_matrix.md. - The loader and typed
Translationsstructs live inmarco/src/components/language/.
- The application uses a
ThemeManagerto map editor schemes to preview theme modes. Changing themes from the settings dialog calls back into functions returned bycreate_editor_with_preview_and_buffer.
- Add CSS files under
assets/themes/ - Place editor style schemes under
assets/themes/editor/. - Place view style schemes under
assets/themes/html_viewer/ - Update the theme manager to include your new theme.
Build all crates:
cargo build --release --workspaceBuild specific crates:
cargo build --release -p core # Core library only
cargo build --release -p marco # Full editor
cargo build --release -p polo # Viewer onlyRun the full editor (development):
cargo run --release -p marcoRun the viewer only:
cargo run --release -p poloRun tests for all crates:
cargo test --workspace --lib --tests -- --nocaptureRun tests for specific crate:
cargo test -p core -- --nocapture- GTK CSS errors: Ensure you run from the repository root so relative theme paths resolve. Check
assets/themes/*exists. - Missing fonts or icons: Confirm
assets/fonts/andassets/icons/are present and thatcrate::paths::find_asset_root()(orMarcoPaths::new()) finds the repo asset path. - Preview not updating: Verify the buffer change signal is firing and that the core parser is working correctly. Check the WebKit6 console for base URI issues with local images.
- Core parsing issues: The app uses a custom nom-based parser in
core/src/grammar/andcore/src/parser/— check the grammar modules and AST builder if markdown isn't rendering correctly. Runcargo test -p coreto validate parser behavior. - Local images not displaying: Ensure WebKit6 security settings are enabled and DocumentBuffer is providing correct base URIs for file:// protocol access.
- Import errors: Use
core::for core functionality (parser, buffer, logic),crate::for local modules within marco or polo binaries. - Rust Analyzer shows lots of "can't find crate ...": Make sure you opened the correct VS Code workspace for your OS.
- On Linux use
marco-linux.code-workspace. - On Windows use
marco-windows.code-workspace. - If you set a Windows GNU target on Linux, you may hit
glib-sys/pkg-configcross-compilation failures which cascade into many unrelated diagnostics.
- On Linux use
If you hit a problem you can't resolve, open an issue with a short description, steps to reproduce, and the output of running the app in a terminal.
These are areas where an implemented contribution will have big impact. If you plan to work on any of these, open an issue first so we can coordinate and reserve the scope.
- Goal: Add a shared-document component that syncs buffer state across peers using a CRDT backend (Yjs, automerge, or similar).
- Integration points:
- Create a new
marco/src/components/collab/module that implements aCollabBackendtrait (connect, disconnect, apply_remote_ops, get_local_patch). - Wire the component into the editor buffer event loop: when the local buffer changes, the component should produce and broadcast a patch; when remote patches arrive, they should be applied to the
DocumentBufferusing documented public update methods. - Respect existing undo/redo and cursor/selection synchronization: treat remote changes as first-class edits and emit events the UI can use to update cursors.
- Create a new
- Testing notes: add unit tests for concurrent patches, and an integration test using two in-process backends that exchange patches.
- Goal: Provide a component API and example component that offers in-editor assistance (summaries, rewrite suggestions, universal spell checking, autocorrect).
- Integration points:
- Define a
marco/src/components/ai/interface that accepts a text range and returns suggested edits or annotations. Keep the component optional and behind a feature flag or runtime toggle. - Provide a small example implementation that uses an HTTP-based LLM adapter (local or remote) and demonstrates non-blocking requests using async tasks; always run requests off the UI thread and apply edits on the main loop.
- Offer a CLI or developer test harness under
tests/ai/to run the component against sample documents.
- Define a
- Security & privacy notes: document privacy expectations clearly. Components that call external APIs must expose where data is sent and provide opt-in configuration.
Reference README and asset locations for contributors working on components and translations:
- marco/src/components/ai/README.md — AI component guidance and interface notes
- marco/src/components/collab/README.md — Collaboration integration notes and references
marco/src/components/language/mod.rs— Localization provider contract and loader implementation- assets/language/language_matrix.md — language implementation matrix (coverage & contributors)
If you add new component folders, please include a short README.md in the folder that explains the contract, tests, and how to run the component's dev harness.