A fast, native editor for Apple platforms — with Markdown preview, multi-language syntax highlighting, and a built-in Lisp REPL with Lotus Agenda-inspired personal information management.
v1.1 · Requires macOS Sonoma or later, iOS 17 or later
A full-featured editor, organizer, and data engine — in a single native app.
Real-time color coding for 24+ languages. Markdown headings, bold, code blocks, and more — all highlighted as you type.
Render Markdown to beautifully styled HTML with dark/light mode. Toggle with a shortcut or button tap.
Export any document as a print-ready A4 PDF. Save dialog on macOS, share sheet on iOS.
Create today's note with one shortcut. Files named YYYY-MM-DD.md and opened instantly.
Generate structured weekly planners with Monday–Sunday headers and Morning/Afternoon/Evening time blocks.
Browse files by modification date in a month grid. Dual dots show file activity (blue) and agenda items (orange). Click a day to see files and scheduled items.
Search across all files with context snippets, line numbers, and match counts. Case-sensitive toggle.
Fuzzy file finder with keyboard navigation. Find and open any file in milliseconds.
Open multiple files as tabs. Pin favorites so they persist across sessions. Close all unpinned in one go.
Changes saved automatically with a 1-second debounce. Your work is never lost.
Built-in Lisp interpreter with SQLite database. Define tables, query data, and browse records in interactive dBASE-style forms — right inside the editor. Available as a tab or in the sidebar.
Lotus Agenda-inspired personal information manager. Add items with natural language, auto-categorize with rules, define views, recurring items, templates, multiple agendas with export/import. Agenda sidebar panel shows Overdue/Today/Upcoming at a glance.
Embed ```eelisp fenced code blocks in Markdown. Press ⌘⇧Return to run them inline and see results appear right in your document.
Read and write files, access the clipboard, manipulate the cursor, insert text, and query the editor buffer — all from EELisp. Your notes become programmable.
Build interactive forms with computed fields, dropdown pickers, and optional database backing. Calculator forms, data entry, and CRUD — all defined in a single expression.
SwiftUI for macOS, iOS, and iPadOS. Touch-friendly quick actions bar on mobile.
Real-time syntax highlighting with keyword, type, string, number, comment, and preprocessor rules.
A Lisp interpreter for personal data management — inspired by dBASE III and Lotus Agenda. With a full SQLite database engine and agenda-style item tracking.
;; Define functions (defn greet (name) (str "Hello, " name "!")) (greet "world") ; → "Hello, world!" ;; Higher-order functions (map inc (range 1 6)) ; → (2 3 4 5 6) (filter even? (range 1 11)) ; → (2 4 6 8 10) (reduce + 0 '(1 2 3 4 5)) ; → 15
;; Create a table (deftable contacts (name:string email:string age:number)) ;; Insert records (insert contacts {:name "Alice" :age 30}) (insert contacts {:name "Bob" :age 25}) ;; Query with filters (query contacts :where "age > ?" :params (list 28) :order "name")
;; Table view (navigable grid) (browse contacts) ;; CRUD form (edit/save/delete) (edit contacts :where "age > ?" :params (list 25)) ;; Calculator with computed fields (defform interest (principal:number rate:number years:number) :computed ((total (* principal (pow (+ 1 (/ rate 100)) years))) (gain (- total principal))))
;; Auto-categorization rules (defrule urgent-flag :when (str-contains text "URGENT") :assign "priority/high") (defrule errand-detect :when (str-contains text "buy ") :assign "personal/errands") ;; Auto-apply rules on insert (auto-categorize true) ;; Items get categorized automatically! (add-item "URGENT: Deploy hotfix") ;; → priority/high assigned (add-item "Buy groceries") ;; → personal/errands assigned
;; Define saved views (defview work-board :source items :filter (has-category "work") :group-by category :sort-by when) (defview urgent :source items :filter (= priority "1")) (defview overdue :source items :filter (overdue?)) ;; Show a view (show work-board) ;; → ▸ work/meetings (2) ;; Team standup ;; Call with client ;; ▸ work/projects (3) ;; Deploy v2.1 ;; Finish report
;; Natural language item creation (add "Meet Alice tomorrow !!") ;; → date, priority, person extracted (add "Call Bob next Monday") ;; → :when = next Monday, :who Bob ;; Preview extraction (smart-parse "URGENT deploy end of month") ;; → {:text "deploy" :when "..." ;; :priority 1} ;; JSON & HTTP builtins (json-parse "{\"a\": 1}") ;; → {:a 1} (http-get "https://api.example.com/data") ;; → {:status 200 :body "..."}
;; Embed in Markdown, run with ⌘⇧Return ```eelisp (add-item (smart-parse "tomorrow review report !!")) ``` ```result Added item #42 ``` ;; Inline calculations in your notes ```eelisp (* 24 365) ``` ```result 8760 ```
;; Read/write files in your workspace (write-file "todo.txt" (str "Generated: " (today))) (read-file "notes.md") ;; Interact with the editor (insert-at (cursor-pos) (str "Updated: " (today))) (def sel (selection)) ;; Clipboard access (clipboard-set "copied!") (clipboard-get)
;; Choice fields (dropdown pickers) (defform new-task (title:string (priority:choice "low" "medium" "high") (status:choice "todo" "in-progress" "done") notes:memo)) ;; Table-backed form with CRUD (defform project-editor (name:string (status:choice "active" "done") budget:number) :source projects)
Every action is one shortcut away. All shortcuts are also shown in the in-app help overlay.
Everything you need to know to get the most out of EEditor.
When you first launch EEditor, you will see an empty editor with a sidebar. To begin working:
EEditor provides a distraction-free writing environment with real-time syntax highlighting.
YYYY-MM-DD HH:mm) at the cursor position.The sidebar shows a hierarchical view of your folder. Directories are sorted first, then files alphabetically.
subfolder/file.md to create nested files.The sidebar has three modes accessible via the segmented toggle at the top: Files (file tree), Calendar (see section 8), and Agenda (see section 8b).
Each file you open appears as a tab at the top of the editor area.
Full-text search (⌘⇧F) searches across all files in your workspace:
Quick Open (⌘P) is a fuzzy file finder:
Wiki-links (⌘O): place your cursor on a [[filename]] link and press ⌘O to open that file directly. If no wiki-link is detected, the standard Open Folder dialog appears.
Markdown preview (⌘⇧P) renders your document as styled HTML:
PDF export (⌘⇧E) generates a print-ready document:
Daily note (⌘D):
YYYY-MM-DD.md (e.g., 2026-02-19.md) in your root folder.# 2026-02-19.Weekly planner (⌘⇧W):
YYYY-WEEK-WW.md (e.g., 2026-WEEK-08.md) using ISO 8601 week numbering.# Week 08 — 2026 ## Monday ### Morning - ### Afternoon - ### Evening - ## Tuesday ...through Sunday
Toggle the sidebar to Calendar mode using the segmented control (folder / calendar / agenda icons) at the top of the sidebar.
Toggle the sidebar to Agenda mode using the clipboard icon in the segmented control. The agenda panel provides an at-a-glance view of your upcoming schedule:
:when date before today, shown with a red section header.(items), (items-on), and (items-between) queries behind the scenes. Add items in the REPL, then switch to the Agenda tab to see them organized by time.
EEditor includes a built-in Lisp interpreter called EELisp — designed for personal data management.
Opening the REPL:
Using the REPL:
> prompt and press Enter to evaluate it... and waits for more input. Press Escape (or the × button) to cancel.Pinning the REPL:
Clearing:
The REPL includes a SQLite database engine. By default it uses an in-memory database. When you open a folder containing an eelisp.db file, the REPL automatically connects to it for persistent storage (see Database Management). Table names and field definitions are written as bare symbols (no quoting needed).
Define a table:
(deftable tasks (title:string priority:number done:bool due:date))
Field types: string, number, bool, date, memo.
Insert records:
(insert tasks {:title "Write docs" :priority 1 :done false})
Query records:
;; All records (query tasks) ;; With filters (query tasks :where "priority <= ?" :params (list 2)) ;; Sorted, limited (query tasks :order "priority" :limit 5) ;; Select specific columns (query tasks :select "title, done")
Query results are displayed as formatted ASCII tables.
Interactive forms (browse):
;; Open a form to browse, edit, and manage records (browse tasks) ;; Browse with filters and sorting (browse tasks :where "done = ?" :params (list false) :order "priority")
The form shows one record at a time with Prev/Next navigation, inline editing, Save, New, and Delete buttons.
Update & delete:
;; Update record by ID (update tasks 1 {:done true}) ;; Soft-delete (dBASE-style) (delete tasks 1) ;; Purge soft-deleted records (pack tasks)
Other commands:
| Command | Description |
|---|---|
(tables) | List all table names |
(describe tasks) | Show table schema |
(count-records tasks) | Count records (with optional :where) |
(drop-table tasks) | Delete a table |
(field-get record :name) | Get a field from a record |
(record-id record) | Get the record's ID |
(records result-set) | Extract record list from query results |
(browse table) | Open a navigable table/grid view |
(edit table) | Open an interactive CRUD form |
(defform name (fields) :computed ...) | Create a calculator form with computed fields |
EELisp supports both in-memory and persistent SQLite databases.
Automatic connection:
eelisp.db file exists in the root, the REPL automatically connects to it.deftable and insert is persisted to disk.REPL database commands:
| Command | Description |
|---|---|
:db | Show the current database path |
:db <path> | Switch to a different database file |
:db new <name> | Create a new database file in the current folder |
:db memory | Switch to an in-memory database |
:db mydata.db instead of typing the full path.
EELisp includes a Lotus Agenda-inspired personal information manager. Add free-form items, auto-categorize with rules, define dynamic views, organize with hierarchical categories, and query with filters — all stored in SQLite.
Adding items:
;; Text-first — just type what's on your mind (add-item "Finish quarterly report for Sarah") ;; With metadata (add-item "Call dentist" :when "2026-03-01" :priority 2) (add-item "Buy groceries" :category "personal" :notes "milk, eggs, bread")
Querying items:
;; Browse all items (interactive table) (items) ;; Filter by category, search text, priority, or date (items :category "work") (items :search "quarterly") (items :priority 1) (items :when-before "2026-03-01")
Managing items:
| Command | Description |
|---|---|
(add-item text :when :priority :category :notes) | Create a new item |
(items :category :search :priority :when-before :when-after) | Query items with filters (table view) |
(item-get id) | Fetch a single item by ID |
(item-edit id) | Open item in form view for editing |
(item-set id :field val ...) | Update item properties |
(item-done id) | Mark item as done (soft-delete) |
(item-count :category cat) | Count items with optional filter |
Categories:
Categories are hierarchical (use / separators) and can be mutually exclusive:
;; Hierarchical categories (defcategory work) (defcategory work/projects) (defcategory work/meetings) (defcategory personal) (defcategory personal/errands) ;; Exclusive categories (item can only be in one child) (defcategory priority :exclusive true :children (high medium low)) ;; Assign / unassign (assign 1 "work/projects") (assign 1 "priority/high") (unassign 1 "personal") ;; View the category tree (categories)
:exclusive true, assigning one child (e.g., priority/low) automatically removes siblings (e.g., priority/high). This is great for status, priority, or any single-choice grouping.
Auto-Categorization Rules:
Define rules that automatically categorize items based on their content — the magic of Lotus Agenda:
;; Text-matching rules (defrule urgent-flag :when (str-contains text "URGENT") :assign "priority/high") (defrule meeting-detect :when (or (str-contains text "meeting") (str-contains text "call with")) :assign "work/meetings") ;; Date extraction via regex (defrule date-extract :when (str-matches text "\\b(\\d{4}-\\d{2}-\\d{2})\\b") :action (item-set id :when (match 1))) ;; Apply rules (apply-rules) ;; batch-apply to all items (apply-rules 42) ;; apply to item #42 (auto-categorize true) ;; auto-apply on every add-item
Rule management:
| Command | Description |
|---|---|
(defrule name :when cond :assign cat) | Define a categorization rule |
(defrule name :when cond :action expr) | Define a rule with custom action |
(apply-rules) | Apply all rules to all items |
(apply-rules id) | Apply all rules to a specific item |
(auto-categorize true/false) | Toggle auto-apply on insert |
(rules) | List all defined rules |
(drop-rule name) | Delete a rule |
str-contains for simple text matching, str-matches for regex patterns, has-category to check existing categories, and (get :property) to test item properties. Combine with and/or/not for complex logic.
Views — Dynamic Perspectives:
Define saved queries that filter, sort, and group items. Show a view anytime to see the latest matching items:
;; Define views with filter expressions (defview work-board :source items :filter (has-category "work") :group-by category :sort-by when) (defview urgent :source items :filter (= priority "1") :sort-by when) (defview inbox :source items :filter (= (length categories) 0)) ;; Show a view (show work-board) ;; → grouped by category with item counts (show urgent) ;; → filtered, sorted items
View management:
| Command | Description |
|---|---|
(defview name :source :filter :group-by :sort-by) | Define a saved view with filter expression |
(show view-name) | Display a view's matching items |
(views) | List all defined views |
(drop-view name) | Delete a view |
Date helpers:
| Command | Description |
|---|---|
(today) | Returns today's date as ISO string (e.g., "2026-02-21") |
(date-add date n :days/:weeks/:months) | Add days, weeks, or months to a date |
(date-diff date1 date2) | Difference in days between two dates |
text, notes, categories, all properties, plus has-category and overdue? helpers. Items with missing properties are silently excluded, not errors.
Calendar Integration:
View items by date and quickly add items for today:
;; View items for a specific date (items-on "2026-02-24") ;; View items in a date range (items-between "2026-02-24" "2026-02-28") ;; Quick-add with today's date auto-filled (add-item-today "Review pull requests" :priority 1) (add-item-today "Quick grocery run" :notes "milk, bread") ;; Combine with date helpers (items-on (today)) (items-between (today) (date-add (today) 7 :days))
| Command | Description |
|---|---|
(items-on "date") | Show items scheduled for a specific date |
(items-between "start" "end") | Show items in a date range (inclusive) |
(add-item-today text :priority :category :notes) | Add item with today's date auto-filled |
Smart Input:
The add command parses natural language to extract dates, priorities, and people automatically:
;; Natural language item creation (add "Meet Alice tomorrow for coffee !!") ;; → Item with :when = tomorrow, :priority 2, :who "Alice" (add "Call Bob next Monday about the project") ;; → Item with :when = next Monday, :who "Bob" (add "URGENT fix server crash") ;; → Item with :priority 1 ;; Preview without creating an item (smart-parse "email Sarah March 15 about renewal !!") ;; → {:text "email Sarah about renewal" :when "2026-03-15" :priority 2 :who ("Sarah")}
| Command | Description |
|---|---|
(add "text") | Smart-add: parse natural language, extract metadata, create item |
(smart-parse "text") | Preview extraction without creating an item |
tomorrow/today/yesterday, next Monday, this weekend, in 3 days, end of week/month, March 15. Priority: URGENT/ASAP, !!!/!!/!, high/low priority. People: @name, with/for/from Name, call/email/meet Name.
HTTP & JSON:
Generic HTTP client and JSON builtins for API integration:
| Command | Description |
|---|---|
(http-get url) | GET request, returns {:status N :body "..."} |
(http-post url body :content-type "...") | POST request with body |
(json-parse string) | Parse JSON string to EELisp dict/list |
(json-stringify value) | Convert EELisp value to JSON string |
Recurring Items & Templates:
Items with recurrence patterns auto-create the next occurrence when marked done. Templates define reusable item blueprints:
| Command | Description |
|---|---|
(add-item text :recur :weekly) | Create a recurring item (:daily, :weekly, :monthly) |
(every N :unit) | Custom interval, e.g. (every 3 :months) |
(item-done id) | Mark done; auto-creates next if recurring |
(deftemplate name :text "..." :category "..." :priority N) | Define a reusable item template |
(from-template name :when "..." ...) | Create item from template with overrides |
(templates) | List all defined templates |
(drop-template name) | Remove a template |
Multiple Agendas:
Open, switch between, and manage multiple agenda databases. Export and import for backup or migration:
| Command | Description |
|---|---|
(open-agenda "path/to/file.db") | Open a new agenda database and switch to it |
(use-agenda name) | Switch to an already-open agenda |
(agendas) | List all open agendas with active marker |
(close-agenda name) | Close and remove an agenda from the registry |
(export-agenda name :format :json :path "file.json") | Export all agenda tables to JSON |
(import-agenda "file.json") | Import records from a JSON export |
Variables & functions:
(def x 42) (defn square (x) (* x x)) (square 5) ; → 25 ;; Anonymous functions (map (fn (x) (* x x)) '(1 2 3)) ; → (1 4 9) ;; Rest parameters (defn sum (first . rest) (reduce + first rest))
Conditionals & loops:
(if (> x 0) "positive" "non-positive") (cond (< x 0) "negative" (= x 0) "zero" else "positive") (let ((x 1) (y 2)) (+ x y)) ; → 3 ;; Loop with named-let style (loop ((i 0) (acc 0)) (> i 10) (recur (+ i 1) (+ acc i))) ; → 55
Strings:
(str "hello" ", " "world") ; → "hello, world" (str-split "a,b,c" ",") ; → ("a" "b" "c") (str-join '("a" "b" "c") "-") ; → "a-b-c" (str-upper "hello") ; → "HELLO"
Lists:
(list 1 2 3) ; → (1 2 3) (cons 0 '(1 2)) ; → (0 1 2) (map inc (range 1 6)) ; → (2 3 4 5 6) (filter even? (range 1 11)) ; → (2 4 6 8 10) (reduce + 0 '(1 2 3 4 5)) ; → 15 (sort-by id '(3 1 4 1 5)) ; → (1 1 3 4 5)
Dictionaries:
(def d {:name "Alice" :age 30})
(dict-get d :name) ; → "Alice"
(dict-set d :age 31) ; → {:name "Alice" :age 31}
(dict-keys d) ; → (:name :age)
Prelude functions:
| Function | Description |
|---|---|
inc / dec | Increment / decrement by 1 |
even? / odd? | Parity checks |
zero? / pos? / neg? | Sign checks |
first / second / third / last | List access |
take / drop | Take/drop first n elements |
some? / every? | Any/all match predicate |
pipe | Threading macro: (pipe 5 inc inc square) |
compose | Function composition |
partial | Partial application |
query, deftable, defn), and a clear button.