Build your own local Python prompt editor with real syntax highlighting
Wednesdays are for hands-on AI and computer tinkering.
This week’s free eBooks are live. Download what you want, read what you need, and if you like a title, post a short Amazon review. After that, I’ll show you a fast, local prompt editor you can run on your own machine.
the disinformation dictionary
mastering populism & authoritarian democracy
Political Action Committes, Activism, and Campaign Finance Law
mafia state art kiss kiss kill
brave new orange
why trump won, why clinton lost
watergate 2.0
The Battle of Hostomel
the vampire trump meets his master
Advanced Prompt Editor README
Overview
Advanced Prompt Editor is a desktop text editor built with Python and Tkinter that is designed specifically for working with AI prompts and structured instruction text. It combines a normal editable text area with live part-of-speech highlighting, prompt-keyword highlighting, keyword insertion, autocomplete, find/replace, file open/save, undo/redo, and a dark mode toggle. The script uses Tkinter’s ScrolledText widget for the main editor area and spaCy’s en_core_web_sm model to analyze text and color-code words by grammatical role.
The program is a single-file GUI application. When you run it, it opens a window titled Advanced Prompt Editor and lets you type or load plain text, then visually inspect and edit that text with interactive tools.
What It Does
The editor provides several features that work together to make prompt-writing easier:
It highlights common prompt-structure labels such as
TASK:,GOAL:,CONTEXT:, andFORMAT:in bold blue.It colors words by part of speech, for example nouns in green, verbs in red, adjectives in blue, and adverbs in magenta.
It lets you insert prompt keywords from the menu instead of typing them manually.
It supports autocomplete for prompt keywords when you press
Ctrl+Space.It includes standard editor actions like open, save, undo, redo, and find/replace.
It can switch to a dark background for easier reading in low light.
This is not a word processor or a markdown editor. It is a lightweight prompt-focused text tool that helps you visually structure instructions while editing plain .txt files.
Menu Bar
The menu bar is divided into four sections:
File: Open, Save, Exit.
Edit: Undo, Redo, Find/Replace.
Insert Keyword: Quick insertion of predefined prompt labels.
View: Toggle Dark Mode.
Text Highlighting
The editor uses two kinds of live highlighting.
Prompt Keywords
The script looks for the following labels anywhere in the text, ignoring case:
TASK:GOAL:ROLE:CONTEXT:EXAMPLES:OUTPUT AS:INPUT:CONSTRAINTS:FORMAT:LIMITS:STYLE:TONE:LANGUAGE:
When one of these appears, it is highlighted in bold blue so the structure of a prompt is easy to scan.
Part-of-Speech Colors
Words are colored based on the spaCy part-of-speech tag.
This makes the editor behave a bit like a linguistic analyzer while you type. The coloring is recalculated every time the text changes.
File Actions
Open
The Open command shows a file picker and lets you choose a text file to load. The script uses Tkinter’s open-file dialog, which is a standard way to prompt the user for a filename in a GUI app. After a file is loaded, the text area is replaced with that file’s contents and the title bar is updated to show the filename.
Save
The Save command opens a save dialog and writes the current editor contents to a file. It defaults to .txt and also updates the window title after saving. If saving fails, the script shows an error message box.
Exit
The Exit command closes the app.
Editing Tools
Undo and Redo
The editor enables Tkinter text-widget undo support. Ctrl+Z performs undo and Ctrl+Y performs redo. These operate on the contents of the text widget, not on file history.
Find and Replace
The Find/Replace dialog lets you enter a search term and an optional replacement. The Find button highlights all matches in yellow. The Replace button replaces every exact occurrence of the search term with the replacement text in the entire document.
Important detail: replacement is a direct string replacement, so it is case-sensitive and global. The find/highlight behavior is case-insensitive, but the replace operation itself uses Python’s str.replace().
Keyword Insertion and Autocomplete
The Insert Keyword menu contains all of the predefined prompt labels. Clicking one inserts that keyword at the current cursor position followed by a space.
Autocomplete is available with Ctrl+Space. The script examines the partial word immediately before the cursor and looks for matching prompt keywords that start with that text. If there are matches, a small popup list appears so you can pick one. After selection, the partial text is replaced with the full keyword and a space is added.
This is meant to speed up repeated prompt-template writing.
Dark Mode
The Toggle Dark Mode command switches the text widget background between white and dark gray. It also changes the text insertion cursor and foreground color so the editor remains readable.
The toggle only changes the main text area. It does not fully restyle every menu, popup, or dialog in the app.
Requirements
To run the script, you need:
Python 3.x
Tkinter
spaCy
The spaCy English model
en_core_web_sm
Tkinter is included with many Python installations, but on some systems it may need to be installed separately through the operating system package manager. spaCy and the model must be installed in the Python environment you use to run the script.
Installation
A typical setup looks like this:
python -m venv .venv
Activate the virtual environment:
Windows: .venv\Scripts\activate
macOS/Linux: source .venv/bin/activate
Install spaCy:
pip install spacy
Download the English model:
python -m spacy download en_core_web_sm
The script expects the model name en_core_web_sm exactly, because it loads it directly at startup with spacy.load("en_core_web_sm").
Running the Script
Save the Python code in a file, for example:
prompt_editor.py
Then run it with:
python prompt_editor.py
When the program starts, a GUI window opens immediately. There is no command-line interface, and no arguments are required.
Basic Usage
Launch the script.
Type or paste text into the editor.
Watch prompt keywords and parts of speech get highlighted automatically.
Use the Insert Keyword menu or
Ctrl+Spaceto add prompt labels.Use Find/Replace to edit repeated wording.
Use Open and Save to manage
.txtfiles.Use Toggle Dark Mode if you prefer a darker background.
For example, if you paste a structured prompt like:
ROLE: You are a helpful tutor. TASK: Explain the concept clearly. TONE: Friendly and concise. FORMAT: Bullet points.
the labels will be highlighted and the surrounding words will be color-coded by grammatical role.
Keyboard Shortcuts
ShortcutActionCtrl+OOpen fileCtrl+SSave fileCtrl+ZUndoCtrl+YRedoCtrl+FOpen Find/ReplaceCtrl+SpaceAutocomplete prompt keyword
Limitations
It only works with plain text files.
It depends on the spaCy English model being installed before launch.
The POS coloring is based on spaCy’s tagging, so unusual text, incomplete sentences, or code-like content may be tagged imperfectly.
Dark mode only updates the editor widget, not every dialog in a fully unified theme.
There is no autosave.
There is no multi-file tab support.
There is no syntax highlighting for programming languages.
Troubleshooting
ModuleNotFoundError: No module named 'spacy'
Install spaCy:
pip install spacy
OSError: [E050] Can't find model 'en_core_web_sm'
Install the model:
python -m spacy download en_core_web_sm
Tkinter Does Not Start
Your Python installation may not include Tkinter. On Linux, install the OS package. On macOS or Windows, reinstall Python from an official distribution that includes Tkinter.
Window Opens But Highlighting Seems Slow
The app re-parses the entire text on each keystroke. Very long files can slow it down. Try shorter prompt documents or expect reduced responsiveness with large text blocks.
Summary
This script is a prompt-oriented text editor with live linguistic highlighting, quick keyword insertion, file management, search/replace, and a dark mode. It is best used for drafting structured AI prompts or other annotated plain-text notes where visual organization matters.
import tkinter as tk
from tkinter.scrolledtext import ScrolledText
from tkinter import simpledialog, filedialog, messagebox
import spacy
# Load SpaCy model
nlp = spacy.load("en_core_web_sm")
# POS colors and prompt keywords (case-insensitive match)
POS_COLOR_MAP = {
"NOUN": "green",
"VERB": "red",
"ADJ": "blue",
"ADV": "magenta",
"PRON": "cyan",
"PROPN": "darkgreen",
"NUM": "orange",
"DET": "brown",
"ADP": "purple",
"CONJ": "purple",
"PUNCT": "gray",
}
PROMPT_KEYWORDS = [
"TASK:", "GOAL:", "ROLE:", "CONTEXT:", "EXAMPLES:", "OUTPUT AS:", "INPUT:",
"CONSTRAINTS:", "FORMAT:", "LIMITS:", "STYLE:", "TONE:", "LANGUAGE:"
]
class PromptEditor(tk.Tk):
def __init__(self):
super().__init__()
self.title("Advanced Prompt Editor")
self.geometry("900x600")
self.dark_mode = False
# Text widget with scrollbars
self.text = ScrolledText(self, wrap=tk.WORD, undo=True, font=("Consolas", 12))
self.text.pack(fill=tk.BOTH, expand=True)
# Configure tags for POS
for pos, color in POS_COLOR_MAP.items():
self.text.tag_configure(pos, foreground=color)
# Keyword highlight tag: bold and blue
self.text.tag_configure("KEYWORD", foreground="#4C9AFF", font=("Consolas", 12, "bold"))
# Highlight all matches tag for find/replace
self.text.tag_configure("highlight", background="yellow", foreground="black")
# Bind events
self.text.bind("<KeyRelease>", self.on_text_change)
self.text.bind("<Control-space>", self.autocomplete)
# Menu
menubar = tk.Menu(self)
# File
filemenu = tk.Menu(menubar, tearoff=0)
filemenu.add_command(label="Open", command=self.open_file, accelerator="Ctrl+O")
filemenu.add_command(label="Save", command=self.save_file, accelerator="Ctrl+S")
filemenu.add_separator()
filemenu.add_command(label="Exit", command=self.quit)
menubar.add_cascade(label="File", menu=filemenu)
self.bind_all("<Control-o>", lambda e: self.open_file())
self.bind_all("<Control-s>", lambda e: self.save_file())
# Edit with Undo/Redo & Find/Replace
editmenu = tk.Menu(menubar, tearoff=0)
editmenu.add_command(label="Undo", command=self.text.edit_undo, accelerator="Ctrl+Z")
editmenu.add_command(label="Redo", command=self.text.edit_redo, accelerator="Ctrl+Y")
editmenu.add_separator()
editmenu.add_command(label="Find/Replace", command=self.find_replace_dialog, accelerator="Ctrl+F")
menubar.add_cascade(label="Edit", menu=editmenu)
self.bind_all("<Control-z>", lambda e: self.text.edit_undo())
self.bind_all("<Control-y>", lambda e: self.text.edit_redo())
self.bind_all("<Control-f>", lambda e: self.find_replace_dialog())
# Insert keyword menu
insertmenu = tk.Menu(menubar, tearoff=0)
for kw in PROMPT_KEYWORDS:
insertmenu.add_command(label=kw, command=lambda k=kw: self.insert_keyword(k))
menubar.add_cascade(label="Insert Keyword", menu=insertmenu)
# View menu: Dark mode toggle
viewmenu = tk.Menu(menubar, tearoff=0)
viewmenu.add_command(label="Toggle Dark Mode", command=self.toggle_dark_mode)
menubar.add_cascade(label="View", menu=viewmenu)
self.config(menu=menubar)
# Initial parse/colorize
self.on_text_change()
# For autocomplete popup
self.popup = None
def on_text_change(self, event=None):
content = self.text.get("1.0", tk.END)
# Remove all tags
for tag in list(POS_COLOR_MAP.keys()) + ["KEYWORD", "highlight"]:
self.text.tag_remove(tag, "1.0", tk.END)
# Highlight keywords (case insensitive)
lower_content = content.lower()
for keyword in PROMPT_KEYWORDS:
key_lower = keyword.lower()
start = 0
while True:
idx = lower_content.find(key_lower, start)
if idx == -1:
break
start_index = f"1.0+{idx}c"
end_index = f"1.0+{idx + len(keyword)}c"
self.text.tag_add("KEYWORD", start_index, end_index)
start = idx + len(keyword)
# POS tagging colorization
doc = nlp(content)
for token in doc:
if token.pos_ in POS_COLOR_MAP:
start_index = f"1.0+{token.idx}c"
end_index = f"1.0+{token.idx + len(token.text)}c"
self.text.tag_add(token.pos_, start_index, end_index)
def insert_keyword(self, keyword):
self.text.insert(tk.INSERT, keyword + " ")
self.on_text_change()
self.text.focus_set()
def open_file(self):
path = filedialog.askopenfilename(filetypes=[("Text files", "*.txt"), ("All files", "*.*")])
if path:
try:
with open(path, "r") as f:
content = f.read()
self.text.delete("1.0", tk.END)
self.text.insert(tk.END, content)
self.on_text_change()
self.title(f"Advanced Prompt Editor - {path}")
except Exception as e:
messagebox.showerror("Error", f"Cannot open file:\n{e}")
def save_file(self):
path = filedialog.asksaveasfilename(defaultextension=".txt",
filetypes=[("Text files", "*.txt"), ("All files", "*.*")])
if path:
try:
content = self.text.get("1.0", tk.END)
with open(path, "w") as f:
f.write(content)
self.title(f"Advanced Prompt Editor - {path}")
messagebox.showinfo("Saved", "File saved successfully")
except Exception as e:
messagebox.showerror("Error", f"Cannot save file:\n{e}")
def find_replace_dialog(self, event=None):
def find():
self.text.tag_remove("highlight", "1.0", tk.END)
target = find_entry.get()
if target:
idx = "1.0"
while True:
idx = self.text.search(target, idx, nocase=1, stopindex=tk.END)
if not idx:
break
end = f"{idx}+{len(target)}c"
self.text.tag_add("highlight", idx, end)
idx = end
def replace():
target = find_entry.get()
replacement = replace_entry.get()
if target:
content = self.text.get("1.0", tk.END)
new_content = content.replace(target, replacement)
self.text.delete("1.0", tk.END)
self.text.insert(tk.END, new_content)
self.on_text_change()
dialog = tk.Toplevel(self)
dialog.title("Find and Replace")
dialog.geometry("400x120")
dialog.transient(self)
dialog.grab_set()
tk.Label(dialog, text="Find:").grid(row=0, column=0, sticky="w", padx=5, pady=5)
find_entry = tk.Entry(dialog, width=30)
find_entry.grid(row=0, column=1, sticky="ew", padx=5, pady=5)
find_entry.focus()
tk.Label(dialog, text="Replace:").grid(row=1, column=0, sticky="w", padx=5, pady=5)
replace_entry = tk.Entry(dialog, width=30)
replace_entry.grid(row=1, column=1, sticky="ew", padx=5, pady=5)
tk.Button(dialog, text="Find", command=find).grid(row=2, column=0, padx=5, pady=5, sticky="ew")
tk.Button(dialog, text="Replace", command=replace).grid(row=2, column=1, padx=5, pady=5, sticky="ew")
dialog.columnconfigure(1, weight=1)
def autocomplete(self, event=None):
# Get current word before cursor
cursor_index = self.text.index(tk.INSERT)
line, col = map(int, cursor_index.split('.'))
line_text = self.text.get(f"{line}.0", f"{line}.end")
word = ""
for i in range(col - 1, -1, -1):
if not line_text[i].isalnum() and line_text[i] not in ":_-":
break
word = line_text[i] + word
if not word:
return "break"
# Find matching keywords
matches = [kw for kw in PROMPT_KEYWORDS if kw.lower().startswith(word.lower())]
if not matches:
return "break"
# Popup menu for selection
if self.popup:
self.popup.destroy()
self.popup = tk.Toplevel(self)
self.popup.wm_overrideredirect(True)
x, y, _, _ = self.text.bbox(tk.INSERT)
x += self.text.winfo_rootx()
y += self.text.winfo_rooty() + 20
self.popup.wm_geometry(f"+{x}+{y}")
lb = tk.Listbox(self.popup, height=min(6, len(matches)), bg="white", activestyle="dotbox")
lb.pack(side=tk.LEFT, fill=tk.BOTH, expand=True)
for m in matches:
lb.insert(tk.END, m)
def on_select(evt):
sel = lb.curselection()
if sel:
keyword = lb.get(sel[0])
# Delete current partial word
start = f"{line}.{col - len(word)}"
self.text.delete(start, tk.INSERT)
self.text.insert(start, keyword + " ")
self.popup.destroy()
self.popup = None
self.on_text_change()
self.text.focus_set()
lb.bind("<<ListboxSelect>>", on_select)
lb.focus_set()
def close_popup(event=None):
if self.popup:
self.popup.destroy()
self.popup = None
self.popup.bind("<FocusOut>", close_popup)
self.popup.bind("<Escape>", close_popup)
return "break"
def toggle_dark_mode(self):
bg = "#1e1e1e" if not self.dark_mode else "white"
fg = "#d4d4d4" if not self.dark_mode else "black"
self.text.config(background=bg, foreground=fg, insertbackground=fg)
# Adjust highlight colors for dark mode if desired
self.dark_mode = not self.dark_mode
if __name__ == "__main__":
app = PromptEditor()
app.mainloop()Powered by Caffeine Rage and Justice.












