<?xml version="1.0" encoding="utf-8" standalone="yes"?><rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom"><channel><title>2026 on Emacs@ Dyerdwelling</title><image><url/><title>2026 on Emacs@ Dyerdwelling</title><link>https://www.emacs.dyerdwelling.family/tags/2026/</link><width>32</width><height>32</height></image><link>https://www.emacs.dyerdwelling.family/tags/2026/</link><description>Recent content in 2026 on Emacs@ Dyerdwelling</description><generator>Hugo -- gohugo.io</generator><language>en</language><managingEditor>captainflasmr@gmail.com (James Dyer)</managingEditor><webMaster>captainflasmr@gmail.com (James Dyer)</webMaster><lastBuildDate>Wed, 04 Mar 2026 09:34:00 +0000</lastBuildDate><atom:link href="https://www.emacs.dyerdwelling.family/tags/2026/index.xml" rel="self" type="application/rss+xml"/><item><title>Ollama Buddy - Web Search Integration</title><link>https://www.emacs.dyerdwelling.family/emacs/20260212142000-emacs--web-search-integration-in-ollama-buddy/</link><pubDate>Wed, 04 Mar 2026 09:34:00 +0000</pubDate><author>James Dyer</author><guid>https://www.emacs.dyerdwelling.family/emacs/20260212142000-emacs--web-search-integration-in-ollama-buddy/</guid><description>&lt;p>One of the fundamental limitations of local LLMs is their knowledge cutoff - they don&amp;rsquo;t know about anything that happened after their training data ended. The new web search integration in &lt;a href="https://github.com/captainflasmr/ollama-buddy">ollama-buddy&lt;/a> solves this by fetching current information from the web and injecting it into your conversation context. Ollama has a specific API web search section, so it has now been activated!&lt;/p>
&lt;p>Here is a demonstration:&lt;/p>
&lt;p>&lt;a href="https://www.youtube.com/watch?v=05VzAajH404">https://www.youtube.com/watch?v=05VzAajH404&lt;/a>&lt;/p>
&lt;figure>&lt;img src="https://www.emacs.dyerdwelling.family/emacs/20250424085731-emacs--Ollama-Buddy-0-9-35-Grok-Gemini-Integration-Enhanced-Sessions.jpg" width="100%">
&lt;/figure>
&lt;p>The web search feature implements a multi-stage pipeline that transforms search queries into clean, LLM-friendly context, your search query is sent to Ollama&amp;rsquo;s Web Search API, the API returns structured search results with URLs and snippets.&lt;/p>
&lt;p>I have decided that each URL by default is fetched and processed through Emacs&amp;rsquo; built-in &lt;code>eww&lt;/code> and &lt;code>shr&lt;/code> HTML rendering, but this can of course be configured, set &lt;code>ollama-buddy-web-search-content-source&lt;/code> to control how content is retrieved:&lt;/p>
&lt;ul>
&lt;li>`eww&amp;rsquo; (default): Fetch each URL and render through eww/shr for clean text&lt;/li>
&lt;li>`api&amp;rsquo;: Use content returned directly from Ollama API (faster, less refined)&lt;/li>
&lt;/ul>
&lt;p>The &lt;code>shr&lt;/code> (Simple HTML Renderer) library does an excellent job of converting HTML to readable plain text, stripping ads, navigation, and other noise, so I thought why not just use this rather than the return results from the ollama API, as they didn&amp;rsquo;t seem to be particularly accurate.&lt;/p>
&lt;p>The cleaned text is formatted with org headings showing the source URL and attached to your conversation context, so when you send your next prompt, the search results are automatically included in the context. The LLM can now reason about current information as if it had this knowledge all along.&lt;/p>
&lt;p>There are multiple ways to search; firstly, is inline &lt;code>@search()&lt;/code> syntax in your prompts (gradually expanding the inline prompting language!), so for example:&lt;/p>
&lt;pre tabindex="0">&lt;code class="language-nil" data-lang="nil">What are the key improvements in @search(Emacs 31 new features)?
Compare @search(Rust async programming) with @search(Go concurrency model)
&lt;/code>&lt;/pre>&lt;p>ollama-buddy automatically detects these markers, executes the searches, attaches the results, and then sends your prompt, so you can carry out multiple searches.&lt;/p>
&lt;p>You can also manual Search and Attach, Use &lt;code>C-c / a&lt;/code> (or &lt;code>M-x ollama-buddy-web-search-attach&lt;/code>)&lt;/p>
&lt;p>The search executes, results are attached to your session, and the &lt;code>♁1&lt;/code> indicator appears in the header line and the results can be viewed from the attachments menu, so for example would display something like:&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">&lt;code class="language-text" data-lang="text">&lt;span style="display:flex;">&lt;span>* Web Searches (1)
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>** latest Emacs 31 features
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>*** 1. Hide Minor Modes in the Modeline in Emacs 31
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>*** 2. New Window Commands For Emacs 31
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>*** 3. Latest version of Emacs (GNU Emacs FAQ)
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>*** 4. bug#74145: 31.0.50; Default lexical-binding to t
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>*** 5. New in Emacs 30 (GNU Emacs FAQ)
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>with each header foldable, containing the actual search results.&lt;/p>
&lt;p>There is a little configuration required to go through the ollama API, first, get an API key from &lt;a href="https://ollama.com/settings/keys">https://ollama.com/settings/keys&lt;/a> (it&amp;rsquo;s free). Then configure:&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">&lt;code class="language-elisp" data-lang="elisp">&lt;span style="display:flex;">&lt;span>(use-package ollama-buddy
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> :bind
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> (&lt;span style="color:#e6db74">&amp;#34;C-c o&amp;#34;&lt;/span> &lt;span style="color:#f92672">.&lt;/span> ollama-buddy-role-transient-menu)
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> (&lt;span style="color:#e6db74">&amp;#34;C-c O&amp;#34;&lt;/span> &lt;span style="color:#f92672">.&lt;/span> ollama-buddy-transient-menu-wrapper)
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> :custom
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#75715e">;; Required: Your Ollama web search API key&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> (ollama-buddy-web-search-api-key &lt;span style="color:#e6db74">&amp;#34;your-api-key-here&amp;#34;&lt;/span>))
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>For clarification, the content source options are as follows:&lt;/p>
&lt;p>The &lt;code>ollama-buddy-web-search-content-source&lt;/code> variable controls how content is retrieved:&lt;/p>
&lt;p>&lt;strong>&lt;code>eww&lt;/code> (default, recommended)&lt;/strong>&lt;/p>
&lt;p>Fetches each URL and renders HTML through Emacs&amp;rsquo; eww/shr. Produces cleaner, more complete content but requires additional HTTP requests.&lt;/p>
&lt;p>Pros:&lt;/p>
&lt;ul>
&lt;li>Much cleaner text extraction&lt;/li>
&lt;li>Full page content, not just snippets&lt;/li>
&lt;li>Removes ads, navigation, clutter&lt;/li>
&lt;li>Works with any website&lt;/li>
&lt;/ul>
&lt;p>Cons:&lt;/p>
&lt;ul>
&lt;li>Slightly slower (additional HTTP requests)&lt;/li>
&lt;li>Requires network access for each URL&lt;/li>
&lt;/ul>
&lt;p>&lt;strong>&lt;code>api&lt;/code> (experimental)&lt;/strong>&lt;/p>
&lt;p>Uses content returned directly from the Ollama API without fetching individual URLs. Faster but content quality depends on what the API provides.&lt;/p>
&lt;p>Pros:&lt;/p>
&lt;ul>
&lt;li>Faster (single API call)&lt;/li>
&lt;li>Less network traffic&lt;/li>
&lt;/ul>
&lt;p>Cons:&lt;/p>
&lt;ul>
&lt;li>Content may be truncated&lt;/li>
&lt;li>Quality varies by source&lt;/li>
&lt;li>May miss important context&lt;/li>
&lt;/ul>
&lt;p>I strongly recommend sticking with &lt;code>eww&lt;/code> - the quality difference is substantial.&lt;/p>
&lt;p>By default, web search fetches up to 5 URLs with 2000 characters per result. This provides rich context without overwhelming the LLM&amp;rsquo;s context window.&lt;/p>
&lt;p>For longer research sessions, you can adjust:&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">&lt;code class="language-elisp" data-lang="elisp">&lt;span style="display:flex;">&lt;span>(setq ollama-buddy-web-search-max-results &lt;span style="color:#ae81ff">10&lt;/span>) &lt;span style="color:#75715e">;; More sources&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>(setq ollama-buddy-web-search-snippet-length &lt;span style="color:#ae81ff">5000&lt;/span>) &lt;span style="color:#75715e">;; Longer excerpts&lt;/span>
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>Be mindful of your LLM&amp;rsquo;s context window limits. With 5 results at 2000 chars each, you&amp;rsquo;re adding ~10K characters to your context.&lt;/p>
&lt;p>The web search integration fundamentally expands what your local LLMs can do. They&amp;rsquo;re no longer limited to their training data - they can reach out, fetch current information, and reason about it just like they would with any other context, so hopefully this will now make &lt;code>ollama-buddy&lt;/code> a little more useful&lt;/p></description></item><item><title>Ollama Buddy v2.5 - RAG (Retrieval-Augmented Generation) Support</title><link>https://www.emacs.dyerdwelling.family/emacs/20260224104044-emacs--ollama-buddy-v2/</link><pubDate>Tue, 24 Feb 2026 11:50:00 +0000</pubDate><author>James Dyer</author><guid>https://www.emacs.dyerdwelling.family/emacs/20260224104044-emacs--ollama-buddy-v2/</guid><description>&lt;p>One of the things that has always slightly bothered me about chatting with a local LLM is that it only knows what it was trained on (although I suppose most LLMs are like that) . Ask it about your own codebase, your org notes, your project docs - and it&amp;rsquo;s just guessing. Well, not anymore! Ollama Buddy now ships with proper Retrieval Augmented Generation support built-in&lt;/p>
&lt;figure>&lt;img src="https://www.emacs.dyerdwelling.family/emacs/20250424085731-emacs--Ollama-Buddy-0-9-35-Grok-Gemini-Integration-Enhanced-Sessions.jpg" width="100%">
&lt;/figure>
&lt;h2 id="what-even-is-rag">What even is RAG?&lt;/h2>
&lt;p>If you haven&amp;rsquo;t come across the term before, the basic idea is simple. Instead of asking the LLM a question cold, you first go off and find the most relevant bits of text from your own documents, then you hand those bits to the LLM along with your question. The LLM now has actual context to work with rather than just vibes. The &amp;ldquo;retrieval&amp;rdquo; part is done using vector embeddings - each chunk of your documents gets turned into a mathematical representation, and at query time your question gets the same treatment. Chunks that are mathematically &amp;ldquo;close&amp;rdquo; to your question are the ones that get retrieved.&lt;/p>
&lt;p>In this case, I have worked to keep the whole pipeline inside Emacs; it talks to Ollama directly to contact an embedding model, which then returns the required information. I have tried to make this as Emacs Org-friendly as possible by storing the embedding information in Org files.&lt;/p>
&lt;h2 id="getting-started">Getting started&lt;/h2>
&lt;p>You&amp;rsquo;ll need an embedding model pulled alongside your chat model. The default is &lt;code>nomic-embed-text&lt;/code> which is a solid general-purpose choice:&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">&lt;code class="language-sh" data-lang="sh">&lt;span style="display:flex;">&lt;span>ollama pull nomic-embed-text
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>or just do it within ollama-buddy from the Model Management page.&lt;/p>
&lt;h2 id="indexing-your-documents">Indexing your documents&lt;/h2>
&lt;p>The main entry point is &lt;code>M-x ollama-buddy-rag-index-directory&lt;/code>. Point it at a directory and it will crawl through, chunk everything up, generate embeddings for each chunk, and save an index file. The first time you run this it can take a while depending on how much content you have and how fast your machine is - subsequent updates are much quicker as it only processes changed files.&lt;/p>
&lt;p>Supported file types (and I even managed to get pdf text extraction working!):&lt;/p>
&lt;ul>
&lt;li>Emacs Lisp (&lt;code>.el&lt;/code>)&lt;/li>
&lt;li>Python, JavaScript, TypeScript, Go, Rust, C/C++, Java, Ruby - basically most languages&lt;/li>
&lt;li>Org-mode and Markdown&lt;/li>
&lt;li>Plain text&lt;/li>
&lt;li>PDF files (if you have &lt;code>pdftotext&lt;/code> from poppler-utils installed)&lt;/li>
&lt;li>YAML, TOML, JSON, HTML, CSS&lt;/li>
&lt;/ul>
&lt;p>Files over 1MB are skipped (configurable), and the usual suspects like &lt;code>.git&lt;/code>, &lt;code>node_modules&lt;/code>, &lt;code>__pycache__&lt;/code> are excluded automatically.&lt;/p>
&lt;p>The index gets saved into &lt;code>~/.emacs.d/ollama-buddy/rag-indexes/&lt;/code> as a &lt;code>.rag&lt;/code> file named after the directory. You can see what you&amp;rsquo;ve got with &lt;code>M-x ollama-buddy-rag-list-indexes&lt;/code>.&lt;/p>
&lt;h2 id="the-chunking-strategy">The chunking strategy&lt;/h2>
&lt;p>One thing I&amp;rsquo;m quite happy with here is the chunking. Rather than just splitting on a fixed character count, documents are split into overlapping word-based chunks. The defaults are:&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">&lt;code class="language-elisp" data-lang="elisp">&lt;span style="display:flex;">&lt;span>(setq ollama-buddy-rag-chunk-size &lt;span style="color:#ae81ff">400&lt;/span>) &lt;span style="color:#75715e">; ~500 tokens per chunk&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>(setq ollama-buddy-rag-chunk-overlap &lt;span style="color:#ae81ff">50&lt;/span>) &lt;span style="color:#75715e">; 50-word overlap between chunks&lt;/span>
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>The overlap is important - it means a piece of information that sits right at a chunk boundary doesn&amp;rsquo;t get lost. Each chunk also tracks its source file and line numbers, so you can see exactly where a result came from.&lt;/p>
&lt;h2 id="searching-and-attaching-context">Searching and attaching context&lt;/h2>
&lt;p>Once you have an index, there are two main ways to use it:&lt;/p>
&lt;ul>
&lt;li>
&lt;p>&lt;code>M-x ollama-buddy-rag-search&lt;/code> - searches and displays the results in a dedicated buffer so you can read through them&lt;/p>
&lt;/li>
&lt;li>
&lt;p>&lt;code>M-x ollama-buddy-rag-attach&lt;/code> - searches and attaches the results directly to your chat context&lt;/p>
&lt;/li>
&lt;/ul>
&lt;p>The second one is the useful one for day-to-day work. After running it, your next chat message will automatically include the retrieved document chunks as context. The status line shows &lt;code>♁N&lt;/code> (where N is the number of attached searches) so you always know what context is in play. Clear everything with &lt;code>M-x ollama-buddy-clear-attachments&lt;/code> or &lt;code>C-c 0&lt;/code>.&lt;/p>
&lt;p>You can also trigger searches inline using the &lt;code>@rag()&lt;/code> syntax directly in your prompt and is something fun I have been working on to include an inline command language of sorts, but more about that in a future post.&lt;/p>
&lt;p>The similarity search uses cosine similarity with sensible defaults (hopefully!)&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">&lt;code class="language-elisp" data-lang="elisp">&lt;span style="display:flex;">&lt;span>(setq ollama-buddy-rag-top-k &lt;span style="color:#ae81ff">5&lt;/span>) &lt;span style="color:#75715e">; return top 5 matching chunks&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>(setq ollama-buddy-rag-similarity-threshold &lt;span style="color:#ae81ff">0.3&lt;/span>) &lt;span style="color:#75715e">; filter out low-relevance results&lt;/span>
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>Bump &lt;code>top-k&lt;/code> if you want more context, lower the threshold if you&amp;rsquo;re not getting enough results.&lt;/p>
&lt;h2 id="a-practical-example">A practical example&lt;/h2>
&lt;p>Say you&amp;rsquo;ve been working on a large Emacs package and you want the LLM to help you understand something specific. You&amp;rsquo;d do:&lt;/p>
&lt;ol>
&lt;li>&lt;code>M-x ollama-buddy-rag-index-directory&lt;/code> → point at your project directory&lt;/li>
&lt;li>Wait for indexing to complete (the chat header-line shows progress)&lt;/li>
&lt;li>&lt;code>M-x ollama-buddy-rag-attach&lt;/code> → type your search query, e.g. &amp;ldquo;streaming filter process&amp;rdquo;&lt;/li>
&lt;li>Ask your question in the chat buffer as normal&lt;/li>
&lt;/ol>
&lt;p>The LLM now has the relevant source chunks as context and can give you a much more grounded answer than it would cold.&lt;/p>
&lt;p>And the important aspect, especially regarding local models which don&amp;rsquo;t often have the huge context sizes often found in online LLMs is that it allows for very efficient context retrieval.&lt;/p>
&lt;h2 id="that-s-pretty-much-it">That&amp;rsquo;s pretty much it!&lt;/h2>
&lt;p>The whole thing is self-contained inside Emacs, no external packages or vector databases, you index once, search as needed, and the LLM gets actual information rather than hallucinating answers about your codebase or anything else that you would want to ingest and it will hopefully make working with local LLMs through ollama noticeably more useful and accurate.&lt;/p></description></item><item><title>Ollama Buddy v2.0 - LLMs can now call Emacs functions!</title><link>https://www.emacs.dyerdwelling.family/emacs/20260216084213-emacs--ollama-buddy-v2/</link><pubDate>Mon, 16 Feb 2026 08:56:00 +0000</pubDate><author>James Dyer</author><guid>https://www.emacs.dyerdwelling.family/emacs/20260216084213-emacs--ollama-buddy-v2/</guid><description>&lt;p>Tool calling has landed in ollama-buddy!, it&amp;rsquo;s originally not something I really thought I would end up doing, but as ollama has provided tool enabled models and an API for this feature then I felt obliged to add it. So now LLMs through ollama can now actually do things inside Emacs rather than just talk about them, my original &amp;ldquo;do things only in the chat buffer and copy and paste&amp;rdquo; might have gone right out the window in an effort to fully support the ollama API!&lt;/p>
&lt;figure>&lt;img src="https://www.emacs.dyerdwelling.family/emacs/20250424085731-emacs--Ollama-Buddy-0-9-35-Grok-Gemini-Integration-Enhanced-Sessions.jpg" width="100%">
&lt;/figure>
&lt;p>What is Tool Calling?&lt;/p>
&lt;p>The basic idea is simple: instead of the model only generating text, it can request to invoke functions. You ask &amp;ldquo;what files are in my project?&amp;rdquo;, and instead of guessing, the model calls list_directory, gets the real answer, and responds with actual information.&lt;/p>
&lt;p>This creates a conversational loop:&lt;/p>
&lt;ol>
&lt;li>You send a prompt&lt;/li>
&lt;li>The model decides it needs to call a tool&lt;/li>
&lt;li>ollama-buddy executes the tool and feeds the result back&lt;/li>
&lt;li>The model generates a response using the real data&lt;/li>
&lt;li>Steps 2-4 repeat if more tools are needed&lt;/li>
&lt;/ol>
&lt;p>All of this is transparent - you just see the final response in the chat buffer.&lt;/p>
&lt;p>The new ollama-buddy-tools.el module ships with 8 built-in tools:&lt;/p>
&lt;p>Safe tools (read-only, enabled by default):&lt;/p>
&lt;ul>
&lt;li>&lt;strong>read_file&lt;/strong> - read file contents&lt;/li>
&lt;li>&lt;strong>list_directory&lt;/strong> - list directory contents&lt;/li>
&lt;li>&lt;strong>get_buffer_content&lt;/strong> - read an Emacs buffer&lt;/li>
&lt;li>&lt;strong>list_buffers&lt;/strong> - list open buffers with optional regex filtering&lt;/li>
&lt;li>&lt;strong>search_buffer&lt;/strong> - regex search within a buffer&lt;/li>
&lt;li>&lt;strong>calculate&lt;/strong> - evaluate math expressions via calc-eval&lt;/li>
&lt;/ul>
&lt;p>Unsafe tools (require safe mode off):&lt;/p>
&lt;ul>
&lt;li>&lt;strong>write_file&lt;/strong> - write content to files&lt;/li>
&lt;li>&lt;strong>execute_shell&lt;/strong> - run shell commands&lt;/li>
&lt;/ul>
&lt;p>Safe mode is on by default, so the model can only read - it can&amp;rsquo;t modify anything unless you explicitly allow it, I think this is quite a nice simple implementation, at the moment I generally have safe mode off but always allowing confirmation for each tool action, but of course you can configure as necessary.&lt;/p>
&lt;p>&lt;strong>Example Session&lt;/strong>&lt;/p>
&lt;p>With a tool-capable model like qwen3:8b and tools enabled (C-c W):&lt;/p>
&lt;p>&lt;strong>&amp;gt;&amp;gt; PROMPT: What defuns are defined in ollama-buddy-tools.el?&lt;/strong>&lt;/p>
&lt;p>The model calls &lt;strong>search_buffer&lt;/strong> with a regex pattern, gets the list of function definitions, and gives you a nicely formatted summary. No copy-pasting needed.&lt;/p>
&lt;p>&lt;strong>Custom Tools&lt;/strong>&lt;/p>
&lt;p>You can register your own tools with ollama-buddy-tools-register:&lt;/p>
&lt;pre tabindex="0">&lt;code class="language-nil" data-lang="nil"> (ollama-buddy-tools-register
&amp;#39;my-tool
&amp;#34;Description of what the tool does&amp;#34;
&amp;#39;((type . &amp;#34;object&amp;#34;)
(required . [&amp;#34;param1&amp;#34;])
(properties . ((param1 . ((type . &amp;#34;string&amp;#34;)
(description . &amp;#34;Parameter description&amp;#34;))))))
(lambda (args)
(let ((param1 (alist-get &amp;#39;param1 args)))
(format &amp;#34;Result: %s&amp;#34; param1)))
t) ; t = safe tool
&lt;/code>&lt;/pre>&lt;p>The registration API takes a name, description, JSON schema for parameters, an implementation function, and a safety flag. The model sees the schema and decides when to call your tool based on the conversation.&lt;/p>
&lt;p>A ⚒ symbol now appears next to tool-capable models everywhere - header line, model selector (C-c m), and model management buffer (C-c M). This follows the same pattern as the existing ⊙ vision indicator, so you can see at a glance which models support tools.&lt;/p>
&lt;p>That&amp;rsquo;s it. Pull a tool-capable model (qwen3, llama3.1, mistral, etc.) or use an online tool enabled model from ollama and start chatting. Next up is probably some web searching!, as again the ollama API supports this, so you will be able to pull in the latest from the interwebs to augment your prompt definition!&lt;/p></description></item><item><title>Automatically Syncing Emacs Tab Bar Styling With Your Theme</title><link>https://www.emacs.dyerdwelling.family/emacs/20260211182648-emacs--automatically-syncing-emacs-tab-bar-styling-with-your-theme/</link><pubDate>Wed, 11 Feb 2026 18:26:00 +0000</pubDate><author>James Dyer</author><guid>https://www.emacs.dyerdwelling.family/emacs/20260211182648-emacs--automatically-syncing-emacs-tab-bar-styling-with-your-theme/</guid><description>&lt;p>If you&amp;rsquo;ve ever enabled a new theme and noticed your &lt;strong>tab-bar&lt;/strong> faces stubbornly hanging onto old colours or custom tweaks, I have found often that the &lt;code>tab-bar&lt;/code>, &lt;code>tab-bar-tab&lt;/code>, and &lt;code>tab-bar-tab-inactive&lt;/code> faces don’t always blend cleanly with freshly loaded themes — especially of the older variety (a bit like me) and especially ones that came out before the tab bar was introduced into Emacs.&lt;/p>
&lt;figure>&lt;img src="https://www.emacs.dyerdwelling.family/emacs/20260211182648-emacs--Automatically-Syncing-Emacs-Tab-Bar-Styling-With-Your-Theme.jpg" width="100%">
&lt;/figure>
&lt;p>So how about a simple solution?, Can I implement something, that whenever I load a theme, the tab-bar faces update based on the theme’s default faces to establish a visually pleasant and coherent look?&lt;/p>
&lt;p>Yes, yes I can!; the result is a tiny Elisp enhancement that hooks directly into Emacs&amp;rsquo; theme-loading process.&lt;/p>
&lt;p>Firstly however we need to have a method that will reliably pass over the themes default faces to the tab-bar. Here’s the function that realigns the tab-bar styling with your active theme:&lt;/p>
&lt;pre tabindex="0">&lt;code class="language-nil" data-lang="nil">(defun selected-window-accent-sync-tab-bar-to-theme ()
&amp;#34;Synchronize tab-bar faces with the current theme.&amp;#34;
(interactive)
(let ((default-bg (face-background &amp;#39;default))
(default-fg (face-foreground &amp;#39;default))
(inactive-fg (face-foreground &amp;#39;mode-line-inactive)))
(custom-set-faces
`(tab-bar ((t (:inherit default :background ,default-bg :foreground ,default-fg))))
`(tab-bar-tab ((t (:inherit default :background ,default-fg :foreground ,default-bg))))
`(tab-bar-tab-inactive ((t (:inherit default :background ,default-bg :foreground ,inactive-fg)))))))
&lt;/code>&lt;/pre>&lt;p>This simply rebuilds the key tab-bar faces so they derive their colours from the current theme’s normal face definitions, so any old themes should now not leave the tab bar faces hanging.&lt;/p>
&lt;p>Now for the function activation; Emacs 29 introduced &lt;code>enable-theme-functions&lt;/code>, a hook that runs &lt;strong>every time a theme is enabled&lt;/strong> — perfect for our use case, but as always I have my eye on older Emacs versions, so lets fall back to a classic approach: advice on &lt;code>load-theme&lt;/code>.&lt;/p>
&lt;p>Here’s a version‑aware setup that does the right thing automatically:&lt;/p>
&lt;pre tabindex="0">&lt;code class="language-nil" data-lang="nil">(if (version&amp;lt;= &amp;#34;29.1&amp;#34; emacs-version)
;; Emacs 29.1+ — use the official theme hook
(add-hook &amp;#39;enable-theme-functions
(lambda (_theme)
(selected-window-accent-sync-tab-bar-to-theme)))
;; Older Emacs — fall back to advising load-theme
(progn
(defun selected-window-accent-sync-tab-bar-to-theme--after (&amp;amp;rest _)
(selected-window-accent-sync-tab-bar-to-theme))
(advice-add &amp;#39;load-theme :after
#&amp;#39;selected-window-accent-sync-tab-bar-to-theme--after)))
&lt;/code>&lt;/pre>&lt;p>With this tweak in place, every time you change themes, your tab-bar instantly updates, colours stay consistent, clean, and theme‑accurate without you having to do anything at all! The downside to this of course is that any newer themes that were created after the advent of the tab bar in Emacs will have their tab-bar faces overridden, but for me this solution is good enough and gives a pleasant coherent visual tab bar experience.&lt;/p>
&lt;p>Yay!, yet another Yak shaved!&lt;/p></description></item><item><title>Spent a bit of free time polishing ollama-buddy - github Copilot is now onboard!</title><link>https://www.emacs.dyerdwelling.family/emacs/20260204100913-emacs--ollama-buddy-updates---github-copilot-integration-plus-others/</link><pubDate>Wed, 04 Feb 2026 10:40:00 +0000</pubDate><author>James Dyer</author><guid>https://www.emacs.dyerdwelling.family/emacs/20260204100913-emacs--ollama-buddy-updates---github-copilot-integration-plus-others/</guid><description>&lt;p>I&amp;rsquo;ve had a little free time recently (figuring out this baby stuff!) and thought I would spend time revisiting and refining my AI assistant &lt;a href="https://github.com/captainflasmr/ollama-buddy">ollama-buddy&lt;/a>&lt;/p>
&lt;p>I&amp;rsquo;ve been playing around with agentic coding and keeping up-to-date on the rapid development of the Emacs AI package landscape and I think I have refined in my own mind my idea of what I would like to see in an Emacs AI assistant.&lt;/p>
&lt;p>The headline change regarding the latest release of ollama-buddy is GitHub Copilot integration; the rest of the work is about smoothing the UI and simplifying day-to-day use.&lt;/p>
&lt;figure>&lt;img src="https://www.emacs.dyerdwelling.family/emacs/20250424085731-emacs--Ollama-Buddy-0-9-35-Grok-Gemini-Integration-Enhanced-Sessions.jpg" width="100%">
&lt;/figure>
&lt;p>What’s new - the Copilot addition (v1.2)&lt;/p>
&lt;ul>
&lt;li>GitHub Copilot Chat API support via a new file, ollama-buddy-copilot.el, so Copilot models can be used alongside your existing providers.&lt;/li>
&lt;li>Authentication uses GitHub’s device flow (OAuth). No API key required: M-x ollama-buddy-copilot-login opens a browser and guides you through secure authentication.&lt;/li>
&lt;li>Copilot models are identified with a &amp;ldquo;p:&amp;rdquo; prefix (for example, p:gpt-4o). The header line shows a &amp;ldquo;p&amp;rdquo; indicator when the Copilot provider is loaded so you always know it’s available.&lt;/li>
&lt;li>Copilot access exposes a broad set of models from multiple vendors through the Copilot interface: OpenAI (gpt-4o, gpt-5), Anthropic (claude-sonnet-4, claude-opus-4.5), Google (gemini-2.5-pro), and xAI models.&lt;/li>
&lt;li>Quick usage notes:
&lt;ol>
&lt;li>Ensure you have an active GitHub Copilot subscription.&lt;/li>
&lt;li>Run M-x ollama-buddy-copilot-login.&lt;/li>
&lt;li>Enter the device code in your browser at github.com/login/device when prompted.&lt;/li>
&lt;li>Select a Copilot model with C-c m (e.g., p:gpt-4o).&lt;/li>
&lt;/ol>
&lt;/li>
&lt;li>Example config to load Copilot support:&lt;/li>
&lt;/ul>
&lt;!--listend-->
&lt;pre tabindex="0">&lt;code class="language-nil" data-lang="nil">(use-package ollama-buddy
:bind
(&amp;#34;C-c o&amp;#34; . ollama-buddy-menu)
(&amp;#34;C-c O&amp;#34; . ollama-buddy-transient-menu-wrapper)
:config
(require &amp;#39;ollama-buddy-copilot nil t))
&lt;/code>&lt;/pre>&lt;p>Other notable updates in this release series&lt;/p>
&lt;ul>
&lt;li>&lt;strong>v1.2.1 (2026-02-02)&lt;/strong>
&lt;ul>
&lt;li>Attachment count indicator on the header line so you get a constant visual reminder that the session has attachments.&lt;/li>
&lt;/ul>
&lt;/li>
&lt;li>&lt;strong>v1.1.5 (2026-01-31)&lt;/strong>
&lt;ul>
&lt;li>Global system prompt feature (enabled by default): sets a baseline set of instructions (for example, to prefer plain prose and avoid markdown tables) that is prepended to session-specific system prompts. This helps keep responses consistent across providers and things like malformed markdown tables for example, which seems to be common. There’s a toggle (ollama-buddy-global-system-prompt-enabled) and a quick command to flip it (ollama-buddy-toggle-global-system-prompt), plus a transient-menu entry.&lt;/li>
&lt;li>Consolidated model management: streamlined into a single model management buffer (C-c W) and the welcome screen now points to that buffer for model tasks.&lt;/li>
&lt;/ul>
&lt;/li>
&lt;li>&lt;strong>v1.1.4 (2026-01-31)&lt;/strong>
&lt;ul>
&lt;li>Header-line and keybinding cleanup: C-c RET to send prompts (matches gptel, as I feel this seems intuitive), removed a redundant backend indicator, shortened the markdown indicator to &amp;ldquo;MD&amp;rdquo;, and fixed markdown → org heading conversion to keep structure sane.&lt;/li>
&lt;/ul>
&lt;/li>
&lt;li>&lt;strong>v1.1.3 (2026-01-31)&lt;/strong>
&lt;ul>
&lt;li>Chat UX improvements and simplification: added ollama-buddy-auto-scroll (default nil — don’t auto-scroll so you can read while streaming) and ollama-buddy-pulse-response (flashes the response on completion, taking from gptel again, as if there is no autoscrolling it is useful to visually see when the response has completed). Removed the model name coloring feature and related toggles to simplify code and improve org-mode performance.&lt;/li>
&lt;/ul>
&lt;/li>
&lt;li>&lt;strong>v1.1.2 (2026-01-30)&lt;/strong>
&lt;ul>
&lt;li>Streamlined welcome screen and model selection, clearer provider indicators in the header line and an improved list of enabled online LLM providers.&lt;/li>
&lt;/ul>
&lt;/li>
&lt;/ul></description></item><item><title>Ollama buddy now supports cloud models!</title><link>https://www.emacs.dyerdwelling.family/emacs/20260128082917-emacs--ollama-buddy-now-supports-cloud-models/</link><pubDate>Wed, 28 Jan 2026 08:29:00 +0000</pubDate><author>James Dyer</author><guid>https://www.emacs.dyerdwelling.family/emacs/20260128082917-emacs--ollama-buddy-now-supports-cloud-models/</guid><description>&lt;p>Having another look at my AI assistant - ollama-buddy, its been a while and it seems ollama has moved on since I started creating this package last year, so I have developed a new roadmap and the first step is to add ollama cloud models!&lt;/p>
&lt;figure>&lt;img src="https://www.emacs.dyerdwelling.family/emacs/20250424085731-emacs--Ollama-Buddy-0-9-35-Grok-Gemini-Integration-Enhanced-Sessions.jpg" width="100%">
&lt;/figure>
&lt;p>Here are some references to the project, including a youtube channel where I upload ollama-buddy demonstrations:&lt;/p>
&lt;p>&lt;a href="https://melpa.org/#/ollama-buddy">https://melpa.org/#/ollama-buddy&lt;/a>&lt;/p>
&lt;p>&lt;a href="https://github.com/captainflasmr/ollama-buddy">https://github.com/captainflasmr/ollama-buddy&lt;/a>&lt;/p>
&lt;p>Here is the changelog for the cloud model implementation:&lt;/p>
&lt;h2 id="1-dot-1">&lt;span class="timestamp-wrapper">&lt;span class="timestamp">&amp;lt;2026-01-28 Wed&amp;gt; &lt;/span>&lt;/span> &lt;strong>1.1&lt;/strong>&lt;/h2>
&lt;p>Added Ollama Cloud Models support&lt;/p>
&lt;ul>
&lt;li>Cloud models (running on ollama.com infrastructure) now work seamlessly&lt;/li>
&lt;li>&lt;code>ollama-buddy-cloud-signin&lt;/code> to automatically open browser for authentication&lt;/li>
&lt;li>Cloud models are proxied through the local Ollama server which handles authentication&lt;/li>
&lt;li>Use &lt;code>C-u C-c m&lt;/code> or transient menu &amp;ldquo;Model &amp;gt; Cloud&amp;rdquo; to select cloud models&lt;/li>
&lt;li>Status line shows ☁ indicator when using a cloud model&lt;/li>
&lt;li>Available cloud models include: qwen3-coder:480b-cloud, deepseek-v3.1:671b-cloud, gpt-oss:120b-cloud, minimax-m2.1:cloud, and more&lt;/li>
&lt;/ul></description></item><item><title>Auto-Populating Weekly Dates in Org-Mode Tables</title><link>https://www.emacs.dyerdwelling.family/emacs/20260126101317-emacs--auto-populating-weekly-dates-in-org-mode-tables/</link><pubDate>Mon, 26 Jan 2026 10:13:00 +0000</pubDate><author>James Dyer</author><guid>https://www.emacs.dyerdwelling.family/emacs/20260126101317-emacs--auto-populating-weekly-dates-in-org-mode-tables/</guid><description>&lt;p>Here is just a quick one, I was working with an org-mode table for tracking work weeks and needed to auto-populate a Date column where each row increments by exactly one week. The table structure looked like this:&lt;/p>
&lt;figure>&lt;img src="https://www.emacs.dyerdwelling.family/emacs/20260126101317-emacs--Auto-Populating-Weekly-Dates-in-Org-Mode-Tables.jpg" width="100%">
&lt;/figure>
&lt;p>The first row has a base date (2026-01-05), and I wanted subsequent rows to automatically calculate as weekly increments: 2026-01-12, 2026-01-19, and so on.&lt;/p>
&lt;p>Initially, I tried several approaches that seemed logical but encountered &lt;code>#ERROR&lt;/code> results and eventually settled on a working solution which is to hardcode the base date directly in the formula:&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">&lt;code class="language-org" data-lang="org">&lt;span style="display:flex;">&lt;span>&lt;span style="color:#75715e">#+TBLFM&lt;/span>&lt;span style="color:#75715e">: $3=&amp;#39;(format-time-string &amp;#34;%Y-%m-%d&amp;#34; (time-add (date-to-time &amp;#34;2026-01-05&amp;#34;) (* (- @# 2) 7 24 3600)))&lt;/span>
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>which gave:&lt;/p>
&lt;figure>&lt;img src="https://www.emacs.dyerdwelling.family/emacs/20260126101317-emacs--Auto-Populating-Weekly-Dates-in-Org-Mode-Tables2.jpg" width="100%">
&lt;/figure>
&lt;p>and I can now of course extend the table for all the weeks in the year and I don&amp;rsquo;t have to fill in manually any more!&lt;/p>
&lt;p>Here&amp;rsquo;s how it works:&lt;/p>
&lt;ul>
&lt;li>&lt;code>(date-to-time &amp;quot;2026-01-05&amp;quot;)&lt;/code> - Convert the hardcoded base date to Emacs time format&lt;/li>
&lt;li>&lt;code>(- @# 2)&lt;/code> - Calculate the offset from the base row&lt;/li>
&lt;li>&lt;code>(* (- @# 2) 7 24 3600)&lt;/code> - Convert the offset to seconds (weeks × days × hours × seconds)&lt;/li>
&lt;li>&lt;code>time-add&lt;/code> - Add the offset to the base date&lt;/li>
&lt;li>&lt;code>format-time-string &amp;quot;%Y-%m-%d&amp;quot;&lt;/code> - Format back to ISO date string&lt;/li>
&lt;/ul></description></item><item><title>Speed Reading in Emacs: Building an RSVP Reader</title><link>https://www.emacs.dyerdwelling.family/emacs/20260116182841-emacs--speed-reading-in-emacs-building-an-rsvp-reader/</link><pubDate>Sun, 18 Jan 2026 10:30:00 +0000</pubDate><author>James Dyer</author><guid>https://www.emacs.dyerdwelling.family/emacs/20260116182841-emacs--speed-reading-in-emacs-building-an-rsvp-reader/</guid><description>&lt;p>I recently came across a fascinating video titled &amp;ldquo;How Fast Can You Read? - Speed Reading Challenge&amp;rdquo; that demonstrated the power of RSVP (Rapid Serial Visual Presentation) for speed reading. The concept is quite nice and simple and I vaguely remember seeing something about it a few years back. Instead of your eyes scanning across lines of text, words are presented one at a time in a fixed position. This eliminates the mechanical overhead of eye movements and can dramatically increase reading speed!&lt;/p>
&lt;figure>&lt;img src="https://www.emacs.dyerdwelling.family/emacs/20260116182841-emacs--Speed-Reading-in-Emacs:-Building-an-RSVP-Reader.jpg" width="100%">
&lt;/figure>
&lt;p>So, I immediately wondered, could I build this into Emacs?, actually no, firstly I thought, are there any packages for Emacs that can do this?, of course there are!, the &lt;strong>spray&lt;/strong> package from MELPA is a more mature, feature-rich option if you&amp;rsquo;re looking for production-ready RSVP reading in Emacs, and also there is &lt;strong>speedread&lt;/strong>. However, there&amp;rsquo;s something satisfying about having a compact, single-function solution that does exactly what you need, so lets see if I can build one!&lt;/p>
&lt;p>RSVP works by displaying words sequentially in the same location on screen. Your eyes remain stationary, focused on a single point, while words flash by at a controlled pace. This technique can boost reading speeds to 300-600+ words per minute, compared to typical reading speeds of 200-300 WPM.&lt;/p>
&lt;p>The key innovation is the &lt;strong>Optimal Recognition Point (ORP)&lt;/strong> - typically positioned about one-third into each word. This is where your eye naturally fixates when reading. By aligning each word&amp;rsquo;s ORP at the same screen position, RSVP creates an optimal visual flow.&lt;/p>
&lt;p>Given Emacs&amp;rsquo; extensive text processing capabilities, this sounds something that Emacs could eat for breakfast. Here is what I came up with:&lt;/p>
&lt;p>Here is a quick video of my implementation:&lt;/p>
&lt;figure>&lt;img src="https://www.emacs.dyerdwelling.family/emacs/20260118100321--screen-recording.gif" width="100%">
&lt;/figure>
&lt;p>and the defun:&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">&lt;code class="language-elisp" data-lang="elisp">&lt;span style="display:flex;">&lt;span>(defun rsvp-minibuffer ()
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#e6db74">&amp;#34;Display words from point (or mark to point) in minibuffer using RSVP.
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#e6db74">Use f/s for speed, [/] for size, b/n to skip, SPC to pause, q to quit.&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> (interactive)
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> (let* ((start (if (region-active-p) (&lt;span style="color:#a6e22e">region-beginning&lt;/span>) (&lt;span style="color:#a6e22e">point&lt;/span>)))
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> (end (if (region-active-p) (&lt;span style="color:#a6e22e">region-end&lt;/span>) (&lt;span style="color:#a6e22e">point-max&lt;/span>)))
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> (text (&lt;span style="color:#a6e22e">buffer-substring-no-properties&lt;/span> start end))
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> (wpm &lt;span style="color:#ae81ff">350&lt;/span>) (font-size &lt;span style="color:#ae81ff">200&lt;/span>) (orp-column &lt;span style="color:#ae81ff">20&lt;/span>)
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> (word-positions &lt;span style="color:#f92672">&amp;#39;&lt;/span>()) (pos &lt;span style="color:#ae81ff">0&lt;/span>) (i &lt;span style="color:#ae81ff">0&lt;/span>)
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> (message-log-max &lt;span style="color:#66d9ef">nil&lt;/span>)) &lt;span style="color:#75715e">; Disable message logging&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#75715e">;; Build word positions list&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> (dolist (word (split-string text))
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> (unless (string-blank-p word)
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> (when-let ((word-start (&lt;span style="color:#a6e22e">string-match&lt;/span> (&lt;span style="color:#a6e22e">regexp-quote&lt;/span> word) text pos)))
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> (push (&lt;span style="color:#a6e22e">cons&lt;/span> word (&lt;span style="color:#a6e22e">+&lt;/span> start word-start)) word-positions)
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> (setq pos (&lt;span style="color:#a6e22e">+&lt;/span> word-start (&lt;span style="color:#a6e22e">length&lt;/span> word))))))
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> (setq word-positions (&lt;span style="color:#a6e22e">nreverse&lt;/span> word-positions))
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#75715e">;; Display loop&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> (while (&lt;span style="color:#a6e22e">&amp;lt;&lt;/span> i (&lt;span style="color:#a6e22e">length&lt;/span> word-positions))
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> (let* ((word (&lt;span style="color:#a6e22e">car&lt;/span> (&lt;span style="color:#a6e22e">nth&lt;/span> i word-positions)))
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> (word-pos (&lt;span style="color:#a6e22e">cdr&lt;/span> (&lt;span style="color:#a6e22e">nth&lt;/span> i word-positions)))
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> (word-len (&lt;span style="color:#a6e22e">length&lt;/span> word))
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> (delay (&lt;span style="color:#a6e22e">*&lt;/span> (&lt;span style="color:#a6e22e">/&lt;/span> &lt;span style="color:#ae81ff">60.0&lt;/span> wpm)
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> (cond ((&lt;span style="color:#a6e22e">&amp;lt;&lt;/span> word-len &lt;span style="color:#ae81ff">3&lt;/span>) &lt;span style="color:#ae81ff">0.8&lt;/span>) ((&lt;span style="color:#a6e22e">&amp;gt;&lt;/span> word-len &lt;span style="color:#ae81ff">8&lt;/span>) &lt;span style="color:#ae81ff">1.3&lt;/span>) (&lt;span style="color:#66d9ef">t&lt;/span> &lt;span style="color:#ae81ff">1.0&lt;/span>))
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> (if (string-match-p &lt;span style="color:#e6db74">&amp;#34;[.!?]$&amp;#34;&lt;/span> word) &lt;span style="color:#ae81ff">1.5&lt;/span> &lt;span style="color:#ae81ff">1.0&lt;/span>)))
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> (orp-pos (&lt;span style="color:#a6e22e">/&lt;/span> word-len &lt;span style="color:#ae81ff">3&lt;/span>))
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> (face-mono &lt;span style="color:#f92672">`&lt;/span>(:height &lt;span style="color:#f92672">,&lt;/span>font-size :family &lt;span style="color:#e6db74">&amp;#34;monospace&amp;#34;&lt;/span>))
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> (face-orp &lt;span style="color:#f92672">`&lt;/span>(:foreground &lt;span style="color:#e6db74">&amp;#34;red&amp;#34;&lt;/span> :weight normal &lt;span style="color:#f92672">,@&lt;/span>face-mono))
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> (padded-word (&lt;span style="color:#a6e22e">concat&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> (&lt;span style="color:#a6e22e">propertize&lt;/span> (&lt;span style="color:#a6e22e">make-string&lt;/span> (&lt;span style="color:#a6e22e">max&lt;/span> &lt;span style="color:#ae81ff">0&lt;/span> (&lt;span style="color:#a6e22e">-&lt;/span> orp-column orp-pos)) &lt;span style="color:#e6db74">?\s&lt;/span>) &lt;span style="color:#e6db74">&amp;#39;face&lt;/span> face-mono)
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> (&lt;span style="color:#a6e22e">propertize&lt;/span> (&lt;span style="color:#a6e22e">substring&lt;/span> word &lt;span style="color:#ae81ff">0&lt;/span> orp-pos) &lt;span style="color:#e6db74">&amp;#39;face&lt;/span> face-mono)
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> (&lt;span style="color:#a6e22e">propertize&lt;/span> (&lt;span style="color:#a6e22e">substring&lt;/span> word orp-pos (&lt;span style="color:#a6e22e">1+&lt;/span> orp-pos)) &lt;span style="color:#e6db74">&amp;#39;face&lt;/span> face-orp)
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> (&lt;span style="color:#a6e22e">propertize&lt;/span> (&lt;span style="color:#a6e22e">substring&lt;/span> word (&lt;span style="color:#a6e22e">1+&lt;/span> orp-pos)) &lt;span style="color:#e6db74">&amp;#39;face&lt;/span> face-mono))))
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> (&lt;span style="color:#a6e22e">goto-char&lt;/span> (&lt;span style="color:#a6e22e">+&lt;/span> word-pos word-len))
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> (&lt;span style="color:#a6e22e">message&lt;/span> &lt;span style="color:#e6db74">&amp;#34;%s&amp;#34;&lt;/span> padded-word)
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> (pcase (&lt;span style="color:#a6e22e">read-event&lt;/span> &lt;span style="color:#66d9ef">nil&lt;/span> &lt;span style="color:#66d9ef">nil&lt;/span> delay)
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> (&lt;span style="color:#e6db74">?f&lt;/span> (setq wpm (&lt;span style="color:#a6e22e">min&lt;/span> &lt;span style="color:#ae81ff">1000&lt;/span> (&lt;span style="color:#a6e22e">+&lt;/span> wpm &lt;span style="color:#ae81ff">50&lt;/span>))))
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> (&lt;span style="color:#e6db74">?s&lt;/span> (setq wpm (&lt;span style="color:#a6e22e">max&lt;/span> &lt;span style="color:#ae81ff">50&lt;/span> (&lt;span style="color:#a6e22e">-&lt;/span> wpm &lt;span style="color:#ae81ff">50&lt;/span>))))
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> (&lt;span style="color:#e6db74">?\[&lt;/span> (setq font-size (&lt;span style="color:#a6e22e">max&lt;/span> &lt;span style="color:#ae81ff">100&lt;/span> (&lt;span style="color:#a6e22e">-&lt;/span> font-size &lt;span style="color:#ae81ff">20&lt;/span>))))
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> (&lt;span style="color:#e6db74">?\]&lt;/span> (setq font-size (&lt;span style="color:#a6e22e">min&lt;/span> &lt;span style="color:#ae81ff">400&lt;/span> (&lt;span style="color:#a6e22e">+&lt;/span> font-size &lt;span style="color:#ae81ff">20&lt;/span>))))
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> (&lt;span style="color:#e6db74">?b&lt;/span> (setq i (&lt;span style="color:#a6e22e">max&lt;/span> &lt;span style="color:#ae81ff">0&lt;/span> (&lt;span style="color:#a6e22e">-&lt;/span> i &lt;span style="color:#ae81ff">10&lt;/span>))))
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> (&lt;span style="color:#e6db74">?n&lt;/span> (setq i (&lt;span style="color:#a6e22e">min&lt;/span> (&lt;span style="color:#a6e22e">1-&lt;/span> (&lt;span style="color:#a6e22e">length&lt;/span> word-positions)) (&lt;span style="color:#a6e22e">+&lt;/span> i &lt;span style="color:#ae81ff">10&lt;/span>))))
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> (&lt;span style="color:#e6db74">?\s&lt;/span> (&lt;span style="color:#a6e22e">read-event&lt;/span> (&lt;span style="color:#a6e22e">format&lt;/span> &lt;span style="color:#e6db74">&amp;#34;%s [PAUSED - WPM: %d]&amp;#34;&lt;/span> padded-word wpm)))
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> (&lt;span style="color:#e6db74">?q&lt;/span> (setq i (&lt;span style="color:#a6e22e">length&lt;/span> word-positions)))
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> (_ (setq i (&lt;span style="color:#a6e22e">1+&lt;/span> i))))))))
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>The function calculates the ORP as one-third through each word and highlights it in red. By padding each word with spaces, the ORP character stays perfectly aligned in the same column, creating that crucial stationary focal point.&lt;/p>
&lt;p>To ensure pixel-perfect alignment, the function explicitly sets a monospace font family for all displayed text. Without this, proportional fonts would cause the ORP to drift slightly between words, although I think at times there is a little waddle, but it is good enough.&lt;/p>
&lt;p>Also, Not all words are created equal:&lt;/p>
&lt;ul>
&lt;li>Short words (&amp;lt; 3 characters) display 20% faster&lt;/li>
&lt;li>Long words (&amp;gt; 8 characters) display 30% slower&lt;/li>
&lt;li>Words ending in punctuation (&lt;code>.!?&lt;/code>) get 50% more time&lt;/li>
&lt;/ul>
&lt;p>This mimics natural reading rhythms where you&amp;rsquo;d naturally pause at sentence boundaries.&lt;/p>
&lt;p>While reading, you can try these keybindings: (which I borrowed off &lt;strong>spray&lt;/strong>)&lt;/p>
&lt;ul>
&lt;li>&lt;code>f&lt;/code> / &lt;code>s&lt;/code> - Speed up or slow down (±50 WPM)&lt;/li>
&lt;li>&lt;code>[&lt;/code> / &lt;code>]&lt;/code> - Decrease or increase font size&lt;/li>
&lt;li>&lt;code>b&lt;/code> / &lt;code>n&lt;/code> - Skip backward or forward by 10 words&lt;/li>
&lt;li>&lt;code>SPC&lt;/code> - Pause (press any key to resume)&lt;/li>
&lt;li>&lt;code>q&lt;/code> - Quit&lt;/li>
&lt;li>&lt;code>C-g&lt;/code> - Emergency quit&lt;/li>
&lt;/ul>
&lt;p>Also The function tracks each word&amp;rsquo;s position in the original buffer and updates &lt;code>point&lt;/code> as you read. This means:&lt;/p>
&lt;ul>
&lt;li>You can see where you are in the text&lt;/li>
&lt;li>When you quit, your cursor is at the last word you read&lt;/li>
&lt;li>You can resume reading by running the function again&lt;/li>
&lt;/ul>
&lt;p>To use it, simply:&lt;/p>
&lt;ol>
&lt;li>Position your cursor where you want to start reading (or select a region)&lt;/li>
&lt;li>Run &lt;code>M-x rsvp-minibuffer&lt;/code>&lt;/li>
&lt;li>Watch the words flow in the minibuffer&lt;/li>
&lt;/ol>
&lt;p>The function works from point to end of buffer, or if you have an active region, it only processes the selected text.&lt;/p>
&lt;p>If you&amp;rsquo;re curious about RSVP reading, drop this function into your Emacs config and give it a try. Start at 300-350 WPM and see how it feels. You might be surprised at how much faster you can consume text when your eyes aren&amp;rsquo;t constantly moving across the page.&lt;/p>
&lt;p>The code is simple enough to customize - adjust the default WPM, change the ORP colour, modify the timing multipliers, or add new controls. That&amp;rsquo;s the beauty of Emacs, if you can imagine it, you can build it.&lt;/p></description></item><item><title>A single function ripgrep alternative to rgrep</title><link>https://www.emacs.dyerdwelling.family/emacs/20260109094340-emacs--a-single-function-ripgrep-alternative-to-rgrep/</link><pubDate>Fri, 09 Jan 2026 09:43:00 +0000</pubDate><author>James Dyer</author><guid>https://www.emacs.dyerdwelling.family/emacs/20260109094340-emacs--a-single-function-ripgrep-alternative-to-rgrep/</guid><description>&lt;p>For years, &lt;code>rgrep&lt;/code> has been the go-to solution for searching codebases in Emacs. It&amp;rsquo;s built-in, reliable, and works everywhere. But it&amp;rsquo;s slow on large projects and uses the aging &lt;code>find&lt;/code> and &lt;code>grep&lt;/code> commands.&lt;/p>
&lt;p>Packages like &lt;code>deadgrep&lt;/code> and &lt;code>rg.el&lt;/code> provide ripgrep integration, and for years I used &lt;code>deadgrep&lt;/code> and really liked it. But what if you could get ripgrep&amp;rsquo;s speed with just a single function you paste into your config?&lt;/p>
&lt;figure>&lt;img src="https://www.emacs.dyerdwelling.family/emacs/20260109094340-emacs--A-Single-Function-ripgrep-Alternative-to-rgrep.jpg" width="100%">
&lt;/figure>
&lt;p>This post introduces a ~100 line &lt;code>defun&lt;/code> that replaces rgrep, no packages, no dependencies, just pure Elisp. It&amp;rsquo;s fast, asynchronous, works offline, and mimics rgrep&amp;rsquo;s familiar interface so it can leverage &lt;code>grep-mode&lt;/code>&lt;/p>
&lt;p>So, why not just use rgrep?&lt;/p>
&lt;p>I think that rgrep has three main limitations:&lt;/p>
&lt;p>Firstly, speed. On a project with 10,000+ files, rgrep can take 15-30 seconds. Ripgrep completes the same search in under a second.&lt;/p>
&lt;p>Secondly, file ignoring, rgrep requires manually configuring &lt;code>grep-find-ignored-directories&lt;/code> or &lt;code>grep-find-ignored-files&lt;/code>, I had the following typical configuration for rgrep, but it wasn&amp;rsquo;t as flexible as I would like it to be:&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">&lt;code class="language-elisp" data-lang="elisp">&lt;span style="display:flex;">&lt;span>(eval-after-load &lt;span style="color:#e6db74">&amp;#39;grep&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#f92672">&amp;#39;&lt;/span>(progn
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> (dolist (dir &lt;span style="color:#f92672">&amp;#39;&lt;/span>(&lt;span style="color:#e6db74">&amp;#34;nas&amp;#34;&lt;/span> &lt;span style="color:#e6db74">&amp;#34;.cache&amp;#34;&lt;/span> &lt;span style="color:#e6db74">&amp;#34;cache&amp;#34;&lt;/span> &lt;span style="color:#e6db74">&amp;#34;elpa&amp;#34;&lt;/span> &lt;span style="color:#e6db74">&amp;#34;chromium&amp;#34;&lt;/span> &lt;span style="color:#e6db74">&amp;#34;.local/share&amp;#34;&lt;/span> &lt;span style="color:#e6db74">&amp;#34;syncthing&amp;#34;&lt;/span> &lt;span style="color:#e6db74">&amp;#34;.mozilla&amp;#34;&lt;/span> &lt;span style="color:#e6db74">&amp;#34;.local/lib&amp;#34;&lt;/span> &lt;span style="color:#e6db74">&amp;#34;Games&amp;#34;&lt;/span>))
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> (push dir grep-find-ignored-directories))
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> (dolist (file &lt;span style="color:#f92672">&amp;#39;&lt;/span>(&lt;span style="color:#e6db74">&amp;#34;.cache&amp;#34;&lt;/span> &lt;span style="color:#e6db74">&amp;#34;*cache*&amp;#34;&lt;/span> &lt;span style="color:#e6db74">&amp;#34;*.iso&amp;#34;&lt;/span> &lt;span style="color:#e6db74">&amp;#34;*.xmp&amp;#34;&lt;/span> &lt;span style="color:#e6db74">&amp;#34;*.jpg&amp;#34;&lt;/span> &lt;span style="color:#e6db74">&amp;#34;*.mp4&amp;#34;&lt;/span>))
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> (push file grep-find-ignored-files))
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> ))
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>Ripgrep automatically respects an &lt;code>.ignore&lt;/code> file. Just create an &lt;code>.ignore&lt;/code> file in your project root and list patterns to exclude, this is just a simple text file, universally applied across all searches and any changes can be easily applied.&lt;/p>
&lt;p>Thirdly, modern features. Ripgrep includes smart-case search, better regex support, and automatic binary file detection. Of course, there is a context that can be displayed around the found line, but in order to get ripgrep to work with grep-mode, this is not really doable, and it&amp;rsquo;s not something I need anyway.&lt;/p>
&lt;hr>
&lt;p>Here is the complete ripgrep implementation that you can paste directly into your &lt;code>init.el&lt;/code>:&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">&lt;code class="language-elisp" data-lang="elisp">&lt;span style="display:flex;">&lt;span>(defun my/grep (search-term &lt;span style="color:#66d9ef">&amp;amp;optional&lt;/span> directory glob)
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#e6db74">&amp;#34;Run ripgrep (rg) with SEARCH-TERM and optionally DIRECTORY and GLOB.
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#e6db74">If ripgrep is unavailable, fall back to Emacs&amp;#39;s rgrep command. Highlights SEARCH-TERM in results.
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#e6db74">By default, only the SEARCH-TERM needs to be provided. If called with a
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#e6db74">universal argument, DIRECTORY and GLOB are prompted for as well.&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> (interactive
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> (let* ((univ-arg current-prefix-arg)
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> (default-search-term
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> (cond
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> ((use-region-p)
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> (&lt;span style="color:#a6e22e">buffer-substring-no-properties&lt;/span> (&lt;span style="color:#a6e22e">region-beginning&lt;/span>) (&lt;span style="color:#a6e22e">region-end&lt;/span>)))
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> ((thing-at-point &lt;span style="color:#e6db74">&amp;#39;symbol&lt;/span> &lt;span style="color:#66d9ef">t&lt;/span>))
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> ((thing-at-point &lt;span style="color:#e6db74">&amp;#39;word&lt;/span> &lt;span style="color:#66d9ef">t&lt;/span>))
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> (&lt;span style="color:#66d9ef">t&lt;/span> &lt;span style="color:#e6db74">&amp;#34;&amp;#34;&lt;/span>))))
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> (&lt;span style="color:#a6e22e">list&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> (&lt;span style="color:#a6e22e">read-string&lt;/span> (if (string-empty-p default-search-term)
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#e6db74">&amp;#34;Search for: &amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> (&lt;span style="color:#a6e22e">format&lt;/span> &lt;span style="color:#e6db74">&amp;#34;Search for (default `%s`): &amp;#34;&lt;/span> default-search-term))
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#66d9ef">nil&lt;/span> &lt;span style="color:#66d9ef">nil&lt;/span> default-search-term)
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> (when univ-arg (read-directory-name &lt;span style="color:#e6db74">&amp;#34;Directory: &amp;#34;&lt;/span>))
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> (when univ-arg (&lt;span style="color:#a6e22e">read-string&lt;/span> &lt;span style="color:#e6db74">&amp;#34;File pattern (glob, default: ): &amp;#34;&lt;/span> &lt;span style="color:#66d9ef">nil&lt;/span> &lt;span style="color:#66d9ef">nil&lt;/span> &lt;span style="color:#e6db74">&amp;#34;&amp;#34;&lt;/span>)))))
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> (let* ((directory (&lt;span style="color:#a6e22e">expand-file-name&lt;/span> (or directory default-directory)))
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> (glob (or glob &lt;span style="color:#e6db74">&amp;#34;&amp;#34;&lt;/span>))
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> (&lt;span style="color:#a6e22e">buffer-name&lt;/span> &lt;span style="color:#e6db74">&amp;#34;*grep*&amp;#34;&lt;/span>))
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> (if (executable-find &lt;span style="color:#e6db74">&amp;#34;rg&amp;#34;&lt;/span>)
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> (let ((buffer (&lt;span style="color:#a6e22e">get-buffer-create&lt;/span> &lt;span style="color:#a6e22e">buffer-name&lt;/span>)))
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> (with-current-buffer buffer
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> (setq default-directory directory)
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> (let ((inhibit-read-only &lt;span style="color:#66d9ef">t&lt;/span>))
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> (&lt;span style="color:#a6e22e">erase-buffer&lt;/span>)
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> (&lt;span style="color:#a6e22e">insert&lt;/span> (&lt;span style="color:#a6e22e">format&lt;/span> &lt;span style="color:#e6db74">&amp;#34;-*- mode: grep; default-directory: \&amp;#34;%s\&amp;#34; -*-\n\n&amp;#34;&lt;/span> directory))
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> (if (not (string= &lt;span style="color:#e6db74">&amp;#34;&amp;#34;&lt;/span> glob))
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> (&lt;span style="color:#a6e22e">insert&lt;/span> (&lt;span style="color:#a6e22e">format&lt;/span> &lt;span style="color:#e6db74">&amp;#34;[o] Glob: %s\n\n&amp;#34;&lt;/span> glob)))
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> (&lt;span style="color:#a6e22e">insert&lt;/span> &lt;span style="color:#e6db74">&amp;#34;Searching...\n\n&amp;#34;&lt;/span>))
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> (grep-mode)
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> (setq-local my/grep-search-term search-term)
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> (setq-local my/grep-directory directory)
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> (setq-local my/grep-glob glob))
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> (pop-to-buffer buffer)
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> (&lt;span style="color:#a6e22e">goto-char&lt;/span> (&lt;span style="color:#a6e22e">point-min&lt;/span>))
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> (make-process
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> :name &lt;span style="color:#e6db74">&amp;#34;ripgrep&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> :buffer buffer
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> :command &lt;span style="color:#f92672">`&lt;/span>(&lt;span style="color:#e6db74">&amp;#34;rg&amp;#34;&lt;/span> &lt;span style="color:#e6db74">&amp;#34;--color=never&amp;#34;&lt;/span> &lt;span style="color:#e6db74">&amp;#34;--max-columns=500&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#e6db74">&amp;#34;--column&amp;#34;&lt;/span> &lt;span style="color:#e6db74">&amp;#34;--line-number&amp;#34;&lt;/span> &lt;span style="color:#e6db74">&amp;#34;--no-heading&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#e6db74">&amp;#34;--smart-case&amp;#34;&lt;/span> &lt;span style="color:#e6db74">&amp;#34;-e&amp;#34;&lt;/span> &lt;span style="color:#f92672">,&lt;/span>search-term
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#e6db74">&amp;#34;--glob&amp;#34;&lt;/span> &lt;span style="color:#f92672">,&lt;/span>glob &lt;span style="color:#f92672">,&lt;/span>directory)
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> :filter (lambda (proc &lt;span style="color:#a6e22e">string&lt;/span>)
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> (when (&lt;span style="color:#a6e22e">buffer-live-p&lt;/span> (&lt;span style="color:#a6e22e">process-buffer&lt;/span> proc))
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> (with-current-buffer (&lt;span style="color:#a6e22e">process-buffer&lt;/span> proc)
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> (let ((inhibit-read-only &lt;span style="color:#66d9ef">t&lt;/span>)
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> (moving (&lt;span style="color:#a6e22e">=&lt;/span> (&lt;span style="color:#a6e22e">point&lt;/span>) (&lt;span style="color:#a6e22e">process-mark&lt;/span> proc))))
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> (setq &lt;span style="color:#a6e22e">string&lt;/span> (replace-regexp-in-string &lt;span style="color:#e6db74">&amp;#34;[\r\0\x01-\x08\x0B-\x0C\x0E-\x1F]&amp;#34;&lt;/span> &lt;span style="color:#e6db74">&amp;#34;&amp;#34;&lt;/span> &lt;span style="color:#a6e22e">string&lt;/span>))
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#75715e">;; Replace full directory path with ./ in the incoming output&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> (setq &lt;span style="color:#a6e22e">string&lt;/span> (replace-regexp-in-string
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> (&lt;span style="color:#a6e22e">concat&lt;/span> &lt;span style="color:#e6db74">&amp;#34;^&amp;#34;&lt;/span> (&lt;span style="color:#a6e22e">regexp-quote&lt;/span> directory))
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#e6db74">&amp;#34;./&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#a6e22e">string&lt;/span>))
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> (save-excursion
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> (&lt;span style="color:#a6e22e">goto-char&lt;/span> (&lt;span style="color:#a6e22e">process-mark&lt;/span> proc))
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> (&lt;span style="color:#a6e22e">insert&lt;/span> &lt;span style="color:#a6e22e">string&lt;/span>)
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> (&lt;span style="color:#a6e22e">set-marker&lt;/span> (&lt;span style="color:#a6e22e">process-mark&lt;/span> proc) (&lt;span style="color:#a6e22e">point&lt;/span>)))
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> (if moving (&lt;span style="color:#a6e22e">goto-char&lt;/span> (&lt;span style="color:#a6e22e">process-mark&lt;/span> proc)))))))
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> :sentinel
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> (lambda (proc _event)
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> (when (&lt;span style="color:#a6e22e">memq&lt;/span> (&lt;span style="color:#a6e22e">process-status&lt;/span> proc) &lt;span style="color:#f92672">&amp;#39;&lt;/span>(exit &lt;span style="color:#a6e22e">signal&lt;/span>))
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> (with-current-buffer (&lt;span style="color:#a6e22e">process-buffer&lt;/span> proc)
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> (let ((inhibit-read-only &lt;span style="color:#66d9ef">t&lt;/span>))
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#75715e">;; Remove &amp;#34;Searching...&amp;#34; line&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> (&lt;span style="color:#a6e22e">goto-char&lt;/span> (&lt;span style="color:#a6e22e">point-min&lt;/span>))
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> (while (&lt;span style="color:#a6e22e">re-search-forward&lt;/span> &lt;span style="color:#e6db74">&amp;#34;Searching\\.\\.\\.\n\n&amp;#34;&lt;/span> &lt;span style="color:#66d9ef">nil&lt;/span> &lt;span style="color:#66d9ef">t&lt;/span>)
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> (&lt;span style="color:#a6e22e">replace-match&lt;/span> &lt;span style="color:#e6db74">&amp;#34;&amp;#34;&lt;/span> &lt;span style="color:#66d9ef">nil&lt;/span> &lt;span style="color:#66d9ef">t&lt;/span>))
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#75715e">;; Clean up the output - replace full paths with ./&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> (&lt;span style="color:#a6e22e">goto-char&lt;/span> (&lt;span style="color:#a6e22e">point-min&lt;/span>))
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> (&lt;span style="color:#a6e22e">forward-line&lt;/span> &lt;span style="color:#ae81ff">3&lt;/span>)
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> (let ((start-pos (&lt;span style="color:#a6e22e">point&lt;/span>)))
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> (while (&lt;span style="color:#a6e22e">re-search-forward&lt;/span> (&lt;span style="color:#a6e22e">concat&lt;/span> &lt;span style="color:#e6db74">&amp;#34;^&amp;#34;&lt;/span> (&lt;span style="color:#a6e22e">regexp-quote&lt;/span> directory)) &lt;span style="color:#66d9ef">nil&lt;/span> &lt;span style="color:#66d9ef">t&lt;/span>)
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> (&lt;span style="color:#a6e22e">replace-match&lt;/span> &lt;span style="color:#e6db74">&amp;#34;./&amp;#34;&lt;/span> &lt;span style="color:#66d9ef">t&lt;/span> &lt;span style="color:#66d9ef">t&lt;/span>))
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#75715e">;; Check if any results were found&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> (&lt;span style="color:#a6e22e">goto-char&lt;/span> start-pos)
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> (when (&lt;span style="color:#a6e22e">=&lt;/span> (&lt;span style="color:#a6e22e">point&lt;/span>) (&lt;span style="color:#a6e22e">point-max&lt;/span>))
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> (&lt;span style="color:#a6e22e">insert&lt;/span> &lt;span style="color:#e6db74">&amp;#34;No results found.\n&amp;#34;&lt;/span>)))
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> (&lt;span style="color:#a6e22e">goto-char&lt;/span> (&lt;span style="color:#a6e22e">point-max&lt;/span>))
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> (&lt;span style="color:#a6e22e">insert&lt;/span> &lt;span style="color:#e6db74">&amp;#34;\nRipgrep finished\n&amp;#34;&lt;/span>)
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#75715e">;; Highlight search terms using grep&amp;#39;s match face&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> (&lt;span style="color:#a6e22e">goto-char&lt;/span> (&lt;span style="color:#a6e22e">point-min&lt;/span>))
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> (&lt;span style="color:#a6e22e">forward-line&lt;/span> &lt;span style="color:#ae81ff">3&lt;/span>)
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> (save-excursion
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> (while (&lt;span style="color:#a6e22e">re-search-forward&lt;/span> (&lt;span style="color:#a6e22e">regexp-quote&lt;/span> search-term) &lt;span style="color:#66d9ef">nil&lt;/span> &lt;span style="color:#66d9ef">t&lt;/span>)
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> (&lt;span style="color:#a6e22e">put-text-property&lt;/span> (&lt;span style="color:#a6e22e">match-beginning&lt;/span> &lt;span style="color:#ae81ff">0&lt;/span>) (&lt;span style="color:#a6e22e">match-end&lt;/span> &lt;span style="color:#ae81ff">0&lt;/span>)
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#e6db74">&amp;#39;face&lt;/span> &lt;span style="color:#e6db74">&amp;#39;match&lt;/span>)
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> (&lt;span style="color:#a6e22e">put-text-property&lt;/span> (&lt;span style="color:#a6e22e">match-beginning&lt;/span> &lt;span style="color:#ae81ff">0&lt;/span>) (&lt;span style="color:#a6e22e">match-end&lt;/span> &lt;span style="color:#ae81ff">0&lt;/span>)
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#e6db74">&amp;#39;font-lock-face&lt;/span> &lt;span style="color:#e6db74">&amp;#39;match&lt;/span>))))
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#75715e">;; Set up keybindings&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> (local-set-key (kbd &lt;span style="color:#e6db74">&amp;#34;D&amp;#34;&lt;/span>)
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> (lambda ()
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> (interactive)
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> (my/grep my/grep-search-term
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> (read-directory-name &lt;span style="color:#e6db74">&amp;#34;New search directory: &amp;#34;&lt;/span>)
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> my/grep-glob)))
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> (local-set-key (kbd &lt;span style="color:#e6db74">&amp;#34;S&amp;#34;&lt;/span>)
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> (lambda ()
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> (interactive)
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> (my/grep (&lt;span style="color:#a6e22e">read-string&lt;/span> &lt;span style="color:#e6db74">&amp;#34;New search term: &amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#66d9ef">nil&lt;/span> &lt;span style="color:#66d9ef">nil&lt;/span> my/grep-search-term)
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> my/grep-directory
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> my/grep-glob)))
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> (local-set-key (kbd &lt;span style="color:#e6db74">&amp;#34;o&amp;#34;&lt;/span>)
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> (lambda ()
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> (interactive)
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> (my/grep my/grep-search-term
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> my/grep-directory
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> (&lt;span style="color:#a6e22e">read-string&lt;/span> &lt;span style="color:#e6db74">&amp;#34;New glob: &amp;#34;&lt;/span>))))
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> (local-set-key (kbd &lt;span style="color:#e6db74">&amp;#34;g&amp;#34;&lt;/span>)
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> (lambda ()
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> (interactive)
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> (my/grep my/grep-search-term my/grep-directory my/grep-glob)))
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> (&lt;span style="color:#a6e22e">goto-char&lt;/span> (&lt;span style="color:#a6e22e">point-min&lt;/span>))
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> (&lt;span style="color:#a6e22e">message&lt;/span> &lt;span style="color:#e6db74">&amp;#34;ripgrep finished.&amp;#34;&lt;/span>))))
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> )
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> (&lt;span style="color:#a6e22e">message&lt;/span> &lt;span style="color:#e6db74">&amp;#34;ripgrep started...&amp;#34;&lt;/span>))
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#75715e">;; Fallback to rgrep&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> (progn
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> (setq default-directory directory)
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> (&lt;span style="color:#a6e22e">message&lt;/span> (&lt;span style="color:#a6e22e">format&lt;/span> &lt;span style="color:#e6db74">&amp;#34;%s : %s : %s&amp;#34;&lt;/span> search-term glob directory))
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> (rgrep search-term (if (string= &lt;span style="color:#e6db74">&amp;#34;&amp;#34;&lt;/span> glob) &lt;span style="color:#e6db74">&amp;#34;*&amp;#34;&lt;/span> glob) directory)))))
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>That&amp;rsquo;s it. ~100 lines. No dependencies. No packages to manage! (well except ripgrep of course)&lt;/p>
&lt;p>Now that I have complete control over this function, I have added further improvements over rgrep, inspired by &lt;code>deadgrep&lt;/code>&lt;/p>
&lt;ul>
&lt;li>&lt;strong>&lt;code>S&lt;/code>&lt;/strong> - New search term&lt;/li>
&lt;li>&lt;strong>&lt;code>D&lt;/code>&lt;/strong> - New directory&lt;/li>
&lt;li>&lt;strong>&lt;code>o&lt;/code>&lt;/strong> - New glob pattern&lt;/li>
&lt;li>&lt;strong>&lt;code>g&lt;/code>&lt;/strong> - Re-run current search&lt;/li>
&lt;/ul>
&lt;p>and a universal argument can be passed through to set these up on the initial grep&lt;/p>
&lt;p>I have tried to make the output as similar as possible to rgrep, to be compatible with &lt;code>grep-mode&lt;/code> and for familiarity, so it will be something like:&lt;/p>
&lt;pre tabindex="0">&lt;code class="language-nil" data-lang="nil">-*- mode: grep; default-directory: &amp;#34;~/project/&amp;#34; -*-
[o] Glob: *.el
./init.el:42:10:(defun my-function ()
./config.el:156:5: (my-function)
./helpers.el:89:12:;; Helper for my-function
Ripgrep finished
&lt;/code>&lt;/pre>&lt;p>and if a glob is applied it will display the glob pattern.&lt;/p>
&lt;p>Its perfect for offline environments, and yes, I&amp;rsquo;m banging on about this again!, no network, no package manager, no dependencies (except ripgrep of course!)&lt;/p></description></item></channel></rss>