Skip to main content

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).

CI Build & Release Binaries

PyMarkdownEditor


Features

  • Live preview
    Debounced, side-by-side Markdown preview while you type.

  • Markdown rendering
    Uses python-markdown with common extensions: extra, fenced_code, codehilite, toc, sane_lists, smarty, and supports extra extensions such as pymdown-extensions (e.g. math/LaTeX via arithmatex).

  • Dark-mode aware CSS
    Preview uses theme-friendly styles that work in both light and dark environments.

  • Robust file handling

    • Open/Save .md with atomic writes (QSaveFile, UTF-8).
    • Recent files persisted via QSettings.
    • Drag & drop files onto the window to open them.
  • 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.
  • Architecture

    • SOLID-leaning design with clear boundaries.
    • Simple dependency injection container.
    • Strategy-based exporters registered in an ExporterRegistry singleton.
    • 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-WebEngine

and 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.py Application bootstrap – sets up Qt app, DI container, and main window.

  • pymd/di/container.py Wires 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-master branches, and a fuller matrix runs for PRs to master.

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 .spec file on Windows for predictable layout.

  • Uses platform-appropriate commands on Linux/macOS (e.g. .app bundle on macOS).

  • Collects hidden imports for:

    • markdown
    • pygments
    • PyQt6 modules
    • pymdownx (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 ruff formatting checks and pytest with 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.
  • 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) matches project.version in pyproject.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-WebEngine and matching Qt WebEngine libraries are available.
    • You can fall back to the classic QTextDocument/QPrinter exporter if needed.
  • 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

py_markdown_editor-0.8.8.tar.gz (43.3 kB view details)

Uploaded Source

Built Distribution

If you're not sure about the file name format, learn more about wheel file names.

py_markdown_editor-0.8.8-py3-none-any.whl (44.2 kB view details)

Uploaded Python 3

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

Hashes for py_markdown_editor-0.8.8.tar.gz
Algorithm Hash digest
SHA256 d8c47e913ad294000c5facd4c0a8cdbfea3e1ff5c27b8a20c4041eba3eb1284d
MD5 746c86d39b4c85b3a7f7193691adc8c8
BLAKE2b-256 5f4be2914da04dfb3b197c8d7b4b159721b0f4878ab283785e0539b7b74457fb

See more details on using hashes here.

Provenance

The following attestation bundles were made for py_markdown_editor-0.8.8.tar.gz:

Publisher: publish.yml on clintonshane84/PyMarkdownEditor

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

File details

Details for the file py_markdown_editor-0.8.8-py3-none-any.whl.

File metadata

File hashes

Hashes for py_markdown_editor-0.8.8-py3-none-any.whl
Algorithm Hash digest
SHA256 38636672f99fb62431da51e94a6279b02a654086b17dd9e65324e3c7cce6f63f
MD5 90e22aaa814d60e1f9f94bfb0be7e483
BLAKE2b-256 66f58849c2c070a43502640a65a2223e806b00f719eb73fff73ceedaaa1e8b1e

See more details on using hashes here.

Provenance

The following attestation bundles were made for py_markdown_editor-0.8.8-py3-none-any.whl:

Publisher: publish.yml on clintonshane84/PyMarkdownEditor

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

Supported by

AWS Cloud computing and Security Sponsor Datadog Monitoring Depot Continuous Integration Fastly CDN Google Download Analytics Pingdom Monitoring Sentry Error logging StatusPage Status page