A curses-based text editor with syntax highlighting
Project description
Editor Documentation
,gggggggggggg,
dP"""88""""""Y8b,
Yb, 88 `8b,
`" 88 `8b
88 Y8
88 d8 ,ggg, ,ggg,,ggg, ,gggg,gg ,gggggg, gg gg
88 ,8P i8" "8i ,8" "8P" "8, dP" "Y8I dP""""8I I8 8I
88 ,8P' I8, ,8I I8 8I 8I i8' ,8I ,8' 8I I8, ,8I
88______,dP' `YbadP' ,dP 8I Yb,,d8, ,d8b,,dP Y8,,d8b, ,d8I
888888888P" 888P"Y8888P' 8I `Y8P"Y8888P"`Y88P `Y8P""Y88P"888
,d8I'
,dP'8I
,8" 8I
I8 8I
`8, ,8I
`Y8P"
A modal terminal editor built on curses with a command-driven, event-based architecture and chunked text storage.
Includes syntax highlighting, multi-buffer management, registers and markers, undo/redo history, Git integration, async LSP support, subprocess execution, and HTTP requests.
Installation
Install via pip (Recommended)
If your package is published:
pip install denary
Then run:
denary
Or open a file directly:
denary main.py
Install from Source
Clone the repository:
git clone https://gitlab.com/jordaly/editor.git denary
cd denary
Using Poetry:
poetry install
poetry run python -m denary
Or using plain pip:
pip install -e .
denary
Recommended (Better): Alias That Runs Denary Directly
Instead of activating the venv every time, just point the alias to the venv’s binary.
Assume your venv is here:
~/venvs/denary_venv
Edit your ~/.bashrc:
nano ~/.bashrc
Add this line:
alias denary="~/venvs/denary_venv/bin/denary"
Then reload:
source ~/.bashrc
Now you can run:
denary
denary myfile.py
Quick Start
Open a File
denary myfile.py
If the file does not exist, it will be created.
Key Bindings
Arrow & Basic Movement
| Shortcut | Action |
|---|---|
| Right | Move cursor right |
| Left | Move cursor left |
| Up | Move cursor up |
| Down | Move cursor down |
| PageUp | Page up |
| PageDown | Page down |
| Ctrl+Right | Jump right (word/semantic move) |
| Ctrl+Left | Jump left (word/semantic move) |
| Ctrl+Up | Scroll / move up (mode dependent) |
| Ctrl+Down | Scroll / move down (mode dependent) |
Vim-Style Normal Mode Navigation
| Shortcut | Action |
|---|---|
| h | Move left |
| j | Move down |
| k | Move up |
| l | Move right |
| 0 | Jump to start of line |
| $ | Jump to end of line |
| w / W | Move forward by word |
| b / B | Move backward by word |
| e / E | Move to end of word |
| f / F | Find character forward/backward |
| g | Go command prefix |
| G | Go to bottom |
| H | Jump to top of visible window |
| L | Jump to bottom of visible window |
| % | Jump to matching bracket |
Insert & Editing (Normal Mode)
| Shortcut | Action |
|---|---|
| i | Enter insert mode |
| a | Append after cursor |
| A | Append at end of line |
| o | Insert new line below |
| O | Insert new line above |
| x | Delete character |
| d | Delete (operator) |
| c | Change (operator) |
| y | Yank (copy) |
| p | Paste after |
| P | Paste before |
| u | Undo |
| U | Undo line / extended undo |
| J | Join lines |
| > | Indent right |
| < | Indent left |
Search & Repeat
| Shortcut | Action |
|---|---|
| / | Search forward |
| n | Next match |
| N | Previous match |
| ***** | Search word under cursor |
Marks & Registers
| Shortcut | Action |
|---|---|
| m | Set marker |
| ' | Jump to marker (linewise) |
| ` | Jump to marker (exact position) |
| @ | Execute macro |
| q | Start/stop macro recording |
LSP / Diagnostics (Normal Mode)
| Shortcut | Action |
|---|---|
| K | Show LSP hover / definition |
| ; | LSP-related navigation |
| Ctrl+D | Diagnostics-related action |
(Exact behavior depends on your handler implementations.)
Alt-Based Commands
| Shortcut | Action |
|---|---|
| Alt+; | Toggle selection mode |
| Alt+Right / Alt+. | Indent right |
| Alt+Left / Alt+, | Indent left |
| Alt+/ | Alternative slash action |
| Alt+h / Alt+l | Alternative horizontal move |
| Alt+j / Alt+k | Move lines or cursor |
| Alt+Up / Alt+Down | Scroll view |
| Alt+9 / Alt+0 | Jump line start/end |
| Alt+w | File search |
| Alt+r | Custom action |
| Alt+i / Alt+o | Punctuation jump |
| Alt+[ | Bracket-related action |
| Alt+' | Quote-related action |
Shift Selection
| Shortcut | Action |
|---|---|
| Shift+Right | Expand selection right |
| Shift+Left | Expand selection left |
| Shift+Up | Expand selection up |
| Shift+Down | Expand selection down |
| Ctrl+Shift+Right | Expand selection by word |
| Ctrl+Shift+Left | Expand selection by word |
Misc
| Shortcut | Action |
|---|---|
| Ctrl+A | Select all |
| Ctrl+P | Previous buffer |
| Ctrl+X | Cut / special action |
| Ctrl+O | Open file |
| Ctrl+I | Jump forward in jump list |
| Q | Quit variant |
| PageUp / PageDown | Scroll |
Commands
Buffer Actions
| Command | Description |
|---|---|
h / help |
Show help |
q |
Exit editor |
w |
Save current buffer |
ls |
List open buffers |
open |
Open a file |
bufnew |
Create new buffer |
nbuf / nextbuf / next / n |
Next buffer |
pbuf / prevbuf / prev / p |
Previous buffer |
delbuf / d |
Delete buffer |
ff |
File search |
ffr |
New file search |
fb |
Buffer picker |
ex |
File explorer |
UI & Behavior
| Command | Description |
|---|---|
themes |
Change editor theme |
ln |
Toggle line numbers |
gch |
Toggle Git highlight |
scrolloff <n> |
Set scroll offset |
Markers
| Command | Description |
|---|---|
m / marker |
Add local/global marker |
pm |
Pick marker |
dm |
Delete markers in this buffer |
dgm |
Delete global markers |
dpm |
Delete specific marker |
dam |
Delete all markers |
Text Utilities
| Command | Description |
|---|---|
clear_registers |
Reset registers |
reset_h_matches |
Reset highlighting |
replace |
Replace text |
comment |
Toggle comment |
upper / lower / capitalize |
Change case |
filetype <type> |
Set syntax mode |
Utilities
| Command | Description |
|---|---|
align |
Align text by a delimiter (e.g. =, :, ,, ` |
column_edit |
Insert, replace, or delete text within column ranges per line |
date |
Copy date |
datetime |
Copy current date & time |
file_path |
Copy current file path |
json_format |
Format JSON in selection or entire file |
number_lines |
Add line numbers to each line |
rejoin |
Split and rejoin selection or entire file using a delimiter |
remove_empty_lines |
Remove blank or whitespace-only lines |
reverse_lines |
Reverse the order of lines |
shuffle_lines |
Randomly shuffle lines |
slugify |
Convert text to URL-friendly slug format |
sort / sort_r |
Sort selection or entire file by lines (normal or reverse) |
trim / trim_l / trim_r |
Trim whitespace (both, left, or right) per line |
unique_lines / dedup |
Remove duplicate lines (keep first occurrence) |
uuid |
Copy random UUID |
wrap |
Wrap text to a specified width |
json_format |
formats json with indentation |
Git Commands
| Command | Description |
|---|---|
git_status |
Show Git status |
git_diff |
Show diff |
git_commit_all |
Commit all |
git_blame |
Show blame |
git_log |
Show history |
git_refresh |
Refresh state |
git_hunk |
Show previous version of changed code |
LSP Commands
| Command | Description |
|---|---|
lsp_diagnostics |
Show buffer diagnostics |
lsp_hover |
Show hover info |
lsp_definition |
Go to definition |
lsp_refresh |
Restart LSP for buffer |
lsp_references |
References picker |
lsp_line_diagnostics |
Show diagnostics for current line |
lsp_clear_diagnostics |
Clear diagnostics |
lsp_clear_line_diag / clrld |
Clear diagnostics for current line |
AI Commands
| Command | Description |
|---|---|
ai |
Send a custom instruction to the AI |
ai_gen |
Generate code at the cursor |
ai_review |
Review Git diff for issues |
ai_commit_message |
Generate a commit message from Git diff |
ai_get |
Retrieve AI result at the cursor |
ai_show |
Show AI completion picker |
ai_remove |
Cancel/remove AI completion at the cursor |
change_ai_model |
Change the active AI model |
HTTP Request Execution
Select a block and run:
| Command | Description |
|---|---|
req |
Make http request |
Request Format
<HTTP_METHOD> <URL>
<Header>: <Value>
<Header>: <Value>
<Optional Body>
Examples
GET:
GET https://jsonplaceholder.typicode.com/todos/1
Accept: application/json
POST:
POST https://jsonplaceholder.typicode.com/posts
Content-Type: application/json
{
"title": "Hello world",
"body": "Testing request from editor",
"userId": 1
}
Regex Replacement
Perform regex-based find and replace in the current buffer.
| Command | Description |
|---|---|
replace |
Run a regex search and replace |
Capture Groups
Parentheses create capture groups that can be reused in the replacement.
| Syntax | Meaning |
|---|---|
\0 |
Entire match |
\1 |
First group |
\2 |
Second group |
\3 |
Third group |
Example
Find
(\w+)\s+(\w+)
Replace
\2 \1
Result
hello world → world hello
Named Capture Groups
Groups can also be named.
(?P<name>pattern)
Named groups are referenced in the replacement using:
\g<name>
Example
Find
(?P<first>\w+)\s+(?P<last>\w+)
Replace
\g<last> \g<first>
Result
john doe → doe john
Case Transformations
The replacement string supports case modifiers.
| Syntax | Effect |
|---|---|
\U |
Uppercase until \E |
\L |
Lowercase until \E |
\u |
Uppercase next character |
\l |
Lowercase next character |
\E |
End transformation |
Example
Find
select
Replace
\U\0\E
Result
select → SELECT
Word Boundaries
\b matches the boundary between a word and a non-word character.
Example
Find
\bselect\b
Matches
select
Does not match
selected
Example: SQL Keyword Formatting
Find
\b(select|from|where|join|group\s+by|order\s+by)\b
Replace
\U\0\E
Result
select name from users where id = 1
↓
SELECT name FROM users WHERE id = 1
Configuration
Denary loads its configuration from a platform-specific directory.
Config Location
Linux
~/.config/denary/init.py
macOS
~/Library/Application Support/denary/init.py
Windows (WSL recommended)
~/.config/denary/init.py
If the file does not exist, create it manually.
Example:
mkdir -p ~/.config/denary
nano ~/.config/denary/init.py
LSP Configuration
The init.py file allows you to:
- Configure Language Servers (LSPs)
- Map file extensions to language identifiers
- Provide custom initialization settings
Denary expects two dictionaries:
LSP_CONFIG = {}
EXT_TO_LANG = {}
Example Configuration
LSP_CONFIG
LSP_CONFIG = {
"python": {
"cmd": [
"/home/user/.config/denary/pylsp_venv/bin/python",
"-m",
"pylsp",
],
"settings": {
"pylsp": {
"plugins": {
"flake8": {"enabled": True, "maxLineLength": 100},
"pyflakes": {"enabled": False},
"pycodestyle": {"enabled": False},
}
},
},
},
"javascript": {
"cmd": [
"/home/user/.config/denary/ts_server/node_modules/.bin/typescript-language-server",
"--stdio",
],
},
"c": {
"cmd": [
"/home/user/.config/denary/clangd_venv/bin/clangd",
"--all-scopes-completion",
"--clang-tidy",
"--offset-encoding=utf-8",
],
},
"csharp": {
"cmd": [
"mono",
"/home/user/.config/denary/omnisharp/OmniSharp.exe",
"--languageserver",
],
},
"sql": {
"cmd": [
"/home/user/go/bin/sqls",
],
},
}
EXT_TO_LANG
This maps file extensions to language keys defined in LSP_CONFIG.
EXT_TO_LANG = {
".py": "python",
".c": "c",
".h": "c",
".cpp": "c",
".js": "javascript",
".ts": "javascript",
".jsx": "javascript",
".tsx": "javascript",
".cs": "csharp",
".sql": "sql",
}
How It Works
-
You open a file:
denary main.py -
Denary checks the file extension (
.py) -
It maps it using
EXT_TO_LANG -
It loads the corresponding LSP from
LSP_CONFIG -
The language server is started using the
cmdarray
Recommended Structure for LSP Tools
It is recommended to keep all LSP-related tools inside:
~/.config/denary/
Example layout:
~/.config/denary/
├── init.py
├── pylsp_venv/
├── clangd_venv/
├── ts_server/
└── omnisharp/
This keeps your setup portable and isolated.
Important Notes
cmdmust be a list (not a string)- Paths must be absolute
- The language key in
EXT_TO_LANGmust exist inLSP_CONFIG - If an extension is not mapped, no LSP will be started
Project details
Release history Release notifications | RSS feed
Download files
Download the file for your platform. If you're not sure which to choose, learn more about installing packages.
Source Distribution
Built Distribution
Filter files by name, interpreter, ABI, and platform.
If you're not sure about the file name format, learn more about wheel file names.
Copy a direct link to the current filters
File details
Details for the file denary-0.0.12.tar.gz.
File metadata
- Download URL: denary-0.0.12.tar.gz
- Upload date:
- Size: 137.5 kB
- Tags: Source
- Uploaded using Trusted Publishing? No
- Uploaded via: poetry/2.3.3 CPython/3.13.0 Linux/5.15.167.4-microsoft-standard-WSL2
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
d3e6106723340a6fea3379643bc7046902491bab85c5011d8a74861f5639ccf8
|
|
| MD5 |
0828b8c45317923df1b2923a51b03162
|
|
| BLAKE2b-256 |
b912975ffe6fad94a2471396c309f68c2bc29981fd8eaa9c4775800e01168542
|
File details
Details for the file denary-0.0.12-py3-none-any.whl.
File metadata
- Download URL: denary-0.0.12-py3-none-any.whl
- Upload date:
- Size: 144.4 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? No
- Uploaded via: poetry/2.3.3 CPython/3.13.0 Linux/5.15.167.4-microsoft-standard-WSL2
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
46802c51ce7c171e99408f8e44df2c831eca8b980b92448fefdfb3c4c2001dba
|
|
| MD5 |
8fda7502651e774d116c8aecd02fbd56
|
|
| BLAKE2b-256 |
f046b59bd49c5930c467dc5750a37de18979b8705f904ec705158d1bf0a4cdd8
|