Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -12,3 +12,6 @@ __pycache__/

# headless test harness scratch
tests/work/

# pinned third-party checkouts for the sidebar -- vendor.sh recreates them
vendor/
27 changes: 27 additions & 0 deletions KEYS.md
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ Syntax highlighting only. No completion, no LSP, no snippets, no AI.
| `Ctrl+Shift+Z` | redo (needs a terminal that speaks CSI-u) | normal, insert, visual |
| `Tab` | indent the selected lines | **selection only** |
| `Shift+Tab` | unindent the selected lines — or, in insert, the line you are on | selection, insert |
| `Ctrl+B` | **show / hide the file sidebar** (see its own section below) | normal, insert |
| `Ctrl+Q` | visual block (was `Ctrl+V`) | normal, visual |
| `Ctrl+Shift+Q` | **quit, with a confirmation** | normal, insert, **and inside the REPL** — except on a terminal with no CSI-u, where **normal mode is not covered**; see below |
| **Shift+arrows** | **select, like any other editor** | normal, insert |
Expand Down Expand Up @@ -242,6 +243,32 @@ Costs: `Ctrl+R` was normal-mode redo (use `Ctrl+Y`), `Ctrl+E` was scroll-down.
To scroll back through output, `Ctrl+\` `Ctrl+N` first — otherwise the wheel
goes to python.

## The file sidebar

`Ctrl+B` shows and hides it (`Cmd+B` too on a forwarding terminal — including
from inside the REPL, where `Ctrl+B` is deliberately left to readline). It
opens without stealing your caret: the tree appears on the left and you are
still typing in your code — **rooted at the folder of the file you are in**,
not at whatever directory your shell was sitting in. The mouse is how you
use it:

| Gesture | Does |
|---|---|
| click a file | open it — in the file window, never the interpreter |
| click a folder | fold / unfold |
| `[+ File]` / `[+ Folder]` | clickable buttons in the tree's top bar; type the name at the prompt |
| right-click | menu: new file/folder, open, open in split, rename, cut, copy, paste, delete |
| Ctrl+click | select several (marked with `*`) |
| drag onto a folder | **move it there** — drag a marked row and the whole selection moves |

A drag released back on its own row is treated as a click that jittered, not
a move — same trackpad rule as everywhere else in drill. `Ctrl+B` is *not*
bound at the `/` prompt (there it stays cursor-to-start) or in the REPL
(readline's backward-char; tmux users keep their prefix too) — that is what
`Cmd+B` is for. The sidebar is drill's one plugin-powered feature; it loads
on first press and never before, and `Ctrl+S`/`Ctrl+E`/`Ctrl+R` pressed in
the tree do nothing, exactly as in the directory listing below.

## The directory listing

`d` / `dt` / `ds` with no argument opens the listing instead of a file. It is
Expand Down
26 changes: 23 additions & 3 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -84,7 +84,11 @@ And underneath it is still real Neovim: `hjkl`, `dd`, `ciw`, macros and

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P3: The README still tells users 'Requires Neovim 0.9+', but this PR bumps install.sh's Neovim gate to 0.10+ (the sidebar's pinned plugins need 0.10). Update the Install details line to match so the documented minimum matches the enforced minimum.

Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At README.md, line 116:

<comment>The README still tells users 'Requires Neovim 0.9+', but this PR bumps install.sh's Neovim gate to 0.10+ (the sidebar's pinned plugins need 0.10). Update the Install details line to match so the documented minimum matches the enforced minimum.</comment>

<file context>
@@ -109,6 +113,22 @@ t -k      # stop
 
 Non-blocking, with a sound and a desktop notification at zero.
 
+## `Ctrl+B` — a file sidebar, when you want one
+
+The one optional extra: press `Ctrl+B` (or `Cmd+B` on a Mac that forwards it,
</file context>

## Fast enough to keep a thought

Two files. No plugins, no plugin manager, no LSP, no startup spinner. From any
No plugin manager, no LSP, no startup spinner. One deliberate exception to
"no plugins": the optional `Ctrl+B` file sidebar is three checkouts pinned to
exact commits, fetched once by `vendor.sh`, and **loaded only the first time
you press the key** — never toggle it and nothing is even added to the
runtime path. From any
terminal, mid-thought, you're in the editor before the thought fades — and
everything **auto-saves** about 0.7s after you stop typing, plus instantly when
you switch away. Quit whenever; `d two-sum` brings it all back. You never lose
Expand All @@ -93,7 +97,7 @@ work, because there is no unsaved state to lose.
## Nothing helps you type

No autocomplete. No LSP, no Copilot, no snippets, no signature hints, no
auto-import. Not "turned off" — **absent, at every source, and a 558-case test
auto-import. Not "turned off" — **absent, at every source, and a 729-case test
suite asserts it**. Syntax highlighting is the only thing on screen besides your
own keystrokes. Your `~/.config/nvim` and `~/.vimrc` are never touched; the
config loads with `nvim -u` and exists only inside drill.
Expand All @@ -109,6 +113,22 @@ t -k # stop

Non-blocking, with a sound and a desktop notification at zero.

## `Ctrl+B` — a file sidebar, when you want one

The one optional extra: press `Ctrl+B` (or `Cmd+B` on a Mac that forwards it,
REPL included) and a file tree slides in on the left — press again and it is
gone, with your caret still in your code either way. It is built for the
mouse: **one click** opens a file or folds a folder, **`[+ File]` /
`[+ Folder]` are real clickable buttons**, **right-click** gets a menu (new,
rename, cut/copy/paste, delete, open in split), **Ctrl+click** multi-selects,
and **dragging a file onto a folder moves it** — drag a selected one and the
whole selection moves. Details: [docs/explorer.md](docs/explorer.md).

This is drill's only plugin-powered feature, and it stays out of the way: the
three checkouts are pinned to exact commits and load lazily on the first
press, so an editor that never opens the sidebar runs exactly the config it
always has.

## The quality-of-life layer

- **Quit that can't misfire** — `Ctrl+Shift+Q` asks first, and Cancel is the
Expand Down Expand Up @@ -161,7 +181,7 @@ so `git pull` stays clean forever.
| [KEYS.md](KEYS.md) | every keybinding, every conflict, and how each is resolved |
| [docs/design.md](docs/design.md) | why the editor behaves the way it does — the mouse, autosave, search, quitting, and the five deliberate trade-offs |
| [docs/verify.md](docs/verify.md) | prove nothing is helping you, and that your own nvim is untouched |
| [docs/testing.md](docs/testing.md) | the 558-case gate that drives the real config with real keycodes |
| [docs/testing.md](docs/testing.md) | the 729-case gate that drives the real config with real keycodes |
| [docs/recording.md](docs/recording.md) | `demo.sh` — the self-driving tour that recorded the video above |
| [docs/wsl.md](docs/wsl.md) | WSL: the clipboard, the timer's sound and notification, and the one thing you have to check by hand |

Expand Down
75 changes: 75 additions & 0 deletions docs/explorer.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,75 @@
# The file sidebar

`Ctrl+B` shows it, `Ctrl+B` hides it, and in both directions your caret stays
in your code — the tree is furniture, not a destination. On a Mac whose
terminal forwards Cmd ([docs/macos-cmd.md](macos-cmd.md)), `Cmd+B` does the
same thing and also works from inside the REPL, where `Ctrl+B` is
deliberately left alone (readline's backward-char, and tmux's prefix).

**It roots at the folder of the file you are in**, freshly on every open —
`d lld-prac main` shows the `lld-prac/` folder with `main.py` highlighted,
no matter which directory your shell happened to be in when you typed it.
(drill launches nvim from wherever you were; the shell's directory is a tree
of everything *except* your project.) Switch to a file elsewhere and the next
`Ctrl+B` roots there instead.

## The gestures

Everything is a mouse gesture; the keyboard is only for typing names.

- **Click a file** — opens it, in the file window. Never in the interpreter:
the open logic refuses terminal windows outright.
- **Click a folder** — folds or unfolds it. One click. There is no
double-click folklore anywhere in the tree.
- **`[+ File]` / `[+ Folder]`** — real buttons in the tree's top bar. Click,
type a name at the prompt, Enter. The location is taken from the row your
cursor is on: a folder row means *in here*, a file row means *next to me*.
- **Right-click** — a menu on the row you clicked: New file, New folder,
Open, Open in split, Rename, Cut, Copy, Paste, Delete. Plain text labels
on purpose — no nerd font required, anywhere in the sidebar.
- **Ctrl+click** — select several files (marked with `*`).
- **Drag a file onto a folder** — moves it into that folder. Drop it on a
file instead and it moves next to that file. If the row you dragged was
marked, the **whole marked selection moves** in one gesture.

A press that drifts a cell before the button comes up is a click that
jittered, not a drag — the same trackpad rule the rest of drill's mouse
handling lives by, so nothing ever moves by accident.

Inside the tree, drill's file keys say no the same way they do in the
directory listing: `Ctrl+S`, `Ctrl+E`, `Ctrl+R` do nothing there.

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P3: The doc promises that Ctrl+E and Ctrl+R "do nothing" inside the tree, but the global mappings still act there: repl_show and run_once only gate on source_state(), which, because the NvimTree buffer fails typing_buffer() (it is not modifiable and is not the netrw listing), simply returns the stale non-nil last_file. So once any file has been opened, pressing Ctrl+E in the tree focuses/opens the interpreter and Ctrl+R runs the file, neither of which is "nothing" — the same applies in the netrw listing the doc cites as the comparison. The suite only asserts "no wedge / no extra window" (tests/tree_drive.py), not a true no-op, so the tests don't back the doc claim. Either narrow the sentence to what actually holds (only Ctrl+S, which save() guards on typing_buffer(), is a no-op) or add a buffer-type guard to repl_show/run_once if the intent is genuinely inert keys in the tree.

Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At docs/explorer.md, line 33:

<comment>The doc promises that `Ctrl+E` and `Ctrl+R` "do nothing" inside the tree, but the global mappings still act there: `repl_show` and `run_once` only gate on `source_state()`, which, because the NvimTree buffer fails `typing_buffer()` (it is not modifiable and is not the netrw listing), simply returns the stale non-nil `last_file`. So once any file has been opened, pressing `Ctrl+E` in the tree focuses/opens the interpreter and `Ctrl+R` runs the file, neither of which is "nothing" — the same applies in the netrw listing the doc cites as the comparison. The suite only asserts "no wedge / no extra window" (tests/tree_drive.py), not a true no-op, so the tests don't back the doc claim. Either narrow the sentence to what actually holds (only `Ctrl+S`, which `save()` guards on `typing_buffer()`, is a no-op) or add a buffer-type guard to `repl_show`/`run_once` if the intent is genuinely inert keys in the tree.</comment>

<file context>
@@ -0,0 +1,60 @@
+handling lives by, so nothing ever moves by accident.
+
+Inside the tree, drill's file keys say no the same way they do in the
+directory listing: `Ctrl+S`, `Ctrl+E`, `Ctrl+R` do nothing there.
+
+## What it costs when you don't use it
</file context>


## What it costs when you don't use it

Nothing. This is drill's one exception to "no plugins", and it is built to
stay an exception:

- The three checkouts (nvim-tree, and volt+menu for the right-click menu)
are **pinned to exact commits** by `vendor.sh` — a table of three SHAs and
a loop, not a plugin manager. The same commits on every machine, every
install, until a pin is bumped on purpose.
- **Nothing loads at startup.** The checkouts are not even on the runtime
path until the first `Ctrl+B` of the session. Never press it and you run
the config drill always had, byte for byte.
- The same change **sealed the accidental plugin door**: plugins in your
`~/.local/share/nvim/site` used to load inside drill through `packpath`
even though the README said "no plugins". They no longer do. Your own
nvim setup outside drill is untouched either way.

## When it says no

- *"sidebar plugins are not fetched"* — run `./vendor.sh` in your drill
directory (`~/drill` unless you moved it). `install.sh` does this for you;
a `git pull` upgrade does not.
- *"the sidebar needs nvim 0.10+"* — everything else in drill still works;

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P3: install.sh now hard-requires Neovim 0.10+ (HARD abort), but README.md still states "Requires Neovim 0.9+" and this new line implies the rest of drill runs fine on 0.9. Align the minimum version in README.md (and soften this bullet) so users don't rely on 0.9 support that the installer refuses.

Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At docs/explorer.md, line 57:

<comment>install.sh now hard-requires Neovim 0.10+ (HARD abort), but README.md still states "Requires Neovim 0.9+" and this new line implies the rest of drill runs fine on 0.9. Align the minimum version in README.md (and soften this bullet) so users don't rely on 0.9 support that the installer refuses.</comment>

<file context>
@@ -0,0 +1,60 @@
+- *"sidebar plugins are not fetched"* — run `./vendor.sh` in your drill
+  directory (`~/drill` unless you moved it). `install.sh` does this for you;
+  a `git pull` upgrade does not.
+- *"the sidebar needs nvim 0.10+"* — everything else in drill still works;
+  the pinned plugins are what need 0.10.
+- `Cmd+B` does nothing — your terminal is not forwarding Cmd. One setting:
</file context>

the pinned plugins are what need 0.10.
- `Cmd+B` does nothing — your terminal is not forwarding Cmd. One setting:
[docs/macos-cmd.md](macos-cmd.md).
- **Right-click opens your terminal's own menu instead of drill's** — the
terminal is eating the click before nvim sees it. In iTerm2 that is a
Pointer binding, and it wins over mouse reporting: **Settings → Pointer →
Bindings**, select the *Right button · single click → Open Context Menu*
row, press **−** to remove it. With the binding gone, the click reaches
drill whenever the editor is running, and iTerm2 still shows its own menu
at the shell prompt, where nothing is listening for the mouse. (Ctrl-click
in drill is multi-select, not the menu — that one is drill's on purpose.)
3 changes: 2 additions & 1 deletion docs/macos-cmd.md
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,7 @@ So drill binds every editor chord on Cmd too, same modes, same handlers:
| `Cmd+Z` | undo |
| `Cmd+Shift+Z` | redo (the mac redo; `Ctrl+Y` still works everywhere) |
| `Cmd+/` | comment / uncomment |
| `Cmd+B` | the file sidebar — including from the REPL, where `Ctrl+B` is left to readline |
| `Cmd+Q` | quit, with the same confirmation as `Ctrl+Shift+Q` |
| `Cmd+←` / `Cmd+→` | start / end of line |
| `Cmd+↑` / `Cmd+↓` | start / end of file |
Expand All @@ -56,7 +57,7 @@ like Shift+arrows and typing over that selection replaces it.
an *unmapped* forwarded chord does not die quietly in nvim — measured, a bare
`<D-w>` typed the literal text `<D-w>` into the buffer in insert, replaced
the selection with it in Select mode, and ate a character in normal mode. So
a reflexive Cmd+W or Cmd+B from a mac hand would spray key notation into
a reflexive Cmd+W or Cmd+N from a mac hand would spray key notation into
your file. drill floors every printable Cmd chord to a no-op — in the REPL
too, where the junk would have gone to python — and binds the useful ones on
top.
Expand Down
9 changes: 5 additions & 4 deletions docs/testing.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,20 +5,21 @@ and diffing what comes out. Nothing is mocked: if a case passes, that keystroke
does that thing in this config.

```sh
./tests/run.sh # 558 cases; exit 0 only if all pass
./tests/run.sh # 729 cases; exit 0 only if all pass
./tests/run.sh sel_ # just the select-mode cases
```

| Suite | Cases | What it drives |
|---|---|---|
| `suite_options.sh` | 86 | config invariants no key-driven test can see: completion off at every source, zero LSP clients, no swap/backup/undo files, the cursor shape in both panes, and every mapping registered in the modes it claims — including that `Ctrl+/` is *not* bound in terminal mode and `Ctrl+C` is *not* bound in normal or terminal, so SIGINT still reaches a running program |
| `suite_config.sh` | 30 | headless nvim: shift+arrow selection, `Tab`/`Shift+Tab`, cut/paste/select-all, undo/redo, `Ctrl+C` copying without losing the selection |
| `suite_mouse.sh` | 58 | pty: click to caret from every mode, into empty space and past EOF, jitter in both axes, real drags, double-click, Option/Ctrl+click, and clicks between the file and the interpreter |
| `suite_options.sh` | 172 | config invariants no key-driven test can see: completion off at every source, zero LSP clients, no swap/backup/undo files, no third-party site dir on `runtimepath` OR `packpath`, the cursor shape in both panes, and every mapping registered in the modes it claims — the whole Cmd layer and the sidebar toggle included, and that `Ctrl+/` is *not* bound in terminal mode and `Ctrl+C` is *not* bound in normal or terminal, so SIGINT still reaches a running program |
| `suite_config.sh` | 52 | headless nvim: shift+arrow selection, `Tab`/`Shift+Tab`, cut/paste/select-all, undo/redo, `Ctrl+C` copying without losing the selection, and the Cmd chords with their `<Nop>` floor |
| `suite_mouse.sh` | 66 | pty: click to caret from every mode, into empty space and past EOF, jitter in both axes, real drags, double-click, Option/Ctrl+click, clicks between the file and the interpreter, and backspacing out of the ghost space |
| `suite_search.sh` | 36 | `Ctrl+F`, exactly when the highlight and hints appear and go, and the Esc chain — including that it resumes typing at the exact column. Headless **and** pty: the Esc chain cannot be seen headlessly, because feedkeys force-ends Insert as the typeahead drains |
| `suite_autosave.sh` | 8 | what is on **disk**, read back from the shell |
| `suite_quit.sh` | 21 | pty: the confirmation is drawn, Cancel and Esc both return you to typing, Quit writes the pending edit, and all of it again from inside the REPL |
| `suite_runwin.sh` | 12 | pty: the `Ctrl+R` output window — that it closes when you leave it, that any key still closes it from inside, that `Ctrl+R` then `Ctrl+E` is two windows and not three, and that a program still running is left alone |
| `suite_netrw.sh` | 10 | pty: the directory listing is not a file — `Ctrl+S` does not wedge it, `Ctrl+E`/`Ctrl+R` start nothing. Headless is useless here: it does not produce a netrw buffer at all |
| `suite_tree.sh` | 55 | pty: the `Ctrl+B` sidebar, gesture by gesture — both toggle chords (Cmd+B as raw CSI-u bytes), focus that never leaves your code, single-click open and fold, the winbar `[+ File]`/`[+ Folder]` buttons all the way to files on disk, the right-click menu drawn and an item clicked by its float coordinates, ctrl-click marks, drag-and-drop single and marked-pair moves, a jitter that must not move anything, the three-window layout with the REPL, and the root landing on the file's folder on every open |
| `suite_timer.sh` | 36 | not nvim at all — 18 assertions against `drill.sh` run under **bash and zsh**, which disagree about word splitting |
| `suite_projects.sh` | 40 | also not nvim — nested `d <dir> <name>` and `d search` against stubbed `nvim`/`fzf` binaries in a scratch `DRILL_HOME`, under **bash and zsh** — including that `..` cannot leave the bucket and bracketed names resolve literally |
| `suite_preload.sh` | 26 | the LeetCode desk: import-free files using the toolkit under `r`/`ri` in **bash and zsh** — shadowing, argv, `__name__`, exit codes, sibling imports in project folders, names still live at the prompt after a raise, a typo'd name getting python's own error, the plain-python3 fallback — plus a pty pass proving `Ctrl+R` and `Ctrl+E` route through the shim |
Expand Down
5 changes: 4 additions & 1 deletion docs/verify.md
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,10 @@ And confirm it left your own setup alone: run plain `nvim` and check
- No autocomplete, no LSP, no Copilot, no AI, no snippet expansion
- No linter suggestions, no auto-import, no signature hints
- Syntax highlighting only — nothing else appears on screen while you type
- No plugin manager, no distro
- No plugin manager, no distro. The one exception to "no plugins" is the
optional `Ctrl+B` sidebar: three checkouts pinned to exact commits by
`vendor.sh`, loaded only when you first press the key, and none of them
touches what appears while you type
- No swapfile, no backup, no undo file — the directory holds the `.py` you
wrote and nothing else
- Stock Python 3, Neovim and fzf — and fzf only powers `d search`, never the
Expand Down
2 changes: 1 addition & 1 deletion docs/wsl.md
Original file line number Diff line number Diff line change
Expand Up @@ -142,7 +142,7 @@ Ctrl+click are both just clicks, not vim surprises.
## Running the tests

```sh
./tests/run.sh # 558 cases
./tests/run.sh # 729 cases
```

Needs a clipboard provider on PATH; `run.sh` warns up front if there is none,
Expand Down
Loading
Loading