▎CMeDit

The CMeDit Manual

Everything CMeDit does, in one page. If you want the one-screen version instead, press F1 in the editor; a shorter copy of this manual also ships inside the binary under Help ▸ Manual.

Start here

Introduction

CMeDit is a modeless terminal text editor. There is no insert mode and no command mode: typing types, Ctrl+S saves, Ctrl+F finds, Ctrl+Q quits. It has a drop-down menu bar, full mouse support, the real system clipboard, a file explorer panel, project-wide search and replace, syntax highlighting, external linting, crash recovery and per-folder session restore, and view modes for spreadsheets, images, PDFs, RTF and Word documents, workbooks, e-books and ZIP archives.

It is written from first principles in Haskell with no TUI framework: raw-mode terminal control, the input parser, the diff renderer, the menus and the dialogs are built directly on termios and ANSI/VT escape sequences. Its only dependencies are the libraries that ship with the compiler, which is why it builds offline and runs as a single binary you can drop onto any machine.

What it will not do to your files

Start here

Installing

Linux and macOS

CMeDit builds with a plain make. You need GHC 9.0 or newer; nothing else is fetched, so the build works on a machine with no network and no Hackage index.

$ git clone https://github.com/appmakernetwork/cmedit $ cd cmedit $ make # builds the optimised ./cmedit binary $ make test # builds and runs the test suite $ ./cmedit . # open the current directory as a workspace

Copy the resulting cmedit binary anywhere on your PATH. It has no runtime dependencies of its own.

Windows

There is a native Windows port: the same codebase with a Windows implementation of the platform layer (hand-rolled kernel32 FFI, no extra packages). With GHC installed through ghcup and make from MSYS2:

$ make windows # builds cmedit.exe

It needs a console that speaks VT, which means Windows 10 1809 or later. Windows Terminal is recommended and is the default on Windows 11; legacy conhost gets the portable fallback path like any other minimal terminal. On any other operating system, make windows-check typechecks the whole program against the Windows platform layer without linking, which is how the port is kept honest from Linux. WSL and ssh from Windows Terminal run the POSIX build unchanged.

Clipboard helpers

For full system-clipboard integration, install any one of xclip, wl-clipboard (wl-copy/wl-paste) or xsel on Linux; macOS already has pbcopy/pbpaste. Without a helper, CMeDit falls back to the OSC 52 escape sequence, which is what makes copy-and-paste work over SSH in the first place.

Tip. cabal build and cabal run cmedit also work in an environment whose Hackage index cache exists. On a fully offline machine prefer make, which drives ghc --make directly and needs no index.

Start here

The command line

cmedit [OPTIONS] [FILE|DIR...]

Each FILE is opened for editing and created on save if it does not exist. A DIR argument opens as a workspace folder in the explorer panel, so cmedit . opens the current directory that way. With no argument, CMeDit starts with an empty buffer.

OptionMeaning
-h, --helpShow the built-in help, including the full list of config keys, and exit.
-v, --versionShow the version and exit.
-t N, --tab-width NTab width in columns. Default 4, valid range 1 to 16.
--tabsIndent with real tab characters. This is the default.
--spacesIndent with spaces.
--line-numbersShow the line-number gutter.
--no-line-numbersHide the gutter. This is the default.
--no-auto-indentDo not copy indentation onto new lines.
--readonlyOpen the file read-only. The status bar shows [RO].
--restoreRestore this folder's session — the files you had open here last time, in the same order, at the same cursors. See Sessions.
--convertConvert instead of editing, even when stdout is a terminal. See Converting files.
--sheet NWhich sheet a workbook conversion exports, counted from 1. Implies --convert.
--stats-on-exitPrint a session summary (frames, memory, garbage collection) when the editor exits.

Flags override the configuration file, which is read first. See Settings and config file for the persistent equivalents.

Start here

Converting from the command line

Every reading view turns an awkward format into text a terminal can show, so the same machinery makes CMeDit a converter — and a converter is what you want when the terminal is not there. No flag is needed: the editor draws on stdout, so a redirected stdout cannot mean "open the editor" and can only mean "give me the text".

$ cmedit report.docx > report.txt # .pdf .odt .epub .rtf too $ cmedit book.xlsx > book.csv # .ods too; --sheet N picks another sheet $ cmedit paper.pdf | grep -i abstract

The converted content goes to stdout and one line describing it to stderr, which is the only arrangement that survives a redirect:

report.docx: DOCX, 96 paragraphs → 8032 characters book.xlsx: workbook, sheet 1 “Sales” → 42 rows, 5 columns as CSV (of 3; --sheet N for the others)

--convert forces conversion when stdout is a terminal. An image (no text to give) or a file too large to load (already plain text) says so and exits non-zero.

Start here

The interface

From top to bottom, the screen is: the menu bar, the text area (with the explorer panel on its left when a folder is open, the line-number gutter next to it, and the vertical scroll bar on the right), the horizontal scroll bar, the status bar and the shortcut hint bar.

Fig. 1 — Menu bar, explorer panel, gutter, status bar, hint bar. In the default dark theme, which keeps your terminal's own background.

The menu bar

Press F10 to open the menus, or Alt plus a title's underlined letter (Alt+F for File, Alt+E Edit, Alt+I Find, Alt+V View, Alt+W Window, Alt+H Help). Arrows move, Enter runs the highlighted item, Esc closes. Every item shows its keyboard shortcut next to it, which makes the menus the gentlest way to learn the keyboard. Clicking works everywhere.

Menus adapt to context: Table View only appears for a .csv or .tsv file, Formatted View only for an .rtf, Archive Contents only for a container that has both a reading view and a listing, Revert only when the file can be reverted, Delete only with a selection, Save All only when more than one file is open with unsaved changes, and the in-file find entries are hidden in views that have no cursor to search from.

The status bar

The left side shows the active file, a bullet when it has unsaved changes, and its position in the list of open files. The right side is clickable:

ZoneClick action
Ln 24, Col 41Opens Go to Line (or Go to Page in the PDF view).
INS / OVRToggles overwrite mode.
UTF-8 / UTF-8-BOMToggles the byte-order mark written on save.
LF / CRLFSwitches the line ending written on save.
ruff: 2E 1WJumps to the first diagnostic. Only shown when a linter has reported something.

In other views the right side reports what matters there instead: the cell reference and table size in CSV, the image format and pixel size in the image view, the page number in a PDF, the sheet in a workbook, the chapter in an e-book, the line position in the paged viewer.

The hint bar

The bottom line lists the shortcuts that apply right now, and changes with the focus: the editor shows save, open, find and quit; the explorer shows new, rename and delete; the search panel shows its toggles; the CSV, image and PDF views show their own keys.

Scroll bars

A vertical bar occupies the rightmost column whenever the content overflows, with a proportional thumb you can drag; clicking the track jumps. A horizontal bar sits directly above the status bar for views that can scroll sideways (plain text without word wrap, and CSV tables). Either can be turned off in Settings, which also frees the column or row they occupy. In a table the horizontal drag is character-granular, so a wide first column can be scrolled partly out of view.

Dialogs

Tab moves between fields, options and buttons; Enter presses the highlighted button; Esc always cancels. Value pickers (the rows in Settings, and the theme picker) cycle with /, Space or a click; Enter deliberately does not confirm the dialog while a picker has focus, so you cannot dismiss it by accident while changing a value. Informational boxes, including the F1 help card and About, close on a click anywhere outside them; confirmations stay modal.

Using it

Files and documents

Opening

Opening a file that is already open switches to it rather than making a second copy, and re-opening a file you had open before restores the cursor position you left it at.

Several files at once

CMeDit keeps every open file in one window. Alt+. and Alt+, cycle forward and back (Ctrl+PageDown / Ctrl+PageUp do the same), Alt+1 to Alt+9 jump straight to a file by number, and the Window menu (Alt+W) lists everything open. Ctrl+N starts a new empty buffer; Ctrl+W closes the active file.

Saving

Ctrl+SSave the active file.
Ctrl+Shift+SSave As: write a copy under a new name and continue editing that. In the read-only reading views the same key is an export — see below.
File ▸ Save AllWrite every modified document at once.
File ▸ RevertReload the file from disk, confirming first if you have unsaved edits.

Two optional save-time cleanups are available in Settings: trimming trailing whitespace and ensuring a final newline. They are applied as ordinary undoable edits so you can see and undo exactly what changed, and they never touch CSV or image documents.

Quitting with unsaved files prompts for each one. With a large batch (more than eight) it asks once, offering Save All, Discard All or Cancel.

Files that change on disk

CMeDit re-stats open documents every couple of seconds while the terminal is focused, and immediately when you switch back to it. A file that changed underneath you (a git pull, say) gets a diamond marker in the explorer, a notice on the status bar when it is the active file, and a File ▸ Revert entry to reload it. Nothing happens automatically.

Encoding, line endings and the BOM

Files are read and written as UTF-8. View ▸ Line Endings flips between LF and CRLF, and View ▸ UTF-8 BOM toggles the byte-order mark; both apply on the next save, and both keep the file marked as modified until you save. Status bar clicks do the same thing in one step. A file is written back exactly as it came in unless you change these yourself.

Size and type guards

Every open route funnels through the same guards, so a huge or hostile file can never be decoded into millions of junk lines:

Using it

The file explorer

Open a folder (File ▸ Open Folder, cmedit somedir, or Ctrl+B) and a project tree docks on the left. Ctrl+B moves focus into the panel and back to the editor; on a collapsed panel it expands it first.

KeyAction
Move the selection.
/ Expand / collapse a directory.
Enter or clickOpen the selected file.
Ins or Ctrl+NCreate a file in the selected directory. End the name with / to create a folder instead.
F2Rename. Open buffers follow the new path, even under a renamed directory.
DelDelete, after a confirmation with Cancel preselected.
.Show or hide dot-files.
Ctrl+BReturn focus to the editor.

Reading the tree

Open files are highlighted, a bullet marks unsaved changes and a diamond marks a file that changed on disk since you opened it. Unopened file names are tinted by type: source code green, Markdown and HTML cyan, data formats such as JSON, YAML and CSV yellow, known binary blobs dimmed grey, and displayable images get a magenta glyph before the name. Large files show their size, and files too big to edit are dimmed.

Layout

Drag the divider to resize the panel. The chevron button (or dragging the divider all the way left) collapses it to a one-column strip you can click to bring back. The cross button closes the folder after a confirmation. The tree keeps itself current: expanding a directory always re-lists it, and a background poll re-lists any expanded directory whose modification time changed, so work done in another window shows up on its own.

Using it

Editing

Moving around

Arrows, Home, End, PageUp and PageDown behave as they do everywhere else. Ctrl+← and Ctrl+→ move by word, Ctrl+Home and Ctrl+End go to the start and end of the document. The mouse wheel scrolls; Shift+wheel (or a horizontal wheel or touchpad gesture) pans sideways when word wrap is off.

Selecting

Hold Shift with any movement key, or drag with the mouse. Double-click selects a word, triple-click a line, and Ctrl+A selects everything. The status bar shows the selected character count.

Clipboard

Ctrl+X, Ctrl+C and Ctrl+V use the real system clipboard through xclip, wl-copy/wl-paste, xsel or pbcopy/pbpaste when one is available, and the OSC 52 escape sequence otherwise, so copying inside an SSH session can still land on your laptop's clipboard. Pasting large amounts of text is handled in chunks, so a multi-megabyte paste does not stall the editor.

Why Ctrl+C copies instead of interrupting. CMeDit puts the terminal in raw mode with signal generation disabled, so Ctrl+C is an ordinary keystroke. The same applies to Ctrl+S and Ctrl+Q, which would otherwise be swallowed by terminal flow control.

Undo and redo

Ctrl+Z and Ctrl+Y. Runs of typing coalesce into sensible steps rather than one character each, held line moves collapse into a single undo step, and the CSV table view keeps its own undo history. Undo snapshots share their unchanged text, so deep history stays cheap even in large files, and the history is bounded so it cannot grow for the length of a session.

Indentation and overwrite

Tab and Shift+Tab indent and unindent: the whole selection at once when there is one, otherwise the current line. Whether that inserts a tab character or spaces, and how wide a tab is, are configurable. New lines copy the previous line's indentation when auto-indent is on. Insert toggles overwrite mode, shown as OVR on the status bar.

Line and word operations

Ctrl+DDuplicate the current line, or the selected lines.
Shift+Alt+↑ / Shift+Alt+↓Copy the line or selection one row up or down.
Alt+↑ / Alt+↓Move the line or selection up or down. Held moves undo as one step.
Alt+JJoin lines, collapsing the whitespace at the seam to a single space.
Ctrl+Shift+KDelete the line or selected lines.
Ctrl+KCut the line to the clipboard.
Ctrl+Backspace / Ctrl+DelDelete the word to the left / right of the cursor.

A selection that ends at column 0 does not include that last line, which is what you want when you have selected downward through whole lines.

Toggle comment

Ctrl+/ comments or uncomments the current line or selection using the file type's own syntax: #, //, -- and so on, with HTML, CSS, Markdown, FreeMarker and Jinja wrapping the span in a block comment. JSON gets JSONC-style //, since toggling a comment is an explicit request. The prefix is aligned at the block's indentation and blank lines are skipped.

Word completion

Ctrl+Space completes the identifier at the cursor from the words of every open buffer, with occurrences nearest the cursor ranked first. No language server is involved, so it works offline and in any file type. Keep typing to narrow the list, Tab or Enter accepts, Esc dismisses, and any other key dismisses the popup and is passed through to the buffer. A single remaining candidate is inserted immediately, and the whole thing counts as one undo step.

Bracket matching

When the cursor sits on one of ()[]{} its partner is highlighted, and Ctrl+] (Find ▸ Go to Bracket) jumps between the two. The scan is bounded, so an unmatched bracket in a huge file cannot stall a repaint.

Links in text

http:// and https:// URLs are recognised in the visible text. Hovering one shows a hand pointer and a status-bar hint; Ctrl+Click or a right-click opens it in your browser through xdg-open, open or rundll32. On terminals that support OSC 8 hyperlinks, URLs in the document and file names in the explorer, search results and status bar are real links your terminal can also act on.

Using it

Find and replace in the current file

Ctrl+F opens Find, Ctrl+R opens Replace. Both have match-case and whole-word options, and both remember what you typed.

Ctrl+G goes to a line number. The same dialog goes to a page in the PDF view, a chapter in an e-book, a sheet in a workbook — and in the paged viewer it jumps to a line of a file far too large to load, which is the point of that view.

Using it

Search and replace across files

F4 opens the workspace search panel over the whole open folder; F6 opens it with the replace field showing. (Ctrl+Shift+F and Ctrl+Shift+H are aliases, but many terminals intercept those, which is why the function keys are the documented way in. The Find menu always works.)

Running a search

Results stream in grouped by file with match counts and snippet lines as the walk proceeds. Select a result with the keyboard or click it to jump straight to that spot in the file. Files already open are searched from their in-memory contents, so unsaved edits are included.

KeyAction
TabMove between the search, replace and glob fields (and the Replace All button).
Move through results.
Fold and unfold a file's results.
EnterRun the search, or open the selected result.
Alt+C / Alt+W / Alt+XToggle match case, whole word, regular expression.
Alt+DAlso search inside documents (PDF, Word, workbooks, e-books). See below.
Alt+HShow or hide the replace field.
Alt+RReplace all matches in every file.
Ctrl+EnterReplace in just the selected file.
DelDismiss the selected result row (or a whole file's results) from the list.
EscReturn to the editor. The results stay, so you can come back to them.

Narrowing the scope

The include and exclude fields take glob patterns, comma separated: *.py, src/**/*.ts, !vendor/**. The walker also prunes on its own: dot directories, node_modules, common build directories, symbolic links, binaries (by extension where possible, otherwise by sniffing the first 8 KiB) and files over 8 MB.

How replace works

Replacing across a workspace is deliberately staged rather than written straight to disk:

  1. Open plain-text buffers are edited in place, undoably.
  2. Closed files are opened as unsaved documents with the change applied.
  3. The explorer expands to reveal the now-modified files, each marked with a bullet.
  4. Nothing reaches disk until you press Ctrl+S or File ▸ Save All. Close what you do not want.

A replace touching more than ten files asks for confirmation first. A very large replace (more than fifty files) falls back to writing directly to disk, preserving each file's byte-order mark and line endings, since opening hundreds of documents would help nobody.

Performance

The walk runs on background threads with a small pool of workers, so a search over thousands of files stays responsive and a new search supersedes a running one instantly. Results are capped at 2000 matches per file and 20000 in total. A finished search posts a desktop notification if your terminal is unfocused.

Using it

Search inside documents

The same panel can look inside PDFs, Word and OpenDocument files, workbooks and e-books, decoding each one through the very reader that would display it — so anything found is something you can then go and look at. Toggle it with Alt+D (or the [Doc] chip). It is off by default, because decoding one document costs roughly what grepping a hundred source files costs.

Hits are addressed by something intrinsic to the document rather than by a line number — these views reflow to your window, so a stored row would point somewhere else in a wider one: a PDF says p.7, an e-book ch.3, a Word or OpenDocument file a paragraph, a workbook the cell (B4). Enter opens the document at that unit and highlights the term there. A workbook is searched cell by cell, so a phrase spanning two columns is deliberately not a match.

Replace never touches a document. None of these formats can be written back, so they are excluded from every replace path, and the panel reports how many it left alone rather than skipping them silently.

Using it

Regular expressions

Turn on the regex toggle (Alt+X) in the workspace search. The engine is written from scratch as a Thompson NFA executed by a Pike VM, which means matching is linear in the length of the line: a pathological pattern such as (a+)+b cannot hang the search, and no match is ever dropped to a step budget. It is line-oriented, so a pattern matches within a single line.

SyntaxMeaning
.Any character.
[abc] [a-z] [^a-z]Character class, ranges, negated class.
\d \D \w \W \s \SDigit, non-digit, word, non-word, whitespace, non-whitespace.
\b \BWord boundary, non-boundary.
^ $Start and end of the line.
(…)Capturing group.
(?:…)Non-capturing group.
a|bAlternation.
* + ?Zero or more, one or more, optional. Greedy.
{n} {n,} {n,m}Bounded repetition.
*? +? ?? {n,m}?The same quantifiers, lazy.

In the replacement field, $1 to $9 insert capture groups and $0 (or $&) inserts the whole match. An invalid pattern is reported in the panel rather than silently matching nothing.

Views

How a file's view is chosen

CMeDit looks at a file's bytes, not its name. Images, PDFs and ZIP archives are recognised by their magic bytes, so a mislabelled file still opens correctly — and a ZIP container is classified by what is inside it (a word/document.xml, a workbook part, an e-book's container.xml, an OpenDocument content.xml), so a Word file with the wrong extension still opens as a document. CSV and RTF views are chosen by extension, because both are text and only the name distinguishes them from any other text file. The paged viewer is chosen by size.

ViewChosen forEditableToggle
TextEverything elseYesDefault
CSV table.csv, .tsvYesAlt+T
Formatted RTF.rtfNo, the markup isAlt+T
Word documentDOCX / ODT contentsNo; select & copyAlt+T to the listing
Workbook gridXLSX / ODS contentsNo; select & copyAlt+T to the listing
E-bookEPUB contentsNo; select & copyAlt+T to the listing
PDF reader%PDF- magicNo; select & copyNone
Archive listingZIP magicNoAlt+T where a reading view exists
ImageImage magic bytesNoNone
Paged viewerText over 100 MBNopaged-view

Views

CSV tables

.csv and .tsv files open as a spreadsheet grid with column letters and row numbers. Alt+T (View ▸ Table View) switches to the raw text and back.

cmedit exports/orders.csv
A B C D 1 order_id region amount status 2 10412 eu-west 8,940.00 shipped 3 10387 us-east 4,102.50 pending 4 10305 ap-south 1,988.75 shipped 5 10510 eu-west 412.00 refunded
orders.csv · tableCell C2 · sorted ↓ C
Alt+S Sort  Alt+T Text view  ^S Save
Fig. 2 — Frozen header, numeric-aware sort, draggable columns. In the flashbang theme.

Moving and editing

KeyAction
Arrows, TabMove between cells.
Enter or F2Edit the current cell. Typing a character also starts a fresh value.
Shift+EnterInsert a line break inside a cell.
Alt+← Alt+→Insert a column before or after the cursor.
Alt+↑ Alt+↓Insert a row above or below the cursor.
Ctrl+DelDelete the current row.
Alt+BackspaceDelete the current column.
Alt+SSort by the current column. Press again to reverse.
Ctrl+Z / Ctrl+YUndo and redo, in the table's own history.

A cell can hold several lines; a row's height is that of its tallest cell, capped at three lines, and a taller cell scrolls while you edit it. and inside a cell move between its lines before they move between cells.

Selection, copy and paste

Selection is rectangular: Shift+arrows or a mouse drag grow a box. Copy yields a mini-CSV of the box (or the raw value for a single cell), and paste is shape-aware: one value pasted over a box fills the box, a copied box pasted over a single cell spreads out from it, and a box pasted over a box of the same shape overwrites it. Cut and Del clear the selected cells.

The header row

The first row is pinned below the column letters while you scroll, because spreadsheets almost always have a header. View ▸ Freeze Header Row turns that off for the session, and freeze-header = off in the config makes that permanent.

Column widths

Columns are sized to fit their content. Drag a column border in the lettered header row to set your own width, and double-click the border to fit it to the content again. Overrides move with the column when you insert or delete columns, and are remembered per document.

Sorting

Alt+S sorts by the column the cursor is in. Values that look like numbers sort numerically (so 9 comes before 10), text sorts case-insensitively, and empty cells go last. A frozen header stays pinned, the cursor follows its own row to its new position, and one undo restores the previous order.

Saving

While the table view is showing, the table is the document. Saving, or switching back to text, writes RFC 4180 CSV with correct quoting and preserves the file's line ending. The original file is only rewritten when you save.

Large tables. A table with hundreds of thousands of rows stays responsive: column widths are cached and updated per edited cell rather than rescanned, and the modified flag is computed by pointer comparison rather than by comparing content.

Views

Office files and e-books

A .docx, .xlsx, .odt, .ods or .epub is a ZIP full of XML, so instead of stopping at the archive listing CMeDit reads it. Only the members a format needs are ever decompressed, so a 1 GB .docx full of photographs costs the members its text lives in and no more. Any failure at any stage falls back to the plain archive listing with a note saying why, so a damaged file still opens — and Alt+T swaps between the reading view and that listing at any time.

Word documents (.docx, .odt)

A Word document shows as a document: headings, bold, italic, underline, colour, alignment, indents, bullets and tables on tab stops, reflowed to your window.

Workbooks (.xlsx, .ods)

A workbook opens in the spreadsheet grid, with [ / ] turning the sheets and Ctrl+G jumping to one by number. Gaps in a sheet are real and are shown, shared strings and inline strings are resolved, and formulas show the value Excel last calculated. Where a workbook was written by a library rather than by Excel — openpyxl, xlsxwriter, pandas.to_excel — there is no cached value to show, so CMeDit evaluates the formula itself: around fifty functions (SUM, AVERAGE, IF, COUNT/COUNTIF, MIN/MAX, ROUND, SUMIF, VLOOKUP, the text and maths families), the full operator set, ranges including whole columns, cross-sheet references and chains of formulas. A value the file already gives is never recomputed, so nothing CMeDit works out can contradict the program that wrote the file; a formula it cannot parse or has no function for is left blank and counted, and the status bar reports both totals. Number formats are not applied, so a date reads as its stored serial number, and the status bar says so.

OpenDocument files — what LibreOffice writes — go through the same two views, with one difference in their favour: an .ods stores each cell's displayed text as well as its value, so dates and currencies read as 15/01/2024 and $1,234.50 rather than as the numbers underneath them. (.odp and .odg are positioned shapes rather than documents, and fall back to the listing.)

E-books (.epub)

An e-book shows its chapters in reading order, with [ / ] turning chapters and Ctrl+G going to one by number. The container, package document and spine are followed properly, so the chapters are the ones the book actually orders.

Selecting, copying, exporting

The document views select and copy exactly as the RTF and PDF ones do — drag, double-click a word, triple-click a line, Shift+arrows, Ctrl+A, then Ctrl+C — and a workbook copies a rectangular block of cells the way a CSV file does. Ctrl+Shift+S exports the sheet you are looking at as a CSV file (and, in the document views, the document as plain text) — an export, not a Save As: it writes a copy and leaves the file itself open and untouched.

Everything the readers do not model is skipped rather than mis-rendered, and all three views are read-only: there is no serialiser back to any of these formats, and could not be.

Views

RTF documents

.rtf files open as the document rather than as its markup. Bold, italic, underline, strike-through and text colour map onto the terminal's own attributes; paragraphs wrap to the window with their real alignment, indents and hanging bullets; \'hh and \uN escapes, curly quotes and dashes decode properly. Font tables, style sheets, embedded pictures and every other {\*\…} destination are skipped rather than mis-rendered, which is a property of the format: RTF defines those groups as safely ignorable.

You can select text with the mouse (double-click for a word, triple for a line, Shift+click to extend, or Shift+arrows) and Ctrl+C copies it, so quoting a paragraph out of a document you are reading is one gesture; Ctrl+A takes the whole thing and Esc clears.

Alt+T (View ▸ Formatted View) switches to the raw markup, which edits and saves like any other text file, with the control words syntax-highlighted.

The formatted view is read-only by design. It is a projection of the text buffer, and it is never written back. A word-processor file also carries style sheets, tables, embedded pictures and revision marks that the reader does not model, so if the view could save, those could be lost. Because it cannot, they cannot. Editing an RTF means editing its markup.

The view shows no cursor until you select, since the buffer cursor is a position in the markup and showing it would blink somewhere arbitrary in the rendered text. It re-derives itself whenever the buffer changes underneath it, so undo and staged replaces are reflected immediately. Document colours are honoured only when they would actually read against your theme, because nearly every RTF file specifies black body text, which would be invisible on a dark background. Text set in a large font renders bold, since a terminal has only one font size.

Views

Reading PDFs

Opening a PDF shows the document, not the file. A PDF is glyphs at coordinates with no paragraphs and no reading order, so the reader reconstructs one: columns from the gaps in the page's horizontal occupancy, lines from the vertical clustering of glyphs, spaces from the gap between glyph runs measured in ems, and paragraphs from lines that reach the right margin under a consistent leading. Words hyphenated across the original line breaks are rejoined. Bold, italic and monospaced runs are inferred from the fonts and drawn with the terminal's own attributes, while lines whose horizontal positions carry meaning (table rows, code listings) are placed by column instead of being re-wrapped.

cmedit ~/docs/spec.pdf
──────────────────────────────────────────── 4. Objects An object is an unordered collection of zero or more name/value pairs, where a name is a string and a value is a string, number, boolean, null, object, or array. object = begin-object [ member *( sep member ) ] end-object
spec.pdf  [2/3]Page 3 of 16 · PDF
[ Prev  ] Next  ^G Go to page  ^F Find
Fig. 3 — Columns, paragraphs, bold and italic put back together and reflowed to the window. In the graphite theme.
KeyAction
[ / ]Previous / next page. p and n do the same.
Ctrl+GGo to a page number.
Arrows, PageUp/PageDown, wheelScroll.
Ctrl+Home / Ctrl+EndStart and end of the document.
Drag, double/triple-click, Shift+arrowsSelect text. Ctrl+C copies it; Esc clears.
Ctrl+F, F3 / Shift+F3Search the document; every match highlights while the dialog is open, and the hit becomes the selection, so finding and copying are one gesture.

The text reflows when you resize the terminal. Page boundaries are drawn as a rule, and the status bar shows which page you are on out of how many.

What the reader deliberately does not do: decrypt encrypted files (it says so instead), show embedded images or vector graphics, or reproduce the page's typesetting. It is for reading a document, not for looking at it. A scanned page with no text layer reports that rather than showing you nothing, and the view is read-only, so a PDF you have open can never be saved over. Ctrl+Shift+S exports the reconstructed text as a plain-text copy.

Robustness. Objects are found by scanning the file for N G obj rather than by following the cross-reference table, so broken, patched and incrementally updated files all read: a later definition of an object number wins, which is exactly what an appended update means. Compressed object streams, universal since PDF 1.5, are expanded afterwards under the same rule.

Views

ZIP archives

Opening a .zip shows what is inside it: an indented file tree with each member's size, how much compression saved and when it was last modified, above a summary of the whole archive. Everything built on ZIP gets at least this treatment — .jar, .war, .apk, .whl, .pptx, .cbz, .vsix and friends list as a tree, while .docx, .xlsx, .odt, .ods and .epub go further and open as documents, workbooks and e-books, with Alt+T dropping to this listing. As with images, the file is recognised by its content rather than its name.

cmedit dist/release.zip
Name Size Saved Modified ▾ release/ bin/cmedit 9.4 MB 62% 07-30 14:02 man/cmedit.1 18 KB 71% 07-30 14:02 README.md 11 KB 64% 07-28 09:41 LICENSE 34 KB 77% 06-02 17:19
release.zip · archive4 entries
Enter Open entry  ^W Close
Fig. 4 — The listing is read from the archive's central directory: two short reads however large the file. In the cherry-blossom theme.

Only the archive's table of contents is read, never the members themselves. That has two happy consequences: the listing is as quick on a multi-gigabyte archive as on a tiny one, because the central directory lives at the end of the file and is proportional to the number of entries rather than the size of the data; and encryption does not get in the way, because names, sizes and dates are not the part that is encrypted. Encrypted members are listed and flagged on their own row, as are members using an unusual compression method.

The listing is an ordinary read-only document, so Ctrl+F searches it, Ctrl+G jumps to a line, word wrap works, and you can select and copy from it. What you cannot do is save it: there is no serialiser back to ZIP, and Save As offers a .txt name so that you cannot replace an archive with a description of itself by accident.

Views

Images

Opening a PNG, JPEG (baseline or progressive), GIF, WebP (lossless VP8L or lossy VP8), BMP or Netpbm file shows the picture in the terminal, scaled to fit. Every decoder is written from first principles on the boot libraries: the DEFLATE inflate, GIF's LZW, JPEG's Huffman tables and IDCT, WebP's prefix codes, transforms and boolean arithmetic decoder.

Key or gestureAction
aSwitch between 24-bit colour half-blocks and a monochrome ASCII ramp.
Drag a rectangleZoom into that region of the source pixels.
Click, or EscZoom back out to the whole image.
Ctrl+WClose the image.

How it is drawn

On a terminal that supports the kitty graphics protocol or sixel, the picture is drawn at true pixel resolution, aspect-corrected using the terminal's real cell size and capped at native resolution so a small image sits centred at 1:1 rather than being blown up. Everywhere else it is drawn with half-block characters, two pixels per cell in 24-bit colour, which needs no terminal capability at all. Both paths use the same geometry, so the picture occupies the same box either way. Resizing the terminal re-scales the picture; scrolling and moving around do not, because the scaled result is cached.

Animation

Animated GIFs play in place. On kitty itself the whole animation is uploaded once with the placement and the terminal loops it, so the editor never ticks. On terminals that speak the static kitty protocol but drop animation, every frame is pre-uploaded and the editor swaps the visible placement, which costs a few dozen bytes per frame. Elsewhere the frames are stepped by the editor and drawn as cells. A still image, or an animation in a background file, costs nothing at all. Zooming a kitty-protocol placement deliberately freezes on the current frame, since re-uploading a cropped frame per tick would be a full-cost transmission.

Views

Very large files

A text file too large to hold as an editable buffer (over 100 MB) opens in a read-only paged viewer whose memory does not depend on the file's size. A 281 MB log of four million lines opens at about 32 MB resident, and stays there after forty page-downs; a 120 MB file that is one single line opens at about 30 MB.

cmedit /var/log/app.log
2026-08-06T14:02:11Z WARN rl: bucket 8412 at 97% capacity 2026-08-06T14:02:11Z INFO request id=9f3a route=/v2/orders 200 in 41ms 2026-08-06T14:02:12Z ERROR upstream timeout host=pg-3 after 5000ms 2026-08-06T14:02:12Z INFO retry 1/3 host=pg-2 2026-08-06T14:02:12Z INFO request id=9f3b route=/v2/orders 200 in 38ms
app.log · paged, read-onlyLn 2,000,000 of 4,113,882
^G Go to line  ^Home/^End Ends  ^W Close
Fig. 5 — A jump to line 2,000,000 is instant; memory stays flat. In the midnight theme.

Two structures make that work. One streaming pass indexes a byte offset every thousand lines (and sniffs the byte-order mark and line ending, so a CR-only file pages correctly), and only a window of lines around the viewport is ever decoded. Every read is bounded three ways: the stride of skipped lines, the number of lines requested, and 64 KiB per line.

Scrolling, Ctrl+Home, Ctrl+End and Ctrl+G (Go To Line) are all instant, including a jump to line 2,000,000 of a log. Syntax highlighting still applies to each visible line, lexed from the initial state, since the state before it would mean reading from the top of a multi-gigabyte file; a construct opened earlier is therefore not carried in. The view is read-only by construction, so editing keys are swallowed with a note and Save and Revert are refused. The index is a snapshot, so a log that keeps growing keeps its extent until you reopen it.

Set paged-view = off to have oversized files refused instead.

Setup

Linting

CMeDit can run your project's own linters as you type. It does not implement any checks itself and there is no language server involved.

you@prod: cmedit ~/api/src/parse.py
21from pathlib import Path
22import json
23
24def parse_events(rows):
25 out = []
26 for r in rows:
27 try:
28 out.append(r["ts"])
29 except:
30 continue
31 return out
● parse.py  1E 1WLn 29, Col 9  INS  UTF-8  LF
E722 [ruff] do not use bare except
Fig. 6 — Squiggly underlines, tinted line numbers, the message on the hint bar when the cursor rests on a marker. In the light theme.

Detection

At startup, whenever Settings opens, and when you open a folder, CMeDit looks for the supported tools on your PATH. Node tools are also looked for in the workspace's node_modules/.bin; in a Yarn Plug'n'Play workspace with no node_modules, the workspace's own copy is run through .pnp.cjs via node, so the project's exact tool and plugins apply.

ToolFilesDefaultInstall
ruff.py, .pyionpip install ruff
flake8.py, .pyion, skipped when ruff runspip install flake8
eslint.js .jsx .ts .tsx .mjs .cjsonnpm install -D eslint
stylelint.css .scss .sass .lessonnpm install -D stylelint
pyright.py, .pyioff, and only on savepip install pyright
shellcheck.sh, .bashonapt install shellcheck

When it runs

About half a second after you stop typing, the active file is sent to its linter on standard input, so unsaved changes are checked rather than what happens to be on disk. The working directory is the workspace root, so project settings such as pyproject.toml, eslint.config.mjs or .shellcheckrc apply exactly as they would from the command line. Saving re-lints immediately, including the save-time-only tools. Only one pass runs at a time: a superseded pass is cancelled, there is a whole-pass timeout, and a rate floor keeps a burst of typing from starting a queue of processes.

Reading the results

Turning it on and off

File ▸ Settings has a Linting section: a master switch, then one row per tool. Installed tools show their version, and anything missing shows the exact command that would install it. Saving Settings writes lint plus one lint-<name> key per tool, which you can also edit by hand.

Setup

Appearance and themes

Themes

View ▸ Theme opens a picker; moving the focus previews each theme live behind the dialog, so Esc restores what you had simply because nothing was written. The choice applies to the session; set theme in the config file to make it permanent.

ThemeDescription
autoFollows your terminal's background colour, re-checked whenever the terminal regains focus, so a system light/dark switch follows you. Falls back to dark when the terminal never answers. This is the default.
dark-terminalDark palette that keeps your terminal's own background. (dark is accepted as a legacy spelling.)
light-terminalLight palette that keeps your terminal's own background. (light is accepted as a legacy spelling.)
cherry-blossomSoft pink, painted on every cell.
flashbangBlinding pure white, for the sun-drenched.
midnightDeep navy, for terminals whose dark is not dark enough.
graphiteNeutral near-black with the colour left to the syntax, and IDE-style chrome that sits below the page.

The two terminal themes restyle the chrome and tokens only. The other four paint their own background on every cell, so they look identical whatever your terminal's palette is. The cursor colour is matched to the theme too, and restored when CMeDit exits.

Syntax highlighting

Highlighting is chosen by file extension and covers SQL (PostgreSQL flavour), Python, JavaScript and TypeScript (including JSX and TSX), CSS, SCSS and Less, HTML and XML, FreeMarker, Jinja, shell, Haskell, Markdown, JSON, YAML, TOML, INI and conf files, CSV, and RTF markup. Multi-line constructs stay correct no matter where you scroll, including dollar-quoted SQL bodies, Python docstrings, JavaScript template literals, fenced Markdown code and HTML comments. Lines longer than 20,000 characters are rendered unstyled, so one minified line cannot dominate a frame.

Layout toggles

Alt+ZWord wrap. Long lines wrap to the window instead of scrolling sideways.
Alt+LThe line-number gutter.
View ▸ WhitespaceWhitespace markers: spaces and tabs drawn as glyphs. No shortcut — Alt+W opens the Window menu.
Ctrl+BThe explorer panel.

All four are also in the View menu, and all four have config keys so a session starts the way you like it.

Setup

Settings and the config file

The Settings dialog

File ▸ Settings (Ctrl+,) lists every option as an arrow-key value picker, grouped into Editing, Display, Files and Linting, with a one-line hint for the highlighted row. Move with the arrows, change a value with /, Space, Enter or a click. Changes apply immediately behind the dialog so you can see them. Save writes them to the config file; Cancel or Esc puts everything back the way it was. The dialog scrolls when it does not fit the terminal.

Writing is surgical: existing keys are updated in place, your comments and any unfamiliar lines are preserved, and a key is only appended when its value differs from the default.

The file

The same settings can be edited by hand in ~/.config/cmedit/config (XDG_CONFIG_HOME is honoured). One key = value per line; # starts a comment. Booleans accept true, yes, on or 1 and false, no, off or 0. Anything unrecognised is reported once on the status line rather than silently ignored, and one bad line never takes the rest of the file down with it. Command-line flags override the file.

tab-width = 4 # columns per tab, 1-16 indent = tabs # tabs | spaces auto-indent = on # copy indent onto new lines word-wrap = off # wrap long lines to the window line-numbers = off # show the line-number gutter whitespace = off # show spaces/tabs as glyphs scrollbar-vertical = on # right-edge scroll bar scrollbar-horizontal = on # bottom scroll bar trim-trailing-whitespace = off final-newline = off freeze-header = on # pin a CSV table's first row theme = auto # auto | dark-terminal | light-terminal | # cherry-blossom | flashbang | midnight | graphite paged-view = on # page huge files instead of refusing them journal = on # crash-recovery journal + exit snapshots restore-session = off # bare `cmedit` restores this folder's session lint = on # run external linters lint-ruff = on lint-flake8 = on lint-eslint = on lint-stylelint = on lint-pyright = off lint-shellcheck = on debug-stats = off # live counters on the status bar

Every key

KeyValuesDefaultMeaning
tab-width1 to 164Columns per tab stop.
indenttabs, spacestabsWhat pressing Tab inserts.
auto-indentbooleanonNew lines copy the previous line's indentation.
word-wrapbooleanoffStart with word wrap on.
line-numbersbooleanoffShow the line-number gutter.
whitespacebooleanoffShow whitespace markers.
scrollbar-verticalbooleanonReserve the right-hand column for the vertical scroll bar.
scrollbar-horizontalbooleanonReserve a row for the horizontal scroll bar.
trim-trailing-whitespacebooleanoffStrip line-end whitespace on save, as an undoable edit.
final-newlinebooleanoffEnsure the file ends with a newline on save.
freeze-headerbooleanonPin a CSV table's first row while scrolling.
themesee aboveautoColour theme.
paged-viewbooleanonOpen files too large to edit in the paged viewer instead of refusing them.
journalbooleanonCrash-recovery journal and clean-exit snapshots. Off means CMeDit caches no file content anywhere. See Sessions.
restore-sessionbooleanoffHave a bare cmedit restore this folder's session, as --restore does.
lintbooleanonMaster switch for external linters.
lint-rufflint-shellcheckbooleanon, except lint-pyrightPer-tool switches: lint-ruff, lint-flake8, lint-eslint, lint-stylelint, lint-pyright, lint-shellcheck.
debug-statsbooleanoffShow live session counters (frame time, heap, background jobs) on the status bar.

Setup

Sessions and crash recovery

Crash recovery

Unsaved changes are journalled every couple of seconds to ~/.cache/cmedit/journal (a directory created mode 0700; a very large file a little less often, and always on a background thread), so an SSH drop, a closed terminal or an OOM kill does not take the last hour with it. The next time CMeDit starts it lists what it found and offers to Recover, Discard or Keep for later; recovered files come back as unsaved buffers, so nothing is written over your file until you save it. A journal is deleted as soon as its document is saved or closed, and on a clean exit.

The journal holds file content in your cache directory — and, so a restored session can offer your files back as you left them, a clean exit also writes a copy of every open document (up to 4 MB each) to ~/.cache/cmedit/snapshots. One switch covers both: journal = off in the config (or the row turned off in File ▸ Settings) and CMeDit caches no file content anywhere — no journals while you work, no snapshots when you quit. That is the setting to reach for if you edit secrets.

Session restore, per workspace folder

cmedit --restore reopens the files you had open in this folder last time — in the same order, with the same cursor positions, the same workspace folder and the same file in front. Each folder gets its own session file under ~/.config/cmedit/sessions, so alternating between two projects in two terminals does not have the second one overwrite the first one's list; --restore takes the session for the directory you start in, or — if that folder has none — the most recently written session of any folder, with the status line naming the folder it came from. A run with no folder open keeps using ~/.config/cmedit/session, and older session files still restore.

Put restore-session = true in the config (or turn on "Restore session on start" in File ▸ Settings) to have a bare cmedit do it; naming files on the command line still just opens those, and files named alongside --restore open on top of the restored session. Session files hold paths, cursors and the times the files were last seen — never file content.

The File menu also lists your last few sessions ("website (6 files)", up to four): choosing one restores it into the running editor, adding its folder and files to what you already have open — an already-open file is switched to, not duplicated — and closing nothing.

Files that moved while you were away

If a restored file changed on disk since the session ended, a "Files Changed Since This Session" dialog lists them (◆) and offers Latest on Disk (the default; Esc means it too, and it is a no-op since the files are already open at that version) or As You Left Them, which brings back the contents from the session's clean exit as unsaved, modified buffers marked ◆ — nothing is written over the newer file until you save it yourself. One answer covers the whole list.

Files over 4 MB, and sessions that ended in a crash (or ran with journal = off), have no saved copy: those are annotated in the list, stay at their newest version, and if none of the listed files has a copy the dialog just reports what changed with a single button. Files deleted since the session are skipped with a note ("Restored 4 of 5 files"). Restore composes with crash recovery: restore runs first and the journal is applied last, so unsaved changes from a crash come back in the restored files and always have the final word.

Setup

Files CMeDit writes

PathContents
~/.config/cmedit/configYour settings. Written by File ▸ Settings ▸ Save, and safe to edit by hand.
~/.config/cmedit/recentRecently opened files with their cursor positions, most recent first, capped at 50 entries. This is what restores your cursor when you reopen a file.
~/.config/cmedit/historyFind and replace terms, capped at 50 each, escaped so multi-line terms survive.
~/.config/cmedit/session, ~/.config/cmedit/sessions/Per-folder session lists: paths, cursors and last-seen times — never file content. Capped at 50 files per session.
~/.cache/cmedit/journal/The crash-recovery journal: unsaved buffer content, written every couple of seconds, deleted on save, close or clean exit. Created mode 0700.
~/.cache/cmedit/snapshots/Clean-exit copies of open documents (up to 4 MB each), which back the "As You Left Them" restore choice.

XDG_CONFIG_HOME and XDG_CACHE_HOME are honoured. Delete any of them to start fresh; none is required for CMeDit to run. journal = off stops both cache directories from receiving any file content. Recents are persisted when the list order changes and once more on exit, including on an external kill, so positions are not lost.

Reference

Keyboard reference

Files and windows

Ctrl+NNew fileCtrl+OOpen file
Ctrl+PGo to file (fuzzy)Ctrl+Shift+PCommand palette
Ctrl+SSaveCtrl+Shift+SSave As (export in reading views)
Ctrl+WClose fileCtrl+QQuit
Ctrl+BExplorer panelCtrl+,Settings
Alt+. / Alt+,Next / previous fileCtrl+PgDn / Ctrl+PgUpNext / previous file
Alt+1Alt+9Go to file NF10, Alt+letterMenus

Editing

Ctrl+Z / Ctrl+YUndo / redoCtrl+X Ctrl+C Ctrl+VCut, copy, paste
Ctrl+ASelect allShift + movementExtend the selection
Tab / Shift+TabIndent / unindentInsertOverwrite mode
Ctrl+DDuplicate lineCtrl+KCut line
Alt+↑ / Alt+↓Move lineShift+Alt+↑ / Shift+Alt+↓Copy line up / down
Alt+JJoin linesCtrl+Shift+KDelete line
Ctrl+/Toggle commentCtrl+SpaceComplete word
Ctrl+BackspaceDelete word leftCtrl+DelDelete word right

Movement, find and go

Ctrl+← / Ctrl+→By wordCtrl+Home / Ctrl+EndDocument ends
Ctrl+FFindF3 / Shift+F3Find next / previous
Ctrl+RReplaceCtrl+GGo to line (page, chapter, sheet)
F4Find in filesF6Replace in files
F12Go to definitionCtrl+]Matching bracket
F8Next problemAlt+← / Alt+→Back / forward

View

Alt+ZWord wrapAlt+LLine numbers
Alt+TTable view / formatted view / archive contentsView ▸ WhitespaceWhitespace markers (no shortcut)
F1Keyboard help cardF10Menu bar

In the explorer panel

MoveEnterOpen
Ins / Ctrl+NNew file, or folder with a trailing /F2Rename
DelDelete.Show hidden files

In the search panel

TabNext fieldEnterSearch, or open a result
Alt+CMatch caseAlt+WWhole word
Alt+XRegular expressionAlt+DSearch inside documents
Alt+HShow the replace fieldAlt+RReplace all
Ctrl+EnterReplace in this fileDelDismiss the selected result

In the table view

Enter / F2Edit the cellTabNext cell
Alt+↑ Alt+↓Insert a rowAlt+← Alt+→Insert a column
Ctrl+DelDelete the rowAlt+BackspaceDelete the column
Alt+SSort by columnAlt+TBack to text

In the reading views

[ / ]Page (PDF), chapter (EPUB), sheet (workbook)Ctrl+GGo to page / chapter / sheet
Drag, double/triple-click, Shift+arrowsSelect (PDF, RTF, DOCX, ODT, EPUB)Ctrl+CCopy the selection
aColour or ASCII rendering (image)DragZoom to a rectangle (image)
Ctrl+Shift+SExport as text / CSVCtrl+FSearch the document

Reference

Terminal support

At startup CMeDit asks the terminal what it can do, then upgrades itself feature by feature. A terminal that answers nothing gets the portable escape stream, which is the same stream CMeDit has always emitted. Every upgrade is opt-in by evidence, so a plain terminal is never corrupted.

What is asked

What you get when it answers

Emitted unconditionally, because a terminal that lacks them ignores them

Keyboard protocols

CMeDit enables the kitty keyboard protocol's disambiguate flag at startup, which is what lets modified keys legacy encodings cannot express, such as Ctrl+Enter and Shift+Enter, arrive intact. Terminal focus reporting is enabled too, which is how the freshness poll and the automatic theme know when you have come back. Both are turned off again on exit, along with mouse reporting, the alternate screen and the raw-mode terminal settings; a SIGTERM or SIGHUP still runs that cleanup.

Reference

Limits and safeguards

Every one of these exists because something without it went wrong on a real file.

LimitValueWhat it protects
Editable file size100 MBLarger text files open in the paged viewer instead.
Background load threshold2 MBAbove this a file loads on a background thread with a spinner.
Binary snifffirst 8 KiBA NUL byte means the file is binary and is not decoded as text.
Paged viewer line cap64 KiB per lineA file with no line separators cannot be concatenated into memory.
Paged index stride1000 linesThe index stays small on a file of any size.
PDF size / pages / text128 MB, 2000 pages, 4M charactersExtraction cannot run away on a hostile file.
Searched file size8 MBBigger files are skipped by the workspace search.
Searched document size64 MBBigger PDFs, workbooks and e-books are skipped by the document search.
Matches2000 per file, 20000 totalA broad search over a huge tree stays usable.
Staged replace50 filesAbove this, replace writes to disk directly instead of opening documents.
Replace confirmation10 filesAbove this, Replace All asks first.
Highlighted line length20,000 charactersA minified line renders unstyled rather than dominating the frame.
Bracket scan200,000 charactersAn unmatched bracket cannot stall a repaint.
CSV cell height3 linesA cell with many newlines cannot swallow the screen.
Exit snapshot4 MB per documentSnapshots stay cheap; larger files restore from disk.
Recent files / search history50 eachBounded structurally, so neither grows with session length.

Two more behaviours are worth knowing: the event loop collects memory after 30 seconds of inactivity, so an editor left open all day does not keep the high-water mark of everything it has ever opened; and background work (searches, definition scans, lint passes, loads) is superseded rather than queued, so the newest request always wins.

Reference

Troubleshooting

Ctrl+Shift+F or Ctrl+Shift+H does nothing

Your terminal is intercepting it, which several do. Use F4 and F6, or the Find menu.

Copy and paste do not reach my desktop clipboard

Install xclip, wl-clipboard or xsel. Without one, CMeDit falls back to OSC 52, which your terminal must be configured to allow; many terminals disable clipboard writes from applications by default.

The colours look wrong, or the theme does not follow my system

Automatic theming needs the terminal to answer the background colour query. If yours does not, set theme = dark-terminal or light-terminal explicitly, or pick one of the four painted themes, which look the same everywhere.

Images show as blocks rather than real pixels

That is the fallback rendering. True pixels need kitty graphics or sixel support; the detection is by probe reply, so a terminal that does not answer gets half-blocks. An animation played by the editor rather than the terminal is normal on terminals that support static kitty graphics but not its animation actions.

A file will not open

Check the message on the status bar. Binary files are refused unless they are a known image, PDF or archive; files over 100 MB open in the paged viewer unless paged-view is off; and a corrupt image or an encrypted PDF reports what went wrong rather than dumping bytes at you. A damaged office file or e-book falls back to its archive listing with a note saying why.

Linting is not happening

Open File ▸ Settings and look at the Linting section: each row shows either the detected version or the command that would install the tool. Check that the master switch is on, that the file's extension is one the tool handles, and that the document has a real path on disk.

Something is slow

Turn on debug-stats = on for live frame time, heap and background job counters on the status bar, or run with --stats-on-exit for a one-shot session summary.

Reference

Licence and links

Copyright © 2026 Benjamin Marsh. CMeDit is free software under the GNU General Public License, version 3 (GPL-3.0-only). You may use, study, share and modify it under those terms; distributed derivative works must remain under the GPL. It comes with no warranty.

Contributions are welcome; merging one requires agreeing to the Contributor License Agreement in the repository.