Posts

  • copilot.el 0.7

    Good news, everyone – copilot.el 0.7 is out! And it’s a big one.

    Ever since I took over the maintenance of copilot.el in the spring of 2025, I’ve had one north star: close the gap between Emacs and the editors that enjoy first-party Copilot support. VS Code and (Neo)vim got the rich, official experience – chat, agents, the whole shebang – while we Emacs folks were stuck with humble ghost-text completion. That never sat right with me. Emacs deserves better.

    For a bit of perspective on the pace: in its first three years the project never shipped a single tagged release. 0.7 is the seventh I’ve cut since taking over. We’ve been busy.

    For a long time that gap felt unbridgeable. We were essentially reverse-engineering an undocumented protocol and replicating it in Elisp, always one step behind. But then Microsoft open-sourced @github/copilot-language-server, and everything changed. Suddenly we had a documented, first-party LSP server to talk to – the very same backend that powers Copilot in VS Code. The migration to it in 0.4 was the turning point. From that moment on, catching up to the other editors stopped being a fantasy and became a (long) TODO list.

    We’ve been chipping away at that list ever since. A few months ago, in 0.5, we added basic support for Copilot Chat – the first time the package did anything beyond completion. 0.6 brought Next Edit Suggestions (NES) and the experimental beginnings of agent mode. And now 0.7 fills in pretty much all of the remaining blanks.

    So what’s actually in it? Quite a lot. Let me walk you through the highlights.

    Agent Mode, For Real

    Chat is nice, but talking to an assistant that can’t do anything gets old fast. Enter agent mode:

    (setopt copilot-chat-use-agent-mode t)
    

    With this on, Copilot can run tools to actually get work done – run shell commands, create and edit files, read your diagnostics, fetch web pages. Ask it to “add a test for foo and run it” and it’ll write the test, invoke your test runner, read the output, and iterate, all without you leaving the chat buffer.

    Of course, letting an AI run commands and rewrite your files unsupervised is a recipe for excitement of the wrong kind, so every tool call asks for confirmation first. File edits show you a diff preview before you approve them, and if you trust a particular tool you can pick always to stop being asked for the rest of the conversation. Long-running terminal commands now run asynchronously too, so Emacs stays responsive while that test suite chugs along – and C-g aborts a command that’s overstaying its welcome.

    Workspace Awareness

    An agent that can only see the current buffer isn’t much of an agent. So 0.7 teaches copilot.el to answer the server’s workspace requests: Copilot can now search your project by file glob or by text/regex (via ripgrep, so your .gitignore is respected), read files, and list directories across the whole tree. That’s what makes “where is X handled in this codebase?” actually work.

    For the truly ambitious there’s also opt-in semantic search:

    (setopt copilot-chat-enable-semantic-search t)
    

    Flip that on and the server builds an embeddings index of your workspace, so you can ask whole-codebase questions instead of pointing at specific files. It’s off by default, since indexing isn’t free, but it’s there when you want it.

    MCP Support

    Model Context Protocol is how you give an assistant extra superpowers, and copilot.el now speaks it. Point copilot-mcp-servers at the servers you want and their tools show up in chat, right alongside the built-in ones:

    (setopt copilot-mcp-servers
            '(:fetch (:command "uvx" :args ["mcp-server-fetch"])
              :memory (:command "npx"
                       :args ["-y" "@modelcontextprotocol/server-memory"])))
    

    M-x copilot-chat-list-mcp-tools shows you which servers connected and what they offer (and complains loudly when one fails to start, which beats the old silent treatment).

    A Chat You Can Actually Use

    The chat buffer grew up too. A few of the quality-of-life additions:

    • Slash commands (C-c /) – /explain, /fix, /tests, /doc, and friends, fetched live from the server.
    • Acting on code blocksC-c C-i inserts the code block at point into your source buffer, C-c M-w copies it to the kill ring. No more copy-paste gymnastics out of a read-only buffer.
    • Attaching contextcopilot-chat-add-file-reference (C-c C-f) and copilot-chat-add-region-reference let you hand Copilot specific files or selections along with your question.

    Odds and Ends

    A grab bag of smaller, but handy, additions:

    • M-x copilot-quota shows how much of your chat, completion, and premium-request allowance is left – useful now that the fancy models eat into a quota.
    • Copilot will tell you when a suggestion closely matches public code and collect those matches, with their licenses and reference URLs, in a buffer you can open via M-x copilot-list-code-citations.
    • A pile of bug fixes, including agent-mode tool confirmations that were flat-out broken against newer servers, and chat finally picking a sensible default model on its own.

    As always, see the full changelog for the complete rundown.

    Epilogue

    Agent mode, MCP, and semantic search are all opt-in, so nothing here changes your setup unless you ask for it. But I’d love for you to ask for it! Turn on agent mode, point it at a real task, and tell me where it shines and where it falls on its face. This kind of feature only gets good with people kicking the tires and filing detailed reports, so please do open issues and let me know how it goes. Your feedback is what shapes the next release.

    I’d be remiss not to acknowledge it: there’s a decent chance that by now half of you have wandered off to Claude Code or some other agentic CLI, and that pouring this much time and energy into Copilot support for Emacs is, objectively, a questionable use of a human life. Fair enough.

    But here’s the thing – Emacs has always excelled at providing amazing support for niche technologies long after the rest of the world has moved on. We’re the people who’ll lovingly maintain a mode for a language nobody has written any code in since 2003. Keeping Copilot first-class in Emacs is squarely in that grand tradition, and I, for one, intend to see it through. :-)

    That’s all I have for you today. Keep hacking!

  • MrAnderson 0.6

    MrAnderson 0.6 is out. If you’ve never heard of it, that’s perfectly fine - it’s the kind of tool that’s supposed to do its job quietly and stay out of sight. cider-nrepl has been using MrAnderson to inline its dependencies (bundle private copies of them that can’t clash with yours) for about as long as cider-nrepl has existed, and most of the time you’d never know it was there.

    The trouble is the “most of the time”. MrAnderson has always had a few rough edges, and every so often one of them would draw blood during a cider-nrepl release and I’d find myself wondering whether we should just rip the whole thing out. I wasn’t even joking about it - at one point I went and opened a PR to remove MrAnderson from cider-nrepl entirely. That’s roughly how fond of it I was feeling.

    Read More
  • CIDER 1.22 ("São Miguel")

    Great news, everyone - CIDER 1.22 (“São Miguel”) is finally out!

    And “finally” is the operative word here. This release took me way longer than I wanted it to, but that’s because I decided to stop kicking a few cans down the road and finally tackle some long-standing problems that had been bugging me for years:

    • Session and connection management - the logic for figuring out which REPL a buffer is associated with had grown into something I could barely follow myself.
    • The decoupling of nREPL from CIDER’s UI layer - a piece of technical debt so old it predates most of you reading this (the tracking issue, #1099, is from 2017).
    • A full audit of the codebase and the documentation, hunting for inconsistencies, dead code, broken menu entries, and gaps in the docs.

    None of this is the kind of work that makes for a flashy release announcement, but it’s exactly the kind of work that keeps a 14 year old project healthy. I genuinely think this is one of the most important CIDER releases in recent memory, even though most of the changes aren’t really user-visible.

    Highlights

    Picking a handful of items out of a very long changelog, here’s what I’d call the highlights:

    • A huge editor responsiveness fix (#3933) - Clojure buffers no longer lag when there’s no REPL connected. The friendly-session matching used to scan the project classpath on every redisplay; now it does something far cheaper. If you’ve ever felt CIDER make your editing sluggish, this one’s for you.
    • Default sessions (#3865) - cider-set-default-session lets you bypass sesman’s project-based dispatch and pin a REPL as the default. Handy for all those workflows that never quite fit the project model.
    • nREPL is now decoupled from CIDER’s UI (#3892) - the transport layer no longer reaches into CIDER-specific handlers and UI strings. This finally closes #1099 and opens the door for other tools to build on our nREPL client.
    • Faster connection completions (#3888) - repeated cider-connect completions no longer re-spawn a round of ps/lsof subprocesses every single time.
    • A massive discoverability pass on menus and keybindings - menus across the inspector, REPL history, log, spec browser, and more now actually expose the commands they were missing (often a dozen+ per mode), and C-h m now lists the active bindings for several modes. A lot of functionality that was technically there but practically invisible is now front and center.
    • cider-repl-history-doctor (#3921) - a new command that walks your REPL history, finds entries with unbalanced parens, and helps you clean them up. Born out of a real bug report about history rendering breaking.
    • Support for let-go (#3926) - a Clojure dialect implemented in Go is now recognized as a known nREPL runtime.
    • Better remote (TRAMP) jack-in and connect (#3885, #3886, #3887) - endpoint detection, command resolution, and SSH tunneling all behave correctly against remote hosts now.
    • Plenty of nREPL robustness fixes - plugged several request-id leaks, bounded the completed-requests table, and made a misbehaving response handler no longer able to drop later responses on the floor.
    • Bumped the injected nREPL to 1.7.0 and cider-nrepl to 0.59.0.

    The full list is much longer - check out the changelog if you want the gory details.

    A fun little detour: Port and Neat

    While I was doing the nREPL decoupling work, I got curious and started experimenting with adding support for prepl (Clojure’s built-in socket REPL) as an alternative to nREPL. I even put together a prototype (cider#3899). It sort of worked, but it also reaffirmed my belief that prepl and nREPL are different enough that bolting prepl onto CIDER would mean papering over its limitations in dozens of subtle places. So instead of forcing it, that little experiment grew into two brand new projects of mine:

    • Port - a minimalist prepl client for Emacs.
    • Neat - a tiny, deliberately language-agnostic nREPL client.

    Both have write-ups of their own, so I won’t repeat the details here. I’m pretty excited about where they might lead - a great example of how digging into old technical debt can spark entirely new ideas.

    Epilogue

    This release is dedicated to São Miguel, the stunningly beautiful main island of the Azores archipelago.1 I got a lot of my recent inspiration for CIDER there, and naming the release after it felt right.

    As always, none of this happens in a vacuum. A huge thank you to Alex Yakushev for his continued work on the inspector - it keeps getting better and better. And of course a massive shoutout to Clojurists Together and to all the other contributors and backers of my open-source work. You’re the reason CIDER and friends keep moving forward.

    That’s all I have for you today. I hope you’ll enjoy using CIDER 1.22 as much as I enjoyed (eventually) shipping it. Keep hacking!

    1. If you ever get the chance to visit, do it. Crater lakes, hot springs, and the greenest hills you’ve ever seen. 

  • What's Next for clojure-mode?

    Good news, everyone! clojure-mode 5.22 is out with many small improvements and bug fixes!

    Read More
  • copilot.el 0.4

    Good news, everyone – copilot.el 0.4 is out!

    But that’s just the start of it. This is the most important release for me since I assumed the project’s leadership and I hope this article will manage to make you agree with my reasoning.

    Enough empty words – let me now walk you through the highlights.

    A Proper Copilot Client

    The single biggest change in this release is the migration from the legacy getCompletions API (reverse-engineered from copilot.vim) to the standard textDocument/inlineCompletion LSP method provided by the official @github/copilot-language-server.

    This might sound like a dry and boring internal change, but it’s actually a big deal. copilot.el started its life as a port of copilot.vim – we were essentially reverse-engineering how that plugin talked to the Copilot server and replicating it in Elisp. That worked, but it was fragile and meant we were always playing catch-up with undocumented protocol changes.

    Now we speak the official LSP protocol. We send proper textDocument/didOpen, textDocument/didChange, and textDocument/didFocus notifications. We manage workspace folders. We handle server-to-client requests like window/showMessageRequest and window/showDocument. We perform a clean shutdown/exit sequence instead of just killing the process. In short, copilot.el is now a proper Copilot LSP client, not a reverse-engineered hack.

    This release, in a way, completes the cycle – from a package born out of reverse engineering copilot.vim to a legitimate Copilot client built on the official API.1

    But wait, there’s (a lot) more!

    AI Model Selection

    You can now choose which AI model powers your completions via M-x copilot-select-completion-model. The command queries the server for available models on your subscription and lets you pick one interactively. The selection is persisted in copilot-completion-model.

    Parentheses Balancer 2.0

    The parentheses balancer – the component that post-processes completions in Lisp modes to fix unbalanced delimiters – got a complete rewrite. The old implementation counted parentheses as raw characters, which meant it would “balance” parens inside comments and strings where they shouldn’t be touched. The new implementation uses parse-partial-sexp to understand the actual syntactic structure, so it only fixes genuinely unbalanced delimiters.

    Whether the balancer will remain necessary in the long run is an open question – as Copilot’s models get smarter, they produce fewer unbalanced completions. But for now it still catches enough edge cases to earn its keep. You can disable it with (setopt copilot-enable-parentheses-balancer nil) if you want to see how well the raw completions work for you.

    Improved Server Communication

    Beyond the core API migration, we’ve improved the server communication in several ways:

    • Status reporting: didChangeStatus notifications show Copilot’s state (Normal, Warning, Error, Inactive) in the mode-line.
    • Progress tracking: $/progress notifications display progress for long-running operations like indexing.
    • Request cancellation: stale completion requests are cancelled with $/cancelRequest so the server doesn’t waste cycles on abandoned work.
    • User-defined handlers: copilot-on-request and copilot-on-notification let you hook into any server message.
    • UTF-16 positions: position offsets now correctly use UTF-16 code units, so emoji and other supplementary-plane characters no longer confuse the server.

    Tests and Documentation

    This release adds a proper test suite using buttercup. We went from zero tests to over 120, covering everything from URI generation and position calculation to the balancer, overlay management, and server lifecycle. CI now runs across multiple Emacs versions (27.2 through snapshot) and on macOS and Windows in addition to Linux.

    The README got a (almost) complete rewrite – it now covers installation for every popular package manager, documents all commands and customization options, includes a protocol coverage table, and has a new FAQ section addressing the most common issues people run into. Plenty of good stuff in it!

    This might sound like a lot of effort for not much user-visible payoff, but when I started hacking on the project:

    • I really struggled to understand how to make the best use of the package
    • The lack of tests made it hard to make significant changes, as every change felt quite risky

    Anyways, I hope you’ll enjoy the improved documentation and you’ll have easier time setting up copilot.el.

    Bug Fixes

    Too many to list individually, but here are some highlights:

    • copilot-complete now works without copilot-mode enabled (#450)
    • Partial accept-by-word no longer loses trailing text when the server uses a replacement range (#448)
    • JSON-RPC requests send an empty object instead of omitting params, fixing authentication on newer server versions (#445)
    • The company-mode dependency is gone – no more void-function company--active-p errors (#243)
    • The completion overlay plays nice with Emacs 30’s completion-preview-mode (#377)

    See the full changelog for the complete list.

    What’s Next

    There’s still plenty of work ahead. We have three big feature branches in the pipeline, all open as PRs and ready for adventurous testers:

    If any of these sound interesting to you, please give them a spin and report back. Your feedback is what shapes the next release.

    Thanks

    A big thanks to Paul Nelson for contributing several partial acceptance commands and the overlay clearing improvements – those are some of the most user-visible quality-of-life changes in this release. Thanks also to everyone who filed detailed bug reports and tested fixes – you know who you are, and this release wouldn’t be the same without you.

    That’s all I have for you today. Keep hacking!

    1. That’s why I dropped the word “unofficial” from the package’s description. 

Subscribe via RSS | View Older Posts