Skip to main content

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 and Debug Adapter Protocol support, subprocess execution, and HTTP requests.

Architecture Documentation

  • System architecture — component boundaries, state ownership, lifecycle, and integrations.
  • Data flow — interactive traces from input and file I/O through buffers, services, and rendering.

Debug Adapter Protocol

Denary can launch any stdio-based DAP adapter through named profiles in the user init.py. For example, a Python profile using debugpy is:

DAP_CONFIG = {
    "Python: current file": {
        "cmd": ["python", "-m", "debugpy.adapter"],
        "request": "launch",
        "arguments": {
            "program": "${file}",
            "cwd": "${workspaceFolder}",
        },
    },
}

${file}, ${fileDirname}, and ${workspaceFolder} are expanded throughout the profile. The adapter itself must be installed separately.

Core debugger commands are debug_start, debug_stop, debug_toggle_breakpoint, debug_continue, debug_pause, debug_next, debug_step_in, debug_step_out, debug_threads, debug_stack, debug_variables, and debug_output. Breakpoints are restored from the project session file.

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.

AI Chat

Denary can open a persistent, project-specific Ollama conversation beside the current code pane. Start Ollama, install at least one model, then run:

:ai_chat

The chat pane streams responses and keeps its conversation history outside the project in Denary's platform data directory. It sends conversation history but does not include editor code unless you explicitly attach the current selection or buffer.

Chat key Action
Enter / i Enter the multiline composer
Ctrl+G Send the composed message
Enter Insert a newline while composing
Esc Return to chat navigation while retaining the draft
Ctrl+P Paste the system or Denary clipboard into the composer
a Attach the current selection, current buffer, or any open buffer
x Detach a pending attachment before sending
n / h Start a new chat / open chat history
m Choose an AI provider/model
c / r Cancel the active response / retry the last turn
R / d Rename / delete the active conversation
j, k, arrows, page keys, g, G Move through the transcript
v / y Start or end a line selection / copy selected chat text
q Close the chat pane without deleting its history

Ollama keeps its native API workflow, and providers are configured in the user init.py:

AI_PROVIDERS = {
    "ollama": {
        "type": "ollama",
        "base_url": "http://localhost:11434",
    },
    "cloud": {
        "type": "openai_compatible",
        "base_url": "https://api.example.com/v1",
        "api_key_env": "CLOUD_API_KEY",
        "context_window": 8192,
        "models": ["coding-model"],
    },
}

For a remote Ollama server, replace the URL with its reachable HTTP address. External OpenAI-compatible providers can be added without provider-specific code. api_key_env is only the name of the environment variable containing the API key; it is not the key itself and should not include Bearer.

For the example above, set the key in your shell before starting Denary:

export CLOUD_API_KEY="your-secret-key"

The editor reads CLOUD_API_KEY and sends it as Authorization: Bearer .... Do not store the actual API key in static_config.json.

The chat role is used by chat and non-inline AI commands. The autocomplete role controls inline completion. Existing model and ai_autocomplete_model settings continue to work as Ollama defaults.

Related commands use the ai_chat_* prefix and are listed by :help.

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
saveas [path] Save under a new path
wall / wa Save all modified buffers
wq Save and close current buffer
wqa Save all modified buffers and exit
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
rln Toggle relative line numbers
wordwrap / ww Toggle soft word wrapping
completions / lspcomp Toggle LSP completion suggestions
gch Toggle Git highlight
scrolloff <n> Set scroll offset
tabsize <n> Set current buffer tab width
indent <style> Use spaces or tabs for future indentation
indentdefaults <style> <n> Persist indentation defaults for new files
retab [style] [n] Convert leading indentation without changing visual width

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

LSP Completion

In Insert mode, press Ctrl+Space to request completion at the cursor. A language server may also request completion automatically when you type one of its advertised trigger characters (for example, .). The completion picker combines language-server results with words found in open buffers and matching file names when the cursor is in a string.

Use the picker to filter and select an entry. LSP entries show their kind and detail, and their documentation appears in the preview pane; when supported, Denary resolves missing documentation from the server on demand. Selecting an entry applies its replacement range, snippet text, UTF-16 positions, and any additional edits supplied by the server. Language-server commands attached to an item are executed after the edits are applied.

Completion requires an LSP configured for the current file extension. If no usable LSP is available, Ctrl+Space still opens the local word and file completion picker. This picker is separate from AI autocomplete: AI suggestions appear as ghost text and are controlled by the ai_autocomplete_* commands.


AI Commands

Denary automatically requests AI code completions after a short pause in Insert mode at safe code boundaries, including before closing delimiters and between tokens. Requests are suppressed in the middle of identifiers and in an empty, context-free file. Suggestions appear as dim virtual text: press Tab to accept the entire suggestion, or Esc to dismiss it without leaving Insert mode. A second Esc returns to Normal mode.

Command Description
ai Send a custom instruction to the AI
ai_gen Manually request inline autocomplete
ai_autocomplete_toggle Pause/resume autocomplete for this session
`ai_autocomplete_default on off`
ai_autocomplete_multifile_toggle Toggle open-buffer context for this session
`ai_autocomplete_multifile_default on off`
ai_autocomplete_model Choose a dedicated autocomplete model
ai_autocomplete_accept Accept the visible suggestion
ai_autocomplete_dismiss Dismiss the visible suggestion
ai_review Review Git diff for issues
ai_commit_message Generate a commit message from Git diff
ai_get Retrieve a non-inline AI result at the cursor
ai_show Show the non-inline AI result picker
ai_remove Cancel/remove a non-inline AI request
change_ai_model Change the shared AI model
editor_info / info Show editor state and diagnostics

Static configuration accepts ai_autocomplete_enabled (default true), ai_autocomplete_model (falls back to model), and ai_autocomplete_delay_ms (default 600, clamped to 100–5000 ms). All Ollama autocomplete requests use the Ollama provider's base_url; external autocomplete requests use the selected OpenAI-compatible provider. Ollama remains the default. ai_autocomplete_multifile_enabled defaults to true. Related context comes only from loaded open buffers and uses their unsaved in-memory content. The ai_autocomplete_related_context_chars setting accepts "auto" (the default) or a numeric upper bound. Automatic sizing reads the selected model's Ollama context metadata when available (or the provider's configured context window), reserves room for the current file and response, and uses only the remaining budget for relevant buffer excerpts.


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

AI Static Configuration

AI provider definitions belong in init.py; provider and model selections are persisted automatically in static_config.json. A provider example is available at examples/init.py.

Set "completions": false in static_config.json, or run the completions command, to disable LSP suggestion popups.

For example, on Linux:

cp examples/init.py ~/.config/denary/init.py
export CLOUD_API_KEY="your-secret-key"

To use remote Ollama, change its base_url, for example:

AI_PROVIDERS["ollama"]["base_url"] = "http://my-ollama-server:11434"

api_key_env must contain only the environment variable name, such as CLOUD_API_KEY; never put the secret itself in a configuration file.


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": {
        "name": "sqls",
        "cmd": [
            "/home/user/go/bin/sqls",
        ],
    },
}

name is optional and defaults to the language-server executable name. It gives server-specific command workflows, such as SQLS connection switching, a stable identity.


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

  1. You open a file:

    denary main.py
    
  2. Denary checks the file extension (.py)

  3. It maps it using EXT_TO_LANG

  4. It loads the corresponding LSP from LSP_CONFIG

  5. The language server is started using the cmd array


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

  • cmd must be a list (not a string)
  • Paths must be absolute
  • The language key in EXT_TO_LANG must exist in LSP_CONFIG
  • If an extension is not mapped, no LSP will be started

Download files

Download the file for your platform. If you're not sure which to choose, learn more about installing packages.

Source Distribution

denary-0.0.136.tar.gz (238.5 kB view details)

Uploaded Source

Built Distribution

If you're not sure about the file name format, learn more about wheel file names.

denary-0.0.136-py3-none-any.whl (255.5 kB view details)

Uploaded Python 3

File details

Details for the file denary-0.0.136.tar.gz.

File metadata

  • Download URL: denary-0.0.136.tar.gz
  • Upload date:
  • Size: 238.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

Hashes for denary-0.0.136.tar.gz
Algorithm Hash digest
SHA256 169e6919a879a4ade403207f388d67b683358568a3e5ec43d5e30266f800ea5d
MD5 dae6b905b076685f7117cd16d0003abd
BLAKE2b-256 73e7fa543ee702334094828523248bef8c440d1cb13a27597d2b1bd465012a30

See more details on using hashes here.

File details

Details for the file denary-0.0.136-py3-none-any.whl.

File metadata

  • Download URL: denary-0.0.136-py3-none-any.whl
  • Upload date:
  • Size: 255.5 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

Hashes for denary-0.0.136-py3-none-any.whl
Algorithm Hash digest
SHA256 6e2ae97132567782517ca936ed932c4a39c20e9afced97438dee6b82cacd1fab
MD5 c17e36130e09545c0f8b948b16d70c95
BLAKE2b-256 5b1f069a5c41cfca479df81c9084dd4d5258132a88a10bc1361fb5ba7c2b7e00

See more details on using hashes here.

Release history Release notifications | RSS feed

0.0.146

2 files

0.0.145

2 files

0.0.144

2 files

0.0.143

2 files

0.0.142

2 files

0.0.141

2 files

0.0.140

2 files

0.0.139

2 files

0.0.138

2 files

0.0.137

2 files

This release

0.0.136 This release

2 files

0.0.135

2 files

0.0.134

2 files

0.0.133

2 files

0.0.132

2 files

0.0.131

2 files

0.0.130

2 files

0.0.129

2 files

0.0.128

2 files

0.0.127

2 files

0.0.126

2 files

0.0.125

2 files

0.0.124

2 files

0.0.123

2 files

0.0.122

2 files

0.0.121

2 files

0.0.120

2 files

0.0.119

2 files

0.0.118

2 files

0.0.117

2 files

0.0.116

2 files

0.0.115

2 files

0.0.114

2 files

0.0.113

2 files

0.0.112

2 files

0.0.111

2 files

0.0.110

2 files

0.0.109

2 files

0.0.108

2 files

0.0.107

2 files

0.0.106

2 files

0.0.105

2 files

0.0.104

2 files

0.0.103

2 files

0.0.102

2 files

0.0.101

2 files

0.0.100

2 files

0.0.99

2 files

0.0.98

2 files

0.0.97

2 files

0.0.96

2 files

0.0.95

2 files

0.0.94

2 files

0.0.93

2 files

0.0.92

2 files

0.0.91

2 files

0.0.90

2 files

0.0.89

2 files

0.0.88

2 files

0.0.87

2 files

0.0.86

2 files

0.0.85

2 files

0.0.84

2 files

0.0.83

2 files

0.0.82

2 files

0.0.81

2 files

0.0.80

2 files

0.0.79

2 files

0.0.78

2 files

0.0.77

2 files

0.0.76

2 files

0.0.75

2 files

0.0.74

2 files

0.0.73

2 files

0.0.72

2 files

0.0.71

2 files

0.0.70

2 files

0.0.69

2 files

0.0.68

2 files

0.0.67

2 files

0.0.66

2 files

0.0.65

2 files

0.0.63

2 files

0.0.62

2 files

0.0.61

2 files

0.0.60

2 files

0.0.59

2 files

0.0.58

2 files

0.0.57

2 files

0.0.56

2 files

0.0.55

2 files

0.0.54

2 files

0.0.53

2 files

0.0.52

2 files

0.0.51

2 files

0.0.50

2 files

0.0.49

2 files

0.0.48

2 files

0.0.47

2 files

0.0.46

2 files

0.0.45

2 files

0.0.44

2 files

0.0.43

2 files

0.0.42

2 files

0.0.41

2 files

0.0.40

2 files

0.0.39

2 files

0.0.38

2 files

0.0.37

2 files

0.0.36

2 files

0.0.35

2 files

0.0.34

2 files

0.0.33

2 files

0.0.32

2 files

0.0.31

2 files

0.0.30

2 files

0.0.29

2 files

0.0.28

2 files

0.0.27

2 files

0.0.26

2 files

0.0.25

2 files

0.0.24

2 files

0.0.23

2 files

0.0.22

2 files

0.0.21

2 files

0.0.20

2 files

0.0.19

2 files

0.0.18

2 files

0.0.17

2 files

0.0.16

2 files

0.0.15

2 files

0.0.14

2 files

0.0.13

2 files

0.0.12

2 files

0.0.11

2 files

0.0.10

2 files

0.0.9

2 files

0.0.8

2 files

0.0.7

2 files

0.0.6

2 files

0.0.5

2 files

0.0.4

2 files

0.0.3

2 files

0.0.2

2 files

0.0.1

2 files

Anthropic, PBC Visionary sponsor Bloomberg Visionary sponsor Hudson River Trading Visionary sponsor Meta Visionary sponsor NVIDIA Visionary sponsor Microsoft Sustainability sponsor Depot Continuous Integration AWS Cloud computing and Security Sponsor Datadog Monitoring Fastly CDN Google Download Analytics Sentry Error logging StatusPage Status page