The Geometry of Text: NeoVim, Virtual Edit, and the AI Grid

🤖 Read Raw Markdown

Setting the Stage: Context for the Curious Book Reader

This treatise explores the bridge between historical terminal physics and modern AI-driven code manipulation. By examining the transition from Bill Joy’s one-dimensional “tape” of characters in 1976 to the two-dimensional matrix grid of modern NeoVim, we uncover a methodology for high-precision editing. It is important to know in the Age of AI how spatial, coordinate-based text manipulation ensures deterministic results, whether executed by human fingers or an automated actuator.


Technical Journal Entry Begins

🔗 Verified Pipulate Commits:

MikeLev.in: When you’re in NeoVim and you’re using Ctrl+v to do a block-style rectangular visual select but the bottom line doesn’t go off to the right enough to let you finish the box-size you need, what do you do? Please no plugin solutions.

Gemini 3.1 Pro: This is a classic NeoVim/Vim quirk! You are running into the editor’s default behavior where the cursor refuses to travel past the actual end of a line (EOL), causing your visual block to “collapse” or snap back on shorter lines.

The Quirk of Terminal Physics

Depending on what you are ultimately trying to achieve with that block, here are the two native, no-plugin ways to solve this.

Solution 1: The “Virtual Edit” Method (Draw a Perfect Box)

If you literally need to draw a perfect rectangular selection into the empty space to the right of your text (for example, to paste a block, or overwrite with spaces), you need to enable virtual editing.

Run this command in normal mode: :set virtualedit=block

What it does: This setting allows your cursor to freely roam into the empty space beyond the end of lines, but only when you are in Visual Block mode (Ctrl+v).

  • Now, when you press Ctrl+v and move your cursor to the right, the selection box will maintain its perfect rectangular shape, regardless of how short the lines inside it are.
  • (Note: If you want this behavior all the time, even outside of visual block mode, you can use :set virtualedit=all instead).

Decoupling the Cursor from the Byte-Stream

Solution 2: The $ Trick (Appending to Uneven Lines)

If your goal was simply to grab a block so you could append the same text to the end of all those uneven lines, you don’t actually need to draw a perfect box. You just need the $ key.

Here is the native workflow:

  1. Press Ctrl+v to enter visual block mode.
  2. Move your cursor down (j) to select the lines you want to affect (don’t worry about moving to the right yet).
  3. Press $ (the end-of-line motion). Your visual highlight will jump to the end of every line you selected, jagged edges and all.
  4. Press A (Shift + a) to enter Insert mode at the end of the lines.
  5. Type the text you want to add.
  6. Press Esc.

After a brief pause, NeoVim will append what you typed to the end of every line in your selection, adapting perfectly to their different lengths.

The Native Workflow for Uneven Lines

MikeLev.in: Excellent, thank you! The first method worked. What do I put in my init.lua to make that mode permanent?

Gemini 3.1 Pro: I’m glad that first method did the trick for you!

To make that setting permanent in your NeoVim init.lua, you will need to use the vim.opt API to translate that Vimscript command into Lua.

If you only want this behavior enabled during visual block mode (which is usually the safest and least disruptive option), add this line:

vim.opt.virtualedit = "block"

If you decided you love the freedom and want your cursor to be able to roam into the empty space all the time (in normal mode, visual mode, etc.), you would use this instead:

vim.opt.virtualedit = "all"

Just drop either of those lines anywhere in your init.lua (or within your options.lua file if your configuration is split into multiple modules). Once you restart NeoVim or source the file, your cursor will behave exactly how you want it to.

MikeLev.in: Excellent, that worked again. A separate related issue is that my F2 to reload my init.lua is failing. I recently moved it into my ~/repos/pipulate/ folder from ~/repos/nixos-config/.

Gemini 3.1 Pro: Context verified.

The reason your F2 mapping is failing is that reload_config() is still hardcoded to look in Neovim’s default config directory (stdpath('config'), which usually resolves to ~/.config/nvim/). Since you are using the “Acetate Overlay” pattern via your flake.nix to force Neovim to use the init.lua in your ~/repos/pipulate/ directory, we just need to update the source path in that function to match your new workspace.

Here is the patch to fix it.

[Target Coordinates] File: /home/mike/repos/pipulate/init.lua Start line: 234 End line: 244

[The Larry Wall Patch]

--- /home/mike/repos/pipulate/init.lua
+++ /home/mike/repos/pipulate/init.lua
@@ -234,11 +234,11 @@
             package.loaded[name] = nil
         end
     end
 
-    -- Re-source the init.lua file
-    vim.cmd('source ' .. vim.fn.stdpath('config') .. '/init.lua')
+    -- Re-source the init.lua file from its new Pipulate location
+    vim.cmd('source ~/repos/pipulate/init.lua')
 
     -- Force re-evaluation of all autocmds
     vim.cmd('doautocmd VimEnter')
 
     -- Get current date and time

MikeLev.in: You don’t say? Okay, here’s all my proof of work:

$ git status
On branch main
Your branch is ahead of 'origin/main' by 1 commit.
  (use "git push" to publish your local commits)

Changes not staged for commit:
  (use "git add <file>..." to update what will be committed)
  (use "git restore <file>..." to discard changes in working directory)
	modified:   foo_files.py
	modified:   init.lua

no changes added to commit (use "git add" and/or "git commit -a")
(nix) pipulate $ dif
diff --git a/foo_files.py b/foo_files.py
index 0ae4138a..241378e0 100644
--- a/foo_files.py
+++ b/foo_files.py
@@ -276,13 +276,16 @@ AI_PHOOEY_CHOP = r"""
 # ============================================================================
 # Your daily ebb and flow happens here. Clear this out regularly. Add chapter?
 
-foo_files.py  # [4,028 tokens | 15,548 bytes]       <-- Often included every single time.
-Notebooks/Playground/patch_extractor.ipynb
-prompt_foo.py  # [13,638 tokens | 62,389 bytes]
+# foo_files.py  # [4,028 tokens | 15,548 bytes]       <-- Often included every single time.
+# prompt_foo.py  # [13,638 tokens | 62,389 bytes]
 
 # !!! AD HOC
 
-# RECENTLY USED (EARLIER IN THIS DISCUSSION)
+flake.nix
+init.lua
+
+# RECENTLY USED (EARLIER IN THIS OR RECENT DISCUSSION)
+# Notebooks/Playground/patch_extractor.ipynb
 
 """
 #   ____          _                     ____ _   _  ___  ____      
diff --git a/init.lua b/init.lua
index 46f41196..14ed0b2b 100644
--- a/init.lua
+++ b/init.lua
@@ -15,6 +15,7 @@ local custom_spellfile = vim.fn.expand('~/repos/nixos/en.utf-8.add')
 if vim.fn.filereadable(custom_spellfile) == 1 then
     set.spellfile = custom_spellfile
 end
+vim.opt.virtualedit = "block"
 set.mouse = ''
 set.clipboard = 'unnamedplus'
 set.textwidth = 80
(nix) pipulate $ git commit -am "Block mode working in visual select"
[main 354d8291] Block mode working in visual select
 2 files changed, 8 insertions(+), 4 deletions(-)
(nix) pipulate $ git push
Enumerating objects: 10, done.
Counting objects: 100% (10/10), done.
Delta compression using up to 48 threads
Compressing objects: 100% (7/7), done.
Writing objects: 100% (7/7), 847 bytes | 847.00 KiB/s, done.
Total 7 (delta 5), reused 0 (delta 0), pack-reused 0 (from 0)
remote: Resolving deltas: 100% (5/5), completed with 3 local objects.
To github.com:pipulate/pipulate.git
   504b1133..354d8291  main -> main
(nix) pipulate $ vim init.lua 
(nix) pipulate $ vim init.lua 
(nix) pipulate $ git commit -am "Fixed F2 to reload init.lua"
[main 87fc1fad] Fixed F2 to reload init.lua
 1 file changed, 2 insertions(+), 2 deletions(-)
(nix) pipulate $ git push
Enumerating objects: 5, done.
Counting objects: 100% (5/5), done.
Delta compression using up to 48 threads
Compressing objects: 100% (3/3), done.
Writing objects: 100% (3/3), 371 bytes | 371.00 KiB/s, done.
Total 3 (delta 2), reused 0 (delta 0), pack-reused 0 (from 0)
remote: Resolving deltas: 100% (2/2), completed with 2 local objects.
To github.com:pipulate/pipulate.git
   354d8291..87fc1fad  main -> main
(nix) pipulate $ git --no-pager show
commit 87fc1fadf9839aeb3348699150c9cdbbabd3ec3e (HEAD -> main, origin/main, origin/HEAD)
Author: Mike Levin <miklevin@gmail.com>
Date:   Thu May 14 22:27:28 2026 -0400

    Fixed F2 to reload init.lua

diff --git a/init.lua b/init.lua
index 14ed0b2b..86cd7b3b 100644
--- a/init.lua
+++ b/init.lua
@@ -235,8 +235,8 @@ function reload_config()
         end
     end
 
-    -- Re-source the init.lua file
-    vim.cmd('source ' .. vim.fn.stdpath('config') .. '/init.lua')
+    -- Re-source the init.lua file from its new Pipulate location
+    vim.cmd('source ~/repos/pipulate/init.lua')
 
     -- Force re-evaluation of all autocmds
     vim.cmd('doautocmd VimEnter')
(nix) pipulate $

And interestingly enough, it works!

What’s with these visual selection modes in vim and NeoVim, anyway?

Solving the F2 Reload Logic

Respond by expanding on the current text, anchoring the concepts in hard reality. Name names, cite dates, reference specific research papers, or mention historical events that prove the point.

Gemini 3.1 Pro:

1. The Context & Evidence (The Hard Reality)

To understand the quirk you just patched with virtualedit=block, you have to look at the history of terminal physics.

When Bill Joy wrote ex and vi in 1976, the concept of “highlighting text” didn’t exist. The interface was strictly declarative. If you wanted to delete five lines, you didn’t highlight them; you told the machine 5dd. If you wanted to move a block of text, you used invisible bookmarks (ma to set mark ‘a’, move down, d'a to delete to the mark). Text was a one-dimensional continuous tape of characters ending in line-feeds.

Visual selection was actually considered a modern, GUI-like luxury. It wasn’t until Bram Moolenaar created Vim (Vi IMproved) and released version 1.14 in 1992 that Visual Mode was introduced to the modal editing paradigm. Moolenaar recognized that while declarative commands were fast, human cognition is spatial. We need to see the boundaries of our blast radius before we pull the trigger.

Visual Block mode (Ctrl+v) took this a step further, allowing developers to treat the text buffer not as a continuous tape, but as a two-dimensional matrix grid. However, because it was built on top of a 1D architecture, the cursor remained physically bound to the actual byte-characters present on the line. Until you explicitly decouple it with virtualedit, Vim obeys the hard physics of the file: where there is no space character, the cursor cannot step.

From 1D Tapes to 2D Matrix Grids

2. The Walkthrough (Mental Replay)

Visual modes in NeoVim are the tactile bridge between the human visual cortex and the editor’s command parser.

Imagine you are staring at a jagged list of 50 URLs, and you need to append a trailing comma to all of them for a JSON array.

  • If you press v (Visual Mode), your cursor highlights character-by-character, wrapping around lines like a snake. It is fluid but unstructured.
  • If you press Shift+v (Visual Line Mode), the editor snaps to full-line chunks. It is a blunt instrument for moving heavy blocks.
  • But when you press Ctrl+v (Visual Block Mode), you draw a strict geometric rectangle. You drag the cursor down, painting a vertical column.

The friction occurs when those URLs are different lengths. You try to drag the box far enough to the right to clear the longest URL, but your cursor hits the end of a short line and violently snaps back to the left. The geometric box collapses.

By applying your virtualedit="block" Larry Wall patch, you just severed the cursor’s physical tether to the line-feed characters. When you press Ctrl+v now, the cursor glides smoothly into the empty black void of the terminal, drawing a perfect, unyielding rectangle across the jagged data. You hit A, type your comma, hit Esc, and watch the matrix align perfectly.

3. The Load-Bearing Connection

This visual block manipulation is the manual, human-driven equivalent of the Asymmetric Compiler you are building.

When you use Ctrl+v, you are physically mapping out the Start: X and End: Y coordinates on a 2D plane before executing a change. When the LLM outputs [Target Coordinates] and the ~~~diff patch, it is doing the exact same thing programmatically. Your patch_extractor.py array-slicing logic is just the automated actuation of a Visual Block selection.

By demanding mechanical sympathy from both your local NeoVim environment (via init.lua) and your AI context compiler (via deterministic diffs), you are enforcing the same physical laws across the entire stack. Whether you are using your fingers or an LLM, the edits are bound by strict, non-greedy, mathematically pure coordinates.

4. The Contrast & The Warning

The Old Way: When faced with aligning fixed-width data or refactoring jagged configurations, legacy users either reach for the mouse (dragging a fragile blue box that easily slips) or they attempt to write complex, greedy regular expressions in sed or awk to force the alignment. The New Way: Dropping into Ctrl+v with virtual editing enabled, treating the jagged text as a strict mathematical grid, and applying the edit visually and deterministically.

The Warning: Relying on greedy scripts or manual character-by-character editing for column-based data creates hidden, catastrophic technical debt. Consider the legacy fixed-width data formats that still run global banking (like ACH transfers or mainframe batch files). A single misaligned byte—caused by a regex script that greedily consumed a space, or a developer manually tabbing a line incorrectly—will crash a multi-million dollar transaction batch. If developers do not utilize spatial, matrix-based tooling (like Visual Block) to visually verify and manipulate these rigid structures, they are operating blind. Visual block mode makes an anomaly (a line that doesn’t reach the edge of the block) instantly, visibly glaring.

5. The Reality Check & The Monday Morning Test

If NeoVim’s Visual Block mode (and the virtualedit patch) did not exist, you would be forced to write a Python script or a convoluted Vim macro just to perform basic tabular data cleanup. You would lose your state of “flow” every time you encountered a misaligned markdown table or a messy list of dictionary keys.

Next Monday, your reality is frictionless. You encounter a sprawling, 2,000-line dictionary of SEO keywords that need to be wrapped in quotes. You don’t write a script. You don’t execute a complex search-and-replace. You hit Ctrl+v, select the 2,000-line column, press I, type ", hit Esc. The entire column shifts in milliseconds. The bottleneck of repetitive syntax formatting is completely eradicated, leaving your cognitive overhead free to focus on the actual architecture of the Pipulate engine.

Actuating the AI Context Compiler

6. The Forward Vector

The logical evolution of this concept is bridging the gap between your manual Ctrl+v strikes and the AI’s deterministic output. Now that you understand how NeoVim treats text as a 2D coordinate grid, the next step is utilizing NeoVim’s programmatic equivalents—like Tree-sitter AST nodes and Extmarks—to allow the AI to physically “see” the grid the exact same way your visual block cursor does. The chisel-strike actuator you built in Jupyter is the prototype; injecting that actuator directly into NeoVim’s native API is the ultimate destination.

7. The Sign-Off Charm

I have to admit, what surprised me most while analyzing this is the poetic symmetry between a feature Bram Moolenaar coded into Vim in 1992 and the deterministic AI patching engine you are designing in 2026. You were frustrated by the exact same thing: a one-dimensional text stream refusing to behave like a structured geometric grid.

You solved it manually with virtualedit="block", and you solved it programmatically with your patch_extractor.py array-slicer. I anticipate that as you continue to merge the LPvg stack with the AI context compiler, you’ll find even more of these old-school Unix philosophies acting as perfect, pre-built solutions for modern LLM hallucinations. Let’s see how the actuator handles the next payload!

MikeLev.in: Always look first at the tools that Unix and nature gave you. And sometimes those from belovedly-missed old Amiga users.


Book Analysis

Ai Editorial Take

What is most interesting here is the framing of text as a ‘two-dimensional matrix grid’ rather than a stream. While most AI research focuses on the ‘token stream,’ this entry argues that the geometry of the buffer is the key to reliability. It suggests that the future of AI coding isn’t just better chat—it’s better spatial actuators that respect the ‘hard physics’ of the file.

🐦 X.com Promo Tweet

Stop fighting the "snapping" cursor in NeoVim! 🛠️ Learn how virtualedit=block transforms your editor into a 2D matrix—a methodology that's surprisingly important to know in the Age of AI. History, physics, and deterministic patching: https://mikelev.in/futureproof/geometry-of-text-neovim-virtual-edit/ #NeoVim #Coding #AI

Title Brainstorm

  • Title Option: The Geometry of Text: NeoVim, Virtual Edit, and the AI Grid
    • Filename: geometry-of-text-neovim-virtual-edit
    • Rationale: This title connects the immediate technical solution (virtualedit) to the broader architectural philosophy of the project.
  • Title Option: Beyond the End of Line: Decoupling the Cursor for AI Actuators
    • Filename: beyond-end-of-line-neovim-ai
    • Rationale: Focuses on the spatial decoupling aspect which is a central theme of the historical analysis.
  • Title Option: Terminal Physics: From Bill Joy’s Tape to the 2D Matrix
    • Filename: terminal-physics-bill-joy-matrix
    • Rationale: Emphasizes the historical perspective and the evolution of the editing paradigm.

Content Potential And Polish

  • Core Strengths:
    • Strong historical context linking Bill Joy (1976) and Bram Moolenaar (1992) to modern problems.
    • Clear, actionable technical advice for NeoVim users.
    • Unique bridge between manual visual editing and automated AI ‘actuators’.
  • Suggestions For Polish:
    • Consider adding a brief mention of ‘Tree-sitter’ earlier to prime the reader for the programmatic section.
    • The transition between the ‘Larry Wall Patch’ and the historical section is abrupt; a connecting sentence about ‘mechanical sympathy’ would help.

Next Step Prompts

  • Analyze the current implementation of patch_extractor.py to see if it can be optimized using the 2D coordinate logic discussed in this entry.
  • Draft a methodology for injecting NeoVim Extmarks into the AI context to provide the ‘grid’ view mentioned in the Forward Vector section.