Skip to main content

Writing a Markdown Editor in C++ with wxWidgets and cmark

·4 mins

It’s incredible what you can achieve with modern open-source libraries. Take a code editor, for example: imagine writing code in the left pane while instantly seeing the rendered output on the right, with every change reflected in real time. No manual refreshes, no delays—just seamless, live feedback as you work.

But here’s the best part: this editor isn’t just fast and responsive—it’s also truly cross-platform. A single codebase runs natively on Windows, Linux, and macOS without any modifications. The executable is lean, weighing just a few megabytes, unlike bloated Electron-based apps that devour hundreds of megabytes just to display a simple interface.

And we’re going to build it. Right now. In C++. With minimal dependencies and barely a hundred lines of code.

Our Markdown Editor
Our Markdown Editor

CMake #

CMake makes building C++ projects a breeze. It is a cross-platform build system that generates native makefiles or project files for your platform. It is widely used in the C++ community and has great support for wxWidgets.

Modern CMake lets you manage dependencies easily. We will use FetchContent to download both wxWidgets and cmark libraries:

FetchContent_Declare(
    wxWidgets
    GIT_REPOSITORY https://github.com/wxWidgets/wxWidgets.git
    GIT_TAG 3e6b6647628c944ba9d5e599d599081e58ad2ac4
)

FetchContent_Declare(
    cmark
    GIT_REPOSITORY https://github.com/commonmark/cmark.git
    GIT_TAG 0.31.1
)

FetchContent_MakeAvailable(wxWidgets cmark)

This will download the libraries and make them available for your project. You can then link them to your executable:

target_link_libraries(main PRIVATE wxbase wxcore wxhtml cmark)

Check out the full CMakeLists.txt in my project’s repo on GitHub.

Our Plan #

The idea is simple: use the built in capabilities of both libraries to create the app with as little effort as possible:

  1. CMark can parse Markdown and render it to HTML with cmark_markdown_to_html().
  2. wxWidgets can render HTML with wxHtmlWindow - a lightweight HTML renderer that covers the basics, like text, links, lists, etc - basically everything we need for Markdown, without the need for a full-blown browser engine.
  3. wxWidgets can also handle events, like text changes in the source editor. We will use that to trigger the re-rendering of the HTML output in real-time.
  4. The UI design is incredibly easy: use a splitter to divide the window into two panes, one for the source editor and one for the rendered output. The source editor will be a simple wxTextCtrl, and the output will be a wxHtmlWindow.

The Code #

We can keep things simple and put all the code in a single file:

#include <wx/wx.h>
#include <wx/html/htmlwin.h>
#include <wx/textctrl.h>
#include <wx/splitter.h>
#include <cmark.h>

class MyApp : public wxApp
{
    public:
        virtual bool OnInit();
};

class MyFrame : public wxFrame
{
    public:
        MyFrame(const wxString &title, const wxPoint &pos, const wxSize &size);
    private:
        void RenderMarkdown();
        wxHtmlWindow *htmlControl;
        wxTextCtrl *markdownControl;
};

wxIMPLEMENT_APP(MyApp);

bool MyApp::OnInit()
{
    MyFrame *frame = new MyFrame("Hello Everyone!", wxDefaultPosition, wxDefaultSize);
    frame->Show(true);
    return true;
}

MyFrame::MyFrame(const wxString &title, const wxPoint &pos, const wxSize &size)
    : wxFrame(NULL, wxID_ANY, title, pos, size)
{
    const auto MarkdownExample = R"(
# Hello World
This is a simple wxWidgets application that displays a "Hello World!" message in a text control.

## Features
- wxFrame
- wxTextCtrl

# Learn More

Visit [wxWidgets Documentation](https://docs.wxwidgets.org/3.2) to learn more about wxWidgets.
    )";

    wxSplitterWindow *splitter = new wxSplitterWindow(this, wxID_ANY);

    htmlControl = new wxHtmlWindow(splitter, wxID_ANY);

    markdownControl = new wxTextCtrl(splitter, wxID_ANY, MarkdownExample, wxDefaultPosition, wxDefaultSize, wxTE_MULTILINE );
    markdownControl->SetValue(MarkdownExample);
    markdownControl->SetFont(wxFontInfo(12).Family(wxFONTFAMILY_TELETYPE));

    splitter->SetSashGravity(0.5);
    splitter->SetMinimumPaneSize(FromDIP(200));
    splitter->SplitVertically(markdownControl, htmlControl);

    markdownControl->Bind(wxEVT_TEXT, 
            [this](wxCommandEvent& event) {
                RenderMarkdown();
            });

    RenderMarkdown();
}


void MyFrame::RenderMarkdown()
{
    auto text = markdownControl->GetValue();
    auto buffer = text.utf8_str();

    auto htmlText = cmark_markdown_to_html(buffer.data(), buffer.length(), CMARK_OPT_DEFAULT);

    htmlControl->SetPage(htmlText);

    free(htmlText);
}

Now, it does not get any simpler than that. We have a wxFrame with a wxSplitterWindow that contains a wxTextCtrl for the Markdown source and a wxHtmlWindow for the rendered output. The RenderMarkdown() function is called whenever the text in the source editor changes, and it updates the HTML output accordingly.

The users can click on links in the rendered output, but we need to handle them ourselves. We can do this by binding to the wxEVT_HTML_LINK_CLICKED event:

    htmlControl->Bind(wxEVT_HTML_LINK_CLICKED, 
            [this](wxHtmlLinkEvent& event) {
                wxLaunchDefaultBrowser(event.GetLinkInfo().GetHref());
            });

This will open the link in the default web browser when the user clicks on it. Just one line of code, and we have full link support in our Markdown editor.

Performance improvements #

Immediate rendering is great, but it can be a bit slow for larger documents. We can improve performance by using a wxTimer to debounce the rendering, so it only happens after the user stops typing for a short period of time. This way, we avoid rendering on every keystroke and improve the overall responsiveness of the application.

I challenge you to implement this feature yourself. It is a great exercise to learn how to use wxTimer and how to debounce events in wxWidgets. If you get stuck, check out my YouTube video where I explain how to do it step by step.

Conclusion #

This is how you can build a simple Markdown editor in C++ with wxWidgets and cmark. It is fast, lightweight, and works on all major platforms. You can extend it with more features, like syntax highlighting, saving/loading files, or even adding support for more advanced Markdown features. Feel free to experiment with this code and make it your own.