Metadata-Version: 2.4
Name: candat
Version: 1.6.0
Summary: A terminal text editor with emacs keybindings, built on Textual — file tree, markdown preview, CSV table viewer, a large-file pager, and a real PTY terminal
Keywords: editor,terminal,tui,emacs,textual,markdown
Author: Petr Novak
Author-email: Petr Novak <petr@umbr.cas.cz>
License-Expression: MIT
License-File: LICENSE
Classifier: Development Status :: 5 - Production/Stable
Classifier: Environment :: Console
Classifier: Intended Audience :: Developers
Classifier: Operating System :: POSIX :: Linux
Classifier: Operating System :: MacOS
Classifier: Programming Language :: Python :: 3.10
Classifier: Programming Language :: Python :: 3.11
Classifier: Programming Language :: Python :: 3.12
Classifier: Programming Language :: Python :: 3.13
Classifier: Topic :: Text Editors
Requires-Dist: pyte>=0.8.2
Requires-Dist: tomli>=2 ; python_full_version < '3.11'
Requires-Dist: textual[syntax]>=8.2.8,<9
Requires-Dist: tree-sitter-language-pack>=1.12.2
Requires-Python: >=3.10
Project-URL: Homepage, https://github.com/kavonrtep/candat
Project-URL: Repository, https://github.com/kavonrtep/candat
Project-URL: Issues, https://github.com/kavonrtep/candat/issues
Description-Content-Type: text/markdown

# candat

A terminal text editor with emacs keybindings, built on [Textual](https://textual.textualize.io/).

## Demo

![candat — a terminal editor with emacs keybindings](https://raw.githubusercontent.com/kavonrtep/candat/main/docs/brag.gif)

More than the basics, without leaving the terminal — emacs keys, split windows
on the same file, live markdown preview with smart markdown editing, a CSV
table viewer, a large-file pager, and a real shell inside.

## Features

- **Emacs editing** — kill ring, mark/region, incremental search with match
  highlighting, query-replace, line moving, comment toggle, and an `M-x`
  command palette; fixed emacs bindings by design.
- **File tree** — live path filter, dired-style refresh and copy-path,
  resizable by keys or mouse, configurable icons.
- **Buffers and windows** — tabs, side-by-side or stacked splits showing the
  same buffer as linked views with independent cursors.
- **Markdown** — live side-by-side preview (scroll-synced, rendered in the
  background so big documents never block typing), plus structural editing:
  smart Enter for lists/quotes, `M-q` paragraph fill and table alignment,
  table cell navigation, checkbox and emphasis helpers.
- **Table viewer** — `.csv`/`.tsv` (or any delimited buffer) as a table with
  no row limit, background search and regex row filter.
- **Documents** — `.docx` and `.odt` open as rendered markdown (read-only;
  via pandoc when installed, a built-in converter otherwise), and the
  markdown can be exported with `C-x C-w`.
- **Large-file pager** — `less`-style streaming view for multi-GB files,
  with follow mode and background search; binary files are guarded.
- **Terminal inside** — a real PTY panel with scrollback, and `C-c C-c` to
  send the region or line to it as a REPL.
- **System clipboard** — copies mirror over OSC 52 (works through SSH and
  tmux) with a local wl-copy/xclip fallback, configurable.
- **Safety** — atomic saves, encoding/line-ending preservation, disk-change
  watching, session restore per project, and crash recovery snapshots.
- **Syntax highlighting** — python, markdown, json, yaml, bash, html, xml,
  css, toml, js, sql, go, rust, java, R, and config formats: INI/`.cfg`/
  `.conf`, Makefiles, Dockerfiles, and shell dotfiles (`.bashrc`, `.env`, …).

## Install

From [PyPI](https://pypi.org/project/candat/), as a standalone tool
(recommended — gives you a global `candat` command in its own isolated
environment):

```sh
uv tool install candat
# or, with pipx (needs Python >= 3.10):
pipx install candat
```

Or with plain pip into the environment of your choice: `pip install candat`.
To try it once without installing anything permanently: `uvx candat`.

The development version installs straight from GitHub:

```sh
uv tool install git+https://github.com/kavonrtep/candat
```

Requires Python >= 3.10, on Linux or macOS.

## Run

```sh
candat [FILE|DIR ...]
```

Passing a directory sets the file-tree root; files are opened in buffers.
Started bare, candat shows a welcome screen with the essential keys — type
to turn it into a scratch buffer, or `C-x C-f` straight into a file.

## Keys (so far)

| Key | Action |
| --- | --- |
| `C-x C-f` | find file (opens new file if it doesn't exist) |
| `C-x C-s` | save buffer |
| `C-x C-w` | write buffer to another file |
| `C-x C-q` | toggle read-only (status bar shows `%%`) |
| `C-x w` | toggle soft wrap for this buffer |
| `C-x C-r` | open a file read-only |
| `C-x k` | kill buffer |
| `C-x b` | buffer list (Enter switches; next buffer preselected) |
| `C-x o` | move focus: tree → window(s) → terminal |
| `C-x 3` / `C-x 2` | split window side-by-side / stacked (same buffer, linked view) |
| `C-x 0` / `C-x 1` | close this window / the others |
| `C-x C-c` | quit (confirms if unsaved buffers) |
| `C-x C-x` | exchange point and mark |
| `C-x h` | mark whole buffer |
| `C-x u`, `C-/`, `C-z` | undo |
| `C-f` `C-b` `C-n` `C-p` `C-a` `C-e` | char/line movement |
| `M-f` `M-b` | word movement |
| `C-v` `M-v` | page down / up |
| `M-<` `M->` | beginning / end of buffer |
| `C-space` | set mark (movement extends region) |
| `C-k` | kill line (consecutive kills accumulate) |
| `C-w` / `M-w` | kill / copy region |
| `C-y` / `M-y` | yank / yank-pop |
| `M-d` / `M-backspace` | kill word forward / backward |
| `M-up` / `M-down` | move current line (or marked block) up / down |
| `C-s` / `C-r` | incremental search (smart case, wraps) |
| `M-%` | query-replace (y/n/!/q) |
| `C-x g` | project-wide regex search (results list, Enter jumps) |
| `M-;` | toggle line comment |
| `C-c C-c` | send region or current line to the terminal REPL |
| `C-x t` | toggle terminal panel (keys pass through raw; only `C-x` is reserved) |
| `Shift+PgUp/PgDn`, wheel | terminal scrollback (typing snaps back) |
| `C-c C-v` | alternate view: markdown preview cycle, or table view of the buffer |
| `M-q` | fill paragraph to `fill_column`; in a markdown table, align the columns |
| `Tab` / `Shift-Tab` | markdown: next / previous table cell, or nest / un-nest a list item |
| `C-c C-t` | markdown: toggle a `[ ]`/`[x]` checkbox |
| `C-c b` / `C-c i` / `C-c c` | markdown: bold / italic / inline-code the region or word |
| `C-x {` / `C-x }` | narrow / widen the file tree (or drag the divider) |
| `C-x d` | show / hide the file tree (remembered across runs) |
| `M-x`, `Ctrl+Shift+P` | command palette |
| `C-g` / `Esc` | cancel chord / prompt / search / mark |

`ESC` acts as the Meta prefix, so `ESC w` == `M-w`, `ESC x` == `M-x`, etc.
`Tab` completes paths in the find-file and write-file prompts, listing the
choices (navigable with arrows, Enter to pick) when more than one matches.

Open files are watched for external changes: clean buffers reload
automatically; buffers with local edits ask before discarding them.

Text files over 10 MB open in a `less`-style pager instead of the editor:
the file stays on disk behind a sparse line index (multi-GB files open with
bounded memory, with progress shown while indexing), and searches scan in
the background so the UI never freezes — `C-g` cancels one mid-flight.
Scroll (keys or mouse wheel), `g`/`G`, `/`/`?` and `C-s`/`C-r` search with
all visible matches highlighted, `C-x w` wrap, `M-g` goto line. `F` follows
a growing file like `less +F` (rotation-aware; any key stops), and `e`
loads the file into a real editor buffer if you insist. Binary files are
shown as a placeholder, and the truncated/binary views refuse to save, so a
partial view can never overwrite the real file.

`.csv` and `.tsv` files open in a table viewer (inspired by
[csvlens](https://github.com/YS-L/csvlens)): a sticky header, row cursor,
and original file line numbers in the gutter. There is **no row limit**:
big files get a windowed table backed by a sparse index (built in the
background — the table shows and scrolls immediately), so ten million rows
cost the same as a hundred, `G` jumps straight to the last row, and search
scans the whole file once and then steps match-to-match instantly. And it isn't just for CSV: **any** buffer
— a `.tab`, `.gff`, a log, even an unsaved one — switches to a table with
`C-c C-v`. The delimiter is auto-detected; press `d` in the table to re-pick
it (`,` `;` `|` `tab` `space` or any character) if the guess was wrong, and
add suffixes to `table_suffixes` in the config to make more file types open
as tables automatically. In the table: `/` (or `C-s`) searches — literal
with smart case, the same dialect as everywhere else — with the matched text
highlighted in the cells (`Esc` clears), `C-s`/`n` and `C-r`/`N` step to the
next / previous match, `&` filters rows by regex, `g`/`G` jump to
top/bottom. `C-c C-v` switches back to the raw text. The table is read-only.

Word-processor documents — `.docx` and `.odt` — open as rendered markdown:
headings, emphasis, lists, tables and links come through; images become
placeholders. The conversion uses [pandoc](https://pandoc.org) when it is
on your `PATH` and a built-in stdlib converter otherwise. The view is
read-only and the document itself is never written; `C-c C-v` cycles to
the markdown source, and `C-x C-w` exports that markdown to a file. The
document is re-converted when it changes on disk.

Tabs show the file name; when two open files share a name (`a/config.toml`
and `b/config.toml`), each tab gets as much of its directory as it takes to
tell them apart, and the status bar always shows the active file's full path.

Markdown buffers edit structurally, not just visually. `Enter` continues
lists — bullets, numbered items (the block renumbers itself), `- [ ]` task
boxes, `>` quotes — and `Enter` on an empty item ends the list; after an
unclosed ` ``` ` it closes the fence. `M-q` reformats by context: paragraphs,
list items, and quotes wrap to `fill_column` (works in plain-text buffers
too), and inside a pipe table it aligns the columns, honouring `:---:`
alignment markers. `Tab`/`Shift-Tab` hop cell to cell through a table
(keeping it aligned; `Tab` past the last cell adds a row) or nest/un-nest a
list item. Pasting a URL over selected text makes a `[link](url)`. Fenced
code blocks are exempt from all of it.

The file-tree icons are emoji by default; if your terminal renders them poorly
(Konsole, some others), set `CANDAT_TREE_ICONS=nerd` (needs a Nerd Font) or
`=ascii`, or switch live with `M-x cycle-tree-icons` — the choice is saved.

## Configuration

`~/.config/candat/config.toml` (XDG aware), all keys optional:

```toml
tree_icons = "emoji"     # or "nerd" / "ascii"; cycle-tree-icons saves here
tree_width = 32          # file-tree width; C-x {/} and the splitter save here
tree_visible = true      # show the file tree; C-x d toggles and saves here
pager_wrap = false       # start the large-file pager with soft wrap on
tabstop = 8              # tab width in the pager
restore_session = true   # reopen last session's files (see below)
table_suffixes = [".csv", ".tsv"]   # files that open straight into the table
terminal_history = 2000  # scrollback lines kept by the terminal panel
fill_column = 80         # wrap width for M-q (fill-paragraph)
system_clipboard = "copy"   # mirror copies to the system clipboard (see below)
```

### System clipboard

Copies can be mirrored to the system clipboard, controlled by
`system_clipboard`: `"copy"` (the default) mirrors explicit copies — `M-w`
and `w` in the file tree; `"all"` mirrors every kill (`C-k`, `C-w`, `M-d`,
…, like emacs' `select-enable-clipboard`); `"off"` keeps everything in the
app-internal kill ring only. Pasting *from* the system clipboard needs no
setup — the terminal's own paste (usually `Ctrl+Shift+V`) already arrives
in the buffer.

Two channels are used at once, since neither can report success:

- **OSC 52**, an escape sequence the terminal turns into a clipboard write.
  This is what makes **SSH work**: the sequence travels back through the
  connection and your *local* terminal does the copy, so text copied in a
  remote candat lands on your local clipboard. Support depends on the
  terminal: Konsole has it (any recent release; if it doesn't work, look
  for "remote clipboard" in the profile settings), kitty / WezTerm /
  Alacritty / foot / xterm / Windows Terminal work out of the box, and
  GNOME Terminal and other VTE terminals only from VTE 0.76 (≈ Ubuntu
  24.04). **Inside tmux** add `set -g set-clipboard on` to `~/.tmux.conf`,
  or tmux swallows the sequence — that applies to local *and* SSH'd tmux
  sessions.
- **A local clipboard tool** — `wl-copy`, `xclip`, `xsel`, or `pbcopy`,
  whichever is installed and matches the session type — as a fallback for
  terminals without OSC 52. This channel only helps on the machine candat
  runs on; over SSH it would set the *remote* clipboard, so there OSC 52
  is the one that matters.

Unsupported terminals silently ignore OSC 52 — if a copy doesn't reach the
clipboard, it's the terminal or tmux configuration, and the text is still
on the kill ring (`C-y`) regardless.

Starting candat without file arguments reopens the files you had open the
last time you quit in that directory — tabs, cursor positions, scroll, and
the active buffer (per project root, kept in `~/.local/state/candat/`).
Passing files on the command line skips the restore.

Files are read and written in whatever encoding and line ending they arrive
in: UTF-8 by default, a UTF-8/UTF-16 byte-order mark is honoured, and anything
else falls back to latin-1 so the bytes round-trip untouched (the status bar
shows the encoding and `CRLF`/`CR` when they aren't plain UTF-8/LF). Saves are
atomic — written to a temp file and renamed over the original — so a crash or
full disk never leaves a half-written file.

Unsaved edits are snapshotted to `~/.cache/candat/recovery/` every 20 seconds
and on a crash; a clean quit clears them, and if any survive they are reported
(never auto-applied) on the next launch. Crash logs (including hard faults
caught by `faulthandler`) land in `~/.cache/candat/`.

The file tree has a filter box on top: press `/` while the tree is focused (or click it), type to narrow the tree to files whose path matches, `Esc` clears it. `r` (or `g`, dired-style) refreshes the tree from disk — deliberately manual, since re-walking a large tree can take a moment. `w` (or `M-w`, dired-style again) copies the selected file or directory's absolute path to the kill ring, ready to yank into any buffer with `C-y`. The file tree opens files on selection. The default theme is `candat-light`
(high-contrast dark-on-white). The markdown preview is linked: it follows
the editor's scroll position.

## Stability

The keybindings are fixed emacs bindings by design — there is no rebinding
layer, and that is a deliberate choice, not a missing feature. `candat` is a
comfortable emacs-muscle-memory editor, not a configurable one.

From 1.0 onward the project follows semantic versioning: the config-file keys,
the `CANDAT_TREE_ICONS` variable, the command-line interface, and the on-disk
locations of the config, session, and recovery files are treated as stable and
will not change incompatibly within a major version. The keybindings and the
`M-x` command names are equally stable. Behaviour details not listed here
(exact status-bar wording, colours, internal module layout) may still change.

## Development

```sh
uv run pytest
```

Release history is in [CHANGELOG.md](CHANGELOG.md).
