Building a Markdown Editor with Python and Tkinter

Markdown is a lightweight markup language that has become increasingly popular among developers, writers, and many others for formatting plain text. It was created in 2004 by John Gruber with significant contributions from Aaron Swartz, with the goal of enabling people "to write using an easy-to-read and easy-to-write plain text format, optionally convert it to structurally valid XHTML (or HTML)."

Since then, Markdown has been widely adopted across the software development industry and beyond. It‘s the default format for documenting projects on GitHub, powers discussion forums like Stack Overflow and Reddit, and is used by popular tools like Slack and Trello. According to data from GitHub, Markdown is by far the most popular markup language used by developers on the platform, with over 67% of repositories using Markdown for documentation:

Markup Language Repositories Using It
Markdown 9,938,581
reStructuredText 1,195,772
Asciidoc 153,056
Org 64,378
Other 1,601,671

Part of Markdown‘s appeal is its simplicity and readability. It uses intuitive and minimal syntax to format text, making it easy to learn and use. For example, you can create a heading by prefixing text with hash marks (#), turn text into a link by wrapping it in brackets and providing the URL in parentheses ([link text](url)), and make a list by starting lines with dashes or numbers. This readable syntax, combined with the fact that Markdown files are plain text and can be edited in any text editor, makes Markdown a versatile choice for many kinds of writing.

As a developer learning Python, building your own Markdown editor is a great project to practice working with graphical user interfaces (GUIs). Not only will you get experience with core Python concepts and popular libraries, but you‘ll also create a useful tool you can use and extend.

In this tutorial, we‘ll walk through building a simple but functional Markdown editor using Python and the standard Tkinter GUI library. We‘ll also use the tkhtmlview library to display a real-time preview of the rendered HTML output. By the end, you‘ll have a solid foundation in Python GUI programming and a working Markdown editor you can customize further.

Setting Up the Project

Before we start coding, let‘s make sure we have the necessary libraries installed. This project requires Python 3.x and the following packages:

  • tkinter (included with Python)
  • tkhtmlview
  • markdown2

We can install the latter two libraries using pip:

pip install tkhtmlview markdown2

With our prerequisites ready, let‘s start building our Markdown editor!

Creating the Main Window

The first step is to create the main application window where we‘ll place our Markdown editing and preview components. We‘ll use Tkinter to create and manipulate our GUI elements.

First, let‘s import the required libraries and set up the basic window:

import tkinter as tk
from tkinter import filedialog
from tkinter import font
from tkinter import messagebox
from tkinter import *

class MarkdownEditor(tk.Frame):
    def __init__(self, master=None):
        super().__init__(master)
        self.master = master
        self.master.title("Markdown Editor")
        self.master.geometry("800x600")

        self.create_widgets()

    def create_widgets(self):
        pass

root = tk.Tk()
app = MarkdownEditor(master=root)
app.mainloop()

Here we create a MarkdownEditor class that inherits from tk.Frame, which is a Tkinter widget that acts as a container for other widgets. In the __init__ method, we set up the main window, giving it a title and default size. We then call a create_widgets method, which we‘ll use to add the rest of our interface components.

The mainloop method at the end kicks off the event loop, which listens for user input and updates the interface.

Adding the Markdown Editor and Preview Panes

Next, let‘s add the actual Markdown editing and preview components to our window. We want a paned window with two resizable panes: a text area on the left for editing Markdown code, and an HTML viewer on the right to display the rendered output.

We can use Tkinter‘s PanedWindow widget to create the split panes, a Text widget for the editor, and tkhtmlview‘s HTMLLabel for the preview:

def create_widgets(self):
    self.paned_window = tk.PanedWindow(self.master, orient=tk.HORIZONTAL)
    self.paned_window.pack(fill=tk.BOTH, expand=1)

    self.editor = tk.Text(self.paned_window, wrap=tk.WORD)
    self.editor.pack(fill=tk.BOTH, expand=1)
    self.paned_window.add(self.editor)

    html_frame = tk.Frame(self.paned_window)
    self.html_view = HTMLLabel(html_frame, html="")
    self.html_view.pack(fill=tk.BOTH, expand=1)
    self.html_view.fit_height()
    self.paned_window.add(html_frame)

This code creates a horizontal PanedWindow and adds two panes to it. The first pane contains a Text widget for editing Markdown, while the second pane contains an HTMLLabel inside a Frame.

The pack geometry manager is used to make the widgets expand to fill their containers. The fit_height method of HTMLLabel ensures that the height of the preview pane fits the rendered content.

At this point, we have the core of our Markdown editor interface set up. However, the preview doesn‘t yet update when we type in the editor. Let‘s fix that.

Updating the Preview in Real Time

To make the preview pane update in real time as we type Markdown code, we need to hook into the Text widget‘s events. Specifically, we‘ll listen for <Key> events, which fire whenever a key is pressed in the widget.

We‘ll define a function called update_preview that takes the Markdown code from the editor pane, converts it to HTML using the markdown2 library, and sets the HTML of the preview pane:

def update_preview(self, event=None):
    markdown = self.editor.get("1.0", tk.END)
    html = markdown2.markdown(markdown)
    self.html_view.set_html(html)

def create_widgets(self):
    ...
    self.editor.bind("<Key>", self.update_preview)

Now, whenever a key is pressed in the editor pane, update_preview will be called, converting the Markdown to HTML and updating the preview.

Adding File Operations

A text editor isn‘t very useful if you can‘t open and save files. Let‘s add that functionality to our Markdown editor.

We‘ll create an "Open" command that shows a file dialog for selecting a Markdown file, reads its contents into the editor pane, and updates the preview:

def open_file(self):
    filepath = filedialog.askopenfilename(
        filetypes=[("Markdown Files", "*.md"), ("Text Files", "*.txt"), ("All Files", "*.*")]
    )
    if not filepath:
        return
    with open(filepath, mode="r", encoding="utf-8") as input_file:
        text = input_file.read()
        self.editor.delete("1.0", tk.END)
        self.editor.insert(tk.END, text)
    self.update_preview()

Similarly, we‘ll add a "Save As" command that prompts for a filename to save the current contents of the editor pane:

def save_file(self):
    filepath = filedialog.asksaveasfilename(
        defaultextension=".md",
        filetypes=[("Markdown Files", "*.md"), ("Text Files", "*.txt"), ("All Files", "*.*")]
    )
    if not filepath:
        return
    with open(filepath, mode="w", encoding="utf-8") as output_file:
        text = self.editor.get("1.0", tk.END)
        output_file.write(text)

To make these functions accessible to the user, let‘s add a menu bar with "File" options for opening and saving files:

def create_widgets(self):
    ...
    menubar = tk.Menu(self.master)
    file_menu = tk.Menu(menubar, tearoff=0)
    file_menu.add_command(label="Open", command=self.open_file)
    file_menu.add_command(label="Save As", command=self.save_file)
    menubar.add_cascade(label="File", menu=file_menu)

    self.master.config(menu=menubar)

With this, our Markdown editor is starting to feel fairly complete. We can edit Markdown, see a real-time preview, and open and save files.

Styling the Editor

While our editor is functional, it‘s a bit spartan in appearance. Let‘s add some styling to make it a bit more visually appealing.

First, let‘s set a default font and size for our Text widget:

def create_widgets(self):
    ...
    self.editor = tk.Text(font=("Helvetica", 14))

We can also add some padding and a border around the preview pane:

html_frame = tk.Frame(self.paned_window, padx=10, pady=10, borderwidth=1, relief=tk.SUNKEN)

These small touches make our editor look a bit more polished.

Potential Enhancements

While our editor is perfectly usable in its current state, there are many potential improvements we could make:

  • Syntax highlighting: We could apply color highlighting to the Markdown syntax in the editor pane to make it easier to read and write. This could be achieved using a library like Pygments.

  • Customizable preview styles: We could allow the user to customize the CSS styles applied to the preview pane. This would enable them to tweak the appearance of the rendered HTML.

  • Markdown shortcuts: We could provide toolbar buttons or keyboard shortcuts for common Markdown formatting like bold, italics, links, images, etc. This would make it quicker to apply formatting without having to type the syntax.

  • Live word count: We could display a running count of the words and characters in the Markdown document. This would be useful for writers trying to hit a specific word count.

  • Spell checking: We could integrate a spell checker to underline misspelled words in the editor pane.

  • Document outline: For longer documents, we could provide a clickable outline of the headings to allow quick navigation.

The possibilities are endless! The beauty of building your own tool is that you can tailor it precisely to your needs and preferences.

Conclusion

In this tutorial, we‘ve built a functional Markdown editor from scratch using Python and Tkinter. Along the way, we‘ve learned how to:

  • Create a multi-pane window interface with Tkinter
  • Update a preview pane in real-time as the user types
  • Open and save files using dialog boxes
  • Apply basic styling to Tkinter widgets
  • Convert Markdown to HTML using the markdown2 library
  • Display rendered HTML using tkhtmlview

While our editor may not rival full-featured Markdown tools, it‘s a powerful demonstration of how much you can achieve with Python and a few libraries. More importantly, it provides a solid foundation for further experimentation and learning.

As you continue on your Python and GUI programming journey, I encourage you to extend and enhance this editor. Add new features, experiment with different libraries and approaches, and most importantly, have fun!

Happy coding!

Similar Posts