A fast, minimal PyQt6 Markdown editor with live preview and HTML/PDF export
Project description
PyMarkdownEditor
A fast, minimal PyQt6 Markdown editor with live preview, HTML/PDF export, and a clean, SOLID-friendly architecture.
Owner-led governance; contributions welcome (see CONTRIBUTING).
Features
-
Live preview
Debounced, side-by-side Markdown preview while you type. -
Markdown rendering
Usespython-markdownwith common extensions:extra,fenced_code,codehilite,toc,sane_lists,smarty, and supports extra extensions such aspymdown-extensions(e.g. math/LaTeX viaarithmatex). -
Dark-mode aware CSS
Preview uses theme-friendly styles that work in both light and dark environments. -
Robust file handling
- Open/Save
.mdwith atomic writes (QSaveFile, UTF-8). - Recent files persisted via
QSettings. - Drag & drop files onto the window to open them.
- Open/Save
-
Exporters
- HTML – saves the preview HTML as-is.
- PDF (classic) – QTextDocument/QPrinter (A4, 12.7 mm margins) to mirror the preview.
- PDF (WebEngine) – optional QWebEngine-based exporter (
web_pdf_exporter.py) for closer “what you see is what you print” output when Qt WebEngine is available.
-
Editor helpers
- One-click bold, italic,
`inline code`. - Heading helpers:
# H1,## H2. - List helper:
- list. - Insert link dialog.
- Insert image dialog.
- Insert table dialog.
- Find / Replace / Replace all dialog.
- Toggle wrap and preview from toolbar/menu.
- One-click bold, italic,
-
Architecture
- SOLID-leaning design with clear boundaries.
- Simple dependency injection container.
- Strategy-based exporters registered in an
ExporterRegistrysingleton. - Thin Qt UI (
MainWindow+ dialogs) that delegates to services.
Installation
From source (current)
# 1) Create and activate a virtual environment
python -m venv .venv
# Windows
. .venv/Scripts/activate
# macOS / Linux
source .venv/bin/activate
# 2) Install runtime dependencies
pip install -r requirements.txt
# 3) Run the app
python -m pymd
Python: 3.10+ recommended.
From PyPI (planned / when published)
Once the package is live on PyPI under py-markdown-editor, installation will look like:
pip install py-markdown-editor
python -m pymd
If you later add a console script entry point, this could become as simple as:
pymd
Requirements
Runtime dependencies (see requirements.txt for exact versions):
PyQt6>=6.6
Markdown>=3.5
Pygments>=2.17
pymdown-extensions
For WebEngine-based PDF export, you may also need:
PyQt6-WebEngineand the corresponding Qt WebEngine system libraries on your platform.
Dev/test tools live in dev-requirements.txt—see Testing.
Keyboard Shortcuts & UI
Core actions
- New / Open / Save / Save As – standard platform shortcuts.
- Toggle wrap – toolbar/menu.
- Toggle preview – toolbar/menu.
- Quit – standard platform shortcut.
Text & formatting
-
Toolbar/menu helpers:
**B**– bold.*i*– italic.`code`– inline code.# H1,## H2– heading prefixes.- list– bullet list prefix.
Insert / dialogs
- Insert link… – opens the link dialog.
- Insert image… – opens a file chooser and inserts an image reference.
- Insert table… – opens the table dialog to generate Markdown tables.
- About… – opens the About dialog.
Find & replace
- Find… – open the find dialog.
- Replace… – open find/replace dialog.
- Replace all – replace all matches in the document.
(Exact keyboard accelerators may vary slightly by platform/Qt style, but all are exposed via menus and toolbars.)
Project Structure
High-level repo layout:
.
├── build-requirements.txt
├── CHANGELOG.md
├── config/
├── CONTRIBUTING.md
├── dev-requirements.txt
├── dist/
├── docs/
│ ├── CI.md
│ ├── RELEASING.md
│ └── screenshot.png
├── LICENSE
├── pyinstaller.spec
├── PyMarkdownEditor.spec
├── pyproject.toml
├── README.md
├── requirements.txt
├── ruff.toml
├── tests/
│ ├── conftest.py
│ ├── test_about_dialog.py
│ ├── test_container.py
│ ├── test_exporter_registry.py
│ ├── test_file_service.py
│ ├── test_html_exporter.py
│ ├── test_ini_config_service.py
│ ├── test_main_window.py
│ ├── test_markdown_renderer.py
│ ├── test_models.py
│ ├── test_pdf_exporter.py
│ ├── test_settings_service.py
│ └── test_table_dialog.py
└── pymd/
├── __init__.py
├── __main__.py # python -m pymd entry point
├── main.py # legacy/alt entry point
├── app.py # QApplication bootstrap + DI container wiring
├── di/
│ ├── __init__.py
│ └── container.py
├── domain/
│ ├── __init__.py
│ ├── interfaces.py
│ └── models.py
├── services/
│ ├── __init__.py
│ ├── config/
│ │ └── ini_config_service.py
│ ├── exporters/
│ │ ├── __init__.py
│ │ ├── base.py
│ │ ├── html_exporter.py
│ │ ├── pdf_exporter.py
│ │ └── web_pdf_exporter.py
│ ├── file_service.py
│ ├── markdown_renderer.py
│ ├── settings_service.py
│ └── ui/
│ ├── __init__.py
│ ├── about.py
│ ├── create_link.py
│ ├── find_replace.py
│ ├── main_window.py
│ ├── table_dialog.py
│ ├── adapters/
│ │ ├── __init__.py
│ │ ├── qt_dialogs.py
│ │ ├── qt_messages.py
│ │ └── qt_text_editor.py
│ ├── commands/
│ │ ├── __init__.py
│ │ ├── prefix_lines.py
│ │ └── surround_selection.py
│ ├── ports/
│ │ ├── __init__.py
│ │ ├── dialogs.py
│ │ └── messages.py
│ └── presenters/
│ ├── __init__.py
│ └── main_presenter.py
└── utils/
├── __init__.py
└── constants.py
Key pieces:
-
pymd/app.pyApplication bootstrap – sets up Qt app, DI container, and main window. -
pymd/di/container.pyWires services, exporters, and UI into a single container. -
pymd/domain/Core interfaces and models (keeps contracts decoupled from Qt/UI details). -
pymd/services/markdown_renderer.py– wraps Markdown + extensions.file_service.py– safe file IO with atomic writes.settings_service.py/config/ini_config_service.py– settings and INI-based config.exporters/– HTML/PDF/Web PDF exporters behind a common base.
-
pymd/services/ui/main_window.py– main editor window.about.py– About dialog.create_link.py,find_replace.py,table_dialog.py– feature dialogs.adapters/– Qt-specific implementations of dialog, message, and text editor ports.ports/– abstraction interfaces for dialogs/messages (for testability and decoupling).presenters/– presenter layer (e.g.main_presenter.py) coordinating UI + services.commands/– small text-editing “command” helpers (e.g. prefix lines, surround selection).
-
pymd/utils/Small shared constants and helpers. -
tests/Coverage for container wiring, dialogs, exporters, config service, and core services.
Testing
Install dev dependencies:
pip install -r dev-requirements.txt
Then run the test suite:
pytest --cov=pymd --cov-report=term-missing --timeout=120
dev-requirements.txt includes (excerpt):
pytest>=8.0
pytest-qt>=4.4
pytest-cov>=5.0
pytest-timeout
ruff
Notes:
- Qt tests are written to avoid blocking modal dialogs.
- Coverage includes both happy paths and error handling (e.g. file write failures, malformed config).
- A fast “quick CI” config exists for non-
masterbranches, and a fuller matrix runs for PRs tomaster.
Building Binaries (PyInstaller)
You can bundle PyMarkdownEditor into standalone binaries using PyInstaller.
A minimal local example:
pip install pyinstaller
pyinstaller -n PyMarkdownEditor --windowed --onefile \
-i NONE -s -y pymd/__main__.py
The repository’s GitHub Actions workflow .github/workflows/release-binaries.yml:
-
Builds on Windows, Linux, and macOS.
-
Uses a
.specfile on Windows for predictable layout. -
Uses platform-appropriate commands on Linux/macOS (e.g.
.appbundle on macOS). -
Collects hidden imports for:
markdownpygmentsPyQt6modulespymdownx(e.g.arithmatex).
Artifacts are zipped or packaged per platform and attached to GitHub Releases for tagged versions (e.g. v0.8.2).
CI, Releases & Publishing
-
Continuous Integration –
.github/workflows/ci.yml- Runs on pushes/PRs.
- Performs
ruffformatting checks andpytestwith coverage. - Uses a quick single-job run for development branches and a full OS/Python matrix for PRs to
master.
-
Binary Releases –
.github/workflows/release-binaries.yml- Triggered by pushing a semver tag:
vMAJOR.MINOR.PATCH(e.g.v0.8.2). - Builds cross-platform binaries and uploads artifacts to the GitHub Release.
- Triggered by pushing a semver tag:
-
PyPI / TestPyPI Publishing –
.github/workflows/publish.yml-
Triggered on tags like
v0.8.0,v0.8.1a1,v0.8.1-foo. -
Verifies that the tag (e.g.
v0.8.2) matchesproject.versioninpyproject.toml. -
Builds sdist + wheel using
python -m build. -
Uses Trusted Publishing:
- Pre-release or hyphenated tags → TestPyPI.
- Final releases → PyPI.
-
See CHANGELOG.md for a detailed history of changes.
Troubleshooting
-
PDF export blank/empty
- Ensure the target folder exists and is writable.
- Check that any required Qt print/WebEngine libraries are installed.
-
Fonts differ between preview and PDF
- QPrinter rasterization and platform fonts may differ.
- Consider tweaking the CSS or installing appropriate fonts system-wide.
-
Qt / WebEngine errors
- If using the WebEngine PDF exporter, make sure
PyQt6-WebEngineand matching Qt WebEngine libraries are available. - You can fall back to the classic QTextDocument/QPrinter exporter if needed.
- If using the WebEngine PDF exporter, make sure
-
ModuleNotFoundError: 'pymdownx'-
Install
pymdown-extensions:pip install pymdown-extensions
-
Ensure it is present in your environment or packaged with your binary.
-
Contributing
We welcome issues and pull requests.
- CONTRIBUTING.md – owner-led governance, DCO sign-off, PR checklist.
- LICENSE – Apache-2.0.
.github/CODEOWNERS(if present) – lists current code owners.
“Owner-led governance” means:
- The maintainer steers overall scope and architecture.
- The app aims to stay small, focused, and easy to package.
- PRs are reviewed for fit, clarity, and maintainability before merging.
Quick dev loop:
# format / lint
pip install ruff black
ruff format .
ruff check .
# run tests
pytest --cov=pymd --cov-report=term-missing --timeout=120
You can wire these into pre-commit hooks for a smoother local workflow.
License
Apache-2.0 © 2025 clintonshane84 See LICENSE.
Project details
Download files
Download the file for your platform. If you're not sure which to choose, learn more about installing packages.
Source Distribution
Built Distribution
Filter files by name, interpreter, ABI, and platform.
If you're not sure about the file name format, learn more about wheel file names.
Copy a direct link to the current filters
File details
Details for the file py_markdown_editor-0.8.8.tar.gz.
File metadata
- Download URL: py_markdown_editor-0.8.8.tar.gz
- Upload date:
- Size: 43.3 kB
- Tags: Source
- Uploaded using Trusted Publishing? Yes
- Uploaded via: twine/6.1.0 CPython/3.13.7
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
d8c47e913ad294000c5facd4c0a8cdbfea3e1ff5c27b8a20c4041eba3eb1284d
|
|
| MD5 |
746c86d39b4c85b3a7f7193691adc8c8
|
|
| BLAKE2b-256 |
5f4be2914da04dfb3b197c8d7b4b159721b0f4878ab283785e0539b7b74457fb
|
Provenance
The following attestation bundles were made for py_markdown_editor-0.8.8.tar.gz:
Publisher:
publish.yml on clintonshane84/PyMarkdownEditor
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
py_markdown_editor-0.8.8.tar.gz -
Subject digest:
d8c47e913ad294000c5facd4c0a8cdbfea3e1ff5c27b8a20c4041eba3eb1284d - Sigstore transparency entry: 715041861
- Sigstore integration time:
-
Permalink:
clintonshane84/PyMarkdownEditor@d2cfba24f36d3d6b23ac2f646e77d6d410154da9 -
Branch / Tag:
refs/tags/v0.8.8 - Owner: https://github.com/clintonshane84
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
publish.yml@d2cfba24f36d3d6b23ac2f646e77d6d410154da9 -
Trigger Event:
push
-
Statement type:
File details
Details for the file py_markdown_editor-0.8.8-py3-none-any.whl.
File metadata
- Download URL: py_markdown_editor-0.8.8-py3-none-any.whl
- Upload date:
- Size: 44.2 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? Yes
- Uploaded via: twine/6.1.0 CPython/3.13.7
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
38636672f99fb62431da51e94a6279b02a654086b17dd9e65324e3c7cce6f63f
|
|
| MD5 |
90e22aaa814d60e1f9f94bfb0be7e483
|
|
| BLAKE2b-256 |
66f58849c2c070a43502640a65a2223e806b00f719eb73fff73ceedaaa1e8b1e
|
Provenance
The following attestation bundles were made for py_markdown_editor-0.8.8-py3-none-any.whl:
Publisher:
publish.yml on clintonshane84/PyMarkdownEditor
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
py_markdown_editor-0.8.8-py3-none-any.whl -
Subject digest:
38636672f99fb62431da51e94a6279b02a654086b17dd9e65324e3c7cce6f63f - Sigstore transparency entry: 715041862
- Sigstore integration time:
-
Permalink:
clintonshane84/PyMarkdownEditor@d2cfba24f36d3d6b23ac2f646e77d6d410154da9 -
Branch / Tag:
refs/tags/v0.8.8 - Owner: https://github.com/clintonshane84
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
publish.yml@d2cfba24f36d3d6b23ac2f646e77d6d410154da9 -
Trigger Event:
push
-
Statement type: