Movatterモバイル変換


[0]ホーム

URL:


How to Make a Markdown Editor using Tkinter in Python

Learn how you can create a markdown editor using the Tkinter library and regular expressions in Python.
  · 9 min read · Updated aug 2022 ·GUI Programming

Step up your coding game withAI-powered Code Explainer. Get insights like never before!

Idea

In this tutorial, we will make a markdown editor using theGUI Library Tkinter andregular expressions. We will make it so there is a text area on the left where the user writes and a display area on the right where the markdown is slightly styled. To do this, we need to learn about regular expressions and the tkinter'sText widget and how to style parts of these text widgets.

Let's get started!

Imports

As always, we import some modules and libraries to help us. First, we get everything from Tkinter by using the*. With Tkinter, we will build our Graphical User Interface (GUI). By using this syntax with the asterisk (*) we ensure that we can use the constants defined by Tkinter.

Because windows generated by Tkinter often don't look good, we also getctypes so we can enable high Dots Per Inch (DPI). You see this in action in the last line of the code block below.

Lastly, we getre which we'll use to find patterns in our text. So we will also learn a little bit about regular expressions:

# Importsfrom tkinter import *import ctypesimport re# Increas Dots Per inch so it looks sharperctypes.windll.shcore.SetProcessDpiAwareness(True)

Setup

Now we get to setting up Tkinter. We start by making a newTk object and saving it to a variable. Then we set the title, which will appear in the top left of our window. We then also set the window dimensions with thegeometry() function. Lastly, we set the font globally to becourier in size 15:

# Setuproot = Tk()root.title('Markdown Editor')root.geometry('1000x600')# Setting the Font globalyroot.option_add('*Font', 'Courier 15')

Styles

Now we make many variables representing style settings for our little program. We use a function to transform these RGB values to hex that we create ourselves. We later see how it is made up. Thewidth is used in the text widget later:

# Style SetupeditorBackground = rgbToHex((40, 40, 40))editorTextColor = rgbToHex((230, 230, 230))displayBackground = rgbToHex((60, 60, 60))displayTextColor = rgbToHex((200, 200, 200))caretColor = rgbToHex((255, 255, 255))# Width of the Textareas in characterswidth = 10

We also define some fonts. For the edit area, we setCourier, which is redundant, but we do it either way. We then define the display area font asCalibri; this may not work for you, so you change it toArial or any other font installed on your computer. We also set the sizes and colors for headers from layers 1 to 3:

# FontseditorfontName = 'Courier'displayFontName = 'Calibri'# Font SizesnormalSize = 15h1Size = 40h2Size = 30h3Size = 20# font Colorsh1Color = rgbToHex((240, 240, 240))h2Color = rgbToHex((200, 200, 200))h3Color = rgbToHex((160, 160, 160))

Regular Expressions

Now we also define a list of the replacements and stylings made in the display area. This is a list comprised of other lists. In these lists, the first item is the pattern that is searched. The second one is the name. The third is the font data which is a formatted string in our case.

The fourth is the color, and the last is the offset. Later we see what this means when we use this nested list. For now, we only enlargen headers with one, two, and three hashtags, and we make text surrounded with* bold:

# Replacements tell us were to insert tags with the font and colors givenreplacements = [    [        '^#[a-zA-Z\s\d\?\!\.]+$',        'Header 1',        f'{displayFontName} {h1Size}',         h1Color,        0    ], [        '^##[a-zA-Z\s\d\?\!\.]+$',        'Header 2',         f'{displayFontName} {h2Size}',        h2Color,        0    ], [        '^###[a-zA-Z\s\d\?\!\.]+$',         'Header 3',         f'{displayFontName} {h3Size}',         h3Color,        0            ], [        '\*.+?\*',         'Bold',         f'{displayFontName} {normalSize} bold',         displayTextColor,        2    ],]

Let's take a look at one of the expressions:^#[a-zA-Z\s\d\?\!\.]+$ will match the largest header made with one# hashtag. The^ means the beginning of the string and# means itself,[a-zA-Z\s\d\?\!\.] indicates any character, digit, whitespace, '?',. or!. We modify this group with+ so it means one or more of this character. And in the end, we have a dollar sign that means the end of the string. We do it this way because markup can not span multiple lines divided by a\n.

Widgets

Now let's make some widgets. We start with the editor, that is just aText. We give it the root element as a master. We set aheight and awidth that does not matter here. We set the background and foreground (text) color with our variables. We then put a hefty border and the relief to flat, resulting in nice padding for our text area. We also set the color of the caret with theinsertbackground parameter. We then place the widget withpack:

# Making the Editor Areaeditor = Text(    root,    height=5,    width=width,    bg=editorBackground,    fg=editorTextColor,    border=30,    relief=FLAT,    insertbackground=caretColor)editor.pack(expand=1, fill=BOTH, side=LEFT)

We bind theKeyRelease event to ourchanges() function that we'll define, highlighting the display area and updating the text. We then set the focus on the editor withfocus_set(). We also insert some starting text into the editor:

# Bind <KeyReleas> so every change is registerededitor.bind('<KeyRelease>', changes)editor.focus_set()# Insert a starting texteditor.insert(INSERT, """#Heading 1##Heading 2###Heading 3This is a *bold* move!- Markdown Editor -""")

We also make aText Area for the Display. It is pretty similar to the editor area. But we set it to be disabled so the user cannot edit it:

# Making the Display Areadisplay = Text(    root,    height=5,    width=width,    bg=displayBackground,    fg=displayTextColor,    border=30,    relief=FLAT,    font=f"{displayFontName} {normalSize}",)display.pack(expand=1, fill=BOTH, side=LEFT)# Disable the Display Area so the user cant write in it# We will have to toggle it so we can insert textdisplay['state'] = DISABLED

Helper Functions

Now we define three functions. Keep in mind that we have to insert these definitions early in the code because we have used them in code before.

changes()

This function was bound to theKeyRelease event of the edit area, and it will handle changes, sync the two text areas and highlight certain parts of them.

In the function, we have a parameter called event that is not needed but have to define it because the event will pass itself to the function. Then we set the state of the display to normal so it can be edited. After that, we delete its content with itsdelete() method. Here1.0 means the first letter in the first line andEND means the end of the content.

def changes(event=None):    display['state'] = NORMAL    # Clear the Display Area    display.delete(1.0, END)

Continuing, we get the editor's text and save it to a variable. We then also copy this value to another variable because, in the following two lines, we remove# and* from the text, so it looks nicer. But we keep the raw version so we can search the patterns. We then insert thetext into thedisplay:

    # Insert the content of the Edit Area into the Display Area    text = editor.get('1.0', END)    # Save Raw Text for later    textRaw = text    # Remove Unwanted Characters    text = ''.join(text.split('#'))    text = ''.join(text.split('*'))    display.insert(1.0, text)

Then we loop through our replacements list and save each item to a different variable by unpacking them:

    # Loop through each replacement, unpacking it fully    for pattern, name, fontData, colorData, offset in replacements:        # Get the location indices of the given pattern        locations = search_re(pattern, textRaw, offset)

Then we call oursearch_re(text) which will return a list like this. Where each tuple represents the starting and ending position of the matched pattern:

[("1.3", "1.7"), ("2.3", "2.5")]

Then we also loop through this list and add a tag at these positions with the name. These tags allow us to style certain parts of the text.

        # Add tags where the search_re function found the pattern        for start, end in locations:            display.tag_add(name, start, end)

Then at the end of the loop, we also configure this tag to use our font data and color. We access the tag by its name.

The last thing we do in the function is set the state toDISABLED once again.

        # Configure the tag to use the specified font and color        # to this every time to delete the previous tags        display.tag_config(name, font=fontData, foreground=colorData)    display['state'] = DISABLED

search_re()

Now let's go over the pattern searching function. We give it the pattern, the text, and the offset. In it, we define amatches list that will be returned in the end:

def search_re(pattern, text, offset):    matches = []

Then we split the text by\n using thesplitlines() method on it, and we enumerate over it. For each line, we use thefinditer() function fromre which will return a list of the occurrences to loop over it:

    text = text.splitlines()    for i, line in enumerate(text):        for match in re.finditer(pattern, line):

Then we append a tuple with the starting and ending position and return the list at the end after the loop:

            matches.append(                (f"{i + 1}.{match.start()}", f"{i + 1}.{match.end() - offset}")            )    return matches

rgbToHex()

Now also cover the small RGB to hex conversion function. By using"string %s" % (value) syntax, we can insert values from a tuple in a string with placeholders a little bit likef"{var}". But with this syntax, we can convert number to hex using%02x where x means to hex and the 02 means convert it to two characters.

# Convert an RGB tuple to a HEX string using the % Operator# 02 means print 2 characters# x means hexadecimaldef rgbToHex(rgb):    return "#%02x%02x%02x" % rgb

Starting

In the end, we call thechanges()function, so the styling is applied to the starting text, and we run the main loop:

# Starting the Applicationchanges()root.mainloop()

Showcase

Conclusion

Excellent! You have successfully created a markdown editor using Python code! See how you can add more features to this program, such as:

  • Saving and opening.md files. The functionality is covered inthe text editor tutorial.
  • Highlighting the code, inline code, and italic styling.

Learn also:How to Make a Planet Simulator with PyGame in Python

Happy coding ♥

Loved the article? You'll love ourCode Converter even more! It's your secret weapon for effortless coding. Give it a whirl!

View Full Code Auto-Generate My Code
Sharing is caring!



Read Also


How to Make a File Explorer using Tkinter in Python
How to Make a Text Editor using Tkinter in Python
How to Make a Calculator with Tkinter in Python

Comment panel

    Got a coding query or need some guidance before you comment? Check out thisPython Code Assistant for expert advice and handy tips. It's like having a coding tutor right in your fingertips!





    Ethical Hacking with Python EBook - Topic - Top


    Join 50,000+ Python Programmers & Enthusiasts like you!



    Tags


    New Tutorials

    Popular Tutorials


    Ethical Hacking with Python EBook - Topic - Bottom






    Claim your Free Chapter!

    Download a Completely Free Ethical hacking with Python from Scratch Chapter.

    See how the book can help you build awesome hacking tools with Python!



    [8]ページ先頭

    ©2009-2025 Movatter.jp