{"slug": "vim-pi-chat-make-vim-a-pi-powered-editor", "title": "Vim-pi-chat: make Vim a pi-powered editor", "summary": "Developer galeone released vim-pi-chat, a Vim 9 plugin that embeds the pi coding agent in a chat window by spawning `pi --mode rpc` as a background job over a strict JSON-lines protocol. The plugin requires Vim 9.0+ compiled with `+job +channel` and the pi CLI on `$PATH`, and does not support Neovim because it is built on Vim's job/channel API. It renders `text_delta` output incrementally, shows tool calls as `⚙ …`/`✓ …`/`✗ …` lines with syntax-highlighted diff previews for `edit` and `write`, and answers extension UI requests such as `notify`, `confirm`, `select` and `input` through native Vim prompts.", "body_md": "A chat window for the [pi coding agent](https://github.com/earendil-works/pi)\ninside Vim 9 (no GUI, no Node.js runtime needed by Vim itself — it just\nspeaks to the `pi` CLI). Neovim is not supported: the plugin is built on Vim's\njob/channel API.\n\n```\n┌────────────────────────────────────────────┬─────────────────────────────┐\n│                                            │ ── pi chat ──               │\n│   your code in the main window             │ Type at the bottom line     │\n│                                            │ ...                         │\n│                                            │ ❯ fix the failing test      │\n│                                            │   ⚙ bash npm test           │\n│                                            │   ✓ bash                    │\n│                                            │ The test failed on line 42  │\n│                                            │ because...                  │\n│                                            │ ──────────────────────────  │\n└────────────────────────────────────────────┴─────────────────────────────┘\n```\n\nThe plugin spawns `pi --mode rpc` as a background job\n(`job_start`/` ch_sendraw`). RPC mode is a strict JSON-lines protocol over\nstdin/stdout (see pi's `docs/rpc.md`): commands go in as one JSON object per\nline, events come back as one JSON object per line. The plugin:\n\n- buffers stdout lines and drains them on a `timer_start` tick, so output\nnever interrupts you mid-keystroke;\n- renders `text_delta` deltas incrementally (line-buffered), tool executions\nas`⚙ …` /`✓ …` /`✗ …` lines, and your prompts as`❯ …` ;`edit` and`write` tool calls additionally get a syntax-highlighted diff preview in\nthe same format as pi's TUI (`g:pi_chat_tool_diff` ), and other tools show\nthe first lines of their output under the`✓` line (`│ …` ,`g:pi_chat_tool_output` ; a failed tool always shows its error output);\n- answers extension UI requests: `notify` renders as an in-chat transcript\nline with a level marker (ℹ/⚠/⛔), and`confirm` /`select` /`input` use native\nVim prompts showing the request's title and message. Esc always sends a\ncancellation (never a choice: Esc on \"Allow dangerous command?\" does not\nallow it), and an`input` placeholder is shown as a hint, not pre-filled.`setStatus` entries show in the chat statusline,`set_editor_text` prefills\nthe`❯` prompt (once the turn ends, if pi is busy), and`setWidget` /`setTitle` are ignored.`editor` requests are answered as cancelled (see\nLimitations).\n\nThe conversation buffer is a plain text buffer. Everything is the transcript\nexcept the `❯` prompt block at the bottom: type there, `<CR>` sends, and\n`<C-CR>` opens a continuation line so prompts can span\nmultiple lines. The transcript above the prompt is guarded by an autocmd:\nany interactive edit is detected and reverted (the buffer stays modifiable\nso the plugin can keep appending to it, and yank/visual still work).\n\n```\ngit clone https://github.com/galeone/vim-pi-chat ~/.vim/pack/plugins/start/vim-pi-chat\n```\n\n(or any path under `pack/*/start`, a pathogen/tp plugin dir, or drop\n`plugin/pi_chat.vim` in your runtimepath; with a plugin manager add e.g.\n`Plug 'galeone/vim-pi-chat'` as usual)\n\nRequirements:\n\n- Vim **9.0+** compiled with`+job +channel` (`vim --version | grep job` );\nNeovim is not supported\n- the `pi` CLI installed and on`$PATH`\n- `pi auth` done beforehand (the plugin never handles credentials)\n\n| Command | Effect | \n|---|---|\n| `:PiOpen` | open the chat **and** the thinking panel (chat in a vertical split on the right by default) | \n| `:PiOpen <message>` | open (chat + thinking) and immediately send `<message>` | \n| `:PiOpen quiet` | open (chat + thinking) without moving the cursor to the prompt (no focus steal, no insert mode) — used by the VimEnter boot hook so stray startup input can never be sent to the agent | \n| `:PiSend <text>` | send a prompt to the running agent from any window (no text: jump to the chat prompt); while pi is busy it is queued per `g:pi_chat_streaming_behavior` . Anything half-typed on the`❯` prompt is kept and comes back when the turn ends. Errors if the agent is not running (use`:PiOpen` first) | \n| `:PiAbort` | abort the current run ( `{\"type\":\"abort\"}` ) | \n| `:PiClear` | start a fresh session: restarts the agent process (so extensions never see a replaced session) and **deletes the context's own saved session** from pi's store, reusing the same context-keyed session id. A folder conversation the file had only inherited is left untouched: the file gets its own fresh session | \n| `:PiRestart` | re-launch the pi process in place, resuming the same session (transcript and context kept) | \n| `:PiThinking` | toggle a small read-only panel across the bottom of the screen, under both your code and the chat (opened automatically with `:PiOpen` ) streaming the model's thinking live, auto-scrolled to the newest line (height:`g:pi_chat_thinking_height` ). Thoughts accumulate even while hidden, under a`──── prompt` marker per turn, so opening it later shows past thinking;`:PiClear` /`:PiClose` wipe it | \n| `:PiClose` | stop the agent and close (tear down) the chat | \n| `:PiFile [path]` | show or set the context file (see below) | \n| `<leader>pi` | `:PiOpen` (default mapping, set`g:pi_chat_map` to change) | \n\nIn the chat buffer (buffer-local mappings): `A` (or `i`) jumps to the `❯` prompt and enters insert\nmode, `<CR>` sends (multi-line: continuation lines are joined), and `<C-c>`\naborts the current run. While pi is generating a turn, typing on the prompt\nis ignored (the prompt glyph is hidden and the input line is blanked — you\nsee an in-chat `⏳ pi is working…` line instead — and the statusline\nspinner shows pi is working). The statusline also shows the current model\n(pi reports it via `get_state` / `set_model` / `cycle_model`); model\nchanges are made from the pi side (e.g. pi's own `/model`), not from Vim. To queue a message explicitly while it is\nbusy, use `:PiSend <text>` (handled per `g:pi_chat_streaming_behavior`).\n\nWindow switching is non-destructive: closing the chat window (or leaving it\nand it being the last window on the buffer) just parks the agent — the\ntranscript and your half-typed prompt survive. Reopening with `:PiOpen` or\n`:buffer __PiChat__` resumes the same conversation. `:PiClose` is the one that\nactually tears the session down.\n\nQuitting your last file window takes the panels with it: `:q`, `:x`, `:wq`\nor `ZZ` in the last real window of a tab also closes the pi panels there, so\nthe command exits Vim (or closes the tab) as it would without the plugin — no\n`:qall` needed. With another file window still open, or when you quit from\ninside the chat (which parks the agent), nothing extra happens; `:close` and\n`:only` are never affected. If the quit then fails (e.g. `E37` unsaved\nchanges on `:q`), the panels stay closed and `:PiOpen` brings them back.\nSet `g:pi_chat_quit_with_last_window = 0` for plain Vim behavior.\n\nThe panels also defend their own windows. If you run a buffer-switching\ncommand (`:e file`, `:b`, `:bn`, …) while the cursor is on the chat or\nthinking panel, the file is moved into your last real-file window and the\npanel is restored in place, so a stray `:e` can never replace a panel. With\nno file window open the file stays put and `:PiOpen` brings the panel back.\n\nSessions persist the usual way — pi stores them in\n`~/.pi/agent/sessions` and `:PiOpen` resumes the conversation for the\ncontext file (see Resuming a conversation). Use `g:pi_chat_no_session = 1`\nfor `--no-session`.\n\nThe pi agent has read/write/bash tools but no idea which buffer you had on\nscreen, so the plugin tracks a **context file**: by default it's the buffer\nyou were viewing when `:PiOpen` started the session — if that was a bare\nscratch buffer (plain `vim`), it falls back at send time to the buffer you\nlast had open (Vim's alternate buffer, `#`). Every prompt you send is\ntransparently prefixed with `The file I am working on is: <abs path> (read it if you need its contents; edit it in place when asked)` (shown\nin the transcript as the bare `❯ text` you typed), and the pi job runs with\nthat file's directory as its working directory. Override it at any time with\n`:PiFile <path>` (no argument prints the current one; changing it mid-session\napplies to new prompts, while the job's cwd only changes the next time the pi\nprocess starts: `:PiRestart`, `:PiClear`, or reopening a parked chat).\nUnsaved files work too: the path is passed with a `(not saved to disk yet; create it when I ask for new content)` hint, so pi can create the file with\nits write tool (as long as the parent directory exists). A still-unnamed scratch buffer gives no context file\n(the job then runs in your shell's cwd). Disable with\n`g:pi_chat_context_file = 0`.\n\nWhen pi edits the context file the plugin reloads the buffer from disk so\nyou see the change live — immediately, even if that window is in the\nbackground (the plugin forces a full redraw after reloading, since vim may\notherwise not repaint a non-current window until your next keystroke). This\ncovers the edit/write tools, and — because the plugin snapshots the file's\n`[mtime, size]` around every tool call — *any* tool that rewrites the file\n(a `bash` one-liner included), logged as `ℹ reloaded TODO (changed on disk)`.\nTool calls that pi runs in parallel are tracked individually, so every file\nthey touch is reloaded. The reload goes through Vim's own file-change check\n(`:checktime` with `'autoread'` on for that buffer), so it keeps your cursor\nand undo history and Vim records the new timestamp: no \"file changed\" warning\nfollows on `:w` or focus.\nIf the buffer has unsaved changes of your own, the reload is skipped with a\nnotification and a `:e!` hint, so it never clobbers your in-progress edits.\n\nEdits that don't go through pi at all (your own shell in another terminal,\ngit, another process) are vim's territory: `'autoread'` re-reads an\nunmodified buffer when vim notices the file changed, and never touches a\nbuffer with unsaved changes. In a terminal vim only notices on certain\nevents, so give it one:\n\n```\nset autoread\nset updatetime=200\naugroup AutoRead\n  autocmd!\n  autocmd CursorHold,CursorHoldI * checktime\n  autocmd FocusGained * checktime   \" needs focus events (tmux: set -g focus-events on)\naugroup END\n```\n\nAn unmodified buffer then refreshes within ~200ms of any external write; a\nmodified one is left alone until you `:e!`. (The tempting `clientserver`\nremote-poke alternative is not available in the current MacPorts 9.2 build:\nit advertises `+clientserver` but lacks the `servername` option, like other\nshaved APIs in that build.)\n\nWith `g:pi_chat_track_files` (default `1`) the context also follows your\nworking file automatically: opening a different real file (`:e`, `:b`, …)\nre-keys it (logged in the chat while the agent runs). pi is not sent a prompt\nof its own for the switch, so jumping around files (`:e`, `:b`, quickfix)\ncosts no model turns: your next prompt tells it, opening with `I switched the file I am working on to: <path>` instead of the usual context line. With the\nagent parked, the new file is simply used on the next `:PiOpen`. Set\n`g:pi_chat_track_files = 0` to keep the context fixed until you use `:PiFile`.\n\npi keeps its conversation history in its own session store. The plugin ties\neach session to *your* context so `:PiOpen` picks up where you left off instead\nof starting a fresh agent: a stable session id (`pchat-<len>-<sha256 of the resolved path>`) is derived from the context file's path and passed to pi with\n`--session-id`, so reopening the same file\nresumes that file's conversation. If you never chatted about that specific file\nbut did chat from its folder, `:PiOpen` falls back to the folder's conversation\n(a file you just opened inherits its folder's history); otherwise a new\nconversation is started. On resume the prior transcript is shown at the top of\nthe chat buffer, above the input line, so you have the context as you keep\nworking. It is laid out like the live chat: your prompts appear as you typed\nthem (without the context line the plugin adds for pi), and only the\nsession's current branch is shown (pi keeps abandoned branches in the same\nfile).\n\nThis is on by default. `g:pi_chat_no_session` still wins and forces a\ndisposable conversation. `g:pi_chat_session_resume = 0` passes no session id\nat all, and pi then applies its own default (a new session each time).\nThe config below turns it off or tunes the fallback and how much history is\nshown.\n\nIf you want the panel already open when vim starts, hook `:PiOpen` into\n`VimEnter`. The safe form is a small boot function:\n\n```\n\" Open the chat panel at startup, without stealing focus.\nfunction! s:pi_boot() abort\n  if exists(':PiOpen') && !&diff && (has('terminal') || has('gui_running'))\n    let s:pi_boot_win = win_getid()\n    PiOpen quiet\n    call win_gotoid(s:pi_boot_win)\n    unlet s:pi_boot_win\n  endif\nendfunction\nautocmd VimEnter * call s:pi_boot()\n```\n\nThere are two reasons for the exact shape:\n\n1. \n**Focus.** A plain`:PiOpen` leaves the cursor in the chat's insert-mode\nprompt, so your first keystroke after startup would be typed into the\nprompt (and`<CR>` there sends it to pi).`:PiOpen quiet` starts the job\nand opens the split but skips the insert-mode focus, and`win_gotoid()` returns the cursor to the window you were in. Both parts matter: the chat\nwindow still gains focus the moment the split opens (the window manager\ndoes that), and pi's first streamed output re-focuses it — so`win_gotoid` alone is not enough; it is the quiet flag that keeps your\nkeystrokes out of the prompt.\n2. \n**Keep `:PiOpen` alone on its line.** The command is`-nargs=*` , and at\nleast one stripped-down vim build parses the rest of a`|` -chained logical\nline as part of its argument:`... | PiOpen quiet | win_gotoid(w)` made`:PiOpen` receive the whole string`quiet | win_gotoid(w)` and send it to\npi as a user message — a stray boot send. Wrapping the call in a function\nso`PiOpen quiet` sits alone on its line sidesteps that completely.\n\n``` js\nlet g:pi_chat_split = 'vsplit'          \" 'vsplit' (default) | 'split' | 'new' (both horizontal)\nlet g:pi_chat_width = 60                \" split width/height (float 0-1 = fraction of the screen)\nlet g:pi_chat_args = []                 \" extra pi args, e.g. ['--model', '...']\nlet g:pi_chat_no_session = 0            \" 1 = pass --no-session\nlet g:pi_chat_streaming_behavior = 'followUp'  \" 'followUp' (default) or 'steer'\nlet g:pi_chat_show_thinking = 0         \" 1 = also render thinking deltas inline in the chat\nlet g:pi_chat_thinking_height = 0.3     \" :PiThinking panel height: float 0-1 of the screen height, or rows\nlet g:pi_chat_markdown = 1              \" 0 = disable the built-in markdown highlighting in the chat/thinking buffers\nlet g:pi_chat_map = '<leader>pi'        \" '' disables the global mapping\nlet g:pi_chat_context_file = 1          \" 1 = inject the context file into prompts\nlet g:pi_chat_track_files = 1           \" 1 = re-key pi's context when you switch files (:e, :b, …)\nlet g:pi_chat_quit_with_last_window = 1 \" 1 = :q/:x in the last file window also closes the pi panels (exits Vim)\nlet g:pi_chat_master_prompt = ''        \" file path (or inline text) of standing rules, appended to pi's system prompt\nlet g:pi_chat_autosave_context = 0      \" 1 = save the context file before each send (else prompt)\nlet g:pi_chat_tool_diff = 1             \" 1 = show a diff preview for pi's edit/write tool calls\nlet g:pi_chat_tool_diff_max = 200       \" cap diff previews at N lines (0 = unlimited)\nlet g:pi_chat_tool_output = 5           \" show N lines of tool output under ✓/✗ (0 = off)\nlet g:pi_chat_run_timeout = 300         \" 0 = off; N = warn if a run is still busy after N seconds\nlet g:pi_chat_session_resume = 1        \" 1 = :PiOpen resumes the file's (or folder's) pi conversation\nlet g:pi_chat_session_fallback_dir = 1  \" 1 = fall back to the file's folder session when no file session exists\nlet g:pi_chat_session_dir = ''          \" '' = pi's default ~/.pi/agent/sessions; else an explicit store dir\nlet g:pi_chat_resume_max_messages = 50  \" 0 = show the whole prior transcript; N = only the last N messages\n```\n\n`g:pi_chat_streaming_behavior` only matters when you send a prompt while the\nagent is already running: `followUp` queues it until the run fully settles,\n`steer` injects it after the current tool calls finish.\n\nThe chat and thinking panels render markdown with the plugin's own\nbuffer-local Vim highlighting (`g:pi_chat_markdown`, on by default):\nheadings, bold/italic, inline and fenced code, lists, quotes and links, all\nlayered on as the buffer streams in. Set `g:pi_chat_markdown = 0` to turn\nit off.\n\n`g:pi_chat_master_prompt` sets standing rules for the whole conversation: a\nfile path (e.g. `\"~/.pi-master-prompt.md\"`) or a plain string of instructions\nis passed to pi with `--append-system-prompt`, so the rules are in the system\nprompt from the first message of every session (fresh and resumed). A\nreadable file is sent as an absolute path and pi loads its contents; anything\nelse is treated as inline text. The chat log shows which one was applied when\na fresh session starts. Changes take effect when the pi job restarts (e.g.\n`:PiClear`, or `:PiClose` + `:PiOpen`).\n\n`g:pi_chat_autosave_context` controls what happens when you send a prompt with\nunsaved changes in the context file: `1` saves it to disk first (so pi edits\nthe file on disk); `0` (default) prompts you first — save the changes, or\nlet pi edit the on-disk version.\n\n`g:pi_chat_session_resume` (default `1`) makes `:PiOpen` resume the pi\nconversation for the context file, falling back to its folder's conversation\n(`g:pi_chat_session_fallback_dir`) when there is no file-level session, else\nstarting a new one. `g:pi_chat_resume_max_messages` caps how many prior messages\nare shown on resume (`0` = the whole transcript, handy for a long-running file).\n`g:pi_chat_no_session = 1` forces a disposable (unpersisted) conversation.\n\n- Multi-line prompts are opened with `<C-CR>` only (there is no`<C-o><CR>` -style alias); if your terminal remaps`<C-CR>` , multi-line\nprompt input won't work.\n- The transcript is plain text in a modifiable buffer. If the chat window is focused, scrolling up while output streams leaves you in place; if focus is elsewhere, the window auto-follows the newest line so live updates stay visible without stealing focus.\n- Tool progress (`tool_execution_update` ) and partial tool arguments are not\nrendered: a tool's output appears once it finishes.\n- Extension `editor` requests (multi-line text dialogs) are not supported\nyet: they are answered as cancelled right away, with a ⚠ notice in the chat.\n- Dialog `timeout` s are not enforced on the Vim side (Vim runs no timers while\na prompt is waiting); pi resolves a timed-out dialog itself.\n\nIf the agent process dies (crash, an extension exiting, `:PiClose`), the\npanel recovers: sending to a dead agent logs `⚠ agent process is not running (use :PiOpen)` and clears the working state instead of wedging the\ninput, and `:PiOpen` restarts the agent (resuming the session for the same\nfile). `:PiClear` always forces a brand-new session.\n\nRun the whole end-to-end suite (the basic E2E plus the scenarios in\n`test/scenarios/`: `abort`, `abortreplay`, `abortresume`, `abortdeath`,\n`blankgap`, `clear`, `cleardir`, `clearwipe`, `close`, `closethink`, `crash`,\n`cursorprompt`, `dialogs`, `fail`, `leak`, `markdown`, `modelstatus`, `multi`,\n`nldelta`, `nosession`, `notify`, `notifycursor`, `panelguard`,\n`paralleltools`, `park`, `pifile`, `pifilecmd`, `pisend`, `queue`, `quitkeep`, `quitoff`, `quitx`,\n`reject`,\n`reload`, `restart`, `resume`, `resumefmt`, `resumethink`, `sessionid`,\n`stash`, `think`, `thinkoff`, `toolout`, `tooloutfail`,\n`thinkpanel`, `tools`, `trackfile`, `working`, `diffedit`, `diffoff`,\n`diffwrite`, `diskchg`) from the repo root.\nThe `abortreplay` scenario replays a captured real-pi session byte-for-byte\n(`test/replay/`). The `stall` watchdog scenario is\nmanual-only (a slow fake triggers a headless hit-enter barrier):\n\n```\nsh test/run.sh            # all scenarios\nsh test/run.sh think      # just one\n```\n\nEach scenario drives the real plugin through its public commands\n(`:PiOpen`/`:PiFile`/`:PiClear`/`<CR>`-send) against `test/fake-pi.js` — a\ndeterministic stub `pi` RPC peer reached through the `test/pi` PATH shim — and\ngreps the transcript dump for the expected lines, asserting zero Vim E-errors.\nManual test with the real agent: `vim +PiOpen` (with `pi` on PATH).\n\nNotes:\n\n- `test/run.sh` exports`PATH=test:$PATH` so the plugin's`job_start(['pi', ...])` resolves the`test/pi` shim, which in turn finds`node` (it also falls back to`$HOME/.nvm/versions/node/*/bin` ,`/opt/local/bin` , and`/usr/local/bin` ).\n- Each scenario is launched as `sleep N | vim --not-a-term -Nu ...` : the\nforeground`sleep` keeps vim's stdin open for N seconds (a closed stdin makes\nheadless vim exit before its timers fire), and the pipeline always terminates\nat N seconds, so a run can never hang.`--not-a-term` is required on this vim\nbuild (it otherwise warns and can hang on a hit-enter prompt in non-TTY\nmode).\n- `test/fake-pi.js` is env-configurable (`FAKE_PI_THINKING` ,`FAKE_PI_TOOL` ,`FAKE_PI_TOOLFAIL` ,`FAKE_PI_TITLE` ,`FAKE_PI_DELAY_MS` ,`FAKE_PI_TURN_MS` ,`FAKE_PI_EDIT_PATH` ,`FAKE_PI_DELTAS` ,`FAKE_PI_REJECT` ,`FAKE_PI_CRASH_MS` ,`FAKE_PI_QUEUE` ,`FAKE_PI_LIFE_LOG` ,`FAKE_PI_PARALLEL` ,`FAKE_PI_TOOL_OUTPUT` ,\n…; see its header) so one stub covers\nevery scenario; it also logs the\nlines it receives (`FAKE_PI_LOG` ) and its launch argv (`FAKE_PI_ARGV_LOG` )\nfor protocol assertions (e.g.`nosession` asserts the`--no-session` startup arg,`clear` asserts the`--session-id` kept across the restart).\n- `g:pi_chat_run_timeout` watchdog: when a run is still busy past the budget the\nstatus line appends`(long run: :PiClose to stop)` and one transcript line`⏱ pi run exceeded Ns - may be stuck; :PiClose to force-stop` is logged.\n\nReleases are automated with [semantic-release](https://github.com/semantic-release/semantic-release):\na `release` job (`.github/workflows/test.yml`, config in `release.config.json`)\nruns after the E2E suite passes on pushes to `main`, bumps the version from the\nconventional commit messages, tags it (`vX.Y.Z`) and publishes a GitHub Release\nwith generated notes. To trigger a release, write commits as `fix: …` (patch),\n`feat: …` (minor) or with a `BREAKING CHANGE` footer / `!` suffix (major);\nplain messages do not produce a release.\nVerify by hand with a slow reply (or a hung pi).\n\nExpected results (see `test/vimrc-test` header for the full spec):\n\n- `PiChatStatusText()` samples:`pi chat` →`⠋ contacting pi 0s` (frames\nadvance while the fake is slow to respond) →`⠋ pi is working …` (after`agent_start` ) →`pi chat` (after`agent_settled` ).\n- `__PiChat__` buffer: header,`❯ hello fake` , the streamed`Echo: hello fake` reply, tool lines`⚙ bash …` /`✓ bash` , and the notify line.\n- No `E*` errors in`/tmp/pilog.txt` .\n\nLint gates:\n\n```\n.venv/bin/vint plugin/pi_chat.vim   # pip install 'vim-vint==0.3.21' 'setuptools<81'\nnode --check test/fake-pi.js\n```\n\nUseful protocol reference: pi repo `docs/rpc.md` (commands, events,\nextension UI protocol, example Python/Node clients).", "url": "https://wpnews.pro/news/vim-pi-chat-make-vim-a-pi-powered-editor", "canonical_source": "https://github.com/galeone/vim-pi-chat", "published_at": "2026-10-03 03:52:03+00:00", "updated_at": "2026-10-03 04:06:05.443446+00:00", "lang": "en", "topics": ["ai-agents", "ai-tools", "developer-tools", "ai-products"], "entities": ["vim-pi-chat", "Vim 9", "pi coding agent", "galeone", "Neovim", "GitHub"], "also_reported_by": [], "alternates": {"html": "https://wpnews.pro/news/vim-pi-chat-make-vim-a-pi-powered-editor", "markdown": "https://wpnews.pro/news/vim-pi-chat-make-vim-a-pi-powered-editor.md", "text": "https://wpnews.pro/news/vim-pi-chat-make-vim-a-pi-powered-editor.txt", "jsonld": "https://wpnews.pro/news/vim-pi-chat-make-vim-a-pi-powered-editor.jsonld"}}