Explore my love for Markdown editors, starting with Mou and evolving to ReText, a Python-based editor. Learn why Markdown is a powerful tool for writing, its pros and cons, and how to set up ReText on macOS using Python virtual environments (venv). Plus, discover tips like creating a minimal app bundle for easier use of Python apps on a Mac.
This post was last updated 1 year ago. The core ideas should still be useful, but tech moves fast; always check the latest docs for current best practices before applying what is mentioned.
My Love For Markdown Editors
As a Markdown user for over a decade, I can vividly recall downloading my first Markdown editor, Mou, by Chen Luo.1 It was an excellent, lightweight application that made note-taking enjoyable during my college years. I loved the customizability; and typing in Markdown meant I didn’t have to use my trackpad/mouse, speeding up my note taking.
Last month, I discovered an open source version called MacDown, but unfortunately it hasn’t been updated since 2020.
I use Markdown when I’m writing all my notes on Obsidian (which I’ve blogged about before) and in VSCodium. I don’t even remember the last time my computer had a word processor installed! A great word processor if you need one is LibreOffice; but I digress.
Markdown Beginners
If you’re just starting with Markdown, check out this great tutorial by Commonmark, which is the main spec of Markdown (There are various dialects or “flavours”, if you will, of Markdown used by different editor/apps like GitHub Markdown). Or read through a detailed guide here.
Why I Use Markdown
- Easy to get started.
- High readability.
- Fast formatting without mouse input.
- Ability to export to other formats;
.md to .htmlor.md to .pdf. - Many other places support Markdown, including instant messaging apps, forums, etc.
- Minimal appearance in your workspace, i.e., no space-wasting formatting bars in word processors.
- No more proprietary formats! Markdown files are simple plain text and can be opened for years to come.
Disadvantages of Markdown
- As mentioned previously, Markdown is not standardized, so you may need to learn specific syntax for the app you’re using
A Minimal Markdown Cheatsheet
Here’s a quick Markdown cheatsheet I made with an A.I. tool I’ve been enjoying; Napkin.AI (not affiliated).
This week, I was getting the itch to find a plain Markdown editor again, seeing if I could finally find something that could fit the missing hole Mou left.
I checked the following alternatives, but none were really suitable for me or runs well on macOS (which is still my daily driver OS). If you’re on Linux, I think Ghostwriter and Marknote are both nice options from KDE.
- Ghostwriter—No macOS installer
- Marknote—Unstable macOS version
- MarkFlowy (alpha)—I didn’t want built-in AI features
In the end, I decided to try ReText, a 100% Python compiled project. The appearance can be fully customized using custom CSS and supports more than just Markdown; reStructuredText, Textile and AsciiDoc are also supported. Although it is also a Linux-first application, I found ReText easier to set up and run on macOS than the other two KDE apps. Building apps from source code and managing dependencies is beyond my current skill level.
Some Notes on Python
Just to make a disclaimer… I have limited Python knowledge from experimenting with Jupyter Notebooks, writing Python automation scripts and using libraries like Matplotlib to analyse data from my Garmin watch. So I’m not sure if the methods I mention below are correct or best practices. I would love any pointers or correction (message me) from those who are more fluent with all this!
Please make your own judgement if it is worth using an app that could lose your work! (So far, the app has been pretty stable, no crashes yet on my Mac Mini M1, Sonoma 15.5.)
Anyway, one thing I learnt was the importance of using virtual environments, AKA venv, as they keep packages, the Python interpreter, and related files within its own isolated environment.
Warning
ReText also suggests installing it inside a venv, but warn about Windows and macOS are in no way officially supported. Use ReText at your own risk.
How I Installed ReText on macOS
The gist of my ReText installation guide is to install ReText into a virtual isolated Python environment, create a shell script that activates the venv automatically whenever ReText is run and closes it when the app is quit. It’s a bit of a makeshift way of running ReText I have to admit.
Discovered a Better Method
Update
After writing up the steps below, I’ve done some further testing and realise that apps launched in the manner I describe—Method A: Using a Shell Script, launching a GUI app from the terminal, sometimes breaks the macOS app focus and fails to switch to ReText; causing it to become unresponsive intermittently, e.g. showing the Terminal menu instead of the Python app launched.
So I investigated on how to make it a native macOS application bundle (.app), and I have failed to get it working, without generating other problems, with both Platypus and Py2app. Platypus worked better than Py2app and was easier to use but it opened another can of worms so I gave up on both.
After quite a bit of reading and troubleshooting, I found out a minimal app bundle could work to make a
ReText.appso macOS can launch ReText properly. Very happy to get it to work in the end! It launches ReText without using the terminal, has its own app icon and no more UI glitches. I am pretty excited. I’ve added this second method below.End of update! I’ll leave the Method A in for reference. Scroll down for an alternative, improved Method B.
Method A: Using a Shell Script
(A bit janky, prone to UI errors, but can let you test out ReText relatively quickly.)
Make a script (name it anything you want) and set correct permissions (make it executable). We will come back to it in step 4.
1touch launch_retext.sh 2chmod +x launch_retext.shCreate a virtual environment,
cdinto the path of the venv and usesourceactivate the venv.1python3 -m venv /path/to/venv/retext-env 2cd /path/to/venv/retext-env 3source retext_env/bin/activateTip
I tend to use the long form install command and not
pip3 install ReTextor justpip installas I have multiple versions of Python on my Mac and it is the most explicit way to installing packages. It guarantees the correct Python version, works well in virtual environments and avoids PATH issues.Double check if
pipis installed in the venv withpip --version. If so, install ReText withpython3 -m pip install ReText. Ensure the virtual environment is activated before installing ReText. And remember to usedeactivateafter installation to stop the venv!Important
At this point, you can decide to continue with the shell script method or switch to an improved app bundle method I worked out.
Edit the script we made in step 1 with
codium launch_retext.sh(My choice of IDE is VS Codium, butnanois good too), and fill in the correct paths. Save when done. Note: Because we are installing viapip, the RETEXT_EXECUTABLE will be in the bin directory of the venv directory.1#!/bin/zsh 2 3# Set the path to your virtual environment's activation script 4VENV_ACTIVATE="/path/to/venv/retext-env/bin/activate" 5# Set the path to the ReText executable 6RETEXT_EXECUTABLE="path/to/where/retext/is/installed/local/bin/python3" 7# Path to the __main__.py script within the venv 8RETEXT_SCRIPT="path/to/where/retext/is/installed/lib/python3.13/site-packages/ReText/__main__.py" 9 10echo "Activating virtual environment: $VENV_ACTIVATE" 11 12# Activate the virtual environment 13source "$VENV_ACTIVATE" 14 15# Check if the activation worked 16if [ $? -ne 0 ]; then 17 echo "Failed to activate virtual environment." 18 exit 1 19fiTo make the script more accessible and executable from any directory, there are two options I usually use:
Add the folder of where we saved our script
launch_retext.shto our PATH so that any executable script in that directory can be run by its filename alone.Add a shell function for our particular script.
Both methods allow the script to be run anywhere, but the shell function option is more explicit and actually more flexible as it can handle more processing steps, e.g. error checking and argument validations. If you have a lot of scripts, the PATH method is easier.
How to Add Script Directory to PATH
To add the script’s directory to your PATH, copy the pathname of the newly created script. Identify which shell you’re using with
echo $SHELL. Open the shell configuration file, e.g..zshrcor.bash_profilein the macOS home directory (~).Add a line near the end of the file:
export PATH="$PATH:/location/to/your/scripts".If you have prompt themes installed like
powerlevel10k,zsh-theme, add the aliases or PATH mods before theme mods. This ensures are applied and not overwritten by the theme.Save and reload shell with
source ~/.zshrc(or the config you edited). Or just close the terminal window and launch a new one.
How to Add a Shell Function
Add this snippet to
.zshrc, or to your shell config file, following the same rules as the PATH method regarding where in the config it is added. (The config is read in order, from top to bottom.)I have named my function
rtxfor fast access, i.e. less typing to open the app. You can name it anything you want. This is the word you type into terminal to open ReText.1rtx() { 2 source "/location/to/your/scripts/launch_retext.sh" "$@" 3}Next, add this extra snippet to the bottom of
launch_retext.shto allow for ReText to open even if we don’t provide a file for it to open, e.g.rtx first_note.md1# Check if a file path was provided as an argument 2if [ -z "$1" ]; then 3 # If no argument, open ReText with a new, empty file 4 "$RETEXT_EXECUTABLE" "$RETEXT_SCRIPT" 5else 6 # Run ReText with the specified file 7 "$RETEXT_EXECUTABLE" "$RETEXT_SCRIPT" "$1" 8fiThe whole script should now look like this:
1#!/bin/zsh 2 3# Set the path to your virtual environment's activation script 4VENV_ACTIVATE="/path/to/venv/retext-env/bin/activate" 5# Set the path to the ReText executable 6RETEXT_EXECUTABLE="path/to/where/retext/is/installed/local/bin/python3" 7# Path to the __main__.py script within the venv 8RETEXT_SCRIPT="path/to/where/retext/is/installed/lib/python3.13/site-packages/ReText/__main__.py" 9 10echo "Activating virtual environment: $VENV_ACTIVATE" 11 12# Activate the virtual environment 13source "$VENV_ACTIVATE" 14 15# Check if the activation worked 16if [ $? -ne 0 ]; then 17 echo "Failed to activate virtual environment." 18 exit 1 19fi 20 21# Check if a file path was provided as an argument 22if [ -z "$1" ]; then 23 # If no argument, open ReText with a new, empty file 24 "$RETEXT_EXECUTABLE" "$RETEXT_SCRIPT" 25else 26 # Run ReText with the specified file 27 "$RETEXT_EXECUTABLE" "$RETEXT_SCRIPT" "$1" 28fiRemember to run
source ~/.zshrcto reload config after making changes.Last step: to open the app, go to Terminal, type the function or script name you have set like
rtxor to open a file directlyrtx /path/to/a/note.mdand you should see this:
That’s it for Method A! To close ReText, just close the main ReText window and when you go back to the Terminal, you’ll see you still have the venv activated by the visual cue (name of venv). Just closing the Terminal window will close the venv, or type deactivate.
Let’s get on to the second, perhaps more robust method!
Method B: Make a Minimal App Bundle
Follow step 2 (creating a Python virtual environment for ReText) and step 3 (installing ReText with
pip) from Method A.We will be creating an app bundle with this directory structure. Use
mkdir -p ReText.app/Contents/MacOS ReText.app/Contents/Resourcesto make the folder structure shown below. (To access the files inside the app, right click on the app > Show Package Contents). PutReText.appinto the/Applicationsfolder. We will deal with theResources/retext.icnsin the last step.1ReText.app 2└── Contents 3 ├── Info.plist 4 ├── MacOS 5 │ └── ReTextLauncher 6 └── Resources 7 └── retext.icnsCreate a
Info.plistwithxmlsyntax.CFBundleExecutablewill be the name of the executable script andCFBundleIdentifiercan be anything you want (I filled it in with the ReText’s creator details). Make sure the file is using UTF-8 encoding.1<?xml version="1.0" encoding="UTF-8"?> 2<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd"> 3<plist version="1.0"> 4<dict> 5 <key>CFBundleExecutable</key> 6 <string>ReTextLauncher</string> 7 <key>CFBundleIconFile</key> 8 <string>retext</string> 9 <key>CFBundleIdentifier</key> 10 <string>com.DmitryShachnev.ReTextLauncher</string> 11 <key>CFBundleInfoDictionaryVersion</key> 12 <string>6.0</string> 13 <key>CFBundleName</key> 14 <string>ReTextLauncher</string> 15 <key>CFBundleDisplayName</key> 16 <string>ReTextLauncher</string> 17 <key>CFBundlePackageType</key> 18 <string>APPL</string> 19 <key>CFBundleShortVersionString</key> 20 <string>1.0</string> 21 <key>CFBundleVersion</key> 22 <string>1.0</string> 23 <key>LSMinimumSystemVersion</key> 24 <string>10.10</string> 25 <key>NSPrincipalClass</key> 26 <string>NSApplication</string> 27 <key>NSRequiresAquaSystemAppearance</key> 28 <false/> 29</dict> 30</plist>Like in Method A, step 4, we will create a script in
ReText.app/Contents/MacOS/ReTextLauncherto launch ReText in a venv.- Fill in the correct path for your VENV_DIR (the one you created in Method A: Step 2) for the script, the rest should not need to be edited.
- After saving the script, type
chmod +x /path/to/ReTextLauncher.app/Contents/MacOS/ReTextLauncherto make it executable.
1#!/bin/zsh 2 3# Set path to the virtual environment 4VENV_DIR="/Users/username/path/to/venv" 5 6# Path to the virtual environment's activate script 7ACTIVATE_SCRIPT="$VENV_DIR/bin/activate" 8 9# Path to the ReText executable 10RETEXT_EXECUTABLE="$VENV_DIR/bin/retext" 11 12# PYTHONPATH (change version number, i.e. 3.13, as required) 13export PYTHONPATH="$VENV_DIR/lib/python3.13/site-packages:$PYTHONPATH" 14 15# Add the virtual environment's bin directory to the PATH 16export PATH="$VENV_DIR/bin:$PATH" 17 18# Launch ReText using the retext command 19"$RETEXT_EXECUTABLE" "$@"Tip
Sometimes apps fail to run because it’s downloaded outside the Mac App Store or was created manually. It’s due to the new quarantine mechanism. Although I didn’t have to use it on this occasion, I often use it for FOSS apps I get direct from repos online. Always be wary of what you download from the Internet though! Check SHA256 hashes when you can.
Run
xattr -d com.apple.quarantine /path/to/ReTextLauncher.appto remove the quarantine attribute.Another cause for errors is the fact this app bundle is not code signed. Code signing is like the seal found in medicine; it is to verify that the contents (app code) is not tampered with. To sign an app for personal use (not distribution), like in this scenario, we can create a self-signed certificate using Keychain Access on the Mac for this.
- Go to Applications > Utilities > Keychain Access > In the menu bar > Keychain Access > Certificate Assistant > Create a Certificate.
- Name it something like “Self-Signed Cert”.
- Identity Type: “Self Signed Root” and check the box “Let me override defaults”, click create.
- Continue with all the default options until the cert is generated.
- Go back to the Terminal and use
codesign -s "Self-Signed Cert" -f -v /path/to/ReText.app
We’re nearly done!
- Grab the
.pngicon from/path/to/venv/lib/python3.13/site-packages/ReText/icons/retext.pngand convert it to.icnsusing an online converter - Place the
.icnsfile intoReText.app/Contents/Resources/retext.icns
- Grab the
Double click the app to run and close it without going through the Terminal! 🎉
Wrap Up
This ended up longer than I expected, but I learnt some new tricks here and there…2 Hope you did too; and that this tutorial allows Mac users to enjoy this Python-based, Linux-optimized Markdown editor!
Update: Here’s a follow-up guide on how to customize ReText like syntax highlighting and using Python Markdown extensions. Learn how to setup the configuration file because I found it slightly unclear to begin with.
P.S. I made some pretty major changes to the look of this blog last week; which in turn gave me some extra motivation to write! Hope it’s easier on the eyes and looks more polished. 😉




Comments
Nothing yet. Say the first thing.
Sign in to join the conversation.