<?xml version="1.0" encoding="utf-8" standalone="yes"?><rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom"><channel><title>Emacs on Emacs@ Dyerdwelling</title><image><url/><title>Emacs on Emacs@ Dyerdwelling</title><link>https://www.emacs.dyerdwelling.family/tags/emacs/</link><width>32</width><height>32</height></image><link>https://www.emacs.dyerdwelling.family/tags/emacs/</link><description>Recent content in Emacs 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/emacs/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><item><title>New package dired-video-thumbnail added to MELPA!</title><link>https://www.emacs.dyerdwelling.family/emacs/20251231183401-emacs--dired-video-thumbnail/</link><pubDate>Wed, 31 Dec 2025 18:34:00 +0000</pubDate><author>James Dyer</author><guid>https://www.emacs.dyerdwelling.family/emacs/20251231183401-emacs--dired-video-thumbnail/</guid><description>&lt;p>I have created another package!, this time something that I thought was missing from the mighty Emacs and that is the ability to show video thumbnails in a grid and to be able to filter, sort e.t.c. Basically like an enhanced &lt;code>image-dired&lt;/code>. I have been increasingly using &lt;code>image-dired&lt;/code> for my image editing and management needs and am always adding little improvements, to such an extent I decided to create a video thumb grid package, enjoy!&lt;/p>
&lt;figure>&lt;img src="https://www.emacs.dyerdwelling.family/emacs/20251231183401-emacs--dired-video-thumbnail.jpg" width="100%">
&lt;/figure>
&lt;h2 id="introduction">Introduction&lt;/h2>
&lt;p>&lt;code>dired-video-thumbnail&lt;/code> is an Emacs package that provides &lt;code>image-dired&lt;/code> style thumbnail viewing for video files. It extracts thumbnails from videos using &lt;code>ffmpeg&lt;/code> and displays them in a grid layout, allowing you to visually browse and manage video collections directly from Emacs.&lt;/p>
&lt;h2 id="features">Features&lt;/h2>
&lt;ul>
&lt;li>&lt;strong>Thumbnail grid display&lt;/strong> - View video thumbnails in a configurable grid layout&lt;/li>
&lt;li>&lt;strong>Persistent caching&lt;/strong> - Thumbnails are cached and only regenerated when the source file changes&lt;/li>
&lt;li>&lt;strong>Async generation&lt;/strong> - Emacs remains responsive while thumbnails are generated in the background&lt;/li>
&lt;li>&lt;strong>Dired integration&lt;/strong> - Marks sync bidirectionally with the associated dired buffer&lt;/li>
&lt;li>&lt;strong>Visual mark indication&lt;/strong> - Marked thumbnails display a coloured border (like &lt;code>image-dired&lt;/code>)&lt;/li>
&lt;li>&lt;strong>Dynamic header line&lt;/strong> - Shows filename, dimensions, duration, and file size for the current video&lt;/li>
&lt;li>&lt;strong>Click to play&lt;/strong> - Open videos in your preferred external player&lt;/li>
&lt;li>&lt;strong>Cross-platform&lt;/strong> - Works on Linux, macOS, and Windows&lt;/li>
&lt;li>&lt;strong>Resizable thumbnails&lt;/strong> - Adjust thumbnail size on the fly&lt;/li>
&lt;li>&lt;strong>Sorting&lt;/strong> - Sort videos by name, date, size, or duration&lt;/li>
&lt;li>&lt;strong>Filtering&lt;/strong> - Filter videos by name pattern, duration range, or file size&lt;/li>
&lt;li>&lt;strong>Recursive search&lt;/strong> - Browse videos across subdirectories with optional auto-recursive mode&lt;/li>
&lt;li>&lt;strong>Transient menu&lt;/strong> - Comprehensive command menu accessible via &lt;code>.&lt;/code> or &lt;code>C-c .&lt;/code>&lt;/li>
&lt;/ul>
&lt;h2 id="whats-new">Whats New&lt;/h2>
&lt;h3 id="0-dot-3-dot-0">&lt;span class="timestamp-wrapper">&lt;span class="timestamp">&amp;lt;2025-12-15 Mon&amp;gt; &lt;/span>&lt;/span> 0.3.0&lt;/h3>
&lt;p>Added transient menu interface&lt;/p>
&lt;p>Introduced a comprehensive transient menu (&lt;code>dired-video-thumbnail-transient&lt;/code>) providing quick access to all commands via &lt;code>.&lt;/code> or &lt;code>C-c .&lt;/code> in the thumbnail buffer. The menu displays current state (sort order, filters, video count, recursive/wrap mode) and organises commands into logical groups: Navigation, Playback, Sorting, Filtering, Marking, Delete, Display, and Other.&lt;/p>
&lt;h3 id="0-dot-2-dot-0">&lt;span class="timestamp-wrapper">&lt;span class="timestamp">&amp;lt;2025-12-15 Mon&amp;gt; &lt;/span>&lt;/span> 0.2.0&lt;/h3>
&lt;p>Enhanced package with sorting, filtering, and docs&lt;/p>
&lt;p>Added sorting and filtering features to &lt;code>dired-video-thumbnail&lt;/code>. Introduced customizable options for sorting and filtering criteria, and implement interactive commands for toggling these settings. Included comprehensive documentation in Texinfo format, covering installation, usage, and customization.&lt;/p>
&lt;h2 id="requirements">Requirements&lt;/h2>
&lt;ul>
&lt;li>Emacs 28.1 or later&lt;/li>
&lt;li>&lt;a href="https://ffmpeg.org/">ffmpeg&lt;/a> and &lt;code>ffprobe&lt;/code> installed and available in your PATH&lt;/li>
&lt;li>&lt;a href="https://github.com/magit/transient">transient&lt;/a> 0.4.0 or later (for the transient menu)&lt;/li>
&lt;/ul>
&lt;h2 id="installation">Installation&lt;/h2>
&lt;h3 id="manual">Manual&lt;/h3>
&lt;p>Download &lt;code>dired-video-thumbnail.el&lt;/code> and place it in your load-path:&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-emacs-lisp" data-lang="emacs-lisp">&lt;span style="display:flex;">&lt;span>(add-to-list &lt;span style="color:#e6db74">&amp;#39;load-path&lt;/span> &lt;span style="color:#e6db74">&amp;#34;/path/to/dired-video-thumbnail/&amp;#34;&lt;/span>)
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>(require &lt;span style="color:#e6db74">&amp;#39;dired-video-thumbnail&lt;/span>)
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;h3 id="use-package">use-package&lt;/h3>
&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-emacs-lisp" data-lang="emacs-lisp">&lt;span style="display:flex;">&lt;span>(use-package dired-video-thumbnail
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> :load-path &lt;span style="color:#e6db74">&amp;#34;/path/to/dired-video-thumbnail/&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> :bind (:map dired-mode-map
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> (&lt;span style="color:#e6db74">&amp;#34;C-t v&amp;#34;&lt;/span> &lt;span style="color:#f92672">.&lt;/span> dired-video-thumbnail)))
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;h3 id="straight-dot-el">straight.el&lt;/h3>
&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-emacs-lisp" data-lang="emacs-lisp">&lt;span style="display:flex;">&lt;span>(straight-use-package
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#f92672">&amp;#39;&lt;/span>(dired-video-thumbnail :type git :host github :repo &lt;span style="color:#e6db74">&amp;#34;captainflasmr/dired-video-thumbnail&amp;#34;&lt;/span>))
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;h2 id="usage">Usage&lt;/h2>
&lt;h3 id="basic-usage">Basic Usage&lt;/h3>
&lt;ol>
&lt;li>Open a directory containing video files in dired&lt;/li>
&lt;li>Run &lt;code>M-x dired-video-thumbnail&lt;/code>&lt;/li>
&lt;li>A new buffer opens displaying thumbnails for all videos in the directory&lt;/li>
&lt;li>The cursor automatically moves to the first thumbnail&lt;/li>
&lt;/ol>
&lt;h3 id="with-marked-files">With Marked Files&lt;/h3>
&lt;ol>
&lt;li>In dired, mark specific video files with &lt;code>m&lt;/code>&lt;/li>
&lt;li>Run &lt;code>M-x dired-video-thumbnail&lt;/code>&lt;/li>
&lt;li>Only thumbnails for the marked videos are displayed&lt;/li>
&lt;/ol>
&lt;h3 id="recursive-mode">Recursive Mode&lt;/h3>
&lt;p>To include videos from subdirectories:&lt;/p>
&lt;ul>
&lt;li>Use &lt;code>C-u M-x dired-video-thumbnail&lt;/code> (with prefix argument)&lt;/li>
&lt;li>Or run &lt;code>M-x dired-video-thumbnail-recursive&lt;/code>&lt;/li>
&lt;li>Or press &lt;code>R&lt;/code> in the thumbnail buffer to toggle recursive mode&lt;/li>
&lt;/ul>
&lt;p>When &lt;code>dired-video-thumbnail-auto-recursive&lt;/code> is enabled (the default), the package automatically searches subdirectories if the current directory contains no video files.&lt;/p>
&lt;h3 id="suggested-keybinding">Suggested Keybinding&lt;/h3>
&lt;p>Add a keybinding in dired for quick access:&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-emacs-lisp" data-lang="emacs-lisp">&lt;span style="display:flex;">&lt;span>(with-eval-after-load &lt;span style="color:#e6db74">&amp;#39;dired&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> (&lt;span style="color:#a6e22e">define-key&lt;/span> dired-mode-map (kbd &lt;span style="color:#e6db74">&amp;#34;C-t v&amp;#34;&lt;/span>) &lt;span style="color:#a6e22e">#&amp;#39;&lt;/span>dired-video-thumbnail))
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;h2 id="transient-menu">Transient Menu&lt;/h2>
&lt;p>Press &lt;code>.&lt;/code> or &lt;code>C-c .&lt;/code> in the thumbnail buffer to open the transient menu. This provides a comprehensive interface to all commands with a live status display.&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>State: Sort: name ↑ | Videos: 42 | Recursive: OFF | Wrap: ON
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>Navigation Playback Sorting Filtering
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>n Next RET Play video s Sort menu... / Filter menu...
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>p Previous o Play video S Interactive sort \ Interactive filter
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>C-n Next row r Reverse order c Clear filters
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>C-p Previous row
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>d Go to dired
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>Marking Delete Display Other
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>m Mark menu... D Delete v Display menu... g Regenerate thumbnail
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>M Mark all x Delete marked+ Larger thumbnails G Regenerate all
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>U Unmark all - Smaller thumbnails C Clear cache
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>t Toggle all marks w Toggle wrap ? Help
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> R Toggle recursive q Quit menu
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> Q Quit buffer
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>The status line at the top shows:&lt;/p>
&lt;ul>
&lt;li>Current sort criteria and direction (e.g., &lt;code>name ↑&lt;/code>)&lt;/li>
&lt;li>Number of videos displayed (and total if filtered)&lt;/li>
&lt;li>Recursive mode status&lt;/li>
&lt;li>Wrap display mode status&lt;/li>
&lt;li>Active filters (if any)&lt;/li>
&lt;/ul>
&lt;h3 id="submenus">Submenus&lt;/h3>
&lt;p>Several keys open submenus with additional options:&lt;/p>
&lt;ul>
&lt;li>&lt;code>s&lt;/code> - &lt;strong>Sort menu&lt;/strong>: Sort by name, date, size, or duration; reverse order&lt;/li>
&lt;li>&lt;code>/&lt;/code> - &lt;strong>Filter menu&lt;/strong>: Filter by name regexp, duration range, or size range; clear filters&lt;/li>
&lt;li>&lt;code>m&lt;/code> - &lt;strong>Mark menu&lt;/strong>: Mark/unmark current, toggle current, mark/unmark/toggle all&lt;/li>
&lt;li>&lt;code>v&lt;/code> - &lt;strong>Display menu&lt;/strong>: Adjust size, toggle wrap/recursive, refresh, regenerate thumbnails&lt;/li>
&lt;/ul>
&lt;h2 id="header-line">Header Line&lt;/h2>
&lt;p>As you navigate between thumbnails, the header line dynamically displays information about the current video:&lt;/p>
&lt;ul>
&lt;li>&lt;strong>Mark indicator&lt;/strong> - A red &lt;code>*&lt;/code> if the video is marked&lt;/li>
&lt;li>&lt;strong>Filename&lt;/strong> - The video filename in bold&lt;/li>
&lt;li>&lt;strong>Dimensions&lt;/strong> - Video resolution (e.g., &lt;code>1920x1080&lt;/code>)&lt;/li>
&lt;li>&lt;strong>Duration&lt;/strong> - Video length (e.g., &lt;code>5:32&lt;/code> or &lt;code>1:23:45&lt;/code>)&lt;/li>
&lt;li>&lt;strong>File size&lt;/strong> - Size in MB (e.g., &lt;code>45.2 MB&lt;/code>)&lt;/li>
&lt;/ul>
&lt;p>The header also shows current sort settings (e.g., &lt;code>[name ↑]&lt;/code>), active filters, and a &lt;code>[recursive]&lt;/code> indicator when browsing subdirectories.&lt;/p>
&lt;h2 id="keybindings">Keybindings&lt;/h2>
&lt;p>In the &lt;code>*Video Thumbnails*&lt;/code> buffer:&lt;/p>
&lt;h3 id="transient-menu">Transient Menu&lt;/h3>
&lt;table>
&lt;thead>
&lt;tr>
&lt;th>Key&lt;/th>
&lt;th>Command&lt;/th>
&lt;th>Description&lt;/th>
&lt;/tr>
&lt;/thead>
&lt;tbody>
&lt;tr>
&lt;td>&lt;code>.&lt;/code>&lt;/td>
&lt;td>&lt;code>dired-video-thumbnail-transient&lt;/code>&lt;/td>
&lt;td>Open transient menu&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>&lt;code>C-c .&lt;/code>&lt;/td>
&lt;td>&lt;code>dired-video-thumbnail-transient&lt;/code>&lt;/td>
&lt;td>Open transient menu&lt;/td>
&lt;/tr>
&lt;/tbody>
&lt;/table>
&lt;h3 id="navigation">Navigation&lt;/h3>
&lt;table>
&lt;thead>
&lt;tr>
&lt;th>Key&lt;/th>
&lt;th>Command&lt;/th>
&lt;th>Description&lt;/th>
&lt;/tr>
&lt;/thead>
&lt;tbody>
&lt;tr>
&lt;td>&lt;code>n&lt;/code>&lt;/td>
&lt;td>&lt;code>dired-video-thumbnail-next&lt;/code>&lt;/td>
&lt;td>Move to next thumbnail&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>&lt;code>p&lt;/code>&lt;/td>
&lt;td>&lt;code>dired-video-thumbnail-previous&lt;/code>&lt;/td>
&lt;td>Move to previous thumbnail&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>&lt;code>SPC&lt;/code>&lt;/td>
&lt;td>&lt;code>dired-video-thumbnail-play&lt;/code>&lt;/td>
&lt;td>Play video at point&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>&lt;code>C-f&lt;/code>&lt;/td>
&lt;td>&lt;code>dired-video-thumbnail-forward&lt;/code>&lt;/td>
&lt;td>Move to next thumbnail&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>&lt;code>C-b&lt;/code>&lt;/td>
&lt;td>&lt;code>dired-video-thumbnail-backward&lt;/code>&lt;/td>
&lt;td>Move to previous thumbnail&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>&lt;code>&amp;lt;right&amp;gt;&lt;/code>&lt;/td>
&lt;td>&lt;code>dired-video-thumbnail-forward&lt;/code>&lt;/td>
&lt;td>Move to next thumbnail&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>&lt;code>&amp;lt;left&amp;gt;&lt;/code>&lt;/td>
&lt;td>&lt;code>dired-video-thumbnail-backward&lt;/code>&lt;/td>
&lt;td>Move to previous thumbnail&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>&lt;code>&amp;lt;up&amp;gt;&lt;/code>&lt;/td>
&lt;td>&lt;code>dired-video-thumbnail-previous-row&lt;/code>&lt;/td>
&lt;td>Move up one row&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>&lt;code>&amp;lt;down&amp;gt;&lt;/code>&lt;/td>
&lt;td>&lt;code>dired-video-thumbnail-next-row&lt;/code>&lt;/td>
&lt;td>Move down one row&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>&lt;code>d&lt;/code>&lt;/td>
&lt;td>&lt;code>dired-video-thumbnail-goto-dired&lt;/code>&lt;/td>
&lt;td>Switch to associated dired buffer&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>&lt;code>q&lt;/code>&lt;/td>
&lt;td>&lt;code>quit-window&lt;/code>&lt;/td>
&lt;td>Close the thumbnail buffer&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>&lt;code>Q&lt;/code>&lt;/td>
&lt;td>&lt;code>dired-video-thumbnail-quit-and-kill&lt;/code>&lt;/td>
&lt;td>Quit and kill the buffer&lt;/td>
&lt;/tr>
&lt;/tbody>
&lt;/table>
&lt;h3 id="playback">Playback&lt;/h3>
&lt;table>
&lt;thead>
&lt;tr>
&lt;th>Key&lt;/th>
&lt;th>Command&lt;/th>
&lt;th>Description&lt;/th>
&lt;/tr>
&lt;/thead>
&lt;tbody>
&lt;tr>
&lt;td>&lt;code>RET&lt;/code>&lt;/td>
&lt;td>&lt;code>dired-video-thumbnail-play&lt;/code>&lt;/td>
&lt;td>Play video at point&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>&lt;code>o&lt;/code>&lt;/td>
&lt;td>&lt;code>dired-video-thumbnail-play&lt;/code>&lt;/td>
&lt;td>Play video at point&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>&lt;code>mouse-1&lt;/code>&lt;/td>
&lt;td>&lt;code>dired-video-thumbnail-play&lt;/code>&lt;/td>
&lt;td>Play video (click)&lt;/td>
&lt;/tr>
&lt;/tbody>
&lt;/table>
&lt;p>On Linux, videos open with &lt;code>xdg-open&lt;/code>. On macOS, they open with &lt;code>open&lt;/code>. On Windows, they open with the system default player. You can also specify a custom player.&lt;/p>
&lt;h3 id="marking">Marking&lt;/h3>
&lt;p>Marks are synchronised with the associated dired buffer, so marking a video in the thumbnail view also marks it in dired, and vice versa.&lt;/p>
&lt;table>
&lt;thead>
&lt;tr>
&lt;th>Key&lt;/th>
&lt;th>Command&lt;/th>
&lt;th>Description&lt;/th>
&lt;/tr>
&lt;/thead>
&lt;tbody>
&lt;tr>
&lt;td>&lt;code>m&lt;/code>&lt;/td>
&lt;td>&lt;code>dired-video-thumbnail-mark&lt;/code>&lt;/td>
&lt;td>Mark video and move to next&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>&lt;code>u&lt;/code>&lt;/td>
&lt;td>&lt;code>dired-video-thumbnail-unmark&lt;/code>&lt;/td>
&lt;td>Unmark video and move to next&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>&lt;code>mouse-3&lt;/code>&lt;/td>
&lt;td>&lt;code>dired-video-thumbnail-toggle-mark&lt;/code>&lt;/td>
&lt;td>Toggle mark (right-click)&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>&lt;code>M&lt;/code>&lt;/td>
&lt;td>&lt;code>dired-video-thumbnail-mark-all&lt;/code>&lt;/td>
&lt;td>Mark all videos&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>&lt;code>U&lt;/code>&lt;/td>
&lt;td>&lt;code>dired-video-thumbnail-unmark-all&lt;/code>&lt;/td>
&lt;td>Unmark all videos&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>&lt;code>t&lt;/code>&lt;/td>
&lt;td>&lt;code>dired-video-thumbnail-toggle-all-marks&lt;/code>&lt;/td>
&lt;td>Invert all marks&lt;/td>
&lt;/tr>
&lt;/tbody>
&lt;/table>
&lt;h3 id="deletion">Deletion&lt;/h3>
&lt;table>
&lt;thead>
&lt;tr>
&lt;th>Key&lt;/th>
&lt;th>Command&lt;/th>
&lt;th>Description&lt;/th>
&lt;/tr>
&lt;/thead>
&lt;tbody>
&lt;tr>
&lt;td>&lt;code>D&lt;/code>&lt;/td>
&lt;td>&lt;code>dired-video-thumbnail-delete&lt;/code>&lt;/td>
&lt;td>Delete video at point (with confirmation)&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>&lt;code>x&lt;/code>&lt;/td>
&lt;td>&lt;code>dired-video-thumbnail-delete-marked&lt;/code>&lt;/td>
&lt;td>Delete marked videos (with confirmation)&lt;/td>
&lt;/tr>
&lt;/tbody>
&lt;/table>
&lt;h3 id="display">Display&lt;/h3>
&lt;table>
&lt;thead>
&lt;tr>
&lt;th>Key&lt;/th>
&lt;th>Command&lt;/th>
&lt;th>Description&lt;/th>
&lt;/tr>
&lt;/thead>
&lt;tbody>
&lt;tr>
&lt;td>&lt;code>+&lt;/code>&lt;/td>
&lt;td>&lt;code>dired-video-thumbnail-increase-size&lt;/code>&lt;/td>
&lt;td>Increase thumbnail size&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>&lt;code>-&lt;/code>&lt;/td>
&lt;td>&lt;code>dired-video-thumbnail-decrease-size&lt;/code>&lt;/td>
&lt;td>Decrease thumbnail size&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>&lt;code>r&lt;/code>&lt;/td>
&lt;td>&lt;code>dired-video-thumbnail-refresh&lt;/code>&lt;/td>
&lt;td>Refresh the display&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>&lt;code>w&lt;/code>&lt;/td>
&lt;td>&lt;code>dired-video-thumbnail-toggle-wrap&lt;/code>&lt;/td>
&lt;td>Toggle wrap mode (flow vs fixed cols)&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>&lt;code>R&lt;/code>&lt;/td>
&lt;td>&lt;code>dired-video-thumbnail-toggle-recursive&lt;/code>&lt;/td>
&lt;td>Toggle recursive directory search&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>&lt;code>g&lt;/code>&lt;/td>
&lt;td>&lt;code>dired-video-thumbnail-regenerate&lt;/code>&lt;/td>
&lt;td>Regenerate thumbnail at point&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>&lt;code>G&lt;/code>&lt;/td>
&lt;td>&lt;code>dired-video-thumbnail-regenerate-all&lt;/code>&lt;/td>
&lt;td>Regenerate all thumbnails&lt;/td>
&lt;/tr>
&lt;/tbody>
&lt;/table>
&lt;h3 id="sorting">Sorting&lt;/h3>
&lt;table>
&lt;thead>
&lt;tr>
&lt;th>Key&lt;/th>
&lt;th>Command&lt;/th>
&lt;th>Description&lt;/th>
&lt;/tr>
&lt;/thead>
&lt;tbody>
&lt;tr>
&lt;td>&lt;code>S&lt;/code>&lt;/td>
&lt;td>&lt;code>dired-video-thumbnail-sort&lt;/code>&lt;/td>
&lt;td>Interactive sort menu&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>&lt;code>sn&lt;/code>&lt;/td>
&lt;td>&lt;code>dired-video-thumbnail-sort-by-name&lt;/code>&lt;/td>
&lt;td>Sort by filename&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>&lt;code>sd&lt;/code>&lt;/td>
&lt;td>&lt;code>dired-video-thumbnail-sort-by-date&lt;/code>&lt;/td>
&lt;td>Sort by modification date&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>&lt;code>ss&lt;/code>&lt;/td>
&lt;td>&lt;code>dired-video-thumbnail-sort-by-size&lt;/code>&lt;/td>
&lt;td>Sort by file size&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>&lt;code>sD&lt;/code>&lt;/td>
&lt;td>&lt;code>dired-video-thumbnail-sort-by-duration&lt;/code>&lt;/td>
&lt;td>Sort by video duration&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>&lt;code>sr&lt;/code>&lt;/td>
&lt;td>&lt;code>dired-video-thumbnail-sort-reverse&lt;/code>&lt;/td>
&lt;td>Reverse sort order&lt;/td>
&lt;/tr>
&lt;/tbody>
&lt;/table>
&lt;h3 id="filtering">Filtering&lt;/h3>
&lt;table>
&lt;thead>
&lt;tr>
&lt;th>Key&lt;/th>
&lt;th>Command&lt;/th>
&lt;th>Description&lt;/th>
&lt;/tr>
&lt;/thead>
&lt;tbody>
&lt;tr>
&lt;td>&lt;code>\&lt;/code>&lt;/td>
&lt;td>&lt;code>dired-video-thumbnail-filter&lt;/code>&lt;/td>
&lt;td>Interactive filter menu&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>&lt;code>/n&lt;/code>&lt;/td>
&lt;td>&lt;code>dired-video-thumbnail-filter-by-name&lt;/code>&lt;/td>
&lt;td>Filter by name regexp&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>&lt;code>/d&lt;/code>&lt;/td>
&lt;td>&lt;code>dired-video-thumbnail-filter-by-duration&lt;/code>&lt;/td>
&lt;td>Filter by duration range&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>&lt;code>/s&lt;/code>&lt;/td>
&lt;td>&lt;code>dired-video-thumbnail-filter-by-size&lt;/code>&lt;/td>
&lt;td>Filter by size range&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>&lt;code>/c&lt;/code>&lt;/td>
&lt;td>&lt;code>dired-video-thumbnail-filter-clear&lt;/code>&lt;/td>
&lt;td>Clear all filters&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>&lt;code>//&lt;/code>&lt;/td>
&lt;td>&lt;code>dired-video-thumbnail-filter-clear&lt;/code>&lt;/td>
&lt;td>Clear all filters&lt;/td>
&lt;/tr>
&lt;/tbody>
&lt;/table>
&lt;h3 id="help">Help&lt;/h3>
&lt;table>
&lt;thead>
&lt;tr>
&lt;th>Key&lt;/th>
&lt;th>Command&lt;/th>
&lt;th>Description&lt;/th>
&lt;/tr>
&lt;/thead>
&lt;tbody>
&lt;tr>
&lt;td>&lt;code>h&lt;/code>&lt;/td>
&lt;td>&lt;code>dired-video-thumbnail-help&lt;/code>&lt;/td>
&lt;td>Show help&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>&lt;code>?&lt;/code>&lt;/td>
&lt;td>&lt;code>dired-video-thumbnail-help&lt;/code>&lt;/td>
&lt;td>Show help&lt;/td>
&lt;/tr>
&lt;/tbody>
&lt;/table>
&lt;h2 id="customisation">Customisation&lt;/h2>
&lt;p>All customisation options are in the &lt;code>dired-video-thumbnail&lt;/code> group. Use &lt;code>M-x customize-group RET dired-video-thumbnail RET&lt;/code> to browse them interactively.&lt;/p>
&lt;h3 id="thumbnail-cache-location">Thumbnail Cache Location&lt;/h3>
&lt;p>Thumbnails are stored in &lt;code>~/.emacs.d/dired-video-thumbnails/&lt;/code> by default:&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-emacs-lisp" data-lang="emacs-lisp">&lt;span style="display:flex;">&lt;span>(setq dired-video-thumbnail-cache-dir &lt;span style="color:#e6db74">&amp;#34;~/path/to/cache/&amp;#34;&lt;/span>)
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;h3 id="thumbnail-size">Thumbnail Size&lt;/h3>
&lt;p>Control the generated thumbnail size and display height:&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-emacs-lisp" data-lang="emacs-lisp">&lt;span style="display:flex;">&lt;span>(setq dired-video-thumbnail-size &lt;span style="color:#ae81ff">200&lt;/span>) &lt;span style="color:#75715e">;; Generated thumbnail size (pixels)&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>(setq dired-video-thumbnail-display-height &lt;span style="color:#ae81ff">150&lt;/span>) &lt;span style="color:#75715e">;; Display height in buffer&lt;/span>
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>Thumbnails are generated as squares to ensure consistent grid alignment regardless of video aspect ratio.&lt;/p>
&lt;h3 id="grid-layout">Grid Layout&lt;/h3>
&lt;p>Set the number of columns in the thumbnail grid:&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-emacs-lisp" data-lang="emacs-lisp">&lt;span style="display:flex;">&lt;span>(setq dired-video-thumbnail-columns &lt;span style="color:#ae81ff">4&lt;/span>)
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;h3 id="wrap-display">Wrap Display&lt;/h3>
&lt;p>Control whether thumbnails wrap to fill the window width:&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-emacs-lisp" data-lang="emacs-lisp">&lt;span style="display:flex;">&lt;span>(setq dired-video-thumbnail-wrap-display &lt;span style="color:#66d9ef">t&lt;/span>) &lt;span style="color:#75715e">;; Wrap to window width (default)&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>(setq dired-video-thumbnail-wrap-display &lt;span style="color:#66d9ef">nil&lt;/span>) &lt;span style="color:#75715e">;; Use fixed columns&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>(setq dired-video-thumbnail-spacing &lt;span style="color:#ae81ff">4&lt;/span>) &lt;span style="color:#75715e">;; Spacing between thumbnails (pixels)&lt;/span>
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;h3 id="thumbnail-timestamp">Thumbnail Timestamp&lt;/h3>
&lt;p>By default, thumbnails are extracted at 5 seconds into the video. Change this to get a more representative frame:&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-emacs-lisp" data-lang="emacs-lisp">&lt;span style="display:flex;">&lt;span>(setq dired-video-thumbnail-timestamp &lt;span style="color:#e6db74">&amp;#34;00:00:10&amp;#34;&lt;/span>) &lt;span style="color:#75715e">;; 10 seconds in&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>(setq dired-video-thumbnail-timestamp &lt;span style="color:#e6db74">&amp;#34;00:01:00&amp;#34;&lt;/span>) &lt;span style="color:#75715e">;; 1 minute in&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>(setq dired-video-thumbnail-timestamp &lt;span style="color:#66d9ef">nil&lt;/span>) &lt;span style="color:#75715e">;; Let ffmpeg choose&lt;/span>
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;h3 id="video-player">Video Player&lt;/h3>
&lt;p>Set your preferred video player:&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-emacs-lisp" data-lang="emacs-lisp">&lt;span style="display:flex;">&lt;span>(setq dired-video-thumbnail-video-player &lt;span style="color:#e6db74">&amp;#34;mpv&amp;#34;&lt;/span>)
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>(setq dired-video-thumbnail-video-player &lt;span style="color:#e6db74">&amp;#34;vlc&amp;#34;&lt;/span>)
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>(setq dired-video-thumbnail-video-player &lt;span style="color:#66d9ef">nil&lt;/span>) &lt;span style="color:#75715e">;; Use system default&lt;/span>
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>When set to &lt;code>nil&lt;/code> (the default), videos open with:&lt;/p>
&lt;ul>
&lt;li>Linux: &lt;code>xdg-open&lt;/code>&lt;/li>
&lt;li>macOS: &lt;code>open&lt;/code>&lt;/li>
&lt;li>Windows: System default player (e.g., Films &amp;amp; TV)&lt;/li>
&lt;/ul>
&lt;h3 id="video-extensions">Video Extensions&lt;/h3>
&lt;p>Add or modify recognised video file extensions:&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-emacs-lisp" data-lang="emacs-lisp">&lt;span style="display:flex;">&lt;span>(setq dired-video-thumbnail-video-extensions
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#f92672">&amp;#39;&lt;/span>(&lt;span style="color:#e6db74">&amp;#34;mp4&amp;#34;&lt;/span> &lt;span style="color:#e6db74">&amp;#34;mkv&amp;#34;&lt;/span> &lt;span style="color:#e6db74">&amp;#34;avi&amp;#34;&lt;/span> &lt;span style="color:#e6db74">&amp;#34;mov&amp;#34;&lt;/span> &lt;span style="color:#e6db74">&amp;#34;webm&amp;#34;&lt;/span> &lt;span style="color:#e6db74">&amp;#34;m4v&amp;#34;&lt;/span> &lt;span style="color:#e6db74">&amp;#34;wmv&amp;#34;&lt;/span> &lt;span style="color:#e6db74">&amp;#34;flv&amp;#34;&lt;/span> &lt;span style="color:#e6db74">&amp;#34;mpeg&amp;#34;&lt;/span> &lt;span style="color:#e6db74">&amp;#34;mpg&amp;#34;&lt;/span> &lt;span style="color:#e6db74">&amp;#34;ogv&amp;#34;&lt;/span> &lt;span style="color:#e6db74">&amp;#34;3gp&amp;#34;&lt;/span>))
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;h3 id="mark-border-appearance">Mark Border Appearance&lt;/h3>
&lt;p>Marked thumbnails are indicated with a coloured border. Customise the border width and colour:&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-emacs-lisp" data-lang="emacs-lisp">&lt;span style="display:flex;">&lt;span>(setq dired-video-thumbnail-mark-border-width &lt;span style="color:#ae81ff">4&lt;/span>) &lt;span style="color:#75715e">;; Border width in pixels&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">;; Change border colour via the face&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>(set-face-foreground &lt;span style="color:#e6db74">&amp;#39;dired-video-thumbnail-mark&lt;/span> &lt;span style="color:#e6db74">&amp;#34;blue&amp;#34;&lt;/span>)
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;h3 id="default-sorting">Default Sorting&lt;/h3>
&lt;p>Set the default sort criteria and order:&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-emacs-lisp" data-lang="emacs-lisp">&lt;span style="display:flex;">&lt;span>(setq dired-video-thumbnail-sort-by &lt;span style="color:#e6db74">&amp;#39;name&lt;/span>) &lt;span style="color:#75715e">;; Options: name, date, size, duration&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>(setq dired-video-thumbnail-sort-order &lt;span style="color:#e6db74">&amp;#39;ascending&lt;/span>) &lt;span style="color:#75715e">;; Options: ascending, descending&lt;/span>
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;h3 id="recursive-search">Recursive Search&lt;/h3>
&lt;p>Control recursive directory searching behaviour:&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-emacs-lisp" data-lang="emacs-lisp">&lt;span style="display:flex;">&lt;span>(setq dired-video-thumbnail-recursive &lt;span style="color:#66d9ef">nil&lt;/span>) &lt;span style="color:#75715e">;; Always search recursively&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>(setq dired-video-thumbnail-auto-recursive &lt;span style="color:#66d9ef">t&lt;/span>) &lt;span style="color:#75715e">;; Auto-recursive when no local videos (default)&lt;/span>
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>When &lt;code>dired-video-thumbnail-auto-recursive&lt;/code> is enabled and the current directory has no video files but has subdirectories, the package automatically searches recursively.&lt;/p>
&lt;h3 id="ffmpeg-path">ffmpeg Path&lt;/h3>
&lt;p>If ffmpeg/ffprobe are not in your PATH:&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-emacs-lisp" data-lang="emacs-lisp">&lt;span style="display:flex;">&lt;span>(setq dired-video-thumbnail-ffmpeg-program &lt;span style="color:#e6db74">&amp;#34;/usr/local/bin/ffmpeg&amp;#34;&lt;/span>)
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>(setq dired-video-thumbnail-ffprobe-program &lt;span style="color:#e6db74">&amp;#34;/usr/local/bin/ffprobe&amp;#34;&lt;/span>)
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;h2 id="example-configuration">Example Configuration&lt;/h2>
&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-emacs-lisp" data-lang="emacs-lisp">&lt;span style="display:flex;">&lt;span>(use-package dired-video-thumbnail
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> :load-path &lt;span style="color:#e6db74">&amp;#34;/path/to/dired-video-thumbnail/&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> :bind (:map dired-mode-map
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> (&lt;span style="color:#e6db74">&amp;#34;C-t v&amp;#34;&lt;/span> &lt;span style="color:#f92672">.&lt;/span> dired-video-thumbnail))
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> :custom
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> (dired-video-thumbnail-size &lt;span style="color:#ae81ff">250&lt;/span>)
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> (dired-video-thumbnail-display-height &lt;span style="color:#ae81ff">180&lt;/span>)
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> (dired-video-thumbnail-columns &lt;span style="color:#ae81ff">5&lt;/span>)
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> (dired-video-thumbnail-timestamp &lt;span style="color:#e6db74">&amp;#34;00:00:10&amp;#34;&lt;/span>)
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> (dired-video-thumbnail-video-player &lt;span style="color:#66d9ef">nil&lt;/span>) &lt;span style="color:#75715e">;; Use system default&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> (dired-video-thumbnail-mark-border-width &lt;span style="color:#ae81ff">5&lt;/span>)
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> (dired-video-thumbnail-sort-by &lt;span style="color:#e6db74">&amp;#39;date&lt;/span>)
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> (dired-video-thumbnail-sort-order &lt;span style="color:#e6db74">&amp;#39;descending&lt;/span>)
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> (dired-video-thumbnail-auto-recursive &lt;span style="color:#66d9ef">t&lt;/span>)
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> :custom-face
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> (dired-video-thumbnail-mark ((&lt;span style="color:#66d9ef">t&lt;/span> (:foreground &lt;span style="color:#e6db74">&amp;#34;orange&amp;#34;&lt;/span>)))))
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;h2 id="cache-management">Cache Management&lt;/h2>
&lt;p>Thumbnails are cached based on the file path and modification time. If you modify a video file, the thumbnail will be automatically regenerated on next view.&lt;/p>
&lt;p>Video metadata (dimensions, duration) is also cached in memory to avoid repeated calls to &lt;code>ffprobe&lt;/code>.&lt;/p>
&lt;p>To manually clear the thumbnail cache:&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-emacs-lisp" data-lang="emacs-lisp">&lt;span style="display:flex;">&lt;span>M-x dired-video-thumbnail-clear-cache
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;h2 id="workflow-examples">Workflow Examples&lt;/h2>
&lt;h3 id="reviewing-and-deleting-unwanted-videos">Reviewing and Deleting Unwanted Videos&lt;/h3>
&lt;ol>
&lt;li>Open a directory with videos in dired&lt;/li>
&lt;li>&lt;code>C-t v&lt;/code> to open thumbnail view&lt;/li>
&lt;li>Browse thumbnails with &lt;code>n&lt;/code>, &lt;code>p&lt;/code>, &lt;code>SPC&lt;/code>, or arrow keys&lt;/li>
&lt;li>Press &lt;code>D&lt;/code> to delete individual videos, or mark with &lt;code>m&lt;/code> and delete with &lt;code>x&lt;/code>&lt;/li>
&lt;/ol>
&lt;h3 id="selecting-videos-for-processing">Selecting Videos for Processing&lt;/h3>
&lt;ol>
&lt;li>Open thumbnail view with &lt;code>C-t v&lt;/code>&lt;/li>
&lt;li>Mark videos you want to process with &lt;code>m&lt;/code>&lt;/li>
&lt;li>Press &lt;code>d&lt;/code> to switch to dired&lt;/li>
&lt;li>Your marked videos are already selected in dired&lt;/li>
&lt;li>Use any dired command (&lt;code>C&lt;/code>, &lt;code>R&lt;/code>, &lt;code>!&lt;/code>, etc.) on marked files&lt;/li>
&lt;/ol>
&lt;h3 id="quick-video-preview">Quick Video Preview&lt;/h3>
&lt;ol>
&lt;li>In dired, position cursor on a video file&lt;/li>
&lt;li>&lt;code>C-t v&lt;/code> opens thumbnail view&lt;/li>
&lt;li>&lt;code>RET&lt;/code> to play the video&lt;/li>
&lt;li>&lt;code>q&lt;/code> to return to dired&lt;/li>
&lt;/ol>
&lt;h3 id="finding-large-videos">Finding Large Videos&lt;/h3>
&lt;ol>
&lt;li>Open thumbnail view with &lt;code>C-t v&lt;/code>&lt;/li>
&lt;li>Press &lt;code>.&lt;/code> to open the transient menu&lt;/li>
&lt;li>Press &lt;code>s&lt;/code> then &lt;code>s&lt;/code> to sort by size&lt;/li>
&lt;li>Press &lt;code>r&lt;/code> to reverse order (largest first)&lt;/li>
&lt;li>Or use &lt;code>/&lt;/code> then &lt;code>s&lt;/code> to filter by size range&lt;/li>
&lt;/ol>
&lt;h3 id="finding-long-videos">Finding Long Videos&lt;/h3>
&lt;ol>
&lt;li>Press &lt;code>.&lt;/code> to open the transient menu&lt;/li>
&lt;li>Press &lt;code>s&lt;/code> then &lt;code>D&lt;/code> to sort by duration&lt;/li>
&lt;li>Or use &lt;code>/&lt;/code> then &lt;code>d&lt;/code> to filter by duration range (e.g., &lt;code>5:00&lt;/code> to &lt;code>30:00&lt;/code>)&lt;/li>
&lt;/ol>
&lt;h3 id="searching-by-name">Searching by Name&lt;/h3>
&lt;ol>
&lt;li>Press &lt;code>.&lt;/code> to open the transient menu&lt;/li>
&lt;li>Press &lt;code>/&lt;/code> then &lt;code>n&lt;/code> and enter a regexp pattern&lt;/li>
&lt;li>Only matching videos are shown&lt;/li>
&lt;li>Press &lt;code>c&lt;/code> to clear the filter&lt;/li>
&lt;/ol>
&lt;h2 id="troubleshooting">Troubleshooting&lt;/h2>
&lt;h3 id="thumbnails-not-generating">Thumbnails not generating&lt;/h3>
&lt;ol>
&lt;li>Ensure ffmpeg is installed: &lt;code>ffmpeg -version&lt;/code>&lt;/li>
&lt;li>Check that ffmpeg is in your PATH or set &lt;code>dired-video-thumbnail-ffmpeg-program&lt;/code>&lt;/li>
&lt;li>Try regenerating with &lt;code>g&lt;/code> on a specific thumbnail&lt;/li>
&lt;/ol>
&lt;h3 id="placeholder-showing-instead-of-thumbnail">Placeholder showing instead of thumbnail&lt;/h3>
&lt;p>Some videos may fail to generate thumbnails if:&lt;/p>
&lt;ul>
&lt;li>The video is corrupted&lt;/li>
&lt;li>The timestamp is beyond the video duration (try setting &lt;code>dired-video-thumbnail-timestamp&lt;/code> to &lt;code>nil&lt;/code>)&lt;/li>
&lt;li>ffmpeg doesn&amp;rsquo;t support the codec&lt;/li>
&lt;/ul>
&lt;p>Press &lt;code>g&lt;/code> on the thumbnail to retry generation.&lt;/p>
&lt;h3 id="video-info-not-showing-in-header-line">Video info not showing in header line&lt;/h3>
&lt;p>Ensure &lt;code>ffprobe&lt;/code> is installed (it comes with ffmpeg). Set &lt;code>dired-video-thumbnail-ffprobe-program&lt;/code> if it&amp;rsquo;s not in your PATH.&lt;/p>
&lt;h3 id="marks-not-syncing-with-dired">Marks not syncing with dired&lt;/h3>
&lt;p>Run &lt;code>M-x dired-video-thumbnail-debug&lt;/code> to check if the dired buffer is properly associated. The output should show a live dired buffer reference.&lt;/p>
&lt;h3 id="performance-with-many-videos">Performance with many videos&lt;/h3>
&lt;p>The package processes up to 4 videos concurrently by default. For directories with hundreds of videos, initial thumbnail generation may take some time, but Emacs remains responsive and thumbnails appear as they complete.&lt;/p>
&lt;h2 id="related-packages">Related Packages&lt;/h2>
&lt;ul>
&lt;li>&lt;a href="https://www.gnu.org/software/emacs/manual/html_node/emacs/Image_002dDired.html">image-dired&lt;/a> - Built-in image thumbnail browser for dired&lt;/li>
&lt;li>&lt;a href="https://github.com/alexluigit/dirvish">dirvish&lt;/a> - A modern file manager for Emacs with preview support&lt;/li>
&lt;/ul></description></item><item><title>Setting Up Emacs for C# Development on Windows</title><link>https://www.emacs.dyerdwelling.family/emacs/20251216082551-emacs--setting-up-emacs-for-c%23-development-on-windows/</link><pubDate>Tue, 16 Dec 2025 08:25:00 +0000</pubDate><author>James Dyer</author><guid>https://www.emacs.dyerdwelling.family/emacs/20251216082551-emacs--setting-up-emacs-for-c%23-development-on-windows/</guid><description>&lt;h2 id="introduction">Introduction&lt;/h2>
&lt;p>I have been developing C# with .NET 9.0 for the last year on Windows and I thought it was probably time to write down my current setup, and maybe someone might even find this useful!&lt;/p>
&lt;p>So, this guide documents my setup for running Emacs 30.1 on Windows with full C# development support, including LSP, debugging (through DAPE), and all the ancillary tools you&amp;rsquo;d expect from a modern development environment. The setup is designed to be portable and self-contained, which is particularly useful in air-gapped or restricted environments.&lt;/p>
&lt;p>A version of this can be found at &lt;a href="https://github.com/captainflasmr/Emacs-on-windows">https://github.com/captainflasmr/Emacs-on-windows&lt;/a> which will be a living continually updated version!&lt;/p>
&lt;figure>&lt;img src="https://www.emacs.dyerdwelling.family/emacs/20251216082551-emacs--Setting-Up-Emacs-for-Csharp-Development-on-Windows.jpg" width="100%">
&lt;/figure>
&lt;h2 id="prerequisites">Prerequisites&lt;/h2>
&lt;p>Before we begin, you&amp;rsquo;ll need:&lt;/p>
&lt;ul>
&lt;li>&lt;strong>Windows 10 or 11&lt;/strong> (64-bit)&lt;/li>
&lt;li>&lt;strong>.NET 9.0 SDK&lt;/strong> - Required for csharp-ls and building .NET projects&lt;/li>
&lt;li>&lt;strong>Visual Studio 2022&lt;/strong> (optional) - Useful for MSBuild and if you need the full IDE occasionally&lt;/li>
&lt;li>&lt;strong>Administrator access&lt;/strong> - For initial setup only&lt;/li>
&lt;/ul>
&lt;p>You can verify your .NET installation by opening a command prompt and running:&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-shell" data-lang="shell">&lt;span style="display:flex;">&lt;span>dotnet --version
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>If you see version 9.0.x or later, you&amp;rsquo;re ready to proceed.&lt;/p>
&lt;h2 id="the-big-picture">The Big Picture&lt;/h2>
&lt;p>Here&amp;rsquo;s what we&amp;rsquo;re building:&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>D:\source\emacs-30.1\
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>├── bin\
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>│ ├── emacs.exe, runemacs.exe, etc.
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>│ ├── PortableGit\ # Git for version control
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>│ ├── Apache-Subversion\ # SVN (if needed)
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>│ ├── csharp-ls\ # C# Language Server
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>│ ├── netcoredbg\ # .NET debugger
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>│ ├── omnisharp-win-x64\ # Alternative C# LSP
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>│ ├── hunspell\ # Spell checking
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>│ ├── find\ # ripgrep for fast searching
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>│ ├── ffmpeg-7.1.1-.../ # Video processing
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>│ └── ImageMagick-.../ # Image processing
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>└── share\
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> └── emacs\...
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>The key insight here is keeping everything within the Emacs installation directory. This makes the whole setup portable—you can copy it to another machine or keep it on a USB drive.&lt;/p>
&lt;h2 id="step-1-installing-emacs">Step 1: Installing Emacs&lt;/h2>
&lt;p>Download Emacs 30.1 from the &lt;a href="https://www.gnu.org/software/emacs/download.html">GNU Emacs download page&lt;/a>. For Windows, grab the installer or the zip archive.&lt;/p>
&lt;p>I install to an external drive &lt;code>D:\source\emacs-30.1&lt;/code> rather than Program Files—it avoids permission issues and keeps everything in one place.&lt;/p>
&lt;p>Test your installation by running &lt;code>bin\runemacs.exe&lt;/code>. You should see a fresh Emacs frame.&lt;/p>
&lt;h2 id="step-2-setting-up-csharp-ls--the-c-language-server">Step 2: Setting Up csharp-ls (The C# Language Server)&lt;/h2>
&lt;p>This is the heart of the C# development experience. &lt;code>csharp-ls&lt;/code> provides code completion, go-to-definition, find references, diagnostics, and more through the Language Server Protocol (LSP).&lt;/p>
&lt;h3 id="option-a-installing-via-dotnet-tool--recommended-for-internet-access">Option A: Installing via dotnet tool (Recommended for Internet Access)&lt;/h3>
&lt;p>If you have internet access, the easiest way to install csharp-ls is as a .NET global tool:&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-shell" data-lang="shell">&lt;span style="display:flex;">&lt;span>&lt;span style="color:#75715e"># Install the latest version globally&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>dotnet tool install --global csharp-ls
&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"># Or install a specific version&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>dotnet tool install --global csharp-ls --version 0.20.0
&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"># Verify installation&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>csharp-ls --version
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>By default, global tools are installed to:&lt;/p>
&lt;ul>
&lt;li>Windows: &lt;code>%USERPROFILE%\.dotnet\tools&lt;/code>&lt;/li>
&lt;/ul>
&lt;p>The executable will be &lt;code>csharp-ls.exe&lt;/code> and can be called directly once the tools directory is in your PATH.&lt;/p>
&lt;h3 id="option-b-offline-installation-via-nuget-package">Option B: Offline Installation via NuGet Package&lt;/h3>
&lt;p>For air-gapped environments, you can download the NuGet package and extract it manually:&lt;/p>
&lt;ol>
&lt;li>
&lt;p>On a machine with internet, download the package:&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-shell" data-lang="shell">&lt;span style="display:flex;">&lt;span> &lt;span style="color:#75715e"># Download the nupkg file&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> nuget install csharp-ls -Version 0.20.0 -OutputDirectory ./packages
&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"># Or download directly from NuGet Gallery:&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#75715e"># https://www.nuget.org/packages/csharp-ls/&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#75715e"># Click &amp;#34;Download package&amp;#34; on the right side&lt;/span>
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;/li>
&lt;li>
&lt;p>The &lt;code>.nupkg&lt;/code> file is just a ZIP archive. Extract it:&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-shell" data-lang="shell">&lt;span style="display:flex;">&lt;span> &lt;span style="color:#75715e"># Rename to .zip and extract, or use 7-Zip&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#75715e"># The DLLs are in tools/net9.0/any/&lt;/span>
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;/li>
&lt;li>
&lt;p>Copy the &lt;code>tools/net9.0/any/&lt;/code> directory to your Emacs bin:&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-shell" data-lang="shell">&lt;span style="display:flex;">&lt;span> xcopy /E packages&lt;span style="color:#ae81ff">\c&lt;/span>sharp-ls.0.20.0&lt;span style="color:#ae81ff">\t&lt;/span>ools&lt;span style="color:#ae81ff">\n&lt;/span>et9.0&lt;span style="color:#ae81ff">\a&lt;/span>ny D:&lt;span style="color:#ae81ff">\s&lt;/span>ource&lt;span style="color:#ae81ff">\e&lt;/span>macs-30.1&lt;span style="color:#ae81ff">\b&lt;/span>in&lt;span style="color:#ae81ff">\c&lt;/span>sharp-ls&lt;span style="color:#ae81ff">\
&lt;/span>&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;/li>
&lt;li>
&lt;p>The language server is now at:
&lt;code>D:\source\emacs-30.1\bin\csharp-ls\CSharpLanguageServer.dll&lt;/code>&lt;/p>
&lt;/li>
&lt;/ol>
&lt;h3 id="configuring-eglot-for-csharp-ls">Configuring Eglot for csharp-ls&lt;/h3>
&lt;p>In your &lt;code>init.el&lt;/code>, configure Eglot to use csharp-ls:&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-emacs-lisp" data-lang="emacs-lisp">&lt;span style="display:flex;">&lt;span>(require &lt;span style="color:#e6db74">&amp;#39;eglot&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">;; Option A: If installed as a global tool&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>(setq eglot-server-programs
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#f92672">&amp;#39;&lt;/span>((csharp-mode &lt;span style="color:#f92672">.&lt;/span> (&lt;span style="color:#e6db74">&amp;#34;csharp-ls&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">;; Option B: If running from extracted DLL&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>(setq eglot-server-programs
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#f92672">&amp;#39;&lt;/span>((csharp-mode &lt;span style="color:#f92672">.&lt;/span> (&lt;span style="color:#e6db74">&amp;#34;dotnet&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#e6db74">&amp;#34;D:/source/emacs-30.1/bin/csharp-ls/CSharpLanguageServer.dll&amp;#34;&lt;/span>))))
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>I also have the following commented out if there are some eglot functions that causes slowdowns or I just think I don&amp;rsquo;t need:&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 eglot-ignored-server-capabilities
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#f92672">&amp;#39;&lt;/span>(
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#75715e">;; :hoverProvider ; Documentation on hover&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#75715e">;; :completionProvider ; Code completion&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#75715e">;; :signatureHelpProvider ; Function signature help&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#75715e">;; :definitionProvider ; Go to definition&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#75715e">;; :typeDefinitionProvider ; Go to type definition&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#75715e">;; :implementationProvider ; Go to implementation&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#75715e">;; :declarationProvider ; Go to declaration&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#75715e">;; :referencesProvider ; Find references&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#75715e">;; :documentHighlightProvider ; Highlight symbols automatically&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#75715e">;; :documentSymbolProvider ; List symbols in buffer&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#75715e">;; :workspaceSymbolProvider ; List symbols in workspace&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#75715e">;; :codeActionProvider ; Execute code actions&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#75715e">;; :codeLensProvider ; Code lens&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#75715e">;; :documentFormattingProvider ; Format buffer&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#75715e">;; :documentRangeFormattingProvider ; Format portion of buffer&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#75715e">;; :documentOnTypeFormattingProvider ; On-type formatting&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#75715e">;; :renameProvider ; Rename symbol&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#75715e">;; :documentLinkProvider ; Highlight links in document&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#75715e">;; :colorProvider ; Decorate color references&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#75715e">;; :foldingRangeProvider ; Fold regions of buffer&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#75715e">;; :executeCommandProvider ; Execute custom commands&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#75715e">;; :inlayHintProvider ; Inlay hints&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> ))
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;h2 id="step-3-setting-up-the-debugger--netcoredbg">Step 3: Setting Up the Debugger (netcoredbg)&lt;/h2>
&lt;p>For debugging .NET applications, we&amp;rsquo;ll use &lt;code>netcoredbg&lt;/code>, which implements the Debug Adapter Protocol (DAP).&lt;/p>
&lt;h3 id="installing-netcoredbg">Installing netcoredbg&lt;/h3>
&lt;ol>
&lt;li>Download from &lt;a href="https://github.com/Samsung/netcoredbg/releases">Samsung&amp;rsquo;s GitHub releases&lt;/a>&lt;/li>
&lt;li>Extract to &lt;code>D:\source\emacs-30.1\bin\netcoredbg\&lt;/code>&lt;/li>
&lt;li>Verify: &lt;code>netcoredbg.exe --version&lt;/code>&lt;/li>
&lt;/ol>
&lt;h3 id="configuring-dape-for-debugging">Configuring dape for Debugging&lt;/h3>
&lt;p>&lt;code>dape&lt;/code> is an excellent DAP client for Emacs. Here&amp;rsquo;s my configuration:&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-emacs-lisp" data-lang="emacs-lisp">&lt;span style="display:flex;">&lt;span>(use-package dape
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> :load-path &lt;span style="color:#e6db74">&amp;#34;z:/SharedVM/source/dape-master&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> :init
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#75715e">;; Set key prefix BEFORE loading dape&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> (setq dape-key-prefix (kbd &lt;span style="color:#e6db74">&amp;#34;C-c d&amp;#34;&lt;/span>))
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> :config
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#75715e">;; Define common configuration&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> (defvar project-netcoredbg-path &lt;span style="color:#e6db74">&amp;#34;d:/source/emacs-30.1/bin/netcoredbg/netcoredbg.exe&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#e6db74">&amp;#34;Path to netcoredbg executable.&amp;#34;&lt;/span>)
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> (defvar project-netcoredbg-log &lt;span style="color:#e6db74">&amp;#34;d:/source/emacs-30.1/bin/netcoredbg/netcoredbg.log&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#e6db74">&amp;#34;Path to netcoredbg log file.&amp;#34;&lt;/span>)
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> (defvar project-project-root &lt;span style="color:#e6db74">&amp;#34;d:/source/PROJECT&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#e6db74">&amp;#34;Root directory of PROJECT project.&amp;#34;&lt;/span>)
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> (defvar project-build-config &lt;span style="color:#e6db74">&amp;#34;Debug&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#e6db74">&amp;#34;Build configuration (Debug or Release).&amp;#34;&lt;/span>)
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> (defvar project-target-arch &lt;span style="color:#e6db74">&amp;#34;x64&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#e6db74">&amp;#34;Target architecture (x64, x86, or AnyCPU).&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">;; Helper function to create component configs&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> (defun project-dape-config (component-name dll-name &lt;span style="color:#66d9ef">&amp;amp;optional&lt;/span> stop-at-entry)
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#e6db74">&amp;#34;Create a dape configuration for a component.
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#e6db74">COMPONENT-NAME is the component directory name
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#e6db74">DLL-NAME is the DLL filename without extension.
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#e6db74">STOP-AT-ENTRY if non-nil, stops at program entry point.&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> (let* ((component-dir (&lt;span style="color:#a6e22e">format&lt;/span> &lt;span style="color:#e6db74">&amp;#34;%s/%s&amp;#34;&lt;/span> project-project-root component-name))
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> (bin-path (&lt;span style="color:#a6e22e">format&lt;/span> &lt;span style="color:#e6db74">&amp;#34;%s/bin/%s/%s/net9.0&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> component-dir
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> project-target-arch
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> project-build-config))
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> (dll-path (&lt;span style="color:#a6e22e">format&lt;/span> &lt;span style="color:#e6db74">&amp;#34;%s/%s.dll&amp;#34;&lt;/span> bin-path dll-name))
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> (config-name (&lt;span style="color:#a6e22e">intern&lt;/span> (&lt;span style="color:#a6e22e">format&lt;/span> &lt;span style="color:#e6db74">&amp;#34;netcoredbg-launch-%s&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> (&lt;span style="color:#a6e22e">downcase&lt;/span> component-name)))))
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#f92672">`&lt;/span>(&lt;span style="color:#f92672">,&lt;/span>config-name
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> modes (csharp-mode csharp-ts-mode)
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> command &lt;span style="color:#f92672">,&lt;/span>project-netcoredbg-path
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> command-args (&lt;span style="color:#f92672">,&lt;/span>(&lt;span style="color:#a6e22e">format&lt;/span> &lt;span style="color:#e6db74">&amp;#34;--interpreter=vscode&amp;#34;&lt;/span>)
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#f92672">,&lt;/span>(&lt;span style="color:#a6e22e">format&lt;/span> &lt;span style="color:#e6db74">&amp;#34;--engineLogging=%s&amp;#34;&lt;/span> project-netcoredbg-log))
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> normalize-path-separator &lt;span style="color:#e6db74">&amp;#39;windows&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> :type &lt;span style="color:#e6db74">&amp;#34;coreclr&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> :request &lt;span style="color:#e6db74">&amp;#34;launch&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> :program &lt;span style="color:#f92672">,&lt;/span>dll-path
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> :cwd &lt;span style="color:#f92672">,&lt;/span>component-dir
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> :console &lt;span style="color:#e6db74">&amp;#34;externalTerminal&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> :internalConsoleOptions &lt;span style="color:#e6db74">&amp;#34;neverOpen&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> :suppressJITOptimizations &lt;span style="color:#66d9ef">t&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> :requireExactSource &lt;span style="color:#66d9ef">nil&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> :justMyCode &lt;span style="color:#66d9ef">t&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> :stopAtEntry &lt;span style="color:#f92672">,&lt;/span>(if stop-at-entry &lt;span style="color:#66d9ef">t&lt;/span> :json-false))))
&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">;; Register all component configurations&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> (dolist (config (&lt;span style="color:#a6e22e">list&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> (project-dape-config &lt;span style="color:#e6db74">&amp;#34;DM&amp;#34;&lt;/span> &lt;span style="color:#e6db74">&amp;#34;DM.MSS&amp;#34;&lt;/span> &lt;span style="color:#66d9ef">t&lt;/span>)
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> (project-dape-config &lt;span style="color:#e6db74">&amp;#34;Demo&amp;#34;&lt;/span> &lt;span style="color:#e6db74">&amp;#34;Demo.MSS&amp;#34;&lt;/span> &lt;span style="color:#66d9ef">t&lt;/span>)
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> (project-dape-config &lt;span style="color:#e6db74">&amp;#34;Test_001&amp;#34;&lt;/span> &lt;span style="color:#e6db74">&amp;#34;Test&amp;#34;&lt;/span> &lt;span style="color:#66d9ef">t&lt;/span>)))
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> (add-to-list &lt;span style="color:#e6db74">&amp;#39;dape-configs&lt;/span> config))
&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 buffer arrangement and other options&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> (setq dape-buffer-window-arrangement &lt;span style="color:#e6db74">&amp;#39;gud&lt;/span>)
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> (setq dape-debug &lt;span style="color:#66d9ef">t&lt;/span>)
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> (setq dape-repl-echo-shell-output &lt;span style="color:#66d9ef">t&lt;/span>))
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>Now you can start debugging with &lt;code>M-x dape&lt;/code> and selecting your configuration.&lt;/p>
&lt;h2 id="step-4-installing-supporting-tools">Step 4: Installing Supporting Tools&lt;/h2>
&lt;h3 id="portable-git">Portable Git&lt;/h3>
&lt;ol>
&lt;li>Download &lt;code>PortableGit-2.50.0-64-bit.7z.exe&lt;/code> from &lt;a href="https://git-scm.com/download/win">git-scm.com&lt;/a>&lt;/li>
&lt;li>Run and extract to &lt;code>D:\source\emacs-30.1\bin\PortableGit\&lt;/code>&lt;/li>
&lt;/ol>
&lt;p>This gives you &lt;code>git.exe&lt;/code>, &lt;code>bash.exe&lt;/code>, and a whole Unix-like environment.&lt;/p>
&lt;h3 id="ripgrep--fast-searching">ripgrep (Fast Searching)&lt;/h3>
&lt;ol>
&lt;li>Download from &lt;a href="https://github.com/BurntSushi/ripgrep/releases">ripgrep releases&lt;/a>&lt;/li>
&lt;li>Extract &lt;code>rg.exe&lt;/code> to &lt;code>D:\source\emacs-30.1\bin\find\&lt;/code>&lt;/li>
&lt;/ol>
&lt;p>ripgrep is dramatically faster than grep for searching codebases.&lt;/p>
&lt;h3 id="hunspell--spell-checking">Hunspell (Spell Checking)&lt;/h3>
&lt;ol>
&lt;li>Download &lt;code>hunspell-1.3.2-3-w32-bin.zip&lt;/code>&lt;/li>
&lt;li>Extract to &lt;code>D:\source\emacs-30.1\bin\hunspell\&lt;/code>&lt;/li>
&lt;li>Download dictionary files (&lt;code>en_GB.dic&lt;/code> and &lt;code>en_GB.aff&lt;/code>) and place in &lt;code>hunspell\share\hunspell\&lt;/code>&lt;/li>
&lt;/ol>
&lt;h3 id="imagemagick--image-processing">ImageMagick (Image Processing)&lt;/h3>
&lt;ol>
&lt;li>Download the portable Q16 x64 version from &lt;a href="https://imagemagick.org/script/download.php">imagemagick.org&lt;/a>&lt;/li>
&lt;li>Extract to &lt;code>D:\source\emacs-30.1\bin\ImageMagick-7.1.2-9-portable-Q16-x64\&lt;/code>&lt;/li>
&lt;/ol>
&lt;p>This enables &lt;code>image-dired&lt;/code> thumbnail generation.&lt;/p>
&lt;h3 id="ffmpeg--video-processing">FFmpeg (Video Processing)&lt;/h3>
&lt;ol>
&lt;li>Download from &lt;a href="https://ffmpeg.org/download.html">ffmpeg.org&lt;/a> (essentials build is fine)&lt;/li>
&lt;li>Extract to &lt;code>D:\source\emacs-30.1\bin\ffmpeg-7.1.1-essentials_build\&lt;/code>&lt;/li>
&lt;/ol>
&lt;p>Useful for video thumbnails in dired and media processing.&lt;/p>
&lt;h2 id="step-5-configuring-the-path">Step 5: Configuring the PATH&lt;/h2>
&lt;p>This is crucial—Emacs needs to find all these tools. Here&amp;rsquo;s the PATH configuration from my &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-emacs-lisp" data-lang="emacs-lisp">&lt;span style="display:flex;">&lt;span>(when (&lt;span style="color:#a6e22e">eq&lt;/span> system-type &lt;span style="color:#e6db74">&amp;#39;windows-nt&lt;/span>)
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> (let* ((emacs-bin &lt;span style="color:#e6db74">&amp;#34;d:/source/emacs-30.1/bin&amp;#34;&lt;/span>)
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> (xPaths
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#f92672">`&lt;/span>(&lt;span style="color:#f92672">,&lt;/span>emacs-bin
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#f92672">,&lt;/span>(&lt;span style="color:#a6e22e">concat&lt;/span> emacs-bin &lt;span style="color:#e6db74">&amp;#34;/PortableGit/bin&amp;#34;&lt;/span>)
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#f92672">,&lt;/span>(&lt;span style="color:#a6e22e">concat&lt;/span> emacs-bin &lt;span style="color:#e6db74">&amp;#34;/PortableGit/usr/bin&amp;#34;&lt;/span>)
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#f92672">,&lt;/span>(&lt;span style="color:#a6e22e">concat&lt;/span> emacs-bin &lt;span style="color:#e6db74">&amp;#34;/hunspell/bin&amp;#34;&lt;/span>)
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#f92672">,&lt;/span>(&lt;span style="color:#a6e22e">concat&lt;/span> emacs-bin &lt;span style="color:#e6db74">&amp;#34;/find&amp;#34;&lt;/span>)
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#f92672">,&lt;/span>(&lt;span style="color:#a6e22e">concat&lt;/span> emacs-bin &lt;span style="color:#e6db74">&amp;#34;/netcoredbg&amp;#34;&lt;/span>)
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#f92672">,&lt;/span>(&lt;span style="color:#a6e22e">concat&lt;/span> emacs-bin &lt;span style="color:#e6db74">&amp;#34;/csharp-ls/tools/net9.0/any&amp;#34;&lt;/span>)
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#f92672">,&lt;/span>(&lt;span style="color:#a6e22e">concat&lt;/span> emacs-bin &lt;span style="color:#e6db74">&amp;#34;/ffmpeg-7.1.1-essentials_build/bin&amp;#34;&lt;/span>)
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#f92672">,&lt;/span>(&lt;span style="color:#a6e22e">concat&lt;/span> emacs-bin &lt;span style="color:#e6db74">&amp;#34;/ImageMagick-7.1.2-9-portable-Q16-x64&amp;#34;&lt;/span>)))
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> (winPaths (getenv &lt;span style="color:#e6db74">&amp;#34;PATH&amp;#34;&lt;/span>)))
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> (setenv &lt;span style="color:#e6db74">&amp;#34;PATH&amp;#34;&lt;/span> (&lt;span style="color:#a6e22e">concat&lt;/span> (&lt;span style="color:#a6e22e">mapconcat&lt;/span> &lt;span style="color:#e6db74">&amp;#39;identity&lt;/span> xPaths &lt;span style="color:#e6db74">&amp;#34;;&amp;#34;&lt;/span>) &lt;span style="color:#e6db74">&amp;#34;;&amp;#34;&lt;/span> winPaths))
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> (setq exec-path (&lt;span style="color:#a6e22e">append&lt;/span> xPaths (parse-colon-path winPaths)))))
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;h2 id="step-6-installing-emacs-packages">Step 6: Installing Emacs Packages&lt;/h2>
&lt;p>Extract these to a shared location or download from MELPA&lt;/p>
&lt;table>
&lt;thead>
&lt;tr>
&lt;th>Package&lt;/th>
&lt;th>Purpose&lt;/th>
&lt;/tr>
&lt;/thead>
&lt;tbody>
&lt;tr>
&lt;td>corfu&lt;/td>
&lt;td>Modern completion UI&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>dape&lt;/td>
&lt;td>Debug Adapter Protocol client&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>highlight-indent-guides&lt;/td>
&lt;td>Visual indentation guides&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>ztree&lt;/td>
&lt;td>Directory tree comparison&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>web-mode&lt;/td>
&lt;td>Web template editing&lt;/td>
&lt;/tr>
&lt;/tbody>
&lt;/table>
&lt;p>Example package configuration:&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-emacs-lisp" data-lang="emacs-lisp">&lt;span style="display:flex;">&lt;span>(use-package corfu
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> :load-path &lt;span style="color:#e6db74">&amp;#34;z:/SharedVM/source/corfu-main&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> :custom
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> (corfu-auto &lt;span style="color:#66d9ef">nil&lt;/span>) &lt;span style="color:#75715e">; Manual completion trigger&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> (corfu-cycle &lt;span style="color:#66d9ef">t&lt;/span>) &lt;span style="color:#75715e">; Cycle through candidates&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> (corfu-preselect &lt;span style="color:#e6db74">&amp;#39;first&lt;/span>))
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>(use-package ztree
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> :load-path &lt;span style="color:#e6db74">&amp;#34;z:/SharedVM/source/ztree&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> :config
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> (setq ztree-diff-filter-list
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#f92672">&amp;#39;&lt;/span>(&lt;span style="color:#e6db74">&amp;#34;build&amp;#34;&lt;/span> &lt;span style="color:#e6db74">&amp;#34;\\.dll&amp;#34;&lt;/span> &lt;span style="color:#e6db74">&amp;#34;\\.git&amp;#34;&lt;/span> &lt;span style="color:#e6db74">&amp;#34;bin&amp;#34;&lt;/span> &lt;span style="color:#e6db74">&amp;#34;obj&amp;#34;&lt;/span>))
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> (global-set-key (kbd &lt;span style="color:#e6db74">&amp;#34;C-c z d&amp;#34;&lt;/span>) &lt;span style="color:#e6db74">&amp;#39;ztree-diff&lt;/span>))
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>(use-package web-mode
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> :load-path &lt;span style="color:#e6db74">&amp;#34;z:/SharedVM/source/web-mode-master&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> :mode &lt;span style="color:#e6db74">&amp;#34;\\.cshtml?\\&amp;#39;&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> :hook (html-mode &lt;span style="color:#f92672">.&lt;/span> web-mode)
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> :bind (:map web-mode-map (&lt;span style="color:#e6db74">&amp;#34;M-;&amp;#34;&lt;/span> &lt;span style="color:#f92672">.&lt;/span> &lt;span style="color:#66d9ef">nil&lt;/span>)))
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>Note that I turn off autocomplete for corfu and complete using &lt;code>complete-symbol&lt;/code> manually, otherwise the LSP is constantly accessed with slowdown.&lt;/p>
&lt;p>I often use &lt;code>Meld&lt;/code> but am currently am looking to adapt &lt;code>ztree&lt;/code> to perform better for directory comparisons.&lt;/p>
&lt;p>Web-mode is the best package I have found for html type file navigation and folding, very useful when developing Razor pages for example.&lt;/p>
&lt;h2 id="step-7-auto-open-file-modes">Step 7: auto open file modes&lt;/h2>
&lt;p>Of course running and building in windows means in Emacs probably having to open .csproj files from time to time, well &lt;code>nxml-mode&lt;/code> comes in useful for this:&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>(add-to-list &lt;span style="color:#e6db74">&amp;#39;auto-mode-alist&lt;/span> &lt;span style="color:#f92672">&amp;#39;&lt;/span>(&lt;span style="color:#e6db74">&amp;#34;\\.csproj\\&amp;#39;&amp;#34;&lt;/span> &lt;span style="color:#f92672">.&lt;/span> nxml-mode))
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;h2 id="step-8-build-script">Step 8: build script&lt;/h2>
&lt;p>Here is my general build script, leveraging msbuild and running generally from &lt;code>eshell&lt;/code>&lt;/p>
&lt;p>New projects are added to :&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-bat" data-lang="bat">&lt;span style="display:flex;">&lt;span>&lt;span style="color:#66d9ef">set&lt;/span> PROJECTS
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#66d9ef">set&lt;/span> PROJECT_NAMES
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&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-bat" data-lang="bat">&lt;span style="display:flex;">&lt;span>@&lt;span style="color:#66d9ef">echo&lt;/span> off
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#66d9ef">setlocal&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">REM =================================================================&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#75715e">REM Build Management Script&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#75715e">REM =================================================================&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#75715e">REM Usage: build-selected.bat [action] [verbosity] [configuration] [platform]&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#75715e">REM action: build, clean, restore, rebuild (default: build)&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#75715e">REM verbosity: quiet, minimal, normal, detailed, diagnostic (default: minimal)&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#75715e">REM configuration: Debug, Release (default: Debug)&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#75715e">REM platform: x64, x86, &amp;#34;Any CPU&amp;#34; (default: x64)&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#75715e">REM =================================================================&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">REM Set defaults&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#66d9ef">set&lt;/span> ACTION=%1
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#66d9ef">set&lt;/span> VERBOSITY=%2
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#66d9ef">set&lt;/span> CONFIGURATION=%3
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#66d9ef">set&lt;/span> PLATFORM=%4
&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:#66d9ef">if&lt;/span> &lt;span style="color:#e6db74">&amp;#34;&lt;/span>%ACTION%&lt;span style="color:#e6db74">&amp;#34;&lt;/span>&lt;span style="color:#f92672">==&lt;/span>&lt;span style="color:#e6db74">&amp;#34;&amp;#34;&lt;/span> &lt;span style="color:#66d9ef">set&lt;/span> ACTION=build
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#66d9ef">if&lt;/span> &lt;span style="color:#e6db74">&amp;#34;&lt;/span>%VERBOSITY%&lt;span style="color:#e6db74">&amp;#34;&lt;/span>&lt;span style="color:#f92672">==&lt;/span>&lt;span style="color:#e6db74">&amp;#34;&amp;#34;&lt;/span> &lt;span style="color:#66d9ef">set&lt;/span> VERBOSITY=minimal
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#66d9ef">if&lt;/span> &lt;span style="color:#e6db74">&amp;#34;&lt;/span>%CONFIGURATION%&lt;span style="color:#e6db74">&amp;#34;&lt;/span>&lt;span style="color:#f92672">==&lt;/span>&lt;span style="color:#e6db74">&amp;#34;&amp;#34;&lt;/span> &lt;span style="color:#66d9ef">set&lt;/span> CONFIGURATION=Debug
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#66d9ef">if&lt;/span> &lt;span style="color:#e6db74">&amp;#34;&lt;/span>%PLATFORM%&lt;span style="color:#e6db74">&amp;#34;&lt;/span>&lt;span style="color:#f92672">==&lt;/span>&lt;span style="color:#e6db74">&amp;#34;&amp;#34;&lt;/span> &lt;span style="color:#66d9ef">set&lt;/span> PLATFORM=x64
&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:#66d9ef">echo&lt;/span> Build Script - Action=%ACTION%, Verbosity=%VERBOSITY%, Config=%CONFIGURATION%, Platform=%PLATFORM%
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#66d9ef">echo&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">REM Common build parameters&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#66d9ef">set&lt;/span> BUILD_PARAMS=/p:Configuration=%CONFIGURATION% /p:Platform=&lt;span style="color:#e6db74">&amp;#34;&lt;/span>%PLATFORM%&lt;span style="color:#e6db74">&amp;#34;&lt;/span> /verbosity:%VERBOSITY%
&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">REM Set MSBuild target based on action&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#66d9ef">if&lt;/span> &lt;span style="color:#66d9ef">/I&lt;/span> &lt;span style="color:#e6db74">&amp;#34;&lt;/span>%ACTION%&lt;span style="color:#e6db74">&amp;#34;&lt;/span>&lt;span style="color:#f92672">==&lt;/span>&lt;span style="color:#e6db74">&amp;#34;build&amp;#34;&lt;/span> &lt;span style="color:#66d9ef">set&lt;/span> TARGET=Build
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#66d9ef">if&lt;/span> &lt;span style="color:#66d9ef">/I&lt;/span> &lt;span style="color:#e6db74">&amp;#34;&lt;/span>%ACTION%&lt;span style="color:#e6db74">&amp;#34;&lt;/span>&lt;span style="color:#f92672">==&lt;/span>&lt;span style="color:#e6db74">&amp;#34;clean&amp;#34;&lt;/span> &lt;span style="color:#66d9ef">set&lt;/span> TARGET=Clean
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#66d9ef">if&lt;/span> &lt;span style="color:#66d9ef">/I&lt;/span> &lt;span style="color:#e6db74">&amp;#34;&lt;/span>%ACTION%&lt;span style="color:#e6db74">&amp;#34;&lt;/span>&lt;span style="color:#f92672">==&lt;/span>&lt;span style="color:#e6db74">&amp;#34;restore&amp;#34;&lt;/span> &lt;span style="color:#66d9ef">set&lt;/span> TARGET=Restore
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#66d9ef">if&lt;/span> &lt;span style="color:#66d9ef">/I&lt;/span> &lt;span style="color:#e6db74">&amp;#34;&lt;/span>%ACTION%&lt;span style="color:#e6db74">&amp;#34;&lt;/span>&lt;span style="color:#f92672">==&lt;/span>&lt;span style="color:#e6db74">&amp;#34;rebuild&amp;#34;&lt;/span> &lt;span style="color:#66d9ef">set&lt;/span> TARGET=Rebuild
&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:#66d9ef">if&lt;/span> &lt;span style="color:#e6db74">&amp;#34;&lt;/span>%TARGET%&lt;span style="color:#e6db74">&amp;#34;&lt;/span>&lt;span style="color:#f92672">==&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:#66d9ef">echo&lt;/span> Error: Invalid action &amp;#39;%ACTION%&amp;#39;. Use: build, clean, restore, or rebuild
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#66d9ef">exit&lt;/span> /b 1
&lt;/span>&lt;/span>&lt;span style="display:flex;">&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:#66d9ef">echo&lt;/span> Executing %ACTION% action...
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#66d9ef">echo&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:#66d9ef">set&lt;/span> PROJECTS[1]=Demo/Demo.csproj
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#66d9ef">set&lt;/span> PROJECT_NAMES[1]=Demo
&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:#66d9ef">set&lt;/span> PROJECTS[2]=Test/Test.csproj
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#66d9ef">set&lt;/span> PROJECT_NAMES[2]=Test
&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:#66d9ef">set&lt;/span> PROJECT_COUNT=2
&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">REM Special handling for rebuild (clean then build)&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#66d9ef">if&lt;/span> &lt;span style="color:#66d9ef">/I&lt;/span> &lt;span style="color:#e6db74">&amp;#34;&lt;/span>%ACTION%&lt;span style="color:#e6db74">&amp;#34;&lt;/span>&lt;span style="color:#f92672">==&lt;/span>&lt;span style="color:#e6db74">&amp;#34;rebuild&amp;#34;&lt;/span> (
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#66d9ef">echo&lt;/span> === CLEANING PHASE ===
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#66d9ef">for&lt;/span> &lt;span style="color:#66d9ef">/L&lt;/span> &lt;span style="color:#ae81ff">%%&lt;/span>i &lt;span style="color:#66d9ef">in&lt;/span> (&lt;span style="color:#ae81ff">1&lt;/span>,&lt;span style="color:#ae81ff">1&lt;/span>,%PROJECT_COUNT%) &lt;span style="color:#66d9ef">do&lt;/span> (
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#66d9ef">call&lt;/span> :process_project &lt;span style="color:#ae81ff">%%&lt;/span>i Clean
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#66d9ef">if&lt;/span> &lt;span style="color:#66d9ef">errorlevel&lt;/span> &lt;span style="color:#ae81ff">1&lt;/span> &lt;span style="color:#66d9ef">goto&lt;/span> :error
&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:#66d9ef">echo&lt;/span>.
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#66d9ef">echo&lt;/span> === BUILDING PHASE ===
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#66d9ef">set&lt;/span> TARGET=Build
&lt;/span>&lt;/span>&lt;span style="display:flex;">&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">REM Process all active projects&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#66d9ef">for&lt;/span> &lt;span style="color:#66d9ef">/L&lt;/span> &lt;span style="color:#ae81ff">%%&lt;/span>i &lt;span style="color:#66d9ef">in&lt;/span> (&lt;span style="color:#ae81ff">1&lt;/span>,&lt;span style="color:#ae81ff">1&lt;/span>,%PROJECT_COUNT%) &lt;span style="color:#66d9ef">do&lt;/span> (
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#66d9ef">call&lt;/span> :process_project &lt;span style="color:#ae81ff">%%&lt;/span>i %TARGET%
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#66d9ef">if&lt;/span> &lt;span style="color:#66d9ef">errorlevel&lt;/span> &lt;span style="color:#ae81ff">1&lt;/span> &lt;span style="color:#66d9ef">goto&lt;/span> :error
&lt;/span>&lt;/span>&lt;span style="display:flex;">&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:#66d9ef">echo&lt;/span>.
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#66d9ef">if&lt;/span> &lt;span style="color:#66d9ef">/I&lt;/span> &lt;span style="color:#e6db74">&amp;#34;&lt;/span>%ACTION%&lt;span style="color:#e6db74">&amp;#34;&lt;/span>&lt;span style="color:#f92672">==&lt;/span>&lt;span style="color:#e6db74">&amp;#34;clean&amp;#34;&lt;/span> (
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#66d9ef">echo&lt;/span> All selected components cleaned successfully!
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>) &lt;span style="color:#66d9ef">else&lt;/span> &lt;span style="color:#66d9ef">if&lt;/span> &lt;span style="color:#66d9ef">/I&lt;/span> &lt;span style="color:#e6db74">&amp;#34;&lt;/span>%ACTION%&lt;span style="color:#e6db74">&amp;#34;&lt;/span>&lt;span style="color:#f92672">==&lt;/span>&lt;span style="color:#e6db74">&amp;#34;restore&amp;#34;&lt;/span> (
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#66d9ef">echo&lt;/span> All selected components restored successfully!
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>) &lt;span style="color:#66d9ef">else&lt;/span> &lt;span style="color:#66d9ef">if&lt;/span> &lt;span style="color:#66d9ef">/I&lt;/span> &lt;span style="color:#e6db74">&amp;#34;&lt;/span>%ACTION%&lt;span style="color:#e6db74">&amp;#34;&lt;/span>&lt;span style="color:#f92672">==&lt;/span>&lt;span style="color:#e6db74">&amp;#34;rebuild&amp;#34;&lt;/span> (
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#66d9ef">echo&lt;/span> All selected components rebuilt successfully!
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>) &lt;span style="color:#66d9ef">else&lt;/span> (
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#66d9ef">echo&lt;/span> All selected components built successfully!
&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:#66d9ef">goto&lt;/span> :end
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>:process_project
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#66d9ef">setlocal&lt;/span> EnableDelayedExpansion
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#66d9ef">set&lt;/span> idx=%1
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#66d9ef">set&lt;/span> target=%2
&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">REM Get project path and name using the index&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#66d9ef">for&lt;/span> &lt;span style="color:#66d9ef">/f&lt;/span> &lt;span style="color:#e6db74">&amp;#34;tokens=2 delims==&amp;#34;&lt;/span> &lt;span style="color:#ae81ff">%%&lt;/span>a &lt;span style="color:#66d9ef">in&lt;/span> (&lt;span style="color:#e6db74">&amp;#39;set PROJECTS[&lt;/span>%idx%&lt;span style="color:#e6db74">] 2^&amp;gt;nul&amp;#39;&lt;/span>) &lt;span style="color:#66d9ef">do&lt;/span> &lt;span style="color:#66d9ef">set&lt;/span> PROJECT_PATH=&lt;span style="color:#ae81ff">%%&lt;/span>a
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#66d9ef">for&lt;/span> &lt;span style="color:#66d9ef">/f&lt;/span> &lt;span style="color:#e6db74">&amp;#34;tokens=2 delims==&amp;#34;&lt;/span> &lt;span style="color:#ae81ff">%%&lt;/span>a &lt;span style="color:#66d9ef">in&lt;/span> (&lt;span style="color:#e6db74">&amp;#39;set PROJECT_NAMES[&lt;/span>%idx%&lt;span style="color:#e6db74">] 2^&amp;gt;nul&amp;#39;&lt;/span>) &lt;span style="color:#66d9ef">do&lt;/span> &lt;span style="color:#66d9ef">set&lt;/span> PROJECT_NAME=&lt;span style="color:#ae81ff">%%&lt;/span>a
&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:#66d9ef">if&lt;/span> &lt;span style="color:#e6db74">&amp;#34;&lt;/span>!PROJECT_PATH!&lt;span style="color:#e6db74">&amp;#34;&lt;/span>&lt;span style="color:#f92672">==&lt;/span>&lt;span style="color:#e6db74">&amp;#34;&amp;#34;&lt;/span> &lt;span style="color:#66d9ef">goto&lt;/span> :eof
&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:#66d9ef">echo&lt;/span> ----------------------------------------
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#66d9ef">echo&lt;/span> [%idx%/%PROJECT_COUNT%] %target%ing !PROJECT_NAME!...
&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">REM Build the project normally&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> msbuild &lt;span style="color:#e6db74">&amp;#34;&lt;/span>!PROJECT_PATH!&lt;span style="color:#e6db74">&amp;#34;&lt;/span> /t:%target% %BUILD_PARAMS%
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#66d9ef">if&lt;/span> &lt;span style="color:#66d9ef">errorlevel&lt;/span> &lt;span style="color:#ae81ff">1&lt;/span> &lt;span style="color:#66d9ef">exit&lt;/span> /b 1
&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:#66d9ef">goto&lt;/span> :eof
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>:error
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#66d9ef">echo&lt;/span>.
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#66d9ef">echo&lt;/span> %ACTION% failed! Check the output above for errors.
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#66d9ef">exit&lt;/span> /b 1
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>:end
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#66d9ef">echo&lt;/span> %ACTION% completed at %time%
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>to launch applications of course, if it is a pure DOTNET project you would use &lt;code>dotnet run&lt;/code>&lt;/p>
&lt;h2 id="troubleshooting">Troubleshooting&lt;/h2>
&lt;h3 id="cannot-find-csharp-ls-or-eglot-won-t-start">&amp;ldquo;Cannot find csharp-ls&amp;rdquo; or Eglot won&amp;rsquo;t start&lt;/h3>
&lt;ol>
&lt;li>Check the PATH: &lt;code>M-x getenv RET PATH&lt;/code>&lt;/li>
&lt;li>Verify the DLL exists at the configured location&lt;/li>
&lt;li>Try running manually: &lt;code>dotnet path\to\CSharpLanguageServer.dll --version&lt;/code>&lt;/li>
&lt;li>Check &lt;code>*eglot-events*&lt;/code> buffer for detailed error messages&lt;/li>
&lt;/ol>
&lt;h3 id="lsp-is-slow-or-uses-too-much-memory">LSP is slow or uses too much memory&lt;/h3>
&lt;p>Try adding to your configuration:&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-emacs-lisp" data-lang="emacs-lisp">&lt;span style="display:flex;">&lt;span>&lt;span style="color:#75715e">;; Increase garbage collection threshold during LSP operations&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>(setq gc-cons-threshold &lt;span style="color:#ae81ff">100000000&lt;/span>) &lt;span style="color:#75715e">; 100MB&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>(setq read-process-output-max (&lt;span style="color:#a6e22e">*&lt;/span> &lt;span style="color:#ae81ff">1024&lt;/span> &lt;span style="color:#ae81ff">1024&lt;/span>)) &lt;span style="color:#75715e">; 1MB&lt;/span>
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;h3 id="debugger-won-t-attach">Debugger won&amp;rsquo;t attach&lt;/h3>
&lt;ol>
&lt;li>Ensure the project is built in Debug configuration&lt;/li>
&lt;li>Check the DLL path matches your build output&lt;/li>
&lt;li>Look at &lt;code>*dape-repl*&lt;/code> for error messages&lt;/li>
&lt;li>Verify netcoredbg runs: &lt;code>netcoredbg.exe --version&lt;/code>&lt;/li>
&lt;/ol>
&lt;h2 id="conclusion">Conclusion&lt;/h2>
&lt;p>This setup has served me well for my windows .NET 9.0 projects and various other C# work. The key benefits:&lt;/p>
&lt;ul>
&lt;li>&lt;strong>Portability&lt;/strong>: Everything lives in one directory&lt;/li>
&lt;li>&lt;strong>Speed&lt;/strong>: csharp-ls is notably faster than OmniSharp&lt;/li>
&lt;li>&lt;strong>Flexibility&lt;/strong>: Easy to customise and extend&lt;/li>
&lt;li>&lt;strong>Offline-capable&lt;/strong>: Works in air-gapped environments&lt;/li>
&lt;/ul>
&lt;p>The initial setup takes some effort, but once it&amp;rsquo;s done, you have a powerful, consistent development environment that travels with you.&lt;/p></description></item><item><title>Expanding Ollama Buddy: Mistral Codestral Integration</title><link>https://www.emacs.dyerdwelling.family/emacs/20251211081935-emacs--expanding-ollama-buddy-mistral-codestral-integration/</link><pubDate>Thu, 11 Dec 2025 08:19:00 +0000</pubDate><author>James Dyer</author><guid>https://www.emacs.dyerdwelling.family/emacs/20251211081935-emacs--expanding-ollama-buddy-mistral-codestral-integration/</guid><description>&lt;p>Ollama Buddy now supports Mistral&amp;rsquo;s Codestral - a powerful code-generation model from Mistral AI that seamlessly integrates into the ollama-buddy ecosystem.&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>&lt;a href="https://melpa.org/#/ollama-buddy">https://melpa.org/#/ollama-buddy&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_001.jpg" width="100%">
&lt;/figure>
&lt;p>So now we have:&lt;/p>
&lt;ul>
&lt;li>&lt;strong>Local Ollama models&lt;/strong> — full control, complete privacy&lt;/li>
&lt;li>&lt;strong>OpenAI&lt;/strong> — extensive model options and API maturity&lt;/li>
&lt;li>&lt;strong>Claude&lt;/strong> — reasoning and complex analysis&lt;/li>
&lt;li>&lt;strong>Gemini&lt;/strong> — multimodal capabilities&lt;/li>
&lt;li>&lt;strong>Grok&lt;/strong> — advanced reasoning models&lt;/li>
&lt;li>&lt;strong>Codestral&lt;/strong> — specialized code generation &lt;strong>NEW&lt;/strong>&lt;/li>
&lt;/ul>
&lt;p>To get up and running&amp;hellip;&lt;/p>
&lt;p>First, sign up at &lt;a href="https://console.mistral.ai/">Mistral AI&lt;/a> and generate an API key from your dashboard.&lt;/p>
&lt;p>Add this to your Emacs configuration:&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-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> (ollama-buddy-codestral-api-key
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> (auth-source-pick-first-password :host &lt;span style="color:#e6db74">&amp;#34;ollama-buddy-codestral&amp;#34;&lt;/span> :user &lt;span style="color:#e6db74">&amp;#34;apikey&amp;#34;&lt;/span>))
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> :config
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> (require &lt;span style="color:#e6db74">&amp;#39;ollama-buddy-codestral&lt;/span> &lt;span style="color:#66d9ef">nil&lt;/span> &lt;span style="color:#66d9ef">t&lt;/span>))
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>Once configured, Codestral models will appear in your model list with an &lt;code>s:&lt;/code> prefix (e.g., &lt;code>s:codestral-latest&lt;/code>). You can:&lt;/p>
&lt;ul>
&lt;li>Select it from the model menu (&lt;code>C-c m&lt;/code>)&lt;/li>
&lt;li>Use it with any command that supports model selection&lt;/li>
&lt;li>Switch between local and cloud models on-the-fly&lt;/li>
&lt;/ul></description></item><item><title>Convert copied jira kanban to org (jira-to-org)</title><link>https://www.emacs.dyerdwelling.family/emacs/20251128145610-emacs--jira-to-org/</link><pubDate>Fri, 28 Nov 2025 14:56:00 +0000</pubDate><author>James Dyer</author><guid>https://www.emacs.dyerdwelling.family/emacs/20251128145610-emacs--jira-to-org/</guid><description>&lt;p>I have been fiddling around with some very rudimentary Jira integration to org, basically something very simple, just a regular copy from the kanban and then convert into org headlines!&lt;/p>
&lt;p>This package is designed for simpler workflows where you copy data from Jira rather than maintaining API integration.&lt;/p>
&lt;figure>&lt;img src="https://www.emacs.dyerdwelling.family/emacs/20251128145610-emacs--jira-to-org.jpg" width="100%">
&lt;/figure>
&lt;p>Here is a link to the package:&lt;/p>
&lt;p>&lt;a href="https://github.com/captainflasmr/jira-to-org">https://github.com/captainflasmr/jira-to-org&lt;/a>&lt;/p>
&lt;h2 id="overview">Overview&lt;/h2>
&lt;p>&lt;code>jira-to-org&lt;/code> is an Emacs package that converts Jira sprint board data into org-mode task entries. It parses text copied directly from Jira sprint boards and transforms it into properly formatted org-mode headings with TODO keywords, priorities, tags, and metadata.&lt;/p>
&lt;h2 id="features">Features&lt;/h2>
&lt;ul>
&lt;li>Parse Jira sprint data from buffers, regions, or clipboard&lt;/li>
&lt;li>Convert Jira statuses to org-mode TODO keywords&lt;/li>
&lt;li>Map Jira priorities to org-mode priorities (A/B/C)&lt;/li>
&lt;li>Generate tags from assignees, sprint identifiers, and years&lt;/li>
&lt;li>Update existing org entries based on fresh Jira data&lt;/li>
&lt;li>Customizable mappings for assignees, priorities, and statuses&lt;/li>
&lt;li>Configurable heading levels and default values&lt;/li>
&lt;/ul>
&lt;h2 id="installation">Installation&lt;/h2>
&lt;h3 id="manual-installation">Manual Installation&lt;/h3>
&lt;ol>
&lt;li>Download &lt;code>jira-to-org.el&lt;/code> to your Emacs configuration directory&lt;/li>
&lt;li>Add to your &lt;code>init.el&lt;/code>:&lt;/li>
&lt;/ol>
&lt;!--listend-->
&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-emacs-lisp" data-lang="emacs-lisp">&lt;span style="display:flex;">&lt;span>(add-to-list &lt;span style="color:#e6db74">&amp;#39;load-path&lt;/span> &lt;span style="color:#e6db74">&amp;#34;/path/to/jira-to-org/&amp;#34;&lt;/span>)
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>(require &lt;span style="color:#e6db74">&amp;#39;jira-to-org&lt;/span>)
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;h3 id="using-use-package">Using &lt;code>use-package&lt;/code>&lt;/h3>
&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-emacs-lisp" data-lang="emacs-lisp">&lt;span style="display:flex;">&lt;span>(use-package jira-to-org
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> :load-path &lt;span style="color:#e6db74">&amp;#34;/path/to/jira-to-org/&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> :custom
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> (jira-to-org-default-sprint &lt;span style="color:#e6db74">&amp;#34;s7&amp;#34;&lt;/span>)
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> (jira-to-org-default-year &lt;span style="color:#e6db74">&amp;#34;2025&amp;#34;&lt;/span>)
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> (jira-to-org-heading-level &lt;span style="color:#ae81ff">4&lt;/span>))
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;h2 id="usage">Usage&lt;/h2>
&lt;h3 id="basic-workflow">Basic Workflow&lt;/h3>
&lt;ol>
&lt;li>Open your Jira sprint board in a web browser&lt;/li>
&lt;li>Select and copy the sprint data (the entire board or specific columns)&lt;/li>
&lt;li>In Emacs, use one of the parsing commands&lt;/li>
&lt;/ol>
&lt;h3 id="commands">Commands&lt;/h3>
&lt;h4 id="jira-to-org-parse-buffer">&lt;code>jira-to-org-parse-buffer&lt;/code>&lt;/h4>
&lt;p>Parse the entire current buffer as Jira data. The result is copied to the kill ring.&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-bash" data-lang="bash">&lt;span style="display:flex;">&lt;span>M-x jira-to-org-parse-buffer
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>With prefix argument (&lt;code>C-u&lt;/code>), prompts for a sprint tag:&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-bash" data-lang="bash">&lt;span style="display:flex;">&lt;span>C-u M-x jira-to-org-parse-buffer
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>Sprint tag &lt;span style="color:#f92672">(&lt;/span>e.g., s7&lt;span style="color:#f92672">)&lt;/span>: s8
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;h4 id="jira-to-org-parse-region">&lt;code>jira-to-org-parse-region&lt;/code>&lt;/h4>
&lt;p>Parse Jira data in the selected region. The result is copied to the kill ring.&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-bash" data-lang="bash">&lt;span style="display:flex;">&lt;span>1. Select region containing Jira data
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>2. M-x jira-to-org-parse-region
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;h4 id="jira-to-org-parse-and-insert">&lt;code>jira-to-org-parse-and-insert&lt;/code>&lt;/h4>
&lt;p>Parse Jira data from the clipboard and insert the org headings at point.&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-bash" data-lang="bash">&lt;span style="display:flex;">&lt;span>1. Copy Jira data to clipboard
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>2. Position cursor where you want entries inserted
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>3. M-x jira-to-org-parse-and-insert
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;h4 id="jira-to-org-update-from-jira">&lt;code>jira-to-org-update-from-jira&lt;/code>&lt;/h4>
&lt;p>Update existing org entries based on fresh Jira data. This command:&lt;/p>
&lt;ul>
&lt;li>Updates TODO statuses for entries that already exist in your buffer&lt;/li>
&lt;li>Reports how many entries were updated&lt;/li>
&lt;li>Copies new items (not found in buffer) to the kill ring&lt;/li>
&lt;/ul>
&lt;!--listend-->
&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-bash" data-lang="bash">&lt;span style="display:flex;">&lt;span>M-x jira-to-org-update-from-jira
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;h3 id="example-input-format">Example Input Format&lt;/h3>
&lt;p>The package expects text copied from Jira sprint boards in this format:&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>To Do
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>MM-78
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>Implement IG connection management APIs
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>Assignee: James Dyer
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>Priority: Medium
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>Issue Type: Story
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>2
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>In Progress
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>MM-79
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>Create gRPC service definition
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>Assignee: Freddy
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>Priority: High
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>3
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>Done
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>MM-75
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>Setup project structure
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>Assignee: N Cropper
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>Priority: Low
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>Issue Type: Task
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>1
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;h3 id="example-output">Example Output&lt;/h3>
&lt;p>The above input would be converted to:&lt;/p>
&lt;p>#+BEGIN_EXAMPLE&lt;/p>
&lt;!--list-separator-->
&lt;ul>
&lt;li>&lt;span class="org-todo todo TODO">TODO&lt;/span> MM-78 Implement IG connection management APIs&lt;/li>
&lt;/ul>
&lt;!--list-separator-->
&lt;ul>
&lt;li>&lt;span class="org-todo todo DOING">DOING&lt;/span> MM-79 Create gRPC service definition&lt;/li>
&lt;/ul>
&lt;!--list-separator-->
&lt;ul>
&lt;li>
&lt;p>&lt;span class="org-todo done DONE">DONE&lt;/span> MM-75 Setup project structure&lt;/p>
&lt;p>#+END_EXAMPLE&lt;/p>
&lt;/li>
&lt;/ul>
&lt;h2 id="configuration">Configuration&lt;/h2>
&lt;h3 id="customization-variables">Customization Variables&lt;/h3>
&lt;h4 id="jira-to-org-default-sprint">&lt;code>jira-to-org-default-sprint&lt;/code>&lt;/h4>
&lt;p>Default sprint tag to add to all entries (e.g., &lt;code>&amp;quot;s7&amp;quot;&lt;/code>). If &lt;code>nil&lt;/code>, no sprint tag is added.&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-emacs-lisp" data-lang="emacs-lisp">&lt;span style="display:flex;">&lt;span>(setq jira-to-org-default-sprint &lt;span style="color:#e6db74">&amp;#34;s7&amp;#34;&lt;/span>)
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;h4 id="jira-to-org-default-year">&lt;code>jira-to-org-default-year&lt;/code>&lt;/h4>
&lt;p>Default year to add as a tag to all entries.&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-emacs-lisp" data-lang="emacs-lisp">&lt;span style="display:flex;">&lt;span>(setq jira-to-org-default-year &lt;span style="color:#e6db74">&amp;#34;2025&amp;#34;&lt;/span>)
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;h4 id="jira-to-org-assignee-map">&lt;code>jira-to-org-assignee-map&lt;/code>&lt;/h4>
&lt;p>Alist mapping full names (as they appear in Jira) to short tag names.&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-emacs-lisp" data-lang="emacs-lisp">&lt;span style="display:flex;">&lt;span>(setq jira-to-org-assignee-map
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#f92672">&amp;#39;&lt;/span>((&lt;span style="color:#e6db74">&amp;#34;James Dyer&amp;#34;&lt;/span> &lt;span style="color:#f92672">.&lt;/span> &lt;span style="color:#e6db74">&amp;#34;jdyer&amp;#34;&lt;/span>)
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> (&lt;span style="color:#e6db74">&amp;#34;Freddy&amp;#34;&lt;/span> &lt;span style="color:#f92672">.&lt;/span> &lt;span style="color:#e6db74">&amp;#34;freddy&amp;#34;&lt;/span>)
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> (&lt;span style="color:#e6db74">&amp;#34;N Cropper&amp;#34;&lt;/span> &lt;span style="color:#f92672">.&lt;/span> &lt;span style="color:#e6db74">&amp;#34;ncropper&amp;#34;&lt;/span>)))
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;h4 id="jira-to-org-priority-map">&lt;code>jira-to-org-priority-map&lt;/code>&lt;/h4>
&lt;p>Alist mapping Jira priority levels to org-mode priority letters.&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-emacs-lisp" data-lang="emacs-lisp">&lt;span style="display:flex;">&lt;span>(setq jira-to-org-priority-map
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#f92672">&amp;#39;&lt;/span>((&lt;span style="color:#e6db74">&amp;#34;Highest&amp;#34;&lt;/span> &lt;span style="color:#f92672">.&lt;/span> &lt;span style="color:#e6db74">&amp;#34;A&amp;#34;&lt;/span>)
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> (&lt;span style="color:#e6db74">&amp;#34;High&amp;#34;&lt;/span> &lt;span style="color:#f92672">.&lt;/span> &lt;span style="color:#e6db74">&amp;#34;A&amp;#34;&lt;/span>)
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> (&lt;span style="color:#e6db74">&amp;#34;Medium&amp;#34;&lt;/span> &lt;span style="color:#f92672">.&lt;/span> &lt;span style="color:#e6db74">&amp;#34;B&amp;#34;&lt;/span>)
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> (&lt;span style="color:#e6db74">&amp;#34;Low&amp;#34;&lt;/span> &lt;span style="color:#f92672">.&lt;/span> &lt;span style="color:#e6db74">&amp;#34;C&amp;#34;&lt;/span>)
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> (&lt;span style="color:#e6db74">&amp;#34;Lowest&amp;#34;&lt;/span> &lt;span style="color:#f92672">.&lt;/span> &lt;span style="color:#e6db74">&amp;#34;C&amp;#34;&lt;/span>)))
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;h4 id="jira-to-org-status-map">&lt;code>jira-to-org-status-map&lt;/code>&lt;/h4>
&lt;p>Alist mapping Jira status names to org-mode TODO keywords.&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-emacs-lisp" data-lang="emacs-lisp">&lt;span style="display:flex;">&lt;span>(setq jira-to-org-status-map
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#f92672">&amp;#39;&lt;/span>((&lt;span style="color:#e6db74">&amp;#34;To Do&amp;#34;&lt;/span> &lt;span style="color:#f92672">.&lt;/span> &lt;span style="color:#e6db74">&amp;#34;TODO&amp;#34;&lt;/span>)
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> (&lt;span style="color:#e6db74">&amp;#34;In Progress&amp;#34;&lt;/span> &lt;span style="color:#f92672">.&lt;/span> &lt;span style="color:#e6db74">&amp;#34;DOING&amp;#34;&lt;/span>)
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> (&lt;span style="color:#e6db74">&amp;#34;Done&amp;#34;&lt;/span> &lt;span style="color:#f92672">.&lt;/span> &lt;span style="color:#e6db74">&amp;#34;DONE&amp;#34;&lt;/span>)))
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;h4 id="jira-to-org-heading-level">&lt;code>jira-to-org-heading-level&lt;/code>&lt;/h4>
&lt;p>Number of asterisks for generated org headings (default: 4).&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-emacs-lisp" data-lang="emacs-lisp">&lt;span style="display:flex;">&lt;span>(setq jira-to-org-heading-level &lt;span style="color:#ae81ff">3&lt;/span>) &lt;span style="color:#75715e">; Use *** instead of ****&lt;/span>
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;h3 id="complete-configuration-example">Complete Configuration Example&lt;/h3>
&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-emacs-lisp" data-lang="emacs-lisp">&lt;span style="display:flex;">&lt;span>(use-package jira-to-org
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> :custom
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> (jira-to-org-default-sprint &lt;span style="color:#e6db74">&amp;#34;s8&amp;#34;&lt;/span>)
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> (jira-to-org-default-year &lt;span style="color:#e6db74">&amp;#34;2025&amp;#34;&lt;/span>)
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> (jira-to-org-heading-level &lt;span style="color:#ae81ff">4&lt;/span>)
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> (jira-to-org-assignee-map
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#f92672">&amp;#39;&lt;/span>((&lt;span style="color:#e6db74">&amp;#34;James Dyer&amp;#34;&lt;/span> &lt;span style="color:#f92672">.&lt;/span> &lt;span style="color:#e6db74">&amp;#34;jdyer&amp;#34;&lt;/span>)
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> (&lt;span style="color:#e6db74">&amp;#34;Freddy Johnson&amp;#34;&lt;/span> &lt;span style="color:#f92672">.&lt;/span> &lt;span style="color:#e6db74">&amp;#34;freddy&amp;#34;&lt;/span>)
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> (&lt;span style="color:#e6db74">&amp;#34;Sarah Chen&amp;#34;&lt;/span> &lt;span style="color:#f92672">.&lt;/span> &lt;span style="color:#e6db74">&amp;#34;schen&amp;#34;&lt;/span>)
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> (&lt;span style="color:#e6db74">&amp;#34;Mike Wilson&amp;#34;&lt;/span> &lt;span style="color:#f92672">.&lt;/span> &lt;span style="color:#e6db74">&amp;#34;mwilson&amp;#34;&lt;/span>)))
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> (jira-to-org-priority-map
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#f92672">&amp;#39;&lt;/span>((&lt;span style="color:#e6db74">&amp;#34;Highest&amp;#34;&lt;/span> &lt;span style="color:#f92672">.&lt;/span> &lt;span style="color:#e6db74">&amp;#34;A&amp;#34;&lt;/span>)
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> (&lt;span style="color:#e6db74">&amp;#34;High&amp;#34;&lt;/span> &lt;span style="color:#f92672">.&lt;/span> &lt;span style="color:#e6db74">&amp;#34;A&amp;#34;&lt;/span>)
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> (&lt;span style="color:#e6db74">&amp;#34;Medium&amp;#34;&lt;/span> &lt;span style="color:#f92672">.&lt;/span> &lt;span style="color:#e6db74">&amp;#34;B&amp;#34;&lt;/span>)
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> (&lt;span style="color:#e6db74">&amp;#34;Low&amp;#34;&lt;/span> &lt;span style="color:#f92672">.&lt;/span> &lt;span style="color:#e6db74">&amp;#34;C&amp;#34;&lt;/span>)
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> (&lt;span style="color:#e6db74">&amp;#34;Lowest&amp;#34;&lt;/span> &lt;span style="color:#f92672">.&lt;/span> &lt;span style="color:#e6db74">&amp;#34;C&amp;#34;&lt;/span>)))
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> (jira-to-org-status-map
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#f92672">&amp;#39;&lt;/span>((&lt;span style="color:#e6db74">&amp;#34;To Do&amp;#34;&lt;/span> &lt;span style="color:#f92672">.&lt;/span> &lt;span style="color:#e6db74">&amp;#34;TODO&amp;#34;&lt;/span>)
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> (&lt;span style="color:#e6db74">&amp;#34;In Progress&amp;#34;&lt;/span> &lt;span style="color:#f92672">.&lt;/span> &lt;span style="color:#e6db74">&amp;#34;DOING&amp;#34;&lt;/span>)
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> (&lt;span style="color:#e6db74">&amp;#34;In Review&amp;#34;&lt;/span> &lt;span style="color:#f92672">.&lt;/span> &lt;span style="color:#e6db74">&amp;#34;REVIEW&amp;#34;&lt;/span>)
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> (&lt;span style="color:#e6db74">&amp;#34;Done&amp;#34;&lt;/span> &lt;span style="color:#f92672">.&lt;/span> &lt;span style="color:#e6db74">&amp;#34;DONE&amp;#34;&lt;/span>)))
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> :bind
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> (:map org-mode-map
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> (&lt;span style="color:#e6db74">&amp;#34;C-c j p&amp;#34;&lt;/span> &lt;span style="color:#f92672">.&lt;/span> jira-to-org-parse-and-insert)
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> (&lt;span style="color:#e6db74">&amp;#34;C-c j u&amp;#34;&lt;/span> &lt;span style="color:#f92672">.&lt;/span> jira-to-org-update-from-jira)))
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;h2 id="workflow-integration">Workflow Integration&lt;/h2>
&lt;h3 id="sprint-planning-workflow">Sprint Planning Workflow&lt;/h3>
&lt;ol>
&lt;li>Open your sprint planning org file&lt;/li>
&lt;li>Navigate to the sprint section&lt;/li>
&lt;li>Copy the Jira sprint board data&lt;/li>
&lt;li>Run &lt;code>M-x jira-to-org-parse-and-insert&lt;/code>&lt;/li>
&lt;li>Adjust any entries as needed&lt;/li>
&lt;/ol>
&lt;h3 id="daily-standup-updates">Daily Standup Updates&lt;/h3>
&lt;ol>
&lt;li>Copy current sprint board state from Jira&lt;/li>
&lt;li>Run &lt;code>M-x jira-to-org-update-from-jira&lt;/code>&lt;/li>
&lt;li>Review updated TODO states&lt;/li>
&lt;li>Any new items are copied to kill ring for manual placement&lt;/li>
&lt;/ol>
&lt;h3 id="custom-key-bindings">Custom Key Bindings&lt;/h3>
&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-emacs-lisp" data-lang="emacs-lisp">&lt;span style="display:flex;">&lt;span>&lt;span style="color:#75715e">;; Add to your org-mode configuration&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>(with-eval-after-load &lt;span style="color:#e6db74">&amp;#39;org&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> (&lt;span style="color:#a6e22e">define-key&lt;/span> org-mode-map (kbd &lt;span style="color:#e6db74">&amp;#34;C-c j p&amp;#34;&lt;/span>) &lt;span style="color:#a6e22e">#&amp;#39;&lt;/span>jira-to-org-parse-and-insert)
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> (&lt;span style="color:#a6e22e">define-key&lt;/span> org-mode-map (kbd &lt;span style="color:#e6db74">&amp;#34;C-c j r&amp;#34;&lt;/span>) &lt;span style="color:#a6e22e">#&amp;#39;&lt;/span>jira-to-org-parse-region)
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> (&lt;span style="color:#a6e22e">define-key&lt;/span> org-mode-map (kbd &lt;span style="color:#e6db74">&amp;#34;C-c j b&amp;#34;&lt;/span>) &lt;span style="color:#a6e22e">#&amp;#39;&lt;/span>jira-to-org-parse-buffer)
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> (&lt;span style="color:#a6e22e">define-key&lt;/span> org-mode-map (kbd &lt;span style="color:#e6db74">&amp;#34;C-c j u&amp;#34;&lt;/span>) &lt;span style="color:#a6e22e">#&amp;#39;&lt;/span>jira-to-org-update-from-jira))
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;h2 id="parsed-fields">Parsed Fields&lt;/h2>
&lt;p>The package extracts and uses the following fields from Jira data:&lt;/p>
&lt;table>
&lt;thead>
&lt;tr>
&lt;th>Jira Field&lt;/th>
&lt;th>Usage&lt;/th>
&lt;th>Example&lt;/th>
&lt;/tr>
&lt;/thead>
&lt;tbody>
&lt;tr>
&lt;td>Issue Key&lt;/td>
&lt;td>Included in heading&lt;/td>
&lt;td>MM-78&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>Title&lt;/td>
&lt;td>Included in heading&lt;/td>
&lt;td>Fix bug in API&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>Status&lt;/td>
&lt;td>Converted to TODO keyword&lt;/td>
&lt;td>In Progress&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>Assignee&lt;/td>
&lt;td>Converted to tag&lt;/td>
&lt;td>James Dyer&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>Priority&lt;/td>
&lt;td>Converted to org priority&lt;/td>
&lt;td>Medium&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>Story Points&lt;/td>
&lt;td>Extracted (not currently used in output)&lt;/td>
&lt;td>3&lt;/td>
&lt;/tr>
&lt;/tbody>
&lt;/table>
&lt;h2 id="customizing-for-your-organization">Customizing for Your Organization&lt;/h2>
&lt;h3 id="adjusting-issue-key-pattern">Adjusting Issue Key Pattern&lt;/h3>
&lt;p>If your Jira instance uses a different project key pattern, modify the regex in &lt;code>jira-to-org--parse-buffer-to-items&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-emacs-lisp" data-lang="emacs-lisp">&lt;span style="display:flex;">&lt;span>&lt;span style="color:#75715e">;; Current pattern matches MM-123&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>((&lt;span style="color:#a6e22e">string-match&lt;/span> &lt;span style="color:#e6db74">&amp;#34;^\\(MM-[0-9]+\\)$&amp;#34;&lt;/span> line)
&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">;; Example: Match PROJECT-123&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>((&lt;span style="color:#a6e22e">string-match&lt;/span> &lt;span style="color:#e6db74">&amp;#34;^\\(PROJECT-[0-9]+\\)$&amp;#34;&lt;/span> line)
&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">;; Example: Match multiple projects (PROJ1-123 or PROJ2-123)&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>((&lt;span style="color:#a6e22e">string-match&lt;/span> &lt;span style="color:#e6db74">&amp;#34;^\\(\\(?:PROJ1\\|PROJ2\\)-[0-9]+\\)$&amp;#34;&lt;/span> line)
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;h3 id="adding-custom-status-mappings">Adding Custom Status Mappings&lt;/h3>
&lt;p>If your Jira workflow has additional statuses:&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-emacs-lisp" data-lang="emacs-lisp">&lt;span style="display:flex;">&lt;span>(setq jira-to-org-status-map
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#f92672">&amp;#39;&lt;/span>((&lt;span style="color:#e6db74">&amp;#34;To Do&amp;#34;&lt;/span> &lt;span style="color:#f92672">.&lt;/span> &lt;span style="color:#e6db74">&amp;#34;TODO&amp;#34;&lt;/span>)
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> (&lt;span style="color:#e6db74">&amp;#34;In Progress&amp;#34;&lt;/span> &lt;span style="color:#f92672">.&lt;/span> &lt;span style="color:#e6db74">&amp;#34;DOING&amp;#34;&lt;/span>)
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> (&lt;span style="color:#e6db74">&amp;#34;Code Review&amp;#34;&lt;/span> &lt;span style="color:#f92672">.&lt;/span> &lt;span style="color:#e6db74">&amp;#34;REVIEW&amp;#34;&lt;/span>)
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> (&lt;span style="color:#e6db74">&amp;#34;QA Testing&amp;#34;&lt;/span> &lt;span style="color:#f92672">.&lt;/span> &lt;span style="color:#e6db74">&amp;#34;TESTING&amp;#34;&lt;/span>)
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> (&lt;span style="color:#e6db74">&amp;#34;Blocked&amp;#34;&lt;/span> &lt;span style="color:#f92672">.&lt;/span> &lt;span style="color:#e6db74">&amp;#34;BLOCKED&amp;#34;&lt;/span>)
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> (&lt;span style="color:#e6db74">&amp;#34;Done&amp;#34;&lt;/span> &lt;span style="color:#f92672">.&lt;/span> &lt;span style="color:#e6db74">&amp;#34;DONE&amp;#34;&lt;/span>)))
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>Make sure your org-mode TODO keywords match:&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-emacs-lisp" data-lang="emacs-lisp">&lt;span style="display:flex;">&lt;span>(setq org-todo-keywords
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#f92672">&amp;#39;&lt;/span>((sequence &lt;span style="color:#e6db74">&amp;#34;TODO&amp;#34;&lt;/span> &lt;span style="color:#e6db74">&amp;#34;DOING&amp;#34;&lt;/span> &lt;span style="color:#e6db74">&amp;#34;REVIEW&amp;#34;&lt;/span> &lt;span style="color:#e6db74">&amp;#34;TESTING&amp;#34;&lt;/span> &lt;span style="color:#e6db74">&amp;#34;BLOCKED&amp;#34;&lt;/span> &lt;span style="color:#e6db74">&amp;#34;|&amp;#34;&lt;/span> &lt;span style="color:#e6db74">&amp;#34;DONE&amp;#34;&lt;/span>)))
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;h2 id="troubleshooting">Troubleshooting&lt;/h2>
&lt;h3 id="no-items-parsed">No Items Parsed&lt;/h3>
&lt;ul>
&lt;li>Verify the Jira data format matches the expected structure&lt;/li>
&lt;li>Check that issue keys match the pattern (e.g., &lt;code>MM-123&lt;/code>)&lt;/li>
&lt;li>Ensure status headers (To Do, In Progress, Done) are present&lt;/li>
&lt;/ul>
&lt;h3 id="wrong-assignee-tags">Wrong Assignee Tags&lt;/h3>
&lt;ul>
&lt;li>Check &lt;code>jira-to-org-assignee-map&lt;/code> contains mappings for all team members&lt;/li>
&lt;li>Verify exact spelling of names in Jira matches the mapping&lt;/li>
&lt;/ul>
&lt;h3 id="incorrect-todo-keywords">Incorrect TODO Keywords&lt;/h3>
&lt;ul>
&lt;li>Verify &lt;code>jira-to-org-status-map&lt;/code> maps all statuses used in your Jira&lt;/li>
&lt;li>Check that target TODO keywords exist in &lt;code>org-todo-keywords&lt;/code>&lt;/li>
&lt;/ul>
&lt;h3 id="entries-not-found-during-update">Entries Not Found During Update&lt;/h3>
&lt;ul>
&lt;li>Ensure issue keys in org file exactly match Jira format&lt;/li>
&lt;li>Verify the heading contains the issue key followed by a space&lt;/li>
&lt;/ul>
&lt;h2 id="api-functions">API Functions&lt;/h2>
&lt;p>For programmatic use:&lt;/p>
&lt;h3 id="jira-to-org-parse-string">&lt;code>jira-to-org-parse-string&lt;/code>&lt;/h3>
&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-emacs-lisp" data-lang="emacs-lisp">&lt;span style="display:flex;">&lt;span>(jira-to-org-parse-string STRING &lt;span style="color:#66d9ef">&amp;amp;optional&lt;/span> SPRINT)
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>Parse STRING containing Jira data and return org-mode headings as a string.&lt;/p>
&lt;h3 id="jira-to-org-parse-buffer-to-items">&lt;code>jira-to-org--parse-buffer-to-items&lt;/code>&lt;/h3>
&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-emacs-lisp" data-lang="emacs-lisp">&lt;span style="display:flex;">&lt;span>(jira-to-org--parse-buffer-to-items BUFFER-OR-STRING)
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>Parse Jira data into a list of &lt;code>jira-to-org-item&lt;/code> structs for further processing.&lt;/p>
&lt;h3 id="jira-to-org-item-to-org-heading">&lt;code>jira-to-org--item-to-org-heading&lt;/code>&lt;/h3>
&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-emacs-lisp" data-lang="emacs-lisp">&lt;span style="display:flex;">&lt;span>(jira-to-org--item-to-org-heading ITEM &lt;span style="color:#66d9ef">&amp;amp;optional&lt;/span> SPRINT)
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>Convert a &lt;code>jira-to-org-item&lt;/code> struct to an org-mode heading string.&lt;/p>
&lt;h2 id="contributing">Contributing&lt;/h2>
&lt;p>Contributions are welcome! Please submit issues or pull requests on the project repository.&lt;/p>
&lt;h3 id="development-setup">Development Setup&lt;/h3>
&lt;ol>
&lt;li>Clone the repository&lt;/li>
&lt;li>Load &lt;code>jira-to-org.el&lt;/code> in Emacs&lt;/li>
&lt;li>Make changes and test with sample Jira data&lt;/li>
&lt;li>Run byte-compilation: &lt;code>M-x byte-compile-file&lt;/code>&lt;/li>
&lt;/ol>
&lt;h3 id="testing">Testing&lt;/h3>
&lt;p>Create a test file with sample Jira data and verify parsing:&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-emacs-lisp" data-lang="emacs-lisp">&lt;span style="display:flex;">&lt;span>(with-temp-buffer
&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;To Do\nMM-1\nTest Issue\nAssignee: Test User\nPriority: High\n&amp;#34;&lt;/span>)
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> (jira-to-org-parse-buffer))
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;h2 id="license">License&lt;/h2>
&lt;p>This package is provided as-is for personal and professional use.&lt;/p>
&lt;h2 id="changelog">Changelog&lt;/h2>
&lt;h3 id="version-1-dot-0">Version 1.0&lt;/h3>
&lt;ul>
&lt;li>Initial release&lt;/li>
&lt;li>Basic parsing of Jira sprint data&lt;/li>
&lt;li>Configurable mappings for assignees, priorities, and statuses&lt;/li>
&lt;li>Buffer, region, and clipboard parsing commands&lt;/li>
&lt;li>Update existing entries from fresh Jira data&lt;/li>
&lt;/ul>
&lt;h2 id="related-packages">Related Packages&lt;/h2>
&lt;ul>
&lt;li>&lt;a href="https://github.com/ahungry/org-jira">org-jira&lt;/a> - Full bidirectional sync with Jira (requires API access)&lt;/li>
&lt;li>&lt;a href="https://github.com/nyyManni/jiralib2">jiralib2&lt;/a> - Jira REST API library for Emacs&lt;/li>
&lt;li>&lt;a href="https://github.com/baohaojun/org-jira">org-jira (baohaojun)&lt;/a> - Alternative Jira integration&lt;/li>
&lt;/ul></description></item><item><title>The Ping Localhost Hack for Emacs Shell on Windows</title><link>https://www.emacs.dyerdwelling.family/emacs/20251101152419-emacs--the-ping-localhost-hack-for-emacs-shell-on-windows/</link><pubDate>Sat, 01 Nov 2025 15:24:00 +0000</pubDate><author>James Dyer</author><guid>https://www.emacs.dyerdwelling.family/emacs/20251101152419-emacs--the-ping-localhost-hack-for-emacs-shell-on-windows/</guid><description>&lt;p>For a little while now, I&amp;rsquo;ve been developing software that needs to run on Windows (I know, I know!) and occasionally firing up batch scripts from within Emacs shell. Everything was working perfectly until I needed to add a simple delay between commands. That&amp;rsquo;s when I discovered one of Windows batch scripting&amp;rsquo;s more peculiar quirks.&lt;/p>
&lt;figure>&lt;img src="https://www.emacs.dyerdwelling.family/emacs/20251101152419-emacs--The-Ping-Localhost-Hack-for-Emacs-Shell-on-Windows.jpg" width="100%">
&lt;/figure>
&lt;p>The obvious solution for adding a delay in a Windows batch script is the &lt;code>timeout&lt;/code> command:&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-batch" data-lang="batch">&lt;span style="display:flex;">&lt;span>timeout /T 5
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>Simple enough, right? Well, it turns out that when you run this from Emacs shell (or any non-interactive environment), it just&amp;hellip; doesn&amp;rsquo;t work. The script either hangs indefinitely or skips the timeout entirely. Even adding the &lt;code>/NOBREAK&lt;/code> flag doesn&amp;rsquo;t reliably solve the issue.&lt;/p>
&lt;p>The &lt;code>timeout&lt;/code> command is actually an interactive program that expects to interact with a proper Windows console. It&amp;rsquo;s designed to wait for user input OR timeout, whichever comes first. When running from Emacs shell:&lt;/p>
&lt;ul>
&lt;li>There&amp;rsquo;s no proper console attachment that &lt;code>timeout&lt;/code> can latch onto&lt;/li>
&lt;li>Input redirection gets confused&lt;/li>
&lt;li>The TTY limitations of Emacs shell mean &lt;code>timeout&lt;/code> can&amp;rsquo;t find what it&amp;rsquo;s looking for&lt;/li>
&lt;/ul>
&lt;p>This is particularly frustrating when you&amp;rsquo;ve got a perfectly good batch script that works fine from cmd.exe but fails spectacularly when launched from your beloved Emacs environment.&lt;/p>
&lt;p>Here&amp;rsquo;s where things get interesting (and slightly ridiculous). The solution that the Windows community by all accounts has rallied around is the &lt;code>ping&lt;/code> command:&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-batch" data-lang="batch">&lt;span style="display:flex;">&lt;span>ping 127.0.0.1 -n 6 &amp;gt; nul
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>So here we are using a network diagnostic tool as a timer. But it works brilliantly! Here&amp;rsquo;s why:&lt;/p>
&lt;ul>
&lt;li>&lt;code>ping&lt;/code> is completely non-interactive - it just runs and exits&lt;/li>
&lt;li>Each ping takes roughly 1 second by default&lt;/li>
&lt;li>The &lt;code>-n&lt;/code> flag specifies the number of pings, so &lt;code>-n 6&lt;/code> gives you approximately 5 seconds (n-1)&lt;/li>
&lt;li>It&amp;rsquo;s available on every Windows system, no exceptions&lt;/li>
&lt;li>It doesn&amp;rsquo;t care about console attachment, TTY setup, or any of that nonsense&lt;/li>
&lt;/ul>
&lt;p>In my batch scripts that launch from Emacs, I&amp;rsquo;ve settled on this pattern:&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-batch" data-lang="batch">&lt;span style="display:flex;">&lt;span>@&lt;span style="color:#66d9ef">echo&lt;/span> off
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#66d9ef">echo&lt;/span> Starting process...
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>ping 127.0.0.1 -n 3 &amp;gt; nul
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#66d9ef">echo&lt;/span> Continuing after 2 second delay...
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>The &lt;code>&amp;gt; nul&lt;/code> redirects all the ping output to nowhere, so you don&amp;rsquo;t see the typical ping response spam in your output. Clean, simple, and most importantly - it actually works!&lt;/p>
&lt;p>This whole situation is a perfect example of the kind of head-scratching workarounds you encounter when working across different environments. Emacs shell on Windows is already operating in a slightly weird space - it&amp;rsquo;s not quite a native Windows console, not quite a Unix-like shell, but something in between.&lt;/p>
&lt;p>After adding this to my batch scripts, everything now runs smoothly from Emacs shell on Windows. The software launches, pauses where it needs to, and continues without any console-related tantrums.&lt;/p>
&lt;p>Is it elegant? Not particularly. Is it a proper use of the ping command? Absolutely not. Does it work perfectly every single time? You bet it does!&lt;/p>
&lt;p>And really, isn&amp;rsquo;t that the most important thing? Sometimes the best solution isn&amp;rsquo;t the most theoretically pure one - it&amp;rsquo;s the one that just works, regardless of how strange it might look to an outsider.&lt;/p></description></item><item><title>Planning my Weekly Meals in Emacs!</title><link>https://www.emacs.dyerdwelling.family/emacs/20250926100543-emacs--planning-my-weekly-meals-in-emacs/</link><pubDate>Fri, 26 Sep 2025 10:05:00 +0100</pubDate><author>James Dyer</author><guid>https://www.emacs.dyerdwelling.family/emacs/20250926100543-emacs--planning-my-weekly-meals-in-emacs/</guid><description>&lt;p>And what have I been working on recently?, well lets consider the following questions&amp;hellip;&lt;/p>
&lt;ul>
&lt;li>What should we eat Monday through Friday?&lt;/li>
&lt;li>What about the weekend when we have more time to cook?&lt;/li>
&lt;li>Didn&amp;rsquo;t we just have pasta last week? And the week before?&lt;/li>
&lt;li>How do I plan for batch cooking without ending up with the same meal rotation?&lt;/li>
&lt;li>Where did I put that list of meal ideas again?&lt;/li>
&lt;/ul>
&lt;figure>&lt;img src="https://www.emacs.dyerdwelling.family/emacs/20250926100543-emacs--Planning-my-Weekly-meals-in-Emacs.jpg" width="100%">
&lt;/figure>
&lt;p>And what has all this to do with Emacs I hear to you say?, well as some of you might have guessed I thought I would let Emacs take the stress out of these types of decisions by writing a very simple meal planning package and instead of trying to be everything to everyone I have just decided to focus it on solving one specific problem: &lt;strong>generating varied weekly meal plans that support batch cooking workflows&lt;/strong>.&lt;/p>
&lt;p>Here is a link to the package : &lt;a href="https://github.com/captainflasmr/meal-planner">https://github.com/captainflasmr/meal-planner&lt;/a>&lt;/p>
&lt;p>Here&amp;rsquo;s how it works:&lt;/p>
&lt;p>Your meals are stored in simple text files, one meal per line:&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-bash" data-lang="bash">&lt;span style="display:flex;">&lt;span>Chicken curry
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>Pizza
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>Lasagna
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>Toad in the hole
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>Chilli con carne
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>Sausage bake
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>Pasta bake
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>and one file per category (which I shall explain later)&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-bash" data-lang="bash">&lt;span style="display:flex;">&lt;span>weekday-dinner.txt
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>weekday-lunch.txt
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>weekday-pudding.txt
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>weekday-sweet.txt
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>weekend-dessert.txt
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>weekend-dinner.txt
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>weekend-lunch.txt
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>Need to add a new meal? Just edit the text file. Want to remove something you&amp;rsquo;re tired of? delete the line and who knows, maybe for fun, add these files to your favourite Emacs LLM integration, gptel of course or my personal favourite (as I wrote it) &lt;a href="https://github.com/captainflasmr/ollama-buddy">https://github.com/captainflasmr/ollama-buddy&lt;/a> and see what myriad of fantastical foods it can add!&lt;/p>
&lt;p>I have simplified the whole concept as much as possible, so instead of planning individual meals for each day (which gets repetitive and tiresome), it recognizes that many of us batch cook, so:&lt;/p>
&lt;ul>
&lt;li>&lt;strong>Weekdays (Monday-Friday)&lt;/strong>: One set of meals for the entire week&lt;/li>
&lt;li>&lt;strong>Weekend (Saturday-Sunday)&lt;/strong>: A different set of meals for more relaxed cooking and possibly straight from the freezer!&lt;/li>
&lt;/ul>
&lt;p>When you run &lt;code>M-x meal-planner-generate-week&lt;/code>, you will get 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-bash" data-lang="bash">&lt;span style="display:flex;">&lt;span>&lt;span style="color:#f92672">===&lt;/span> MEAL PLAN - Week 42 &lt;span style="color:#f92672">===&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>WEEKDAYS &lt;span style="color:#f92672">(&lt;/span>Monday-Friday&lt;span style="color:#f92672">)&lt;/span>:
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> Weekday lunch: Chicken curry batch
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> Weekday sweet: Apple slices
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> Weekday dinner: Pasta bake
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> Weekday pudding: Jam sponge
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>WEEKEND &lt;span style="color:#f92672">(&lt;/span>Saturday-Sunday&lt;span style="color:#f92672">)&lt;/span>:
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> Weekend lunch: Omelette
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> Weekend dinner: Beef stew
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> Weekend dessert: Eton mess
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>Also the package automatically tracks what you&amp;rsquo;ve eaten in recent weeks and avoids suggesting the same combinations. No more &amp;ldquo;didn&amp;rsquo;t we just have this last week?&amp;rdquo; moments.&lt;/p>
&lt;p>The package supports different types of meals to match my current real-world eating patterns:&lt;/p>
&lt;p>&lt;strong>Weekday Categories:&lt;/strong>&lt;/p>
&lt;ul>
&lt;li>Lunch meals (lunchbox-friendly)&lt;/li>
&lt;li>Sweet snacks/desserts&lt;/li>
&lt;li>Main dinner meals&lt;/li>
&lt;li>Evening puddings/desserts&lt;/li>
&lt;/ul>
&lt;p>&lt;strong>Weekend Categories:&lt;/strong>&lt;/p>
&lt;ul>
&lt;li>Leisurely lunch meals&lt;/li>
&lt;li>Weekend dinner meals&lt;/li>
&lt;li>Weekend desserts&lt;/li>
&lt;/ul>
&lt;p>Here&amp;rsquo;s how meal-planner.el fits into our weekly routine:&lt;/p>
&lt;p>&lt;strong>Sunday Morning&lt;/strong>: Run &lt;code>M-x meal-planner-generate-week&lt;/code> while having coffee. The package suggests a week&amp;rsquo;s worth of meals, carefully avoiding anything we&amp;rsquo;ve had recently.&lt;/p>
&lt;p>&lt;strong>Sunday Afternoon&lt;/strong>: Use &lt;code>M-x meal-planner-edit-category&lt;/code> to quickly add any new meal ideas we&amp;rsquo;ve discovered, or remove things we&amp;rsquo;re temporarily tired of.&lt;/p>
&lt;p>&lt;strong>Shopping&lt;/strong>: The meal plan gives us a clear shopping list foundation for when we do our weekly online order.&lt;/p>
&lt;p>&lt;strong>Weekday Evenings&lt;/strong>: No decisions needed! We already know what we&amp;rsquo;re having, and the ingredients are ready to go!&lt;/p>
&lt;p>Installation is straightforward:&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>&lt;span style="color:#75715e">;; Add to your init.el&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>(require &lt;span style="color:#e6db74">&amp;#39;meal-planner&lt;/span>)
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>Then run the setup command to create sample meal files:&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>M-x meal-planner-setup-data-files
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>This creates a &lt;code>~/.emacs.d/meal-data/&lt;/code> directory with sample meals for each category. Edit these files to match your preferences, then generate your first meal plan:&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>M-x meal-planner-generate-week
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>This package embodies one aspect I love about Emacs, taking a real-world problem and solving it in an elegant, customizable way. Now my brain has a little more space to accommodate more Emacs rabbit holes!&lt;/p></description></item><item><title>Debugging Software Breakage with Git Stash and Emacs</title><link>https://www.emacs.dyerdwelling.family/emacs/20250826103741-emacs--debugging-software-breakage-with-git-stash-and-emacs/</link><pubDate>Wed, 10 Sep 2025 08:00:00 +0100</pubDate><author>James Dyer</author><guid>https://www.emacs.dyerdwelling.family/emacs/20250826103741-emacs--debugging-software-breakage-with-git-stash-and-emacs/</guid><description>&lt;p>We&amp;rsquo;ve all been there, your code was working perfectly from a clean checkout, but after making a bunch of changes across multiple files, something has broken. The dreaded question arises! which change caused the break? This is the story of how a debugging session led me to discover gaps in Emacs&amp;rsquo; VC mode and ultimately create a custom solution.&lt;/p>
&lt;figure>&lt;img src="https://www.emacs.dyerdwelling.family/emacs/20250826103741-emacs--Debugging-Software-Breakage-with-Git-Stash-and-Emacs.jpg" width="100%">
&lt;/figure>
&lt;p>I started with a clean, working codebase. After implementing several features across different files, my software suddenly stopped working. The classic debugging nightmare, multiple changes, one (or more) breaking changes, and no clear path to the culprit.&lt;/p>
&lt;p>My debugging strategy as always is methodical. Over many years of software engineering I have learnt that you just need to figure out a systematic approach and then just get on with it!&lt;/p>
&lt;ol>
&lt;li>Start from the known-good base version&lt;/li>
&lt;li>Gradually reintroduce changes from my working set&lt;/li>
&lt;li>Test after each addition to identify the breaking point&lt;/li>
&lt;/ol>
&lt;p>Git stash turned out to be perfect for this workflow. Firstly I stashed all my changes, giving me a clean working directory to start from. My plan was to selectively apply portions of the stash, testing after each addition.&lt;/p>
&lt;p>Using Emacs&amp;rsquo; built-in VC mode, I could use &lt;code>vc-git-stash-show&lt;/code> to display my stashed changes in a diff buffer. From there, I could navigate through the files and selectively apply hunks using Emacs&amp;rsquo; diff mode commands. This gave me fine-grained control over which changes to reintroduce.&lt;/p>
&lt;p>As I progressed through applying changes, I realised that I would really like to keep an eye on what changes remained in my stash compared to my current working directory, basically like a dynamic diff to be regenerated after each application (like typically on an individual file using ediff). This would allow me to keep an eye on likely culprits as I move through the hunking process.&lt;/p>
&lt;p>In pure Git, this is straightforward:&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-bash" data-lang="bash">&lt;span style="display:flex;">&lt;span>git diff stash@&lt;span style="color:#f92672">{&lt;/span>0&lt;span style="color:#f92672">}&lt;/span>
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>But Emacs&amp;rsquo; VC mode doesn&amp;rsquo;t provide a command for this specific operation (I have found this not to be uncommon for Emacs vc-mode, but I still like it anyways!)&lt;/p>
&lt;p>Generally I think, Emacs&amp;rsquo; VC interface is designed to be VCS agnostic, which is both a strength and a limitation. While it provides excellent abstractions for common operations like &lt;code>vc-diff&lt;/code>, it doesn&amp;rsquo;t expose Git specific features like comparing against stash references.&lt;/p>
&lt;p>The available VC commands were:&lt;/p>
&lt;ul>
&lt;li>&lt;code>vc-diff&lt;/code> - compares working directory with HEAD or between revisions&lt;/li>
&lt;li>&lt;code>vc-git-stash-show&lt;/code> - shows the diff of a stash&lt;/li>
&lt;/ul>
&lt;p>But no &amp;ldquo;diff working directory against stash&amp;rdquo; command&lt;/p>
&lt;p>Now, it&amp;rsquo;s worth noting that Magit, does apparently provide this functionality, but I prefer to run on air-gapped systems (yes, that again!) where installing external packages isn&amp;rsquo;t always practical or desired. In such environments, I lean heavily on Emacs&amp;rsquo; built-in functionality and augment it with custom elisp when needed which is probably something I suspect I am likely to do in this case.&lt;/p>
&lt;p>I had an initial eshell idea on how to accomplish this!, for example you can redirect command line output to Emacs buffers using the &lt;code>#&amp;lt;buffer name&amp;gt;&lt;/code> syntax, so lets try that!&lt;/p>
&lt;p>I tried:&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-bash" data-lang="bash">&lt;span style="display:flex;">&lt;span>git diff stash@&lt;span style="color:#f92672">{&lt;/span>0&lt;span style="color:#f92672">}&lt;/span> &amp;gt; &lt;span style="color:#75715e">#&amp;lt;buffer *git-diff*&amp;gt; &amp;amp;&amp;amp; diff-mode&lt;/span>
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>This almost worked, but I encountered a timer error related to eshell&amp;rsquo;s command chaining.&lt;/p>
&lt;p>and then I tried:&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-bash" data-lang="bash">&lt;span style="display:flex;">&lt;span>git diff stash@&lt;span style="color:#f92672">{&lt;/span>0&lt;span style="color:#f92672">}&lt;/span> &amp;gt; &lt;span style="color:#75715e">#&amp;lt;buffer *git-stash-diff*&amp;gt; ; diff-mode&lt;/span>
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>After some experimentation, I still couldn&amp;rsquo;t quite get eshell to generate a buffer from a command and then initiate a mode. Of course I could just jump to the buffer and run it myself, but generally I wanted a solution to be easily repeatable.&lt;/p>
&lt;p>Right, lets scrap the eshell idea and lets fall back on my tried and tested method of writing a defun in elisp!:&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-git-diff-stash (stash-ref)
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#e6db74">&amp;#34;Diff working directory against specified stash&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> (interactive &lt;span style="color:#e6db74">&amp;#34;sStash reference (e.g., 0, 1, 2): &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:#e6db74">&amp;#34;*git-stash-diff*&amp;#34;&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> (&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">call-process&lt;/span> &lt;span style="color:#e6db74">&amp;#34;git&amp;#34;&lt;/span> &lt;span style="color:#66d9ef">nil&lt;/span> buffer &lt;span style="color:#66d9ef">t&lt;/span> &lt;span style="color:#e6db74">&amp;#34;diff&amp;#34;&lt;/span> (&lt;span style="color:#a6e22e">format&lt;/span> &lt;span style="color:#e6db74">&amp;#34;stash@{%s}&amp;#34;&lt;/span> stash-ref))
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> (diff-mode)
&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> (switch-to-buffer buffer)))
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>This function:&lt;/p>
&lt;ul>
&lt;li>Prompts for a stash reference (defaulting to numeric input like 0, 1, 2)&lt;/li>
&lt;li>Creates a dedicated buffer for the diff&lt;/li>
&lt;li>Runs &lt;code>git diff&lt;/code> against the specified stash&lt;/li>
&lt;li>Automatically applies &lt;code>diff-mode&lt;/code> for syntax highlighting&lt;/li>
&lt;li>Opens the buffer and positions the cursor at the beginning&lt;/li>
&lt;/ul>
&lt;p>The final step was to bind this command to the VC prefix map:&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>(&lt;span style="color:#a6e22e">define-key&lt;/span> vc-prefix-map (kbd &lt;span style="color:#e6db74">&amp;#34;S&amp;#34;&lt;/span>) &lt;span style="color:#e6db74">&amp;#39;my-git-diff-stash&lt;/span>)
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>&amp;ldquo;S&amp;rdquo; is currently used for a regex search of some kind which I currently don&amp;rsquo;t understand and hence am not using.&lt;/p>
&lt;p>Now I can use &lt;code>C-x v S&lt;/code> to quickly diff my working directory against any stash (although who knows when I will need this again!)&lt;/p>
&lt;p>With this in place, my debugging workflow became smoother&lt;/p>
&lt;ol>
&lt;li>Stash all changes&lt;/li>
&lt;li>Apply changes incrementally using &lt;code>vc-git-stash-show&lt;/code>&lt;/li>
&lt;li>Test the software after each addition&lt;/li>
&lt;li>When it still works, check what remains &lt;code>C-x v S&lt;/code>&lt;/li>
&lt;li>Continue applying changes from the remaining diff&lt;/li>
&lt;li>When it breaks, I have a good idea of the breaking issue&lt;/li>
&lt;/ol>
&lt;p>This experience taught me several valuable lessons:&lt;/p>
&lt;ol>
&lt;li>
&lt;p>&lt;strong>VC mode&amp;rsquo;s limitations&lt;/strong>: While Emacs&amp;rsquo; VC interface is excellent for common operations, specialized Git workflows sometimes require custom solutions.&lt;/p>
&lt;/li>
&lt;li>
&lt;p>&lt;strong>The value of built-in solutions&lt;/strong>: Working in air-gapped environments has taught me to maximize Emacs&amp;rsquo; built-in capabilities before reaching for external packages. While Magit would have solved this problem out of the box, building the solution myself using VC mode and custom elisp keeps dependencies minimal and increases my understanding of both Git and Emacs internals.&lt;/p>
&lt;/li>
&lt;li>
&lt;p>&lt;strong>Eshell&amp;rsquo;s power&lt;/strong>: The ability to redirect command output directly to Emacs buffers is incredibly useful, even if it has some quirks with command chaining and in the end I never really got it to work, but it is in my brain more concretely now as this blog post now exists!&lt;/p>
&lt;/li>
&lt;li>
&lt;p>&lt;strong>Integration matters&lt;/strong>: Binding custom functions to standard keymaps makes them feel like native features.&lt;/p>
&lt;/li>
&lt;/ol></description></item><item><title>Customizing Emacs Completion: From Fido's Fuzzy Matching to Literal Substring</title><link>https://www.emacs.dyerdwelling.family/emacs/20250905100614-emacs--customizing-emacs-completion-from-fidos-fuzzy-matching-to-literal-substring-search/</link><pubDate>Fri, 05 Sep 2025 10:06:00 +0100</pubDate><author>James Dyer</author><guid>https://www.emacs.dyerdwelling.family/emacs/20250905100614-emacs--customizing-emacs-completion-from-fidos-fuzzy-matching-to-literal-substring-search/</guid><description>&lt;p>For my completion framework, I&amp;rsquo;m currently using &lt;code>fido-mode&lt;/code>, and more recently, &lt;code>fido-vertical-mode&lt;/code>. However, I&amp;rsquo;m scratching yet another itch in my ongoing quest to be more efficient in Emacs, specifically to jump to files more quickly. I explored this in a previous post where I enhanced the &lt;code>recentf&lt;/code> functionality to work through completing-read in a predictable order, but what about the completing-read interface itself?&lt;/p>
&lt;figure>&lt;img src="https://www.emacs.dyerdwelling.family/emacs/20250905100614-emacs--Customizing-Emacs-Completion:-From-Fido%27s-Fuzzy-Matching-to-Literal-Substring-Search.jpg" width="100%">
&lt;/figure>
&lt;p>This happens to me often in Emacs, there is a subconscious functional annoyance which eventually bubbles to the surface and this case the surface bubble revolves around fido&amp;rsquo;s fuzzy matching behaviour. Simply put, I don&amp;rsquo;t like it!&lt;/p>
&lt;p>While it can be helpful for discovering files and commands you partially remember, sometimes you know exactly what you&amp;rsquo;re looking for and want a more literal, predictable search experience, in fact for me now, I would say it is not just sometimes, but always!. The fuzzy matching is finding too many candidates when I type in a few characters and really I want a contiguous input string to be literally matched.&lt;/p>
&lt;p>This post chronicles my journey from fido&amp;rsquo;s flex matching to a custom setup that provides literal substring matching, perfect for when you know what you want and just want to type it directly.&lt;/p>
&lt;p>Hang on a sec, can&amp;rsquo;t I just change the completion style?, this should be easy!&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 completion-styles &lt;span style="color:#f92672">&amp;#39;&lt;/span>(&lt;span style="color:#a6e22e">substring&lt;/span> basic))
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>But that has no effect!, boooo!&lt;/p>
&lt;p>Anyways, that was a quick attempt at a fix, in the meantime lets explore flex a little bit more and &lt;code>icomplete&lt;/code> (which is the underpinning completion technology of fido) and see if we cam come up with a robust solution.&lt;/p>
&lt;p>Fido-mode use what&amp;rsquo;s called &amp;ldquo;flex&amp;rdquo; completion by default. This means that when you type &lt;code>abc&lt;/code>, it will match files like &lt;code>a_long_b_filename_c.txt&lt;/code> because it finds the letters a, b, and c in that order, even with other characters between them.&lt;/p>
&lt;p>While this flexibility is powerful, it can be frustrating when you want to search for a specific substring. If you&amp;rsquo;re looking for a file named &lt;code>project-abc-config.txt&lt;/code>, you might expect typing &lt;code>abc&lt;/code> to prioritize that match, but flex matching might show you &lt;code>a_big_collection.txt&lt;/code> first instead.&lt;/p>
&lt;p>So back to my initial attempt at a fix by setting the &lt;code>completion-styles&lt;/code> variable. The &lt;code>substring&lt;/code> style matches your input as a contiguous block anywhere within candidates, while &lt;code>basic&lt;/code> does prefix matching. This seemed like exactly what I wanted, I just need to find a way to set it and to make it stick.&lt;/p>
&lt;p>After some digging into the source code, I found the culprit in &lt;code>icomplete.el&lt;/code>. The &lt;code>icomplete--fido-mode-setup&lt;/code> function contains the following:&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 icomplete--fido-mode-setup ()
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#e6db74">&amp;#34;Setup &lt;/span>&lt;span style="color:#e6db74">`fido-mode&amp;#39;&lt;/span>&lt;span style="color:#e6db74">&amp;#39;s minibuffer.&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> (when (and icomplete-mode (icomplete-simple-completing-p))
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#75715e">;; ... other settings ...&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> (setq-local completion-styles &lt;span style="color:#f92672">&amp;#39;&lt;/span>(flex) &lt;span style="color:#75715e">; This line forces flex!&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> completion-flex-nospace &lt;span style="color:#66d9ef">nil&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#75715e">;; ... more settings ...&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> )))
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>This function runs every time you enter the minibuffer, forcibly overriding any &lt;code>completion-styles&lt;/code> setting you might have configured. This explains why my &lt;code>setq&lt;/code> had no effect, fido was resetting it on every use!&lt;/p>
&lt;p>Rather than fight fido&amp;rsquo;s opinionated behaviour, I could instead switch to &lt;code>icomplete-vertical-mode&lt;/code>, which provides a similar interface but respects the standard completion configuration.&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>(icomplete-vertical-mode &lt;span style="color:#ae81ff">1&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">;; scroll list rather than rotating&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>(setq icomplete-scroll &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">;; Make completion case-insensitive&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>(setq completion-ignore-case &lt;span style="color:#66d9ef">t&lt;/span>)
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>(setq read-file-name-completion-ignore-case &lt;span style="color:#66d9ef">t&lt;/span>)
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>(setq read-buffer-completion-ignore-case &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>(with-eval-after-load &lt;span style="color:#e6db74">&amp;#39;icomplete&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> (setq completion-styles &lt;span style="color:#f92672">&amp;#39;&lt;/span>(&lt;span style="color:#a6e22e">substring&lt;/span> basic partial-completion emacs22)))
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>This gave me the literal substring matching I wanted and I think I have managed to set up everything else to the way fido comes out of the box.&lt;/p>
&lt;p>However, there was one more hurdle.&lt;/p>
&lt;p>By default, &lt;code>icomplete-vertical-mode&lt;/code> requires you to explicitly select a completion before submitting with &lt;code>C-m&lt;/code> (Enter) which is a keybinding I had grown accustomed to using in fido. This adds an extra confirmation step that fido-mode doesn&amp;rsquo;t have. There is a way around this however and that is to adapt to the keybinding &lt;code>C-j&lt;/code> which typically is more of a do literal action then exit type of thing, where C-m is more of just a simple Enter/action. I am willing to adapt to this keybinding.&lt;/p>
&lt;p>So this works pretty well for me really, but can I not just get &lt;code>completion-styles&lt;/code> to stick for fido?, even though I have a solution I really want to see if I can adjust fido&amp;rsquo;s default functionality.&lt;/p>
&lt;p>Well simply I used an advice function to wrap around the original fido setup function and set up the &lt;code>completion-styles&lt;/code> local variable after fido has done its thing:&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-fido-completion-styles-advice (&lt;span style="color:#66d9ef">&amp;amp;rest&lt;/span> _args)
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#e6db74">&amp;#34;Override completion styles after fido setup.&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> (when (and fido-mode (icomplete-simple-completing-p))
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> (setq-local completion-styles &lt;span style="color:#f92672">&amp;#39;&lt;/span>(&lt;span style="color:#a6e22e">substring&lt;/span> basic partial-completion))))
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>(advice-add &lt;span style="color:#e6db74">&amp;#39;icomplete--fido-mode-setup&lt;/span> :after &lt;span style="color:#a6e22e">#&amp;#39;&lt;/span>my-fido-completion-styles-advice)
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>Now I have two options for using completion in Emacs the way I want it and now I can find files, or anything else for that matter much more quickly.&lt;/p>
&lt;p>This journey taught me several important lessons about Emacs customization:&lt;/p>
&lt;ol>
&lt;li>
&lt;p>&lt;strong>Read the source&lt;/strong>: When configuration variables don&amp;rsquo;t seem to work as expected, the source code often reveals why.&lt;/p>
&lt;/li>
&lt;li>
&lt;p>&lt;strong>Local vs. global settings&lt;/strong>: Fido uses &lt;code>setq-local&lt;/code> to override settings per-buffer, which is why global &lt;code>setq&lt;/code> calls don&amp;rsquo;t work.&lt;/p>
&lt;/li>
&lt;li>
&lt;p>&lt;strong>There&amp;rsquo;s always another way&lt;/strong>: Emacs&amp;rsquo; flexibility means there are usually multiple approaches to achieving the same goal.&lt;/p>
&lt;/li>
&lt;/ol>
&lt;p>While fido-mode&amp;rsquo;s fuzzy matching is excellent for discovery and exploration, I just wanted the predictability of literal substring matching and with a small advice function, you can have the best of both worlds!&lt;/p></description></item><item><title>A Better Way to Indent Your Entire Buffer in Emacs?</title><link>https://www.emacs.dyerdwelling.family/emacs/20250826095622-emacs--a-better-way-to-indent-your-entire-buffer-in-emacs/</link><pubDate>Tue, 26 Aug 2025 09:56:00 +0100</pubDate><author>James Dyer</author><guid>https://www.emacs.dyerdwelling.family/emacs/20250826095622-emacs--a-better-way-to-indent-your-entire-buffer-in-emacs/</guid><description>&lt;p>As an Emacs user, you&amp;rsquo;ve probably found yourself wanting to clean up the indentation of an entire file. The standard approach is to select all (&lt;code>C-x h&lt;/code>) and then run &lt;code>indent-region&lt;/code> (I think, correct me if I am wrong!), but this has an annoying side effect: it destroys your current mark position, which might have been carefully set for other operations.&lt;/p>
&lt;figure>&lt;img src="https://www.emacs.dyerdwelling.family/emacs/20250826095622-emacs--A-Better-Way-to-Indent-Your-Entire-Buffer-in-Emacs.jpg" width="100%">
&lt;/figure>
&lt;p>Also, and just whisper it and don&amp;rsquo;t tell anyone, but I have been using VSCode a little (only for work, of course!) and an indent by default seems to indent the whole file, which initially I thought would be really annoying, but actually it feels quite natural, as always at some point I would like to indent my code.&lt;/p>
&lt;p>This is Emacs however and there is a simple solution and I&amp;rsquo;ve added it to my Emacs configuration:&lt;/p>
&lt;p>So, just a recap on some typical workflow, it goes a little something like this:&lt;/p>
&lt;ol>
&lt;li>You&amp;rsquo;re working in a file with inconsistent indentation&lt;/li>
&lt;li>You want to fix the entire buffer&amp;rsquo;s formatting&lt;/li>
&lt;li>You run &lt;code>C-x h&lt;/code> (select all) followed by &lt;code>M-x indent-region&lt;/code>&lt;/li>
&lt;li>Your mark is now at the beginning of the buffer, disrupting your workflow&lt;/li>
&lt;/ol>
&lt;p>This is particularly frustrating when you&amp;rsquo;ve set a mark for a specific editing task and want to preserve that position.&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 indent-whole-buffer ()
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#e6db74">&amp;#34;Indent the entire buffer without affecting point or mark.&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> (save-excursion
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> (save-restriction
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> (indent-region (&lt;span style="color:#a6e22e">point-min&lt;/span>) (&lt;span style="color:#a6e22e">point-max&lt;/span>)))))
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>(global-set-key (kbd &lt;span style="color:#e6db74">&amp;#34;C-c i&amp;#34;&lt;/span>) &lt;span style="color:#e6db74">&amp;#39;indent-whole-buffer&lt;/span>)
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>The function indents from &lt;code>(point-min)&lt;/code> to &lt;code>(point-max)&lt;/code>, covering the entire buffer, then restores everything to exactly how it was before, except now with proper indentation.&lt;/p>
&lt;p>After adding this to your configuration, simply press &lt;code>C-c i&lt;/code> (or whatever keybinding you prefer) to indent your entire buffer. Your cursor stays put, your mark remains set, and your file gets beautifully formatted!&lt;/p></description></item><item><title>Fast Recent File Navigation in Emacs</title><link>https://www.emacs.dyerdwelling.family/emacs/20250815071935-emacs--fast-file-navigation-in-emacs/</link><pubDate>Sat, 16 Aug 2025 19:07:00 +0100</pubDate><author>James Dyer</author><guid>https://www.emacs.dyerdwelling.family/emacs/20250815071935-emacs--fast-file-navigation-in-emacs/</guid><description>&lt;p>As an Emacs user, you&amp;rsquo;re always hunting for ways to shave milliseconds off common tasks and generally noodling around and shaving some more of that yak!. File switching is one of those operations you do hundreds of times a day, so even small improvements compound dramatically. Today, I want to share a workflow that I have been tinkering with that combines the best of Emacs&amp;rsquo;s built-in recent file tracking with modern completion interfaces.&lt;/p>
&lt;figure>&lt;img src="https://www.emacs.dyerdwelling.family/emacs/20250815071935-emacs--Fast-File-Navigation-in-Emacs.jpg" width="100%">
&lt;/figure>
&lt;p>Emacs gives us several ways out of the box to quickly access files:&lt;/p>
&lt;ul>
&lt;li>find-file&lt;/li>
&lt;li>switch-to-buffer for open buffers&lt;/li>
&lt;li>Registers&lt;/li>
&lt;li>Bookmarks&lt;/li>
&lt;li>recentf-mode for recently accessed files&lt;/li>
&lt;/ul>
&lt;p>and I have a funny feeling there are probably more.&lt;/p>
&lt;p>Each has its place, but they all have limitations. switch-to-buffer only shows currently open buffers, and with fido-mode, I have found that the most frequently used buffers don&amp;rsquo;t always bubble to the top predictably, I may need to look a little more into this though. Registers are great but require manual setup and only remember the position when you set them, not your last edit location.&lt;/p>
&lt;p>What I really wanted was instant access to my recently edited files, with each file opening exactly where I left off!&lt;/p>
&lt;p>I thought I had become quite efficient at file switching through fido completion and dired but only recently I realised I was navigating through dired to find files way too often and wasting time. I think this is probably typical of an Emacsers journey and after a while of continuous minor toil there comes a time to take action and shave off those milliseconds and improve flow.&lt;/p>
&lt;p>The solution starts by enabling two built-in Emacs features:&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>(save-place-mode &lt;span style="color:#ae81ff">1&lt;/span>)
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>(recentf-mode &lt;span style="color:#ae81ff">1&lt;/span>)
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>(setq recentf-max-menu-items &lt;span style="color:#ae81ff">10&lt;/span>)
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>(setq recentf-max-saved-items &lt;span style="color:#ae81ff">10&lt;/span>)
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>save-place-mode automatically remembers your cursor position in every file and restores it when you reopen the file, it was the first time I had heard of this when figuring all this out and couldn&amp;rsquo;t believe I didn&amp;rsquo;t know about it before (not an uncommon Emacs experience), this works across Emacs sessions, so you get that &amp;ldquo;pick up exactly where I left off&amp;rdquo; experience.&lt;/p>
&lt;p>And recentf-mode maintains a persistent list of recently visited files, ordered chronologically. Unlike switch-to-buffer, this survives Emacs restarts and gives you a true history of your work and I thought in this case it better to limit the list size as much as possible for greater clarity.&lt;/p>
&lt;p>The traditional recentf-open-files command shows a nice ordered list in a buffer but to some it could feel clunky compared to modern completion interfaces. There is also recentf-open which does go through completing read but then reorders the recentf list for some reason, so to me the obvious solution is to push the recent files list through completing-read myself:&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/fido-recentf ()
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#e6db74">&amp;#34;Use fido to select from recently opened files.&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> (&lt;span style="color:#a6e22e">completing-read&lt;/span> &lt;span style="color:#e6db74">&amp;#34;Recent file: &amp;#34;&lt;/span> recentf-list &lt;span style="color:#66d9ef">nil&lt;/span> &lt;span style="color:#66d9ef">t&lt;/span> &lt;span style="color:#66d9ef">nil&lt;/span> &lt;span style="color:#e6db74">&amp;#39;recentf-list&lt;/span>))
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>To get this to work the way I wanted, I had to supply recentf-list also as the last argument. By using the same symbol for both the collection and the history, we tell the completion system to respect the original ordering. Your most recently accessed files should stay at the top!&lt;/p>
&lt;p>In my final solution I have decided to keep the old recentf-open-files interface as an option. Strangely I actually like the old list in a buffer interface so I have included it in my final version as an option.&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/fido-recentf (arg)
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#e6db74">&amp;#34;Use fido to select from recently opened files.
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#e6db74">With universal argument, use the traditional recentf-open-files interface.&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> (interactive &lt;span style="color:#e6db74">&amp;#34;P&amp;#34;&lt;/span>)
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> (if arg
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> (recentf-open-files)
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> (find-file (&lt;span style="color:#a6e22e">completing-read&lt;/span> &lt;span style="color:#e6db74">&amp;#34;Recent file: &amp;#34;&lt;/span> recentf-list &lt;span style="color:#66d9ef">nil&lt;/span> &lt;span style="color:#66d9ef">t&lt;/span> &lt;span style="color:#66d9ef">nil&lt;/span> &lt;span style="color:#e6db74">&amp;#39;recentf-list&lt;/span>))))
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>Now M-x my/fido-recentf gives you completing read, while C-u M-x my/fido-recentf drops you into the classic numbered list.&lt;/p>
&lt;p>Bind this to something convenient (I use M-o) and your file navigation transforms! (well that might be a bit of an exaggeration)&lt;/p>
&lt;p>This might seem like a small optimization, but it exemplifies what makes Emacs special. We&amp;rsquo;re not just using the tools as shipped, we&amp;rsquo;re composing them in novel ways to create something perfectly tailored to our needs.&lt;/p>
&lt;p>That&amp;rsquo;s the kind of workflow improvement that makes every day at the keyboard just a little bit more pleasant, and those small pleasures add up to something significant over months and years of coding.&lt;/p></description></item><item><title>View-Mode - Emacs's Hidden Modal Editing Gem?</title><link>https://www.emacs.dyerdwelling.family/emacs/20250731123820-emacs--discovering-view-mode-emacss-hidden-modal-editing-gem/</link><pubDate>Fri, 01 Aug 2025 08:30:00 +0100</pubDate><author>James Dyer</author><guid>https://www.emacs.dyerdwelling.family/emacs/20250731123820-emacs--discovering-view-mode-emacss-hidden-modal-editing-gem/</guid><description>&lt;hr>
&lt;div class="ox-hugo-toc toc local">
&lt;ul>
&lt;li>&lt;a href="https://www.emacs.dyerdwelling.family/emacs/20250731123820-emacs--discovering-view-mode-emacss-hidden-modal-editing-gem/#the-rabbit-hole">The Rabbit Hole&lt;/a>&lt;/li>
&lt;li>&lt;a href="https://www.emacs.dyerdwelling.family/emacs/20250731123820-emacs--discovering-view-mode-emacss-hidden-modal-editing-gem/#view-mode">View Mode&lt;/a>&lt;/li>
&lt;li>&lt;a href="https://www.emacs.dyerdwelling.family/emacs/20250731123820-emacs--discovering-view-mode-emacss-hidden-modal-editing-gem/#the-setup">The Setup&lt;/a>&lt;/li>
&lt;li>&lt;a href="https://www.emacs.dyerdwelling.family/emacs/20250731123820-emacs--discovering-view-mode-emacss-hidden-modal-editing-gem/#baby-proofing">Baby Proofing&lt;/a>&lt;/li>
&lt;li>&lt;a href="https://www.emacs.dyerdwelling.family/emacs/20250731123820-emacs--discovering-view-mode-emacss-hidden-modal-editing-gem/#cursor">Cursor&lt;/a>&lt;/li>
&lt;li>&lt;a href="https://www.emacs.dyerdwelling.family/emacs/20250731123820-emacs--discovering-view-mode-emacss-hidden-modal-editing-gem/#subtleties">Subtleties&lt;/a>&lt;/li>
&lt;li>&lt;a href="https://www.emacs.dyerdwelling.family/emacs/20250731123820-emacs--discovering-view-mode-emacss-hidden-modal-editing-gem/#foibles">Foibles&lt;/a>&lt;/li>
&lt;li>&lt;a href="https://www.emacs.dyerdwelling.family/emacs/20250731123820-emacs--discovering-view-mode-emacss-hidden-modal-editing-gem/#improvements">Improvements?&lt;/a>&lt;/li>
&lt;li>&lt;a href="https://www.emacs.dyerdwelling.family/emacs/20250731123820-emacs--discovering-view-mode-emacss-hidden-modal-editing-gem/#conclusion">Conclusion&lt;/a>&lt;/li>
&lt;/ul>
&lt;/div>
&lt;!--endtoc-->
&lt;hr>
&lt;h2 id="the-rabbit-hole">The Rabbit Hole&lt;/h2>
&lt;p>I&amp;rsquo;ve fallen down another rabbit hole, but then again, this is Emacs we&amp;rsquo;re talking about!&lt;/p>
&lt;p>In this case it relates to a fundamental editing principle.&lt;/p>
&lt;p>I was trying to understand some code, so of course that involved plenty of point/cursor navigation, scrolling, etc. After an hour or so of doing this, I started to wonder if there was a more efficient navigation method involving single keypresses. If I know I only want to view the file, then why not get rid of this modifier key nonsense? 😁 I&amp;rsquo;m aware that the basic functionality I&amp;rsquo;m talking about here is modal editing.&lt;/p>
&lt;p>There are many different packages out there from &lt;strong>boon&lt;/strong> to &lt;strong>meow&lt;/strong>. So does Emacs have something built-in? Well, throughout my series of blog posts, I often ask this question, and the answer always comes back emphatically the same: &lt;strong>&amp;ldquo;Why yes, of course it does!&amp;rdquo;&lt;/strong> This might be a little misleading, as though there isn&amp;rsquo;t a specific built-in that gives modal editing right out of the box, there is a mode that can be tweaked a little to achieve something similar.&lt;/p>
&lt;h2 id="view-mode">View Mode&lt;/h2>
&lt;p>So what is the thing that&amp;rsquo;s been hiding in plain sight? Well, it is &lt;strong>view-mode&lt;/strong>. Although strictly not a modal editing option, I think with a little tweaking I could possibly turn it into one. After a bit of internet stumbling, I came across a Reddit post from someone who has tried something similar. I think I shall add my own spin. The link is &lt;a href="https://www.reddit.com/r/emacs/comments/fojc1y/using_viewmode_for_modal_navigation/">https://www.reddit.com/r/emacs/comments/fojc1y/using_viewmode_for_modal_navigation/&lt;/a>&lt;/p>
&lt;p>If you have been reading my posts over the years, you probably know my general design ethos on such a solution, and that is to use built-in Emacs-centric philosophies and to make sure it can easily be set up on an air-gapped system. Well, for this solution, I&amp;rsquo;m going to deviate slightly from this idiom and incorporate not just more Emacs-ish keybindings but also some Vim ones! My main reasoning for this is that evil-mode is very popular, and from time to time I have recourse to using Vim over ssh, and the &lt;code>jkhl&lt;/code> setup does feel pretty natural.&lt;/p>
&lt;p>View-mode has been part of Emacs forever, but its default keybindings feel a bit&amp;hellip; antiquated. &lt;strong>z&lt;/strong> and &lt;strong>w&lt;/strong> for scroll up and down? Space for page scroll, Return for forward one line, etc. (Maybe it works in the latter case, as it is more of a legacy Unixy vibe, but really I think we should be using more Emacs-ish keybindings, and as an option, why not Vim ones, as they tend to be mutually exclusive)&lt;/p>
&lt;h2 id="the-setup">The Setup&lt;/h2>
&lt;p>Of course, view-mode is completely customizable, so here&amp;rsquo;s my setup that combines the best of both worlds, familiar Emacs patterns and efficient Vim-style navigation:&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>&lt;span style="color:#75715e">;; Enable view-mode when entering read-only&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>(setq view-read-only &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">;; Enhanced keybindings&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>(with-eval-after-load &lt;span style="color:#e6db74">&amp;#39;view&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#75715e">;; More Emacs-ish keys&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">;; Navigation&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> (&lt;span style="color:#a6e22e">define-key&lt;/span> view-mode-map (kbd &lt;span style="color:#e6db74">&amp;#34;n&amp;#34;&lt;/span>) &lt;span style="color:#e6db74">&amp;#39;next-line&lt;/span>)
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> (&lt;span style="color:#a6e22e">define-key&lt;/span> view-mode-map (kbd &lt;span style="color:#e6db74">&amp;#34;p&amp;#34;&lt;/span>) &lt;span style="color:#e6db74">&amp;#39;previous-line&lt;/span>)
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> (&lt;span style="color:#a6e22e">define-key&lt;/span> view-mode-map (kbd &lt;span style="color:#e6db74">&amp;#34;f&amp;#34;&lt;/span>) &lt;span style="color:#e6db74">&amp;#39;forward-char&lt;/span>)
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> (&lt;span style="color:#a6e22e">define-key&lt;/span> view-mode-map (kbd &lt;span style="color:#e6db74">&amp;#34;b&amp;#34;&lt;/span>) &lt;span style="color:#e6db74">&amp;#39;backward-char&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">;; Beginning/end of line&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> (&lt;span style="color:#a6e22e">define-key&lt;/span> view-mode-map (kbd &lt;span style="color:#e6db74">&amp;#34;a&amp;#34;&lt;/span>) &lt;span style="color:#e6db74">&amp;#39;beginning-of-line&lt;/span>)
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> (&lt;span style="color:#a6e22e">define-key&lt;/span> view-mode-map (kbd &lt;span style="color:#e6db74">&amp;#34;e&amp;#34;&lt;/span>) &lt;span style="color:#e6db74">&amp;#39;end-of-line&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">;; Vim-ish keys&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">;; Quick exit to edit mode&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> (&lt;span style="color:#a6e22e">define-key&lt;/span> view-mode-map (kbd &lt;span style="color:#e6db74">&amp;#34;i&amp;#34;&lt;/span>) &lt;span style="color:#e6db74">&amp;#39;View-exit&lt;/span>)
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> (&lt;span style="color:#a6e22e">define-key&lt;/span> view-mode-map (kbd &lt;span style="color:#e6db74">&amp;#34;j&amp;#34;&lt;/span>) &lt;span style="color:#e6db74">&amp;#39;next-line&lt;/span>)
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> (&lt;span style="color:#a6e22e">define-key&lt;/span> view-mode-map (kbd &lt;span style="color:#e6db74">&amp;#34;k&amp;#34;&lt;/span>) &lt;span style="color:#e6db74">&amp;#39;previous-line&lt;/span>)
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> (&lt;span style="color:#a6e22e">define-key&lt;/span> view-mode-map (kbd &lt;span style="color:#e6db74">&amp;#34;h&amp;#34;&lt;/span>) &lt;span style="color:#e6db74">&amp;#39;backward-char&lt;/span>)
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> (&lt;span style="color:#a6e22e">define-key&lt;/span> view-mode-map (kbd &lt;span style="color:#e6db74">&amp;#34;l&amp;#34;&lt;/span>) &lt;span style="color:#e6db74">&amp;#39;forward-char&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">;; Page movement&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> (&lt;span style="color:#a6e22e">define-key&lt;/span> view-mode-map (kbd &lt;span style="color:#e6db74">&amp;#34;u&amp;#34;&lt;/span>) &lt;span style="color:#f92672">&amp;#39;&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> (View-scroll-page-backward &lt;span style="color:#ae81ff">3&lt;/span>)))
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> (&lt;span style="color:#a6e22e">define-key&lt;/span> view-mode-map (kbd &lt;span style="color:#e6db74">&amp;#34;d&amp;#34;&lt;/span>) &lt;span style="color:#f92672">&amp;#39;&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> (View-scroll-page-forward &lt;span style="color:#ae81ff">3&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">;; Beginning/end of line (Vim style)&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> (&lt;span style="color:#a6e22e">define-key&lt;/span> view-mode-map (kbd &lt;span style="color:#e6db74">&amp;#34;0&amp;#34;&lt;/span>) &lt;span style="color:#e6db74">&amp;#39;beginning-of-line&lt;/span>)
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> (&lt;span style="color:#a6e22e">define-key&lt;/span> view-mode-map (kbd &lt;span style="color:#e6db74">&amp;#34;$&amp;#34;&lt;/span>) &lt;span style="color:#e6db74">&amp;#39;end-of-line&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">;; Beginning/end of buffers&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> (&lt;span style="color:#a6e22e">define-key&lt;/span> view-mode-map (kbd &lt;span style="color:#e6db74">&amp;#34;g&amp;#34;&lt;/span>) &lt;span style="color:#e6db74">&amp;#39;beginning-of-buffer&lt;/span>)
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> (&lt;span style="color:#a6e22e">define-key&lt;/span> view-mode-map (kbd &lt;span style="color:#e6db74">&amp;#34;G&amp;#34;&lt;/span>) &lt;span style="color:#e6db74">&amp;#39;end-of-buffer&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">;; Other bespoke bindings&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> (&lt;span style="color:#a6e22e">define-key&lt;/span> view-mode-map (kbd &lt;span style="color:#e6db74">&amp;#34;;&amp;#34;&lt;/span>) &lt;span style="color:#e6db74">&amp;#39;other-window&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">define-key&lt;/span> view-mode-map (kbd &lt;span style="color:#e6db74">&amp;#34;SPC&amp;#34;&lt;/span>) &lt;span style="color:#e6db74">&amp;#39;nil&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">;; Quick toggle keys&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>(global-set-key (kbd &lt;span style="color:#e6db74">&amp;#34;C-&amp;lt;escape&amp;gt;&amp;#34;&lt;/span>) &lt;span style="color:#e6db74">&amp;#39;view-mode&lt;/span>)
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>(global-set-key (kbd &lt;span style="color:#e6db74">&amp;#34;C-&amp;lt;tab&amp;gt;&amp;#34;&lt;/span>) &lt;span style="color:#e6db74">&amp;#39;view-mode&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">;; Optional: return to view-mode after saving&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>(add-hook &lt;span style="color:#e6db74">&amp;#39;after-save-hook&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> (when (and &lt;span style="color:#a6e22e">buffer-file-name&lt;/span> (not view-mode))
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> (view-mode &lt;span style="color:#ae81ff">1&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">;; Visual feedback - box cursor in view mode, bar when editing&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>(add-hook &lt;span style="color:#e6db74">&amp;#39;view-mode-hook&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> (defun view-mode-hookee+ ()
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> (setq cursor-type (if view-mode &lt;span style="color:#e6db74">&amp;#39;box&lt;/span> &lt;span style="color:#e6db74">&amp;#39;bar&lt;/span>))))
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;h2 id="baby-proofing">Baby Proofing&lt;/h2>
&lt;figure>&lt;img src="https://www.emacs.dyerdwelling.family/emacs/20250731123820-emacs--Discovering-View-Mode-Emacss-Hidden-Modal-Editing-Gem.jpg" width="100%">
&lt;/figure>
&lt;p>Here&amp;rsquo;s where it gets interesting (possibly). I have a baby, and babies have this amazing ability to slam their tiny hands down on keyboards at the most inopportune moments. Nothing quite like a random key smash right in the middle of an important file!. Wouldn&amp;rsquo;t it be great to have a protective read-only shield over a file? So I decided, why not put all files into view-mode by default?, this way, accidental keypresses won&amp;rsquo;t modify anything, and I can intentionally switch to edit mode only when I actually need to make changes.&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>&lt;span style="color:#75715e">;; Enable view-mode for files by default&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>(add-hook &lt;span style="color:#e6db74">&amp;#39;find-file-hook&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> (unless (or (derived-mode-p &lt;span style="color:#e6db74">&amp;#39;dired-mode&lt;/span>))
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> (view-mode &lt;span style="color:#ae81ff">1&lt;/span>))))
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>I&amp;rsquo;m currently using the automatic activation to see how it goes, but I have to say I&amp;rsquo;m not completely comfortable with setting files to read-only mode. Although more baby-proof, it does mean I have to activate edit mode (or deactivate view-mode) each time I want to edit a file and that doesn&amp;rsquo;t feel very Emacsy to me.&lt;/p>
&lt;h2 id="cursor">Cursor&lt;/h2>
&lt;p>You will notice that in the setup there is a modification of the cursor depending on the mode state. Although the modeline will indicate if view-mode is on or off, I&amp;rsquo;m actually quite liking the differentiation in editing modes to be reflected in the cursor. It is simple, and my brain is starting to recognize instinctively which mode I am in with no extra visual baggage.&lt;/p>
&lt;h2 id="subtleties">Subtleties&lt;/h2>
&lt;p>It is also worth pointing out some more subtleties:&lt;/p>
&lt;ul>
&lt;li>
&lt;p>I have added the enabling of view-mode every time a file is saved. I am a habitual saver, I have no idea why (oh wait, habit!), so that means having to enter &amp;ldquo;edit mode&amp;rdquo; quite often. We shall see how this goes, but currently it means that I am not having to often flit back to view-mode after some characters are inserted as I would have saved and returned anyway!&lt;/p>
&lt;/li>
&lt;li>
&lt;p>In addition, there is a sneaky &lt;code>(setq view-read-only t)&lt;/code> which means that toggling a file to and from read-only via &lt;code>C-x C-q&lt;/code> will activate &lt;code>view-mode&lt;/code> accordingly. I&amp;rsquo;m not really using this at the moment, but it could come in useful if I start thinking more in a &lt;code>dired&lt;/code> manner, where I activate the edit mode in dired quite often to change filenames, so this could be muscle memory transferable. In the main, though, I don&amp;rsquo;t think setting this is going to hurt anything.&lt;/p>
&lt;/li>
&lt;li>
&lt;p>Another subtlety is the lambdas set for the page scroll. &lt;code>view-mode&lt;/code> has quite a neat implementation for scrolling, in that, with a prefix or a defun argument, the number of lines for a scroll can be set. I don&amp;rsquo;t often like to scroll full pages or even half pages, so I typically have set only a few lines for faster scrolling, this of course is configurable to your own tastes.&lt;/p>
&lt;/li>
&lt;li>
&lt;p>You can see that I have added in all the basic Emacs and Vim typical navigational keys, and in the case of Emacs, stripped of the modifier. This, for me, feels much more natural, and strangely, I find myself naturally jumping between the Vim and Emacs keys, for shame!&lt;/p>
&lt;/li>
&lt;li>
&lt;p>I have already disabled a key within the view-mode mode-map and that is SPC, I found myself often (so far) pressing space without toggling off view-mode and that would scroll one page down - annoying!&lt;/p>
&lt;/li>
&lt;li>
&lt;p>If I am using the vim keys often and it might be likely I am viewing files, maybe code files and maybe I have a single horizontal split, I might just like to have a single key to jump back and forward between windows. I chose &amp;ldquo;;&amp;rdquo; for a few reasons but the main one is that the key is adjacent to the other vim navigation keys.&lt;/p>
&lt;/li>
&lt;/ul>
&lt;p>It is worth noting, of course, that all normal Emacs navigational keybindings are still available, so you also have that option too within view-mode.&lt;/p>
&lt;h2 id="foibles">Foibles&lt;/h2>
&lt;p>So far I have noticed some, lets say, foibles&lt;/p>
&lt;ul>
&lt;li>
&lt;p>It is not uncommon that I would want changes that I have just made and saved to be undone, but when undoing I am in view-mode, and the reversal is rejected due to the file being read only!, I might have to think about this one.&lt;/p>
&lt;/li>
&lt;li>
&lt;p>In org-mode, with the speed keys enabled which gives you a range of org commands from a single character when the point is at the heading start obviously can cause a conflict. For example, &amp;ldquo;s&amp;rdquo; is mapped in speed keys to narrow section, but in view-mode mode it is an incremental search.&lt;/p>
&lt;/li>
&lt;/ul>
&lt;h2 id="improvements">Improvements?&lt;/h2>
&lt;ul>
&lt;li>
&lt;p>One Idea is maybe I can auto-deactivate view-mode when a typical Emacs edit keybinding is detected, but this is fraught with edge cases, but maybe.&lt;/p>
&lt;/li>
&lt;li>
&lt;p>Do I go further with a vim paradigm? I&amp;rsquo;m not going to lie here I like the &amp;ldquo;x&amp;rdquo; to delete a character and &amp;ldquo;dd&amp;rdquo; to delete a whole line (C-k (kill-line) is something I use all the time). Could I add these in without having to break out of view-mode and hence preserving a flow for fast edits?, not sure.&lt;/p>
&lt;/li>
&lt;/ul>
&lt;h2 id="conclusion">Conclusion&lt;/h2>
&lt;p>Anyways, the result is a surprisingly pleasant modal editing experience so far that already feels natural in Emacs while giving me the ergonomic benefits of Vim-style navigation and comfortable single-key navigation options.&lt;/p>
&lt;p>Plus, my codebase is now baby-resistant!&lt;/p></description></item><item><title>Ollama Buddy v1.0: A Simple AI Assistant</title><link>https://www.emacs.dyerdwelling.family/emacs/20250723083048-emacs--announcing-v1-ollama-buddy/</link><pubDate>Wed, 23 Jul 2025 09:20:00 +0100</pubDate><author>James Dyer</author><guid>https://www.emacs.dyerdwelling.family/emacs/20250723083048-emacs--announcing-v1-ollama-buddy/</guid><description>&lt;p>After months of development and refinement, I&amp;rsquo;m excited to announce &lt;strong>Ollama Buddy v1.0&lt;/strong> - an Emacs package that simply interfaces mainly to ollama, for local LLM usage, but can be integrated to the major online players. This project initially started as a simple integration with Ollama and since then has somewhat evolved into a more fully fully-featured AI Emacs assistant. The main focus with this package is a front facing simplicity but hiding (hopefully) all the features you would expect from an AI chatbot - wait I hate that term, I mean, assistant :). There is also the ability to craft a customizable menu system for different roles.&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>I had a blast developing this package and next up is RAG!. I saw recently that a package called &lt;code>vecdb&lt;/code> was introduced into the package ecosystem to help with the storage of vector embeddings, so as ollama can return embedding vectors for semantic search I thought I would combine my package, vecdb, also probably initially a PostgreSQL database with a pgvector extension and ollama into something that could ingest files directly from Emacs. I think I have figured this out now, I just need to do it (when the baby is asleep, probably!)&lt;/p>
&lt;h2 id="why-choose-ollama-buddy">Why Choose Ollama Buddy?&lt;/h2>
&lt;p>I designed Ollama Buddy to be as simple as possible to set up, no backend configuration or complex setup required. This was achievable initially because I focused solely on Ollama integration, where models are automatically discoverable.&lt;/p>
&lt;p>Since then, I&amp;rsquo;ve expanded support to major online AI providers while maintaining that same simplicity through a modular architecture. The system now handles multiple providers without adding complexity to the user experience.&lt;/p>
&lt;p>Another key feature is the customizable menu system, which integrates with role-based switching. You can create specialized AI menus for different contexts, like a coding-focused setup or a writing-optimized configuration and switch between them instantly. Everything is fully configurable to match your workflow.&lt;/p>
&lt;h2 id="links">Links&lt;/h2>
&lt;p>Here are some links:&lt;/p>
&lt;p>&lt;a href="https://github.com/captainflasmr/ollama-buddy">https://github.com/captainflasmr/ollama-buddy&lt;/a>
&lt;a href="https://melpa.org/#/ollama-buddy">https://melpa.org/#/ollama-buddy&lt;/a>&lt;/p>
&lt;p>I will outline the major features below, but I do have a manual available!&lt;/p>
&lt;p>&lt;a href="https://github.com/captainflasmr/ollama-buddy/blob/main/docs/ollama-buddy.org">https://github.com/captainflasmr/ollama-buddy/blob/main/docs/ollama-buddy.org&lt;/a>&lt;/p>
&lt;h2 id="key-features">Key Features&lt;/h2>
&lt;h3 id="multiple-ai-providers">Multiple AI Providers&lt;/h3>
&lt;ul>
&lt;li>&lt;strong>Local Models&lt;/strong>: Full support for Ollama with automatic model management&lt;/li>
&lt;li>&lt;strong>Cloud Services&lt;/strong>: Integrated support for OpenAI (ChatGPT), Anthropic Claude, Google Gemini, and Grok&lt;/li>
&lt;li>&lt;strong>Seamless Switching&lt;/strong>: Change between local and cloud models with a single command&lt;/li>
&lt;li>&lt;strong>Unified Interface&lt;/strong>: Same commands work across all providers&lt;/li>
&lt;/ul>
&lt;h3 id="role-based-workflows-build-your-own-ai-menu">Role-Based Workflows - build your own AI menu&lt;/h3>
&lt;ul>
&lt;li>&lt;strong>Preset Roles&lt;/strong>: Switch between different AI personalities (developer, writer, analyst, etc.)&lt;/li>
&lt;li>&lt;strong>Custom Roles&lt;/strong>: Create specialized workflows with specific models and parameters&lt;/li>
&lt;li>&lt;strong>Menu Customization&lt;/strong>: Each role can have its own set of commands and shortcuts&lt;/li>
&lt;/ul>
&lt;h3 id="chat-interface">Chat Interface&lt;/h3>
&lt;ul>
&lt;li>&lt;strong>Org-mode Integration&lt;/strong>: Conversations rendered in structured org-mode format&lt;/li>
&lt;li>&lt;strong>Real-time Streaming&lt;/strong>: Watch responses appear token by token&lt;/li>
&lt;li>&lt;strong>Context Management&lt;/strong>: Visual context window monitoring with usage warnings&lt;/li>
&lt;li>&lt;strong>History Tracking&lt;/strong>: Full conversation history with model-specific storage&lt;/li>
&lt;/ul>
&lt;h3 id="file-handling">File Handling&lt;/h3>
&lt;ul>
&lt;li>&lt;strong>File Attachments&lt;/strong>: Attach documents directly to conversations for context-aware analysis&lt;/li>
&lt;li>&lt;strong>Vision Support&lt;/strong>: Upload and analyse images with vision-capable models&lt;/li>
&lt;li>&lt;strong>Dired Integration&lt;/strong>: Bulk attach files directly from Emacs file manager&lt;/li>
&lt;/ul>
&lt;h3 id="prompt-management">Prompt Management&lt;/h3>
&lt;ul>
&lt;li>&lt;strong>System Prompts&lt;/strong>: Create and manage reusable system prompts for different use cases&lt;/li>
&lt;li>&lt;strong>Fabric Integration&lt;/strong>: Auto-sync with Fabric patterns (200+ professional prompts)&lt;/li>
&lt;li>&lt;strong>Awesome ChatGPT Prompts&lt;/strong>: Built-in access to the popular prompt collection&lt;/li>
&lt;li>&lt;strong>User Prompts&lt;/strong>: Create and organize your own custom prompt library (which of course is org based)&lt;/li>
&lt;/ul>
&lt;h3 id="session-management">Session Management&lt;/h3>
&lt;ul>
&lt;li>&lt;strong>Save &amp;amp; Restore&lt;/strong>: Full session persistence including history, attachments, and settings&lt;/li>
&lt;li>&lt;strong>Session Browser&lt;/strong>: Visual interface to manage multiple conversation sessions&lt;/li>
&lt;li>&lt;strong>Auto-naming&lt;/strong>: Intelligent session naming based on conversation content&lt;/li>
&lt;/ul>
&lt;h3 id="flexible-interface-options">Flexible Interface Options&lt;/h3>
&lt;ul>
&lt;li>&lt;strong>Two Interface Levels&lt;/strong>: Basic mode for beginners, advanced for power users&lt;/li>
&lt;li>&lt;strong>Transient Menus&lt;/strong>: Magit-style discoverable command interface&lt;/li>
&lt;li>&lt;strong>Custom Menus&lt;/strong>: Traditional text-based menu system&lt;/li>
&lt;li>&lt;strong>Keyboard Shortcuts&lt;/strong>: Comprehensive keybinding system for efficiency, I&amp;rsquo;m not sure there are any keys left!!&lt;/li>
&lt;/ul>
&lt;h2 id="what-s-next">What&amp;rsquo;s Next?&lt;/h2>
&lt;p>Version 1.0 represents a stable, foundation, Ollama Buddy has been out there now for a few months with only a single github issue but development continues with:&lt;/p>
&lt;ul>
&lt;li>RAG integration using perhaps the new &lt;code>vecdb&lt;/code> package, as mentioned above&lt;/li>
&lt;li>Additional AI provider integrations (Perplexity maybe?, any suggestions?)&lt;/li>
&lt;li>Auto-completion (not sure how doable this is with ollama, but I do have a prototype)&lt;/li>
&lt;/ul></description></item><item><title>Building an Emacs Package from Scratch: Cursor Heatmap Tutorial Part 2</title><link>https://www.emacs.dyerdwelling.family/emacs/20250722132631-emacs--building-a-new-package-from-scratch---cursor-heatmap-pt2/</link><pubDate>Tue, 22 Jul 2025 13:26:00 +0100</pubDate><author>James Dyer</author><guid>https://www.emacs.dyerdwelling.family/emacs/20250722132631-emacs--building-a-new-package-from-scratch---cursor-heatmap-pt2/</guid><description>&lt;p>In our ongoing quest to construct a new fully functional Emacs package, in Part 1, we built the foundation and have a very basic package for our cursor heatmap package. Now we&amp;rsquo;re ready to tackle the core challenge, actually tracking where the cursor is and detecting when it moves!&lt;/p>
&lt;p>So, where is the cursor?, this seems simple, but with Emacs and of course generally software engineering there are several different approaches and concepts to understand and think about before we move on to some implementation.&lt;/p>
&lt;ol>
&lt;li>&lt;strong>Point&lt;/strong>: The character position in the buffer (like character 1,847)&lt;/li>
&lt;li>&lt;strong>Line and column&lt;/strong>: Traditional text coordinates (line 42, column 15)&lt;/li>
&lt;li>&lt;strong>Window coordinates&lt;/strong>: Position relative to the current window&lt;/li>
&lt;li>&lt;strong>Frame coordinates&lt;/strong>: Position relative to the entire Emacs frame&lt;/li>
&lt;li>&lt;strong>Pixel coordinates&lt;/strong>: Exact pixel position on screen&lt;/li>
&lt;/ol>
&lt;p>For our heatmap, we want to visualize cursor movement patterns across the entire Emacs interface, so we&amp;rsquo;ll focus on &lt;strong>frame-relative positioning&lt;/strong>, both pixel-based (for GUI Emacs) and character-based (for terminal Emacs).&lt;/p>
&lt;h2 id="exploring-emacs-coordinate-systems">Exploring Emacs Coordinate Systems&lt;/h2>
&lt;p>Let&amp;rsquo;s start by understanding what information Emacs can give us about cursor position. Add this exploration function to your package:&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 cursor-heatmap-debug-position ()
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#e6db74">&amp;#34;Show detailed information about current cursor position.&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* ((point-pos (&lt;span style="color:#a6e22e">point&lt;/span>))
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> (line-col (&lt;span style="color:#a6e22e">cons&lt;/span> (line-number-at-pos) (&lt;span style="color:#a6e22e">current-column&lt;/span>)))
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> (&lt;span style="color:#a6e22e">window-edges&lt;/span> (&lt;span style="color:#a6e22e">window-edges&lt;/span>))
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> (pixel-pos (when (display-graphic-p)
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> (window-absolute-pixel-position))))
&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;Debug - Point: %d | Line/Col: %s | Window edges: %s | Pixels: %s&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> point-pos line-col &lt;span style="color:#a6e22e">window-edges&lt;/span> pixel-pos)))
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>Try this function in different buffers and notice how the values change. You&amp;rsquo;ll see that:&lt;/p>
&lt;ul>
&lt;li>&lt;strong>Point&lt;/strong> changes based on buffer content and cursor position within text&lt;/li>
&lt;li>&lt;strong>Line/column&lt;/strong> reflects traditional text editing coordinates&lt;/li>
&lt;li>&lt;strong>Window edges&lt;/strong> show the window boundaries in character units&lt;/li>
&lt;li>&lt;strong>Pixel position&lt;/strong> (GUI only) gives exact screen coordinates&lt;/li>
&lt;/ul>
&lt;h2 id="building-our-position-detection-system">Building Our Position Detection System&lt;/h2>
&lt;p>Now let&amp;rsquo;s create our core position detection function. We&amp;rsquo;ll handle both GUI and terminal modes:&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 cursor-heatmap--get-cursor-position ()
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#e6db74">&amp;#34;Get cursor position as frame-relative coordinates.
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#e6db74">Returns (x . y) cons cell or nil if position should be excluded.&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> (when (and (not (&lt;span style="color:#a6e22e">minibufferp&lt;/span>)) &lt;span style="color:#75715e">; Skip minibuffer&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> (&lt;span style="color:#a6e22e">get-buffer-window&lt;/span>)) &lt;span style="color:#75715e">; Only track visible buffers&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> (condition-case err
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> (if (display-graphic-p)
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> (cursor-heatmap--get-pixel-position)
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> (cursor-heatmap--get-character-position))
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> (&lt;span style="color:#a6e22e">error&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#75715e">;; If anything goes wrong, return nil silently&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#66d9ef">nil&lt;/span>))))
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>Notice several important design decisions here:&lt;/p>
&lt;p>&lt;strong>Error Handling&lt;/strong>: We wrap everything in &lt;code>condition-case&lt;/code> because cursor position detection can fail in various edge cases (buffer switching, window operations, etc.).&lt;/p>
&lt;p>&lt;strong>Exclusion Logic&lt;/strong>: We skip the minibuffer because it&amp;rsquo;s typically used for brief interactions, not sustained editing work.&lt;/p>
&lt;p>&lt;strong>Display Mode Detection&lt;/strong>: We use different strategies for GUI vs. terminal Emacs.&lt;/p>
&lt;h2 id="implementing-pixel-based-position-detection">Implementing Pixel-Based Position Detection&lt;/h2>
&lt;p>For GUI Emacs, we can get precise pixel coordinates:&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 cursor-heatmap--get-pixel-position ()
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#e6db74">&amp;#34;Get cursor position using pixel coordinates in GUI mode.&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> (let* ((pixel-pos (window-absolute-pixel-position))
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> (frame (&lt;span style="color:#a6e22e">selected-frame&lt;/span>))
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> (&lt;span style="color:#a6e22e">frame-pixel-width&lt;/span> (&lt;span style="color:#a6e22e">frame-pixel-width&lt;/span> frame))
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> (&lt;span style="color:#a6e22e">frame-pixel-height&lt;/span> (&lt;span style="color:#a6e22e">frame-pixel-height&lt;/span> frame)))
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> (when (and pixel-pos
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> (&lt;span style="color:#a6e22e">&amp;gt;&lt;/span> &lt;span style="color:#a6e22e">frame-pixel-width&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:#a6e22e">&amp;gt;&lt;/span> &lt;span style="color:#a6e22e">frame-pixel-height&lt;/span> &lt;span style="color:#ae81ff">0&lt;/span>))
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> (let* ((frame-x-pixel (&lt;span style="color:#a6e22e">car&lt;/span> pixel-pos))
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> (frame-y-pixel (&lt;span style="color:#a6e22e">cdr&lt;/span> pixel-pos))
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#75715e">;; Normalize to 0.0-1.0 range&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> (normalized-x (&lt;span style="color:#a6e22e">/&lt;/span> (&lt;span style="color:#a6e22e">float&lt;/span> frame-x-pixel) &lt;span style="color:#a6e22e">frame-pixel-width&lt;/span>))
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> (normalized-y (&lt;span style="color:#a6e22e">/&lt;/span> (&lt;span style="color:#a6e22e">float&lt;/span> frame-y-pixel) &lt;span style="color:#a6e22e">frame-pixel-height&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">;; Return as percentages for easier grid mapping later&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> (&lt;span style="color:#a6e22e">cons&lt;/span> normalized-x normalized-y)))))
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>This function:&lt;/p>
&lt;ol>
&lt;li>Gets the absolute pixel position of the cursor&lt;/li>
&lt;li>Normalizes it to a 0.0-1.0 range relative to the frame size&lt;/li>
&lt;li>Returns coordinates that we can easily map to any grid size&lt;/li>
&lt;/ol>
&lt;h2 id="implementing-character-based-position-detection">Implementing Character-Based Position Detection&lt;/h2>
&lt;p>Terminal Emacs doesn&amp;rsquo;t have pixel coordinates, so we use character positions:&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 cursor-heatmap--get-character-position ()
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#e6db74">&amp;#34;Get cursor position using character coordinates in terminal mode.&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> (let* ((window (&lt;span style="color:#a6e22e">selected-window&lt;/span>))
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> (&lt;span style="color:#a6e22e">window-edges&lt;/span> (&lt;span style="color:#a6e22e">window-edges&lt;/span> window))
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> (window-left (&lt;span style="color:#a6e22e">nth&lt;/span> &lt;span style="color:#ae81ff">0&lt;/span> &lt;span style="color:#a6e22e">window-edges&lt;/span>))
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> (window-top (&lt;span style="color:#a6e22e">nth&lt;/span> &lt;span style="color:#ae81ff">1&lt;/span> &lt;span style="color:#a6e22e">window-edges&lt;/span>))
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> (point-pos (&lt;span style="color:#a6e22e">window-point&lt;/span> window))
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> (total-frame-width (frame-width))
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> (total-frame-height (frame-height)))
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> (when (and (&lt;span style="color:#a6e22e">&amp;gt;&lt;/span> total-frame-width &lt;span style="color:#ae81ff">0&lt;/span>)
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> (&lt;span style="color:#a6e22e">&amp;gt;&lt;/span> total-frame-height &lt;span style="color:#ae81ff">0&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> point-pos)
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> (let* ((&lt;span style="color:#a6e22e">current-column&lt;/span> (&lt;span style="color:#a6e22e">current-column&lt;/span>))
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> (window-line (count-lines (&lt;span style="color:#a6e22e">window-start&lt;/span> window) point-pos))
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#75715e">;; Calculate frame-relative position&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> (frame-column (&lt;span style="color:#a6e22e">+&lt;/span> window-left &lt;span style="color:#a6e22e">current-column&lt;/span>))
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> (frame-row (&lt;span style="color:#a6e22e">+&lt;/span> window-top window-line))
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#75715e">;; Normalize to 0.0-1.0 range&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> (normalized-x (&lt;span style="color:#a6e22e">/&lt;/span> (&lt;span style="color:#a6e22e">float&lt;/span> frame-column) total-frame-width))
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> (normalized-y (&lt;span style="color:#a6e22e">/&lt;/span> (&lt;span style="color:#a6e22e">float&lt;/span> frame-row) total-frame-height)))
&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">cons&lt;/span> normalized-x normalized-y))))))
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>This is more complex because we need to:&lt;/p>
&lt;ol>
&lt;li>Get the current column position (handling tabs correctly)&lt;/li>
&lt;li>Calculate which line we&amp;rsquo;re on relative to the window&lt;/li>
&lt;li>Convert window-relative coordinates to frame-relative coordinates&lt;/li>
&lt;li>Normalize to the same 0.0-1.0 range as pixel coordinates&lt;/li>
&lt;/ol>
&lt;h2 id="adding-movement-detection">Adding Movement Detection&lt;/h2>
&lt;p>Having a position is only half the battle, we need to detect when the cursor actually moves. Let&amp;rsquo;s add state tracking:&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>(defvar cursor-heatmap--last-position &lt;span style="color:#66d9ef">nil&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#e6db74">&amp;#34;Last recorded cursor position for movement detection.&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>(defvar cursor-heatmap--movement-count &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;#34;Total number of cursor movements detected.&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>(defun cursor-heatmap--detect-movement ()
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#e6db74">&amp;#34;Check if cursor has moved and record the movement.&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> (let ((current-pos (cursor-heatmap--get-cursor-position)))
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> (when current-pos
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> (when (and cursor-heatmap--last-position
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> (not (&lt;span style="color:#a6e22e">equal&lt;/span> current-pos cursor-heatmap--last-position)))
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#75715e">;; This is a real movement!&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> (cl-incf cursor-heatmap--movement-count)
&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;Movement detected! Total movements: %d&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> cursor-heatmap--movement-count))
&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">;; Update last position for next comparison&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> (setq cursor-heatmap--last-position current-pos))))
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;h2 id="setting-up-the-hook-system">Setting Up the Hook System&lt;/h2>
&lt;p>Emacs provides several hooks we can use to detect cursor movement. The most reliable is &lt;code>post-command-hook&lt;/code>, which runs after every command:&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 cursor-heatmap--setup-tracking ()
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#e6db74">&amp;#34;Set up hooks to track cursor movement.&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> (add-hook &lt;span style="color:#e6db74">&amp;#39;post-command-hook&lt;/span> &lt;span style="color:#a6e22e">#&amp;#39;&lt;/span>cursor-heatmap--detect-movement))
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>(defun cursor-heatmap--stop-tracking ()
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#e6db74">&amp;#34;Remove movement tracking hooks.&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> (remove-hook &lt;span style="color:#e6db74">&amp;#39;post-command-hook&lt;/span> &lt;span style="color:#a6e22e">#&amp;#39;&lt;/span>cursor-heatmap--detect-movement))
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;h2 id="creating-our-first-interactive-commands">Creating Our First Interactive Commands&lt;/h2>
&lt;p>Let&amp;rsquo;s add commands users can run to test our tracking:&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>&lt;span style="color:#75715e">;;;###autoload&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>(defun cursor-heatmap-start-tracking ()
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#e6db74">&amp;#34;Start tracking cursor movements.&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> (cursor-heatmap--setup-tracking)
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> (setq cursor-heatmap--last-position (cursor-heatmap--get-cursor-position)
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> cursor-heatmap--movement-count &lt;span style="color:#ae81ff">0&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;Cursor movement tracking started&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">;;;###autoload&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>(defun cursor-heatmap-stop-tracking ()
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#e6db74">&amp;#34;Stop tracking cursor movements.&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> (cursor-heatmap--stop-tracking)
&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;Cursor movement tracking stopped. Total movements: %d&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> cursor-heatmap--movement-count))
&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">;;;###autoload&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>(defun cursor-heatmap-show-stats ()
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#e6db74">&amp;#34;Show current tracking statistics.&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 ((current-pos (cursor-heatmap--get-cursor-position)))
&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;Position: %s | Movements: %d | Tracking: %s&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> (if current-pos
&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;%.3f, %.3f&amp;#34;&lt;/span> (&lt;span style="color:#a6e22e">car&lt;/span> current-pos) (&lt;span style="color:#a6e22e">cdr&lt;/span> current-pos))
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#e6db74">&amp;#34;unknown&amp;#34;&lt;/span>)
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> cursor-heatmap--movement-count
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> (if (&lt;span style="color:#a6e22e">memq&lt;/span> &lt;span style="color:#a6e22e">#&amp;#39;&lt;/span>cursor-heatmap--detect-movement post-command-hook)
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#e6db74">&amp;#34;ON&amp;#34;&lt;/span> &lt;span style="color:#e6db74">&amp;#34;OFF&amp;#34;&lt;/span>))))
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>The &lt;code>;;;###autoload&lt;/code> comments are special, they tell Emacs to make these functions available even before the package is fully loaded.&lt;/p>
&lt;h2 id="handling-edge-cases">Handling Edge Cases&lt;/h2>
&lt;p>Real-world usage reveals edge cases we need to handle:&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>(defcustom cursor-heatmap-exclude-minibuffer &lt;span style="color:#66d9ef">t&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#e6db74">&amp;#34;Whether to exclude minibuffer from tracking.&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> :type &lt;span style="color:#e6db74">&amp;#39;boolean&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> :group &lt;span style="color:#e6db74">&amp;#39;cursor-heatmap&lt;/span>)
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>(defcustom cursor-heatmap-exclude-special-buffers &lt;span style="color:#66d9ef">t&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#e6db74">&amp;#34;Whether to exclude special buffers (starting with space or asterisk).&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> :type &lt;span style="color:#e6db74">&amp;#39;boolean&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> :group &lt;span style="color:#e6db74">&amp;#39;cursor-heatmap&lt;/span>)
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>(defun cursor-heatmap--should-track-buffer-p ()
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#e6db74">&amp;#34;Return non-nil if current buffer should be tracked.&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> (and (not (and cursor-heatmap-exclude-minibuffer
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> (&lt;span style="color:#a6e22e">minibufferp&lt;/span>)))
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> (not (and cursor-heatmap-exclude-special-buffers
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> (&lt;span style="color:#a6e22e">string-match&lt;/span> &lt;span style="color:#e6db74">&amp;#34;^[ *]&amp;#34;&lt;/span> (&lt;span style="color:#a6e22e">buffer-name&lt;/span>))))
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> (&lt;span style="color:#a6e22e">get-buffer-window&lt;/span>))) &lt;span style="color:#75715e">; Only track visible buffers&lt;/span>
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>Update our position function to use this check:&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 cursor-heatmap--get-cursor-position ()
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#e6db74">&amp;#34;Get cursor position as frame-relative coordinates.
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#e6db74">Returns (x . y) cons cell or nil if position should be excluded.&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> (when (cursor-heatmap--should-track-buffer-p)
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> (condition-case err
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> (if (display-graphic-p)
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> (cursor-heatmap--get-pixel-position)
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> (cursor-heatmap--get-character-position))
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> (&lt;span style="color:#a6e22e">error&lt;/span> &lt;span style="color:#66d9ef">nil&lt;/span>))))
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;h2 id="testing-our-movement-detection">Testing Our Movement Detection&lt;/h2>
&lt;p>Let&amp;rsquo;s create a comprehensive test function:&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 cursor-heatmap-test-detection ()
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#e6db74">&amp;#34;Interactive test of cursor position detection.&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> (if (&lt;span style="color:#a6e22e">memq&lt;/span> &lt;span style="color:#a6e22e">#&amp;#39;&lt;/span>cursor-heatmap--detect-movement post-command-hook)
&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;Tracking already active. Use cursor-heatmap-stop-tracking to stop.&amp;#34;&lt;/span>)
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> (cursor-heatmap-start-tracking)
&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;Move your cursor around, then run cursor-heatmap-show-stats&amp;#34;&lt;/span>)))
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;h2 id="our-complete-progress-so-far">Our Complete Progress So Far&lt;/h2>
&lt;p>Here&amp;rsquo;s what we&amp;rsquo;ve built in this post:&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>&lt;span style="color:#75715e">;; Add these to your cursor-heatmap.el file after the customization variables&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>(require &lt;span style="color:#e6db74">&amp;#39;cl-lib&lt;/span>) &lt;span style="color:#75715e">; For cl-incf&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">;; State variables&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>(defvar cursor-heatmap--last-position &lt;span style="color:#66d9ef">nil&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#e6db74">&amp;#34;Last recorded cursor position for movement detection.&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>(defvar cursor-heatmap--movement-count &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;#34;Total number of cursor movements detected.&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">;; New customization options&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>(defcustom cursor-heatmap-exclude-minibuffer &lt;span style="color:#66d9ef">t&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#e6db74">&amp;#34;Whether to exclude minibuffer from tracking.&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> :type &lt;span style="color:#e6db74">&amp;#39;boolean&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> :group &lt;span style="color:#e6db74">&amp;#39;cursor-heatmap&lt;/span>)
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>(defcustom cursor-heatmap-exclude-special-buffers &lt;span style="color:#66d9ef">t&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#e6db74">&amp;#34;Whether to exclude special buffers (starting with space or asterisk).&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> :type &lt;span style="color:#e6db74">&amp;#39;boolean&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> :group &lt;span style="color:#e6db74">&amp;#39;cursor-heatmap&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">;; Core detection functions&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>(defun cursor-heatmap--should-track-buffer-p ()
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#e6db74">&amp;#34;Return non-nil if current buffer should be tracked.&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> (and (not (and cursor-heatmap-exclude-minibuffer
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> (&lt;span style="color:#a6e22e">minibufferp&lt;/span>)))
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> (not (and cursor-heatmap-exclude-special-buffers
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> (&lt;span style="color:#a6e22e">string-match&lt;/span> &lt;span style="color:#e6db74">&amp;#34;^[ *]&amp;#34;&lt;/span> (&lt;span style="color:#a6e22e">buffer-name&lt;/span>))))
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> (&lt;span style="color:#a6e22e">get-buffer-window&lt;/span>)))
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>(defun cursor-heatmap--get-pixel-position ()
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#e6db74">&amp;#34;Get cursor position using pixel coordinates in GUI mode.&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> (let* ((pixel-pos (window-absolute-pixel-position))
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> (frame (&lt;span style="color:#a6e22e">selected-frame&lt;/span>))
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> (&lt;span style="color:#a6e22e">frame-pixel-width&lt;/span> (&lt;span style="color:#a6e22e">frame-pixel-width&lt;/span> frame))
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> (&lt;span style="color:#a6e22e">frame-pixel-height&lt;/span> (&lt;span style="color:#a6e22e">frame-pixel-height&lt;/span> frame)))
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> (when (and pixel-pos
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> (&lt;span style="color:#a6e22e">&amp;gt;&lt;/span> &lt;span style="color:#a6e22e">frame-pixel-width&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:#a6e22e">&amp;gt;&lt;/span> &lt;span style="color:#a6e22e">frame-pixel-height&lt;/span> &lt;span style="color:#ae81ff">0&lt;/span>))
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> (let* ((frame-x-pixel (&lt;span style="color:#a6e22e">car&lt;/span> pixel-pos))
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> (frame-y-pixel (&lt;span style="color:#a6e22e">cdr&lt;/span> pixel-pos))
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> (normalized-x (&lt;span style="color:#a6e22e">/&lt;/span> (&lt;span style="color:#a6e22e">float&lt;/span> frame-x-pixel) &lt;span style="color:#a6e22e">frame-pixel-width&lt;/span>))
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> (normalized-y (&lt;span style="color:#a6e22e">/&lt;/span> (&lt;span style="color:#a6e22e">float&lt;/span> frame-y-pixel) &lt;span style="color:#a6e22e">frame-pixel-height&lt;/span>)))
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> (&lt;span style="color:#a6e22e">cons&lt;/span> normalized-x normalized-y)))))
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>(defun cursor-heatmap--get-character-position ()
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#e6db74">&amp;#34;Get cursor position using character coordinates in terminal mode.&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> (let* ((window (&lt;span style="color:#a6e22e">selected-window&lt;/span>))
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> (&lt;span style="color:#a6e22e">window-edges&lt;/span> (&lt;span style="color:#a6e22e">window-edges&lt;/span> window))
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> (window-left (&lt;span style="color:#a6e22e">nth&lt;/span> &lt;span style="color:#ae81ff">0&lt;/span> &lt;span style="color:#a6e22e">window-edges&lt;/span>))
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> (window-top (&lt;span style="color:#a6e22e">nth&lt;/span> &lt;span style="color:#ae81ff">1&lt;/span> &lt;span style="color:#a6e22e">window-edges&lt;/span>))
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> (point-pos (&lt;span style="color:#a6e22e">window-point&lt;/span> window))
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> (total-frame-width (frame-width))
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> (total-frame-height (frame-height)))
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> (when (and (&lt;span style="color:#a6e22e">&amp;gt;&lt;/span> total-frame-width &lt;span style="color:#ae81ff">0&lt;/span>)
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> (&lt;span style="color:#a6e22e">&amp;gt;&lt;/span> total-frame-height &lt;span style="color:#ae81ff">0&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> point-pos)
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> (let* ((&lt;span style="color:#a6e22e">current-column&lt;/span> (&lt;span style="color:#a6e22e">current-column&lt;/span>))
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> (window-line (count-lines (&lt;span style="color:#a6e22e">window-start&lt;/span> window) point-pos))
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> (frame-column (&lt;span style="color:#a6e22e">+&lt;/span> window-left &lt;span style="color:#a6e22e">current-column&lt;/span>))
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> (frame-row (&lt;span style="color:#a6e22e">+&lt;/span> window-top window-line))
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> (normalized-x (&lt;span style="color:#a6e22e">/&lt;/span> (&lt;span style="color:#a6e22e">float&lt;/span> frame-column) total-frame-width))
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> (normalized-y (&lt;span style="color:#a6e22e">/&lt;/span> (&lt;span style="color:#a6e22e">float&lt;/span> frame-row) total-frame-height)))
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> (&lt;span style="color:#a6e22e">cons&lt;/span> normalized-x normalized-y))))))
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>(defun cursor-heatmap--get-cursor-position ()
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#e6db74">&amp;#34;Get cursor position as frame-relative coordinates.
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#e6db74">Returns (x . y) cons cell or nil if position should be excluded.&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> (when (cursor-heatmap--should-track-buffer-p)
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> (condition-case err
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> (if (display-graphic-p)
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> (cursor-heatmap--get-pixel-position)
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> (cursor-heatmap--get-character-position))
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> (&lt;span style="color:#a6e22e">error&lt;/span> &lt;span style="color:#66d9ef">nil&lt;/span>))))
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>(defun cursor-heatmap--detect-movement ()
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#e6db74">&amp;#34;Check if cursor has moved and record the movement.&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> (let ((current-pos (cursor-heatmap--get-cursor-position)))
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> (when current-pos
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> (when (and cursor-heatmap--last-position
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> (not (&lt;span style="color:#a6e22e">equal&lt;/span> current-pos cursor-heatmap--last-position)))
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> (cl-incf cursor-heatmap--movement-count))
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> (setq cursor-heatmap--last-position current-pos))))
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>(defun cursor-heatmap--setup-tracking ()
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#e6db74">&amp;#34;Set up hooks to track cursor movement.&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> (add-hook &lt;span style="color:#e6db74">&amp;#39;post-command-hook&lt;/span> &lt;span style="color:#a6e22e">#&amp;#39;&lt;/span>cursor-heatmap--detect-movement))
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>(defun cursor-heatmap--stop-tracking ()
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#e6db74">&amp;#34;Remove movement tracking hooks.&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> (remove-hook &lt;span style="color:#e6db74">&amp;#39;post-command-hook&lt;/span> &lt;span style="color:#a6e22e">#&amp;#39;&lt;/span>cursor-heatmap--detect-movement))
&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">;; Interactive commands&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#75715e">;;;###autoload&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>(defun cursor-heatmap-start-tracking ()
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#e6db74">&amp;#34;Start tracking cursor movements.&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> (cursor-heatmap--setup-tracking)
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> (setq cursor-heatmap--last-position (cursor-heatmap--get-cursor-position)
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> cursor-heatmap--movement-count &lt;span style="color:#ae81ff">0&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;Cursor movement tracking started&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">;;;###autoload&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>(defun cursor-heatmap-stop-tracking ()
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#e6db74">&amp;#34;Stop tracking cursor movements.&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> (cursor-heatmap--stop-tracking)
&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;Cursor movement tracking stopped. Total movements: %d&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> cursor-heatmap--movement-count))
&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">;;;###autoload&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>(defun cursor-heatmap-show-stats ()
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#e6db74">&amp;#34;Show current tracking statistics.&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 ((current-pos (cursor-heatmap--get-cursor-position)))
&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;Position: %s | Movements: %d | Tracking: %s&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> (if current-pos
&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;%.3f, %.3f&amp;#34;&lt;/span> (&lt;span style="color:#a6e22e">car&lt;/span> current-pos) (&lt;span style="color:#a6e22e">cdr&lt;/span> current-pos))
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#e6db74">&amp;#34;unknown&amp;#34;&lt;/span>)
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> cursor-heatmap--movement-count
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> (if (&lt;span style="color:#a6e22e">memq&lt;/span> &lt;span style="color:#a6e22e">#&amp;#39;&lt;/span>cursor-heatmap--detect-movement post-command-hook)
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#e6db74">&amp;#34;ON&amp;#34;&lt;/span> &lt;span style="color:#e6db74">&amp;#34;OFF&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">;;;###autoload&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>(defun cursor-heatmap-test-detection ()
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#e6db74">&amp;#34;Interactive test of cursor position detection.&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> (if (&lt;span style="color:#a6e22e">memq&lt;/span> &lt;span style="color:#a6e22e">#&amp;#39;&lt;/span>cursor-heatmap--detect-movement post-command-hook)
&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;Tracking already active. Use cursor-heatmap-stop-tracking to stop.&amp;#34;&lt;/span>)
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> (cursor-heatmap-start-tracking)
&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;Move your cursor around, then run cursor-heatmap-show-stats&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">;; Debug helper&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>(defun cursor-heatmap-debug-position ()
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#e6db74">&amp;#34;Show detailed information about current cursor position.&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* ((point-pos (&lt;span style="color:#a6e22e">point&lt;/span>))
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> (line-col (&lt;span style="color:#a6e22e">cons&lt;/span> (line-number-at-pos) (&lt;span style="color:#a6e22e">current-column&lt;/span>)))
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> (&lt;span style="color:#a6e22e">window-edges&lt;/span> (&lt;span style="color:#a6e22e">window-edges&lt;/span>))
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> (pixel-pos (when (display-graphic-p)
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> (window-absolute-pixel-position)))
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> (our-pos (cursor-heatmap--get-cursor-position)))
&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;Point: %d | Line/Col: %s | Edges: %s | Pixels: %s | Our pos: %s&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> point-pos line-col &lt;span style="color:#a6e22e">window-edges&lt;/span> pixel-pos our-pos)))
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;h2 id="what-we-ve-accomplished">What We&amp;rsquo;ve Accomplished&lt;/h2>
&lt;p>In this post, we&amp;rsquo;ve built the core tracking system:&lt;/p>
&lt;p>✅ &lt;strong>Position detection&lt;/strong>: Works in both GUI and terminal modes
✅ &lt;strong>Movement detection&lt;/strong>: Only counts actual cursor movements
✅ &lt;strong>Error handling&lt;/strong>: Gracefully handles edge cases
✅ &lt;strong>Hook system&lt;/strong>: Reliable tracking using Emacs&amp;rsquo; event system
✅ &lt;strong>Interactive testing&lt;/strong>: Commands to start, stop, and debug tracking
✅ &lt;strong>Customization&lt;/strong>: User control over what gets tracked&lt;/p>
&lt;h2 id="testing-your-implementation">Testing Your Implementation&lt;/h2>
&lt;p>Try these exercises to verify everything works:&lt;/p>
&lt;ol>
&lt;li>&lt;strong>Load and test&lt;/strong>: Reload your package and run &lt;code>cursor-heatmap-test-detection&lt;/code>&lt;/li>
&lt;li>&lt;strong>Move around&lt;/strong>: Navigate between buffers, windows, and frames&lt;/li>
&lt;li>&lt;strong>Check stats&lt;/strong>: Run &lt;code>cursor-heatmap-show-stats&lt;/code> to see movement counts&lt;/li>
&lt;li>&lt;strong>Debug position&lt;/strong>: Use &lt;code>cursor-heatmap-debug-position&lt;/code> to see raw coordinate data&lt;/li>
&lt;li>&lt;strong>Test exclusions&lt;/strong>: Try the minibuffer and special buffers&lt;/li>
&lt;/ol>
&lt;h2 id="common-issues-and-solutions">Common Issues and Solutions&lt;/h2>
&lt;p>&lt;strong>&amp;ldquo;Position always nil&amp;rdquo;&lt;/strong>: Check that you&amp;rsquo;re in a visible buffer and not a special buffer.&lt;/p>
&lt;p>&lt;strong>&amp;ldquo;No movements detected&amp;rdquo;&lt;/strong>: Make sure tracking is active and you&amp;rsquo;re making actual movements, not just staying in one place.&lt;/p>
&lt;p>&lt;strong>&amp;ldquo;Coordinates seem wrong&amp;rdquo;&lt;/strong>: This is normal, we&amp;rsquo;re getting normalized coordinates (0.0-1.0) that we&amp;rsquo;ll map to grids later.&lt;/p>
&lt;h2 id="looking-ahead">Looking Ahead&lt;/h2>
&lt;p>In Part 3, we&amp;rsquo;ll transform these normalized coordinates into a grid system and start building our data structures for the heatmap. We&amp;rsquo;ll cover:&lt;/p>
&lt;ul>
&lt;li>Mapping coordinates to grid cells&lt;/li>
&lt;li>Creating efficient storage for movement data&lt;/li>
&lt;li>Handling different grid sizes&lt;/li>
&lt;li>Building the foundation for visualization&lt;/li>
&lt;/ul>
&lt;p>The tracking system we built today is the engine that will power our entire heatmap, everything else builds on this foundation.&lt;/p>
&lt;h2 id="your-turn-hands-on-exercise">Your Turn: Hands-On Exercise&lt;/h2>
&lt;p>Extend the tracking system:&lt;/p>
&lt;ol>
&lt;li>&lt;strong>Add buffer tracking&lt;/strong>: Modify the system to track which buffer movements occur in&lt;/li>
&lt;li>&lt;strong>Add time tracking&lt;/strong>: Record timestamps with movements&lt;/li>
&lt;li>&lt;strong>Create a movement log&lt;/strong>: Store the last 10 movements for debugging&lt;/li>
&lt;li>&lt;strong>Experiment with hooks&lt;/strong>: Try different hooks beyond &lt;code>post-command-hook&lt;/code>&lt;/li>
&lt;/ol>
&lt;h2 id="next-time">Next Time&lt;/h2>
&lt;p>In Part 3, we&amp;rsquo;ll build the grid mapping system that transforms our position data into the structure needed for heatmap visualization.&lt;/p></description></item><item><title>Building a New Package From Scratch - Cursor Heatmap Pt1</title><link>https://www.emacs.dyerdwelling.family/emacs/20250719094720-emacs--building-an-emacs-cursor-heatmap-pt1/</link><pubDate>Sun, 20 Jul 2025 13:17:00 +0100</pubDate><author>James Dyer</author><guid>https://www.emacs.dyerdwelling.family/emacs/20250719094720-emacs--building-an-emacs-cursor-heatmap-pt1/</guid><description>&lt;hr>
&lt;div class="ox-hugo-toc toc local">
&lt;ul>
&lt;li>&lt;a href="https://www.emacs.dyerdwelling.family/emacs/20250719094720-emacs--building-an-emacs-cursor-heatmap-pt1/#lets-create-a-new-package">Lets Create a New Package!&lt;/a>&lt;/li>
&lt;li>&lt;a href="https://www.emacs.dyerdwelling.family/emacs/20250719094720-emacs--building-an-emacs-cursor-heatmap-pt1/#building-our-foundation">Building Our Foundation&lt;/a>&lt;/li>
&lt;li>&lt;a href="https://www.emacs.dyerdwelling.family/emacs/20250719094720-emacs--building-an-emacs-cursor-heatmap-pt1/#package-naming-conventions">Package Naming Conventions&lt;/a>&lt;/li>
&lt;li>&lt;a href="https://www.emacs.dyerdwelling.family/emacs/20250719094720-emacs--building-an-emacs-cursor-heatmap-pt1/#testing-our-foundation">Testing Our Foundation&lt;/a>&lt;/li>
&lt;li>&lt;a href="https://www.emacs.dyerdwelling.family/emacs/20250719094720-emacs--building-an-emacs-cursor-heatmap-pt1/#our-complete-foundation">Our Complete Foundation&lt;/a>&lt;/li>
&lt;li>&lt;a href="https://www.emacs.dyerdwelling.family/emacs/20250719094720-emacs--building-an-emacs-cursor-heatmap-pt1/#what-we-ve-accomplished">What We&amp;rsquo;ve Accomplished&lt;/a>&lt;/li>
&lt;li>&lt;a href="https://www.emacs.dyerdwelling.family/emacs/20250719094720-emacs--building-an-emacs-cursor-heatmap-pt1/#next-time">Next Time&lt;/a>&lt;/li>
&lt;/ul>
&lt;/div>
&lt;!--endtoc-->
&lt;hr>
&lt;h2 id="lets-create-a-new-package">Lets Create a New Package!&lt;/h2>
&lt;p>Given that I now have greater experience in Emacs package creation, I thought I would create a short series of blog posts over the next few weeks in taking an initial idea I have for a new Emacs package and working it up into a full, working version!&lt;/p>
&lt;p>The idea here is to hopefully be useful to someone who has an idea for doing something new in Emacs and would like to start working it up into a package. There are resources out there, but I thought I would give a go at creating my own.&lt;/p>
&lt;p>As usual with writing a blog and having to explain a concept to someone in an understandable format, I suspect that I&amp;rsquo;m also actually likely to learn something from this activity.&lt;/p>
&lt;p>Right, so what is my idea?&lt;/p>
&lt;figure>&lt;img src="https://www.emacs.dyerdwelling.family/emacs/cursor-heatmap-banner_pt1.jpg" width="100%">
&lt;/figure>
&lt;hr>
&lt;style>.org-center { margin-left: auto; margin-right: auto; text-align: center; }&lt;/style>
&lt;div class="org-center">
&lt;p>&lt;em>Have you ever wondered where your cursor/point spends most of its time when you are fiddling around with Emacs?, do you generally split two windows side-by-side and which window do you typically work in?, do you use olivetti or a similar mode to centralise the text, do you use a four window split on a large monitor and where do you spend most of your time?&lt;/em>&lt;/p>
&lt;p>&lt;em>So how about an Emacs package that monitors your cursor/point position and can provide a graphical report showing by some kind of graphical heat map the often used Emacs window locations&lt;/em>&lt;/p>
&lt;/div>
&lt;hr>
&lt;p>Lets quickly break this concept down into a very high level design (so high it is in fact just a speck!), should we go the full software engineering approach and define requirements?, then start deriving a design, (maybe not!), so lets see what functional aspects we can start with:&lt;/p>
&lt;ul>
&lt;li>Tracks cursor movements throughout your Emacs editing session&lt;/li>
&lt;li>Creates a visual heat map showing movement patterns&lt;/li>
&lt;li>Provides analytics about your editing behaviour&lt;/li>
&lt;li>Persists data across sessions so we can have recall and overall data across many days&lt;/li>
&lt;/ul>
&lt;h2 id="building-our-foundation">Building Our Foundation&lt;/h2>
&lt;p>Lets crack on and get something down on paper and begin with the absolute minimum structure and expand from there.&lt;/p>
&lt;h3 id="step-1-create-the-basic-file-structure">Step 1: Create the Basic File Structure&lt;/h3>
&lt;p>Create a new file called &lt;code>cursor-heatmap.el&lt;/code> with the following contents:&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>&lt;span style="color:#75715e">;;; cursor-heatmap.el --- Monitor and visualize cursor movement patterns -*- lexical-binding: 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">;; Copyright (C) 2025&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">;; Author: Your Name &amp;lt;your.email@example.com&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#75715e">;; Version: 0.1.0&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#75715e">;; Package-Requires: ((emacs &amp;#34;26.1&amp;#34;))&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#75715e">;; Keywords: tools, analytics, visualization&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#75715e">;; URL: https://github.com/yourusername/cursor-heatmap&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">;;; Commentary:&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">;; This package will monitor cursor movements throughout an Emacs session and&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#75715e">;; generate heatmap reports showing where you move your cursor most frequently.&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#75715e">;;&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#75715e">;; Basic usage (to be implemented):&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#75715e">;; M-x cursor-heatmap-mode - start tracking&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#75715e">;; M-x cursor-heatmap-show-report - view results&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">;;; Code:&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">;; Package implementation will go here&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>(provide &lt;span style="color:#e6db74">&amp;#39;cursor-heatmap&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">;;; cursor-heatmap.el ends here&lt;/span>
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;h3 id="step-2-understanding-key-components">Step 2: Understanding Key Components&lt;/h3>
&lt;p>&lt;strong>Why &lt;code>lexical-binding: t&lt;/code>?&lt;/strong>&lt;/p>
&lt;p>This enables modern variable scoping in Emacs Lisp. Without it, all variables have dynamic scope, which can lead to surprising behaviour and bugs. Always use lexical binding for new packages.&lt;/p>
&lt;p>&lt;strong>The &lt;code>provide&lt;/code> statement:&lt;/strong>&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>(provide &lt;span style="color:#e6db74">&amp;#39;cursor-heatmap&lt;/span>)
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>This tells Emacs that loading this file provides the &lt;code>cursor-heatmap&lt;/code> feature. Other packages can then use &lt;code>(require 'cursor-heatmap)&lt;/code> to load your package.&lt;/p>
&lt;p>There are other more advanced ways of loading packages but we shall stick with the basic built-in Emacs package.el for now.&lt;/p>
&lt;p>&lt;strong>Version numbering:&lt;/strong>&lt;/p>
&lt;p>We start with &lt;code>0.1.0&lt;/code> following a simple versioning mechanism. As we add features, we&amp;rsquo;ll increment appropriately.&lt;/p>
&lt;h3 id="step-3-setting-up-customization-groups">Step 3: Setting Up Customization Groups&lt;/h3>
&lt;p>One of Emacs&amp;rsquo; greatest strengths is customizability. Let&amp;rsquo;s set up a customization group for our package:&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>&lt;span style="color:#75715e">;;; Code:&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>(defgroup cursor-heatmap &lt;span style="color:#66d9ef">nil&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#e6db74">&amp;#34;Cursor movement heatmap monitoring and visualization.&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> :group &lt;span style="color:#e6db74">&amp;#39;tools&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> :prefix &lt;span style="color:#e6db74">&amp;#34;cursor-heatmap-&amp;#34;&lt;/span>)
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>This creates a customization group that will appear under the &amp;ldquo;Tools&amp;rdquo; category in Emacs&amp;rsquo; customization interface. Users can access it via &lt;code>M-x customize-group cursor-heatmap&lt;/code>&lt;/p>
&lt;h3 id="step-4-adding-our-first-variables">Step 4: Adding Our First Variables&lt;/h3>
&lt;p>Let&amp;rsquo;s add some basic configuration variables:&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>(defcustom cursor-heatmap-grid-width &lt;span style="color:#ae81ff">100&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#e6db74">&amp;#34;Width of the heatmap grid (number of columns).&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> :type &lt;span style="color:#e6db74">&amp;#39;integer&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> :group &lt;span style="color:#e6db74">&amp;#39;cursor-heatmap&lt;/span>)
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>(defcustom cursor-heatmap-grid-height &lt;span style="color:#ae81ff">50&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#e6db74">&amp;#34;Height of the heatmap grid (number of rows).&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> :type &lt;span style="color:#e6db74">&amp;#39;integer&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> :group &lt;span style="color:#e6db74">&amp;#39;cursor-heatmap&lt;/span>)
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>The &lt;code>defcustom&lt;/code> macro creates variables that users can customize through the Emacs&amp;rsquo; interface. The &lt;code>:type&lt;/code> specification helps Emacs validate user input and provide appropriate UI widgets.&lt;/p>
&lt;h2 id="package-naming-conventions">Package Naming Conventions&lt;/h2>
&lt;p>Notice how all our variables start with &lt;code>cursor-heatmap-&lt;/code> This is a crucial convention in Emacs package development:&lt;/p>
&lt;ul>
&lt;li>&lt;strong>Prevents namespace collisions&lt;/strong>: With thousands of packages, prefixing prevents conflicts&lt;/li>
&lt;li>&lt;strong>Makes code searchable&lt;/strong>: Users can easily find all functions related to your package&lt;/li>
&lt;li>&lt;strong>Shows ownership&lt;/strong>: Clear indication of what belongs to your package&lt;/li>
&lt;/ul>
&lt;p>Common patterns:&lt;/p>
&lt;ul>
&lt;li>Variables: &lt;code>cursor-heatmap-variable-name&lt;/code>&lt;/li>
&lt;li>Functions: &lt;code>cursor-heatmap-function-name&lt;/code>&lt;/li>
&lt;li>Internal functions: &lt;code>cursor-heatmap--internal-function&lt;/code> (note the double dash)&lt;/li>
&lt;/ul>
&lt;h2 id="testing-our-foundation">Testing Our Foundation&lt;/h2>
&lt;p>Let&amp;rsquo;s make sure our basic structure works. Add this simple function to test:&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 cursor-heatmap-hello ()
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#e6db74">&amp;#34;Test function to verify package structure.&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> (&lt;span style="color:#a6e22e">message&lt;/span> &lt;span style="color:#e6db74">&amp;#34;Cursor heatmap package loaded successfully! Grid size: %dx%d&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> cursor-heatmap-grid-width cursor-heatmap-grid-height))
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>Now you can:&lt;/p>
&lt;ol>
&lt;li>Load the file: &lt;code>M-x load-file&lt;/code> and select &lt;code>cursor-heatmap.el&lt;/code>&lt;/li>
&lt;li>Test it: &lt;code>M-x cursor-heatmap-hello&lt;/code>&lt;/li>
&lt;li>Check customization: &lt;code>M-x customize-group cursor-heatmap&lt;/code>&lt;/li>
&lt;/ol>
&lt;h2 id="our-complete-foundation">Our Complete Foundation&lt;/h2>
&lt;p>Here&amp;rsquo;s our complete foundation file, I think I shall show this at the end of each post so progress can be monitored.&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>&lt;span style="color:#75715e">;;; cursor-heatmap.el --- Monitor and visualize cursor movement patterns -*- lexical-binding: 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">;; Copyright (C) 2025&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">;; Author: Your Name &amp;lt;your.email@example.com&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#75715e">;; Version: 0.1.0&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#75715e">;; Package-Requires: ((emacs &amp;#34;26.1&amp;#34;))&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#75715e">;; Keywords: tools, analytics, visualization&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#75715e">;; URL: https://github.com/yourusername/cursor-heatmap&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">;;; Commentary:&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">;; This package will monitor cursor movements throughout an Emacs session and&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#75715e">;; generate heatmap reports showing where you move your cursor most frequently.&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#75715e">;;&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#75715e">;; Basic usage (to be implemented):&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#75715e">;; M-x cursor-heatmap-mode - start tracking&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#75715e">;; M-x cursor-heatmap-show-report - view results&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">;;; Code:&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>(defgroup cursor-heatmap &lt;span style="color:#66d9ef">nil&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#e6db74">&amp;#34;Cursor movement heatmap monitoring and visualization.&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> :group &lt;span style="color:#e6db74">&amp;#39;tools&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> :prefix &lt;span style="color:#e6db74">&amp;#34;cursor-heatmap-&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>(defcustom cursor-heatmap-grid-width &lt;span style="color:#ae81ff">100&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#e6db74">&amp;#34;Width of the heatmap grid (number of columns).&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> :type &lt;span style="color:#e6db74">&amp;#39;integer&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> :group &lt;span style="color:#e6db74">&amp;#39;cursor-heatmap&lt;/span>)
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>(defcustom cursor-heatmap-grid-height &lt;span style="color:#ae81ff">50&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#e6db74">&amp;#34;Height of the heatmap grid (number of rows).&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> :type &lt;span style="color:#e6db74">&amp;#39;integer&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> :group &lt;span style="color:#e6db74">&amp;#39;cursor-heatmap&lt;/span>)
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>(defun cursor-heatmap-hello ()
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#e6db74">&amp;#34;Test function to verify package structure.&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> (&lt;span style="color:#a6e22e">message&lt;/span> &lt;span style="color:#e6db74">&amp;#34;Cursor heatmap package loaded successfully! Grid size: %dx%d&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> cursor-heatmap-grid-width cursor-heatmap-grid-height))
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>(provide &lt;span style="color:#e6db74">&amp;#39;cursor-heatmap&lt;/span>)
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;h2 id="what-we-ve-accomplished">What We&amp;rsquo;ve Accomplished&lt;/h2>
&lt;p>In this first post, we&amp;rsquo;ve established the foundation of our Emacs package:&lt;/p>
&lt;ul>
&lt;li>&lt;input checked="" disabled="" type="checkbox"> &lt;strong>Package structure&lt;/strong>: Proper header, metadata, and organization&lt;/li>
&lt;li>&lt;input checked="" disabled="" type="checkbox"> &lt;strong>Customization system&lt;/strong>: User-configurable options with validation&lt;/li>
&lt;li>&lt;input checked="" disabled="" type="checkbox"> &lt;strong>Naming conventions&lt;/strong>: Consistent, collision-free naming&lt;/li>
&lt;li>&lt;input checked="" disabled="" type="checkbox"> &lt;strong>Basic functionality&lt;/strong>: A working, loadable package&lt;/li>
&lt;li>&lt;input checked="" disabled="" type="checkbox"> &lt;strong>Testing framework&lt;/strong>: Simple way to verify our progress&lt;/li>
&lt;/ul>
&lt;h2 id="next-time">Next Time&lt;/h2>
&lt;p>In the next post, we&amp;rsquo;ll dive into the core functionality: tracking cursor position.&lt;/p>
&lt;p>We&amp;rsquo;ll explore:&lt;/p>
&lt;ul>
&lt;li>How Emacs represents cursor positions&lt;/li>
&lt;li>Different coordinate systems (pixels vs. characters)&lt;/li>
&lt;li>Setting up our first movement detection&lt;/li>
&lt;li>Handling edge cases and error conditions&lt;/li>
&lt;/ul></description></item><item><title>Red pandas also love Emacs!</title><link>https://www.emacs.dyerdwelling.family/emacs/20250710125504-emacs--red-pandas-also-love-emacs/</link><pubDate>Thu, 10 Jul 2025 12:55:00 +0100</pubDate><author>James Dyer</author><guid>https://www.emacs.dyerdwelling.family/emacs/20250710125504-emacs--red-pandas-also-love-emacs/</guid><description>&lt;p>Not much of a post this week (and in fact this is pretty low effort!), but here I present some fun with image generative AI!&lt;/p>
&lt;figure>&lt;img src="https://www.emacs.dyerdwelling.family/emacs/20250710125504-emacs--Red-Pandas-Also-Love-Emacs.jpg" width="100%">
&lt;/figure>
&lt;p>That is all 😀&lt;/p></description></item><item><title>New Package! - Simply Annotate: A Lightweight Annotation System</title><link>https://www.emacs.dyerdwelling.family/emacs/20250704224318-emacs--new-package-simply-annotate-a-lightweight-annotation-system/</link><pubDate>Fri, 04 Jul 2025 22:43:00 +0100</pubDate><author>James Dyer</author><guid>https://www.emacs.dyerdwelling.family/emacs/20250704224318-emacs--new-package-simply-annotate-a-lightweight-annotation-system/</guid><description>&lt;p>Yes thats right, yet another annotation system!, in this case it is a lightweight annotation system for Emacs that allows you to add persistent notes to any text file without modifying the original content.&lt;/p>
&lt;p>&lt;a href="https://melpa.org/#/simply-annotate">https://melpa.org/#/simply-annotate&lt;/a>
&lt;a href="https://github.com/captainflasmr/simply-annotate">https://github.com/captainflasmr/simply-annotate&lt;/a>&lt;/p>
&lt;figure>&lt;img src="https://www.emacs.dyerdwelling.family/emacs/20250704224318-emacs--New-Package-Simply-Annotate-A-Lightweight-Annotation-System.jpg" width="100%">
&lt;/figure>
&lt;p>Still not convinced it is any different to any other note taking package in Emacs given that previous generic sparse description? Well I shall do my best to explain my own note taking ideas. Of course this is also just a pet project to continue to learn elisp, so there is that too :)&lt;/p>
&lt;p>I developed this package to fill my own perceived gap in the existing Emacs note-taking ecosystem. Although &lt;code>annotate.el&lt;/code> comes close to what I needed, I wanted a simplified version and something with a few workflow adjustments that better suit my note taking preferences. The result is a lightweight alternative that handles annotations the way I have always wanted them to work.&lt;/p>
&lt;p>The main functional aspects that I felt were important to me that generally differ from other existing Emacs annotation packages are:&lt;/p>
&lt;ul>
&lt;li>Header-line status display with keybinding reminders&lt;/li>
&lt;li>Simple intuitive workflow (hopefully)&lt;/li>
&lt;li>Multiple display styles (highlight, fringe indicators, or both)&lt;/li>
&lt;li>Annotation editing via a dedicated buffer&lt;/li>
&lt;li>Lightweight (if possible &amp;lt;1000 lines - [UPDATE], no its not possible, lets try &amp;lt;2000 lines!)&lt;/li>
&lt;li>Multi-purpose key to show/edit/toggle annotations&lt;/li>
&lt;li>Dedicated annotation buffer that can pop in and out&lt;/li>
&lt;li>Quickly step through annotations with &lt;code>M-n&lt;/code> and &lt;code>M-p&lt;/code>&lt;/li>
&lt;li>Buffer summary of annotations in grep-mode format&lt;/li>
&lt;li>Quickly browse through all annotated files using completing-read&lt;/li>
&lt;li>Smart action command that adapts based on context&lt;/li>
&lt;li>Threading conversations for each annotation&lt;/li>
&lt;/ul>
&lt;p>See &lt;a href="https://github.com/captainflasmr/simply-annotate/blob/main/docs/simply-annotate.org">https://github.com/captainflasmr/simply-annotate/blob/main/docs/simply-annotate.org&lt;/a> for the manual!&lt;/p>
&lt;h2 id="quick-demo-video">Quick Demo Video&lt;/h2>
&lt;p>Open this file and open the simply-annotate.el file&lt;/p>
&lt;ul>
&lt;li>
&lt;p>highlight some regions and put in some annotation comments&lt;/p>
&lt;/li>
&lt;li>
&lt;p>step through annotation comments&lt;/p>
&lt;/li>
&lt;li>
&lt;p>show summary&lt;/p>
&lt;/li>
&lt;li>
&lt;p>delete some annotations&lt;/p>
&lt;/li>
&lt;/ul>
&lt;p>Go to the directory level and now browse the 2 files&lt;/p>
&lt;figure>&lt;img src="https://www.emacs.dyerdwelling.family/emacs/simply-annotate-screen-recording.gif" width="100%">
&lt;/figure>
&lt;h2 id="whats-new">Whats New&lt;/h2>
&lt;h3 id="0-dot-6-dot-0">&lt;span class="timestamp-wrapper">&lt;span class="timestamp">&amp;lt;2025-07-03 Thu&amp;gt; &lt;/span>&lt;/span> &lt;strong>0.6.0&lt;/strong>&lt;/h3>
&lt;p>A little refactoring and tidying up.&lt;/p>
&lt;h3 id="0-dot-5-dot-1">&lt;span class="timestamp-wrapper">&lt;span class="timestamp">&amp;lt;2025-06-29 Sun&amp;gt; &lt;/span>&lt;/span> &lt;strong>0.5.1&lt;/strong>&lt;/h3>
&lt;ul>
&lt;li>&lt;strong>Raw sexp editing&lt;/strong>: Edit annotation data structures directly as Elisp for complete control&lt;/li>
&lt;li>&lt;strong>Advanced editing capabilities&lt;/strong>: Full access to thread metadata, status, priority, and comments&lt;/li>
&lt;/ul>
&lt;p>&lt;strong>Updated Key Bindings:&lt;/strong>&lt;/p>
&lt;ul>
&lt;li>&lt;code>M-s e&lt;/code>: Edit annotation as raw Elisp sexp&lt;/li>
&lt;/ul>
&lt;h3 id="0-dot-5-dot-0">&lt;span class="timestamp-wrapper">&lt;span class="timestamp">&amp;lt;2025-06-20 Fri&amp;gt; &lt;/span>&lt;/span> &lt;strong>0.5.0&lt;/strong>&lt;/h3>
&lt;ul>
&lt;li>&lt;strong>Threading system&lt;/strong>: Add replies to annotations for conversations&lt;/li>
&lt;li>&lt;strong>Status management&lt;/strong>: Track progress (open, in-progress, resolved, closed)&lt;/li>
&lt;li>&lt;strong>Priority levels&lt;/strong>: Set importance (low, normal, high, critical)&lt;/li>
&lt;li>&lt;strong>Multi-author support&lt;/strong>: Configure team members for collaboration&lt;/li>
&lt;li>&lt;strong>Tag system&lt;/strong>: Organize with hashtags (#review, #bug, #question)&lt;/li>
&lt;li>&lt;strong>Org-mode export&lt;/strong>: Convert threads to structured TODO items&lt;/li>
&lt;li>&lt;strong>Enhanced display&lt;/strong>: Thread info in headers and lists&lt;/li>
&lt;li>&lt;strong>Author management&lt;/strong>: Flexible prompting modes and author changes&lt;/li>
&lt;/ul>
&lt;p>&lt;strong>New Key Bindings:&lt;/strong>&lt;/p>
&lt;ul>
&lt;li>&lt;code>M-s r&lt;/code>: Add reply to annotation&lt;/li>
&lt;li>&lt;code>M-s s&lt;/code>: Set status&lt;/li>
&lt;li>&lt;code>M-s p&lt;/code>: Set priority&lt;/li>
&lt;li>&lt;code>M-s t&lt;/code>: Add tag&lt;/li>
&lt;li>&lt;code>M-s a&lt;/code>: Change author&lt;/li>
&lt;li>&lt;code>M-s o&lt;/code>: Export to org-mode&lt;/li>
&lt;/ul>
&lt;h3 id="0-dot-0-dot-1">&lt;span class="timestamp-wrapper">&lt;span class="timestamp">&amp;lt;2025-06-20 Fri&amp;gt; &lt;/span>&lt;/span> &lt;strong>0.0.1&lt;/strong>&lt;/h3>
&lt;ul>
&lt;li>Basic annotation functionality&lt;/li>
&lt;li>Persistent storage&lt;/li>
&lt;li>Navigation commands&lt;/li>
&lt;li>Org-mode export&lt;/li>
&lt;li>Customizable highlighting&lt;/li>
&lt;/ul>
&lt;h2 id="installation">Installation&lt;/h2>
&lt;h3 id="melpa--recommended">MELPA (Recommended)&lt;/h3>
&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-emacs-lisp" data-lang="emacs-lisp">&lt;span style="display:flex;">&lt;span>(use-package simply-annotate
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> :bind (&lt;span style="color:#e6db74">&amp;#34;C-c A&amp;#34;&lt;/span> &lt;span style="color:#f92672">.&lt;/span> simply-annotate-mode))
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;h3 id="manual-installation">Manual Installation&lt;/h3>
&lt;ol>
&lt;li>Download &lt;code>simply-annotate.el&lt;/code>&lt;/li>
&lt;li>Place it in your Emacs &lt;code>load-path&lt;/code>&lt;/li>
&lt;li>Add to your configuration:&lt;/li>
&lt;/ol>
&lt;!--listend-->
&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-emacs-lisp" data-lang="emacs-lisp">&lt;span style="display:flex;">&lt;span>(require &lt;span style="color:#e6db74">&amp;#39;simply-annotate&lt;/span>)
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>(global-set-key (kbd &lt;span style="color:#e6db74">&amp;#34;C-c A&amp;#34;&lt;/span>) simply-annotate-mode)
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;h2 id="quick-start">Quick Start&lt;/h2>
&lt;ol>
&lt;li>Open any file&lt;/li>
&lt;li>Enable annotation mode: &lt;code>M-x simply-annotate-mode&lt;/code>&lt;/li>
&lt;li>Select/mark text and press &lt;code>M-s j&lt;/code> to create your first annotation&lt;/li>
&lt;li>Create some more annotations&lt;/li>
&lt;li>Navigate with &lt;code>M-n&lt;/code> (next) and &lt;code>M-p&lt;/code> (previous)&lt;/li>
&lt;li>Add replies: Press &lt;code>M-s r&lt;/code> on any annotation to reply&lt;/li>
&lt;li>Set status: Press &lt;code>M-s s&lt;/code> to track progress (open/resolved/etc.)&lt;/li>
&lt;li>Advanced editing: Press &lt;code>M-s e&lt;/code> to edit annotation data structure directly&lt;/li>
&lt;/ol>
&lt;h2 id="usage">Usage&lt;/h2>
&lt;h3 id="enabling-annotation-mode">Enabling Annotation Mode&lt;/h3>
&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-emacs-lisp" data-lang="emacs-lisp">&lt;span style="display:flex;">&lt;span>M-x simply-annotate-mode
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>Or bind to a convenient key:&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-emacs-lisp" data-lang="emacs-lisp">&lt;span style="display:flex;">&lt;span>(global-set-key (kbd &lt;span style="color:#e6db74">&amp;#34;C-c A&amp;#34;&lt;/span>) &lt;span style="color:#e6db74">&amp;#39;simply-annotate-mode&lt;/span>)
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;h3 id="creating-annotations">Creating Annotations&lt;/h3>
&lt;h4 id="simple">Simple&lt;/h4>
&lt;p>The &lt;code>M-s j&lt;/code> command (&lt;code>simply-annotate-smart-action&lt;/code>) is context-aware:&lt;/p>
&lt;ol>
&lt;li>&lt;strong>With region selected&lt;/strong>: Creates new annotation or edits existing one&lt;/li>
&lt;li>&lt;strong>On annotated text&lt;/strong>: Toggles annotation buffer visibility&lt;/li>
&lt;li>&lt;strong>With prefix (C-u M-s j)&lt;/strong>: Forces edit mode on existing annotation&lt;/li>
&lt;li>&lt;strong>Elsewhere&lt;/strong>: Creates annotation for current line&lt;/li>
&lt;li>Enter your annotation text in the dedicated buffer&lt;/li>
&lt;li>Save with &lt;code>C-c C-c&lt;/code>&lt;/li>
&lt;/ol>
&lt;h3 id="advanced-editing">Advanced Editing&lt;/h3>
&lt;h4 id="raw-sexp-editing">Raw Sexp Editing&lt;/h4>
&lt;p>For complete control over annotation data structures:&lt;/p>
&lt;ol>
&lt;li>Place cursor on any annotation&lt;/li>
&lt;li>Press &lt;code>M-s e&lt;/code> to open the raw sexp editor&lt;/li>
&lt;li>Edit the Elisp data structure directly:
&lt;ul>
&lt;li>Modify thread metadata (status, priority, tags)&lt;/li>
&lt;li>Edit comment text and timestamps&lt;/li>
&lt;li>Add/remove/reorder comments&lt;/li>
&lt;li>Change author information&lt;/li>
&lt;/ul>
&lt;/li>
&lt;li>Save with &lt;code>C-c C-c&lt;/code> or cancel with &lt;code>C-c C-k&lt;/code>&lt;/li>
&lt;/ol>
&lt;p>&lt;strong>Example sexp structure:&lt;/strong>&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-emacs-lisp" data-lang="emacs-lisp">&lt;span style="display:flex;">&lt;span>((id &lt;span style="color:#f92672">.&lt;/span> &lt;span style="color:#e6db74">&amp;#34;thread-123456&amp;#34;&lt;/span>)
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> (created &lt;span style="color:#f92672">.&lt;/span> &lt;span style="color:#e6db74">&amp;#34;2025-06-29T10:30:00&amp;#34;&lt;/span>)
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> (status &lt;span style="color:#f92672">.&lt;/span> &lt;span style="color:#e6db74">&amp;#34;open&amp;#34;&lt;/span>)
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> (priority &lt;span style="color:#f92672">.&lt;/span> &lt;span style="color:#e6db74">&amp;#34;high&amp;#34;&lt;/span>)
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> (tags &lt;span style="color:#f92672">.&lt;/span> (&lt;span style="color:#e6db74">&amp;#34;bug&amp;#34;&lt;/span> &lt;span style="color:#e6db74">&amp;#34;critical&amp;#34;&lt;/span>))
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> (comments &lt;span style="color:#f92672">.&lt;/span> (((author &lt;span style="color:#f92672">.&lt;/span> &lt;span style="color:#e6db74">&amp;#34;John Doe&amp;#34;&lt;/span>)
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> (timestamp &lt;span style="color:#f92672">.&lt;/span> &lt;span style="color:#e6db74">&amp;#34;2025-06-29T10:30:00&amp;#34;&lt;/span>)
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> (text &lt;span style="color:#f92672">.&lt;/span> &lt;span style="color:#e6db74">&amp;#34;Found a critical bug here&amp;#34;&lt;/span>)
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> (type &lt;span style="color:#f92672">.&lt;/span> &lt;span style="color:#e6db74">&amp;#34;comment&amp;#34;&lt;/span>))
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> ((author &lt;span style="color:#f92672">.&lt;/span> &lt;span style="color:#e6db74">&amp;#34;Jane Smith&amp;#34;&lt;/span>)
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> (timestamp &lt;span style="color:#f92672">.&lt;/span> &lt;span style="color:#e6db74">&amp;#34;2025-06-29T11:15:00&amp;#34;&lt;/span>)
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> (text &lt;span style="color:#f92672">.&lt;/span> &lt;span style="color:#e6db74">&amp;#34;I can reproduce this issue&amp;#34;&lt;/span>)
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> (type &lt;span style="color:#f92672">.&lt;/span> &lt;span style="color:#e6db74">&amp;#34;reply&amp;#34;&lt;/span>)))))
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;h3 id="threading-and-collaboration">Threading &amp;amp; Collaboration&lt;/h3>
&lt;h4 id="adding-replies">Adding Replies&lt;/h4>
&lt;ol>
&lt;li>Place cursor on any annotation&lt;/li>
&lt;li>Press &lt;code>M-s r&lt;/code> to add a reply&lt;/li>
&lt;li>Enter your response&lt;/li>
&lt;li>The annotation becomes a threaded conversation&lt;/li>
&lt;/ol>
&lt;h4 id="status-management">Status Management&lt;/h4>
&lt;ul>
&lt;li>Press &lt;code>M-s s&lt;/code> to set status: open, in-progress, resolved, closed&lt;/li>
&lt;li>Press &lt;code>M-s p&lt;/code> to set priority: low, normal, high, critical&lt;/li>
&lt;li>Press &lt;code>M-s t&lt;/code> to add tags like #review, #bug, #question&lt;/li>
&lt;/ul>
&lt;h4 id="author-management">Author Management&lt;/h4>
&lt;p>Configure for single-user or team workflows:&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-emacs-lisp" data-lang="emacs-lisp">&lt;span style="display:flex;">&lt;span>&lt;span style="color:#75715e">;; Single user (default behavior)&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>(setq simply-annotate-prompt-for-author &lt;span style="color:#66d9ef">nil&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">;; Team collaboration&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>(setq simply-annotate-author-list &lt;span style="color:#f92672">&amp;#39;&lt;/span>(&lt;span style="color:#e6db74">&amp;#34;John Doe&amp;#34;&lt;/span> &lt;span style="color:#e6db74">&amp;#34;Jane Smith&amp;#34;&lt;/span> &lt;span style="color:#e6db74">&amp;#34;Bob Wilson&amp;#34;&lt;/span>))
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>(setq simply-annotate-prompt-for-author &lt;span style="color:#e6db74">&amp;#39;threads-only&lt;/span>) &lt;span style="color:#75715e">; Prompt only for replies&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>(setq simply-annotate-remember-author-per-file &lt;span style="color:#66d9ef">t&lt;/span>) &lt;span style="color:#75715e">; Remember per file&lt;/span>
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>Available prompting modes:&lt;/p>
&lt;ul>
&lt;li>&lt;code>nil&lt;/code>: Never prompt (single-user mode)&lt;/li>
&lt;li>&lt;code>'first-only&lt;/code>: Prompt once per session&lt;/li>
&lt;li>&lt;code>'always&lt;/code>: Prompt for every annotation&lt;/li>
&lt;li>&lt;code>'threads-only&lt;/code>: Prompt only for thread replies (great for reviews)&lt;/li>
&lt;/ul>
&lt;h4 id="author-commands">Author Commands&lt;/h4>
&lt;ul>
&lt;li>&lt;code>M-s a&lt;/code>: Change author of existing annotation/comment&lt;/li>
&lt;/ul>
&lt;h3 id="display-styles">Display Styles&lt;/h3>
&lt;p>Simply Annotate supports three display styles:&lt;/p>
&lt;ul>
&lt;li>&lt;strong>Highlight&lt;/strong>: Traditional background highlighting (default)&lt;/li>
&lt;li>&lt;strong>Fringe&lt;/strong>: Shows indicators in the left fringe&lt;/li>
&lt;li>&lt;strong>Both&lt;/strong>: Combines highlighting with fringe indicators&lt;/li>
&lt;/ul>
&lt;p>Change styles with &lt;code>M-s ]&lt;/code> or customize &lt;code>simply-annotate-display-style&lt;/code>.&lt;/p>
&lt;h3 id="viewing-annotations">Viewing Annotations&lt;/h3>
&lt;p>When &lt;code>simply-annotate-mode&lt;/code> is active:&lt;/p>
&lt;ul>
&lt;li>Annotated text is displayed according to your chosen style&lt;/li>
&lt;li>The header line shows annotation count, status info, and available commands&lt;/li>
&lt;li>* Thread info*: Header shows &lt;code>[OPEN/HIGH:3]&lt;/code> for status, priority, and comment count&lt;/li>
&lt;li>Moving to annotated text shows annotation details in the header&lt;/li>
&lt;li>Press &lt;code>M-s j&lt;/code> on annotated text to view/edit in detail&lt;/li>
&lt;/ul>
&lt;h3 id="navigation">Navigation&lt;/h3>
&lt;table>
&lt;thead>
&lt;tr>
&lt;th>Key Binding&lt;/th>
&lt;th>Action&lt;/th>
&lt;/tr>
&lt;/thead>
&lt;tbody>
&lt;tr>
&lt;td>&lt;code>M-n&lt;/code>&lt;/td>
&lt;td>Jump to next annotation&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>&lt;code>M-p&lt;/code>&lt;/td>
&lt;td>Jump to previous annotation&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>&lt;code>M-s j&lt;/code>&lt;/td>
&lt;td>Smart action (context-aware)&lt;/td>
&lt;/tr>
&lt;/tbody>
&lt;/table>
&lt;h3 id="managing-annotations">Managing Annotations&lt;/h3>
&lt;h4 id="creating">Creating&lt;/h4>
&lt;ul>
&lt;li>Select/mark some text&lt;/li>
&lt;li>Press &lt;code>M-s j&lt;/code> to open the annotation buffer&lt;/li>
&lt;li>Make your changes&lt;/li>
&lt;li>Save with &lt;code>C-c C-c&lt;/code>&lt;/li>
&lt;/ul>
&lt;h4 id="editing">Editing&lt;/h4>
&lt;p>&lt;strong>Standard Editing:&lt;/strong>&lt;/p>
&lt;ul>
&lt;li>Place cursor on annotated text&lt;/li>
&lt;li>Press &lt;code>C-u M-s j&lt;/code> to open the annotation buffer&lt;/li>
&lt;li>Make your changes&lt;/li>
&lt;li>Save with &lt;code>C-c C-c&lt;/code>&lt;/li>
&lt;/ul>
&lt;p>&lt;strong>Advanced Sexp Editing:&lt;/strong>&lt;/p>
&lt;ul>
&lt;li>Place cursor on annotated text&lt;/li>
&lt;li>Press &lt;code>M-s e&lt;/code> to open the raw sexp editor&lt;/li>
&lt;li>Edit the complete data structure&lt;/li>
&lt;li>Save with &lt;code>C-c C-c&lt;/code> or cancel with &lt;code>C-c C-k&lt;/code>&lt;/li>
&lt;/ul>
&lt;h4 id="deleting">Deleting&lt;/h4>
&lt;ul>
&lt;li>Place cursor on annotated text&lt;/li>
&lt;li>Press &lt;code>M-s -&lt;/code> to remove the annotation&lt;/li>
&lt;/ul>
&lt;h4 id="listing-all-annotations">Listing All Annotations&lt;/h4>
&lt;ul>
&lt;li>Press &lt;code>M-s l&lt;/code> to open a grep-mode buffer showing all annotations in the current file&lt;/li>
&lt;li>&lt;strong>&lt;strong>Enhanced display&lt;/strong>&lt;/strong>: Shows thread status, priority, comment counts, and author info&lt;/li>
&lt;li>Click on line numbers, press &lt;code>Enter&lt;/code> or &lt;code>n/p&lt;/code> keys to jump directly to annotations&lt;/li>
&lt;li>Perfect for getting an overview of all your notes and their status&lt;/li>
&lt;/ul>
&lt;h4 id="cross-file-overview">Cross-file Overview&lt;/h4>
&lt;ul>
&lt;li>Press &lt;code>M-s 0&lt;/code> to browse annotations across all files&lt;/li>
&lt;li>Select a file from the completion list&lt;/li>
&lt;li>&lt;strong>&lt;strong>Statistics&lt;/strong>&lt;/strong>: Shows annotation counts and status summaries per file&lt;/li>
&lt;li>View all annotations for that file in &lt;code>grep-mode&lt;/code> format&lt;/li>
&lt;li>Source file is presented along with &lt;code>grep-mode&lt;/code> list of annotations&lt;/li>
&lt;/ul>
&lt;h3 id="org-mode-integration">Org-mode Integration&lt;/h3>
&lt;p>Export your annotation threads to org-mode files for further processing:&lt;/p>
&lt;ul>
&lt;li>Press &lt;code>M-s o&lt;/code> to export current buffer annotations to an org file&lt;/li>
&lt;li>Each thread becomes a TODO item with proper metadata&lt;/li>
&lt;li>Replies become sub-entries&lt;/li>
&lt;li>Status, priority, tags, and timestamps are preserved&lt;/li>
&lt;/ul>
&lt;h2 id="tips-and-tricks">Tips and Tricks&lt;/h2>
&lt;h3 id="workflow-suggestions">Workflow Suggestions&lt;/h3>
&lt;p>Enable the mode globally if you wish for all files!&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-emacs-lisp" data-lang="emacs-lisp">&lt;span style="display:flex;">&lt;span>(use-package simply-annotate
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> :hook
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> (find-file-hook &lt;span style="color:#f92672">.&lt;/span> simply-annotate-mode)
&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 A&amp;#34;&lt;/span> &lt;span style="color:#f92672">.&lt;/span> simply-annotate-mode)
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> (&lt;span style="color:#e6db74">&amp;#34;C-c 0&amp;#34;&lt;/span> &lt;span style="color:#f92672">.&lt;/span> simply-annotate-show-all))
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;h3 id="smart-action-usage-patterns">Smart Action Usage Patterns&lt;/h3>
&lt;ul>
&lt;li>&lt;strong>Quick annotation&lt;/strong>: No selection, &lt;code>M-s j&lt;/code> to annotate current line&lt;/li>
&lt;li>&lt;strong>Edit existing&lt;/strong>: &lt;code>C-u M-s j&lt;/code> on annotated text to force edit mode&lt;/li>
&lt;li>&lt;strong>Toggle view&lt;/strong>: &lt;code>M-s j&lt;/code> on annotated text to show/hide annotation buffer&lt;/li>
&lt;li>&lt;strong>Region annotation&lt;/strong>: Select text, &lt;code>M-s j&lt;/code> to create detailed annotation&lt;/li>
&lt;/ul>
&lt;h3 id="advanced-editing-tips">Advanced Editing Tips&lt;/h3>
&lt;ul>
&lt;li>&lt;strong>Bulk operations&lt;/strong>: Use &lt;code>M-s e&lt;/code> to edit multiple comments at once in sexp mode&lt;/li>
&lt;li>&lt;strong>Data migration&lt;/strong>: Copy annotation structures between files using sexp editing&lt;/li>
&lt;li>&lt;strong>Precision control&lt;/strong>: Manually adjust timestamps, IDs, or metadata via sexp editing&lt;/li>
&lt;li>&lt;strong>Complex threading&lt;/strong>: Create sophisticated reply structures that aren&amp;rsquo;t possible through the UI&lt;/li>
&lt;/ul>
&lt;h3 id="display-style-tips">Display Style Tips&lt;/h3>
&lt;ul>
&lt;li>Use &lt;strong>fringe&lt;/strong> mode for code files to minimize visual distraction&lt;/li>
&lt;li>Use &lt;strong>highlight&lt;/strong> mode for documents where you want emphasis&lt;/li>
&lt;li>Use &lt;strong>both&lt;/strong> mode for critical files requiring maximum attention&lt;/li>
&lt;li>Change styles on-the-fly with &lt;code>M-s ]&lt;/code> based on current task&lt;/li>
&lt;/ul>
&lt;h3 id="performance-notes">Performance Notes&lt;/h3>
&lt;ul>
&lt;li>Annotations are loaded on-demand per buffer&lt;/li>
&lt;li>Large numbers of annotations (100+) may slightly impact performance&lt;/li>
&lt;li>Fringe mode generally has better performance than highlight mode&lt;/li>
&lt;li>&lt;strong>&lt;strong>Threading&lt;/strong>&lt;/strong>: Complex threads (10+ replies) may slow annotation buffer rendering&lt;/li>
&lt;li>&lt;strong>&lt;strong>Sexp editing&lt;/strong>&lt;/strong>: Large annotation structures may take a moment to format and parse&lt;/li>
&lt;/ul></description></item><item><title>Setting up a C# Language Server (csharp-ls) on an Air-Gapped Windows System</title><link>https://www.emacs.dyerdwelling.family/emacs/20250627222703-emacs--setting-up-csharp-language-server-csharp-ls-on-air-gapped-windows-system/</link><pubDate>Fri, 27 Jun 2025 22:27:00 +0100</pubDate><author>James Dyer</author><guid>https://www.emacs.dyerdwelling.family/emacs/20250627222703-emacs--setting-up-csharp-language-server-csharp-ls-on-air-gapped-windows-system/</guid><description>&lt;p>For a little while now, I have been developing C# applications at work and on Windows!, possibly the perfect storm for Emacs, but actually, it copes pretty well, especially with eglot and the LSP server OmniSharp.&lt;/p>
&lt;p>But recently my work setup has become even more air-gapped, and unfortunately, I reached a point where I couldn&amp;rsquo;t get eglot and OmniSharp working together. I gave it a really good try, but I think the mishmash and dependency installation madness finally took a toll.&lt;/p>
&lt;p>It has something to do with trying to find MSBuild.exe, but due to my potentially wonky installation, I couldn&amp;rsquo;t figure out how to detect it. If Visual Studio is installed on the system, there are no problems; however, offline installation, even of the Community edition, is not an easy task.&lt;/p>
&lt;p>So, the solution? Well, I thought I would try another C# LSP in the form of csharp-ls. Its deliverable form is a NuGet package, which can be extracted, placed in the relevant location, and then you&amp;rsquo;re pretty much off and running!&lt;/p>
&lt;figure>&lt;img src="https://www.emacs.dyerdwelling.family/emacs/20250627222703-emacs--Setting-up-CSharp-Language-Server-csharp-ls-on-Air-Gapped-Windows-System.jpg" width="100%">
&lt;/figure>
&lt;p>So here is how I did it:&lt;/p>
&lt;h2 id="step-1-download-csharp-ls-nuget-components">Step 1: Download csharp-ls nuget Components&lt;/h2>
&lt;p>On a machine with internet access (which fortunately I do have access to):&lt;/p>
&lt;h3 id="download-csharp-ls">Download csharp-ls&lt;/h3>
&lt;ul>
&lt;li>Go to the &lt;a href="https://www.nuget.org/packages/csharp-ls/">csharp-ls NuGet page&lt;/a>&lt;/li>
&lt;li>Click &amp;ldquo;Download package&amp;rdquo; to get the &lt;code>.nupkg&lt;/code> file&lt;/li>
&lt;li>Rename the file from &lt;code>.nupkg&lt;/code> to &lt;code>.zip&lt;/code>&lt;/li>
&lt;li>Extract the contents&lt;/li>
&lt;/ul>
&lt;h2 id="step-2-install-csharp-ls--airgapped-machine">Step 2: Install csharp-ls (Airgapped Machine)&lt;/h2>
&lt;ol>
&lt;li>
&lt;p>Create a directory in your Emacs installation:&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-cmd" data-lang="cmd">&lt;span style="display:flex;">&lt;span> &lt;span style="color:#66d9ef">mkdir&lt;/span> &lt;span style="color:#e6db74">&amp;#34;c:\path\to\emacs\bin\csharp-ls&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;/li>
&lt;li>
&lt;p>Copy the extracted csharp-ls contents to this directory. The key file you need is:&lt;/p>
&lt;pre tabindex="0">&lt;code class="language-nil" data-lang="nil"> tools\net9.0\any\CSharpLanguageServer.dll
&lt;/code>&lt;/pre>&lt;/li>
&lt;/ol>
&lt;h2 id="step-3-configure-emacs">Step 3: Configure Emacs&lt;/h2>
&lt;p>Add this configuration to your Emacs init file:&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>&lt;span style="color:#75715e">;; Configure csharp-ls as the language server for C#&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>(setq eglot-server-programs
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#f92672">&amp;#39;&lt;/span>((csharp-mode &lt;span style="color:#f92672">.&lt;/span> (&lt;span style="color:#e6db74">&amp;#34;dotnet&amp;#34;&lt;/span> &lt;span style="color:#e6db74">&amp;#34;c:/path/to/emacs/bin/csharp-ls/tools/net9.0/any/CSharpLanguageServer.dll&amp;#34;&lt;/span>))))
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;h2 id="step-4-set-up-your-c-project-structure">Step 4: Set Up Your C# Project Structure&lt;/h2>
&lt;p>Basically defining a source code collection as a project in Emacs. This can be a solution file, a source code control directory or as I generally have set up, an empty .project file&lt;/p>
&lt;h2 id="step-5-set-up-paths">Step 5: Set up paths&lt;/h2>
&lt;p>This may not be needed, but for my own sanity and just in case, absorb the new directory in to the PATH and exec-path variables.&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>(when (&lt;span style="color:#a6e22e">eq&lt;/span> system-type &lt;span style="color:#e6db74">&amp;#39;windows-nt&lt;/span>)
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> (let ((xPaths
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#f92672">`&lt;/span>(
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#f92672">,&lt;/span>(&lt;span style="color:#a6e22e">expand-file-name&lt;/span> &lt;span style="color:#e6db74">&amp;#34;~/bin/csharp-ls/tools/net9.0/any&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> (setenv &lt;span style="color:#e6db74">&amp;#34;PATH&amp;#34;&lt;/span> (&lt;span style="color:#a6e22e">concat&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> (&lt;span style="color:#a6e22e">mapconcat&lt;/span> &lt;span style="color:#e6db74">&amp;#39;identity&lt;/span> xPaths &lt;span style="color:#e6db74">&amp;#34;;&amp;#34;&lt;/span>)))
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> (setq exec-path (&lt;span style="color:#a6e22e">append&lt;/span> (split-string winPaths &lt;span style="color:#e6db74">&amp;#34;;&amp;#34;&lt;/span>) xPaths (&lt;span style="color:#a6e22e">list&lt;/span> &lt;span style="color:#e6db74">&amp;#34;.&amp;#34;&lt;/span> exec-directory)))))
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>So it works as I had it with &lt;code>OmniSharp&lt;/code> and that really is all I can ask for, lets see how I get on!&lt;/p></description></item><item><title>Ollama-Buddy 0.13.1: Curl backend support and some optimizations</title><link>https://www.emacs.dyerdwelling.family/emacs/20250621100507-emacs--ollama-buddy-0-13-1-curl-backend-support-and-some-optimizations/</link><pubDate>Sat, 21 Jun 2025 10:05:00 +0100</pubDate><author>James Dyer</author><guid>https://www.emacs.dyerdwelling.family/emacs/20250621100507-emacs--ollama-buddy-0-13-1-curl-backend-support-and-some-optimizations/</guid><description>&lt;p>This is more of a maintenance update. The main headline is the addition of an option to select a curl-based backend for those who may encounter networking issues. By default, ollama-buddy uses built-in low-level networking calls, but if you have network issues, you can now switch to curl!&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="0-dot-13-dot-1">&lt;span class="timestamp-wrapper">&lt;span class="timestamp">&amp;lt;2025-06-21 Sat&amp;gt; &lt;/span>&lt;/span> &lt;strong>0.13.1&lt;/strong>&lt;/h2>
&lt;p>Refactored content register processing to be more efficient and added a new Emacs package brainstorming prompt file.&lt;/p>
&lt;h2 id="0-dot-13-dot-0">&lt;span class="timestamp-wrapper">&lt;span class="timestamp">&amp;lt;2025-06-15 Sun&amp;gt; &lt;/span>&lt;/span> &lt;strong>0.13.0&lt;/strong>&lt;/h2>
&lt;p>Added curl communication backend with fallback support&lt;/p>
&lt;ul>
&lt;li>Added ollama-buddy-curl.el as separate backend implementation&lt;/li>
&lt;li>Implemented backend dispatcher system in ollama-buddy-core.el&lt;/li>
&lt;li>Updated all async functions to use backend dispatcher&lt;/li>
&lt;li>Added curl backend validation and testing functions&lt;/li>
&lt;li>Maintained full compatibility with existing network process backend&lt;/li>
&lt;/ul>
&lt;p>When building Emacs packages that communicate with external services, network connectivity can sometimes be a pain point. While Emacs&amp;rsquo;s built-in &lt;code>make-network-process&lt;/code> works great in most cases, some users have encountered issues on certain systems or network configurations. That&amp;rsquo;s why now I have added a curl-based communication backend to give you an additional option, who knows, maybe it will solve your ollama communication issues!&lt;/p>
&lt;p>The original ollama-buddy implementation relied exclusively on Emacs&amp;rsquo;s native network process functionality. While this works well for most users, I occasionally heard from users who experienced network process failures/flakiness on certain systems.&lt;/p>
&lt;p>Rather than trying to work around these edge cases in the network process code, I took a different approach: I added curl as an alternative communication backend! This gives users a battle-tested, widely-available HTTP client as a fallback option.&lt;/p>
&lt;p>Users can enable the curl backend with a simple customization:&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-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> :config
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#75715e">;; Load curl backend first&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> (require &lt;span style="color:#e6db74">&amp;#39;ollama-buddy-curl&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">;; Then set the backend&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> (setq ollama-buddy-communication-backend &lt;span style="color:#e6db74">&amp;#39;curl&lt;/span>))
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>and then switch backends from the chat buffer &lt;code>C-c e&lt;/code>&lt;/p>
&lt;p>The curl backend supports everything the network backend does:&lt;/p>
&lt;ul>
&lt;li>Real-time streaming responses&lt;/li>
&lt;li>Vision model support with image attachments&lt;/li>
&lt;li>File attachments and context&lt;/li>
&lt;li>All Ollama API parameters&lt;/li>
&lt;li>Multishot model sequences&lt;/li>
&lt;/ul>
&lt;p>If curl is selected but not available, the system automatically falls back to the network process with a helpful warning message.&lt;/p>
&lt;p>From a user perspective, the backend choice is largely transparent. The main indicators are:&lt;/p>
&lt;ul>
&lt;li>Status line shows &lt;code>[C]&lt;/code> for curl or &lt;code>[N]&lt;/code> for network&lt;/li>
&lt;li>Process list shows &lt;code>ollama-chat-curl&lt;/code> vs &lt;code>ollama-chat-stream&lt;/code> processes&lt;/li>
&lt;li>Curl backend shows &amp;ldquo;Curl Processing&amp;hellip;&amp;rdquo; in status messages&lt;/li>
&lt;/ul>
&lt;p>Everything else - streaming behaviour, response formatting, error handling - works identically regardless of the backend.&lt;/p>
&lt;h2 id="0-dot-12-dot-1">&lt;span class="timestamp-wrapper">&lt;span class="timestamp">&amp;lt;2025-05-31 Sat&amp;gt; &lt;/span>&lt;/span> &lt;strong>0.12.1&lt;/strong>&lt;/h2>
&lt;p>Optimized Unicode escape function to fix blocking with large file attachments&lt;/p>
&lt;ul>
&lt;li>Fixed UI blocking when sending large attached files&lt;/li>
&lt;li>Used temp buffer with delete-char/insert instead of repeated concat calls&lt;/li>
&lt;li>Reduced processing time from minutes to milliseconds for large payloads&lt;/li>
&lt;/ul></description></item><item><title>Emacs dired with Ultra-Lightweight Visual Icons</title><link>https://www.emacs.dyerdwelling.family/emacs/20250612223745-emacs--emacs-dired-with-ultra-lightweight-visual-icons/</link><pubDate>Thu, 12 Jun 2025 22:37:00 +0100</pubDate><author>James Dyer</author><guid>https://www.emacs.dyerdwelling.family/emacs/20250612223745-emacs--emacs-dired-with-ultra-lightweight-visual-icons/</guid><description>&lt;p>If you spend any time browsing files in Emacs, you&amp;rsquo;ve probably wished for a bit more visual distinction in dired buffers?. While packages like &lt;code>all-the-icons-dired&lt;/code> provide beautiful icon sets, they come with dependencies, font requirements, and potential compatibility issues. What if you could get meaningful visual file type indicators with just a few simple lines of pure Elisp? I have adapted the original idea provided by &lt;code>Emacs-solo&lt;/code> and focused more on the earlier Unicode characters that are most likely to always be present in an Emacs environment.&lt;/p>
&lt;p>Popular dired icon packages often require:&lt;/p>
&lt;ul>
&lt;li>Installing specific icon fonts&lt;/li>
&lt;li>Managing font fallbacks across different systems&lt;/li>
&lt;li>Dealing with alignment issues in terminal Emacs&lt;/li>
&lt;li>Large dependency chains that slow down startup&lt;/li>
&lt;li>Compatibility headaches when sharing configs&lt;/li>
&lt;/ul>
&lt;p>I have also noticed that Emacs can just crash if it is dealing with an icon that isn&amp;rsquo;t installed on the system.&lt;/p>
&lt;p>Here&amp;rsquo;s an implementation that adds visual file type indicators using only standard Unicode characters. Of course the icons-map isn&amp;rsquo;t exhaustive but might be a sensible minimal starting point.&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>(defvar dired-icons-map
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#f92672">&amp;#39;&lt;/span>((&lt;span style="color:#e6db74">&amp;#34;el&amp;#34;&lt;/span> &lt;span style="color:#f92672">.&lt;/span> &lt;span style="color:#e6db74">&amp;#34;λ&amp;#34;&lt;/span>) (&lt;span style="color:#e6db74">&amp;#34;rb&amp;#34;&lt;/span> &lt;span style="color:#f92672">.&lt;/span> &lt;span style="color:#e6db74">&amp;#34;◆&amp;#34;&lt;/span>) (&lt;span style="color:#e6db74">&amp;#34;js&amp;#34;&lt;/span> &lt;span style="color:#f92672">.&lt;/span> &lt;span style="color:#e6db74">&amp;#34;○&amp;#34;&lt;/span>) (&lt;span style="color:#e6db74">&amp;#34;ts&amp;#34;&lt;/span> &lt;span style="color:#f92672">.&lt;/span> &lt;span style="color:#e6db74">&amp;#34;●&amp;#34;&lt;/span>) (&lt;span style="color:#e6db74">&amp;#34;json&amp;#34;&lt;/span> &lt;span style="color:#f92672">.&lt;/span> &lt;span style="color:#e6db74">&amp;#34;◎&amp;#34;&lt;/span>) (&lt;span style="color:#e6db74">&amp;#34;md&amp;#34;&lt;/span> &lt;span style="color:#f92672">.&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:#e6db74">&amp;#34;txt&amp;#34;&lt;/span> &lt;span style="color:#f92672">.&lt;/span> &lt;span style="color:#e6db74">&amp;#34;□&amp;#34;&lt;/span>) (&lt;span style="color:#e6db74">&amp;#34;html&amp;#34;&lt;/span> &lt;span style="color:#f92672">.&lt;/span> &lt;span style="color:#e6db74">&amp;#34;▲&amp;#34;&lt;/span>) (&lt;span style="color:#e6db74">&amp;#34;css&amp;#34;&lt;/span> &lt;span style="color:#f92672">.&lt;/span> &lt;span style="color:#e6db74">&amp;#34;▼&amp;#34;&lt;/span>) (&lt;span style="color:#e6db74">&amp;#34;png&amp;#34;&lt;/span> &lt;span style="color:#f92672">.&lt;/span> &lt;span style="color:#e6db74">&amp;#34;◉&amp;#34;&lt;/span>) (&lt;span style="color:#e6db74">&amp;#34;jpg&amp;#34;&lt;/span> &lt;span style="color:#f92672">.&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:#e6db74">&amp;#34;pdf&amp;#34;&lt;/span> &lt;span style="color:#f92672">.&lt;/span> &lt;span style="color:#e6db74">&amp;#34;▣&amp;#34;&lt;/span>) (&lt;span style="color:#e6db74">&amp;#34;zip&amp;#34;&lt;/span> &lt;span style="color:#f92672">.&lt;/span> &lt;span style="color:#e6db74">&amp;#34;▢&amp;#34;&lt;/span>) (&lt;span style="color:#e6db74">&amp;#34;py&amp;#34;&lt;/span> &lt;span style="color:#f92672">.&lt;/span> &lt;span style="color:#e6db74">&amp;#34;∆&amp;#34;&lt;/span>) (&lt;span style="color:#e6db74">&amp;#34;c&amp;#34;&lt;/span> &lt;span style="color:#f92672">.&lt;/span> &lt;span style="color:#e6db74">&amp;#34;◇&amp;#34;&lt;/span>) (&lt;span style="color:#e6db74">&amp;#34;sql&amp;#34;&lt;/span> &lt;span style="color:#f92672">.&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:#e6db74">&amp;#34;mp3&amp;#34;&lt;/span> &lt;span style="color:#f92672">.&lt;/span> &lt;span style="color:#e6db74">&amp;#34;♪&amp;#34;&lt;/span>) (&lt;span style="color:#e6db74">&amp;#34;mp4&amp;#34;&lt;/span> &lt;span style="color:#f92672">.&lt;/span> &lt;span style="color:#e6db74">&amp;#34;▶&amp;#34;&lt;/span>) (&lt;span style="color:#e6db74">&amp;#34;exe&amp;#34;&lt;/span> &lt;span style="color:#f92672">.&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>&lt;/span>&lt;span style="display:flex;">&lt;span>(defun dired-add-icons ()
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> (when (derived-mode-p &lt;span style="color:#e6db74">&amp;#39;dired-mode&lt;/span>)
&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> (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">point-min&lt;/span>))
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> (while (and (not (&lt;span style="color:#a6e22e">eobp&lt;/span>)) (&lt;span style="color:#a6e22e">&amp;lt;&lt;/span> (line-number-at-pos) &lt;span style="color:#ae81ff">200&lt;/span>))
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> (condition-case &lt;span style="color:#66d9ef">nil&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> (let ((line (&lt;span style="color:#a6e22e">buffer-substring-no-properties&lt;/span> (&lt;span style="color:#a6e22e">line-beginning-position&lt;/span>) (&lt;span style="color:#a6e22e">line-end-position&lt;/span>))))
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> (when (and (&lt;span style="color:#a6e22e">&amp;gt;&lt;/span> (&lt;span style="color:#a6e22e">length&lt;/span> line) &lt;span style="color:#ae81ff">10&lt;/span>)
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> (&lt;span style="color:#a6e22e">string-match&lt;/span> &lt;span style="color:#e6db74">&amp;#34;\\([rwxd-]\\{10\\}\\)&amp;#34;&lt;/span> line)
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> (dired-move-to-filename &lt;span style="color:#66d9ef">t&lt;/span>)
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> (not (&lt;span style="color:#a6e22e">looking-at&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* ((is-dir (&lt;span style="color:#a6e22e">eq&lt;/span> (&lt;span style="color:#a6e22e">aref&lt;/span> line (&lt;span style="color:#a6e22e">match-beginning&lt;/span> &lt;span style="color:#ae81ff">1&lt;/span>)) &lt;span style="color:#e6db74">?d&lt;/span>))
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> (filename (and (&lt;span style="color:#a6e22e">string-match&lt;/span> &lt;span style="color:#e6db74">&amp;#34;\\([^ ]+\\)$&amp;#34;&lt;/span> line) (match-string &lt;span style="color:#ae81ff">1&lt;/span> line)))
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> (icon (cond (is-dir &lt;span style="color:#e6db74">&amp;#34;▶&amp;#34;&lt;/span>)
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> ((and filename (&lt;span style="color:#a6e22e">string-match&lt;/span> &lt;span style="color:#e6db74">&amp;#34;\\.\\([^.]+\\)$&amp;#34;&lt;/span> filename))
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> (or (&lt;span style="color:#a6e22e">cdr&lt;/span> (&lt;span style="color:#a6e22e">assoc&lt;/span> (&lt;span style="color:#a6e22e">downcase&lt;/span> (match-string &lt;span style="color:#ae81ff">1&lt;/span> filename)) dired-icons-map)) &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:#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">insert&lt;/span> icon &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">error&lt;/span> &lt;span style="color:#66d9ef">nil&lt;/span>))
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> (&lt;span style="color:#a6e22e">forward-line&lt;/span>))))))
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>(add-hook &lt;span style="color:#e6db74">&amp;#39;dired-after-readin-hook&lt;/span> &lt;span style="color:#e6db74">&amp;#39;dired-add-icons&lt;/span>)
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>The icons use only standard Unicode geometric shapes and mathematical symbols that have been supported in every font since the 1990s. No special fonts required, it works identically in:&lt;/p>
&lt;ul>
&lt;li>Terminal Emacs&lt;/li>
&lt;li>GUI Emacs on any platform&lt;/li>
&lt;li>Ancient and modern Emacs versions&lt;/li>
&lt;/ul>
&lt;p>There are some advantages to be had:&lt;/p>
&lt;ul>
&lt;li>&lt;strong>Zero dependencies&lt;/strong>: Pure Elisp, no external packages&lt;/li>
&lt;li>&lt;strong>Tiny footprint&lt;/strong>: 30 lines vs hundreds in full icon packages&lt;/li>
&lt;li>&lt;strong>Instant startup&lt;/strong>: No font loading or icon caching&lt;/li>
&lt;li>&lt;strong>Robust error handling&lt;/strong>: Gracefully skips problematic files&lt;/li>
&lt;li>&lt;strong>Performance limits&lt;/strong>: Processes max 200 files to prevent freezing but of course that can be modified to taste&lt;/li>
&lt;/ul>
&lt;p>Once added to your config, it just works. No font updates, no package maintenance, no breaking changes from upstream dependencies.&lt;/p>
&lt;p>Here is a typical example:&lt;/p>
&lt;p>After:&lt;/p>
&lt;figure>&lt;img src="https://www.emacs.dyerdwelling.family/emacs/20250612223745-emacs--Emacs-Dired-with-Ultra-Lightweight-Visual-Icons.jpg" width="100%">
&lt;/figure>
&lt;p>Before:&lt;/p>
&lt;figure>&lt;img src="https://www.emacs.dyerdwelling.family/emacs/20250612223745-emacs--Emacs-Dired-with-Ultra-Lightweight-Visual-Icons2.jpg" width="100%">
&lt;/figure>
&lt;p>Want different icons? Just modify the &lt;code>dired-icons-map&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>&lt;span style="color:#75715e">;; Prefer asterisks for code files?&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>(&lt;span style="color:#e6db74">&amp;#34;js&amp;#34;&lt;/span> &lt;span style="color:#f92672">.&lt;/span> &lt;span style="color:#e6db74">&amp;#34;*&amp;#34;&lt;/span>) (&lt;span style="color:#e6db74">&amp;#34;py&amp;#34;&lt;/span> &lt;span style="color:#f92672">.&lt;/span> &lt;span style="color:#e6db74">&amp;#34;*&amp;#34;&lt;/span>) (&lt;span style="color:#e6db74">&amp;#34;rb&amp;#34;&lt;/span> &lt;span style="color:#f92672">.&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>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#75715e">;; Like filled shapes for documents?&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>(&lt;span style="color:#e6db74">&amp;#34;md&amp;#34;&lt;/span> &lt;span style="color:#f92672">.&lt;/span> &lt;span style="color:#e6db74">&amp;#34;▪&amp;#34;&lt;/span>) (&lt;span style="color:#e6db74">&amp;#34;txt&amp;#34;&lt;/span> &lt;span style="color:#f92672">.&lt;/span> &lt;span style="color:#e6db74">&amp;#34;▪&amp;#34;&lt;/span>) (&lt;span style="color:#e6db74">&amp;#34;pdf&amp;#34;&lt;/span> &lt;span style="color:#f92672">.&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>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#75715e">;; Add your own file types&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>(&lt;span style="color:#e6db74">&amp;#34;log&amp;#34;&lt;/span> &lt;span style="color:#f92672">.&lt;/span> &lt;span style="color:#e6db74">&amp;#34;□&amp;#34;&lt;/span>) (&lt;span style="color:#e6db74">&amp;#34;cfg&amp;#34;&lt;/span> &lt;span style="color:#f92672">.&lt;/span> &lt;span style="color:#e6db74">&amp;#34;○&amp;#34;&lt;/span>) (&lt;span style="color:#e6db74">&amp;#34;bak&amp;#34;&lt;/span> &lt;span style="color:#f92672">.&lt;/span> &lt;span style="color:#e6db74">&amp;#34;▫&amp;#34;&lt;/span>)
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>Simply copy the code into your &lt;code>init.el&lt;/code> and off you go!&lt;/p></description></item><item><title>Building Your Own Orderless Style Completion in Emacs Lisp</title><link>https://www.emacs.dyerdwelling.family/emacs/20250604085817-emacs--building-your-own-orderless-style-completion-in-emacs-lisp/</link><pubDate>Wed, 04 Jun 2025 09:40:00 +0100</pubDate><author>James Dyer</author><guid>https://www.emacs.dyerdwelling.family/emacs/20250604085817-emacs--building-your-own-orderless-style-completion-in-emacs-lisp/</guid><description>&lt;p>While packages like &lt;code>orderless&lt;/code> provide flexible “any word, any order” completion, sometimes you want something lightweight and easy to tweak (well I do anyway). In this post, I’ll show you how to implement a simple orderless-like completion style using only Emacs Lisp, and how to integrate it smoothly into your workflow.&lt;/p>
&lt;figure>&lt;img src="https://www.emacs.dyerdwelling.family/emacs/20250604085817-emacs--Building-Your-Own-Orderless-Style-Completion-in-Emacs-Lisp.jpg" width="100%">
&lt;/figure>
&lt;p>Traditional completion in Emacs often matches prefixes or substrings, but sometimes you want to type a few key parts of a word, in any order, and jump straight to your target. That’s what &lt;code>orderless&lt;/code> and similar completion styles allow. But what if you want to write your own, or experiment with the logic?, well I will show you how&amp;hellip;&lt;/p>
&lt;p>Let’s walk through the logic:&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 simple-orderless-completion (&lt;span style="color:#a6e22e">string&lt;/span> table pred &lt;span style="color:#a6e22e">point&lt;/span>)
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#e6db74">&amp;#34;Enhanced orderless completion with better partial matching.&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> (let* ((words (split-string &lt;span style="color:#a6e22e">string&lt;/span> &lt;span style="color:#e6db74">&amp;#34;[-, ]+&amp;#34;&lt;/span>))
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> (patterns (&lt;span style="color:#a6e22e">mapcar&lt;/span> (lambda (word)
&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;\\b.*&amp;#34;&lt;/span> (&lt;span style="color:#a6e22e">regexp-quote&lt;/span> word) &lt;span style="color:#e6db74">&amp;#34;.*&amp;#34;&lt;/span>))
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> words))
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> (full-regexp (&lt;span style="color:#a6e22e">mapconcat&lt;/span> &lt;span style="color:#e6db74">&amp;#39;identity&lt;/span> patterns &lt;span style="color:#e6db74">&amp;#34;&amp;#34;&lt;/span>)))
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> (if (string-empty-p &lt;span style="color:#a6e22e">string&lt;/span>)
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> (&lt;span style="color:#a6e22e">all-completions&lt;/span> &lt;span style="color:#e6db74">&amp;#34;&amp;#34;&lt;/span> table pred)
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> (cl-remove-if-not
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> (lambda (candidate)
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> (let ((case-fold-search completion-ignore-case))
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> (and (cl-every (lambda (word)
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> (string-match-p
&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;\\b.*&amp;#34;&lt;/span> (&lt;span style="color:#a6e22e">regexp-quote&lt;/span> word))
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> candidate))
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> words)
&lt;/span>&lt;/span>&lt;span style="display:flex;">&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">all-completions&lt;/span> &lt;span style="color:#e6db74">&amp;#34;&amp;#34;&lt;/span> table pred)))))
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;h2 id="what-s-happening-here">&lt;strong>What’s Happening Here?&lt;/strong>&lt;/h2>
&lt;ul>
&lt;li>
&lt;p>&lt;strong>Word Splitting:&lt;/strong>&lt;/p>
&lt;p>The user&amp;rsquo;s input (a string) is split into words on spaces, dashes, or commas. This produces a list of &amp;ldquo;search terms.&amp;rdquo; This means that, in the minibuffer, the word separator can be any of these characters. I was initially really faffing around and struggling to work out how to insert a space between words in the minibuffer, as it seems to perform some form of completion. However, I eventually figured out that &lt;code>M-SPC&lt;/code> actually inserts a space, allowing you to separate words. I use &lt;code>fido-mode&lt;/code>, so I&amp;rsquo;m not sure if this is the same for other minibuffer completion systems.&lt;/p>
&lt;p>After initially adding in the comma separator however I found that I actually prefer it, it is easier to access and I don&amp;rsquo;t think any keywords, functions e.t.c will typically contain a comma?&lt;/p>
&lt;/li>
&lt;li>
&lt;p>&lt;strong>Pattern Construction:&lt;/strong>&lt;/p>
&lt;p>For each word, a regex pattern is constructed: &lt;code>\\b.*WORD.*&lt;/code>.
This means: “find a word boundary, followed by any characters, then the word, then anything else.” This is a bit looser than strict word matching, and you can tune it.&lt;/p>
&lt;/li>
&lt;li>
&lt;p>&lt;strong>Candidate Filtering:&lt;/strong>&lt;/p>
&lt;p>We generate all possible completions with &lt;code>all-completions&lt;/code> and then filter them down. For a candidate to match, all the search terms (words) must appear somewhere, in any order.&lt;/p>
&lt;/li>
&lt;li>
&lt;p>&lt;strong>Case Sensitivity:&lt;/strong>&lt;/p>
&lt;p>Matching respects &lt;code>completion-ignore-case&lt;/code>, so your results will be case-insensitive if you want of course.&lt;/p>
&lt;/li>
&lt;/ul>
&lt;h2 id="registering-and-using-the-style">&lt;strong>Registering and Using the Style&lt;/strong>&lt;/h2>
&lt;p>To make Emacs aware of your new completion style, add it to &lt;code>completion-styles-alist&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>(add-to-list &lt;span style="color:#e6db74">&amp;#39;completion-styles-alist&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#f92672">&amp;#39;&lt;/span>(simple-orderless simple-orderless-completion
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> simple-orderless-completion))
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;h2 id="contextual-use-minibuffer-only">&lt;strong>Contextual Use: Minibuffer Only&lt;/strong>&lt;/h2>
&lt;p>You might not want this style everywhere (which I suspect is likely). For example, in file completion you might prefer strict prefix matching. So, let’s activate it only in the minibuffer:&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 setup-minibuffer-completion-styles ()
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#e6db74">&amp;#34;Use orderless completion in minibuffer, regular completion elsewhere.&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#75715e">;; For minibuffer: use orderless first, then fallback to flex and basic&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> (setq-local completion-styles &lt;span style="color:#f92672">&amp;#39;&lt;/span>(basic simple-orderless flex &lt;span style="color:#a6e22e">substring&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">;; Hook into minibuffer setup&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>(add-hook &lt;span style="color:#e6db74">&amp;#39;minibuffer-setup-hook&lt;/span> &lt;span style="color:#a6e22e">#&amp;#39;&lt;/span>setup-minibuffer-completion-styles)
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;h2 id="tweaking-and-extending">&lt;strong>Tweaking and Extending&lt;/strong>&lt;/h2>
&lt;ul>
&lt;li>
&lt;p>&lt;strong>Pattern Tuning:&lt;/strong>&lt;/p>
&lt;p>The regexes can be made stricter or looser (e.g., remove &lt;code>\\b&lt;/code> for more “fuzzy” matching).&lt;/p>
&lt;/li>
&lt;li>
&lt;p>&lt;strong>Word Separators:&lt;/strong>&lt;/p>
&lt;p>You can split on other characters if your workflow uses different delimiters.&lt;/p>
&lt;/li>
&lt;li>
&lt;p>&lt;strong>Order of Styles:&lt;/strong>&lt;/p>
&lt;p>Adjust the order in &lt;code>completion-styles&lt;/code> to prefer your custom style over others. I found that if the &lt;code>simple-orderless&lt;/code> style was listed first, pressing Tab to bring up the completions buffer doesn&amp;rsquo;t work, which I like to use sometimes, so that is why &lt;code>basic&lt;/code> is first.&lt;/p>
&lt;/li>
&lt;/ul>
&lt;h3 id="conclusion">&lt;strong>Conclusion&lt;/strong>&lt;/h3>
&lt;p>With just a handful of lines, you can build your own orderless-like completion style, giving you full control and transparency. This is a great starting point for experimenting with more advanced completion logic, and a good illustration of the power of Emacs’ built-in completion framework!&lt;/p></description></item><item><title>Bank Buddy - Your Financial Analysis Companion for Emacs!</title><link>https://www.emacs.dyerdwelling.family/emacs/20250528081648-emacs--your-financial-analysis-companion-for-emacs/</link><pubDate>Wed, 28 May 2025 08:50:00 +0100</pubDate><author>James Dyer</author><guid>https://www.emacs.dyerdwelling.family/emacs/20250528081648-emacs--your-financial-analysis-companion-for-emacs/</guid><description>&lt;p>I created a new package!, here are the details:&lt;/p>
&lt;p>Bank Buddy is an Emacs package that provides financial analysis and reporting capabilities for your bank statements. It processes CSV bank statement data, categorizes transactions using customizable patterns, and generates detailed reports in Org-mode format.&lt;/p>
&lt;figure>&lt;img src="https://www.emacs.dyerdwelling.family/emacs/20250528081648-emacs--Your-Financial-Analysis-Companion-for-Emacs.jpg" width="100%">
&lt;/figure>
&lt;p>It does not depend on any external account system, and the analysis is handled by &lt;code>elisp&lt;/code>. The only external tool that may be required is &lt;code>gnuplot&lt;/code> to visualize the generated data.&lt;/p>
&lt;p>Here is an example of the type of report that is generated (obviously using test data :))&lt;/p>
&lt;p>&lt;a href="https://github.com/captainflasmr/bank-buddy/blob/main/tests/bank-statement_report/bank-statement_report.org">https://github.com/captainflasmr/bank-buddy/blob/main/tests/bank-statement_report/bank-statement_report.org&lt;/a>&lt;/p>
&lt;p>See &lt;a href="https://github.com/captainflasmr/bank-buddy/blob/main/docs/bank-buddy.org">https://github.com/captainflasmr/bank-buddy/blob/main/docs/bank-buddy.org&lt;/a> for the manual!&lt;/p>
&lt;h2 id="key-features">Key Features&lt;/h2>
&lt;ul>
&lt;li>&lt;strong>Smart Transaction Categorization&lt;/strong>: Auto-categorizes transactions based on customizable regex patterns&lt;/li>
&lt;li>&lt;strong>Financial Reports&lt;/strong>: Generates detailed reports in Org-mode with:
&lt;ul>
&lt;li>Transaction summaries and overviews&lt;/li>
&lt;li>Spending category analysis&lt;/li>
&lt;li>Top merchant identification&lt;/li>
&lt;li>Monthly spending patterns with visual representation&lt;/li>
&lt;li>Recurring subscription detection&lt;/li>
&lt;/ul>
&lt;/li>
&lt;li>&lt;strong>Interactive Category Management&lt;/strong>: Edit and refine categorization patterns directly from reports&lt;/li>
&lt;li>&lt;strong>Data Visualizations&lt;/strong>: Generates charts and graphs using external gnuplot scripts&lt;/li>
&lt;li>&lt;strong>No Reliance on External Accounting System&lt;/strong> - Analysis is all Emacs built-in&lt;/li>
&lt;li>&lt;strong>Asynchronous Processing&lt;/strong>: Efficiently handles large bank statements without blocking Emacs&lt;/li>
&lt;/ul>
&lt;h2 id="screenshots">Screenshots&lt;/h2>
&lt;h3 id="monthly-spending-categories">Monthly Spending categories&lt;/h3>
&lt;figure>&lt;img src="https://www.emacs.dyerdwelling.family/emacs/financial-report--monthly-spending-categories.png" width="100%">
&lt;/figure>
&lt;h3 id="monthly-spending-categories--stacked">Monthly Spending categories (stacked)&lt;/h3>
&lt;figure>&lt;img src="https://www.emacs.dyerdwelling.family/emacs/financial-report--monthly-spending-stacked.png" width="100%">
&lt;/figure>
&lt;h2 id="quick-start">Quick Start&lt;/h2>
&lt;ol>
&lt;li>Export your bank statement as a CSV file&lt;/li>
&lt;li>Edit CSV using &lt;code>csv-mode&lt;/code> for all lines to the format DATE,DESCRIPTION,AMOUNT&lt;/li>
&lt;li>Open CSV file&lt;/li>
&lt;li>Run: &lt;code>M-x bank-buddy-generate&lt;/code>&lt;/li>
&lt;li>Open the generated report&lt;/li>
&lt;/ol>
&lt;h2 id="generate-gnuplots">Generate gnuplots&lt;/h2>
&lt;p>Since version 0.2.0, Bank Buddy generates external gnuplot scripts that are executed via &lt;code>call-process&lt;/code>. You need to have gnuplot installed on your system.&lt;/p>
&lt;p>To install gnuplot:&lt;/p>
&lt;ul>
&lt;li>Linux: &lt;code>sudo apt install gnuplot&lt;/code> (or equivalent for your distribution)&lt;/li>
&lt;li>macOS: &lt;code>brew install gnuplot&lt;/code>&lt;/li>
&lt;li>Windows: Download from &lt;a href="http://www.gnuplot.info/">http://www.gnuplot.info/&lt;/a>&lt;/li>
&lt;/ul>
&lt;p>The generated reports will include links to both the gnuplot script files (.gp) and data files (.dat) for each visualization. This allows for:&lt;/p>
&lt;ul>
&lt;li>Easy customization of plots by editing the gnuplot scripts&lt;/li>
&lt;li>Regeneration of plots without reprocessing the CSV data&lt;/li>
&lt;li>Better control over visualization settings&lt;/li>
&lt;/ul>
&lt;h2 id="usage-guide">Usage Guide&lt;/h2>
&lt;h3 id="understanding-csv-format">Understanding CSV Format&lt;/h3>
&lt;p>Bank Buddy expects CSV files with at least the following columns:&lt;/p>
&lt;ul>
&lt;li>Transaction date&lt;/li>
&lt;li>Transaction description&lt;/li>
&lt;li>Debit amount&lt;/li>
&lt;/ul>
&lt;p>Different banks format their CSV exports differently. You may need to preprocess your CSV to match this format, I would advise to use the package &lt;code>csv-mode&lt;/code>, open up a csv file and &lt;code>C-c C-k&lt;/code> you way to removed unwanted columns so all you have left are those described above.&lt;/p>
&lt;h3 id="example-csv">Example CSV&lt;/h3>
&lt;pre tabindex="0">&lt;code class="language-csv" data-lang="csv">Date,Description,Amount
2024-06-30,PAYPAL TRANSFER,28.50
2024-06-28,JUST-EAT TAKEAWAY,32.99
2024-06-25,ASDA GROCERIES,78.50
2024-06-22,AUDIBLE SUBSCRIPTION,7.99
2024-06-20,THREE MOBILE,25.00
2024-06-18,RIVER-ISLAND CLOTHES,85.99
2024-06-15,SPOTIFY PREMIUM,9.99
2024-06-12,RAILWAY TICKET,42.00
2024-06-10,AMAZON PURCHASE,55.25
2024-06-07,NETFLIX SUBSCRIPTION,13.99
2024-06-05,VIRGIN-MEDIA MONTHLY,65.50
2024-06-03,NOTEMACHINE WITHDRAWAL,100.00
2024-06-01,KATHERINE ALLOWANCE,200.00
2024-05-30,DENTIST APPOINTMENT,60.00
2024-05-28,SKY-BETTING RACES,25.00
2024-05-25,WAITROSE GROCERIES,115.45
2024-05-22,NOWTV SUBSCRIPTION,9.99
2024-05-20,PAYPAL TRANSFER,40.00
2024-05-18,THREE MOBILE,25.00
2024-05-15,UBER RIDE,18.25
2024-05-12,SAINSBURYS GROCERIES,105.75
2024-05-10,AMAZON PURCHASE,32.99
2024-05-07,NETFLIX SUBSCRIPTION,13.99
2024-05-05,VIRGIN-MEDIA MONTHLY,65.50
2024-05-02,KATHERINE ALLOWANCE,200.00
2024-04-30,DELIVEROO FOOD,25.50
2024-04-28,PRIME VIDEO RENTAL,4.99
2024-04-25,PETS AT HOME,45.00
2024-04-23,RAILWAY TICKET,18.50
2024-04-20,YOUTUBE PREMIUM,11.99
2024-04-18,IKEA FURNITURE,245.99
2024-04-15,TESCO GROCERIES,68.95
2024-04-12,STARBUCKS COFFEE,9.85
2024-04-10,AMAZON PURCHASE,78.50
2024-04-07,NETFLIX SUBSCRIPTION,13.99
2024-04-05,CLAUDE SUBSCRIPTION,20.00
2024-04-03,VIRGIN-MEDIA MONTHLY,65.50
2024-04-01,KATHERINE ALLOWANCE,200.00
2024-03-30,PAYPAL TRANSFER,45.00
2024-03-28,BET365 RACES,30.00
2024-03-25,UBER RIDE,15.75
2024-03-22,WITHDRAWAL ATM,50.00
2024-03-20,THREE MOBILE,25.00
2024-03-17,SAINSBURYS GROCERIES,95.25
2024-03-15,SPECSAVERS APPOINTMENT,25.00
2024-03-12,RAILWAY TICKET,22.50
2024-03-10,AMAZON PURCHASE,28.99
2024-03-07,NETFLIX SUBSCRIPTION,13.99
2024-03-05,NATWEST-BANK-REFERENCE RENT,750.00
2024-03-03,NEXT RETAIL-LTD,125.00
2024-03-01,VIRGIN-MEDIA MONTHLY,65.50
2024-02-28,YOUTUBE PREMIUM,11.99
2024-02-25,SKY SUBSCRIPTION,45.99
2024-02-23,ASDA GROCERIES,92.45
2024-02-20,DISNEY+ SUBSCRIPTION,7.99
2024-02-18,PAYPAL TRANSFER,35.99
2024-02-15,TESCO GROCERIES,75.40
2024-02-12,WATERSTONES BOOK,15.99
2024-02-10,AMAZON PURCHASE,65.75
2024-02-07,NETFLIX SUBSCRIPTION,13.99
2024-02-05,ROYAL-MAIL POSTAGE,8.95
2024-02-03,RAILWAY TICKET,35.45
2024-02-01,KATHERINE ALLOWANCE,200.00
2024-01-30,UBER RIDE,12.50
2024-01-28,STARBUCKS COFFEE,4.95
2024-01-25,THREE MOBILE,25.00
2024-01-20,AMAZON PURCHASE,45.30
2024-01-18,SAINSBURYS GROCERIES,88.75
2024-01-15,NETFLIX SUBSCRIPTION,13.99
2024-01-12,PAYPAL TRANSFER,22.45
2024-01-10,VIRGIN-MEDIA MONTHLY,65.50
2024-01-07,SKY-BETTING RACES,20.00
2024-01-05,PAYPAL TRANSFER,55.99
&lt;/code>&lt;/pre>&lt;h3 id="generating-a-report">Generating a Report&lt;/h3>
&lt;p>To generate a financial report &lt;code>M-x bank-buddy-generate-report&lt;/code>&lt;/p>
&lt;p>You&amp;rsquo;ll be prompted to select an input CSV file and specify the output Org file.&lt;/p>
&lt;p>The package processes the data asynchronously and a buffer will appear reporting on the analysis progress, so Emacs remains responsive even with large CSV files.&lt;/p>
&lt;p>When processing is complete, you&amp;rsquo;ll be asked if you want to open the generated report.&lt;/p>
&lt;h3 id="understanding-the-report">Understanding the Report&lt;/h3>
&lt;p>A typical Bank Buddy report includes:&lt;/p>
&lt;h4 id="summary-overview">Summary Overview&lt;/h4>
&lt;ul>
&lt;li>Total transactions analyzed&lt;/li>
&lt;li>Date range&lt;/li>
&lt;li>Total, average daily, and weekly spending&lt;/li>
&lt;/ul>
&lt;h4 id="top-spending-categories">Top Spending Categories&lt;/h4>
&lt;ul>
&lt;li>Ranked list of spending categories&lt;/li>
&lt;li>Total amount, percentage, and monthly/yearly averages&lt;/li>
&lt;li>Links to generated gnuplot scripts and data files&lt;/li>
&lt;/ul>
&lt;h4 id="monthly-spending-patterns">Monthly Spending Patterns&lt;/h4>
&lt;ul>
&lt;li>Month-by-month spending visualization&lt;/li>
&lt;li>Category breakdown for each month&lt;/li>
&lt;li>Highest and lowest spending months&lt;/li>
&lt;li>Links to visualization files (*.gp, *.dat)&lt;/li>
&lt;/ul>
&lt;h4 id="monthly-category-breakdowns">Monthly Category Breakdowns&lt;/h4>
&lt;ul>
&lt;li>Detailed charts for each month showing spending by category&lt;/li>
&lt;li>Consistent color coding across months for easy comparison&lt;/li>
&lt;li>Links to individual gnuplot scripts for customization&lt;/li>
&lt;/ul>
&lt;h4 id="top-merchants">Top Merchants&lt;/h4>
&lt;ul>
&lt;li>Your highest-spending merchants&lt;/li>
&lt;li>Total amount, percentage, and monthly/yearly averages&lt;/li>
&lt;li>Links to generated visualization files&lt;/li>
&lt;/ul>
&lt;h4 id="recurring-subscriptions">Recurring Subscriptions&lt;/h4>
&lt;ul>
&lt;li>Detected recurring payments&lt;/li>
&lt;li>Estimated monthly cost&lt;/li>
&lt;li>Frequency analysis (weekly, bi-weekly, monthly, annual)&lt;/li>
&lt;/ul>
&lt;h4 id="transaction-size-distribution">Transaction Size Distribution&lt;/h4>
&lt;ul>
&lt;li>Analysis of transaction sizes (under £10, £10-50, £50-100, over £100)&lt;/li>
&lt;/ul>
&lt;h4 id="unmatched-transactions">Unmatched Transactions&lt;/h4>
&lt;ul>
&lt;li>List of transactions that didn&amp;rsquo;t match specific categories&lt;/li>
&lt;li>Suggested patterns to add to your customization&lt;/li>
&lt;/ul>
&lt;h3 id="managing-transaction-categories">Managing Transaction Categories&lt;/h3>
&lt;p>Bank Buddy comes with predefined category patterns, but you&amp;rsquo;ll likely want to customize these for your personal transactions. The package includes an interactive mode for managing categories.&lt;/p>
&lt;p>When viewing a report, you can:&lt;/p>
&lt;ol>
&lt;li>Navigate to an unmatched transaction (in the &amp;ldquo;Unmatched Transactions&amp;rdquo; section)&lt;/li>
&lt;li>Press &lt;code>C-c C-a&lt;/code> to add it to a category&lt;/li>
&lt;li>Choose an existing category or create a new one&lt;/li>
&lt;li>Optionally save the updated category definitions to your init file&lt;/li>
&lt;li>Regenerate the report to see the changes&lt;/li>
&lt;/ol>
&lt;p>To manage existing categories or add new ones manually, customize &lt;code>bank-buddy-core-cat-list-defines&lt;/code>.&lt;/p>
&lt;h3 id="category-format">Category Format&lt;/h3>
&lt;p>Categories are defined as patterns in the form:&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>(REGEX-PATTERN CATEGORY-CODE)
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>Where:&lt;/p>
&lt;ul>
&lt;li>&lt;code>REGEX-PATTERN&lt;/code> is a regular expression that matches transaction descriptions&lt;/li>
&lt;li>&lt;code>CATEGORY-CODE&lt;/code> is a short code representing the category (e.g., &amp;ldquo;fod&amp;rdquo; for food)&lt;/li>
&lt;/ul>
&lt;p>For example:&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>(&lt;span style="color:#e6db74">&amp;#34;amazon\\|amz&amp;#34;&lt;/span> &lt;span style="color:#e6db74">&amp;#34;amz&amp;#34;&lt;/span>) &lt;span style="color:#75715e">; Amazon purchases&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>(&lt;span style="color:#e6db74">&amp;#34;netflix\\|spotify\\|youtube&amp;#34;&lt;/span> &lt;span style="color:#e6db74">&amp;#34;str&amp;#34;&lt;/span>) &lt;span style="color:#75715e">; Streaming services&lt;/span>
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>You can customize category codes and their display names by modifying &lt;code>bank-buddy-core-category-names&lt;/code>.&lt;/p>
&lt;h3 id="updating-and-regenerating-reports">Updating and Regenerating Reports&lt;/h3>
&lt;p>If you add or modify category patterns after generating a report:&lt;/p>
&lt;ol>
&lt;li>With the report open, enable Bank Buddy Category mode if not already active: &lt;code>M-x bank-buddy-cat-mode&lt;/code>&lt;/li>
&lt;li>Press &lt;code>C-c C-r&lt;/code> to regenerate the report with the updated categories&lt;/li>
&lt;/ol>
&lt;p>The report will be refreshed with the new categorization rules.&lt;/p>
&lt;h2 id="example-workflow">Example Workflow&lt;/h2>
&lt;ul>
&lt;li>
&lt;p>&lt;strong>Generate Initial Report&lt;/strong> &lt;code>M-x bank-buddy-generate-report&lt;/code>&lt;/p>
&lt;/li>
&lt;li>
&lt;p>&lt;strong>Review Unmatched Transactions&lt;/strong>
Navigate to the &amp;ldquo;Unmatched Transactions&amp;rdquo; section of the report.&lt;/p>
&lt;/li>
&lt;li>
&lt;p>&lt;strong>Categorize Transactions&lt;/strong>&lt;/p>
&lt;ul>
&lt;li>Place cursor on an unmatched transaction&lt;/li>
&lt;li>&lt;code>C-c C-a&lt;/code> to add it to a category&lt;/li>
&lt;li>Choose an existing category or create a new one&lt;/li>
&lt;/ul>
&lt;/li>
&lt;li>
&lt;p>&lt;strong>Regenerate Report&lt;/strong>&lt;/p>
&lt;ul>
&lt;li>&lt;code>C-c C-r&lt;/code> to see your changes&lt;/li>
&lt;/ul>
&lt;/li>
&lt;li>
&lt;p>&lt;strong>Save Category Definitions&lt;/strong>
When prompted, choose to save your category definitions to your init file.&lt;/p>
&lt;/li>
&lt;/ul>
&lt;h2 id="customization">Customization&lt;/h2>
&lt;h3 id="core-settings">Core Settings&lt;/h3>
&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>&lt;span style="color:#75715e">;; Exclude large transactions from analysis&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>(setq bank-buddy-core-exclude-large-txns &lt;span style="color:#66d9ef">t&lt;/span>)
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>(setq bank-buddy-core-large-txn-threshold &lt;span style="color:#ae81ff">2000&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">;; Number of occurrences to detect subscriptions&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>(setq bank-buddy-core-subscription-min-occurrences &lt;span style="color:#ae81ff">3&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">;; Number of top items to display&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>(setq bank-buddy-core-top-spending-categories &lt;span style="color:#ae81ff">5&lt;/span>)
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>(setq bank-buddy-core-top-merchants &lt;span style="color:#ae81ff">5&lt;/span>)
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;h3 id="customizing-category-patterns">Customizing Category Patterns&lt;/h3>
&lt;p>You can customize the category patterns by setting &lt;code>bank-buddy-core-cat-list-defines&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>(customize-set-variable &lt;span style="color:#e6db74">&amp;#39;bank-buddy-core-cat-list-defines&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#f92672">&amp;#39;&lt;/span>((&lt;span style="color:#e6db74">&amp;#34;amazon\\|amz&amp;#34;&lt;/span> &lt;span style="color:#e6db74">&amp;#34;amz&amp;#34;&lt;/span>)
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> (&lt;span style="color:#e6db74">&amp;#34;netflix\\|spotify&amp;#34;&lt;/span> &lt;span style="color:#e6db74">&amp;#34;str&amp;#34;&lt;/span>)
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> (&lt;span style="color:#e6db74">&amp;#34;uber\\|lyft&amp;#34;&lt;/span> &lt;span style="color:#e6db74">&amp;#34;txi&amp;#34;&lt;/span>)
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> (&lt;span style="color:#e6db74">&amp;#34;sainsburys\\|tesco\\|asda&amp;#34;&lt;/span> &lt;span style="color:#e6db74">&amp;#34;fod&amp;#34;&lt;/span>)
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#75715e">;; Add your own patterns here&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> (&lt;span style="color:#e6db74">&amp;#34;.*&amp;#34;&lt;/span> &lt;span style="color:#e6db74">&amp;#34;o&amp;#34;&lt;/span>))) &lt;span style="color:#75715e">; Catch-all pattern should be last&lt;/span>
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;h3 id="customizing-category-names">Customizing Category Names&lt;/h3>
&lt;p>Category codes are mapped to human-readable names via &lt;code>bank-buddy-core-category-names&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>(customize-set-variable &lt;span style="color:#e6db74">&amp;#39;bank-buddy-core-category-names&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#f92672">&amp;#39;&lt;/span>((&lt;span style="color:#e6db74">&amp;#34;amz&amp;#34;&lt;/span> &lt;span style="color:#f92672">.&lt;/span> &lt;span style="color:#e6db74">&amp;#34;Amazon&amp;#34;&lt;/span>)
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> (&lt;span style="color:#e6db74">&amp;#34;str&amp;#34;&lt;/span> &lt;span style="color:#f92672">.&lt;/span> &lt;span style="color:#e6db74">&amp;#34;Streaming Services&amp;#34;&lt;/span>)
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> (&lt;span style="color:#e6db74">&amp;#34;txi&amp;#34;&lt;/span> &lt;span style="color:#f92672">.&lt;/span> &lt;span style="color:#e6db74">&amp;#34;Taxi &amp;amp; Rideshare&amp;#34;&lt;/span>)
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> (&lt;span style="color:#e6db74">&amp;#34;fod&amp;#34;&lt;/span> &lt;span style="color:#f92672">.&lt;/span> &lt;span style="color:#e6db74">&amp;#34;Groceries&amp;#34;&lt;/span>)
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#75715e">;; Add your own mappings here&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> (&lt;span style="color:#e6db74">&amp;#34;o&amp;#34;&lt;/span> &lt;span style="color:#f92672">.&lt;/span> &lt;span style="color:#e6db74">&amp;#34;Other&amp;#34;&lt;/span>)))
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;h3 id="customizing-subscription-detection">Customizing Subscription Detection&lt;/h3>
&lt;p>Define subscription patterns for better detection of recurring payments:&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>(customize-set-variable &lt;span style="color:#e6db74">&amp;#39;bank-buddy-core-subscription-patterns&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#f92672">&amp;#39;&lt;/span>((&lt;span style="color:#e6db74">&amp;#34;NETFLIX&amp;#34;&lt;/span> &lt;span style="color:#f92672">.&lt;/span> &lt;span style="color:#e6db74">&amp;#34;Netflix&amp;#34;&lt;/span>)
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> (&lt;span style="color:#e6db74">&amp;#34;SPOTIFY&amp;#34;&lt;/span> &lt;span style="color:#f92672">.&lt;/span> &lt;span style="color:#e6db74">&amp;#34;Spotify&amp;#34;&lt;/span>)
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> (&lt;span style="color:#e6db74">&amp;#34;AMAZON PRIME&amp;#34;&lt;/span> &lt;span style="color:#f92672">.&lt;/span> &lt;span style="color:#e6db74">&amp;#34;Amazon Prime&amp;#34;&lt;/span>)
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#75715e">;; Add your own patterns here&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> ))
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;h2 id="advanced-usage">Advanced Usage&lt;/h2>
&lt;h3 id="integration-with-other-financial-tools">Integration with Other Financial Tools&lt;/h3>
&lt;p>Bank Buddy reports are generated as Org-mode files, making them compatible with other Org-based tools:&lt;/p>
&lt;ul>
&lt;li>Export to HTML, PDF, or other formats with Org export functions&lt;/li>
&lt;li>Use &lt;code>org-capture&lt;/code> to add notes to specific transactions or categories&lt;/li>
&lt;/ul>
&lt;h3 id="custom-visualization">Custom Visualization&lt;/h3>
&lt;p>Bank Buddy generates visualizations using external gnuplot scripts. You can customize these by:&lt;/p>
&lt;ol>
&lt;li>Editing the generated .gp files in the report directory&lt;/li>
&lt;li>Creating your own gnuplot scripts based on the generated .dat files&lt;/li>
&lt;li>Running the scripts manually with &lt;code>gnuplot filename.gp&lt;/code>&lt;/li>
&lt;/ol>
&lt;h3 id="keyboard-shortcuts">Keyboard Shortcuts&lt;/h3>
&lt;p>When viewing a report with &lt;code>bank-buddy-cat-mode&lt;/code> enabled:&lt;/p>
&lt;ul>
&lt;li>&lt;code>C-c C-a&lt;/code>: Add the transaction at point to a category&lt;/li>
&lt;li>&lt;code>C-c C-r&lt;/code>: Regenerate the report with current category definitions&lt;/li>
&lt;/ul>
&lt;h2 id="caveats-and-tips">Caveats and Tips&lt;/h2>
&lt;ul>
&lt;li>&lt;strong>CSV Format&lt;/strong>: Bank Buddy expects a CSV with date, description, and amount columns&lt;/li>
&lt;li>&lt;strong>Performance&lt;/strong>: For very large CSV files (10K+ rows), the async processing helps but may still take time&lt;/li>
&lt;li>&lt;strong>Categorization&lt;/strong>: Start with broad patterns and refine as needed&lt;/li>
&lt;li>&lt;strong>Visualization&lt;/strong>: Ensure gnuplot is installed on your system for chart generation&lt;/li>
&lt;li>&lt;strong>Saving Patterns&lt;/strong>: Always save your category patterns to persist between sessions&lt;/li>
&lt;/ul>
&lt;h2 id="comparison-with-other-financial-packages">Comparison with Other Financial Packages&lt;/h2>
&lt;p>Several Emacs packages exist for financial management, but they serve different purposes. Here&amp;rsquo;s how Bank Buddy compares to other notable financial packages:&lt;/p>
&lt;h3 id="ledger-mode">Ledger-mode&lt;/h3>
&lt;p>&lt;a href="https://github.com/ledger/ledger-mode">Ledger-mode&lt;/a> is an Emacs interface to the command-line Ledger accounting system.&lt;/p>
&lt;p>&lt;strong>Key differences:&lt;/strong>&lt;/p>
&lt;ul>
&lt;li>Ledger is a complete double-entry accounting system; Bank Buddy is focused on bank statement analysis&lt;/li>
&lt;li>Ledger requires manual transaction entry or carefully formatted imports; Bank Buddy automates categorization&lt;/li>
&lt;li>Ledger offers more comprehensive accounting features (accounts, assets, liabilities); Bank Buddy focuses on spending insights&lt;/li>
&lt;li>Bank Buddy provides visual spending breakdowns and charts; Ledger focuses on accurate accounting&lt;/li>
&lt;/ul>
&lt;p>&lt;strong>When to use Ledger:&lt;/strong> For complete personal finance tracking, investments, budgeting, and double-entry accounting.
&lt;strong>When to use Bank Buddy:&lt;/strong> For quick analysis of bank statements and visualizing spending patterns.&lt;/p>
&lt;h3 id="hledger-mode">HLedger-mode&lt;/h3>
&lt;p>&lt;a href="https://github.com/narendraj9/hledger-mode">HLedger-mode&lt;/a> is an Emacs major mode for working with hledger, a plain-text accounting system similar to Ledger.&lt;/p>
&lt;p>&lt;strong>Key differences:&lt;/strong>&lt;/p>
&lt;ul>
&lt;li>HLedger, like Ledger, is a full double-entry accounting system; Bank Buddy focuses on bank statement analysis&lt;/li>
&lt;li>HLedger requires manual transaction entry or formatted imports; Bank Buddy automates categorization&lt;/li>
&lt;li>HLedger offers comprehensive accounting features (multiple currencies, time reporting); Bank Buddy emphasizes spending insights&lt;/li>
&lt;li>Bank Buddy provides visual spending breakdowns; HLedger focuses on textual reports and balances&lt;/li>
&lt;/ul>
&lt;p>&lt;strong>When to use HLedger-mode:&lt;/strong> For detailed personal finance tracking, multi-currency support, and generating various financial reports.
&lt;strong>When to use Bank Buddy:&lt;/strong> For quick analysis of bank statements and visualizing spending patterns without learning a full accounting system.&lt;/p>
&lt;h3 id="elbank">Elbank&lt;/h3>
&lt;p>&lt;a href="https://github.com/NicolasPetton/Elbank">Elbank&lt;/a> is a personal finance reporting tool for Emacs that uses Weboob to fetch data from bank websites.&lt;/p>
&lt;p>&lt;strong>Key differences:&lt;/strong>&lt;/p>
&lt;ul>
&lt;li>Elbank can automatically fetch transactions from supported banks; Bank Buddy works with CSV exports&lt;/li>
&lt;li>Elbank focuses on reporting and visualization; Bank Buddy offers both analysis and reporting&lt;/li>
&lt;li>Elbank requires Weboob setup and configuration; Bank Buddy works directly with CSV files&lt;/li>
&lt;li>Bank Buddy provides customizable transaction categorization; Elbank may rely on bank-provided categories&lt;/li>
&lt;/ul>
&lt;p>&lt;strong>When to use Elbank:&lt;/strong> For automated tracking of multiple bank accounts with direct data fetching and basic reporting.
&lt;strong>When to use Bank Buddy:&lt;/strong> For detailed analysis and categorization of bank statements, especially when working with CSV exports or when bank integration isn&amp;rsquo;t available or desired.&lt;/p>
&lt;h3 id="beancount-mode">Beancount-mode&lt;/h3>
&lt;p>&lt;a href="https://github.com/beancount/beancount-mode">Beancount-mode&lt;/a> is an Emacs mode for Beancount, another plain-text accounting system.&lt;/p>
&lt;p>&lt;strong>Key differences:&lt;/strong>&lt;/p>
&lt;ul>
&lt;li>Beancount, like Ledger, is a full double-entry accounting system&lt;/li>
&lt;li>Beancount has stricter syntax requirements than Ledger&lt;/li>
&lt;li>Bank Buddy offers automatic categorization and reporting, while Beancount requires manual entry&lt;/li>
&lt;li>Beancount generates sophisticated reports, but requires more setup and knowledge&lt;/li>
&lt;/ul>
&lt;p>&lt;strong>When to use Beancount:&lt;/strong> For precise, auditable personal accounting with strict validation.
&lt;strong>When to use Bank Buddy:&lt;/strong> For simple spending analysis without learning accounting principles.&lt;/p>
&lt;h3 id="csv-mode-and-orgtbl-mode">csv-mode and orgtbl-mode&lt;/h3>
&lt;p>Some users analyze financial CSV data using built-in Emacs packages like csv-mode combined with org-table functionality.&lt;/p>
&lt;p>&lt;strong>Key differences:&lt;/strong>&lt;/p>
&lt;ul>
&lt;li>These are general-purpose tools that require manual customization for financial analysis&lt;/li>
&lt;li>Bank Buddy provides specialized, financial-specific analysis and visualization&lt;/li>
&lt;li>Bank Buddy automatically categorizes transactions based on patterns&lt;/li>
&lt;li>Bank Buddy generates reports without manual processing&lt;/li>
&lt;/ul>
&lt;p>&lt;strong>When to use csv/orgtbl-mode:&lt;/strong> For custom, one-off analysis of financial data.
&lt;strong>When to use Bank Buddy:&lt;/strong> For consistent, repeatable analysis of bank statements.&lt;/p>
&lt;h2 id="roadmap">Roadmap&lt;/h2>
&lt;table>
&lt;thead>
&lt;tr>
&lt;th>TODO&lt;/th>
&lt;th>DOING&lt;/th>
&lt;th>DONE&lt;/th>
&lt;/tr>
&lt;/thead>
&lt;tbody>
&lt;tr>
&lt;td>Add Paypal break down csv&lt;/td>
&lt;td>Highlight bank lines not matched for iterative tweaks&lt;/td>
&lt;td>Asynchronous operation&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>Add large sum outlays&lt;/td>
&lt;td>Generate test data and unit test&lt;/td>
&lt;td>&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>Budget Tracking&lt;/td>
&lt;td>Data Visualization&lt;/td>
&lt;td>&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>AI-Powered Categorization&lt;/td>
&lt;td>Better gnuplot autogeneration of plots&lt;/td>
&lt;td>&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>&lt;/td>
&lt;td>Custom Category Mapping&lt;/td>
&lt;td>&lt;/td>
&lt;/tr>
&lt;/tbody>
&lt;/table></description></item><item><title>Ollama Buddy 0.12.0: User Prompt Library, File Attachments, Vision and Context Tracking</title><link>https://www.emacs.dyerdwelling.family/emacs/20250523135757-emacs--ollama-buddy-0-12-0-user-prompt-library-file-attachments-vision-context-tracking/</link><pubDate>Fri, 23 May 2025 14:10:00 +0100</pubDate><author>James Dyer</author><guid>https://www.emacs.dyerdwelling.family/emacs/20250523135757-emacs--ollama-buddy-0-12-0-user-prompt-library-file-attachments-vision-context-tracking/</guid><description>&lt;p>There have been quite a few updates recently. The main highlights include support for attachments, so you can push a file to the chat directly from &lt;code>dired&lt;/code> for potential inclusion in your next query.&lt;/p>
&lt;p>Vision support has been added for models that can handle it. If you supply the path to an image file in the chat, it will be processed. This means you can now, for example, extract text from images using models like &lt;code>o:gemma3:4b&lt;/code>.&lt;/p>
&lt;p>I&amp;rsquo;ve also introduced the ability to save user system prompts. If you have a favorite prompt, or have crafted one that works especially well for you, you can now save it by category and title in a simple Org format for later recall. Prompt recall now works the same way as Fabric patterns and Awesome ChatGPT prompts. This makes it much easier to display the currently used system prompt concisely in the status bar, as it will be based on the prompt title (and thus likely the role).&lt;/p>
&lt;p>What else? Oh yes, I received a request for better context tracking. Now, when context is nearing full capacity, or has exceeded it, it will be indicated in the status bar!&lt;/p>
&lt;p>That’s probably it for the major changes. There was also some refactoring, but you probably don&amp;rsquo;t care about that. Anyway, here is the full list of changes:&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="0-dot-12-dot-0">&lt;span class="timestamp-wrapper">&lt;span class="timestamp">&amp;lt;2025-05-22 Thu&amp;gt; &lt;/span>&lt;/span> &lt;strong>0.12.0&lt;/strong>&lt;/h2>
&lt;p>Full system prompt in the status bar replaced with a more meaningful simple role title&lt;/p>
&lt;ul>
&lt;li>Added system prompt metadata tracking with title, source, and timestamp registry&lt;/li>
&lt;li>Implemented automatic title extraction and unified completing-read interface&lt;/li>
&lt;li>Enhanced fabric/awesome prompt integration with proper metadata handling&lt;/li>
&lt;li>Improved transient menu organization and org-mode formatting with folding&lt;/li>
&lt;li>Added system prompt history display and better error handling for empty files&lt;/li>
&lt;li>Transient menu has been simplified and reorganised&lt;/li>
&lt;/ul>
&lt;p>Previously, the header status bar would show truncated system prompt text like &lt;code>[You are a helpful assistant wh...]&lt;/code>, making it difficult to quickly identify which prompt was active. Now, the display shows meaningful role titles with source indicators:&lt;/p>
&lt;ul>
&lt;li>&lt;code>[F:Code Reviewer]&lt;/code> - Fabric pattern&lt;/li>
&lt;li>&lt;code>[A:Linux Terminal]&lt;/code> - Awesome ChatGPT prompt&lt;/li>
&lt;li>&lt;code>[U:Writing Assistant]&lt;/code> - User-defined prompt&lt;/li>
&lt;/ul>
&lt;p>The system now intelligently extracts titles from prompt content by recognizing common patterns like &amp;ldquo;You are a&amp;hellip;&amp;rdquo;, &amp;ldquo;Act as&amp;hellip;&amp;rdquo;, or &amp;ldquo;I want you to act as&amp;hellip;&amp;rdquo;. When these patterns aren&amp;rsquo;t found, it generates a concise title from the first few words.&lt;/p>
&lt;p>Behind the scenes, Ollama Buddy now maintains a registry of all system prompts with their titles, sources, and timestamps. This enables new features like system prompt history viewing and better organization across Fabric patterns, Awesome ChatGPT prompts, and user-defined prompts.&lt;/p>
&lt;p>The result is a cleaner interface that makes it immediately clear which role your AI assistant is currently embodying, without cluttering the status bar with long, truncated text.&lt;/p>
&lt;h2 id="0-dot-11-dot-1">&lt;span class="timestamp-wrapper">&lt;span class="timestamp">&amp;lt;2025-05-21 Wed&amp;gt; &lt;/span>&lt;/span> &lt;strong>0.11.1&lt;/strong>&lt;/h2>
&lt;p>Quite a bit of refactoring to generally make this project more maintainable and I have added a starter kit of user prompts.&lt;/p>
&lt;ul>
&lt;li>
&lt;p>Color System Reworking&lt;/p>
&lt;ul>
&lt;li>Removed all model color-related functions and variables&lt;/li>
&lt;li>Removed dependency on &lt;code>color.el&lt;/code>&lt;/li>
&lt;li>Replaced with &lt;code>highlight-regexp&lt;/code> and hashing to &lt;code>^font-lock&lt;/code> faces, so now using a more native built-in solutions for model colouring rather than shoe-horning in overlays.&lt;/li>
&lt;/ul>
&lt;/li>
&lt;li>
&lt;p>UI Improvements&lt;/p>
&lt;ul>
&lt;li>Simplified the display system by leveraging Org mode&lt;/li>
&lt;li>Added org-mode styling for output buffers&lt;/li>
&lt;li>Added &lt;code>org-hide-emphasis-markers&lt;/code> and &lt;code>org-hide-leading-stars&lt;/code> settings&lt;/li>
&lt;li>Changed formatting to use Org markup instead of text properties&lt;/li>
&lt;li>Converted plain text headers to proper Org headings&lt;/li>
&lt;li>Replaced color properties with Org emphasis (bold)&lt;/li>
&lt;/ul>
&lt;/li>
&lt;li>
&lt;p>History Management Updates&lt;/p>
&lt;ul>
&lt;li>Streamlined history editing functionality&lt;/li>
&lt;li>Improved model-specific history editing&lt;/li>
&lt;li>Refactored history display and navigation&lt;/li>
&lt;/ul>
&lt;/li>
&lt;li>
&lt;p>System Prompts&lt;/p>
&lt;ul>
&lt;li>Added library of system prompts in these categories:
&lt;ul>
&lt;li>analysis (3 prompts)&lt;/li>
&lt;li>coding (5 prompts)&lt;/li>
&lt;li>creative (3 prompts)&lt;/li>
&lt;li>documentation (3 prompts)&lt;/li>
&lt;li>emacs (10 prompts)&lt;/li>
&lt;li>general (3 prompts)&lt;/li>
&lt;li>technical (3 prompts)&lt;/li>
&lt;li>writing (3 prompts)&lt;/li>
&lt;/ul>
&lt;/li>
&lt;/ul>
&lt;/li>
&lt;/ul>
&lt;h2 id="0-dot-11-dot-0">&lt;span class="timestamp-wrapper">&lt;span class="timestamp">&amp;lt;2025-05-19 Mon&amp;gt; &lt;/span>&lt;/span> &lt;strong>0.11.0&lt;/strong>&lt;/h2>
&lt;p>Added user system prompts management&lt;/p>
&lt;ul>
&lt;li>You can now save, load and manage system prompts&lt;/li>
&lt;li>Created new transient menu for user system prompts (C-c s)&lt;/li>
&lt;li>Organized prompts by categories with org-mode format storage&lt;/li>
&lt;li>Supported prompt editing, listing, creation and deletion&lt;/li>
&lt;li>Updated key bindings to integrate with existing functionality&lt;/li>
&lt;li>Added prompts directory customization with defaults&lt;/li>
&lt;/ul>
&lt;p>This feature makes it easier to save, organize, and reuse your favorite system prompts when working with Ollama language models.&lt;/p>
&lt;p>System prompts are special instructions that guide the behavior of language models. By setting effective system prompts, you can:&lt;/p>
&lt;ul>
&lt;li>Define the AI&amp;rsquo;s role (e.g., &amp;ldquo;You are a helpful programming assistant who explains code clearly&amp;rdquo;)&lt;/li>
&lt;li>Establish response formats&lt;/li>
&lt;li>Set the tone and style of responses&lt;/li>
&lt;li>Provide background knowledge for specific domains&lt;/li>
&lt;/ul>
&lt;p>The new &lt;code>ollama-buddy-user-prompts&lt;/code> module organizes your system prompts in a clean, category-based system:&lt;/p>
&lt;ul>
&lt;li>&lt;strong>Save your prompts&lt;/strong> - Store effective system prompts you&amp;rsquo;ve crafted for future use&lt;/li>
&lt;li>&lt;strong>Categorize&lt;/strong> - Prompts are organized by domains like &amp;ldquo;coding,&amp;rdquo; &amp;ldquo;writing,&amp;rdquo; &amp;ldquo;technical,&amp;rdquo; etc.&lt;/li>
&lt;li>&lt;strong>Quick access&lt;/strong> - Browse and load your prompt library with completion-based selection&lt;/li>
&lt;li>&lt;strong>Edit in org-mode&lt;/strong> - All prompts are stored as org files with proper metadata&lt;/li>
&lt;li>&lt;strong>Manage with ease&lt;/strong> - Create, edit, list, and delete prompts through a dedicated transient menu&lt;/li>
&lt;/ul>
&lt;p>The new functionality is accessible through the updated key binding &lt;code>C-c s&lt;/code>, which opens a dedicated transient menu with these options:&lt;/p>
&lt;ul>
&lt;li>&lt;strong>Save current (S)&lt;/strong> - Save your active system prompt&lt;/li>
&lt;li>&lt;strong>Load prompt (L)&lt;/strong> - Choose a previously saved prompt&lt;/li>
&lt;li>&lt;strong>Create new (N)&lt;/strong> - Start fresh with a new prompt&lt;/li>
&lt;li>&lt;strong>List all Prompts (l)&lt;/strong> - View your entire prompt library&lt;/li>
&lt;li>&lt;strong>Edit prompt (e)&lt;/strong> - Modify an existing prompt&lt;/li>
&lt;li>&lt;strong>Delete prompt (d)&lt;/strong> - Remove prompts you no longer need&lt;/li>
&lt;/ul>
&lt;p>If you work frequently with Ollama models, you&amp;rsquo;ve likely discovered the power of well-crafted system prompts. They can dramatically improve the quality and consistency of responses. With this new management system, you can:&lt;/p>
&lt;ul>
&lt;li>Build a personal library of effective prompts&lt;/li>
&lt;li>Maintain context continuity across sessions&lt;/li>
&lt;li>Share prompts with teammates&lt;/li>
&lt;li>Refine your prompts over time&lt;/li>
&lt;/ul>
&lt;h2 id="0-dot-10-dot-0">&lt;span class="timestamp-wrapper">&lt;span class="timestamp">&amp;lt;2025-05-14 Wed&amp;gt; &lt;/span>&lt;/span> &lt;strong>0.10.0&lt;/strong>&lt;/h2>
&lt;p>Added file attachment system for including documents in conversations&lt;/p>
&lt;ul>
&lt;li>Added file attachment support with configurable file size limits (10MB default) and supported file types&lt;/li>
&lt;li>Implemented session persistence for attachments in save/load functionality&lt;/li>
&lt;li>Added attachment context inclusion in prompts with proper token counting&lt;/li>
&lt;li>Created comprehensive attachment management commands:
&lt;ul>
&lt;li>Attach files to conversations&lt;/li>
&lt;li>Show current attachments in dedicated buffer&lt;/li>
&lt;li>Detach specific files&lt;/li>
&lt;li>Clear all attachments&lt;/li>
&lt;/ul>
&lt;/li>
&lt;li>Added Dired integration for bulk file attachment&lt;/li>
&lt;li>Included attachment menu in transient interface (C-c 1)&lt;/li>
&lt;li>Updated help text to document new attachment keybindings&lt;/li>
&lt;li>Enhanced context calculation to include attachment token usage&lt;/li>
&lt;/ul>
&lt;p>You can now seamlessly include text files, code, documentation, and more directly in your conversations with local AI models!&lt;/p>
&lt;p>Simply use &lt;code>C-c C-a&lt;/code> from the chat buffer to attach any file to your current conversation.&lt;/p>
&lt;p>The attached files become part of your conversation context, allowing the AI to reference, analyze, or work with their contents directly.&lt;/p>
&lt;p>The transient menu has also been updated with a new &lt;strong>Attachment Menu&lt;/strong>&lt;/p>
&lt;pre tabindex="0">&lt;code class="language-nil" data-lang="nil">*File Attachments*
a Attach file
w Show attachments
d Detach file
0 Clear all attachments
&lt;/code>&lt;/pre>&lt;p>Your attachments aren&amp;rsquo;t just dumped into the conversation - they&amp;rsquo;re intelligently integrated:&lt;/p>
&lt;ul>
&lt;li>&lt;strong>Token counting&lt;/strong> now includes attachment content, so you always know how much context you&amp;rsquo;re using&lt;/li>
&lt;li>&lt;strong>Session persistence&lt;/strong> means your attachments are saved and restored when you save/load conversations&lt;/li>
&lt;li>&lt;strong>File size limits&lt;/strong> (configurable, 10MB default) prevent accidentally overwhelming your context window&lt;/li>
&lt;/ul>
&lt;p>Managing attached files is intuitive with dedicated commands:&lt;/p>
&lt;ul>
&lt;li>&lt;code>C-c C-w&lt;/code> - View all current attachments in a nicely formatted org mode buffer, folded to each file&lt;/li>
&lt;li>&lt;code>C-c C-d&lt;/code> - Detach specific files when you no longer need them&lt;/li>
&lt;li>&lt;code>C-c 0&lt;/code> - Clear all attachments at once&lt;/li>
&lt;li>&lt;code>C-c 1&lt;/code> - Access the full attachment menu via a transient interface&lt;/li>
&lt;/ul>
&lt;p>Working in Dired? No problem! You can attach files directly from your file browser:&lt;/p>
&lt;ul>
&lt;li>Mark multiple files and attach them all at once&lt;/li>
&lt;li>Attach the file at point with a single command&lt;/li>
&lt;/ul>
&lt;p>Use the configuration as follows:&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;dired&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> (&lt;span style="color:#a6e22e">define-key&lt;/span> dired-mode-map (kbd &lt;span style="color:#e6db74">&amp;#34;C-c C-a&amp;#34;&lt;/span>) &lt;span style="color:#a6e22e">#&amp;#39;&lt;/span>ollama-buddy-dired-attach-marked-files)))
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;h2 id="0-dot-9-dot-50">&lt;span class="timestamp-wrapper">&lt;span class="timestamp">&amp;lt;2025-05-12 Mon&amp;gt; &lt;/span>&lt;/span> &lt;strong>0.9.50&lt;/strong>&lt;/h2>
&lt;p>Added context size management and monitoring&lt;/p>
&lt;ul>
&lt;li>Added configurable context sizes for popular models (llama3.2, mistral, qwen, etc.)&lt;/li>
&lt;li>Implemented real-time context usage display in status bar&lt;/li>
&lt;li>Can display in text or bar display types&lt;/li>
&lt;li>Added context size thresholds with visual warnings&lt;/li>
&lt;li>Added interactive commands for context management:
&lt;ul>
&lt;li>&lt;code>ollama-buddy-show-context-info&lt;/code>: View all model context sizes&lt;/li>
&lt;li>&lt;code>ollama-buddy-set-model-context-size&lt;/code>: Manually configure model context&lt;/li>
&lt;li>&lt;code>ollama-buddy-toggle-context-percentage&lt;/code>: Toggle context display&lt;/li>
&lt;/ul>
&lt;/li>
&lt;li>Implemented context size validation before sending prompts&lt;/li>
&lt;li>Added token estimation and breakdown (history/system/current prompt)&lt;/li>
&lt;li>Added keybindings: C-c $ (set context), C-c % (toggle display), C-c C (show info)&lt;/li>
&lt;li>Updated status bar to show current/max context with fontification&lt;/li>
&lt;/ul>
&lt;p>I&amp;rsquo;ve added context window management and monitoring capabilities to Ollama Buddy!&lt;/p>
&lt;p>This update helps you better understand and manage your model&amp;rsquo;s context usage, preventing errors and optimizing your conversations.&lt;/p>
&lt;p>Enable it with the following:&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-show-context-percentage &lt;span style="color:#66d9ef">t&lt;/span>)
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;h3 id="usage">Usage&lt;/h3>
&lt;p>After implementing these changes:&lt;/p>
&lt;ol>
&lt;li>&lt;strong>Text mode&lt;/strong>: Shows &lt;code>1024/4096&lt;/code> style display&lt;/li>
&lt;li>&lt;strong>Bar mode&lt;/strong> (default): Shows &lt;code>███████░░░░ 2048&lt;/code> style display&lt;/li>
&lt;li>Use &lt;code>C-c 8&lt;/code> to toggle between modes&lt;/li>
&lt;li>The &lt;strong>Text mode&lt;/strong> will change fontification based on your thresholds:
&lt;ul>
&lt;li>Normal: regular fontification&lt;/li>
&lt;li>(85%+): underlined and bold&lt;/li>
&lt;li>(100%+): inverse video and bold&lt;/li>
&lt;/ul>
&lt;/li>
&lt;li>The &lt;strong>Bar mode&lt;/strong> will just fill up as normal&lt;/li>
&lt;/ol>
&lt;p>The progress bar will visually represent how much of the context window you&amp;rsquo;re using, making it easier to see at a glance when you&amp;rsquo;re approaching the limit.&lt;/p>
&lt;h3 id="implementation-details">Implementation Details&lt;/h3>
&lt;h4 id="context-size-detection">Context Size Detection&lt;/h4>
&lt;p>Determining a model&amp;rsquo;s context size proved more complex than expected. While experimenting with parsing model info JSON, I discovered that context size information can be scattered across different fields. Rather than implementing a complex JSON parser (which may come later), I chose a pragmatic approach:&lt;/p>
&lt;p>I created a new &lt;code>defcustom&lt;/code> variable &lt;code>ollama-buddy-fallback-context-sizes&lt;/code> that includes hard-coded values for popular Ollama models. The fallback mechanism is deliberately simple: substring matching followed by a sensible default of 4096 tokens.&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>(defcustom ollama-buddy-fallback-context-sizes
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#f92672">&amp;#39;&lt;/span>((&lt;span style="color:#e6db74">&amp;#34;llama3.2:1b&amp;#34;&lt;/span> &lt;span style="color:#f92672">.&lt;/span> &lt;span style="color:#ae81ff">2048&lt;/span>)
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> (&lt;span style="color:#e6db74">&amp;#34;llama3:8b&amp;#34;&lt;/span> &lt;span style="color:#f92672">.&lt;/span> &lt;span style="color:#ae81ff">4096&lt;/span>)
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> (&lt;span style="color:#e6db74">&amp;#34;tinyllama&amp;#34;&lt;/span> &lt;span style="color:#f92672">.&lt;/span> &lt;span style="color:#ae81ff">2048&lt;/span>)
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> (&lt;span style="color:#e6db74">&amp;#34;phi3:3.8b&amp;#34;&lt;/span> &lt;span style="color:#f92672">.&lt;/span> &lt;span style="color:#ae81ff">4096&lt;/span>)
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> (&lt;span style="color:#e6db74">&amp;#34;gemma3:1b&amp;#34;&lt;/span> &lt;span style="color:#f92672">.&lt;/span> &lt;span style="color:#ae81ff">4096&lt;/span>)
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> (&lt;span style="color:#e6db74">&amp;#34;gemma3:4b&amp;#34;&lt;/span> &lt;span style="color:#f92672">.&lt;/span> &lt;span style="color:#ae81ff">8192&lt;/span>)
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> (&lt;span style="color:#e6db74">&amp;#34;llama3.2:3b&amp;#34;&lt;/span> &lt;span style="color:#f92672">.&lt;/span> &lt;span style="color:#ae81ff">8192&lt;/span>)
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> (&lt;span style="color:#e6db74">&amp;#34;llama3.2:8b&amp;#34;&lt;/span> &lt;span style="color:#f92672">.&lt;/span> &lt;span style="color:#ae81ff">8192&lt;/span>)
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> (&lt;span style="color:#e6db74">&amp;#34;llama3.2:70b&amp;#34;&lt;/span> &lt;span style="color:#f92672">.&lt;/span> &lt;span style="color:#ae81ff">8192&lt;/span>)
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> (&lt;span style="color:#e6db74">&amp;#34;starcoder2:3b&amp;#34;&lt;/span> &lt;span style="color:#f92672">.&lt;/span> &lt;span style="color:#ae81ff">8192&lt;/span>)
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> (&lt;span style="color:#e6db74">&amp;#34;starcoder2:7b&amp;#34;&lt;/span> &lt;span style="color:#f92672">.&lt;/span> &lt;span style="color:#ae81ff">8192&lt;/span>)
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> (&lt;span style="color:#e6db74">&amp;#34;starcoder2:15b&amp;#34;&lt;/span> &lt;span style="color:#f92672">.&lt;/span> &lt;span style="color:#ae81ff">8192&lt;/span>)
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> (&lt;span style="color:#e6db74">&amp;#34;mistral:7b&amp;#34;&lt;/span> &lt;span style="color:#f92672">.&lt;/span> &lt;span style="color:#ae81ff">8192&lt;/span>)
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> (&lt;span style="color:#e6db74">&amp;#34;mistral:8x7b&amp;#34;&lt;/span> &lt;span style="color:#f92672">.&lt;/span> &lt;span style="color:#ae81ff">32768&lt;/span>)
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> (&lt;span style="color:#e6db74">&amp;#34;codellama:7b&amp;#34;&lt;/span> &lt;span style="color:#f92672">.&lt;/span> &lt;span style="color:#ae81ff">8192&lt;/span>)
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> (&lt;span style="color:#e6db74">&amp;#34;codellama:13b&amp;#34;&lt;/span> &lt;span style="color:#f92672">.&lt;/span> &lt;span style="color:#ae81ff">8192&lt;/span>)
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> (&lt;span style="color:#e6db74">&amp;#34;codellama:34b&amp;#34;&lt;/span> &lt;span style="color:#f92672">.&lt;/span> &lt;span style="color:#ae81ff">8192&lt;/span>)
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> (&lt;span style="color:#e6db74">&amp;#34;qwen2.5-coder:7b&amp;#34;&lt;/span> &lt;span style="color:#f92672">.&lt;/span> &lt;span style="color:#ae81ff">8192&lt;/span>)
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> (&lt;span style="color:#e6db74">&amp;#34;qwen2.5-coder:3b&amp;#34;&lt;/span> &lt;span style="color:#f92672">.&lt;/span> &lt;span style="color:#ae81ff">8192&lt;/span>)
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> (&lt;span style="color:#e6db74">&amp;#34;qwen3:0.6b&amp;#34;&lt;/span> &lt;span style="color:#f92672">.&lt;/span> &lt;span style="color:#ae81ff">4096&lt;/span>)
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> (&lt;span style="color:#e6db74">&amp;#34;qwen3:1.7b&amp;#34;&lt;/span> &lt;span style="color:#f92672">.&lt;/span> &lt;span style="color:#ae81ff">8192&lt;/span>)
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> (&lt;span style="color:#e6db74">&amp;#34;qwen3:4b&amp;#34;&lt;/span> &lt;span style="color:#f92672">.&lt;/span> &lt;span style="color:#ae81ff">8192&lt;/span>)
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> (&lt;span style="color:#e6db74">&amp;#34;qwen3:8b&amp;#34;&lt;/span> &lt;span style="color:#f92672">.&lt;/span> &lt;span style="color:#ae81ff">8192&lt;/span>)
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> (&lt;span style="color:#e6db74">&amp;#34;deepseek-r1:7b&amp;#34;&lt;/span> &lt;span style="color:#f92672">.&lt;/span> &lt;span style="color:#ae81ff">8192&lt;/span>)
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> (&lt;span style="color:#e6db74">&amp;#34;deepseek-r1:1.5b&amp;#34;&lt;/span> &lt;span style="color:#f92672">.&lt;/span> &lt;span style="color:#ae81ff">4096&lt;/span>))
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#e6db74">&amp;#34;Mapping of model names to their default context sizes.
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#e6db74">Used as a fallback when context size can&amp;#39;t be determined from the API.&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> :type &lt;span style="color:#f92672">&amp;#39;&lt;/span>(alist :key-type &lt;span style="color:#a6e22e">string&lt;/span> :value-type integer)
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> :group &lt;span style="color:#e6db74">&amp;#39;ollama-buddy&lt;/span>)
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>This approach may not be perfectly accurate for all models, but it&amp;rsquo;s sufficient for getting the core functionality working. More importantly, as a &lt;code>defcustom&lt;/code>, users can easily customize these values for complete accuracy with their specific models. Users can also set context values within the chat buffer through &lt;code>C-c C&lt;/code> (Show Context Information) for each individual model if desired.&lt;/p>
&lt;p>This design choice allowed me to focus on the essential features without getting stuck on complex context retrieval logic.&lt;/p>
&lt;p>One final thing!, if the &lt;code>num_ctx: Context window size in tokens&lt;/code> is set, then that number will also be taken into consideration. An assumption will be made that the model is honouring the context size requested and will incorporated into the context calculations accordingly.&lt;/p>
&lt;h4 id="token-estimation">Token Estimation&lt;/h4>
&lt;p>For token counting, I&amp;rsquo;ve implemented a simple heuristic: each word (using string-split) is multiplied by 1.3. This follows commonly recommended approximations and works well enough in practice. While this isn&amp;rsquo;t currently configurable, I may add it as a customization option in the future.&lt;/p>
&lt;h3 id="how-to-use-context-management-in-practice">How to Use Context Management in Practice&lt;/h3>
&lt;p>The &lt;code>C-c C&lt;/code> (Show Context Information) command is central to this feature. Rather than continuously monitoring context size while you type (which would be computationally expensive and potentially distracting), I&amp;rsquo;ve designed the system to calculate context on-demand when you choose.&lt;/p>
&lt;h4 id="typical-workflows">Typical Workflows&lt;/h4>
&lt;p>&lt;strong>Scenario 1: Paste-and-Send Approach&lt;/strong>&lt;/p>
&lt;p>Let&amp;rsquo;s say you want to paste a large block of text into the chat buffer. You can simply:&lt;/p>
&lt;ol>
&lt;li>Paste your content&lt;/li>
&lt;li>Press the send keybinding&lt;/li>
&lt;li>If the context limit is exceeded, you&amp;rsquo;ll get a warning dialog asking whether to proceed anyway&lt;/li>
&lt;/ol>
&lt;p>&lt;strong>Scenario 2: Preemptive Checking&lt;/strong>&lt;/p>
&lt;p>For more control, you can check context usage before sending:&lt;/p>
&lt;ol>
&lt;li>Paste your content&lt;/li>
&lt;li>Run &lt;code>C-c C&lt;/code> to see the current context breakdown&lt;/li>
&lt;li>If the context looks too high, you have several options:
&lt;ul>
&lt;li>Trim your current prompt&lt;/li>
&lt;li>Remove or simplify your system prompt&lt;/li>
&lt;li>Edit conversation history using Ollama Buddy&amp;rsquo;s history modification features&lt;/li>
&lt;li>Switch to a model with a larger context window&lt;/li>
&lt;/ul>
&lt;/li>
&lt;/ol>
&lt;p>&lt;strong>Scenario 3: Manage the Max History Length&lt;/strong>&lt;/p>
&lt;p>Want tight control over context size without constantly monitoring the real-time display? Since conversation history is part of the context, you can simply limit &lt;code>ollama-buddy-max-history-length&lt;/code> to control the total context size.&lt;/p>
&lt;p>For example, when working with small context windows, set &lt;code>ollama-buddy-max-history-length&lt;/code> to 1. This keeps only the last exchange (your prompt + model response), ensuring your context remains small and predictable, perfect for maintaining control without manual monitoring.&lt;/p>
&lt;p>&lt;strong>Scenario 4: Parameter num_ctx: Context window size in tokens&lt;/strong>&lt;/p>
&lt;p>Simply set this parameter and off you go!&lt;/p>
&lt;h3 id="current-status-experimental">Current Status: Experimental&lt;/h3>
&lt;p>Given the potentially limiting nature of context management, I&amp;rsquo;ve set this feature to &lt;strong>disabled by default&lt;/strong>.&lt;/p>
&lt;p>But to enable set the following :&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-show-context-percentage &lt;span style="color:#66d9ef">t&lt;/span>)
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>This means:&lt;/p>
&lt;ul>
&lt;li>Context checks won&amp;rsquo;t prevent sending prompts&lt;/li>
&lt;li>Context usage won&amp;rsquo;t appear in the status line&lt;/li>
&lt;li>However, calculations still run in the background, so &lt;code>C-c C&lt;/code> (Show Context Information) remains functional&lt;/li>
&lt;/ul>
&lt;p>As the feature matures and proves its value, I may enable it by default. For now, consider it an experimental addition that users can opt into.&lt;/p>
&lt;h3 id="more-details">More Details&lt;/h3>
&lt;p>The status bar now displays your current context usage in real-time. You&amp;rsquo;ll see a fraction showing used tokens versus the model&amp;rsquo;s maximum context size (e.g., &amp;ldquo;2048/8192&amp;rdquo;). The display automatically updates as your conversation grows.&lt;/p>
&lt;p>Context usage changes fontification to help you stay within limits:&lt;/p>
&lt;ul>
&lt;li>&lt;strong>Normal font&lt;/strong>: Normal usage (under 85%)&lt;/li>
&lt;li>&lt;strong>Bold and Underlined&lt;/strong>: Approaching limit (85-100%)&lt;/li>
&lt;li>&lt;strong>Inversed&lt;/strong>: At or exceeding limit (100%+)&lt;/li>
&lt;/ul>
&lt;p>Before sending prompts that exceed the context limit, Ollama Buddy now warns you and asks for confirmation. This prevents unexpected errors and helps you manage long conversations more effectively.&lt;/p>
&lt;p>There are now three new interactive commands:&lt;/p>
&lt;p>&lt;code>C-c $&lt;/code> - Set Model Context Size. Manually configure context sizes for custom or fine-tuned models.&lt;/p>
&lt;p>&lt;code>C-c %&lt;/code> - Toggle Context Display. Show or hide the context percentage in the status bar.&lt;/p>
&lt;p>&lt;code>C-c C&lt;/code> - Show Context Information. View a detailed breakdown of:&lt;/p>
&lt;ul>
&lt;li>All model context sizes&lt;/li>
&lt;li>Current token usage by category (history, system prompt, current prompt)&lt;/li>
&lt;li>Percentage usage&lt;/li>
&lt;/ul>
&lt;hr>
&lt;p>The system estimates token counts for:&lt;/p>
&lt;ul>
&lt;li>&lt;strong>Conversation history&lt;/strong>: All previous messages&lt;/li>
&lt;li>&lt;strong>System prompts&lt;/strong>: Your custom instructions&lt;/li>
&lt;li>&lt;strong>Current input&lt;/strong>: The message you&amp;rsquo;re about to send&lt;/li>
&lt;/ul>
&lt;p>This gives you a complete picture of your context usage before hitting send.&lt;/p>
&lt;p>The context monitoring is not enabled by default.&lt;/p>
&lt;h2 id="0-dot-9-dot-44">&lt;span class="timestamp-wrapper">&lt;span class="timestamp">&amp;lt;2025-05-05 Mon&amp;gt; &lt;/span>&lt;/span> &lt;strong>0.9.44&lt;/strong>&lt;/h2>
&lt;ul>
&lt;li>Sorted model names alphabetically in intro message&lt;/li>
&lt;li>Removed multishot writing to register name letters&lt;/li>
&lt;/ul>
&lt;p>For some reason, when I moved the .ollama folder to an external disk, the models returned with api/tags were inconsistent, which meant it broke consistent letter assignment. I&amp;rsquo;m not sure why this happened, but it is probably sensible to sort the models alphabetically anyway, as this has the benefit of naturally grouping together model families.&lt;/p>
&lt;p>I also removed the multishot feature of writing to the associated model letter. Now that I have to accommodate more than 26 models, incorporating them into the single-letter Emacs register system is all but impossible. I suspect this feature was not much used, and if you think about it, it wouldn&amp;rsquo;t have worked anyway with multiple model shots, as the register letter associated with the model would just show the most recent response. Due to these factors, I think I should remove this feature. If someone wants it back, I will probably have to design a bespoke version fully incorporated into the ollama-buddy system, as I can&amp;rsquo;t think of any other Emacs mechanism that could accommodate this.&lt;/p>
&lt;h2 id="0-dot-9-dot-43">&lt;span class="timestamp-wrapper">&lt;span class="timestamp">&amp;lt;2025-05-05 Mon&amp;gt; &lt;/span>&lt;/span> &lt;strong>0.9.43&lt;/strong>&lt;/h2>
&lt;p>Fix model reference error exceeding 26 models #15&lt;/p>
&lt;p>Update &lt;code>ollama-buddy&lt;/code> to handle more than 26 models by using prefixed combinations for model references beyond &amp;lsquo;z&amp;rsquo;. This prevents errors in &lt;code>create-intro-message&lt;/code> when the local server hosts a large number of models.&lt;/p>
&lt;h2 id="0-dot-9-dot-42">&lt;span class="timestamp-wrapper">&lt;span class="timestamp">&amp;lt;2025-05-03 Sat&amp;gt; &lt;/span>&lt;/span> &lt;strong>0.9.42&lt;/strong>&lt;/h2>
&lt;p>Added the following to recommended models:&lt;/p>
&lt;ul>
&lt;li>qwen3:0.6b&lt;/li>
&lt;li>qwen3:1.7b&lt;/li>
&lt;li>qwen3:4b&lt;/li>
&lt;li>qwen3:8b&lt;/li>
&lt;/ul>
&lt;p>and fixed pull model&lt;/p>
&lt;h2 id="0-dot-9-dot-41">&lt;span class="timestamp-wrapper">&lt;span class="timestamp">&amp;lt;2025-05-02 Fri&amp;gt; &lt;/span>&lt;/span> &lt;strong>0.9.41&lt;/strong>&lt;/h2>
&lt;p>Refactored model prefixing again so that when using only ollama models no prefix is applied and is only applied when online LLMs are selected (for example claude, chatGPT e.t.c)&lt;/p>
&lt;p>I think this makes more sense and is cleaner for I suspect the majority who may use this package are probably more interested in just using ollama models and the prefix will probably be a bit confusing.&lt;/p>
&lt;p>This could be a bit of a breaking change once again I&amp;rsquo;m afraid for those ollama users that have switched and are now familiar with prefixing &amp;ldquo;o:&amp;rdquo;, sorry!&lt;/p>
&lt;h2 id="0-dot-9-dot-40">&lt;span class="timestamp-wrapper">&lt;span class="timestamp">&amp;lt;2025-05-02 Fri&amp;gt; &lt;/span>&lt;/span> &lt;strong>0.9.40&lt;/strong>&lt;/h2>
&lt;p>Added vision support for those ollama models that can support it!&lt;/p>
&lt;p>Image files are now detected within a prompt and then processed if a model can support vision processing. Here&amp;rsquo;s a quick overview of how it works:&lt;/p>
&lt;ol>
&lt;li>
&lt;p>&lt;strong>Configuration&lt;/strong>: Users can configure the application to enable vision support and specify which models and image formats are supported. Vision support is enabled by default.&lt;/p>
&lt;/li>
&lt;li>
&lt;p>&lt;strong>Image Detection&lt;/strong>: When a prompt is submitted, the system automatically detects any image files referenced in the prompt.&lt;/p>
&lt;/li>
&lt;li>
&lt;p>&lt;strong>Vision Processing&lt;/strong>: If the model supports vision, the detected images are processed in relation to the defined prompt. Note that the detection of a model being vision capable is defined in &lt;code>ollama-buddy-vision-models&lt;/code> and can be adjusted as required.&lt;/p>
&lt;/li>
&lt;li>
&lt;p>In addition, a menu item has been added to the custom ollama buddy menu :&lt;/p>
&lt;pre tabindex="0">&lt;code class="language-nil" data-lang="nil"> [I] Analyze an Image
&lt;/code>&lt;/pre>&lt;/li>
&lt;/ol>
&lt;p>When selected, it will allow you to describe a chosen image. At some stage, I may allow integration into &lt;code>dired&lt;/code>, which would be pretty neat. :)&lt;/p></description></item><item><title>The Smallest of Productivity Gains by Instantly Opening Dired Files when isearching</title><link>https://www.emacs.dyerdwelling.family/emacs/20250513085926-emacs--instantly-open-dired-files-with-isearch-and-enter/</link><pubDate>Tue, 13 May 2025 09:20:00 +0100</pubDate><author>James Dyer</author><guid>https://www.emacs.dyerdwelling.family/emacs/20250513085926-emacs--instantly-open-dired-files-with-isearch-and-enter/</guid><description>&lt;p>If you’re an Emacs user (which I know you are), especially one who lives in &lt;code>dired-mode&lt;/code>, you’re probably familiar with the quick power of &lt;code>isearch&lt;/code> for finding files or directories. But if you’re like me, you might have noticed a tiny speed bump in the workflow: after finding a file or directory with &lt;code>isearch&lt;/code>, you would typically have to hit &lt;code>&amp;lt;enter&amp;gt;&lt;/code> to exit the search, and then &lt;code>&amp;lt;enter&amp;gt;&lt;/code> again to open the entry. That’s two steps for something that feels like it should be one and this has been a very minor annoyance for me for a very long time now.&lt;/p>
&lt;figure>&lt;img src="https://www.emacs.dyerdwelling.family/emacs/20250513085926-emacs--Instantly-Open-Dired-Files-with-Isearch-and-Enter.jpg" width="100%">
&lt;/figure>
&lt;p>I had a little time to shave some more yak hair, so lets try and address this!&lt;/p>
&lt;p>The solution I came up with was to add a bit of advice to &lt;code>isearch-exit&lt;/code>, so now, when you’re in &lt;code>dired-mode&lt;/code> and you use &lt;code>isearch&lt;/code> to locate a file or directory, pressing &lt;code>&amp;lt;enter&amp;gt;&lt;/code> will both exit the search &lt;strong>and&lt;/strong> immediately open the file or directory, no need for a second confirmation.&lt;/p>
&lt;p>Here’s the magic!&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-emacs-lisp" data-lang="emacs-lisp">&lt;span style="display:flex;">&lt;span>(defadvice isearch-exit (after dired-enter-directory-or-file activate)
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#e6db74">&amp;#34;In dired mode, enter directory or open file after isearch.&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> (when (&lt;span style="color:#a6e22e">eq&lt;/span> major-mode &lt;span style="color:#e6db74">&amp;#39;dired-mode&lt;/span>)
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> (let ((file (dired-get-file-for-visit)))
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> (when file
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> (dired-find-file)))))
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>The only thing I am a little worried about are the side effects, but I guess we shall see&amp;hellip;&lt;/p>
&lt;p>And another thing, now my &lt;code>dired&lt;/code> navigation is one step smoother; how much time generally is it going to save me? Was it worth the effort?.&lt;/p>
&lt;p>Let’s say, conservatively, that I have pressed that extra Enter 100 times each day. Each press takes, say, 150 ms, accounting for human reaction times, even though it isn’t really a reaction, more of a muscle memory response.&lt;/p>
&lt;p>Let’s do the calculations! I have no idea why I’m doing this, but maybe I can validate the amount of time I spent on this for future time-saving gains. Maybe it will make me feel better about the countless hours of yak shaving I have done over many years (with Emacs, of course, although I do have a yak in the garden shed that really needs some attention!)&lt;/p>
&lt;p>So, the savings are as follows:&lt;/p>
&lt;ul>
&lt;li>&lt;strong>Day:&lt;/strong> 15 seconds&lt;/li>
&lt;li>&lt;strong>Week:&lt;/strong> 105 seconds&lt;/li>
&lt;li>&lt;strong>Month:&lt;/strong> 7 minutes&lt;/li>
&lt;li>&lt;strong>Year:&lt;/strong> 1 hour 24 minutes&lt;/li>
&lt;/ul>
&lt;p>Well, the yearly total is probably about the amount of time I actually took to implement this, so that means in a year I will be making productivity gains! Sweeeeeeet!&lt;/p></description></item><item><title>Discovering Treasures in Emacs-solo</title><link>https://www.emacs.dyerdwelling.family/emacs/20250507201431-emacs--discovering-treasures-in-emacs-solo/</link><pubDate>Wed, 07 May 2025 21:45:00 +0100</pubDate><author>James Dyer</author><guid>https://www.emacs.dyerdwelling.family/emacs/20250507201431-emacs--discovering-treasures-in-emacs-solo/</guid><description>&lt;p>While exploring &lt;a href="https://github.com/LionyxML/emacs-solo">Emacs-solo&lt;/a>, I was struck by how closely its philosophy aligned with my own &lt;a href="https://github.com/captainflasmr/Emacs-DIYer">Emacs-DIYer&lt;/a> project. The parallels were sometimes uncanny, for example, we had independently chosen identical keybindings like &amp;ldquo;M-s g&amp;rdquo; for grep functionality. I had been also considering &amp;ldquo;M-s f&amp;rdquo; for my find variant and was looking for a home for &lt;code>recentf&lt;/code>, potentially at &amp;ldquo;M-g r&amp;rdquo;. There were also some ideas around using other finding tools such as &lt;code>ripgrep&lt;/code> and &lt;code>fd&lt;/code> as a callout to some external tools and there were many other similarities.&lt;/p>
&lt;p>I was particularly intrigued to see &lt;code>newsticker&lt;/code> included, as I had recently adopted it as an &lt;code>elfeed&lt;/code> replacement. The &lt;code>ace-window&lt;/code> alternative implementation also closely mirrored my own single-defun approach.&lt;/p>
&lt;p>So, with my first pass through, I have picked out the following goodies to be purloined and incorporated into &lt;code>Emacs-DIYer&lt;/code>&lt;/p>
&lt;ul>
&lt;li>dired icon replacements&lt;/li>
&lt;li>Git gutter status overlay&lt;/li>
&lt;li>Hide completion buffer when icomplete in buffer&lt;/li>
&lt;li>ollama alternative directly from ansi-term&lt;/li>
&lt;/ul>
&lt;figure>&lt;img src="https://www.emacs.dyerdwelling.family/emacs/20250507201431-emacs--Discovering-Treasures-in-Emacs-solo.jpg" width="100%">
&lt;/figure>
&lt;hr>
&lt;p>Firstly, the dired icon replacement provides visual clarity without requiring the &lt;code>all-the-icons&lt;/code> packages. While perhaps not essential, it gives me a comfy feel and I think these icons significantly improve the Emacs experience, particularly for frequent dired users like myself.&lt;/p>
&lt;p>I pretty much just copied the entire &lt;code>emacs-solo-dired-icons&lt;/code> section but stripped out the &lt;code>use-package&lt;/code> as I&amp;rsquo;m trying to keep &lt;code>Emacs-DIYer&lt;/code> compatible with older versions of Emacs. This worked straight out the box (like all the other integrations) and I just needed to make sure I have the requisite font collection installed.&lt;/p>
&lt;hr>
&lt;p>The git status indicator in the dired gutter was a welcome addition I hadn&amp;rsquo;t previously implemented or even considered. It provides immediate visual feedback that feels natural and intuitive, especially coming from a background of extensive version control experience.&lt;/p>
&lt;p>Having used &lt;code>subversion&lt;/code> for over 20 years (before transitioning to &lt;code>TortoiseSVN&lt;/code> and then &lt;code>magit&lt;/code> and &lt;code>vc&lt;/code>), I was already accustomed to status characters like &amp;ldquo;M&amp;rdquo; indicating modified files and other single key status key definitions seem consistent to my previous experience, and isn&amp;rsquo;t it great to see them visible in dired!&lt;/p>
&lt;p>In summary, this integration complements my existing modeline status indicator, along with &lt;code>vc&lt;/code> and gives me both file-specific and directory-level version control visibility, noice!&lt;/p>
&lt;hr>
&lt;p>I had been working on replacing &lt;code>corfu/company&lt;/code> with in-buffer completion, but hadn&amp;rsquo;t solved how to hide the completion buffer when using &lt;code>icomplete&lt;/code>. My settings already closely matched those in Emacs-solo, but the critical missing piece was this elegant advice:&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>(if icomplete-in-buffer
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> (advice-add &lt;span style="color:#e6db74">&amp;#39;completion-at-point&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> :after &lt;span style="color:#a6e22e">#&amp;#39;&lt;/span>minibuffer-hide-completions))
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>Now it feels much cleaner and I might start using it rather than &lt;code>corfu&lt;/code> from now on.&lt;/p>
&lt;hr>
&lt;p>The final act of pilfering is to incorporate a simple &lt;code>ollama&lt;/code> integration.&lt;/p>
&lt;p>Before creating my &lt;a href="https://github.com/captainflasmr/ollama-buddy">Ollama Buddy&lt;/a> package, I had explored integrating &lt;code>ollama&lt;/code> through a shell and I had also come to the conclusion that using &lt;code>ansi-term&lt;/code> was really the only quick solution, but I just couldn&amp;rsquo;t figure out the implementation details. However, the experience ultimately led me to take an alternative approach, writing a small implementation to interface with &lt;code>ollama&lt;/code> from an Emacs buffer, which eventually gave rise to &lt;code>Ollama Buddy&lt;/code>.&lt;/p>
&lt;p>The &lt;code>Emacs-solo&lt;/code> &lt;code>ansi-term&lt;/code> solution feels surprisingly well-integrated once you become comfortable switching between line and character modes. I&amp;rsquo;ve now incorporated this as an alternative to my API-based implementation, providing two no-dependency options for local LLM access!&lt;/p>
&lt;p>I plan to further explore &lt;code>Emacs-solo&lt;/code>, particularly for additional &lt;code>vc&lt;/code> enhancements. This first pass has already yielded valuable improvements to &lt;code>Emacs-DIYer&lt;/code>, and this has been a valuable exercise to learn from another project with such similar design principles.&lt;/p></description></item><item><title>Ollama-Buddy 0.9.38: Unload Models, Hide AI Reasoning, and Clearly View Modified Parameters on each request</title><link>https://www.emacs.dyerdwelling.family/emacs/20250501133351-emacs--ollama-buddy-0-9-38-unload-models-hide-ai-reasoning-and-clearly-view-modified-parameters-on-each-request/</link><pubDate>Thu, 01 May 2025 13:33:00 +0100</pubDate><author>James Dyer</author><guid>https://www.emacs.dyerdwelling.family/emacs/20250501133351-emacs--ollama-buddy-0-9-38-unload-models-hide-ai-reasoning-and-clearly-view-modified-parameters-on-each-request/</guid><description>&lt;p>More improvements to &lt;code>ollama-buddy&lt;/code> &lt;a href="https://github.com/captainflasmr/ollama-buddy">https://github.com/captainflasmr/ollama-buddy&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;h2 id="0-dot-9-dot-38">&lt;span class="timestamp-wrapper">&lt;span class="timestamp">&amp;lt;2025-04-29 Tue&amp;gt; &lt;/span>&lt;/span> &lt;strong>0.9.38&lt;/strong>&lt;/h2>
&lt;p>Added model unloading functionality to free system resources&lt;/p>
&lt;ul>
&lt;li>Add unload capability for individual models via the model management UI&lt;/li>
&lt;li>Create keyboard shortcut (C-c C-u) for quick unloading of all models&lt;/li>
&lt;li>Display running model count and unload buttons in model management buffer&lt;/li>
&lt;/ul>
&lt;p>Large language models consume significant RAM and GPU memory while loaded. Until now, there wasn&amp;rsquo;t an easy way to reclaim these resources without restarting the Ollama server entirely. This new functionality allows you to:&lt;/p>
&lt;ul>
&lt;li>Free up GPU memory when you&amp;rsquo;re done with your LLM sessions&lt;/li>
&lt;li>Switch between resource-intensive tasks more fluidly&lt;/li>
&lt;li>Manage multiple models more efficiently on machines with limited resources&lt;/li>
&lt;li>Avoid having to restart the Ollama server just to clear memory&lt;/li>
&lt;/ul>
&lt;p>There are several ways to unload models with the new functionality:&lt;/p>
&lt;ol>
&lt;li>
&lt;p>&lt;strong>Unload All Models&lt;/strong>: Press &lt;code>C-c C-u&lt;/code> to unload all running models at once (with confirmation)&lt;/p>
&lt;/li>
&lt;li>
&lt;p>&lt;strong>Model Management Interface&lt;/strong>: Access the model management interface with &lt;code>C-c W&lt;/code> where you&amp;rsquo;ll find:&lt;/p>
&lt;ul>
&lt;li>A counter showing how many models are currently running&lt;/li>
&lt;li>An &amp;ldquo;Unload All&amp;rdquo; button to free all models at once&lt;/li>
&lt;li>Individual &amp;ldquo;Unload&amp;rdquo; buttons next to each running model&lt;/li>
&lt;/ul>
&lt;/li>
&lt;li>
&lt;p>&lt;strong>Quick Access in Management Buffer&lt;/strong>: When in the model management buffer, simply press &lt;code>u&lt;/code> to unload all models&lt;/p>
&lt;/li>
&lt;/ol>
&lt;p>The unloading happens asynchronously in the background, with clear status indicators so you can see when the operation completes.&lt;/p>
&lt;h2 id="0-dot-9-dot-37">&lt;span class="timestamp-wrapper">&lt;span class="timestamp">&amp;lt;2025-04-25 Fri&amp;gt; &lt;/span>&lt;/span> &lt;strong>0.9.37&lt;/strong>&lt;/h2>
&lt;ul>
&lt;li>Display modified parameters in token stats&lt;/li>
&lt;/ul>
&lt;p>Enhanced the token statistics section to include any modified parameters, providing a clearer insight into the active configurations. This update helps in debugging and understanding the runtime environment.&lt;/p>
&lt;h2 id="0-dot-9-dot-36">&lt;span class="timestamp-wrapper">&lt;span class="timestamp">&amp;lt;2025-04-25 Fri&amp;gt; &lt;/span>&lt;/span> &lt;strong>0.9.36&lt;/strong>&lt;/h2>
&lt;p>Added Reasoning/Thinking section visibility toggle functionality&lt;/p>
&lt;ul>
&lt;li>Introduced the ability to hide reasoning/thinking sections during AI responses, making the chat output cleaner and more focused on final results&lt;/li>
&lt;li>Added a new customizable variable &lt;code>ollama-buddy-hide-reasoning&lt;/code> (default: nil) which controls visibility of reasoning sections&lt;/li>
&lt;li>Added &lt;code>ollama-buddy-reasoning-markers&lt;/code> to configure marker pairs that encapsulate reasoning sections (supports multiple formats like &amp;lt;think&amp;gt;&amp;lt;/think&amp;gt; or &amp;mdash;-)&lt;/li>
&lt;li>Added &lt;code>ollama-buddy-toggle-reasoning-visibility&lt;/code> interactive command to switch visibility on/off&lt;/li>
&lt;li>Added keybinding &lt;code>C-c V&lt;/code> for toggling reasoning visibility in chat buffer&lt;/li>
&lt;li>Added transient menu option &amp;ldquo;V&amp;rdquo; for toggling reasoning visibility&lt;/li>
&lt;li>When reasoning is hidden, a status message shows which section is being processed (e.g., &amp;ldquo;Think&amp;hellip;&amp;rdquo; or custom marker names)&lt;/li>
&lt;li>Reasoning sections are automatically detected during streaming responses&lt;/li>
&lt;li>Header line now indicates when reasoning is hidden with &amp;ldquo;REASONING HIDDEN&amp;rdquo; text&lt;/li>
&lt;li>All changes preserve streaming response functionality while providing cleaner output&lt;/li>
&lt;/ul>
&lt;p>This feature is particularly useful when working with AI models that output their &amp;ldquo;chain of thought&amp;rdquo; or reasoning process before providing the final answer, allowing users to focus on the end results while still having the option to see the full reasoning when needed.&lt;/p></description></item><item><title>Ollama-Buddy 0.9.35: Grok, Gemini Integration and Enhanced Sessions</title><link>https://www.emacs.dyerdwelling.family/emacs/20250424085731-emacs--ollama-buddy-0-9-35-grok-gemini-integration-enhanced-sessions/</link><pubDate>Thu, 24 Apr 2025 09:20:00 +0100</pubDate><author>James Dyer</author><guid>https://www.emacs.dyerdwelling.family/emacs/20250424085731-emacs--ollama-buddy-0-9-35-grok-gemini-integration-enhanced-sessions/</guid><description>&lt;p>Several improvements in the latest Ollama Buddy updates (versions 0.9.21 through 0.9.35):&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="new-ai-integrations-with-grok-and-gemini">🎉 New AI Integrations with Grok and Gemini&lt;/h2>
&lt;ul>
&lt;li>Google&amp;rsquo;s Gemini is now complementing existing support for Claude, ChatGPT (OpenAI), and Ollama models. Setting up is straightforward and consistent with other integrations.&lt;/li>
&lt;li>Just like the existing integrations, Grok can now be easily configured with your API key.&lt;/li>
&lt;/ul>
&lt;!--more-->
&lt;h2 id="improved-remote-llm-architecture">🔗 Improved Remote LLM Architecture&lt;/h2>
&lt;p>LLM internal decoupling, making Ollama Buddy&amp;rsquo;s core logic independent from any specific remote LLM. Each LLM integration now functions as a self-contained extension, significantly simplifying future additions and maintenance.&lt;/p>
&lt;h2 id="standardized-model-prefixing">🎯 Standardized Model Prefixing&lt;/h2>
&lt;p>Now there are more remote LLMs into the mix I thought it was probably time to more clearly distinguish between model collections, so I have defined the following prefixes:&lt;/p>
&lt;ul>
&lt;li>Ollama: &lt;code>o:&lt;/code>&lt;/li>
&lt;li>ChatGPT: &lt;code>a:&lt;/code>&lt;/li>
&lt;li>Claude: &lt;code>c:&lt;/code>&lt;/li>
&lt;li>Gemini: &lt;code>g:&lt;/code>&lt;/li>
&lt;li>Grok: &lt;code>k:&lt;/code>&lt;/li>
&lt;/ul>
&lt;p>This change helps ensure clarity, especially when recalling previous sessions. Note: existing session files will need the Ollama prefix (&lt;code>o:&lt;/code>) added manually if you encounter issues recalling older sessions.&lt;/p>
&lt;h2 id="enhanced-session-management">💾 Enhanced Session Management&lt;/h2>
&lt;p>Saving session now makes a little more sense and is more consistent:&lt;/p>
&lt;ul>
&lt;li>Automatic timestamped session names (you can still set your own).&lt;/li>
&lt;li>Sessions now also save as &lt;code>org&lt;/code> files alongside the original &lt;code>elisp&lt;/code> files, allowing for richer recall and easy inspection later.&lt;/li>
&lt;li>The current session name appears dynamically in your modeline, offering quick context.&lt;/li>
&lt;/ul>
&lt;h2 id="️-additional-improvements">🛠️ Additional Improvements&lt;/h2>
&lt;ul>
&lt;li>UTF-8 encoding fixes for remote LLM stream responses.&lt;/li>
&lt;li>Refactored history and model management so all the latest models are available for selection. This is currently most relevant for remote LLMs which often change their model selection.&lt;/li>
&lt;li>History view/edit functionality merged into one keybinding&lt;/li>
&lt;/ul>
&lt;hr>
&lt;p>and here is the change history&lt;/p>
&lt;h2 id="0-dot-9-dot-35">&lt;span class="timestamp-wrapper">&lt;span class="timestamp">&amp;lt;2025-04-21 Mon&amp;gt; &lt;/span>&lt;/span> &lt;strong>0.9.35&lt;/strong>&lt;/h2>
&lt;p>Added Grok support&lt;/p>
&lt;p>Integration is very similar to other remote AIs:&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-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> (ollama-buddy-grok-api-key
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> (auth-source-pick-first-password :host &lt;span style="color:#e6db74">&amp;#34;ollama-buddy-grok&amp;#34;&lt;/span> :user &lt;span style="color:#e6db74">&amp;#34;apikey&amp;#34;&lt;/span>))
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> :config
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> (require &lt;span style="color:#e6db74">&amp;#39;ollama-buddy-grok&lt;/span> &lt;span style="color:#66d9ef">nil&lt;/span> &lt;span style="color:#66d9ef">t&lt;/span>))
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;h2 id="0-dot-9-dot-33">&lt;span class="timestamp-wrapper">&lt;span class="timestamp">&amp;lt;2025-04-20 Sun&amp;gt; &lt;/span>&lt;/span> &lt;strong>0.9.33&lt;/strong>&lt;/h2>
&lt;p>Fixed utf-8 encoding stream response issues from remote LLMs.&lt;/p>
&lt;h2 id="0-dot-9-dot-32">&lt;span class="timestamp-wrapper">&lt;span class="timestamp">&amp;lt;2025-04-19 Sat&amp;gt; &lt;/span>&lt;/span> &lt;strong>0.9.32&lt;/strong>&lt;/h2>
&lt;p>Finished the remote LLM decoupling process, meaning that the core &lt;code>ollama-buddy&lt;/code> logic is now not dependent on any remote LLM, and each remote LLM package is self-contained and functions as a unique extension.&lt;/p>
&lt;h2 id="0-dot-9-dot-31">&lt;span class="timestamp-wrapper">&lt;span class="timestamp">&amp;lt;2025-04-18 Fri&amp;gt; &lt;/span>&lt;/span> &lt;strong>0.9.31&lt;/strong>&lt;/h2>
&lt;p>Refactored model prefixing logic and cleaned up&lt;/p>
&lt;ul>
&lt;li>Standardized model prefixing by introducing distinct prefixes for Ollama (&lt;code>o:&lt;/code>), OpenAI (&lt;code>a:&lt;/code>), Claude (&lt;code>c:&lt;/code>), and Gemini (&lt;code>g:&lt;/code>) models.&lt;/li>
&lt;li>Centralized functions to get full model names with prefixes across different model types.&lt;/li>
&lt;li>Removed redundant and unused variables related to model management.&lt;/li>
&lt;/ul>
&lt;p>Note that there may be some breaking changes here especially regarding session recall as all models will now have a prefix to uniquely identify their type. For &lt;code>ollama&lt;/code> recall, just edit the session files to prepend the ollama prefix of &amp;ldquo;o:&amp;rdquo;&lt;/p>
&lt;h2 id="0-dot-9-dot-30">&lt;span class="timestamp-wrapper">&lt;span class="timestamp">&amp;lt;2025-04-17 Thu&amp;gt; &lt;/span>&lt;/span> &lt;strong>0.9.30&lt;/strong>&lt;/h2>
&lt;p>Added Gemini integration!&lt;/p>
&lt;p>As with the Claude and ChatGPT integration, you will need to add something similar to them in your configuration. I currently have the following set up to enable access to the remote LLMs:&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-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> (ollama-buddy-openai-api-key
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> (auth-source-pick-first-password :host &lt;span style="color:#e6db74">&amp;#34;ollama-buddy-openai&amp;#34;&lt;/span> :user &lt;span style="color:#e6db74">&amp;#34;apikey&amp;#34;&lt;/span>))
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> (ollama-buddy-claude-api-key
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> (auth-source-pick-first-password :host &lt;span style="color:#e6db74">&amp;#34;ollama-buddy-claude&amp;#34;&lt;/span> :user &lt;span style="color:#e6db74">&amp;#34;apikey&amp;#34;&lt;/span>))
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> (ollama-buddy-gemini-api-key
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> (auth-source-pick-first-password :host &lt;span style="color:#e6db74">&amp;#34;ollama-buddy-gemini&amp;#34;&lt;/span> :user &lt;span style="color:#e6db74">&amp;#34;apikey&amp;#34;&lt;/span>))
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> :config
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> (require &lt;span style="color:#e6db74">&amp;#39;ollama-buddy-openai&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> (require &lt;span style="color:#e6db74">&amp;#39;ollama-buddy-claude&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> (require &lt;span style="color:#e6db74">&amp;#39;ollama-buddy-gemini&lt;/span> &lt;span style="color:#66d9ef">nil&lt;/span> &lt;span style="color:#66d9ef">t&lt;/span>))
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>Also with the previous update all the latest model names will be pulled, so there should be a full comprehensive list for each of the main remote AI LLMs!&lt;/p>
&lt;h2 id="0-dot-9-dot-23">&lt;span class="timestamp-wrapper">&lt;span class="timestamp">&amp;lt;2025-04-16 Wed&amp;gt; &lt;/span>&lt;/span> &lt;strong>0.9.23&lt;/strong>&lt;/h2>
&lt;p>Refactored history and model management for remote LLMs&lt;/p>
&lt;ul>
&lt;li>Now pulling in latest model list for remote LLMs (so now ChatGPT 4.1 is available!)&lt;/li>
&lt;li>Removed redundant history and model management functions from &lt;code>ollama-buddy-claude.el&lt;/code> and &lt;code>ollama-buddy-openai.el&lt;/code>. Replaced them with shared implementations to streamline code and reduce duplication&lt;/li>
&lt;/ul>
&lt;h2 id="0-dot-9-dot-22">&lt;span class="timestamp-wrapper">&lt;span class="timestamp">&amp;lt;2025-04-15 Tue&amp;gt; &lt;/span>&lt;/span> &lt;strong>0.9.22&lt;/strong>&lt;/h2>
&lt;p>Enhanced session management&lt;/p>
&lt;ul>
&lt;li>Refactored &lt;code>ollama-buddy-sessions-save&lt;/code> to autogenerate session names using timestamp and model.&lt;/li>
&lt;li>Improved session saving/loading by integrating org file handling.&lt;/li>
&lt;li>Updated mode line to display current session name dynamically.&lt;/li>
&lt;/ul>
&lt;p>Several improvements to session management, making it more intuitive and efficient for users. Here&amp;rsquo;s a breakdown of the new functionality:&lt;/p>
&lt;p>When saving a session, Ollama Buddy now creates a default name using the current timestamp and model name, users can still provide a custom name if desired.&lt;/p>
&lt;p>An org file is now saved alongside the original elisp session file. This allows for better session recall as all interactions will be pulled back with the underlying session parameters still restored as before. There is an additional benefit in not only recalling precisely the session and any additional org interactions but also quickly saving to an org file for potential later inspection. Along with the improved autogenerated session name, this means it is much faster and more intuitive to save a snapshot of the current chat interaction.&lt;/p>
&lt;p>The modeline now displays the current session name!&lt;/p>
&lt;h2 id="0-dot-9-dot-21">&lt;span class="timestamp-wrapper">&lt;span class="timestamp">&amp;lt;2025-04-11 Fri&amp;gt; &lt;/span>&lt;/span> &lt;strong>0.9.21&lt;/strong>&lt;/h2>
&lt;p>Add history edit/view toggle features, so effectively merging the former history display into the history edit functionality.&lt;/p></description></item><item><title>Flex Matching with isearch</title><link>https://www.emacs.dyerdwelling.family/emacs/20250415150114-emacs--flex-matching-with-isearch/</link><pubDate>Thu, 17 Apr 2025 09:00:00 +0100</pubDate><author>James Dyer</author><guid>https://www.emacs.dyerdwelling.family/emacs/20250415150114-emacs--flex-matching-with-isearch/</guid><description>&lt;p>I&amp;rsquo;m having issues with &lt;code>isearch&lt;/code>!&lt;/p>
&lt;p>I&amp;rsquo;ve been using &lt;code>fido-mode&lt;/code> for a while now (I&amp;rsquo;m one of those weirdos who transitioned from the Vertico/Marginalia/Orderless stack back to an Emacs built-in) and have become accustomed to the flexible matching that &lt;code>fido&lt;/code> offers. For example, I often shorten commands to find them, such as:&lt;/p>
&lt;figure>&lt;img src="https://www.emacs.dyerdwelling.family/emacs/20250415150114-emacs--Flex-Matching-With-Isearch.jpg" width="100%">
&lt;/figure>
&lt;p>It is very common for me to search in a buffer for strings that contain a dash (especially now I am doing a lot more elisp programming), and I keep instinctively fido-shortening the search (so searching without the dash). Each time, &lt;code>isearch&lt;/code> says no (or fails), so I have to awkwardly reach out for the &amp;ldquo;-&amp;rdquo; and go back and perform the search again.&lt;/p>
&lt;p>What if there was a more flexible way to perform an in-buffer search? Well, of course, there is, and that is the &lt;code>isearch-regexp&lt;/code> variants. I have seen some people just binding this to their main &lt;code>isearch&lt;/code> because, why not? Most of the time, the search will be identical, and there is always the opportunity for a more flexible search using &amp;ldquo;.*&amp;rdquo;.&lt;/p>
&lt;p>Let&amp;rsquo;s run an example. I would like to search for the first occurrence of &lt;code>use-package&lt;/code> in my config. Let&amp;rsquo;s see how many keys we can get down to for an efficient search and how easy ergonomically they are to get to (excluding the possibility of some annoyingly similar matching lines in the config)&lt;/p>
&lt;p>Firstly, let&amp;rsquo;s try &lt;code>isearch&lt;/code> - you would have to type up to at least &amp;ldquo;use-pa&amp;rdquo; (probably).&lt;/p>
&lt;p>Now for &lt;code>isearch-regex&lt;/code> - you would type &amp;ldquo;us.*p&amp;rdquo; (probably) or of course just the same as &lt;code>isearch&lt;/code>, so &amp;ldquo;use-pa&amp;rdquo;. This is better but a bit awkward to access the &amp;ldquo;*&amp;rdquo; and you would have to be quite aware of what you were searching for and the rough split of words.&lt;/p>
&lt;p>If this were translated into a form of &lt;code>fido&lt;/code> search, you would type at least &amp;ldquo;usep&amp;rdquo; (probably), a similar number of characters, but no awkward punctuation (so faster to type) and the find is more flexible and for me now, more familiar.&lt;/p>
&lt;p>As far as I&amp;rsquo;m aware, by default &lt;code>fido&lt;/code> uses some form of flex matching (but not quite flex), which has been good enough for me for a while now. So, how do I get this form of matching using &lt;code>isearch&lt;/code> and it being in-buffer?&lt;/p>
&lt;p>After a little investigation, there are packages out there in Emacs-land for some fuzzy searching, and the most relevant seemed to be &lt;code>flex-isearch&lt;/code>. I&amp;rsquo;m not sure I quite got it working for me, but looking through the code, I thought I could distil some concepts into simple defuns. I learned that you can actually slot any search function at the backend of isearch, so let&amp;rsquo;s transform a normal search into something on steroids that would give us a flexible search, &lt;code>fido&lt;/code> style!&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>(defvar flex-isearch-group-size &lt;span style="color:#ae81ff">3&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#e6db74">&amp;#34;Number of initial characters to group together for more accurate flex searching.&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>(defun flex-isearch-regexp-compile (&lt;span style="color:#a6e22e">string&lt;/span>)
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#e6db74">&amp;#34;Convert a search string to a more intelligent flex-matching regexp.
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#e6db74">The first `flex-isearch-group-size` characters are grouped together for more accurate matching.&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> (let* ((parts (split-string &lt;span style="color:#a6e22e">string&lt;/span> &lt;span style="color:#e6db74">&amp;#34; &amp;#34;&lt;/span> &lt;span style="color:#66d9ef">t&lt;/span>))
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> (compile-part
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> (lambda (part)
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> (let ((grouped (&lt;span style="color:#a6e22e">substring&lt;/span> part &lt;span style="color:#ae81ff">0&lt;/span> (&lt;span style="color:#a6e22e">min&lt;/span> flex-isearch-group-size (&lt;span style="color:#a6e22e">length&lt;/span> part))))
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> (rest (&lt;span style="color:#a6e22e">substring&lt;/span> part (&lt;span style="color:#a6e22e">min&lt;/span> flex-isearch-group-size (&lt;span style="color:#a6e22e">length&lt;/span> part)))))
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> (&lt;span style="color:#a6e22e">concat&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> (&lt;span style="color:#a6e22e">regexp-quote&lt;/span> grouped)
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> (&lt;span style="color:#a6e22e">mapconcat&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> (lambda (char)
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> (let ((c (&lt;span style="color:#a6e22e">char-to-string&lt;/span> char)))
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> (cond
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> ((and (&lt;span style="color:#a6e22e">&amp;gt;=&lt;/span> char &lt;span style="color:#e6db74">?A&lt;/span>) (&lt;span style="color:#a6e22e">&amp;lt;=&lt;/span> char &lt;span style="color:#e6db74">?Z&lt;/span>))
&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> c &lt;span style="color:#e6db74">&amp;#34;\n]*&amp;#34;&lt;/span> c))
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> ((and (&lt;span style="color:#a6e22e">&amp;gt;=&lt;/span> char &lt;span style="color:#e6db74">?a&lt;/span>) (&lt;span style="color:#a6e22e">&amp;lt;=&lt;/span> char &lt;span style="color:#e6db74">?z&lt;/span>))
&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> c (&lt;span style="color:#a6e22e">upcase&lt;/span> c) &lt;span style="color:#e6db74">&amp;#34;\n]*[&amp;#34;&lt;/span> c (&lt;span style="color:#a6e22e">upcase&lt;/span> c) &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:#66d9ef">t&lt;/span>
&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> c) &lt;span style="color:#e6db74">&amp;#34;\n]*&amp;#34;&lt;/span> (&lt;span style="color:#a6e22e">regexp-quote&lt;/span> c))))))
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> rest
&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:#e6db74">&amp;#34;[^-_[:alnum:]\n]*&amp;#34;&lt;/span>)))))
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> (&lt;span style="color:#a6e22e">concat&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#e6db74">&amp;#34;\\b&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> (&lt;span style="color:#a6e22e">mapconcat&lt;/span> compile-part parts &lt;span style="color:#e6db74">&amp;#34;[^-_[:alnum:]\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>(defun flex-isearch-search-fun ()
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#e6db74">&amp;#34;Return the appropriate search function for flex searching.&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> (if isearch-forward &lt;span style="color:#e6db74">&amp;#39;flex-isearch-forward&lt;/span> &lt;span style="color:#e6db74">&amp;#39;flex-isearch-backward&lt;/span>))
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>(defun flex-isearch-forward (&lt;span style="color:#a6e22e">string&lt;/span> &lt;span style="color:#66d9ef">&amp;amp;optional&lt;/span> bound noerror count)
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#e6db74">&amp;#34;Flex search forward for STRING.&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> (let ((regexp (flex-isearch-regexp-compile &lt;span style="color:#a6e22e">string&lt;/span>)))
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> (&lt;span style="color:#a6e22e">re-search-forward&lt;/span> regexp bound &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>(defun flex-isearch-backward (&lt;span style="color:#a6e22e">string&lt;/span> &lt;span style="color:#66d9ef">&amp;amp;optional&lt;/span> bound noerror count)
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#e6db74">&amp;#34;Flex search backward for STRING.&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> (let ((regexp (flex-isearch-regexp-compile &lt;span style="color:#a6e22e">string&lt;/span>)))
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> (&lt;span style="color:#a6e22e">re-search-backward&lt;/span> regexp bound &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>(setq isearch-search-fun-function &lt;span style="color:#e6db74">&amp;#39;flex-isearch-search-fun&lt;/span>)
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>With my first attempt at this, the search at times seemed to settle on more candidates than I would have liked, but I thought, &amp;ldquo;I generally always know at least the first 2 or 3 characters that I&amp;rsquo;m searching for, at which point things get a little fuzzy&amp;rdquo; (pun intended). So, can I group the first few letters as part of the search, thus narrowing down the candidate list?&lt;/p>
&lt;p>The answer is yes, yes I can.&lt;/p>
&lt;p>The code above is a first attempt and with all these things, I shall play around with it and see if it works for me. At the moment, this seems to give me a more &lt;code>fido-iish&lt;/code> feel when searching in-buffer with &lt;code>isearch&lt;/code>, and no awkward search punctuation in sight!. So, using the examples above I can now find &amp;ldquo;use-package&amp;rdquo; in my config with a simple &amp;ldquo;usep&amp;rdquo;.&lt;/p>
&lt;p>I shall report back in a later post to see if it has settled into my workflow, or whether there is an annoyance that I just cannot put up with so it will have to be discarded, along with my other myriad of Emacs experiments!&lt;/p></description></item><item><title>Ollama-Buddy 0.9.20: Curated AI Prompting with Awesome ChatGPT Prompts</title><link>https://www.emacs.dyerdwelling.family/emacs/20250409134339-emacs--ollama-buddy-0-9-20-curated-ai-prompting-with-awesome-chatgpt-prompts/</link><pubDate>Wed, 09 Apr 2025 13:43:00 +0100</pubDate><author>James Dyer</author><guid>https://www.emacs.dyerdwelling.family/emacs/20250409134339-emacs--ollama-buddy-0-9-20-curated-ai-prompting-with-awesome-chatgpt-prompts/</guid><description>&lt;ul>
&lt;li>Added &lt;code>ollama-buddy-awesome.el&lt;/code> to integrate Awesome ChatGPT Prompts.&lt;/li>
&lt;/ul>
&lt;p>&lt;code>ollama-buddy-awesome&lt;/code> is an &lt;code>ollama-buddy&lt;/code> extension that integrates the popular &lt;a href="https://github.com/f/awesome-chatgpt-prompts">Awesome ChatGPT Prompts&lt;/a> repository, allowing you to leverage hundreds of curated prompts for various tasks and roles right within your Emacs environment, I thought that since I have integrated the &lt;code>fabric&lt;/code> set of curated prompts, so then why not these!&lt;/p>
&lt;figure>&lt;img src="https://www.emacs.dyerdwelling.family/emacs/20250325093201-emacs--Ollama-Buddy-0-9-11-Experimental-ChatGPT-Integration-Customizable-AI-Streaming-and-Texinfo-documentation.jpg" width="100%">
&lt;/figure>
&lt;p>There is a video demonstration here : &lt;a href="https://www.youtube.com/watch?v=5A4bTvjmPeo">https://www.youtube.com/watch?v=5A4bTvjmPeo&lt;/a>&lt;/p>
&lt;h2 id="key-features">Key Features&lt;/h2>
&lt;ol>
&lt;li>
&lt;p>&lt;strong>Seamless Sync&lt;/strong>: Automatically fetch the latest prompts from the GitHub repository, ensuring you always have access to the most up-to-date collection.&lt;/p>
&lt;/li>
&lt;li>
&lt;p>&lt;strong>Smart Categorization&lt;/strong>: Prompts are intelligently categorized based on their content, making it easy to find the perfect prompt for your task.&lt;/p>
&lt;/li>
&lt;li>
&lt;p>&lt;strong>Interactive Selection&lt;/strong>: Choose prompts through Emacs&amp;rsquo; familiar completion interface, with category and title information for quick identification.&lt;/p>
&lt;/li>
&lt;li>
&lt;p>&lt;strong>Effortless Application&lt;/strong>: Apply selected prompts as system prompts in ollama-buddy with a single command, streamlining your AI-assisted workflow.&lt;/p>
&lt;/li>
&lt;li>
&lt;p>&lt;strong>Prompt Management&lt;/strong>: List available prompts, preview their content, and display full prompt details on demand.&lt;/p>
&lt;/li>
&lt;/ol>
&lt;h2 id="getting-started">Getting Started&lt;/h2>
&lt;p>To access the Awesome ChatGPT prompts, just select the transient menu as normal and select &amp;ldquo;[a] Awesome ChatGPT Prompts&amp;rdquo;, this will fetch the prompts and prepare everything for your first use and give you a transient menu as follows:&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>Actions
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>[s] Send with Prompt
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>[p] Set as System Prompt
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>[l] List All Prompts
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>[c] Category Browser
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>[S] Sync Latest Prompts
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>[q] Back to Main Menu
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>Now available are a vast array of role-based and task-specific prompts, enhancing your &lt;code>ollama-buddy&lt;/code> interactions in Emacs!&lt;/p></description></item><item><title>Ollama-Buddy 0.9.17: Claude Support, Asynchronous Operations and Responses to Registers</title><link>https://www.emacs.dyerdwelling.family/emacs/20250330164641-emacs--ollama-buddy-0.9/</link><pubDate>Wed, 02 Apr 2025 17:15:00 +0100</pubDate><author>James Dyer</author><guid>https://www.emacs.dyerdwelling.family/emacs/20250330164641-emacs--ollama-buddy-0.9/</guid><description>&lt;p>I’ve added experimental support for Claude AI, aligning its implementation with my support for ChatGPT, to pave the way for external AI templating which should make it easier to integrate new models in the future. Like the ChatGPT implementation, it doesn’t stream tokens in real time but instead outputs the final result all at once. However, after testing it further, I’m actually starting to prefer it. The response speed is so fast that not seeing a real-time “Typing” indicator isn’t a big deal. I think that streaming feels more relevant for local LLMs running through &lt;code>ollama&lt;/code>, where token generation is slower, making real-time output more useful.&lt;/p>
&lt;figure>&lt;img src="https://www.emacs.dyerdwelling.family/emacs/20250325093201-emacs--Ollama-Buddy-0-9-11-Experimental-ChatGPT-Integration-Customizable-AI-Streaming-and-Texinfo-documentation.jpg" width="100%">
&lt;/figure>
&lt;p>Secondly, the Texinfo manual for this package now magically installs itself when pulling from MELPA. I fluked this!, I just thought it was sensible to create a &lt;code>docs&lt;/code> directory and then plonked an info file there. After looking into it, I found that MELPA performs some extra processing when handling Emacs documentation. It automatically scans common documentation directories like &lt;code>docs&lt;/code> and grabs the &lt;code>.info&lt;/code> file, pretty neat! This means you can now browse &lt;code>ollama-buddy&lt;/code>&amp;rsquo;s functionality directly through &lt;code>info&lt;/code>.&lt;/p>
&lt;p>Next up, I realized I wasn’t efficiently handling &lt;code>ollama&lt;/code> model operations like delete, pull, and copy. I was currently process-calling &lt;code>ollama&lt;/code> and passing through arguments as if I was on the command line. This was functional, but wasn’t ideal and wasn&amp;rsquo;t stimulating the &lt;code>ollama&lt;/code> API for these operations correctly, or even at all. After reassessing my design, I came to the realization that I was in fact using four different methods to communicate with &lt;code>ollama&lt;/code>:&lt;/p>
&lt;ol>
&lt;li>&lt;code>curl&lt;/code>&lt;/li>
&lt;li>Direct process calls&lt;/li>
&lt;li>&lt;code>url.el&lt;/code>&lt;/li>
&lt;li>&lt;code>make-network-process&lt;/code>&lt;/li>
&lt;/ol>
&lt;p>At first, I leaned on &lt;code>curl&lt;/code> since it was straightforward and matched the official &lt;code>ollama&lt;/code> examples. My approach with a project such as this is generally to get things working quickly and then refine/iterate later. However, once I had a solid design (and design principles!), I wanted to eliminate external dependencies like &lt;code>curl&lt;/code>. This lead me to explore &lt;code>url.el&lt;/code>, but initially I couldn&amp;rsquo;t seem to get my head round it / get it working - and I decided to go for the nuclear option of &lt;code>make-network-process&lt;/code> for network-level flexibility. Later, I revisited &lt;code>url.el&lt;/code> for Claude and ChatGPT support, rewriting the implementation to use &lt;code>url-retrieve&lt;/code>, but decided generally to keep &lt;code>make-network-process&lt;/code> for &lt;code>ollama&lt;/code> interactions as it was still Emacs built-in and actually I&amp;rsquo;m more familiar with the lower level network concepts as having wrestled with them over many years at work.&lt;/p>
&lt;p>Anyway, back to the stimulating of the &lt;code>ollama&lt;/code> API for model operations.&lt;/p>
&lt;p>I considered leveraging my existing &lt;code>url.el&lt;/code> based &lt;code>ollama-buddy--make-request&lt;/code> function, but quickly realized it used &lt;code>url-request-data&lt;/code>, which blocks execution. This wasn&amp;rsquo;t an issue for quick requests like model info or tags (although could be), but for long-running tasks like model pulls and general model operations it risked freezing Emacs!&lt;/p>
&lt;p>Switching to &lt;code>url-retrieve&lt;/code> solved this, as it runs asynchronously, however, &lt;code>url-retrieve&lt;/code> only triggers its callback at the end of the request, making real-time progress tracking difficult. To address this, I implemented &lt;code>run-with-timer&lt;/code>, ensuring persistent status updates in the header-line which now allows for multiple operations, including pull, copy, delete, even simultaneously.&lt;/p>
&lt;p>Now that I’m using &lt;code>ollama-buddy&lt;/code> as my primary Emacs AI assistant, I’m refining my AI workflow. Since my design is centered around the chat buffer, all interactions and outputs end up there. But what about quick tasks like proofreading text or refactoring code? Ideally, I want a workflow that aligns with &lt;code>ollama-buddy&lt;/code>&amp;rsquo;s principles, meaning no direct in-buffer edits (though future me might change their mind!).&lt;/p>
&lt;p>For example, if I want to tighten a rambling paragraph (is this one? :), I currently send it via the custom menu to the chat buffer with a proofreading tag. However, retrieving the output requires jumping to the chat buffer, copying it, switching back, deleting the original, and then pasting the revision - too many steps.&lt;/p>
&lt;p>To streamline this, I’ve now implemented a feature that writes the latest response to a customizable register, this way, I can simply delete the original text and insert the improved version without extra navigation.&lt;/p>
&lt;p>Note: I have remapped the &lt;code>insert-register&lt;/code> default keybinding of &lt;code>C-x r i&lt;/code> to &lt;code>M-a&lt;/code>, as by default I am writing to register ?a and &lt;code>M-a a&lt;/code> seems more comfortable.&lt;/p>
&lt;h2 id="0-dot-9-dot-17">&lt;span class="timestamp-wrapper">&lt;span class="timestamp">&amp;lt;2025-04-01 Tue&amp;gt; &lt;/span>&lt;/span> &lt;strong>0.9.17&lt;/strong>&lt;/h2>
&lt;ul>
&lt;li>Added link to &lt;code>ollama-buddy&lt;/code> info manual from the chat buffer and transient menu as MELPA has now picked it up and installed it!&lt;/li>
&lt;/ul>
&lt;h2 id="0-dot-9-dot-16">&lt;span class="timestamp-wrapper">&lt;span class="timestamp">&amp;lt;2025-03-28 Fri&amp;gt; &lt;/span>&lt;/span> &lt;strong>0.9.16&lt;/strong>&lt;/h2>
&lt;ul>
&lt;li>Added &lt;code>ollama-buddy-fix-encoding-issues&lt;/code> to handle text encoding problems.&lt;/li>
&lt;li>Refactored and streamline fabric pattern description handling.&lt;/li>
&lt;li>Removed unused fabric pattern categories to enhance maintainability.&lt;/li>
&lt;/ul>
&lt;h2 id="0-dot-9-dot-15">&lt;span class="timestamp-wrapper">&lt;span class="timestamp">&amp;lt;2025-03-28 Fri&amp;gt; &lt;/span>&lt;/span> &lt;strong>0.9.15&lt;/strong>&lt;/h2>
&lt;ul>
&lt;li>Implement asynchronous operations for model management
&lt;ul>
&lt;li>Introduce non-blocking API requests for fetching, copying, and deleting models&lt;/li>
&lt;/ul>
&lt;/li>
&lt;li>Add caching mechanisms to improve efficiency
&lt;ul>
&lt;li>Cache model data to reduce redundant API calls&lt;/li>
&lt;li>Manage cache expiration with timestamps and time-to-live settings&lt;/li>
&lt;/ul>
&lt;/li>
&lt;li>Update status line to reflect ongoing background operations&lt;/li>
&lt;li>Ensure smooth user interaction by minimizing wait times and enhancing performance&lt;/li>
&lt;/ul>
&lt;h2 id="0-dot-9-dot-13">&lt;span class="timestamp-wrapper">&lt;span class="timestamp">&amp;lt;2025-03-26 Wed&amp;gt; &lt;/span>&lt;/span> &lt;strong>0.9.13&lt;/strong>&lt;/h2>
&lt;ul>
&lt;li>Added automatic writing of last response to a register&lt;/li>
&lt;li>Added M-r to search through prompt history&lt;/li>
&lt;/ul>
&lt;p>I was just thinking about a general workflow aspect and that is getting responses out of the &lt;code>ollama-buddy&lt;/code> chat buffer. Of course if you are already there then it will be easier, but even then the latest prompt, which is probably the one you are interested in will still have to be copied to the kill ring.&lt;/p>
&lt;p>This issue is even more pronounced when you are sending text from other buffers to the chat.&lt;/p>
&lt;p>So, the solution I have put in place is to always write the last response to a register of your choice. I always think registers are an underused part of Emacs, I already have repurposed them for the multishot, so why not always make the last response available.&lt;/p>
&lt;p>For example, you want to proofread a sentence, you can mark the text, send to the chat using the custom menu to proofread then the response will be available in maybe register &amp;ldquo;a&amp;rdquo;. The chat buffer will be brought up if not already visible so you can validate the output, then pop back to your buffer, delete the paragraph and insert the register &amp;ldquo;a&amp;rdquo;?, maybe. I am going to put this in as I suspect no-one uses registers anyway and if they do, they can push the response writing register away using &lt;code>ollama-buddy-default-register&lt;/code>, I don&amp;rsquo;t think this will do any harm, and actually it is something I may starting using more often.&lt;/p>
&lt;p>As a side note, I also need to think about popping into the chat buffer with a buffer text push to the chat, should I do it?, not sure yet, still getting to grips with the whole workflow aspect, so will need a little more time to see what works.&lt;/p>
&lt;p>Also as a side note to this ramble, the general register prefix is annoyingly long &lt;code>C-x r i &amp;lt;register&amp;gt;&lt;/code> so I have rebound in my config to &lt;code>M-a&lt;/code>, as I never want to go back a sentence and also if I just write to the default &amp;ldquo;a&amp;rdquo; register then it feels ergonomically fast.&lt;/p>
&lt;h2 id="0-dot-9-dot-12">&lt;span class="timestamp-wrapper">&lt;span class="timestamp">&amp;lt;2025-03-25 Tue&amp;gt; &lt;/span>&lt;/span> &lt;strong>0.9.12&lt;/strong>&lt;/h2>
&lt;ul>
&lt;li>Added experimental Claude AI support!&lt;/li>
&lt;li>removed curl and replaced with url.el for online AI integration&lt;/li>
&lt;/ul>
&lt;p>A very similar implementation as for ChatGPT.&lt;/p>
&lt;p>To activate, set the following:&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>(require &lt;span style="color:#e6db74">&amp;#39;ollama-buddy-claude&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>(ollama-buddy-claude-api-key &lt;span style="color:#e6db74">&amp;#34;&amp;lt;extremely long key&amp;gt;&amp;#34;&lt;/span>)
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div></description></item><item><title>Ollama-Buddy 0.9.11: Experimental ChatGPT Integration, Customizable Streaming and Texinfo documentation</title><link>https://www.emacs.dyerdwelling.family/emacs/20250325093201-emacs--ollama-buddy-0-9-11-experimental-chatgpt-integration-customizable-ai-streaming-and-texinfo-documentation/</link><pubDate>Tue, 25 Mar 2025 10:00:00 +0000</pubDate><author>James Dyer</author><guid>https://www.emacs.dyerdwelling.family/emacs/20250325093201-emacs--ollama-buddy-0-9-11-experimental-chatgpt-integration-customizable-ai-streaming-and-texinfo-documentation/</guid><description>&lt;p>This week in &lt;a href="https://github.com/captainflasmr/ollama-buddy">ollama-buddy&lt;/a> updates, I have mostly been experimenting with ChatGPT integration! Yes, it is not a local LLM, so not ollama, hence entirely subverting the whole notion and fundamental principles of this package! This I know, and I don&amp;rsquo;t care; I&amp;rsquo;m having fun. I use ChatGPT and would rather use it in Emacs through the now-familiar &lt;code>ollama-buddy&lt;/code> framework, so why not? I&amp;rsquo;m also working on Claude integration too.&lt;/p>
&lt;p>My original principles of a no-config Emacs ollama integration still hold true, as by default, you will only see ollama models available. But with a little tweak to the configuration, with a require here and an API key there, you can now enable communication with an online AI. At the moment, I use Claude and ChatGPT, but if I can get Claude working, I might think about just adding in a basic template framework to easily slot in others. At the moment, there is a little too much internal ollama-buddy faffing to incorporate these external AIs into the main package, but I&amp;rsquo;m sure I can figure out a way to accommodate separate elisp external AIs.&lt;/p>
&lt;figure>&lt;img src="https://www.emacs.dyerdwelling.family/emacs/20250325093201-emacs--Ollama-Buddy-0-9-11-Experimental-ChatGPT-Integration-Customizable-AI-Streaming-and-Texinfo-documentation.jpg" width="100%">
&lt;/figure>
&lt;p>In other &lt;code>ollama-buddy&lt;/code> news, I have now added support for the &lt;code>stream&lt;/code> variable in the ollama API. By default, I had streaming on, and I guess why wouldn&amp;rsquo;t you? It is a chat, and you would want to see &amp;ldquo;typing&amp;rdquo; or tokens arriving as they come in?. But to support more of the API, you can toggle it on and off, so if you want, you can sit there and wait for the response to arrive in one go and maybe it can be less distracting (and possibly more efficient?).&lt;/p>
&lt;p>Just a note back on the topic of online AI offerings: to simplify those integrations, I just disabled streaming for the response to arrive in one shot. Mainly, I just couldn&amp;rsquo;t figure out the ChatGPT streaming, and for an external offering, I wasn&amp;rsquo;t quite willing to spend more time on it, and due to the speed of these online behemoths, do you really need to see each token come in as it arrives?&lt;/p>
&lt;p>Oh, there is something else too, something I have been itching to do for a while now, and that is to write a Texinfo document so a manual can be viewed in Emacs. Of course, this being an AI-based package, I fed in my &lt;code>ollama-buddy&lt;/code> files and got Claude to generate one for me (I have a baby and haven&amp;rsquo;t the time!). Reading through it, I think it turned out pretty well :) It hasn&amp;rsquo;t been made automatically available on MELPA yet, as I need to tweak the recipe, but you can install it for yourself.&lt;/p>
&lt;p>Anyways, see below for the changelog gubbins:&lt;/p>
&lt;h2 id="0-dot-9-dot-11">&lt;span class="timestamp-wrapper">&lt;span class="timestamp">&amp;lt;2025-03-24 Mon&amp;gt; &lt;/span>&lt;/span> &lt;strong>0.9.11&lt;/strong>&lt;/h2>
&lt;p>Added the ability to toggle streaming on and off&lt;/p>
&lt;ul>
&lt;li>Added customization option to enable/disable streaming mode&lt;/li>
&lt;li>Implemented toggle function with keybindings (C-c x) and transient menu option&lt;/li>
&lt;li>Added streaming status indicator in the modeline&lt;/li>
&lt;/ul>
&lt;p>The latest update introduces the ability to toggle between two response modes:&lt;/p>
&lt;ul>
&lt;li>&lt;strong>Streaming mode (default)&lt;/strong>: Responses appear token by token in real-time, giving you immediate feedback as the AI generates content.&lt;/li>
&lt;li>&lt;strong>Non-streaming mode&lt;/strong>: Responses only appear after they&amp;rsquo;re fully generated, showing a &amp;ldquo;Loading response&amp;hellip;&amp;rdquo; placeholder in the meantime.&lt;/li>
&lt;/ul>
&lt;p>While watching AI responses stream in real-time is often helpful, there are situations where you might prefer to see the complete response at once:&lt;/p>
&lt;ul>
&lt;li>When working on large displays where the cursor jumping around during streaming is distracting&lt;/li>
&lt;li>When you want to focus on your work without the distraction of incoming tokens until the full response is ready&lt;/li>
&lt;/ul>
&lt;p>The streaming toggle can be accessed in several ways:&lt;/p>
&lt;ol>
&lt;li>Use the keyboard shortcut &lt;code>C-c x&lt;/code>&lt;/li>
&lt;li>Press &lt;code>x&lt;/code> in the transient menu&lt;/li>
&lt;li>Set the default behaviour through customization:
&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-streaming-enabled &lt;span style="color:#66d9ef">nil&lt;/span>) &lt;span style="color:#75715e">;; Disable streaming by default&lt;/span>
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;/li>
&lt;/ol>
&lt;p>The current streaming status is visible in the modeline indicator, where an &amp;ldquo;X&amp;rdquo; appears when streaming is disabled.&lt;/p>
&lt;h2 id="0-dot-9-dot-10">&lt;span class="timestamp-wrapper">&lt;span class="timestamp">&amp;lt;2025-03-22 Sat&amp;gt; &lt;/span>&lt;/span> &lt;strong>0.9.10&lt;/strong>&lt;/h2>
&lt;p>Added experimental OpenAI support!&lt;/p>
&lt;p>Yes, that&amp;rsquo;s right, I said I never would do it, and of course, this package is still very much &lt;code>ollama&lt;/code>-centric, but I thought I would just sneak in some rudimentary ChatGPT support, just for fun!&lt;/p>
&lt;p>It is a very simple implementation, I haven&amp;rsquo;t managed to get streaming working, so Emacs will just show &amp;ldquo;Loading Response&amp;hellip;&amp;rdquo; as it waits for the response to arrive. It is asynchronous, however, so you can go off on your Emacs day while it loads (although being ChatGPT, you would think the response would be quite fast!)&lt;/p>
&lt;p>By default, OpenAI/ChatGPT will not be enabled, so anyone wanting to use just a local LLM through &lt;code>ollama&lt;/code> can continue as before. However, you can now sneak in some experimental ChatGPT support by adding the following to your Emacs config as part of the &lt;code>ollama-buddy&lt;/code> set up.&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>(require &lt;span style="color:#e6db74">&amp;#39;ollama-buddy-openai&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>(setq ollama-buddy-openai-api-key &lt;span style="color:#e6db74">&amp;#34;&amp;lt;big long key&amp;gt;&amp;#34;&lt;/span>)
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>and you can set the default model to ChatGPT too!&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-default-model &lt;span style="color:#e6db74">&amp;#34;GPT gpt-4o&amp;#34;&lt;/span>)
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>With this enabled, chat will present a list of ChatGPT models to choose from. The custom menu should also now work with chat, so from anywhere in Emacs, you can push predefined prompts to the &lt;code>ollama&lt;/code> buddy chat buffer now supporting ChatGPT.&lt;/p>
&lt;p>There is more integration required to fully incorporate ChatGPT into the &lt;code>ollama&lt;/code> buddy system, like token rates and history, etc. But not bad for a first effort, methinks!&lt;/p>
&lt;p>Here is my current config, now mixing ChatGPT with &lt;code>ollama&lt;/code> models:&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-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> (ollama-buddy-openai-api-key &lt;span style="color:#e6db74">&amp;#34;&amp;lt;very long key&amp;gt;&amp;#34;&lt;/span>)
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> (ollama-buddy-default-model &lt;span style="color:#e6db74">&amp;#34;GPT gpt-4o&amp;#34;&lt;/span>)
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> :config
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> (require &lt;span style="color:#e6db74">&amp;#39;ollama-buddy-openai&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> (ollama-buddy-update-menu-entry
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#e6db74">&amp;#39;refactor-code&lt;/span> :model &lt;span style="color:#e6db74">&amp;#34;qwen2.5-coder:7b&amp;#34;&lt;/span>)
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> (ollama-buddy-update-menu-entry
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#e6db74">&amp;#39;git-commit&lt;/span> :model &lt;span style="color:#e6db74">&amp;#34;qwen2.5-coder:3b&amp;#34;&lt;/span>)
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> (ollama-buddy-update-menu-entry
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#e6db74">&amp;#39;describe-code&lt;/span> :model &lt;span style="color:#e6db74">&amp;#34;qwen2.5-coder:3b&amp;#34;&lt;/span>)
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> (ollama-buddy-update-menu-entry
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#e6db74">&amp;#39;dictionary-lookup&lt;/span> :model &lt;span style="color:#e6db74">&amp;#34;llama3.2:3b&amp;#34;&lt;/span>)
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> (ollama-buddy-update-menu-entry
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#e6db74">&amp;#39;synonym&lt;/span> :model &lt;span style="color:#e6db74">&amp;#34;llama3.2:3b&amp;#34;&lt;/span>)
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> (ollama-buddy-update-menu-entry
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#e6db74">&amp;#39;proofread&lt;/span> :model &lt;span style="color:#e6db74">&amp;#34;GPT gpt-4o&amp;#34;&lt;/span>)
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> (ollama-buddy-update-menu-entry
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#e6db74">&amp;#39;custom-prompt&lt;/span> :model &lt;span style="color:#e6db74">&amp;#34;deepseek-r1:7b&amp;#34;&lt;/span>))
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;h2 id="0-dot-9-dot-9">&lt;span class="timestamp-wrapper">&lt;span class="timestamp">&amp;lt;2025-03-22 Sat&amp;gt; &lt;/span>&lt;/span> &lt;strong>0.9.9&lt;/strong>&lt;/h2>
&lt;p>Added texinfo documentation for future automatic installation through MELPA and created an Emacs manual.&lt;/p>
&lt;p>If you want to see what the manual would look like, just download the docs directory from github, cd into it, and run:&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-bash" data-lang="bash">&lt;span style="display:flex;">&lt;span>make
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>sudo make install-docs
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>Then calling up &lt;code>info&lt;/code> &lt;code>C-h i&lt;/code> and ollama buddy will be present in the Emacs menu, or just select &lt;code>m&lt;/code> and search for &lt;code>Ollama Buddy&lt;/code>&lt;/p>
&lt;p>For those interested in the manual, I have converted it into html format, which is accessible here:&lt;/p>
&lt;p>&lt;a href="https://www.emacs.dyerdwelling.family/tags/ollama-buddy/">/tags/ollama-buddy/&lt;/a>&lt;/p>
&lt;p>It has been converted using the following command:&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-bash" data-lang="bash">&lt;span style="display:flex;">&lt;span>makeinfo --html --no-split ollama-buddy.texi -o ollama-buddy.html
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>pandoc -f html -t org -o ollama-buddy.org ollama-buddy.html
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;h2 id="0-dot-9-dot-9">&lt;span class="timestamp-wrapper">&lt;span class="timestamp">&amp;lt;2025-03-20 Thu&amp;gt; &lt;/span>&lt;/span> &lt;strong>0.9.9&lt;/strong>&lt;/h2>
&lt;p>Intro message with model management options (select, pull, delete) and option for recommended models to pull&lt;/p>
&lt;ul>
&lt;li>Enhance model management and selection features&lt;/li>
&lt;li>Display models available for download but not yet pulled&lt;/li>
&lt;/ul></description></item><item><title>Ollama-Buddy 0.9.8: Transient Menu, Model Managing, GGUF Import, fabric Prompts and History Editing</title><link>https://www.emacs.dyerdwelling.family/emacs/20250319145359-emacs--ollama-buddy-0-9-8-transient-menu-model-managing-gguf-import-fabric-prompts/</link><pubDate>Wed, 19 Mar 2025 16:08:00 +0000</pubDate><author>James Dyer</author><guid>https://www.emacs.dyerdwelling.family/emacs/20250319145359-emacs--ollama-buddy-0-9-8-transient-menu-model-managing-gguf-import-fabric-prompts/</guid><description>&lt;p>This week in &lt;a href="https://github.com/captainflasmr/ollama-buddy">ollama-buddy&lt;/a> updates, I have been continuing on the busy bee side of things.&lt;/p>
&lt;p>The headlines are :&lt;/p>
&lt;ul>
&lt;li>Transient menu - yes, I know I said I would never do it, but, well I did and as it turns out I kinda quite like it and works especially well when setting parameters.&lt;/li>
&lt;li>Support for &lt;code>fabric&lt;/code> prompts presets - mainly as I thought generally user curated prompts was a pretty cool idea, and now I have system prompts implemented it seemed like a perfect fit. All I needed to do was to pull the patterns directory and then parse accordingly, of course Emacs is good at this.&lt;/li>
&lt;li>GGUF import - I don&amp;rsquo;t always pull from ollama&amp;rsquo;s command line, sometimes I download a GGUF file, it is a bit of a process to import to ollama, create a model file, run a command, e.t.c, but now you can import from within &lt;code>dired&lt;/code>!&lt;/li>
&lt;li>More support for the &lt;code>ollama&lt;/code> API - includes model management, so pulling, stopping, deleting and more!&lt;/li>
&lt;li>Conversation history editing - as I store the history in a hash table, I can easily just display an alist, and editing can leverage the &lt;code>sexp&lt;/code> usual keybindings and then load back in to the variable.&lt;/li>
&lt;li>Parameter profiles - When implementing the transient menu I thought it might be fun to try parameter profiles where a set of parameters can be applied in a block for each preset.&lt;/li>
&lt;/ul>
&lt;figure>&lt;img src="https://www.emacs.dyerdwelling.family/emacs/20250319145359-emacs--Ollama-Buddy-0-9-8-Transient-Menu-Model-managing-GGUF-import-fabric-prompts.jpg" width="100%">
&lt;/figure>
&lt;p>And now for the detail, version by version&amp;hellip;&lt;/p>
&lt;h2 id="0-dot-9-dot-8">&lt;span class="timestamp-wrapper">&lt;span class="timestamp">&amp;lt;2025-03-19 Wed&amp;gt; &lt;/span>&lt;/span> &lt;strong>0.9.8&lt;/strong>&lt;/h2>
&lt;p>Added model management interface to pull and delete models&lt;/p>
&lt;ul>
&lt;li>Introduced `ollama-buddy-manage-models` to list and manage models.&lt;/li>
&lt;li>Added actions for selecting, pulling, stopping, and deleting models.&lt;/li>
&lt;/ul>
&lt;p>You can now manage your Ollama models directly within Emacs with &lt;code>ollama-buddy&lt;/code>&lt;/p>
&lt;p>With this update, you can now:&lt;/p>
&lt;ul>
&lt;li>
&lt;p>&lt;strong>Browse Available Models&lt;/strong> – See all installed models at a glance.&lt;/p>
&lt;/li>
&lt;li>
&lt;p>&lt;strong>Select Models Easily&lt;/strong> – Set your active AI model with a single click.&lt;/p>
&lt;/li>
&lt;li>
&lt;p>&lt;strong>Pull Models from Ollama Hub&lt;/strong> – Download new models or update existing ones.&lt;/p>
&lt;/li>
&lt;li>
&lt;p>&lt;strong>Stop Running Models&lt;/strong> – Halt background processes when necessary.&lt;/p>
&lt;/li>
&lt;li>
&lt;p>&lt;strong>Delete Unused Models&lt;/strong> – Clean up your workspace with ease.&lt;/p>
&lt;/li>
&lt;li>
&lt;p>&lt;strong>Open the Model Management Interface&lt;/strong>
Press &lt;strong>&lt;code>C-c W&lt;/code>&lt;/strong> to launch the new &lt;strong>Model Management&lt;/strong> buffer or through the transient menu.&lt;/p>
&lt;/li>
&lt;li>
&lt;p>&lt;strong>Manage Your Models&lt;/strong>&lt;/p>
&lt;ul>
&lt;li>Click on a model to &lt;strong>select&lt;/strong> it.&lt;/li>
&lt;li>Use &lt;strong>&amp;ldquo;Pull&amp;rdquo;&lt;/strong> to fetch models from the Ollama Hub.&lt;/li>
&lt;li>Click &lt;strong>&amp;ldquo;Stop&amp;rdquo;&lt;/strong> to halt active models.&lt;/li>
&lt;li>Use &lt;strong>&amp;ldquo;Delete&amp;rdquo;&lt;/strong> to remove unwanted models.&lt;/li>
&lt;/ul>
&lt;/li>
&lt;li>
&lt;p>&lt;strong>Perform Quick Actions&lt;/strong>&lt;/p>
&lt;ul>
&lt;li>&lt;strong>&lt;code>g&lt;/code>&lt;/strong> → Refresh the model list.&lt;/li>
&lt;li>&lt;strong>&lt;code>i&lt;/code>&lt;/strong> → Import a &lt;strong>GGUF model file&lt;/strong>.&lt;/li>
&lt;li>&lt;strong>&lt;code>p&lt;/code>&lt;/strong> → Pull a new model from the &lt;strong>Ollama Hub&lt;/strong>.&lt;/li>
&lt;/ul>
&lt;/li>
&lt;/ul>
&lt;p>When you open the management interface, you get a structured list like this:&lt;/p>
&lt;pre tabindex="0">&lt;code class="language-nil" data-lang="nil">Ollama Models Management
=======================
Current Model: mistral:7b
Default Model: mistral:7b
Available Models:
[ ] llama3.2:1b Info Pull Delete
[ ] starcoder2:3b Info Pull Delete
[ ] codellama:7b Info Pull Delete
[ ] phi3:3.8b Info Pull Delete
[x] llama3.2:3b Info Pull Delete Stop
Actions:
[Import GGUF File] [Refresh List] [Pull Model from Hub]
&lt;/code>&lt;/pre>&lt;p>Previously, managing Ollama models required manually running shell commands. With this update, you can now &lt;strong>do it all from Emacs&lt;/strong>, keeping your workflow smooth and efficient!&lt;/p>
&lt;h2 id="0-dot-9-dot-7">&lt;span class="timestamp-wrapper">&lt;span class="timestamp">&amp;lt;2025-03-19 Wed&amp;gt; &lt;/span>&lt;/span> &lt;strong>0.9.7&lt;/strong>&lt;/h2>
&lt;ul>
&lt;li>Added GGUF file import and Dired integration&lt;/li>
&lt;/ul>
&lt;p>Import GGUF Models into Ollama from &lt;code>dired&lt;/code> with the new &lt;code>ollama-buddy-import-gguf-file&lt;/code> function. In &lt;code>dired&lt;/code> just navigate to your file and press &lt;code>C-c i&lt;/code> or &lt;code>M-x ollama-buddy-import-gguf-file&lt;/code> to start the import process. This eliminates the need to manually input file paths, making the workflow smoother and faster.&lt;/p>
&lt;p>The model will then be immediately available in the &lt;code>ollama-buddy&lt;/code> chat interface.&lt;/p>
&lt;h2 id="0-dot-9-dot-6">&lt;span class="timestamp-wrapper">&lt;span class="timestamp">&amp;lt;2025-03-18 Tue&amp;gt; &lt;/span>&lt;/span> &lt;strong>0.9.6&lt;/strong>&lt;/h2>
&lt;ul>
&lt;li>Added a transient menu containing all commands currently presented in the chat buffer&lt;/li>
&lt;li>Added fabric prompting support, see &lt;a href="https://github.com/danielmiessler/fabric">https://github.com/danielmiessler/fabric&lt;/a>&lt;/li>
&lt;li>Moved the presets to the top level so they will be present in the package folder&lt;/li>
&lt;/ul>
&lt;p>Ollama Buddy now includes a transient-based menu system to improve usability and streamline interactions. Yes, I originally stated that I would never do it, but I think it compliments my crafted simple textual menu and the fact that I have now defaulted the main chat interface to a simple menu.&lt;/p>
&lt;p>This can give the user more options for configuration, they can use the chat in advanced mode where the keybindings are presented in situ, or a more minimal basic setup where the transient menu can be activated. For my use-package definition I current have the following set up, with the two styles of menus sitting alongside each other :&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> :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-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)
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>The new menu provides an organized interface for accessing the assistant’s core functions, including chat, model management, roles, and Fabric patterns. This post provides an overview of the features available in the Ollama Buddy transient menus.&lt;/p>
&lt;p>Yes that&amp;rsquo;s right also &lt;code>fabric&lt;/code> patterns!, I have decided to add in auto syncing of the patterns directory in &lt;a href="https://github.com/danielmiessler/fabric">https://github.com/danielmiessler/fabric&lt;/a>&lt;/p>
&lt;p>Simply I pull the patterns directory which contain prompt guidance for a range of different topics and then push them through a completing read to set the &lt;code>ollama-buddy&lt;/code> system prompt, so a special set of curated prompts can now be applied right in the &lt;code>ollama-buddy&lt;/code> chat!&lt;/p>
&lt;p>Anyways, here is a description of the transient menu system.&lt;/p>
&lt;h3 id="what-is-the-transient-menu">What is the Transient Menu?&lt;/h3>
&lt;p>The transient menu in Ollama Buddy leverages Emacs&amp;rsquo; &lt;code>transient.el&lt;/code> package (the same technology behind Magit&amp;rsquo;s popular interface) to create a hierarchical, discoverable menu system. This approach transforms the user experience from memorizing numerous keybindings to navigating through logical groups of commands with clear descriptions.&lt;/p>
&lt;h3 id="accessing-the-menu">Accessing the Menu&lt;/h3>
&lt;p>The main transient menu can be accessed with the keybinding &lt;code>C-c O&lt;/code> when in an Ollama Buddy chat buffer. You can also call it via &lt;code>M-x ollama-buddy-transient-menu&lt;/code> from anywhere in Emacs.&lt;/p>
&lt;h3 id="what-the-menu-looks-like">What the Menu Looks Like&lt;/h3>
&lt;p>When called, the main transient menu appears at the bottom of your Emacs frame, organized into logical sections with descriptive prefixes. Here&amp;rsquo;s what you&amp;rsquo;ll see:&lt;/p>
&lt;pre tabindex="0">&lt;code class="language-nil" data-lang="nil">|o(Y)o| Ollama Buddy
[Chat] [Prompts] [Model] [Roles &amp;amp; Patterns]
o Open Chat l Send Region m Switch Model R Switch Roles
O Commands s Set System Prompt v View Model Status E Create New Role
RET Send Prompt C-s Show System i Show Model Info D Open Roles Directory
h Help/Menu r Reset System M Multishot f Fabric Patterns
k Kill/Cancel b Ollama Buddy Menu
[Display Options] [History] [Sessions] [Parameters]
A Toggle Interface Level H Toggle History N New Session P Edit Parameter
B Toggle Debug Mode X Clear History L Load Session G Display Parameters
T Toggle Token Display V Display History S Save Session I Parameter Help
U Display Token Stats J Edit History Q List Sessions K Reset Parameters
C-o Toggle Markdown-&amp;gt;Org Z Delete Session F Toggle Params in Header
c Toggle Model Colors p Parameter Profiles
g Token Usage Graph
&lt;/code>&lt;/pre>&lt;p>This visual layout makes it easy to discover and access the full range of Ollama Buddy&amp;rsquo;s functionality. Let&amp;rsquo;s explore each section in detail.&lt;/p>
&lt;h3 id="menu-sections-explained">Menu Sections Explained&lt;/h3>
&lt;h4 id="chat-section">Chat Section&lt;/h4>
&lt;p>This section contains the core interaction commands:&lt;/p>
&lt;ul>
&lt;li>&lt;strong>Open Chat (o)&lt;/strong>: Opens the Ollama Buddy chat buffer&lt;/li>
&lt;li>&lt;strong>Commands (O)&lt;/strong>: Opens a submenu with specialized commands&lt;/li>
&lt;li>&lt;strong>Send Prompt (RET)&lt;/strong>: Sends the current prompt to the model&lt;/li>
&lt;li>&lt;strong>Help/Menu (h)&lt;/strong>: Displays the help assistant with usage tips&lt;/li>
&lt;li>&lt;strong>Kill/Cancel Request (k)&lt;/strong>: Cancels the current ongoing request&lt;/li>
&lt;/ul>
&lt;h4 id="prompts-section">Prompts Section&lt;/h4>
&lt;p>These commands help you manage and send prompts:&lt;/p>
&lt;ul>
&lt;li>&lt;strong>Send Region (l)&lt;/strong>: Sends the selected region as a prompt&lt;/li>
&lt;li>&lt;strong>Set System Prompt (s)&lt;/strong>: Sets the current prompt as a system prompt&lt;/li>
&lt;li>&lt;strong>Show System Prompt (C-s)&lt;/strong>: Displays the current system prompt&lt;/li>
&lt;li>&lt;strong>Reset System Prompt (r)&lt;/strong>: Resets the system prompt to default&lt;/li>
&lt;li>&lt;strong>Ollama Buddy Menu (b)&lt;/strong>: Opens the classic menu interface&lt;/li>
&lt;/ul>
&lt;h4 id="model-section">Model Section&lt;/h4>
&lt;p>Commands for model management:&lt;/p>
&lt;ul>
&lt;li>&lt;strong>Switch Model (m)&lt;/strong>: Changes the active LLM&lt;/li>
&lt;li>&lt;strong>View Model Status (v)&lt;/strong>: Shows status of all available models&lt;/li>
&lt;li>&lt;strong>Show Model Info (i)&lt;/strong>: Displays detailed information about the current model&lt;/li>
&lt;li>&lt;strong>Multishot (M)&lt;/strong>: Sends the same prompt to multiple models&lt;/li>
&lt;/ul>
&lt;h4 id="roles-and-patterns-section">Roles &amp;amp; Patterns Section&lt;/h4>
&lt;p>These commands help manage roles and use fabric patterns:&lt;/p>
&lt;ul>
&lt;li>&lt;strong>Switch Roles (R)&lt;/strong>: Switch to a different predefined role&lt;/li>
&lt;li>&lt;strong>Create New Role (E)&lt;/strong>: Create a new role interactively&lt;/li>
&lt;li>&lt;strong>Open Roles Directory (D)&lt;/strong>: Open the directory containing role definitions&lt;/li>
&lt;li>&lt;strong>Fabric Patterns (f)&lt;/strong>: Opens the submenu for Fabric patterns&lt;/li>
&lt;/ul>
&lt;p>When you select the Fabric Patterns option, you&amp;rsquo;ll see a submenu like this:&lt;/p>
&lt;pre tabindex="0">&lt;code class="language-nil" data-lang="nil">Fabric Patterns (42 available, last synced: 2025-03-18 14:30)
[Actions] [Sync] [Categories] [Navigation]
s Send with Pattern S Sync Latest u Universal Patterns q Back to Main Menu
p Set as System P Populate Cache c Code Patterns
l List All Patterns I Initial Setup w Writing Patterns
v View Pattern Details a Analysis Patterns
&lt;/code>&lt;/pre>&lt;h4 id="display-options-section">Display Options Section&lt;/h4>
&lt;p>Commands to customize the display:&lt;/p>
&lt;ul>
&lt;li>&lt;strong>Toggle Interface Level (A)&lt;/strong>: Switch between basic and advanced interfaces&lt;/li>
&lt;li>&lt;strong>Toggle Debug Mode (B)&lt;/strong>: Enable/disable JSON debug information&lt;/li>
&lt;li>&lt;strong>Toggle Token Display (T)&lt;/strong>: Show/hide token usage statistics&lt;/li>
&lt;li>&lt;strong>Display Token Stats (U)&lt;/strong>: Show detailed token usage information&lt;/li>
&lt;li>&lt;strong>Toggle Markdown-&amp;gt;Org (C-o)&lt;/strong>: Enable/disable conversion to Org format&lt;/li>
&lt;li>&lt;strong>Toggle Model Colors (c)&lt;/strong>: Enable/disable model-specific colors&lt;/li>
&lt;li>&lt;strong>Token Usage Graph (g)&lt;/strong>: Display a visual graph of token usage&lt;/li>
&lt;/ul>
&lt;h4 id="history-section">History Section&lt;/h4>
&lt;p>Commands for managing conversation history:&lt;/p>
&lt;ul>
&lt;li>&lt;strong>Toggle History (H)&lt;/strong>: Enable/disable conversation history&lt;/li>
&lt;li>&lt;strong>Clear History (X)&lt;/strong>: Clear the current history&lt;/li>
&lt;li>&lt;strong>Display History (V)&lt;/strong>: Show the conversation history&lt;/li>
&lt;li>&lt;strong>Edit History (J)&lt;/strong>: Edit the history in a buffer&lt;/li>
&lt;/ul>
&lt;h4 id="sessions-section">Sessions Section&lt;/h4>
&lt;p>Commands for session management:&lt;/p>
&lt;ul>
&lt;li>&lt;strong>New Session (N)&lt;/strong>: Start a new session&lt;/li>
&lt;li>&lt;strong>Load Session (L)&lt;/strong>: Load a saved session&lt;/li>
&lt;li>&lt;strong>Save Session (S)&lt;/strong>: Save the current session&lt;/li>
&lt;li>&lt;strong>List Sessions (Q)&lt;/strong>: List all available sessions&lt;/li>
&lt;li>&lt;strong>Delete Session (Z)&lt;/strong>: Delete a saved session&lt;/li>
&lt;/ul>
&lt;h4 id="parameters-section">Parameters Section&lt;/h4>
&lt;p>Commands for managing model parameters:&lt;/p>
&lt;ul>
&lt;li>&lt;strong>Edit Parameter (P)&lt;/strong>: Opens a submenu to edit specific parameters&lt;/li>
&lt;li>&lt;strong>Display Parameters (G)&lt;/strong>: Show current parameter settings&lt;/li>
&lt;li>&lt;strong>Parameter Help (I)&lt;/strong>: Display help information about parameters&lt;/li>
&lt;li>&lt;strong>Reset Parameters (K)&lt;/strong>: Reset parameters to defaults&lt;/li>
&lt;li>&lt;strong>Toggle Params in Header (F)&lt;/strong>: Show/hide parameters in header&lt;/li>
&lt;li>&lt;strong>Parameter Profiles (p)&lt;/strong>: Opens the parameter profiles submenu&lt;/li>
&lt;/ul>
&lt;p>When you select the Edit Parameter option, you&amp;rsquo;ll see a comprehensive submenu of all available parameters:&lt;/p>
&lt;pre tabindex="0">&lt;code class="language-nil" data-lang="nil">Parameters
[Generation] [More Generation] [Mirostat]
t Temperature f Frequency Penalty M Mirostat Mode
k Top K s Presence Penalty T Mirostat Tau
p Top P n Repeat Last N E Mirostat Eta
m Min P x Stop Sequences
y Typical P l Penalize Newline
r Repeat Penalty
[Resource] [More Resource] [Memory]
c Num Ctx P Num Predict m Use MMAP
b Num Batch S Seed L Use MLOCK
g Num GPU N NUMA C Num Thread
G Main GPU V Low VRAM
K Num Keep o Vocab Only
[Profiles] [Actions]
d Default Profile D Display All
a Creative Profile R Reset All
e Precise Profile H Help
A All Profiles F Toggle Display in Header
q Back to Main Menu
&lt;/code>&lt;/pre>&lt;h3 id="parameter-profiles">Parameter Profiles&lt;/h3>
&lt;p>Ollama Buddy includes predefined parameter profiles that can be applied with a single command. When you select &amp;ldquo;Parameter Profiles&amp;rdquo; from the main menu, you&amp;rsquo;ll see:&lt;/p>
&lt;pre tabindex="0">&lt;code class="language-nil" data-lang="nil">Parameter Profiles
Current modified parameters: temperature, top_k, top_p
[Available Profiles]
d Default
c Creative
p Precise
[Actions]
q Back to Main Menu
&lt;/code>&lt;/pre>&lt;h3 id="commands-submenu">Commands Submenu&lt;/h3>
&lt;p>The Commands submenu provides quick access to specialized operations:&lt;/p>
&lt;pre tabindex="0">&lt;code class="language-nil" data-lang="nil">Ollama Buddy Commands
[Code Operations] [Language Operations] [Pattern-based] [Custom]
r Refactor Code l Dictionary Lookup f Fabric Patterns C Custom Prompt
d Describe Code s Synonym Lookup u Universal Patterns m Minibuffer Prompt
g Git Commit Message p Proofread Text c Code Patterns
[Actions]
q Back to Main Menu
&lt;/code>&lt;/pre>&lt;h3 id="direct-keybindings">Direct Keybindings&lt;/h3>
&lt;p>For experienced users who prefer direct keybindings, all transient menu functions can also be accessed through keybindings with the prefix of your choice (or &lt;code>C-c O&lt;/code> when in the chat minibuffer) followed by the key shown in the menu. For example:&lt;/p>
&lt;ul>
&lt;li>&lt;code>C-c O s&lt;/code> - Set system prompt&lt;/li>
&lt;li>&lt;code>C-c O m&lt;/code> - Switch model&lt;/li>
&lt;li>&lt;code>C-c O P&lt;/code> - Open parameter menu&lt;/li>
&lt;/ul>
&lt;h3 id="customization">Customization&lt;/h3>
&lt;p>The transient menu can be customized by modifying the &lt;code>transient-define-prefix&lt;/code> definitions in the package. You can add, remove, or rearrange commands to suit your workflow.&lt;/p>
&lt;h2 id="0-dot-9-dot-5">&lt;span class="timestamp-wrapper">&lt;span class="timestamp">&amp;lt;2025-03-17 Mon&amp;gt; &lt;/span>&lt;/span> &lt;strong>0.9.5&lt;/strong>&lt;/h2>
&lt;p>Added conversation history editing&lt;/p>
&lt;ul>
&lt;li>Added functions to edit conversation history (&lt;code>ollama-buddy-history-edit&lt;/code>, &lt;code>ollama-buddy-history-save&lt;/code>, etc.).&lt;/li>
&lt;li>Updated &lt;code>ollama-buddy-display-history&lt;/code> to support history editing.&lt;/li>
&lt;li>Added keybinding &lt;code>C-c E&lt;/code> for history editing.&lt;/li>
&lt;/ul>
&lt;p>Introducing conversation history editing!!&lt;/p>
&lt;p>&lt;strong>Key Features&lt;/strong>&lt;/p>
&lt;p>Now, you can directly modify past interactions, making it easier to refine and manage your &lt;code>ollama-buddy&lt;/code> chat history.&lt;/p>
&lt;p>Previously, conversation history was static, you could view it but not change it. With this update, you can now:&lt;/p>
&lt;ul>
&lt;li>Edit conversation history directly in a buffer.&lt;/li>
&lt;li>Modify past interactions for accuracy or clarity.&lt;/li>
&lt;li>Save or discard changes with intuitive keybindings (&lt;code>C-c C-c&lt;/code> to save, &lt;code>C-c C-k&lt;/code> to cancel).&lt;/li>
&lt;li>Edit the history of all models or a specific one.&lt;/li>
&lt;/ul>
&lt;p>Simply use the new command &lt;strong>&lt;code>C-c E&lt;/code>&lt;/strong> to open the conversation history editor. This will display your past interactions in an editable format (alist). Once you’ve made your changes, press &lt;code>C-c C-c&lt;/code> to save them back into Ollama Buddy’s memory.&lt;/p>
&lt;p>and with a universal argument you can leverage &lt;code>C-c E&lt;/code> to edit an individual model.&lt;/p>
&lt;h2 id="0-dot-9-dot-1">&lt;span class="timestamp-wrapper">&lt;span class="timestamp">&amp;lt;2025-03-17 Mon&amp;gt; &lt;/span>&lt;/span> &lt;strong>0.9.1&lt;/strong>&lt;/h2>
&lt;p>New simple basic interface is available.&lt;/p>
&lt;p>As this package becomes more advanced, I&amp;rsquo;ve been adding more to the intro message, making it increasingly cluttered. This could be off-putting for users who just want a simple interface to a local LLM via Ollama.&lt;/p>
&lt;p>Therefore I have decided to add a customization option to simplify the menu.&lt;/p>
&lt;p>Note: all functionality will still be available through keybindings, so just like Emacs then! :)&lt;/p>
&lt;p>Note: some could see this initially as a breaking change as the intro message will look different, but rest assured all the functionality is still there (just to re-emphasize), so if you have been using it before and want the original functionality/intro message, just set :&lt;/p>
&lt;pre tabindex="0">&lt;code class="language-nil" data-lang="nil">(setq ollama-buddy-interface-level &amp;#39;advanced)
&lt;/code>&lt;/pre>&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>(defcustom ollama-buddy-interface-level &lt;span style="color:#e6db74">&amp;#39;basic&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#e6db74">&amp;#34;Level of interface complexity to display.
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#e6db74">&amp;#39;basic shows minimal commands for new users.
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#e6db74">&amp;#39;advanced shows all available commands and features.&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> :type &lt;span style="color:#f92672">&amp;#39;&lt;/span>(choice (const :tag &lt;span style="color:#e6db74">&amp;#34;Basic (for beginners)&amp;#34;&lt;/span> basic)
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> (const :tag &lt;span style="color:#e6db74">&amp;#34;Advanced (full features)&amp;#34;&lt;/span> advanced))
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> :group &lt;span style="color:#e6db74">&amp;#39;ollama-buddy&lt;/span>)
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>By default the menu will be set to Basic, unless obviously set explictly in an init file. Here is an example of the basic menu:&lt;/p>
&lt;pre tabindex="0">&lt;code class="language-nil" data-lang="nil">*** Welcome to OLLAMA BUDDY
#+begin_example
___ _ _ n _ n ___ _ _ _ _
| | | |__._|o(Y)o|__._| . |_ _ _| |_| | | |
| | | | | . | | . | . | | | . | . |__ |
|___|_|_|__/_|_|_|_|__/_|___|___|___|___|___|
#+end_example
**** Available Models
(a) another:latest (d) jamesio:latest
(b) funnyname2:latest (e) tinyllama:latest
(c) funnyname:latest (f) llama:latest
**** Quick Tips
- Ask me anything! C-c C-c
- Change model C-c m
- Cancel request C-c k
- Browse prompt history M-p/M-n
- Advanced interface (show all tips) C-c A
&lt;/code>&lt;/pre>&lt;p>and of the more advanced version&lt;/p>
&lt;pre tabindex="0">&lt;code class="language-nil" data-lang="nil">*** Welcome to OLLAMA BUDDY
#+begin_example
___ _ _ n _ n ___ _ _ _ _
| | | |__._|o(Y)o|__._| . |_ _ _| |_| | | |
| | | | | . | | . | . | | | . | . |__ |
|___|_|_|__/_|_|_|_|__/_|___|___|___|___|___|
#+end_example
**** Available Models
(a) another:latest (d) jamesio:latest
(b) funnyname2:latest (e) tinyllama:latest
(c) funnyname:latest (f) llama:latest
**** Quick Tips
- Ask me anything! C-c C-c
- Show Help/Token-usage/System-prompt C-c h/U/C-s
- Model Change/Info/Cancel C-c m/i/k
- Prompt history M-p/M-n
- Session New/Load/Save/List/Delete C-c N/L/S/Y/W
- History Toggle/Clear/Show C-c H/X/V
- Prompt to multiple models C-c l
- Parameter Edit/Show/Help/Reset C-c P/G/I/K
- System Prompt/Clear C-u/+C-u +C-u C-c C-c
- Toggle JSON/Token/Params/Format C-c D/T/Z/C-o
- Basic interface (simpler display) C-c A
- In another buffer? M-x ollama-buddy-menu
&lt;/code>&lt;/pre>&lt;h2 id="0-dot-9-dot-0">&lt;span class="timestamp-wrapper">&lt;span class="timestamp">&amp;lt;2025-03-17 Mon&amp;gt; &lt;/span>&lt;/span> &lt;strong>0.9.0&lt;/strong>&lt;/h2>
&lt;p>Added command-specific parameter customization&lt;/p>
&lt;ul>
&lt;li>Added :parameters property to command definitions for granular control&lt;/li>
&lt;li>Implemented functions to apply and restore parameter settings&lt;/li>
&lt;li>Added example configuration to refactor-code command&lt;/li>
&lt;/ul>
&lt;p>With the latest update, you can now define specific parameter sets for each command in the menu, enabling you to optimize each AI interaction for its particular use case.&lt;/p>
&lt;p>Different AI tasks benefit from different parameter settings. When refactoring code, you might want a more deterministic, precise response (lower temperature, higher repetition penalty), but when generating creative content, you might prefer more variation and randomness (higher temperature, lower repetition penalty). Previously, you had to manually adjust these parameters each time you switched between different types of tasks.&lt;/p>
&lt;p>The new command-specific parameters feature lets you pre-configure the optimal settings for each use case. Here&amp;rsquo;s how it works:&lt;/p>
&lt;h3 id="key-features">Key Features&lt;/h3>
&lt;ul>
&lt;li>&lt;strong>Per-Command Parameter Sets&lt;/strong>: Define custom parameter values for each command in your menu&lt;/li>
&lt;li>&lt;strong>Automatic Application&lt;/strong>: Parameters are applied when running a command and restored afterward&lt;/li>
&lt;li>&lt;strong>Non-Destructive&lt;/strong>: Your global parameter settings remain untouched&lt;/li>
&lt;li>&lt;strong>Easy Configuration&lt;/strong>: Simple interface for adding or updating parameters&lt;/li>
&lt;/ul>
&lt;h3 id="example-configuration">Example Configuration&lt;/h3>
&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>&lt;span style="color:#75715e">;; Define a command with specific parameters&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>(refactor-code
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> :key &lt;span style="color:#e6db74">?r&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> :description &lt;span style="color:#e6db74">&amp;#34;Refactor code&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> :prompt &lt;span style="color:#e6db74">&amp;#34;refactor the following code:&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> :system &lt;span style="color:#e6db74">&amp;#34;You are an expert software engineer...&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> :parameters ((temperature &lt;span style="color:#f92672">.&lt;/span> &lt;span style="color:#ae81ff">0.2&lt;/span>) (top_p &lt;span style="color:#f92672">.&lt;/span> &lt;span style="color:#ae81ff">0.7&lt;/span>) (repeat_penalty &lt;span style="color:#f92672">.&lt;/span> &lt;span style="color:#ae81ff">1.3&lt;/span>))
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> :action (lambda () (ollama-buddy--send-with-command &lt;span style="color:#e6db74">&amp;#39;refactor-code&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">;; Add parameters to an existing command&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>(ollama-buddy-add-parameters-to-command &lt;span style="color:#e6db74">&amp;#39;git-commit&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> :temperature &lt;span style="color:#ae81ff">0.4&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> :top_p &lt;span style="color:#ae81ff">0.9&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> :repeat_penalty &lt;span style="color:#ae81ff">1.1&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">;; Update properties and parameters at once&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>(ollama-buddy-update-command-with-params &lt;span style="color:#e6db74">&amp;#39;describe-code&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> :model &lt;span style="color:#e6db74">&amp;#34;codellama:latest&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> :parameters &lt;span style="color:#f92672">&amp;#39;&lt;/span>((temperature &lt;span style="color:#f92672">.&lt;/span> &lt;span style="color:#ae81ff">0.3&lt;/span>) (top_p &lt;span style="color:#f92672">.&lt;/span> &lt;span style="color:#ae81ff">0.8&lt;/span>)))
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>This feature is particularly useful for:&lt;/p>
&lt;ol>
&lt;li>&lt;strong>Code-related tasks&lt;/strong>: Lower temperature for more deterministic code generation&lt;/li>
&lt;li>&lt;strong>Creative writing&lt;/strong>: Higher temperature for more varied and creative outputs&lt;/li>
&lt;li>&lt;strong>Technical explanations&lt;/strong>: Balanced settings for clear, accurate explanations&lt;/li>
&lt;li>&lt;strong>Summarization tasks&lt;/strong>: Custom parameters to control verbosity and focus&lt;/li>
&lt;/ol>
&lt;h2 id="0-dot-8-dot-5">&lt;span class="timestamp-wrapper">&lt;span class="timestamp">&amp;lt;2025-03-16 Sun&amp;gt; &lt;/span>&lt;/span> &lt;strong>0.8.5&lt;/strong>&lt;/h2>
&lt;p>Added system prompt support for commands&lt;/p>
&lt;ul>
&lt;li>Introduced `:system` field to command definitions.&lt;/li>
&lt;li>Added `ollama-buddy-show-system-prompt` to view active system prompt.&lt;/li>
&lt;li>Updated UI elements to reflect system prompt status.&lt;/li>
&lt;/ul>
&lt;p>Previously, individual menu commands in &lt;code>ollama-buddy&lt;/code> only included a user prompt. Now, each command can define a &lt;strong>system prompt&lt;/strong>, which provides background context to guide the AI&amp;rsquo;s responses. This makes interactions more precise and tailored.&lt;/p>
&lt;p>&lt;strong>Key Features&lt;/strong>&lt;/p>
&lt;ul>
&lt;li>&lt;strong>System prompts per command&lt;/strong>: Specify background instructions for each AI-powered command using the new &lt;code>:system&lt;/code> field.&lt;/li>
&lt;li>&lt;strong>View active system prompt&lt;/strong>: Use &lt;code>C-c C-s&lt;/code> to display the current system prompt in a dedicated buffer.&lt;/li>
&lt;li>&lt;strong>Updated UI elements&lt;/strong>: The status line now indicates whether a system prompt is active.&lt;/li>
&lt;/ul>
&lt;p>A helper function has also been added to update the default menu, for example, you might want to tweak a couple of things:&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 style="color:#e6db74">&amp;#34;C-c o&amp;#34;&lt;/span> &lt;span style="color:#f92672">.&lt;/span> ollama-buddy-menu)
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> :custom
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> (ollama-buddy-default-model &lt;span style="color:#e6db74">&amp;#34;llama3.2:3b&amp;#34;&lt;/span>)
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> :config
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> (ollama-buddy-update-menu-entry
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#e6db74">&amp;#39;refactor-code&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> :model &lt;span style="color:#e6db74">&amp;#34;qwen2.5-coder:7b&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> :system &lt;span style="color:#e6db74">&amp;#34;You are an expert software engineer who improves code and only mainly using the principles exhibited by Ada&amp;#34;&lt;/span>)
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> (ollama-buddy-update-menu-entry
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#e6db74">&amp;#39;git-commit&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> :model &lt;span style="color:#e6db74">&amp;#34;qwen2.5-coder:3b&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> :system &lt;span style="color:#e6db74">&amp;#34;You are a version control expert and mainly using subversion&amp;#34;&lt;/span>))
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div></description></item><item><title>Ollama-Buddy 0.8.0 - Added System Prompts, Model Info and simpler menu model assignment</title><link>https://www.emacs.dyerdwelling.family/emacs/20250314132104-emacs--ollama-buddy-0-8-0-added-system-prompts-model-info-and-simpler-menu-model-assignment/</link><pubDate>Fri, 14 Mar 2025 13:21:00 +0000</pubDate><author>James Dyer</author><guid>https://www.emacs.dyerdwelling.family/emacs/20250314132104-emacs--ollama-buddy-0-8-0-added-system-prompts-model-info-and-simpler-menu-model-assignment/</guid><description>&lt;p>More improvements to &lt;code>ollama-buddy&lt;/code> &lt;a href="https://github.com/captainflasmr/ollama-buddy">https://github.com/captainflasmr/ollama-buddy&lt;/a>&lt;/p>
&lt;p>The main addition is that of system prompts, which allows setting the general tone and guidance of the overall chat. Currently the system prompt can be set at any time and turned on and off but I think to enhance my model/command for each menu item concept, I could also add a :system property to the menu alist definition to allow even tighter control of a menu action to prompt response.&lt;/p>
&lt;figure>&lt;img src="https://www.emacs.dyerdwelling.family/emacs/20250314132104-emacs--Ollama-Buddy-0-8-0-Added-System-Prompts-Model-Info-and-simpler-menu-model-assignment.jpg" width="100%">
&lt;/figure>
&lt;p>Also now I have parameter functionality working for fine grained control, I could add these individual parameters for each menu command, for example the &lt;code>temperature&lt;/code> could be very useful in this case to play around with the randomness/casualness of the response.&lt;/p>
&lt;p>The next improvement will likely involve adding support for interacting more directly with Ollama to create and pull models. However, I&amp;rsquo;m still unsure whether performing this within Emacs is the best approach, I could assume that all models are already set up in Ollama.&lt;/p>
&lt;p>That said, importing a GGUF file might be a useful feature, possibly from within &lt;code>dired&lt;/code>. Currently, this process requires multiple steps: creating a simple model file that points to the GGUF file on disk, then running the &lt;code>ollama create&lt;/code> command to import it. Streamlining this workflow could enhance usability.&lt;/p>
&lt;p>Then maybe on to embeddings, of which I currently have no idea, haven&amp;rsquo;t read up on it, nuffin, but that is something to look forward to! :)&lt;/p>
&lt;p>Anyways, here is the latest set of updates to Ollama Buddy:&lt;/p>
&lt;h2 id="0-dot-8-dot-0">&lt;span class="timestamp-wrapper">&lt;span class="timestamp">&amp;lt;2025-03-14 Fri&amp;gt; &lt;/span>&lt;/span> &lt;strong>0.8.0&lt;/strong>&lt;/h2>
&lt;p>Added system prompt support&lt;/p>
&lt;ul>
&lt;li>Added &lt;code>ollama-buddy--current-system-prompt&lt;/code> variable to track system prompts&lt;/li>
&lt;li>Updated prompt area rendering to distinguish system prompts&lt;/li>
&lt;li>Modified request payload to include system prompt when set&lt;/li>
&lt;li>Enhanced status bar to display system prompt indicator&lt;/li>
&lt;li>Improved help menu with system prompt keybindings&lt;/li>
&lt;/ul>
&lt;p>So this is system prompt support in Ollama Buddy!, allowing you to set and manage system-level instructions for your AI interactions. This feature enables you to define a &lt;strong>persistent system prompt&lt;/strong> that remains active across user queries, providing better control over conversation context.&lt;/p>
&lt;p>&lt;strong>Key Features&lt;/strong>&lt;/p>
&lt;p>You can now designate any user prompt as a system prompt, ensuring that the AI considers it as a guiding instruction for future interactions. To set the system prompt, use:&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-bash" data-lang="bash">&lt;span style="display:flex;">&lt;span>C-u C-c C-c
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>&lt;strong>Example:&lt;/strong>&lt;/p>
&lt;ol>
&lt;li>Type:&lt;/li>
&lt;/ol>
&lt;!--listend-->
&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-bash" data-lang="bash">&lt;span style="display:flex;">&lt;span>Always respond in a formal tone.
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;ol>
&lt;li>Press &lt;code>C-u C-c C-c&lt;/code> This prompt is now set as the &lt;strong>system prompt&lt;/strong> and any further chat ollama responses will adhere to the overarching guidelines defined in the prompt.&lt;/li>
&lt;/ol>
&lt;p>If you need to clear the system prompt and revert to normal interactions, use:&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-bash" data-lang="bash">&lt;span style="display:flex;">&lt;span>C-u C-u C-c C-c
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>&lt;strong>How It Works&lt;/strong>&lt;/p>
&lt;ul>
&lt;li>The active &lt;strong>system prompt&lt;/strong> is stored and sent with each user prompt.&lt;/li>
&lt;li>A &amp;ldquo;S&amp;rdquo; indicator appears in the status bar when a system prompt is active.&lt;/li>
&lt;li>The request payload now includes the system role, allowing AI to recognize persistent instructions.&lt;/li>
&lt;/ul>
&lt;p>&lt;strong>Demo&lt;/strong>&lt;/p>
&lt;p>Set the system message to:&lt;/p>
&lt;p>You must always respond in a single sentence.&lt;/p>
&lt;p>Now ask the following:&lt;/p>
&lt;p>Tell me why Emacs is so great!&lt;/p>
&lt;p>Tell me about black holes&lt;/p>
&lt;p>clear the system message and ask again, the responses should now be more verbose!!&lt;/p>
&lt;figure>&lt;img src="https://www.emacs.dyerdwelling.family/emacs/ollama-buddy-screen-recording_015.gif" width="100%">
&lt;/figure>
&lt;h2 id="0-dot-7-dot-4">&lt;span class="timestamp-wrapper">&lt;span class="timestamp">&amp;lt;2025-03-13 Thu&amp;gt; &lt;/span>&lt;/span> &lt;strong>0.7.4&lt;/strong>&lt;/h2>
&lt;p>Added model info command, update keybindings&lt;/p>
&lt;ul>
&lt;li>Added `ollama-buddy-show-raw-model-info` to fetch and display raw JSON details
of the current model in the chat buffer.&lt;/li>
&lt;li>Updated keybindings:
&lt;ul>
&lt;li>`C-c i` now triggers model info display.&lt;/li>
&lt;li>`C-c h` mapped to help assistant.&lt;/li>
&lt;li>Improved shortcut descriptions in quick tips section.&lt;/li>
&lt;/ul>
&lt;/li>
&lt;li>Removed unused help assistant entry from menu.&lt;/li>
&lt;li>Changed minibuffer-prompt key from `?i` to `?b`.&lt;/li>
&lt;/ul>
&lt;h2 id="0-dot-7-dot-3">&lt;span class="timestamp-wrapper">&lt;span class="timestamp">&amp;lt;2025-03-12 Wed&amp;gt; &lt;/span>&lt;/span> &lt;strong>0.7.3&lt;/strong>&lt;/h2>
&lt;p>Added function to associate models with menu commands&lt;/p>
&lt;ul>
&lt;li>Added &lt;code>ollama-buddy-add-model-to-menu-entry&lt;/code> autoload function&lt;/li>
&lt;li>Enabled dynamic modification of command-model associations&lt;/li>
&lt;/ul>
&lt;p>This is a helper function that allows you to associate specific models with individual menu commands.&lt;/p>
&lt;p>Configuration to apply a model to a menu entry is now straightforward, in your Emacs init file, add 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-elisp" data-lang="elisp">&lt;span style="display:flex;">&lt;span>(with-eval-after-load &lt;span style="color:#e6db74">&amp;#39;ollama-buddy&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> (ollama-buddy-add-model-to-menu-entry &lt;span style="color:#e6db74">&amp;#39;dictionary-lookup&lt;/span> &lt;span style="color:#e6db74">&amp;#34;tinyllama:latest&amp;#34;&lt;/span>)
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> (ollama-buddy-add-model-to-menu-entry &lt;span style="color:#e6db74">&amp;#39;synonym&lt;/span> &lt;span style="color:#e6db74">&amp;#34;tinyllama:latest&amp;#34;&lt;/span>))
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>This configures simpler tasks like dictionary lookups and synonym searches to use the more efficient TinyLlama model, while your default model will still be used for more complex operations.&lt;/p>
&lt;h2 id="0-dot-7-dot-2">&lt;span class="timestamp-wrapper">&lt;span class="timestamp">&amp;lt;2025-03-12 Wed&amp;gt; &lt;/span>&lt;/span> &lt;strong>0.7.2&lt;/strong>&lt;/h2>
&lt;p>Added menu model colours back in and removed some redundant code&lt;/p></description></item><item><title>Ollama-Buddy 0.7.1 - Org-mode Chat, Parameter Control and JSON Debugging</title><link>https://www.emacs.dyerdwelling.family/emacs/20250311180746-emacs--ollama-buddy-0-7-1-org-mode-chat-parameter-control-and-json-debugging/</link><pubDate>Tue, 11 Mar 2025 18:07:00 +0000</pubDate><author>James Dyer</author><guid>https://www.emacs.dyerdwelling.family/emacs/20250311180746-emacs--ollama-buddy-0-7-1-org-mode-chat-parameter-control-and-json-debugging/</guid><description>&lt;p>Continuing the development of my local &lt;code>ollama&lt;/code> LLM client called &lt;code>ollama-buddy&lt;/code>&amp;hellip;&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>The basic functionality, I think, is now there (and now literally zero configuration required). If a default model isn&amp;rsquo;t set I just pick the first one, so LLM chat can take place immediately.&lt;/p>
&lt;figure>&lt;img src="https://www.emacs.dyerdwelling.family/emacs/20250311180746-emacs--Ollama-Buddy-0-7-1-Org-mode-Chat-Parameter-Control-and-JSON-Debugging.jpg" width="100%">
&lt;/figure>
&lt;p>Now I&amp;rsquo;m getting more into this chat client malarkey, my original idea of a very minimal chat client to interface to &lt;code>ollama&lt;/code> is starting to skew into supporting as much of the &lt;code>ollama&lt;/code> RESTful API as possible. Hence in this update a more advanced approach is creeping in, including setting up various subtle model parameters and providing a debugging window to monitor incoming raw JSON (pretty printed of course). Hopefully, these features will remain tucked away for advanced users, I’ve done my best to keep them unobtrusive (but not &lt;strong>too&lt;/strong> hidden). The tool is still designed to be a helpful companion to interface to &lt;code>ollama&lt;/code> through Emacs, just now with more powerful options under the hood.&lt;/p>
&lt;p>Also a note about converting the chat buffer into org-mode. My original intention was to keep the chat buffer as a very simple almost &amp;ldquo;no mode&amp;rdquo; buffer, with just text and nothing else. However, with more consideration, I felt that converting this buffer into org-mode actually held quite a few benefits:&lt;/p>
&lt;ul>
&lt;li>Each prompt could be a heading, hence outlining and folding can be activated!&lt;/li>
&lt;li>Navigation between prompts now comes for free (especially if you are using &lt;code>org-use-speed-commands&lt;/code>)&lt;/li>
&lt;li>The org ox export backend now allows us to export to formats of many different kinds&lt;/li>
&lt;/ul>
&lt;p>I&amp;rsquo;m sure there are more as this list isn&amp;rsquo;t quite the &amp;ldquo;quite a few benefits&amp;rdquo; I was hoping for :(&lt;/p>
&lt;p>I have a local keymap defined with some ollama-buddy specific keybindings, and as of yet I haven’t encountered any conflicts with commonly used &lt;code>org-mode&lt;/code> bindings but we shall see how it goes. I think for this package it is important to have a quick chatting mechanism, and what is faster than a good keybind?&lt;/p>
&lt;p>Finally, just a note on the pain of implementing a good prompt mechanism. I had a few goes at it and I think I now have an acceptable robust solution. I kept running into little annoying edge cases and I ended up having to refactor quite a bit. My original idea for this package involved a simple “mark region and send” as at the time I had a feeling that the implementation of a good prompt mechanism would be tough - how right I was!. Things got even trickier with the move to &lt;code>org-mode&lt;/code>, since each prompt heading should contain meaningful content for clean exports and I had to implement a mechanism to replace prompts intelligently. For example, if the model is swapped and the previous prompt is blank, it gets replaced, though, of course, even this has its own edge cases - gives a new meaning to prompt engineering! :)&lt;/p>
&lt;p>Anyways, listed below are my latest changes, with a little deeper dive into more &amp;ldquo;interesting&amp;rdquo; implementations, my next ideas are a little more advanced and are kanban&amp;rsquo;d into my github README at &lt;a href="https://github.com/captainflasmr/ollama-buddy">https://github.com/captainflasmr/ollama-buddy&lt;/a> for those that are interested.&lt;/p>
&lt;h2 id="0-dot-7-dot-1">&lt;span class="timestamp-wrapper">&lt;span class="timestamp">&amp;lt;2025-03-11 Tue&amp;gt; &lt;/span>&lt;/span> &lt;strong>0.7.1&lt;/strong>&lt;/h2>
&lt;p>Added debug mode to display raw JSON messages in a debug buffer&lt;/p>
&lt;ul>
&lt;li>Created new debug buffer to show raw JSON messages from Ollama API&lt;/li>
&lt;li>Added toggle function to enable/disable debug mode (ollama-buddy-toggle-debug-mode)&lt;/li>
&lt;li>Modified stream filter to log and pretty-print incoming JSON messages&lt;/li>
&lt;li>Added keybinding C-c D to toggle debug mode&lt;/li>
&lt;li>Updated documentation in welcome message&lt;/li>
&lt;/ul>
&lt;h2 id="0-dot-7-dot-0">&lt;span class="timestamp-wrapper">&lt;span class="timestamp">&amp;lt;2025-03-11 Tue&amp;gt; &lt;/span>&lt;/span> &lt;strong>0.7.0&lt;/strong>&lt;/h2>
&lt;p>Added comprehensive Ollama parameter management&lt;/p>
&lt;ul>
&lt;li>Added customization for all Ollama option API parameters with defaults&lt;/li>
&lt;li>Only send modified parameters to preserve Ollama defaults&lt;/li>
&lt;li>Display active parameters with visual indicators for modified values&lt;/li>
&lt;li>Add keybindings and help system for parameter management&lt;/li>
&lt;li>Remove redundant temperature controls in favor of unified parameters&lt;/li>
&lt;/ul>
&lt;p>Introduced parameter management capabilities that give you complete control over your Ollama model&amp;rsquo;s behavior through the options in the ollamas API.&lt;/p>
&lt;p>Ollama&amp;rsquo;s API supports a rich set of parameters for fine-tuning text generation, from controlling creativity with &lt;code>temperature&lt;/code> to managing token selection with &lt;code>top_p&lt;/code> and &lt;code>top_k&lt;/code>. Until now, Ollama Buddy only exposed the &lt;code>temperature&lt;/code> parameter, but this update unlocks the full potential of Ollama&amp;rsquo;s parameter system!&lt;/p>
&lt;h3 id="key-features">Key Features:&lt;/h3>
&lt;ul>
&lt;li>&lt;strong>All Parameters&lt;/strong> - set all custom options for the ollama LLM at runtime&lt;/li>
&lt;li>&lt;strong>Smart Parameter Management&lt;/strong>: Only modified parameters are sent to Ollama, preserving the model&amp;rsquo;s built-in defaults for optimal performance&lt;/li>
&lt;li>&lt;strong>Visual Parameter Interface&lt;/strong>: Clear display showing which parameters are active with highlighting for modified values&lt;/li>
&lt;/ul>
&lt;h2 id="keyboard-shortcuts">Keyboard Shortcuts&lt;/h2>
&lt;p>Parameter management is accessible through simple keyboard shortcuts from the chat buffer:&lt;/p>
&lt;ul>
&lt;li>&lt;code>C-c P&lt;/code> - Edit a parameter&lt;/li>
&lt;li>&lt;code>C-c G&lt;/code> - Display current parameters&lt;/li>
&lt;li>&lt;code>C-c I&lt;/code> - Show parameter help&lt;/li>
&lt;li>&lt;code>C-c K&lt;/code> - Reset parameters to defaults&lt;/li>
&lt;/ul>
&lt;h2 id="0-dot-6-dot-1">&lt;span class="timestamp-wrapper">&lt;span class="timestamp">&amp;lt;2025-03-10 Mon&amp;gt; &lt;/span>&lt;/span> &lt;strong>0.6.1&lt;/strong>&lt;/h2>
&lt;p>Refactored prompt handling so each org header line should now always have a prompt for better export&lt;/p>
&lt;ul>
&lt;li>Added functionality to properly handle prompt text when showing/replacing prompts&lt;/li>
&lt;li>Extracted inline lambdas in menu actions into named functions&lt;/li>
&lt;li>Added fallback for when no default model is set&lt;/li>
&lt;/ul>
&lt;h2 id="0-dot-6-dot-0">&lt;span class="timestamp-wrapper">&lt;span class="timestamp">&amp;lt;2025-03-08 Sat&amp;gt; &lt;/span>&lt;/span> &lt;strong>0.6.0&lt;/strong>&lt;/h2>
&lt;p>Chat buffer now in org-mode&lt;/p>
&lt;ul>
&lt;li>Enabled &lt;code>org-mode&lt;/code> in chat buffer for better text structure&lt;/li>
&lt;li>Implemented &lt;code>ollama-buddy--md-to-org-convert-region&lt;/code> for Markdown to Org conversion&lt;/li>
&lt;li>Turn org conversion on and off&lt;/li>
&lt;li>Updated keybindings &lt;code>C-c C-o&lt;/code> to toggle Markdown to Org conversion&lt;/li>
&lt;/ul>
&lt;p>&lt;strong>Key Features&lt;/strong>&lt;/p>
&lt;ol>
&lt;li>
&lt;p>The chat buffer is now in &lt;code>org-mode&lt;/code> which gives the buffer enhanced readability and structure. Now, conversations automatically format user prompts and AI responses with &lt;strong>org-mode headings&lt;/strong>, making them easier to navigate.&lt;/p>
&lt;/li>
&lt;li>
&lt;p>Of course with org-mode you will now get the additional benefits for free, such as:&lt;/p>
&lt;ul>
&lt;li>outlining&lt;/li>
&lt;li>org export&lt;/li>
&lt;li>heading navigation&lt;/li>
&lt;li>source code fontification&lt;/li>
&lt;/ul>
&lt;/li>
&lt;li>
&lt;p>Previously, responses in &lt;strong>Ollama Buddy&lt;/strong> were displayed in markdown formatting, which wasn’t always ideal for &lt;strong>org-mode users&lt;/strong>. Now, you can automatically convert Markdown elements, such as bold/italic text, code blocks, and lists, into proper org-mode formatting. This gives you the flexibility to work with markdown or org-mode as needed.&lt;/p>
&lt;/li>
&lt;/ol></description></item><item><title>Ollama-Buddy V0.5.1 - Session/History/Role Management, Real-Time Token Tracking and More!</title><link>https://www.emacs.dyerdwelling.family/emacs/20250307133856-emacs--ollama-buddy-version-0-5-1/</link><pubDate>Fri, 07 Mar 2025 13:50:00 +0000</pubDate><author>James Dyer</author><guid>https://www.emacs.dyerdwelling.family/emacs/20250307133856-emacs--ollama-buddy-version-0-5-1/</guid><description>&lt;p>I&amp;rsquo;ve been a busy little bee in the last few days, so quite a few improvements to &lt;code>ollama-buddy&lt;/code>, my Emacs LLM &lt;code>ollama&lt;/code> client, they are listed below:&lt;/p>
&lt;figure>&lt;img src="https://www.emacs.dyerdwelling.family/emacs/ollama-buddy-banner.jpg" width="100%">
&lt;/figure>
&lt;h2 id="0-dot-5-dot-1">&lt;span class="timestamp-wrapper">&lt;span class="timestamp">&amp;lt;2025-03-07 Fri&amp;gt; &lt;/span>&lt;/span> &lt;strong>0.5.1&lt;/strong>&lt;/h2>
&lt;p>Added temperature control&lt;/p>
&lt;ul>
&lt;li>Implemented temperature control parameter&lt;/li>
&lt;li>Added menu commands for setting (T), resetting (0)&lt;/li>
&lt;li>Added keybindings (C-c t/T/0) for quick temperature adjustments&lt;/li>
&lt;li>Updated header line and prompt displays to show current temperature&lt;/li>
&lt;li>Included temperature info in welcome screen with usage guidance&lt;/li>
&lt;/ul>
&lt;p>This addition gives users fine-grained control over the creativity and randomness of their AI responses through a new temperature variable.&lt;/p>
&lt;p>This update adds several convenient ways to control temperature in Ollama-Buddy:&lt;/p>
&lt;p>&lt;strong>Key Features&lt;/strong>&lt;/p>
&lt;ol>
&lt;li>
&lt;p>&lt;strong>Direct Temperature Setting&lt;/strong>: Use &lt;code>C-c t&lt;/code> from the chat buffer or the menu command &lt;code>[T]&lt;/code> to set an exact temperature value between 0.0 and 2.0.&lt;/p>
&lt;/li>
&lt;li>
&lt;p>&lt;strong>Preset Temperatures&lt;/strong>: Quickly switch between common temperature presets with &lt;code>C-c T&lt;/code> from the chat buffer:&lt;/p>
&lt;ul>
&lt;li>Precise (0.1) - For factual responses&lt;/li>
&lt;li>Focused (0.3) - For deterministic, coherent outputs&lt;/li>
&lt;li>Balanced (0.7) - Default setting&lt;/li>
&lt;li>Creative (0.9) - For more varied, creative responses&lt;/li>
&lt;/ul>
&lt;/li>
&lt;li>
&lt;p>&lt;strong>Reset to Default&lt;/strong>: Return to the default temperature (0.7) with &lt;code>C-c 0&lt;/code> or the menu command &lt;code>[0]&lt;/code>.&lt;/p>
&lt;/li>
&lt;li>
&lt;p>&lt;strong>Visual Feedback&lt;/strong>: The current temperature is displayed in the header line and before each response, so you always know what setting you&amp;rsquo;re using.&lt;/p>
&lt;/li>
&lt;/ol>
&lt;h2 id="0-dot-5-dot-0">&lt;span class="timestamp-wrapper">&lt;span class="timestamp">&amp;lt;2025-03-06 Thu&amp;gt; &lt;/span>&lt;/span> &lt;strong>0.5.0&lt;/strong>&lt;/h2>
&lt;p>Implemented session management, so you can now save your conversations and bring them back with the relevant context and chat history!&lt;/p>
&lt;ul>
&lt;li>Chat history is now maintained separately for each model&lt;/li>
&lt;li>Added session new/load/save/delete/list functionality&lt;/li>
&lt;li>A switch in context can now be achieved by any of the following methods:
&lt;ul>
&lt;li>Loading a previous session&lt;/li>
&lt;li>Creating a new session&lt;/li>
&lt;li>Clearing history on the current session&lt;/li>
&lt;li>Toggling history on and off&lt;/li>
&lt;/ul>
&lt;/li>
&lt;/ul>
&lt;p>&lt;strong>Key Benefits&lt;/strong>&lt;/p>
&lt;ul>
&lt;li>More relevant responses when switching between models&lt;/li>
&lt;li>Prevents context contamination across different models&lt;/li>
&lt;li>Clearer session management and organization&lt;/li>
&lt;/ul>
&lt;p>&lt;strong>Key Features&lt;/strong>&lt;/p>
&lt;ol>
&lt;li>&lt;strong>Session Management&lt;/strong>&lt;/li>
&lt;/ol>
&lt;p>With session management, you can now:&lt;/p>
&lt;ul>
&lt;li>
&lt;p>&lt;strong>Save session&lt;/strong> with &lt;code>ollama-buddy-sessions-save&lt;/code> (or through the ollama-buddy-menu) Preserve your current conversation with a custom name&lt;/p>
&lt;/li>
&lt;li>
&lt;p>&lt;strong>Load session&lt;/strong> with &lt;code>ollama-buddy-sessions-load&lt;/code> (or through the ollama-buddy-menu) Return to previous conversations exactly where you left off&lt;/p>
&lt;/li>
&lt;li>
&lt;p>&lt;strong>List all sessions&lt;/strong> with &lt;code>ollama-buddy-sessions-list&lt;/code> (or through the ollama-buddy-menu) View all saved sessions with metadata including timestamps and models used&lt;/p>
&lt;/li>
&lt;li>
&lt;p>&lt;strong>Delete session&lt;/strong> with &lt;code>ollama-buddy-sessions-delete&lt;/code> (or through the ollama-buddy-menu) Clean up sessions you no longer need&lt;/p>
&lt;/li>
&lt;li>
&lt;p>&lt;strong>New session&lt;/strong> with &lt;code>ollama-buddy-sessions-new&lt;/code> (or through the ollama-buddy-menu) Begin a clean slate without losing your saved sessions&lt;/p>
&lt;/li>
&lt;li>
&lt;p>&lt;strong>Menu Commands&lt;/strong>&lt;/p>
&lt;/li>
&lt;/ul>
&lt;p>The following commands have been added to the &lt;code>ollama-buddy-menu&lt;/code>:&lt;/p>
&lt;ul>
&lt;li>&lt;code>E&lt;/code> New session&lt;/li>
&lt;li>&lt;code>L&lt;/code> Load session&lt;/li>
&lt;li>&lt;code>S&lt;/code> Save session&lt;/li>
&lt;li>&lt;code>Y&lt;/code> List sessions&lt;/li>
&lt;li>&lt;code>K&lt;/code> Delete session&lt;/li>
&lt;/ul>
&lt;h2 id="0-dot-4-dot-1">&lt;span class="timestamp-wrapper">&lt;span class="timestamp">&amp;lt;2025-03-04 Tue&amp;gt; &lt;/span>&lt;/span> &lt;strong>0.4.1&lt;/strong>&lt;/h2>
&lt;p>Added a sparse version of &lt;code>ollama-buddy&lt;/code> called &lt;code>ollama-buddy-mini&lt;/code>, see the github repository for the elisp file and a description in &lt;code>README-mini.org&lt;/code>&lt;/p>
&lt;h2 id="0-dot-4-dot-0">&lt;span class="timestamp-wrapper">&lt;span class="timestamp">&amp;lt;2025-03-03 Mon&amp;gt; &lt;/span>&lt;/span> &lt;strong>0.4.0&lt;/strong>&lt;/h2>
&lt;p>Added conversation history support and navigation functions&lt;/p>
&lt;ul>
&lt;li>Implemented conversation history tracking between prompts and responses&lt;/li>
&lt;li>Added configurable history length limits and visual indicators&lt;/li>
&lt;li>Created navigation functions to move between prompts/responses in buffer&lt;/li>
&lt;/ul>
&lt;p>&lt;strong>Key Features&lt;/strong>&lt;/p>
&lt;ol>
&lt;li>&lt;strong>Conversation History&lt;/strong>&lt;/li>
&lt;/ol>
&lt;p>Ollama Buddy now maintains context between your interactions by:&lt;/p>
&lt;ul>
&lt;li>Tracking conversation history between prompts and responses&lt;/li>
&lt;li>Sending previous messages to Ollama for improved contextual responses&lt;/li>
&lt;li>Displaying a history counter in the status line showing conversation length&lt;/li>
&lt;li>Providing configurable history length limits to control memory usage&lt;/li>
&lt;/ul>
&lt;p>You can control this feature with:&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>&lt;span style="color:#75715e">;; Enable/disable conversation history (default: t)&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>(setq ollama-buddy-history-enabled &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">;; Set maximum conversation pairs to remember (default: 10)&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>(setq ollama-buddy-max-history-length &lt;span style="color:#ae81ff">10&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">;; Show/hide the history counter in the header line (default: t)&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>(setq ollama-buddy-show-history-indicator &lt;span style="color:#66d9ef">t&lt;/span>)
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;ol>
&lt;li>&lt;strong>Enhanced Navigation&lt;/strong>&lt;/li>
&lt;/ol>
&lt;p>Moving through longer conversations is now much easier with:&lt;/p>
&lt;ul>
&lt;li>
&lt;p>Navigation functions to jump between prompts using C-c n/p&lt;/p>
&lt;/li>
&lt;li>
&lt;p>&lt;strong>Menu Commands&lt;/strong>&lt;/p>
&lt;/li>
&lt;/ul>
&lt;p>Three new menu commands have been added:&lt;/p>
&lt;ul>
&lt;li>&lt;code>H&lt;/code>: Toggle history tracking on/off&lt;/li>
&lt;li>&lt;code>X&lt;/code>: Clear the current conversation history&lt;/li>
&lt;li>&lt;code>V&lt;/code>: View the full conversation history in a dedicated buffer&lt;/li>
&lt;/ul>
&lt;h2 id="0-dot-3-dot-1">&lt;span class="timestamp-wrapper">&lt;span class="timestamp">&amp;lt;2025-03-02 Sun&amp;gt; &lt;/span>&lt;/span> &lt;strong>0.3.1&lt;/strong>&lt;/h2>
&lt;p>Enhanced model colour contrast with themes, allowing &lt;code>ollama-buddy-enable-model-colors&lt;/code> to be enabled by default.&lt;/p>
&lt;h2 id="0-dot-3-dot-0">&lt;span class="timestamp-wrapper">&lt;span class="timestamp">&amp;lt;2025-03-01 Sat&amp;gt; &lt;/span>&lt;/span> &lt;strong>0.3.0&lt;/strong>&lt;/h2>
&lt;p>Added real-time token usage tracking and display&lt;/p>
&lt;ul>
&lt;li>Introduce variables to track token counts, rates, and usage history&lt;/li>
&lt;li>Implement real-time token rate updates with a timer&lt;/li>
&lt;li>Add a function to display token usage statistics in a dedicated buffer&lt;/li>
&lt;li>Allow toggling of token stats display after responses&lt;/li>
&lt;li>Integrate token tracking into response processing and status updates&lt;/li>
&lt;li>Ensure cleanup of timers and tracking variables on completion or cancellation&lt;/li>
&lt;/ul>
&lt;p>&lt;strong>Key Features&lt;/strong>&lt;/p>
&lt;ol>
&lt;li>
&lt;p>&lt;strong>Menu Commands&lt;/strong>&lt;/p>
&lt;p>The following command has been added to the &lt;code>ollama-buddy-menu&lt;/code>:&lt;/p>
&lt;ul>
&lt;li>&lt;code>t&lt;/code> Show a summary of token model usage stats&lt;/li>
&lt;/ul>
&lt;/li>
&lt;/ol>
&lt;h2 id="0-dot-2-dot-4">&lt;span class="timestamp-wrapper">&lt;span class="timestamp">&amp;lt;2025-02-28 Fri&amp;gt; &lt;/span>&lt;/span> &lt;strong>0.2.4&lt;/strong>&lt;/h2>
&lt;p>Added model-specific color highlighting&lt;/p>
&lt;ul>
&lt;li>Introduce `ollama-buddy-enable-model-colors` (default: nil) to toggle model-based color highlighting.&lt;/li>
&lt;li>Assign consistent colors to models based on string hashing.&lt;/li>
&lt;li>Apply colors to model names in the menu, status, headers, and responses.&lt;/li>
&lt;li>Add `ollama-buddy-toggle-model-colors` command to toggle this feature.&lt;/li>
&lt;/ul>
&lt;p>This enhancement aims to improve user experience by visually distinguishing different AI models within the interface.&lt;/p>
&lt;p>Note: I am likely to use both &lt;strong>colour&lt;/strong> and &lt;strong>color&lt;/strong> interchangeably in the following text! :)&lt;/p>
&lt;p>&lt;strong>Key Features&lt;/strong>&lt;/p>
&lt;ol>
&lt;li>
&lt;p>&lt;strong>Model-Specific Colors&lt;/strong>&lt;/p>
&lt;ul>
&lt;li>A new customizable variable, &lt;code>ollama-buddy-enable-model-colors&lt;/code>, allows users to enable or disable model-specific colors.&lt;/li>
&lt;li>Colors are generated based on a model&amp;rsquo;s name using a hashing function that produces consistent and visually distinguishable hues.&lt;/li>
&lt;li>However there could be an improvement regarding ensuring the contrast is sufficient and hence visibility maintained with differing themes.&lt;/li>
&lt;/ul>
&lt;/li>
&lt;li>
&lt;p>&lt;strong>Interactive Color Toggle&lt;/strong>&lt;/p>
&lt;ul>
&lt;li>Users can toggle model-specific colors with the command &lt;code>ollama-buddy-toggle-model-colors&lt;/code>, providing flexibility in interface customization.&lt;/li>
&lt;/ul>
&lt;/li>
&lt;li>
&lt;p>&lt;strong>Colored Model Listings&lt;/strong>&lt;/p>
&lt;ul>
&lt;li>Model names are now displayed with their respective colors in various parts of the interface, including:
&lt;ul>
&lt;li>The status line&lt;/li>
&lt;li>Model selection menus&lt;/li>
&lt;li>Command definitions&lt;/li>
&lt;li>Chat history headers&lt;/li>
&lt;/ul>
&lt;/li>
&lt;/ul>
&lt;/li>
&lt;li>
&lt;p>&lt;strong>Menu Commands&lt;/strong>&lt;/p>
&lt;/li>
&lt;/ol>
&lt;p>The following command hashing been added to the &lt;code>ollama-buddy-menu&lt;/code>:&lt;/p>
&lt;ul>
&lt;li>&lt;code>C&lt;/code> Toggle colors&lt;/li>
&lt;/ul>
&lt;h2 id="0-dot-2-dot-3">&lt;span class="timestamp-wrapper">&lt;span class="timestamp">&amp;lt;2025-02-28 Fri&amp;gt; &lt;/span>&lt;/span> &lt;strong>0.2.3&lt;/strong>&lt;/h2>
&lt;p>Added Prompt History Support&lt;/p>
&lt;ul>
&lt;li>Prompts are now integrated into the Emacs history mechanism which means they persist across sessions.&lt;/li>
&lt;li>Use &lt;code>M-p&lt;/code> to navigate prompt history, and &lt;code>M-p&lt;/code> / &lt;code>M-n&lt;/code> within the minibuffer to insert previous prompts.&lt;/li>
&lt;/ul>
&lt;p>&lt;strong>Key Features&lt;/strong>&lt;/p>
&lt;ul>
&lt;li>Persistent prompt history&lt;/li>
&lt;li>A new variable, &lt;code>ollama-buddy--prompt-history&lt;/code>, now keeps track of past prompts. This means you can quickly recall and reuse previous queries instead of retyping them from scratch.&lt;/li>
&lt;li>&lt;code>M-p&lt;/code> - recall a previous prompt in the buffer which will bring up the minibuffer for prompt history selection.&lt;/li>
&lt;li>Minibuffer &lt;code>M-p&lt;/code> / &lt;code>M-n&lt;/code> - Navigate through past prompts when prompted for input.&lt;/li>
&lt;/ul>
&lt;h2 id="0-dot-2-dot-2">&lt;span class="timestamp-wrapper">&lt;span class="timestamp">&amp;lt;2025-02-27 Thu&amp;gt; &lt;/span>&lt;/span> &lt;strong>0.2.2&lt;/strong>&lt;/h2>
&lt;p>Added support for role-based presets&lt;/p>
&lt;ul>
&lt;li>Introduced `ollama-buddy-roles-directory` for storing role preset files.&lt;/li>
&lt;li>Implemented interactive functions to manage roles:
&lt;ul>
&lt;li>`ollama-buddy-roles-switch-role`&lt;/li>
&lt;li>`ollama-buddy-role-creator-create-new-role`&lt;/li>
&lt;li>`ollama-buddy-roles-open-directory`&lt;/li>
&lt;/ul>
&lt;/li>
&lt;li>Added ability to create and switch between role-specific commands.&lt;/li>
&lt;li>Updated menu commands to include role management options.&lt;/li>
&lt;/ul>
&lt;p>This enhancement allows you to create, switch, and manage role-specific command configurations, which basically generates differing menu layouts and hence command options based on your context, making your workflow more personalized and efficient.&lt;/p>
&lt;p>&lt;strong>What Are Role-Based Presets?&lt;/strong>&lt;/p>
&lt;p>Roles in Ollama Buddy are essentially &lt;strong>profiles&lt;/strong> tailored to specific tasks. Imagine you&amp;rsquo;re using Ollama Buddy for:&lt;/p>
&lt;ul>
&lt;li>&lt;strong>Coding assistance&lt;/strong> with one set of prompts&lt;/li>
&lt;li>&lt;strong>Creative writing&lt;/strong> with a different tone and response style&lt;/li>
&lt;li>&lt;strong>Generating Buffy Style Quips&lt;/strong> - just a fun one!&lt;/li>
&lt;/ul>
&lt;p>With this update, you can now create presets for each of these contexts and switch between them seamlessly without manually re-configuring settings every time. On each switch of context and hence role, a new ollama buddy menu will be generated with the associated keybinding attached to the relevant context commands.&lt;/p>
&lt;p>&lt;strong>Key Features&lt;/strong>&lt;/p>
&lt;p>&lt;strong>1. Store Your Custom Roles&lt;/strong>&lt;/p>
&lt;p>A new directory &lt;code>ollama-buddy-roles-directory&lt;/code> (defaulting to &lt;code>~/.emacs.d/ollama-buddy-presets/&lt;/code>) now holds your role presets. Each role is saved as an &lt;code>.el&lt;/code> file containing predefined &lt;strong>commands&lt;/strong>, &lt;strong>shortcuts&lt;/strong>, and &lt;strong>model preferences&lt;/strong>.&lt;/p>
&lt;p>&lt;strong>2. Easily Switch Between Roles&lt;/strong>&lt;/p>
&lt;p>With &lt;code>M-x ollama-buddy-roles-switch-role&lt;/code> you can pick from available role presets and swap effortlessly between them (or use the menu item from &lt;code>ollama-buddy-menu&lt;/code>)&lt;/p>
&lt;p>&lt;strong>3. Create Custom Roles with Unique Commands&lt;/strong>&lt;/p>
&lt;p>You can now define &lt;strong>custom commands&lt;/strong> for each role with &lt;code>M-x ollama-buddy-role-creator-create-new-role&lt;/code> (or the menu item from &lt;code>ollama-buddy-menu&lt;/code>)&lt;/p>
&lt;p>This interactive function allows you to:&lt;/p>
&lt;ul>
&lt;li>Assign menu shortcuts to commands&lt;/li>
&lt;li>Describe command behaviour&lt;/li>
&lt;li>Set a default AI model&lt;/li>
&lt;li>Define a system prompt for guiding responses&lt;/li>
&lt;/ul>
&lt;p>Once saved, your new role is ready to load anytime!&lt;/p>
&lt;p>&lt;strong>4. Open Role Directory in Dired&lt;/strong>&lt;/p>
&lt;p>Need to tweak a role manually? A simple, run &lt;code>M-x ollama-buddy-roles-open-directory&lt;/code> or of course also from the &lt;code>ollama-buddy-menu&lt;/code> which opens the presets folder in &lt;strong>dired&lt;/strong>, where you can quickly edit, copy, or delete role configurations.&lt;/p>
&lt;p>&lt;strong>5. Preconfigured presets are available if you&amp;rsquo;d like to use a ready-made setup.&lt;/strong>&lt;/p>
&lt;ul>
&lt;li>ollama-buddy&amp;ndash;preset__buffy.el&lt;/li>
&lt;li>ollama-buddy&amp;ndash;preset__default.el&lt;/li>
&lt;li>ollama-buddy&amp;ndash;preset__developer.el&lt;/li>
&lt;li>ollama-buddy&amp;ndash;preset__janeway.el&lt;/li>
&lt;li>ollama-buddy&amp;ndash;preset__translator.el&lt;/li>
&lt;li>ollama-buddy&amp;ndash;preset__writer.el&lt;/li>
&lt;/ul>
&lt;p>If these files are put in the &lt;code>ollama-buddy-roles-directory&lt;/code> then the role selection menu will pass through completing-read, and present the following:&lt;/p>
&lt;p>{buffy | default | developer | janeway | translator | writer}&lt;/p>
&lt;p>With the selection regenerating the &lt;code>ollama-buddy-menu&lt;/code> accordingly, and off you go.&lt;/p>
&lt;p>&lt;strong>6. Menu commands&lt;/strong>&lt;/p>
&lt;p>The following commands have been added to the &lt;code>ollama-buddy-menu&lt;/code>:&lt;/p>
&lt;ul>
&lt;li>&lt;code>R&lt;/code> Switch Role&lt;/li>
&lt;li>&lt;code>N&lt;/code> Create New Role&lt;/li>
&lt;li>&lt;code>D&lt;/code> Open Roles Directory&lt;/li>
&lt;/ul></description></item><item><title>Ollama Buddy Version 0.2.1 - Same prompt to multiple LLMs and choose best answer!</title><link>https://www.emacs.dyerdwelling.family/emacs/20250302093517-emacs--ollama-buddy-version-0-2-1/</link><pubDate>Sun, 02 Mar 2025 09:35:00 +0000</pubDate><author>James Dyer</author><guid>https://www.emacs.dyerdwelling.family/emacs/20250302093517-emacs--ollama-buddy-version-0-2-1/</guid><description>&lt;p>Some improvements to my ollama LLM package&amp;hellip;&lt;/p>
&lt;p>With the new &lt;strong>multishot mode&lt;/strong>, you can now send a prompt to multiple models in sequence, and compare their responses, the results are also available in named registers.&lt;/p>
&lt;figure>&lt;img src="https://www.emacs.dyerdwelling.family/emacs/ollama-buddy-banner.jpg" width="100%">
&lt;/figure>
&lt;p>&lt;strong>Letter-Based Model Shortcuts&lt;/strong>&lt;/p>
&lt;p>Instead of manually selecting models, each available model is now assigned a &lt;strong>letter&lt;/strong> (e.g., &lt;code>(a) mistral&lt;/code>, &lt;code>(b) gemini&lt;/code>). This allows for quick model selection when sending prompts or initiating a &lt;strong>multishot sequence&lt;/strong>.&lt;/p>
&lt;p>&lt;strong>Multishot Execution (&lt;code>C-c C-l&lt;/code>)&lt;/strong>&lt;/p>
&lt;p>Ever wondered how different models would answer the same question? With &lt;strong>Multishot Mode&lt;/strong>, you can:&lt;/p>
&lt;ul>
&lt;li>Send your prompt to a sequence of models in one shot.&lt;/li>
&lt;li>Track progress as responses come in.&lt;/li>
&lt;li>Store each model’s response in a &lt;strong>register&lt;/strong>, making it easy to reference later, each assigned model letter corresponds to the named register.&lt;/li>
&lt;/ul>
&lt;p>&lt;strong>Status Updates&lt;/strong>&lt;/p>
&lt;p>When running a multishot execution, the status now updates dynamically:&lt;/p>
&lt;ul>
&lt;li>&lt;strong>&amp;ldquo;Multi Start&amp;rdquo;&lt;/strong> when the sequence begins.&lt;/li>
&lt;li>&lt;strong>&amp;ldquo;Processing&amp;hellip;&amp;rdquo;&lt;/strong> during responses.&lt;/li>
&lt;li>&lt;strong>&amp;ldquo;Multi Finished&amp;rdquo;&lt;/strong> when all models have responded.&lt;/li>
&lt;/ul>
&lt;p>&lt;strong>How It Works&lt;/strong>&lt;/p>
&lt;ol>
&lt;li>&lt;strong>&lt;code>C-c C-l&lt;/code>&lt;/strong> to start a multishot session in the chat buffer.&lt;/li>
&lt;li>Type a sequence of model letters (e.g., &lt;code>abc&lt;/code> to use models &lt;code>mistral&lt;/code>, &lt;code>gemini&lt;/code>, and &lt;code>llama&lt;/code>).&lt;/li>
&lt;li>The selected models will process the prompt &lt;strong>one by one&lt;/strong>.&lt;/li>
&lt;li>The responses will be saved to registers of the same named letter for recalling later.&lt;/li>
&lt;/ol>
&lt;figure>&lt;img src="https://www.emacs.dyerdwelling.family/emacs/ollama-buddy-screen-recording_007.gif" width="100%">
&lt;/figure></description></item><item><title>Installing Emacs 30.1 On Arch and SUSE</title><link>https://www.emacs.dyerdwelling.family/emacs/20250226140529-emacs--installing-30_1-on-arch/</link><pubDate>Wed, 26 Feb 2025 14:05:00 +0000</pubDate><author>James Dyer</author><guid>https://www.emacs.dyerdwelling.family/emacs/20250226140529-emacs--installing-30_1-on-arch/</guid><description>&lt;p>Seems to be a common post at the moment, so I thought I would quickly put out there how I updated to Emacs 30.1.&lt;/p>
&lt;p>I use an Arch spin called Garuda, running SwayWM, so as its on wayland, this for me is simple, just update the system using &lt;code>pacman -Syu&lt;/code> and &lt;code>emacs-wayland&lt;/code> will pull in 30.1 automatically!&lt;/p>
&lt;figure>&lt;img src="https://www.emacs.dyerdwelling.family/emacs/20250226140529-emacs--Installing-30_1-On-Arch.jpg" width="100%">
&lt;/figure>
&lt;p>For my other environments, or if I want to build old versions of Emacs from source, I use the following script:&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-bash" data-lang="bash">&lt;span style="display:flex;">&lt;span>&lt;span style="color:#75715e">#!/bin/bash
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#75715e">&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#75715e"># Directory for Emacs builds&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>BUILD_ROOT&lt;span style="color:#f92672">=&lt;/span>&lt;span style="color:#e6db74">&amp;#34;&lt;/span>$HOME&lt;span style="color:#e6db74">/emacs-builds&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>INSTALL_ROOT&lt;span style="color:#f92672">=&lt;/span>&lt;span style="color:#e6db74">&amp;#34;&lt;/span>$HOME&lt;span style="color:#e6db74">/emacs-versions&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"># Build dependencies for different distributions&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>ARCH_BUILD_DEPS&lt;span style="color:#f92672">=&lt;/span>&lt;span style="color:#e6db74">&amp;#34;base-devel gtk2 gtk3 libxpm libjpeg-turbo libpng libtiff giflib libxml2 gnutls librsvg&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>SLES_BUILD_DEPS&lt;span style="color:#f92672">=&lt;/span>&lt;span style="color:#e6db74">&amp;#34;gcc gcc-c++ make automake gtk2-devel gtk3-devel libXpm-devel libjpeg8-devel libpng16-devel libtiff-devel giflib-devel libxml2-devel gnutls-devel cairo-devel harfbuzz-devel librsvg-devel&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"># 27.2 2021-03-25&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#75715e"># 28.2 2022-09-12&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#75715e"># 29.4 2024-06-22&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>VERSIONS&lt;span style="color:#f92672">=(&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#e6db74">&amp;#34;emacs-27.2&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#e6db74">&amp;#34;emacs-28.2&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#e6db74">&amp;#34;emacs-29.4&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#e6db74">&amp;#34;emacs-30.1&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#f92672">)&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"># Detect OS&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>detect_os&lt;span style="color:#f92672">()&lt;/span> &lt;span style="color:#f92672">{&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#66d9ef">if&lt;/span> &lt;span style="color:#f92672">[&lt;/span> -f /etc/os-release &lt;span style="color:#f92672">]&lt;/span>; &lt;span style="color:#66d9ef">then&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> . /etc/os-release
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> OS&lt;span style="color:#f92672">=&lt;/span>$NAME
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#66d9ef">else&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> OS&lt;span style="color:#f92672">=&lt;/span>&lt;span style="color:#66d9ef">$(&lt;/span>uname -s&lt;span style="color:#66d9ef">)&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#66d9ef">fi&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#f92672">}&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:#66d9ef">function&lt;/span> prepare_environment&lt;span style="color:#f92672">()&lt;/span> &lt;span style="color:#f92672">{&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> echo &lt;span style="color:#e6db74">&amp;#34;Creating build directories...&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> mkdir -p &lt;span style="color:#e6db74">&amp;#34;&lt;/span>$BUILD_ROOT&lt;span style="color:#e6db74">&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> mkdir -p &lt;span style="color:#e6db74">&amp;#34;&lt;/span>$INSTALL_ROOT&lt;span style="color:#e6db74">&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> detect_os
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> echo &lt;span style="color:#e6db74">&amp;#34;Detected OS: &lt;/span>$OS&lt;span style="color:#e6db74">&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:#66d9ef">case&lt;/span> &lt;span style="color:#e6db74">&amp;#34;&lt;/span>$OS&lt;span style="color:#e6db74">&amp;#34;&lt;/span> in
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> *&lt;span style="color:#e6db74">&amp;#34;SLED&amp;#34;&lt;/span>*|&lt;span style="color:#e6db74">&amp;#34;SLES&amp;#34;&lt;/span>*|*&lt;span style="color:#e6db74">&amp;#34;SUSE&amp;#34;&lt;/span>*&lt;span style="color:#f92672">)&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> echo &lt;span style="color:#e6db74">&amp;#34;Installing build dependencies for SUSE SLES...&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> sudo zypper refresh
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> sudo zypper install -y pattern-devel-base-devel
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> sudo zypper install -y $SLES_BUILD_DEPS
&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:#e6db74">&amp;#34;Garuda&amp;#34;&lt;/span>*&lt;span style="color:#f92672">)&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> echo &lt;span style="color:#e6db74">&amp;#34;Installing build dependencies for Arch Linux...&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> sudo pacman -Syu --needed --noconfirm $ARCH_BUILD_DEPS
&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 we have yay for AUR access (optional)&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#66d9ef">if&lt;/span> ! command -v yay &amp;amp;&amp;gt; /dev/null; &lt;span style="color:#66d9ef">then&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> echo &lt;span style="color:#e6db74">&amp;#34;Installing yay (AUR helper)...&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> cd /tmp
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> git clone https://aur.archlinux.org/yay.git
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> cd yay
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> makepkg -si --noconfirm
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#66d9ef">fi&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:#f92672">)&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> echo &lt;span style="color:#e6db74">&amp;#34;Unsupported OS detected: &lt;/span>$OS&lt;span style="color:#e6db74">&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> echo &lt;span style="color:#e6db74">&amp;#34;Please install build dependencies manually and continue.&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> read -p &lt;span style="color:#e6db74">&amp;#34;Press Enter to continue or Ctrl+C to abort...&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:#66d9ef">esac&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#f92672">}&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:#66d9ef">function&lt;/span> build_emacs&lt;span style="color:#f92672">()&lt;/span> &lt;span style="color:#f92672">{&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> local version&lt;span style="color:#f92672">=&lt;/span>$1
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> local build_dir&lt;span style="color:#f92672">=&lt;/span>&lt;span style="color:#e6db74">&amp;#34;&lt;/span>$BUILD_ROOT&lt;span style="color:#e6db74">/&lt;/span>$version&lt;span style="color:#e6db74">&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> local install_dir&lt;span style="color:#f92672">=&lt;/span>&lt;span style="color:#e6db74">&amp;#34;&lt;/span>$INSTALL_ROOT&lt;span style="color:#e6db74">/&lt;/span>$version&lt;span style="color:#e6db74">&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> echo &lt;span style="color:#e6db74">&amp;#34;Building &lt;/span>$version&lt;span style="color:#e6db74">...&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"># Download and extract&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> cd &lt;span style="color:#e6db74">&amp;#34;&lt;/span>$BUILD_ROOT&lt;span style="color:#e6db74">&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#66d9ef">if&lt;/span> &lt;span style="color:#f92672">[&lt;/span> ! -f &lt;span style="color:#e6db74">&amp;#34;&lt;/span>$version&lt;span style="color:#e6db74">.tar.gz&amp;#34;&lt;/span> &lt;span style="color:#f92672">]&lt;/span>; &lt;span style="color:#66d9ef">then&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> wget &lt;span style="color:#e6db74">&amp;#34;https://ftp.gnu.org/gnu/emacs/&lt;/span>$version&lt;span style="color:#e6db74">.tar.gz&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#66d9ef">fi&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 previous build if exists&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> rm -rf &lt;span style="color:#e6db74">&amp;#34;&lt;/span>$build_dir&lt;span style="color:#e6db74">&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> tar xzf &lt;span style="color:#e6db74">&amp;#34;&lt;/span>$version&lt;span style="color:#e6db74">.tar.gz&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"># Configure and build&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> cd &lt;span style="color:#e6db74">&amp;#34;&lt;/span>$version&lt;span style="color:#e6db74">&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"># Different configure flags for different versions&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#66d9ef">if&lt;/span> &lt;span style="color:#f92672">[[&lt;/span> &lt;span style="color:#e6db74">&amp;#34;&lt;/span>$version&lt;span style="color:#e6db74">&amp;#34;&lt;/span> &lt;span style="color:#f92672">==&lt;/span> &lt;span style="color:#e6db74">&amp;#34;emacs-24.5&amp;#34;&lt;/span> &lt;span style="color:#f92672">||&lt;/span> &lt;span style="color:#e6db74">&amp;#34;&lt;/span>$version&lt;span style="color:#e6db74">&amp;#34;&lt;/span> &lt;span style="color:#f92672">==&lt;/span> &lt;span style="color:#e6db74">&amp;#34;emacs-25.3&amp;#34;&lt;/span> &lt;span style="color:#f92672">]]&lt;/span>; &lt;span style="color:#66d9ef">then&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#75715e"># Older versions use GTK2&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> ./configure &lt;span style="color:#ae81ff">\
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#ae81ff">&lt;/span> --prefix&lt;span style="color:#f92672">=&lt;/span>&lt;span style="color:#e6db74">&amp;#34;&lt;/span>$install_dir&lt;span style="color:#e6db74">&amp;#34;&lt;/span> &lt;span style="color:#ae81ff">\
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#ae81ff">&lt;/span> --with-x-toolkit&lt;span style="color:#f92672">=&lt;/span>gtk2 &lt;span style="color:#ae81ff">\
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#ae81ff">&lt;/span> --with-xpm &lt;span style="color:#ae81ff">\
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#ae81ff">&lt;/span> --with-jpeg &lt;span style="color:#ae81ff">\
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#ae81ff">&lt;/span> --with-png &lt;span style="color:#ae81ff">\
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#ae81ff">&lt;/span> --with-gif &lt;span style="color:#ae81ff">\
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#ae81ff">&lt;/span> --with-tiff &lt;span style="color:#ae81ff">\
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#ae81ff">&lt;/span> --with-gnutls &lt;span style="color:#ae81ff">\
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#ae81ff">&lt;/span> --with-xml2 &lt;span style="color:#ae81ff">\
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#ae81ff">&lt;/span> --with-rsvg
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#66d9ef">else&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#75715e"># Newer versions use GTK3&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> ./configure &lt;span style="color:#ae81ff">\
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#ae81ff">&lt;/span> --prefix&lt;span style="color:#f92672">=&lt;/span>&lt;span style="color:#e6db74">&amp;#34;&lt;/span>$install_dir&lt;span style="color:#e6db74">&amp;#34;&lt;/span> &lt;span style="color:#ae81ff">\
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#ae81ff">&lt;/span> --with-x-toolkit&lt;span style="color:#f92672">=&lt;/span>gtk3 &lt;span style="color:#ae81ff">\
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#ae81ff">&lt;/span> --with-xpm &lt;span style="color:#ae81ff">\
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#ae81ff">&lt;/span> --with-jpeg &lt;span style="color:#ae81ff">\
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#ae81ff">&lt;/span> --with-png &lt;span style="color:#ae81ff">\
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#ae81ff">&lt;/span> --with-gif &lt;span style="color:#ae81ff">\
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#ae81ff">&lt;/span> --with-tiff &lt;span style="color:#ae81ff">\
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#ae81ff">&lt;/span> --with-gnutls &lt;span style="color:#ae81ff">\
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#ae81ff">&lt;/span> --with-xml2 &lt;span style="color:#ae81ff">\
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#ae81ff">&lt;/span> --with-cairo &lt;span style="color:#ae81ff">\
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#ae81ff">&lt;/span> --with-harfbuzz &lt;span style="color:#ae81ff">\
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#ae81ff">&lt;/span> --with-rsvg
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#66d9ef">fi&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"># Use all available cores for compilation&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> make -j&lt;span style="color:#66d9ef">$(&lt;/span>nproc&lt;span style="color:#66d9ef">)&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> make install
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> echo &lt;span style="color:#e6db74">&amp;#34;&lt;/span>$version&lt;span style="color:#e6db74"> installed to &lt;/span>$install_dir&lt;span style="color:#e6db74">&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#f92672">}&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:#66d9ef">function&lt;/span> create_pkgbuild&lt;span style="color:#f92672">()&lt;/span> &lt;span style="color:#f92672">{&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#75715e"># Only create PKGBUILD for Arch Linux&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#66d9ef">if&lt;/span> &lt;span style="color:#f92672">[[&lt;/span> &lt;span style="color:#e6db74">&amp;#34;&lt;/span>$OS&lt;span style="color:#e6db74">&amp;#34;&lt;/span> !&lt;span style="color:#f92672">=&lt;/span> *&lt;span style="color:#e6db74">&amp;#34;Arch Linux&amp;#34;&lt;/span>* &lt;span style="color:#f92672">]]&lt;/span>; &lt;span style="color:#66d9ef">then&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> echo &lt;span style="color:#e6db74">&amp;#34;PKGBUILD creation is only supported on Arch Linux&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#66d9ef">return&lt;/span> &lt;span style="color:#ae81ff">1&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#66d9ef">fi&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> local version&lt;span style="color:#f92672">=&lt;/span>$1
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> local version_num&lt;span style="color:#f92672">=&lt;/span>&lt;span style="color:#e6db74">${&lt;/span>version#emacs-&lt;span style="color:#e6db74">}&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> echo &lt;span style="color:#e6db74">&amp;#34;Creating PKGBUILD for &lt;/span>$version&lt;span style="color:#e6db74">...&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> mkdir -p &lt;span style="color:#e6db74">&amp;#34;&lt;/span>$BUILD_ROOT&lt;span style="color:#e6db74">/pkgbuilds/&lt;/span>$version&lt;span style="color:#e6db74">&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> cd &lt;span style="color:#e6db74">&amp;#34;&lt;/span>$BUILD_ROOT&lt;span style="color:#e6db74">/pkgbuilds/&lt;/span>$version&lt;span style="color:#e6db74">&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> cat &amp;gt; PKGBUILD &lt;span style="color:#e6db74">&amp;lt;&amp;lt; EOF
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#e6db74"># Maintainer: Your Name &amp;lt;your.email@example.com&amp;gt;
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#e6db74">pkgname=$version
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#e6db74">pkgver=$version_num
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#e6db74">pkgrel=1
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#e6db74">pkgdesc=&amp;#34;GNU Emacs version $version_num&amp;#34;
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#e6db74">arch=(&amp;#39;x86_64&amp;#39;)
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#e6db74">url=&amp;#34;https://www.gnu.org/software/emacs/&amp;#34;
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#e6db74">license=(&amp;#39;GPL3&amp;#39;)
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#e6db74">depends=(&amp;#39;gtk3&amp;#39; &amp;#39;libxpm&amp;#39; &amp;#39;libjpeg-turbo&amp;#39; &amp;#39;libpng&amp;#39; &amp;#39;giflib&amp;#39; &amp;#39;libtiff&amp;#39; &amp;#39;libxml2&amp;#39; &amp;#39;gnutls&amp;#39;)
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#e6db74">makedepends=(&amp;#39;base-devel&amp;#39;)
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#e6db74">provides=(&amp;#34;emacs-$version_num&amp;#34;)
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#e6db74">conflicts=(&amp;#34;emacs&amp;#34;)
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#e6db74">source=(&amp;#34;https://ftp.gnu.org/gnu/emacs/emacs-\$pkgver.tar.gz&amp;#34;)
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#e6db74">sha256sums=(&amp;#39;SKIP&amp;#39;)
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#e6db74">
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#e6db74">build() {
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#e6db74"> cd &amp;#34;\$srcdir/emacs-\$pkgver&amp;#34;
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#e6db74"> ./configure \\
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#e6db74"> --prefix=/usr \\
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#e6db74"> --sysconfdir=/etc \\
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#e6db74"> --libexecdir=/usr/lib \\
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#e6db74"> --localstatedir=/var \\
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#e6db74"> --with-x-toolkit=gtk3 \\
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#e6db74"> --with-xpm \\
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#e6db74"> --with-jpeg \\
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#e6db74"> --with-png \\
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#e6db74"> --with-gif \\
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#e6db74"> --with-tiff \\
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#e6db74"> --with-gnutls \\
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#e6db74"> --with-xml2
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#e6db74"> make
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#e6db74">}
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#e6db74">
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#e6db74">package() {
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#e6db74"> cd &amp;#34;\$srcdir/emacs-\$pkgver&amp;#34;
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#e6db74"> make DESTDIR=&amp;#34;\$pkgdir&amp;#34; install
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#e6db74">}
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#e6db74">EOF&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#f92672">}&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"># Main execution&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>echo &lt;span style="color:#e6db74">&amp;#34;This script provides two methods to build Emacs:&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>echo &lt;span style="color:#e6db74">&amp;#34;1. Direct compilation (traditional)&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>echo &lt;span style="color:#e6db74">&amp;#34;2. Using makepkg (Arch Linux only)&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>read -p &lt;span style="color:#e6db74">&amp;#34;Which method do you prefer? (1/2): &amp;#34;&lt;/span> build_method
&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:#66d9ef">case&lt;/span> $build_method in
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> 1&lt;span style="color:#f92672">)&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> prepare_environment
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#66d9ef">for&lt;/span> version in &lt;span style="color:#e6db74">&amp;#34;&lt;/span>&lt;span style="color:#e6db74">${&lt;/span>VERSIONS[@]&lt;span style="color:#e6db74">}&lt;/span>&lt;span style="color:#e6db74">&amp;#34;&lt;/span>; &lt;span style="color:#66d9ef">do&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> build_emacs &lt;span style="color:#e6db74">&amp;#34;&lt;/span>$version&lt;span style="color:#e6db74">&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#66d9ef">done&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"># Create convenience symlinks&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> mkdir -p &lt;span style="color:#e6db74">&amp;#34;&lt;/span>$HOME&lt;span style="color:#e6db74">/bin&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> echo &lt;span style="color:#e6db74">&amp;#34;Creating version-specific symlinks...&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#66d9ef">for&lt;/span> version in &lt;span style="color:#e6db74">&amp;#34;&lt;/span>&lt;span style="color:#e6db74">${&lt;/span>VERSIONS[@]&lt;span style="color:#e6db74">}&lt;/span>&lt;span style="color:#e6db74">&amp;#34;&lt;/span>; &lt;span style="color:#66d9ef">do&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> ln -sf &lt;span style="color:#e6db74">&amp;#34;&lt;/span>$INSTALL_ROOT&lt;span style="color:#e6db74">/&lt;/span>$version&lt;span style="color:#e6db74">/bin/emacs&amp;#34;&lt;/span> &lt;span style="color:#e6db74">&amp;#34;&lt;/span>$HOME&lt;span style="color:#e6db74">/bin/emacs-&lt;/span>&lt;span style="color:#e6db74">${&lt;/span>version#emacs-&lt;span style="color:#e6db74">}&lt;/span>&lt;span style="color:#e6db74">&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#66d9ef">done&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>&lt;/span>&lt;span style="display:flex;">&lt;span> 2&lt;span style="color:#f92672">)&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> detect_os
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#66d9ef">if&lt;/span> &lt;span style="color:#f92672">[[&lt;/span> &lt;span style="color:#e6db74">&amp;#34;&lt;/span>$OS&lt;span style="color:#e6db74">&amp;#34;&lt;/span> !&lt;span style="color:#f92672">=&lt;/span> *&lt;span style="color:#e6db74">&amp;#34;Arch Linux&amp;#34;&lt;/span>* &lt;span style="color:#f92672">]]&lt;/span>; &lt;span style="color:#66d9ef">then&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> echo &lt;span style="color:#e6db74">&amp;#34;makepkg method is only supported on Arch Linux&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> exit &lt;span style="color:#ae81ff">1&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#66d9ef">fi&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> prepare_environment
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#66d9ef">for&lt;/span> version in &lt;span style="color:#e6db74">&amp;#34;&lt;/span>&lt;span style="color:#e6db74">${&lt;/span>VERSIONS[@]&lt;span style="color:#e6db74">}&lt;/span>&lt;span style="color:#e6db74">&amp;#34;&lt;/span>; &lt;span style="color:#66d9ef">do&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> create_pkgbuild &lt;span style="color:#e6db74">&amp;#34;&lt;/span>$version&lt;span style="color:#e6db74">&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> echo &lt;span style="color:#e6db74">&amp;#34;PKGBUILD created for &lt;/span>$version&lt;span style="color:#e6db74">&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> echo &lt;span style="color:#e6db74">&amp;#34;To build, cd to &lt;/span>$BUILD_ROOT&lt;span style="color:#e6db74">/pkgbuilds/&lt;/span>$version&lt;span style="color:#e6db74"> and run &amp;#39;makepkg -si&amp;#39;&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#66d9ef">done&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>&lt;/span>&lt;span style="display:flex;">&lt;span> *&lt;span style="color:#f92672">)&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> echo &lt;span style="color:#e6db74">&amp;#34;Invalid option selected&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> exit &lt;span style="color:#ae81ff">1&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:#66d9ef">esac&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>echo &lt;span style="color:#e6db74">&amp;#34;Build complete. You can run specific versions using:&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#66d9ef">for&lt;/span> version in &lt;span style="color:#e6db74">&amp;#34;&lt;/span>&lt;span style="color:#e6db74">${&lt;/span>VERSIONS[@]&lt;span style="color:#e6db74">}&lt;/span>&lt;span style="color:#e6db74">&amp;#34;&lt;/span>; &lt;span style="color:#66d9ef">do&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> echo &lt;span style="color:#e6db74">&amp;#34;emacs-&lt;/span>&lt;span style="color:#e6db74">${&lt;/span>version#emacs-&lt;span style="color:#e6db74">}&lt;/span>&lt;span style="color:#e6db74">&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#66d9ef">done&lt;/span>
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div></description></item><item><title>Ollama Buddy - Now On MELPA!</title><link>https://www.emacs.dyerdwelling.family/emacs/20250224154229-emacs--ollama-buddy-now-on-melpa/</link><pubDate>Mon, 24 Feb 2025 15:42:00 +0000</pubDate><author>James Dyer</author><guid>https://www.emacs.dyerdwelling.family/emacs/20250224154229-emacs--ollama-buddy-now-on-melpa/</guid><description>&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> ___ _ _ n _ n ___ _ _ _ _
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>| | | |__._|o&lt;span style="color:#f92672">(&lt;/span>Y&lt;span style="color:#f92672">)&lt;/span>o|__._| . |_ _ _| |_| | | |
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>| | | | | . | | . | . | | | . | . |__ |
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>|___|_|_|__/_|_|_|_|__/_|___|___|___|___|___|
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>&lt;a href="https://github.com/captainflasmr/ollama-buddy">https://github.com/captainflasmr/ollama-buddy&lt;/a>&lt;/p>
&lt;p>A friendly Emacs interface for interacting with Ollama models. This package provides a convenient way to integrate Ollama’s local LLM capabilities directly into your Emacs workflow with little or no configuration required.&lt;/p>
&lt;p>Latest improvements:&lt;/p>
&lt;ul>
&lt;li>Chat buffer now more prompt based rather than ad-hoc using C-c C-c to send and C-c C-k to cancel&lt;/li>
&lt;li>Connection monitor now optional, ollama status visibility now maintained by strategic status checks simplifying setup.&lt;/li>
&lt;li>Can now change models from chat buffer using C-c C-m&lt;/li>
&lt;li>Updated intro message with ascii logo&lt;/li>
&lt;li>Suggested default &amp;ldquo;C-c o&amp;rdquo; for &lt;code>ollama-buddy-menu&lt;/code>&lt;/li>
&lt;li>defcustom ollama-buddy-command-definitions now will work in the customization interface.&lt;/li>
&lt;li>The presets directory on github contains elisp files that can be evaluated to generate a role-based menu.&lt;/li>
&lt;li>Added to MELPA, install using the following:&lt;/li>
&lt;/ul>
&lt;!--listend-->
&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 style="color:#e6db74">&amp;#34;C-c o&amp;#34;&lt;/span> &lt;span style="color:#f92672">.&lt;/span> ollama-buddy-menu))
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;ul>
&lt;li>and to add initial model:&lt;/li>
&lt;/ul>
&lt;!--listend-->
&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 style="color:#e6db74">&amp;#34;C-c o&amp;#34;&lt;/span> &lt;span style="color:#f92672">.&lt;/span> ollama-buddy-menu)
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> :custom ollama-buddy-default-model &lt;span style="color:#e6db74">&amp;#34;llama3.2:1b&amp;#34;&lt;/span>)
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;hr>
&lt;p>Roadmap :&lt;/p>
&lt;ul>
&lt;li>
&lt;p>DOING Multi-shot prompt to multiple LLMS to choose best answer&lt;/p>
&lt;/li>
&lt;li>
&lt;p>DOING Customizable Role-Based Menu Preset Generation System&lt;/p>
&lt;/li>
&lt;li>
&lt;p>DOING Distinguishing commands associated with LLMs using colours&lt;/p>
&lt;/li>
&lt;/ul>
&lt;p>For any more information just have a look at the github README!&lt;/p></description></item><item><title>Why I Switched from Magit to VC-Mode (and How It Works for Me)</title><link>https://www.emacs.dyerdwelling.family/emacs/20250221075427-emacs--why-i-switched-from-magit-to-vc-mode/</link><pubDate>Fri, 21 Feb 2025 07:54:00 +0000</pubDate><author>James Dyer</author><guid>https://www.emacs.dyerdwelling.family/emacs/20250221075427-emacs--why-i-switched-from-magit-to-vc-mode/</guid><description>&lt;p>I am currently using &lt;code>vc-mode&lt;/code> for my source code configuration needs. I wouldn’t call myself a die-hard &lt;code>vc-mode&lt;/code> user, (at least not yet!). To earn that title, I think I would need years of experience with it and scoffing at this newfangled thing they call &lt;code>magit&lt;/code>, while my muscle memory recoils at the thought of reading and interacting with a transient menu!&lt;/p>
&lt;figure>&lt;img src="https://www.emacs.dyerdwelling.family/emacs/20250221075427-emacs--Why-I-Switched-from-Magit-to-VC-Mode.jpg" width="100%">
&lt;/figure>
&lt;p>If that were the case, I’d fully embrace the label of a die-hard holdout. But in reality, that just isn&amp;rsquo;t true.&lt;/p>
&lt;p>My version control history has been, well, complicated, or to put it another way, quite simple. It largely revolves around that ancient system known as Subversion (&lt;code>svn&lt;/code>), and leveraging it through the good old command line. It wasn’t until the past couple of years that I even stepped into the Emacs version control ecosystem. So where to start? Naturally, I had heard a lot of praise for &lt;code>magit&lt;/code>.&lt;/p>
&lt;p>First question (and I suspect the usual question): how do you even pronounce it? A soft &amp;ldquo;g&amp;rdquo; like in &lt;strong>magic&lt;/strong>? That sounds better, even if it doesn’t make much sense. Then again, given that I’d likely never discuss it with colleagues at work, pronunciation hardly matters.&lt;/p>
&lt;p>Anyway, I happily &amp;ldquo;magited&amp;rdquo; for a year or so (ironically, I think I learned more about Git&amp;rsquo;s command line through &lt;code>magit&lt;/code> than I would have otherwise). I would have continued using it too, except I decided to make a mental paradigm shift regarding my use in Emacs in general.&lt;/p>
&lt;p>I had reached a point where I think I knew enough Emacs Lisp to start replacing many external packages with custom functions. It was a personal challenge, an opportunity to deepen my understanding of &lt;code>elisp&lt;/code> and tackle more advanced customization. What better way to learn than by replacing something like &lt;code>ace-window&lt;/code>? As it turns out, most of these substitutions were fairly straightforward.&lt;/p>
&lt;p>But &lt;code>magit&lt;/code>? Now that was a tough one. Should I create my own transient menu?, or a more basic menu system? It’s Git, so that could quickly become a tangled mess of options. At the same time, I couldn’t ignore my need for Subversion support at work. &lt;code>magit&lt;/code>, as great as it is, is Git-only (though &lt;code>git svn&lt;/code> exists).&lt;/p>
&lt;p>Wouldn’t it be nice if there was a version control system that seamlessly handled multiple backends?&lt;/p>
&lt;p>Turns out (as we already know), Emacs has one built-in and that is &lt;code>vc-mode&lt;/code>.&lt;/p>
&lt;p>I’ve now been using &lt;code>vc-mode&lt;/code> for about a year. I still appreciate &lt;code>magit&lt;/code>, but for my current workflow, balancing Git for personal projects and Subversion at work, &lt;code>vc-mode&lt;/code> gets the job done.&lt;/p>
&lt;p>I&amp;rsquo;m not generally using Git in any advanced fashion anyway and if I do need to drop into the command line then I can just open up a terminal in emacs.&lt;/p>
&lt;p>Yes &lt;code>vc-mode&lt;/code> has its limitations, for example, why is creating a tag not part of the default keymap of &lt;code>vc-dir&lt;/code>?, actually I think it is and I vaguely remember activating it through &lt;code>vc-create-branch&lt;/code>, was it a universal argument?, I can&amp;rsquo;t remember, but how about just doing things the old-fashioned way, &lt;code>M-x vc-create-tag&lt;/code>?, that works fine for me. Maybe in the not too near future I can yak shave that away anyway! :)&lt;/p>
&lt;p>So far with &lt;code>vc-mode&lt;/code> I’ve made some small improvements, which I shall share in future blog posts:&lt;/p>
&lt;ul>
&lt;li>Adding the ability to toggle tracked files on and off.&lt;/li>
&lt;li>Displaying the current branch permanently in the &lt;code>vc-dir&lt;/code> header line.&lt;/li>
&lt;/ul>
&lt;p>And actually the &lt;code>vc-mode&lt;/code> tweaks above were the original topic of this post, but I seem to have rambled on long enough&amp;hellip;&lt;/p></description></item><item><title>Ollama Buddy - Local LLM Integration for Emacs</title><link>https://www.emacs.dyerdwelling.family/emacs/20250207092636-emacs--ollama-buddy-local-llm-integration-for-emacs/</link><pubDate>Fri, 07 Feb 2025 09:40:00 +0000</pubDate><author>James Dyer</author><guid>https://www.emacs.dyerdwelling.family/emacs/20250207092636-emacs--ollama-buddy-local-llm-integration-for-emacs/</guid><description>&lt;p>I have been playing around with local LLMs recently through ollama and decided to create the basis for an Emacs package to focus on interfacing to ollama specifically. My idea here is to implement something very minimal and as light-weight as possible and that could be run out-of-the-ollamabox with no configuration (obviously the ollama server just needs to be running). I have a deeper dive into my overall design thoughts and decisions in the github README and there are some simple demos:&amp;ldquo;hello&amp;rdquo;&lt;/p>
&lt;p>&lt;a href="https://github.com/captainflasmr/ollama-buddy">https://github.com/captainflasmr/ollama-buddy&lt;/a>&lt;/p>
&lt;figure>&lt;img src="https://www.emacs.dyerdwelling.family/emacs/20250207092636-emacs--Ollama-Buddy-Local-LLM-Integration-for-Emacs.jpg" width="100%">
&lt;/figure>
&lt;hr>
&lt;h2 id="overview">Overview&lt;/h2>
&lt;p>A friendly Emacs interface for interacting with Ollama models. This package provides a convenient way to integrate Ollama&amp;rsquo;s local LLM capabilities directly into your Emacs workflow with little or no configuration required.&lt;/p>
&lt;p>The name is just something a little bit fun and it seems to always remind me of the &amp;ldquo;bathroom buddy&amp;rdquo; from the film Gremlins (although hopefully this will work better than that seemed to!)&lt;/p>
&lt;h2 id="screenshots-demos">Screenshots / Demos&lt;/h2>
&lt;p>Note that all the demos are in real time.&lt;/p>
&lt;p>&lt;strong>Switching to a better model&lt;/strong>&lt;/p>
&lt;figure>&lt;img src="https://www.emacs.dyerdwelling.family/emacs/ollama-buddy/img/ollama-buddy-screen-recording_001.gif" width="100%">
&lt;/figure>
&lt;p>&lt;strong>More conversational&lt;/strong>&lt;/p>
&lt;figure>&lt;img src="https://www.emacs.dyerdwelling.family/emacs/ollama-buddy/img/ollama-buddy-screen-recording_002.gif" width="100%">
&lt;/figure>
&lt;p>&lt;strong>Describing code with different models&lt;/strong>&lt;/p>
&lt;figure>&lt;img src="https://www.emacs.dyerdwelling.family/emacs/ollama-buddy/img/ollama-buddy-screen-recording_003.gif" width="100%">
&lt;/figure>
&lt;p>&lt;strong>Describing code with a more advanced model&lt;/strong>&lt;/p>
&lt;figure>&lt;img src="https://www.emacs.dyerdwelling.family/emacs/ollama-buddy/img/ollama-buddy-screen-recording_004.gif" width="100%">
&lt;/figure>
&lt;p>&lt;strong>The Menu&lt;/strong>&lt;/p>
&lt;figure>&lt;img src="https://www.emacs.dyerdwelling.family/emacs/ollama-buddy/img/ollama-buddy-screenshot_001.png" width="100%">
&lt;/figure>
&lt;h2 id="summary-of-my-design-ethos">Summary of my design ethos&lt;/h2>
&lt;ul>
&lt;li>
&lt;p>&lt;strong>Focused Design Philosophy&lt;/strong>&lt;/p>
&lt;ul>
&lt;li>Dedicated solely to Ollama integration (unlike general-purpose LLM packages)&lt;/li>
&lt;li>Intentionally lightweight and minimal setup&lt;/li>
&lt;li>Particularly suitable for air-gapped systems&lt;/li>
&lt;li>Avoids complex backends and payload configurations&lt;/li>
&lt;/ul>
&lt;/li>
&lt;li>
&lt;p>&lt;strong>Interface Design Choices&lt;/strong>&lt;/p>
&lt;ul>
&lt;li>Flexible, customizable menu through defcustom&lt;/li>
&lt;li>Easy-to-extend command system via simple alist modifications&lt;/li>
&lt;li>Region-based interaction model across all buffers&lt;/li>
&lt;/ul>
&lt;/li>
&lt;li>
&lt;p>&lt;strong>Buffer Implementation&lt;/strong>&lt;/p>
&lt;ul>
&lt;li>Simple, editable chat buffer approach&lt;/li>
&lt;li>Avoids complex modes or bespoke functionality&lt;/li>
&lt;li>Trying to leverage standard Emacs text editing capabilities&lt;/li>
&lt;/ul>
&lt;/li>
&lt;li>
&lt;p>&lt;strong>User Experience&lt;/strong>&lt;/p>
&lt;ul>
&lt;li>&amp;ldquo;AI assistant&amp;rdquo; style welcome interface&lt;/li>
&lt;li>Zero-config startup possible&lt;/li>
&lt;li>Built-in status monitoring and model listing&lt;/li>
&lt;li>Simple tutorial-style introduction&lt;/li>
&lt;/ul>
&lt;/li>
&lt;li>
&lt;p>&lt;strong>Technical Simplicity&lt;/strong>&lt;/p>
&lt;ul>
&lt;li>REST-based Ollama&lt;/li>
&lt;li>Quickly switch between small local LLMs&lt;/li>
&lt;li>Backwards compatibility with older Emacs versions&lt;/li>
&lt;li>Minimal dependencies&lt;/li>
&lt;li>Straightforward configuration options&lt;/li>
&lt;/ul>
&lt;/li>
&lt;/ul>
&lt;h2 id="design-ethos-expanded-why-create-this-package">Design ethos expanded / why create this package?&lt;/h2>
&lt;p>The Ollama Emacs package ecosystem is still emerging. Although there are some great implementations available, they tend to be LLM jack-of-all-trades, catering to various types of LLM integrations, including, of course, the major online offerings.&lt;/p>
&lt;p>Recently, I have been experimenting with a local solution using &lt;code>ollama&lt;/code>. While using &lt;code>ollama&lt;/code> through the terminal interface with &lt;code>readline&lt;/code> naturally leans toward Emacs keybindings, there are a few limitations:&lt;/p>
&lt;ul>
&lt;li>Copy and paste do not use Emacs keybindings like readline navigation. This is due to the way key codes work in terminals, meaning that copying and pasting into Emacs would require using the mouse!&lt;/li>
&lt;li>Searching through a terminal with something like Emacs &lt;code>isearch&lt;/code> can vary depending on the terminal.&lt;/li>
&lt;li>Workflow disruption occur when copying and pasting between Emacs and &lt;code>ollama&lt;/code>.&lt;/li>
&lt;li>There is no easy way to save a session.&lt;/li>
&lt;li>It is not using Emacs!&lt;/li>
&lt;/ul>
&lt;p>I guess you can see where this is going. The question is: how do I integrate a basic query-response mechanism to &lt;code>ollama&lt;/code> into Emacs? This is where existing LLM Emacs packages come in, however, I have always found them to be more geared towards online models with some packages offering experimental implementations of &lt;code>ollama&lt;/code> integration. In my case, I often work on an air-gapped system where downloading or transferring packages is not straightforward. In such an environment, my only option for LLM interaction is &lt;code>ollama&lt;/code> anyway. Given the limitations mentioned earlier of interacting with &lt;code>ollama&lt;/code> through a terminal, why not create a dedicated &lt;code>ollama&lt;/code> Emacs package that is very simple to set up, very lightweight and leverages Emacs&amp;rsquo;s editing capabilities to provide a basic query response interface to &lt;code>ollama&lt;/code>?&lt;/p>
&lt;p>I have found that setting up &lt;code>ollama&lt;/code> within the current crop of LLM Emacs packages can be quite involved. I often struggle with the setup, I get there in the end, but it feels like there&amp;rsquo;s always a long list of payloads, backends, etc., to configure. But what if I just want to integrate Emacs with &lt;code>ollama&lt;/code>? It has a RESTful interface, so could I create a package with minimal setup, allowing users to define a default model in their init file (or select one each time if they prefer)? It could also query the current set of loaded models through the &lt;code>ollama&lt;/code> interface and provide a &lt;code>completing-read&lt;/code> type of model selection, with potentially no model configuration needed!&lt;/p>
&lt;p>Beyond just being lightweight and easy to configure, I also have another idea: a flexible menu system. For a while, I have been using a simple menu-based interface inspired by transient menus. However, I have chosen not to use &lt;code>transient&lt;/code> because I want this package to be compatible with older Emacs versions. Additionally, I haven’t found a compelling use case for a complex transient menu and I prefer a simple, opaque top level menu.&lt;/p>
&lt;p>To achieve this, I have decided to create a flexible &lt;code>defcustom&lt;/code> menu system. Initially, it will be configured for some common actions, but users can easily modify it through the Emacs customization interface by updating a simple alist.&lt;/p>
&lt;p>For example, to refactor code through an LLM, a prepended text string of something like &amp;ldquo;Refactor the following code:&amp;rdquo; is usually applied. To proofread text, &amp;ldquo;Proofread the following:&amp;rdquo; could be prepended to the body of the query. So, why not create a flexible menu where users can easily add their own commands? For instance, if someone wanted a command to uppercase some text (even though Emacs can already do this), they could simply add the following entry to the &lt;code>ollama-buddy-menu-items&lt;/code> alist:&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>(&lt;span style="color:#e6db74">?u&lt;/span> &lt;span style="color:#f92672">.&lt;/span> (&lt;span style="color:#e6db74">&amp;#34;Upcase&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> (lambda () (ollama-buddy--send &lt;span style="color:#e6db74">&amp;#34;convert the following to uppercase:&amp;#34;&lt;/span>))))
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>Then the menu would present a menu item &amp;ldquo;Upcase&amp;rdquo; with a &amp;ldquo;u&amp;rdquo; selection, upcasing the selected region. You could go nuts with this, and in order to double down on the autogeneration of a menu concept, I have provided a &lt;code>defcustom&lt;/code> &lt;code>ollama-buddy-menu-columns&lt;/code> variable so you can flatten out your auto-generated menu as much as you like!&lt;/p>
&lt;p>This is getting rambly, but another key design consideration is how prompts should be handled and in fact how do I go about sending text from within Emacs?. Many implementations rely on a chat buffer as the single focal point, which seems natural to me, so I will follow a similar approach.&lt;/p>
&lt;p>I&amp;rsquo;ve seen different ways of defining a prompt submission mechanism, some using &amp;lt;RET&amp;gt;, others using a dedicated keybinding like C-c &amp;lt;RET&amp;gt;, so, how should I define my prompting mechanism? I have a feeling this could get complicated, so lets use the KISS principle, also, how should text be sent from within Emacs buffers? My solution? simply mark the text and send it, not just from any Emacs buffer, but also within the chat window. It may seem slightly awkward at first (especially in the chat buffer, where you will have to create your prompt and then mark it), but it provides a clear delineation of text and ensures a consistent interface across Emacs. For example, using M-h to mark an element requires minimal effort and greatly simplifies the package implementation. This approach also allows users to use the &lt;strong>&lt;strong>scratch&lt;/strong>&lt;/strong> buffer for sending requests if so desired!&lt;/p>
&lt;p>Many current implementations create a chat buffer with modes for local keybindings and other features. I have decided not to do this and instead, I will provide a simple editable buffer (ASCII text only) where all &lt;code>ollama&lt;/code> interactions will reside. Users will be able to do anything in that buffer; there will be no bespoke Ollama/LLM functionality involved. It will simply be based on a &lt;code>special&lt;/code> buffer and to save a session?, just use &lt;code>save-buffer&lt;/code> to write it to a file, Emacs to the rescue again!&lt;/p>
&lt;p>Regarding the minimal setup philosophy of this package, I also want to include a fun AI assistant-style experience. Nothing complicated, just a bit of logic to display welcome text, show the current &lt;code>ollama&lt;/code> status, and list available models. The idea is that users should be able to jump in immediately. If they know how to install/start &lt;code>ollama&lt;/code>, they can install the package without any configuration, run `M-x ollama-buddy-menu`, and open the chat. At that point, the &amp;ldquo;AI assistant&amp;rdquo; will display the current &lt;code>ollama&lt;/code> status and provide a simple tutorial to help them get started.&lt;/p>
&lt;p>The backend?, well I decided simply to use &lt;code>curl&lt;/code> to stimulate the &lt;code>ollama&lt;/code> RESTful API, so you will need &lt;code>curl&lt;/code> to be installed.&lt;/p>
&lt;p>I have other thoughts regarding the use of local LLMs versus online AI behemoths. The more I use &lt;code>ollama&lt;/code> with Emacs through this package, the more I realize the potential of smaller, local LLMs. This package allows for quick switching between these models while maintaining a decent level of performance on a regular home computer. I could, for instance, load up &lt;code>qwen-coder&lt;/code> for code-related queries (I have found the 7B Q4/5 versions to work particularly well) and switch to a more general model for other queries, such as &lt;code>llama&lt;/code> or even &lt;code>deepseek-r1&lt;/code>.&lt;/p>
&lt;p>Phew! That turned into quite a ramble, maybe I should run this text through &lt;code>ollama-buddy&lt;/code> for proofreading! :)&lt;/p>
&lt;h2 id="ai-assistant">AI assistant&lt;/h2>
&lt;p>A simple text information screen will be presented on the first opening of the chat, or when requested through the menu system:&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>&lt;span style="color:#f92672">====================&lt;/span> n_____n &lt;span style="color:#f92672">====================&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#f92672">====================&lt;/span> | o Y o | &lt;span style="color:#f92672">====================&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>&lt;/span>&lt;span style="display:flex;">&lt;span> │ Welcome to │
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> │ OLLAMA BUDDY │
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> │ Your Friendly AI Assistant │
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> ╰──────────────────────────────────────╯
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> Hi there!
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> ollama RUNNING
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> I&lt;span style="color:#960050;background-color:#1e0010">&amp;#39;&lt;/span>m here to help you with:
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> - Code refactoring and analysis
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> - Writing clear commit messages
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> - Proofreading and text improvements
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> - And much more!
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> Quick Start/Tips:
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> - Try typing a prompt in this buffer
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> - Select/mark all prompt text &lt;span style="color:#f92672">(&lt;/span>&lt;span style="color:#66d9ef">select&lt;/span> region&lt;span style="color:#f92672">)&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> - M-x ollama-buddy-menu
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> - Select menu item
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> - Now wait &lt;span style="color:#66d9ef">for&lt;/span> ollama to &lt;span style="color:#66d9ef">do&lt;/span> its magic!
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> - You can switch models anytime with &lt;span style="color:#f92672">[&lt;/span>m&lt;span style="color:#f92672">]&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> - Use &lt;span style="color:#f92672">[&lt;/span>x&lt;span style="color:#f92672">]&lt;/span> to cancel a running request
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> - You can send to this chat from any buffer
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>-------------------- | @ Y @ | --------------------
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;h2 id="features">Features&lt;/h2>
&lt;ul>
&lt;li>Interactive menu-driven interface&lt;/li>
&lt;li>Dedicated chat buffer with streaming responses&lt;/li>
&lt;li>Easy model switching&lt;/li>
&lt;li>Quick actions for common tasks:
&lt;ul>
&lt;li>Code refactoring&lt;/li>
&lt;li>Git commit message generation&lt;/li>
&lt;li>Code description&lt;/li>
&lt;li>Text proofreading&lt;/li>
&lt;li>Text summarization&lt;/li>
&lt;/ul>
&lt;/li>
&lt;li>Cute ASCII art separators for chat messages&lt;/li>
&lt;li>Region-based interaction with any buffer&lt;/li>
&lt;/ul>
&lt;h2 id="whats-new">Whats New&lt;/h2>
&lt;h3 id="20e477">&lt;span class="timestamp-wrapper">&lt;span class="timestamp">&amp;lt;2025-02-07 Fri&amp;gt;&lt;/span>&lt;/span>&lt;/h3>
&lt;p>Added query finished message.&lt;/p>
&lt;h3 id="33b51a">&lt;span class="timestamp-wrapper">&lt;span class="timestamp">&amp;lt;2025-02-06 Thu&amp;gt;&lt;/span>&lt;/span>&lt;/h3>
&lt;ul>
&lt;li>Initial release&lt;/li>
&lt;li>Basic chat functionality&lt;/li>
&lt;li>Menu-driven interface&lt;/li>
&lt;li>Region-based interactions&lt;/li>
&lt;li>Model switching support&lt;/li>
&lt;/ul>
&lt;h2 id="prerequisites">Prerequisites&lt;/h2>
&lt;ul>
&lt;li>&lt;a href="https://ollama.ai/">Ollama&lt;/a> installed and running locally&lt;/li>
&lt;li>Emacs 27.1 or later&lt;/li>
&lt;li>&lt;code>curl&lt;/code> command-line tool&lt;/li>
&lt;/ul>
&lt;h2 id="installation">Installation&lt;/h2>
&lt;h3 id="manual-installation">Manual Installation&lt;/h3>
&lt;p>Clone this repository:&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-shell" data-lang="shell">&lt;span style="display:flex;">&lt;span>git clone https://github.com/captainflasmr/ollama-buddy.git
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>Add to 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-emacs-lisp" data-lang="emacs-lisp">&lt;span style="display:flex;">&lt;span>(add-to-list &lt;span style="color:#e6db74">&amp;#39;load-path&lt;/span> &lt;span style="color:#e6db74">&amp;#34;path/to/ollama-buddy&amp;#34;&lt;/span>)
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>(require &lt;span style="color:#e6db74">&amp;#39;ollama-buddy&lt;/span>)
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;h3 id="melpa--coming-soon">MELPA (Coming Soon)&lt;/h3>
&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-emacs-lisp" data-lang="emacs-lisp">&lt;span style="display:flex;">&lt;span>(use-package ollama-buddy
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> :ensure &lt;span style="color:#66d9ef">t&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> :bind (&lt;span style="color:#e6db74">&amp;#34;C-c l&amp;#34;&lt;/span> &lt;span style="color:#f92672">.&lt;/span> ollama-buddy-menu))
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;h2 id="usage">Usage&lt;/h2>
&lt;ol>
&lt;li>Start your Ollama server locally&lt;/li>
&lt;li>Use &lt;code>M-x ollama-buddy-menu&lt;/code> or the default keybinding &lt;code>C-c l&lt;/code> to open the menu&lt;/li>
&lt;li>Select your preferred model using the [m] option&lt;/li>
&lt;li>Select text in any buffer&lt;/li>
&lt;li>Choose an action from the menu:&lt;/li>
&lt;/ol>
&lt;table>
&lt;thead>
&lt;tr>
&lt;th>Key&lt;/th>
&lt;th>Action&lt;/th>
&lt;th>Description&lt;/th>
&lt;/tr>
&lt;/thead>
&lt;tbody>
&lt;tr>
&lt;td>o&lt;/td>
&lt;td>Open chat buffer&lt;/td>
&lt;td>Opens the main chat interface&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>m&lt;/td>
&lt;td>Swap model&lt;/td>
&lt;td>Switch between available Ollama models&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>h&lt;/td>
&lt;td>Help assistant&lt;/td>
&lt;td>Display help message&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>l&lt;/td>
&lt;td>Send region&lt;/td>
&lt;td>Send selected text directly to model&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>r&lt;/td>
&lt;td>Refactor code&lt;/td>
&lt;td>Get code refactoring suggestions&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>g&lt;/td>
&lt;td>Git commit message&lt;/td>
&lt;td>Generate commit message for changes&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>d&lt;/td>
&lt;td>Describe code&lt;/td>
&lt;td>Get code explanation&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>p&lt;/td>
&lt;td>Proofread text&lt;/td>
&lt;td>Check text for improvements&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>z&lt;/td>
&lt;td>Make concise&lt;/td>
&lt;td>Reduce wordiness while preserving meaning&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>c&lt;/td>
&lt;td>Custom Prompt&lt;/td>
&lt;td>Enter bespoke prompt through the minibuffer&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>x&lt;/td>
&lt;td>Kill request&lt;/td>
&lt;td>Cancel current Ollama request&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>q&lt;/td>
&lt;td>Quit&lt;/td>
&lt;td>Exit the menu&lt;/td>
&lt;/tr>
&lt;/tbody>
&lt;/table>
&lt;h2 id="key-bindings">Key Bindings&lt;/h2>
&lt;table>
&lt;thead>
&lt;tr>
&lt;th>Key&lt;/th>
&lt;th>Description&lt;/th>
&lt;/tr>
&lt;/thead>
&lt;tbody>
&lt;tr>
&lt;td>&lt;code>C-c l&lt;/code>&lt;/td>
&lt;td>Open ollama-buddy menu&lt;/td>
&lt;/tr>
&lt;/tbody>
&lt;/table>
&lt;h2 id="customization">Customization&lt;/h2>
&lt;table>
&lt;thead>
&lt;tr>
&lt;th>Custom variable&lt;/th>
&lt;th>Description&lt;/th>
&lt;/tr>
&lt;/thead>
&lt;tbody>
&lt;tr>
&lt;td>ollama-buddy-awesome-categorize-prompts&lt;/td>
&lt;td>Whether to categorize prompts based on common keywords.&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>ollama-buddy-awesome-prompts-file&lt;/td>
&lt;td>Filename containing the prompts within the repository.&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>ollama-buddy-grok-api-endpoint&lt;/td>
&lt;td>Endpoint for Grok chat completions API.&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>ollama-buddy-remote-models&lt;/td>
&lt;td>List of available remote models.&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>ollama-buddy-command-definitions&lt;/td>
&lt;td>Comprehensive command definitions for Ollama Buddy.&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>ollama-buddy-codestral-default-model&lt;/td>
&lt;td>Default Mistral Codestral model to use.&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>ollama-buddy-interface-level&lt;/td>
&lt;td>Level of interface complexity to display.&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>ollama-buddy-show-params-in-header&lt;/td>
&lt;td>Whether to show modified parameters in the header line.&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>ollama-buddy-user-prompts-default-categories&lt;/td>
&lt;td>List of default categories for user system prompts.&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>ollama-buddy-debug-mode&lt;/td>
&lt;td>When non-nil, show raw JSON messages in a debug buffer.&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>ollama-buddy-rag-qdrant-url&lt;/td>
&lt;td>URL for Qdrant server.&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>ollama-buddy-codestral-api-key&lt;/td>
&lt;td>API key for accessing Mistral Codestral services.&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>ollama-buddy-mode&lt;/td>
&lt;td>Non-nil if Ollama-Buddy mode is enabled.&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>ollama-buddy-grok-marker-prefix&lt;/td>
&lt;td>Prefix used to identify Grok models in the ollama-buddy interface.&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>ollama-buddy-reasoning-markers&lt;/td>
&lt;td>List of marker pairs that encapsulate reasoning/thinking sections.&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>ollama-buddy-awesome-update-on-startup&lt;/td>
&lt;td>Whether to automatically update prompts when Emacs starts.&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>ollama-buddy-context-bar-width&lt;/td>
&lt;td>Width of the context progress bar in characters.&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>ollama-buddy-sessions-directory&lt;/td>
&lt;td>Directory containing ollama-buddy session files.&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>ollama-buddy-history-model-view-mode-map&lt;/td>
&lt;td>Keymap for model-specific history viewing mode.&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>ollama-buddy-rag-provider-type&lt;/td>
&lt;td>Type of vector database provider to use.&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>ollama-buddy-menu-columns&lt;/td>
&lt;td>Number of columns to display in the Ollama Buddy menu.&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>ollama-buddy-gemini-api-endpoint&lt;/td>
&lt;td>Endpoint format for Google Gemini API.&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>ollama-buddy-fabric-pattern-categories&lt;/td>
&lt;td>List of pattern categories to focus on when listing patterns.&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>ollama-buddy-rag-qdrant-api-key&lt;/td>
&lt;td>API key for Qdrant server.&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>ollama-buddy-status-update-interval&lt;/td>
&lt;td>Interval in seconds to update the status line with background operations.&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>ollama-buddy-fabric-update-on-startup&lt;/td>
&lt;td>Whether to automatically update patterns when Emacs starts.&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>ollama-buddy-available-models&lt;/td>
&lt;td>List of available models to pull from Ollama Hub.&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>ollama-buddy-grok-max-tokens&lt;/td>
&lt;td>Maximum number of tokens to generate in the response.&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>ollama-buddy-highlight-models-enabled&lt;/td>
&lt;td>Highlight model names with distinctive colors in Ollama Buddy buffers.&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>ollama-buddy-claude-max-tokens&lt;/td>
&lt;td>Maximum number of tokens to generate in the response.&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>ollama-buddy-openai-marker-prefix&lt;/td>
&lt;td>Prefix to indicate that a model is from OpenAI rather than Ollama.&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>ollama-buddy-history-enabled&lt;/td>
&lt;td>Whether to use conversation history in Ollama requests.&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>ollama-buddy-grok-api-key&lt;/td>
&lt;td>API key for accessing Grok services.&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>ollama-buddy-grok-default-model&lt;/td>
&lt;td>Default Grok model to use.&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>ollama-buddy-concat-exclude-directories&lt;/td>
&lt;td>&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>ollama-buddy-claude-marker-prefix&lt;/td>
&lt;td>Prefix used to identify Claude models in the model list.&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>ollama-buddy-roles-directory&lt;/td>
&lt;td>Directory containing ollama-buddy role preset files.&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>ollama-buddy-codestral-temperature&lt;/td>
&lt;td>Temperature setting for Mistral Codestral requests (0.0-2.0).&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>ollama-buddy-fabric-local-dir&lt;/td>
&lt;td>Local directory where Fabric patterns will be stored.&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>ollama-buddy-show-history-indicator&lt;/td>
&lt;td>Whether to show the history indicator in the header line.&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>ollama-buddy-context-size-thresholds&lt;/td>
&lt;td>Thresholds for context usage warnings.&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>ollama-buddy-rag-chroma-url&lt;/td>
&lt;td>URL for Chroma server.&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>ollama-buddy-rag-collection-name&lt;/td>
&lt;td>Name of the vecdb collection to use for RAG.&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>ollama-buddy-mode-map&lt;/td>
&lt;td>Keymap for ollama-buddy mode.&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>ollama-buddy-marker-prefix&lt;/td>
&lt;td>Prefix used to identify Ollama models in the ollama-buddy interface.&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>ollama-buddy-codestral-marker-prefix&lt;/td>
&lt;td>Prefix to indicate that a model is from Mistral Codestral rather than Ollama.&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>ollama-buddy-max-file-size&lt;/td>
&lt;td>Maximum size for attached files in bytes.&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>ollama-buddy-fabric-patterns-subdir&lt;/td>
&lt;td>Subdirectory within the Fabric repo containing the patterns.&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>ollama-buddy-context-display-type&lt;/td>
&lt;td>How to display context usage in the status bar.&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>ollama-buddy-autocomplete-model&lt;/td>
&lt;td>&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>ollama-buddy-awesome-repo-url&lt;/td>
&lt;td>URL of the Awesome ChatGPT Prompts GitHub repository.&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>ollama-buddy-user-prompts-directory&lt;/td>
&lt;td>Directory where user system prompts are stored.&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>ollama-buddy-openai-api-endpoint&lt;/td>
&lt;td>Endpoint for OpenAI chat completions API.&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>ollama-buddy-vision-models&lt;/td>
&lt;td>List of models known to support vision capabilities.&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>ollama-buddy-params-active&lt;/td>
&lt;td>Currently active values for Ollama API parameters.&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>ollama-buddy-embedding-model&lt;/td>
&lt;td>The default model to use for generating embeddings.&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>ollama-buddy-codestral-api-endpoint&lt;/td>
&lt;td>Endpoint for Mistral Codestral API.&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>ollama-buddy-openai-default-model&lt;/td>
&lt;td>Default OpenAI model to use.&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>ollama-buddy-claude-default-model&lt;/td>
&lt;td>Default Claude model to use.&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>ollama-buddy-params-modified&lt;/td>
&lt;td>Set of parameters that have been explicitly modified by the user.&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>ollama-buddy-gemini-temperature&lt;/td>
&lt;td>Temperature setting for Gemini requests (0.0-1.0).&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>ollama-buddy-current-session-name&lt;/td>
&lt;td>The name of the currently loaded session.&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>ollama-buddy-host&lt;/td>
&lt;td>Host where Ollama server is running.&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>ollama-buddy-curl-timeout&lt;/td>
&lt;td>Timeout in seconds for curl requests.&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>ollama-buddy-image-formats&lt;/td>
&lt;td>List of regular expressions matching supported image file formats.&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>ollama-buddy-streaming-enabled&lt;/td>
&lt;td>Whether to use streaming mode for responses.&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>ollama-buddy-max-history-length&lt;/td>
&lt;td>Maximum number of message pairs to keep in conversation history.&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>ollama-buddy-gemini-default-model&lt;/td>
&lt;td>Default Gemini model to use.&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>ollama-buddy-default-model&lt;/td>
&lt;td>Default Ollama model to use.&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>ollama-buddy-claude-temperature&lt;/td>
&lt;td>Temperature setting for Claude requests (0.0-1.0).&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>ollama-buddy-vision-enabled&lt;/td>
&lt;td>Whether to enable vision support for models that support it.&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>ollama-buddy-display-token-stats&lt;/td>
&lt;td>Whether to display token usage statistics in responses.&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>ollama-buddy-show-context-percentage&lt;/td>
&lt;td>Whether to show context percentage in the status bar.&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>ollama-buddy-claude-api-endpoint&lt;/td>
&lt;td>Endpoint for Anthropic Claude API.&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>ollama-buddy-gemini-api-key&lt;/td>
&lt;td>API key for accessing Google Gemini services.&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>ollama-buddy-openai-max-tokens&lt;/td>
&lt;td>Maximum number of tokens to generate in the response.&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>ollama-buddy-claude-api-key&lt;/td>
&lt;td>API key for accessing Anthropic Claude services.&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>ollama-buddy-params-profiles&lt;/td>
&lt;td>Predefined parameter profiles for different usage scenarios.&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>ollama-buddy-awesome-local-dir&lt;/td>
&lt;td>Local directory where Awesome ChatGPT Prompts will be stored.&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>ollama-buddy-params-defaults&lt;/td>
&lt;td>Default values for Ollama API parameters.&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>ollama-buddy-mode-line-segment&lt;/td>
&lt;td>Mode line segment for Ollama Buddy.&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>ollama-buddy-grok-temperature&lt;/td>
&lt;td>Temperature setting for Grok requests (0.0-1.0).&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>ollama-buddy-port&lt;/td>
&lt;td>Port where Ollama server is running.&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>ollama-buddy-default-register&lt;/td>
&lt;td>Default register to store the current response when not in multishot mode.&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>ollama-buddy-fabric-repo-url&lt;/td>
&lt;td>URL of the Fabric GitHub repository.&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>ollama-buddy-gemini-max-tokens&lt;/td>
&lt;td>Maximum number of tokens to generate in the response.&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>ollama-buddy-modelfile-directory&lt;/td>
&lt;td>Directory for storing temporary Modelfiles.&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>ollama-buddy-fallback-context-sizes&lt;/td>
&lt;td>Mapping of model names to their default context sizes.&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>ollama-buddy-communication-backend&lt;/td>
&lt;td>Communication backend to use for Ollama API requests.&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>ollama-buddy-codestral-max-tokens&lt;/td>
&lt;td>Maximum number of tokens to generate in the response.&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>ollama-buddy-curl-executable&lt;/td>
&lt;td>Path to the curl executable.&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>ollama-buddy-context-bar-chars&lt;/td>
&lt;td>Characters used to draw the context progress bar.&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>ollama-buddy-supported-file-types&lt;/td>
&lt;td>List of regex patterns for supported file types.&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>ollama-buddy-openai-temperature&lt;/td>
&lt;td>Temperature setting for OpenAI requests (0.0-2.0).&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>ollama-buddy-convert-markdown-to-org&lt;/td>
&lt;td>Whether to automatically convert markdown to `org-mode&amp;rsquo; format in responses.&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>ollama-buddy-mode-hook&lt;/td>
&lt;td>Hook run after entering or leaving `ollama-buddy-mode'.&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>ollama-buddy-gemini-marker-prefix&lt;/td>
&lt;td>Prefix used to identify Gemini models in the ollama-buddy interface.&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>ollama-buddy-hide-reasoning&lt;/td>
&lt;td>When non-nil, hide reasoning/thinking blocks from the stream output.&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>ollama-buddy-openai-api-key&lt;/td>
&lt;td>API key for accessing OpenAI services.&lt;/td>
&lt;/tr>
&lt;/tbody>
&lt;/table>
&lt;p>Customize the package to the default startup using:&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-current-model &lt;span style="color:#e6db74">&amp;#34;qwen-4q:latest&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">;; Change number of menu columns&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>(setq ollama-buddy-menu-columns &lt;span style="color:#ae81ff">4&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">;; Customize separators&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>(setq ollama-buddy-separator-1 &lt;span style="color:#e6db74">&amp;#34;Your custom separator here&amp;#34;&lt;/span>)
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>(setq ollama-buddy-separator-2 &lt;span style="color:#e6db74">&amp;#34;Another custom separator&amp;#34;&lt;/span>)
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>Available customization options:&lt;/p>
&lt;h2 id="contributing">Contributing&lt;/h2>
&lt;p>Contributions are welcome! Please:&lt;/p>
&lt;ol>
&lt;li>Fork the repository&lt;/li>
&lt;li>Create a feature branch&lt;/li>
&lt;li>Commit your changes&lt;/li>
&lt;li>Open a pull request&lt;/li>
&lt;/ol>
&lt;h2 id="license">License&lt;/h2>
&lt;p>&lt;a href="https://opensource.org/licenses/MIT">MIT License&lt;/a>&lt;/p>
&lt;h2 id="acknowledgments">Acknowledgments&lt;/h2>
&lt;ul>
&lt;li>&lt;a href="https://ollama.ai/">Ollama&lt;/a> for making local LLM inference accessible&lt;/li>
&lt;li>Emacs community for continuous inspiration&lt;/li>
&lt;/ul>
&lt;h2 id="issues">Issues&lt;/h2>
&lt;p>Report issues on the &lt;a href="https://github.com/captainflasmr/ollama-buddy/issues">GitHub Issues page&lt;/a>&lt;/p>
&lt;h2 id="alternative-llm-based-packages">Alternative LLM based packages&lt;/h2>
&lt;p>To the best of my knowledge, there are currently a few Emacs packages related to Ollama, though the ecosystem is still relatively young:&lt;/p>
&lt;ol>
&lt;li>
&lt;p>&lt;strong>llm.el&lt;/strong> (by Jacob Hacker)&lt;/p>
&lt;ul>
&lt;li>A more general LLM interface package that supports Ollama as one of its backends&lt;/li>
&lt;li>GitHub: &lt;a href="https://github.com/ahyatt/llm">https://github.com/ahyatt/llm&lt;/a>&lt;/li>
&lt;li>Provides a more abstracted approach to interacting with language models&lt;/li>
&lt;li>Supports multiple backends including Ollama, OpenAI, and others&lt;/li>
&lt;/ul>
&lt;/li>
&lt;li>
&lt;p>&lt;strong>gptel&lt;/strong> (by Karthik Chikmagalur)&lt;/p>
&lt;ul>
&lt;li>While primarily designed for ChatGPT and other online services, it has experimental Ollama support&lt;/li>
&lt;li>GitHub: &lt;a href="https://github.com/karthink/gptel">https://github.com/karthink/gptel&lt;/a>&lt;/li>
&lt;li>Offers a more integrated chat buffer experience&lt;/li>
&lt;li>Has some basic Ollama integration, though it&amp;rsquo;s not the primary focus&lt;/li>
&lt;/ul>
&lt;/li>
&lt;li>
&lt;p>&lt;strong>chatgpt-shell&lt;/strong> (by xenodium)&lt;/p>
&lt;ul>
&lt;li>Primarily designed for ChatGPT, but has some exploration of local model support&lt;/li>
&lt;li>GitHub: &lt;a href="https://github.com/xenodium/chatgpt-shell">https://github.com/xenodium/chatgpt-shell&lt;/a>&lt;/li>
&lt;li>Not specifically Ollama-focused, but interesting for comparison&lt;/li>
&lt;/ul>
&lt;/li>
&lt;li>
&lt;p>&lt;strong>ellama&lt;/strong>&lt;/p>
&lt;ul>
&lt;li>TODO&lt;/li>
&lt;/ul>
&lt;/li>
&lt;/ol>
&lt;h2 id="alternative-package-comparison">Alternative package comparison&lt;/h2>
&lt;p>Let&amp;rsquo;s compare ollama-buddy to the existing solutions:&lt;/p>
&lt;ol>
&lt;li>
&lt;p>&lt;strong>llm.el&lt;/strong>&lt;/p>
&lt;ul>
&lt;li>
&lt;p>&lt;strong>Pros&lt;/strong>:&lt;/p>
&lt;ul>
&lt;li>Provides a generic LLM interface&lt;/li>
&lt;li>Supports multiple backends&lt;/li>
&lt;li>More abstracted and potentially more extensible&lt;/li>
&lt;/ul>
&lt;/li>
&lt;li>
&lt;p>&lt;strong>Cons&lt;/strong>:&lt;/p>
&lt;ul>
&lt;li>Less Ollama-specific&lt;/li>
&lt;li>More complex configuration&lt;/li>
&lt;li>Might have overhead from supporting multiple backends&lt;/li>
&lt;/ul>
&lt;/li>
&lt;/ul>
&lt;p>&lt;code>ollama-buddy&lt;/code> is more:&lt;/p>
&lt;ul>
&lt;li>Directly focused on Ollama&lt;/li>
&lt;li>Lightweight and Ollama-native&lt;/li>
&lt;li>Provides a more interactive, menu-driven approach&lt;/li>
&lt;li>Simpler to set up for Ollama specifically&lt;/li>
&lt;/ul>
&lt;/li>
&lt;li>
&lt;p>&lt;strong>gptel&lt;/strong>&lt;/p>
&lt;ul>
&lt;li>
&lt;p>&lt;strong>Pros&lt;/strong>:&lt;/p>
&lt;ul>
&lt;li>Sophisticated chat buffer interface&lt;/li>
&lt;li>Active development&lt;/li>
&lt;li>Good overall UX&lt;/li>
&lt;/ul>
&lt;/li>
&lt;li>
&lt;p>&lt;strong>Cons&lt;/strong>:&lt;/p>
&lt;ul>
&lt;li>Primarily designed for online services&lt;/li>
&lt;li>Ollama support is experimental&lt;/li>
&lt;li>More complex architecture&lt;/li>
&lt;/ul>
&lt;/li>
&lt;/ul>
&lt;p>&lt;code>ollama-buddy&lt;/code> differentiates by:&lt;/p>
&lt;ul>
&lt;li>Being purpose-built for Ollama&lt;/li>
&lt;li>Offering a more flexible, function-oriented approach&lt;/li>
&lt;li>Providing a quick, lightweight interaction model&lt;/li>
&lt;li>Having a minimal, focused design&lt;/li>
&lt;/ul>
&lt;/li>
&lt;li>
&lt;p>&lt;strong>chatgpt-shell&lt;/strong>&lt;/p>
&lt;ul>
&lt;li>
&lt;p>&lt;strong>Pros&lt;/strong>:&lt;/p>
&lt;ul>
&lt;li>Mature shell-based interaction model&lt;/li>
&lt;li>Rich interaction capabilities&lt;/li>
&lt;/ul>
&lt;/li>
&lt;li>
&lt;p>&lt;strong>Cons&lt;/strong>:&lt;/p>
&lt;ul>
&lt;li>Not truly Ollama-native&lt;/li>
&lt;li>Primarily focused on online services&lt;/li>
&lt;li>More complex setup&lt;/li>
&lt;/ul>
&lt;/li>
&lt;/ul>
&lt;p>&lt;code>ollama-buddy&lt;/code> stands out by:&lt;/p>
&lt;ul>
&lt;li>Being specifically designed for Ollama&lt;/li>
&lt;li>Offering a simpler, more direct interaction model&lt;/li>
&lt;li>Providing a quick menu-based interface&lt;/li>
&lt;li>Having minimal dependencies&lt;/li>
&lt;/ul>
&lt;/li>
&lt;li>
&lt;p>&lt;strong>ellama&lt;/strong>&lt;/p>
&lt;ul>
&lt;li>TODO&lt;/li>
&lt;/ul>
&lt;/li>
&lt;/ol></description></item><item><title>Copying completion candidate to the clipboard</title><link>https://www.emacs.dyerdwelling.family/emacs/20250130074049-emacs--copy-icomplete-candidate-to-clipboard/</link><pubDate>Thu, 30 Jan 2025 07:40:00 +0000</pubDate><author>James Dyer</author><guid>https://www.emacs.dyerdwelling.family/emacs/20250130074049-emacs--copy-icomplete-candidate-to-clipboard/</guid><description>&lt;p>In my continuing quest to replace all my external use packages with my own elisp versions so I can still follow my current workflow on an air-gapped system, I would like to replace a single function I use often from &lt;code>embark&lt;/code>&lt;/p>
&lt;p>&lt;code>embark&lt;/code> allows an action in a certain context, and there are a lot of actions to choose from, but I found I was generally using very few, so to remove my reliance on &lt;code>embark&lt;/code> I think I can implement these actions myself.&lt;/p>
&lt;figure>&lt;img src="https://www.emacs.dyerdwelling.family/emacs/20250130074049-emacs--Copy-icomplete-candidate-to-clipboard.jpg" width="100%">
&lt;/figure>
&lt;p>The main one is to copy the current completion candidate. For example, I might be searching for a function name, so &lt;code>describe-function&lt;/code> function for example, ha!, I just did it!, using &lt;code>describe-function&lt;/code> to select &lt;code>describe-function&lt;/code> and then copying to the clipboard to then paste in to this blog article!&lt;/p>
&lt;p>Well that is it, so lets code it up:&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-icomplete-copy-candidate ()
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#e6db74">&amp;#34;Copy the current Icomplete candidate to the kill ring.&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 ((candidate (&lt;span style="color:#a6e22e">car&lt;/span> completion-all-sorted-completions)))
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> (when candidate
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> (kill-new (&lt;span style="color:#a6e22e">substring-no-properties&lt;/span> candidate))
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> (let ((copied-text candidate))
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> (run-with-timer &lt;span style="color:#ae81ff">0&lt;/span> &lt;span style="color:#66d9ef">nil&lt;/span> (lambda ()
&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;Copied: %s&amp;#34;&lt;/span> copied-text)))
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> (&lt;span style="color:#a6e22e">abort-recursive-edit&lt;/span>)))))
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>(global-set-key (kbd &lt;span style="color:#e6db74">&amp;#34;C-c ,&amp;#34;&lt;/span>) &lt;span style="color:#e6db74">&amp;#39;find-file-at-point&lt;/span>)
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>(&lt;span style="color:#a6e22e">define-key&lt;/span> minibuffer-local-completion-map (kbd &lt;span style="color:#e6db74">&amp;#34;C-c ,&amp;#34;&lt;/span>) &lt;span style="color:#e6db74">&amp;#39;my-icomplete-copy-candidate&lt;/span>)
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>Note of course that I use the built-in &lt;code>fido-mode&lt;/code> and therefore &lt;code>icomplete&lt;/code> so there might be possibly a different implementation if you were using something like vertico for example.&lt;/p>
&lt;p>Also as a bonus, note that I have added another common &lt;code>embark&lt;/code> action and that is to navigate to a file at point. Fortunately Emacs has this function built-in so I again bound to my former standard &lt;code>embark&lt;/code> activation key.&lt;/p></description></item><item><title>Creating a small local elisp rainbow-mode solution</title><link>https://www.emacs.dyerdwelling.family/emacs/20241209081021-emacs--emacs-core-rainbow-mode/</link><pubDate>Sat, 25 Jan 2025 07:50:00 +0000</pubDate><author>James Dyer</author><guid>https://www.emacs.dyerdwelling.family/emacs/20241209081021-emacs--emacs-core-rainbow-mode/</guid><description>&lt;p>In this post, as part of my ongoing mission to replace all (or as many as possible) external packages with pure elisp, I’ll demonstrate how to implement a lightweight alternative to the popular &lt;code>rainbow-mode&lt;/code> package, which highlights hex colour codes in all their vibrant glory. I use this quite often, especially when &amp;ldquo;ricing&amp;rdquo; my tiling window setup.&lt;/p>
&lt;figure>&lt;img src="https://www.emacs.dyerdwelling.family/emacs/20241209081021-emacs--Emacs-core-rainbow-mode.jpg" width="100%">
&lt;/figure>
&lt;p>Instead of relying on &lt;code>rainbow-mode&lt;/code>, lets create a custom Emacs function to colourize hex values in the buffer, along with another function to clear these highlights. We&amp;rsquo;ll also bind this functionality to modes like &lt;code>prog-mode&lt;/code>, &lt;code>org-mode&lt;/code>, and &lt;code>conf-space-mode&lt;/code>.&lt;/p>
&lt;hr>
&lt;p>Hexadecimal colour codes (like &lt;code>#ff0000&lt;/code> for red, &lt;code>#00ff00&lt;/code> for green, etc.) often appear in programming, configuration, or document files. Visualizing these colours directly within your buffer makes working with them much easier.&lt;/p>
&lt;p>Here&amp;rsquo;s how you can create a simple function to overlay these colours in Emacs:&lt;/p>
&lt;p>The following function highlights any 3 or 6 character hex colour codes it finds in the current buffer (e.g., &lt;code>#ff0000&lt;/code>, &lt;code>#33aaff&lt;/code>, &lt;code>#fff&lt;/code>). It uses Emacs overlays to render the colour as the background of the matched text.&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/rainbow-mode ()
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#e6db74">&amp;#34;Overlay colors represented as hex values in the current buffer.&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> (remove-overlays (&lt;span style="color:#a6e22e">point-min&lt;/span>) (&lt;span style="color:#a6e22e">point-max&lt;/span>))
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> (let ((hex-color-regex &lt;span style="color:#e6db74">&amp;#34;#[0-9a-fA-F]\\{3,6\\}&amp;#34;&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">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> hex-color-regex &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> (let* ((color (match-string &lt;span style="color:#ae81ff">0&lt;/span>))
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> (overlay (&lt;span style="color:#a6e22e">make-overlay&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> (if (string-greaterp color &lt;span style="color:#e6db74">&amp;#34;#888888&amp;#34;&lt;/span>)
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> (&lt;span style="color:#a6e22e">overlay-put&lt;/span> overlay &lt;span style="color:#e6db74">&amp;#39;face&lt;/span> &lt;span style="color:#f92672">`&lt;/span>(:background &lt;span style="color:#f92672">,&lt;/span>color :foreground &lt;span style="color:#e6db74">&amp;#34;black&amp;#34;&lt;/span>))
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> (&lt;span style="color:#a6e22e">overlay-put&lt;/span> overlay &lt;span style="color:#e6db74">&amp;#39;face&lt;/span> &lt;span style="color:#f92672">`&lt;/span>(:background &lt;span style="color:#f92672">,&lt;/span>color :foreground &lt;span style="color:#e6db74">&amp;#34;white&amp;#34;&lt;/span>))))))))
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>As an additional note, I have added a little logic regarding generating a contrasting foreground colour to help the hex text value remain prominent.&lt;/p>
&lt;p>Sometimes you may want to remove all colour overlays from the current buffer. The following function clears the overlays applied by &lt;code>my/rainbow-mode&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/rainbow-mode-clear ()
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#e6db74">&amp;#34;Remove all hex color overlays in the current buffer.&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> (remove-overlays (&lt;span style="color:#a6e22e">point-min&lt;/span>) (&lt;span style="color:#a6e22e">point-max&lt;/span>)))
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>Now that we have created &lt;code>my/rainbow-mode&lt;/code>, let’s make it automatically activate in relevant modes like &lt;code>prog-mode&lt;/code>, &lt;code>org-mode&lt;/code>, and &lt;code>conf-space-mode&lt;/code>. You can achieve this by adding hooks to these modes:&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-emacs-lisp" data-lang="emacs-lisp">&lt;span style="display:flex;">&lt;span>(add-hook &lt;span style="color:#e6db74">&amp;#39;prog-mode-hook&lt;/span> &lt;span style="color:#a6e22e">#&amp;#39;&lt;/span>my/rainbow-mode)
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>(add-hook &lt;span style="color:#e6db74">&amp;#39;org-mode-hook&lt;/span> &lt;span style="color:#a6e22e">#&amp;#39;&lt;/span>my/rainbow-mode)
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>(add-hook &lt;span style="color:#e6db74">&amp;#39;conf-space-mode-hook&lt;/span> &lt;span style="color:#a6e22e">#&amp;#39;&lt;/span>my/rainbow-mode)
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>By doing this, whenever you open a buffer in a programming file, Org file, or certain configuration files, &lt;code>my/rainbow-mode&lt;/code> will automatically highlight any hex colour codes present in the file.&lt;/p>
&lt;hr>
&lt;p>And there we go!, by defining &lt;code>my/rainbow-mode&lt;/code>, you can enjoy the benefits of colour-highlighting hex values in Emacs while staying true to a minimalist, package-free configuration!&lt;/p></description></item><item><title>Streamlining Navigation in Org-Mode using an adapted org-goto</title><link>https://www.emacs.dyerdwelling.family/emacs/20241228131958-emacs--streamlining-navigation-in-org-mode/</link><pubDate>Sat, 18 Jan 2025 11:08:00 +0000</pubDate><author>James Dyer</author><guid>https://www.emacs.dyerdwelling.family/emacs/20241228131958-emacs--streamlining-navigation-in-org-mode/</guid><description>&lt;p>Navigating through lengthy org files can sometimes feel cumbersome. To address this I have tweaked &lt;code>org-goto&lt;/code> to be a little more usable through the minibuffer completion system to jump to any org heading.&lt;/p>
&lt;p>I have been aware of the &lt;code>org-goto&lt;/code> command for a while now, which technically, allows for the efficient navigation through an org file. Every now and then I give it a go, but it just feels clunky and some of the keybindings don&amp;rsquo;t quite feel intuitively Emacs for some reason.&lt;/p>
&lt;p>Now my org files are getting more elaborate, I would like a better way to navigate and especially through the hierarchy of headings.&lt;/p>
&lt;p>What about &lt;code>org-imenu-depth&lt;/code> I hear you say! Well as far as I can tell, this allows stepping through to a defined subheading level but while going through multiple menu/hierarchical selection steps. For efficiency, I would like a way to flatten the subheadings for a one-shot completion selection method. This is where &lt;code>org-goto&lt;/code> can actually be adapted to satisfy this need, and I think leverages the org refile menu system.&lt;/p>
&lt;p>To achieve this, some variables will require changing, firstly:&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-emacs-lisp" data-lang="emacs-lisp">&lt;span style="display:flex;">&lt;span>(setq org-goto-interface &lt;span style="color:#e6db74">&amp;#39;outline-path-completionp&lt;/span>)
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>The &lt;code>org-goto-interface&lt;/code> variable defines the interface you use to navigate when calling &lt;code>org-goto&lt;/code>. By default, org goto uses a mixed interface with direct completion and an alternative outline-based navigation using buffers. I prefer the &lt;strong>outline-path-completionp&lt;/strong> setting, which provides a breadcrumb-like structure for easier navigation through the minibuffer completion system.&lt;/p>
&lt;p>By default, Org mode’s outline-path interface works in steps, which means you select one heading level at a time. I find this behaviour a bit clunky, especially for larger files. Fortunately, you can disable this step-by-step behaviour by setting &lt;code>org-outline-path-complete-in-steps&lt;/code> to &lt;code>nil&lt;/code> which flattens the presented org goto heading list when activated so you can see the full list of headings/subheadings in a single step and therefore be able to navigate directly to your desired location.&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-emacs-lisp" data-lang="emacs-lisp">&lt;span style="display:flex;">&lt;span>(setq org-outline-path-complete-in-steps &lt;span style="color:#66d9ef">nil&lt;/span>)
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;hr>
&lt;p>Lets demonstrate how this now works with an example.&lt;/p>
&lt;p>Given the following org:&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="font-weight:bold"> Heading 1&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="font-weight:bold"> Heading 2&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">**&lt;/span> Subheading 2.1
&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="font-weight:bold"> Subheading 3&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">**&lt;/span> Subheading 3.1
&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">***&lt;/span> Subheading 3.1.1
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>imenu would just allow navigation to leaf nodes and not the higher subdirectories.&lt;/p>
&lt;p>(Note : I use &lt;code>fido-mode=/=icomplete&lt;/code>):&lt;/p>
&lt;p>Here is the minibuffer menu presented when &lt;code>imenu&lt;/code> is activated by default:&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>(Heading.1 | Heading.2 | &lt;span style="font-weight:bold">*Rescan*&lt;/span> | Subheading.3)
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>For example, I can navigate to Heading 1 but what about Heading 2 or Heading 3?, when selecting these headings that have subheadings it will take me into further leaf node menus for selection.&lt;/p>
&lt;p>This is where the setup above for &lt;code>org-goto&lt;/code> comes in handy as it now presents the following:&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>{Heading 2/ | Heading 2&lt;span style="font-style:italic">/Subheading 2.1/&lt;/span> | Heading 1&lt;span style="font-style:italic">/ | Subheading 3/&lt;/span> | Subheading 3&lt;span style="font-style:italic">/Subheading 3.1/&lt;/span> | Subheading 3/Subheading 3.1&lt;span style="font-style:italic">/Subheading 3.1.1/&lt;/span>}
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>Where of course the minibuffer completion system can complete to not just the leaf nodes but also the higher level subheadings, allowing navigation to any org heading!&lt;/p>
&lt;figure>&lt;img src="https://www.emacs.dyerdwelling.family/emacs/20241228131958-emacs--Streamlining-Navigation-in-Org-Mode.jpg" width="100%">
&lt;/figure></description></item><item><title>Ediff Comparing Regions</title><link>https://www.emacs.dyerdwelling.family/emacs/20250108140933-emacs--ediff-comparing-regions/</link><pubDate>Wed, 08 Jan 2025 15:30:00 +0000</pubDate><author>James Dyer</author><guid>https://www.emacs.dyerdwelling.family/emacs/20250108140933-emacs--ediff-comparing-regions/</guid><description>&lt;p>&lt;code>ediff&lt;/code> is one of my favourite tools in Emacs and I have recently found myself not only using &lt;code>ediff-buffers&lt;/code> and &lt;code>vc-ediff&lt;/code> often but also now having the need to compare two regions.&lt;/p>
&lt;p>For example, when developing new Elisp functions, I generally modify the bottom of my tangled init file and then evaluate/edit as necessary, or sometimes through the &lt;em>scratch&lt;/em> buffer.&lt;/p>
&lt;p>However, for something more complex, for example, at the moment, I am trying to generate an RSS feed from my Hugo-based blog&amp;rsquo;s Org file through &lt;code>org-publish&lt;/code>. In this case, I prefer creating a separate directory where I can experiment with example files, generated files, and temporarily manage everything under version control.&lt;/p>
&lt;p>At the end of development, I could simply paste my function(s) back into my main configuration, however, as a software engineer, I am accustomed to using merging tools, so what better way to handle this than with &lt;code>ediff&lt;/code>&lt;/p>
&lt;p>Given my development functions and my init file, I can&amp;rsquo;t compare entire files to copy/merge the changes across, and VC in this case is not helpful. So, how do I compare two regions? In fact, is it even possible in Emacs?&lt;/p>
&lt;p>Well, yes, yes it is! (of course)&lt;/p>
&lt;blockquote>
&lt;p>ediff-regions-linewise&lt;/p>
&lt;p>Run Ediff on a pair of regions in specified buffers.
BUFFER-A and BUFFER-B are the buffers to be compared.
Regions (i.e., point and mark) can be set in advance or marked interactively.
Each region is enlarged to contain full lines.&lt;/p>
&lt;/blockquote>
&lt;p>and my keybinding is:&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>(global-set-key (kbd &lt;span style="color:#e6db74">&amp;#34;M-s +&amp;#34;&lt;/span>) &lt;span style="color:#a6e22e">#&amp;#39;&lt;/span>ediff-regions-linewise)
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>Note: at the moment I only ever run &lt;code>ediff-regions-linewise&lt;/code> but the documentation recommends using &lt;code>ediff-regions-wordwise&lt;/code> for smaller regions. For this example I shall be using &lt;code>ediff-regions-linewise&lt;/code> only.&lt;/p>
&lt;p>Documentation for &lt;code>ediff-regions-wordwise&lt;/code>&lt;/p>
&lt;blockquote>
&lt;p>The ediff-regions-linewise function is effective for large regions, over 100-200 lines.&lt;/p>
&lt;p>For small regions, use ‘ediff-regions-wordwise’.&lt;/p>
&lt;/blockquote>
&lt;p>Initially, I prefer to have a horizontal split with the two files containing the regions I would like to compare, and then place the point in the left-hand window.&lt;/p>
&lt;p>When &lt;code>ediff-regions-linewise&lt;/code> is then activated, that is the order the regions are set up, from left to right.&lt;/p>
&lt;p>Note: According to the documentation, it seems possible to set up the two regions using marks before the call, after which you will go directly into an ediff comparison rather than the interactive region described below. However, I have not yet been able to get the region comparison to work this way.&lt;/p>
&lt;p>Ok, lets do a region comparison!&lt;/p>
&lt;hr>
&lt;ol>
&lt;li>
&lt;p>&lt;strong>Run&lt;/strong>&lt;/p>
&lt;ul>
&lt;li>Open both buffers side-by-side, Buffer A : Buffer B&lt;/li>
&lt;li>Run &lt;code>M-x ediff-regions-linewise&lt;/code>&lt;/li>
&lt;li>Select each side-by-side buffer, by &amp;lt;RET&amp;gt;, &amp;lt;RET&amp;gt;&lt;/li>
&lt;/ul>
&lt;/li>
&lt;li>
&lt;p>&lt;strong>Mark Buffer A&amp;rsquo;s Region&lt;/strong>&lt;/p>
&lt;ul>
&lt;li>When prompted for &amp;ldquo;Region A:&amp;rdquo;, Buffer A will be automatically switched to&lt;/li>
&lt;li>Now mark the region for comparison&lt;/li>
&lt;li>Once the region is marked, hit C-M-c&lt;/li>
&lt;/ul>
&lt;/li>
&lt;li>
&lt;p>&lt;strong>Mark Buffer B&amp;rsquo;s Region&lt;/strong>&lt;/p>
&lt;ul>
&lt;li>Similarly, when prompted for &amp;ldquo;Region B:&amp;rdquo;, Buffer B will be automatically switched to&lt;/li>
&lt;li>Now mark the region for comparison&lt;/li>
&lt;li>Once the region is marked, hit C-M-c&lt;/li>
&lt;/ul>
&lt;p>Now the usual familiar ediff comparison begins.&lt;/p>
&lt;hr>
&lt;p>Here is a little visual example set up with a simple paragraph difference:&lt;/p>
&lt;figure>&lt;img src="https://www.emacs.dyerdwelling.family/emacs/20250108140933-emacs--Ediff-Comparing-Regions_001.jpg" width="100%">
&lt;/figure>
&lt;p>In this case Buffer A and Buffer B are set up side-by-side and with the process for a region comparison as described above on &lt;strong>2. Mark Buffer A&amp;rsquo;s Region&lt;/strong> that I have deliberately made slightly different for Buffer B.&lt;/p>
&lt;p>Here is the comparison result:&lt;/p>
&lt;figure>&lt;img src="https://www.emacs.dyerdwelling.family/emacs/20250108140933-emacs--Ediff-Comparing-Regions_002.jpg" width="100%">
&lt;/figure>
&lt;/li>
&lt;/ol>
&lt;hr>
&lt;p>I have something that seems to work for me at the moment, but I would really like to fully understand the difference between a linewise and a wordwise comparison. I understand that wordwise comparison could be computationally expensive on large blocks, but what benefits do I gain from using a smaller wordwise comparison compared to a linewise comparison?&lt;/p>
&lt;p>Also, although the interactive region selection works well, I really want to figure out how to set up the regions initially.&lt;/p>
&lt;p>If anyone knows answers to these questions, just drop in a comment!&lt;/p></description></item><item><title>Emacs Quick Window Pt 4 - Further Tweaks</title><link>https://www.emacs.dyerdwelling.family/emacs/20241231120205-emacs--enhancing-window-navigation-my-quick-window-jump/</link><pubDate>Fri, 03 Jan 2025 20:20:00 +0000</pubDate><author>James Dyer</author><guid>https://www.emacs.dyerdwelling.family/emacs/20241231120205-emacs--enhancing-window-navigation-my-quick-window-jump/</guid><description>&lt;p>Previously, &lt;code>my/quick-window-jump&lt;/code> was geared mostly toward managing two or more windows. The behaviour was straightforward:&lt;/p>
&lt;ul>
&lt;li>If two windows were open, the function would directly jump to the other window.&lt;/li>
&lt;li>For more than two windows, a key selection interface would allow you to pick a window to switch to.&lt;/li>
&lt;/ul>
&lt;p>However, the function didn’t handle a single-window scenario intelligently. A horizontal split from a single window is something I do very frequently, by frequently, I mean at least every minute! This split is currently bound to its own keybinding, but couldn&amp;rsquo;t my &lt;code>ace-window&lt;/code> defun clone do this? For example, I find that the transition from a single window to a split never happens vertically for me, so why not make &lt;code>my/quick-window-jump&lt;/code> perform a horizontal split from a single window and then move the point to that window?&lt;/p>
&lt;p>In addition, it is very rare for me to vertically split into more than two columns. I could probably drop my current horizontal split keybinding now. If I do require another horizontal split, the default &lt;code>C-x 3&lt;/code> is already bound to muscle memory.&lt;/p>
&lt;p>Lets take this function to the next level of convenience, with a very simple change, simply an extra (length window-list) and the relevant logic.&lt;/p>
&lt;p>Below is the updated implementation of &lt;code>my/quick-window-jump&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/quick-window-jump ()
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#e6db74">&amp;#34;Jump to a window by typing its assigned character label.
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#e6db74">If there is only a single window, split it horizontally.
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#e6db74">If there are only two windows, jump directly to the other window.&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* ((&lt;span style="color:#a6e22e">window-list&lt;/span> (&lt;span style="color:#a6e22e">window-list&lt;/span> &lt;span style="color:#66d9ef">nil&lt;/span> &lt;span style="color:#e6db74">&amp;#39;no-mini&lt;/span>)))
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> (cond
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#75715e">;; If there is only a single window, split it horizontally.&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> ((&lt;span style="color:#a6e22e">=&lt;/span> (&lt;span style="color:#a6e22e">length&lt;/span> &lt;span style="color:#a6e22e">window-list&lt;/span>) &lt;span style="color:#ae81ff">1&lt;/span>)
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> (split-window-horizontally)
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> (other-window &lt;span style="color:#ae81ff">1&lt;/span>)) &lt;span style="color:#75715e">;; Move focus to the new window immediately after splitting.&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#75715e">;; If there are only two windows, switch to the other one directly.&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> ((&lt;span style="color:#a6e22e">=&lt;/span> (&lt;span style="color:#a6e22e">length&lt;/span> &lt;span style="color:#a6e22e">window-list&lt;/span>) &lt;span style="color:#ae81ff">2&lt;/span>)
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> (&lt;span style="color:#a6e22e">select-window&lt;/span> (&lt;span style="color:#a6e22e">other-window-for-scrolling&lt;/span>)))
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#75715e">;; Otherwise, present the key selection interface.&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> (&lt;span style="color:#66d9ef">t&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> (let* ((my/quick-window-overlays &lt;span style="color:#66d9ef">nil&lt;/span>)
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> (sorted-windows (&lt;span style="color:#a6e22e">sort&lt;/span> &lt;span style="color:#a6e22e">window-list&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> (lambda (w1 w2)
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> (let ((edges1 (&lt;span style="color:#a6e22e">window-edges&lt;/span> w1))
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> (edges2 (&lt;span style="color:#a6e22e">window-edges&lt;/span> w2)))
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> (or (&lt;span style="color:#a6e22e">&amp;lt;&lt;/span> (&lt;span style="color:#a6e22e">car&lt;/span> edges1) (&lt;span style="color:#a6e22e">car&lt;/span> edges2))
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> (and (&lt;span style="color:#a6e22e">=&lt;/span> (&lt;span style="color:#a6e22e">car&lt;/span> edges1) (&lt;span style="color:#a6e22e">car&lt;/span> edges2))
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> (&lt;span style="color:#a6e22e">&amp;lt;&lt;/span> (cadr edges1) (cadr edges2))))))))
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> (window-keys (seq-take &lt;span style="color:#f92672">&amp;#39;&lt;/span>(&lt;span style="color:#e6db74">&amp;#34;j&amp;#34;&lt;/span> &lt;span style="color:#e6db74">&amp;#34;k&amp;#34;&lt;/span> &lt;span style="color:#e6db74">&amp;#34;l&amp;#34;&lt;/span> &lt;span style="color:#e6db74">&amp;#34;;&amp;#34;&lt;/span> &lt;span style="color:#e6db74">&amp;#34;a&amp;#34;&lt;/span> &lt;span style="color:#e6db74">&amp;#34;s&amp;#34;&lt;/span> &lt;span style="color:#e6db74">&amp;#34;d&amp;#34;&lt;/span> &lt;span style="color:#e6db74">&amp;#34;f&amp;#34;&lt;/span>)
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> (&lt;span style="color:#a6e22e">length&lt;/span> sorted-windows)))
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> (window-map (cl-pairlis window-keys sorted-windows)))
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> (setq my/quick-window-overlays
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> (&lt;span style="color:#a6e22e">mapcar&lt;/span> (lambda (entry)
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> (let* ((key (&lt;span style="color:#a6e22e">car&lt;/span> entry))
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> (window (&lt;span style="color:#a6e22e">cdr&lt;/span> entry))
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> (start (&lt;span style="color:#a6e22e">window-start&lt;/span> window))
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> (overlay (&lt;span style="color:#a6e22e">make-overlay&lt;/span> start start (&lt;span style="color:#a6e22e">window-buffer&lt;/span> window))))
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> (&lt;span style="color:#a6e22e">overlay-put&lt;/span> overlay &lt;span style="color:#e6db74">&amp;#39;after-string&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">format&lt;/span> &lt;span style="color:#e6db74">&amp;#34;[%s]&amp;#34;&lt;/span> key)
&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:#f92672">&amp;#39;&lt;/span>(:foreground &lt;span style="color:#e6db74">&amp;#34;white&amp;#34;&lt;/span> :background &lt;span style="color:#e6db74">&amp;#34;blue&amp;#34;&lt;/span> :weight bold)))
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> (&lt;span style="color:#a6e22e">overlay-put&lt;/span> overlay &lt;span style="color:#e6db74">&amp;#39;window&lt;/span> window)
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> overlay))
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> window-map))
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> (let ((key (read-key (&lt;span style="color:#a6e22e">format&lt;/span> &lt;span style="color:#e6db74">&amp;#34;Select window [%s]: &amp;#34;&lt;/span> (string-join window-keys &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">mapc&lt;/span> &lt;span style="color:#a6e22e">#&amp;#39;delete-overlay&lt;/span> my/quick-window-overlays)
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> (setq my/quick-window-overlays &lt;span style="color:#66d9ef">nil&lt;/span>)
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> (when-let ((&lt;span style="color:#a6e22e">selected-window&lt;/span> (&lt;span style="color:#a6e22e">cdr&lt;/span> (&lt;span style="color:#a6e22e">assoc&lt;/span> (&lt;span style="color:#a6e22e">char-to-string&lt;/span> key) window-map))))
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> (&lt;span style="color:#a6e22e">select-window&lt;/span> &lt;span style="color:#a6e22e">selected-window&lt;/span>))))))))
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>The updated &lt;code>my/quick-window-jump&lt;/code> function now adapts to any window configuration dynamically. Here is now an overview on how it now works:&lt;/p>
&lt;ol>
&lt;li>
&lt;p>&lt;strong>Single Window? Split It Automatically&lt;/strong>&lt;/p>
&lt;ul>
&lt;li>If there’s only one window open, the function splits it horizontally and shifts focus to the new window.&lt;/li>
&lt;/ul>
&lt;/li>
&lt;li>
&lt;p>&lt;strong>Two Windows? Still Jump Directly&lt;/strong>&lt;/p>
&lt;ul>
&lt;li>The behaviour for two windows remains unchanged. It efficiently switches focus to the other window directly without inefficiencies.&lt;/li>
&lt;/ul>
&lt;/li>
&lt;li>
&lt;p>&lt;strong>More Than Two Windows? Key Selection Interface&lt;/strong>&lt;/p>
&lt;ul>
&lt;li>When multiple windows are open, the function presents the familiar key selection interface, allowing you to jump exactly where you intend.&lt;/li>
&lt;/ul>
&lt;/li>
&lt;/ol>
&lt;p>Will there be more tweaks?, not sure, but this is the advantage of now creating my own defun for this piece of functionality!&lt;/p>
&lt;figure>&lt;img src="https://www.emacs.dyerdwelling.family/emacs/20241213115239-emacs--Emacs-core-window-jumping-between-two-windows.jpg" width="100%">
&lt;/figure></description></item><item><title>Exploring Static Website Publishing with Org Publish</title><link>https://www.emacs.dyerdwelling.family/emacs/20241226125955-emacs--exploring-emacs-based-static-website-publishing-with-org-publish/</link><pubDate>Fri, 27 Dec 2024 10:10:00 +0000</pubDate><author>James Dyer</author><guid>https://www.emacs.dyerdwelling.family/emacs/20241226125955-emacs--exploring-emacs-based-static-website-publishing-with-org-publish/</guid><description>&lt;p>As someone who enjoys using Emacs for almost everything, I recently began exploring an alternative Emacs-native method for publishing static websites.&lt;/p>
&lt;p>Currently, I use &lt;a href="https://github.com/kaushalmodi/ox-hugo">ox-hugo&lt;/a> to build a static site from a single Org file, and it works beautifully for exporting content to Hugo-compatible Markdown. However, I wanted to create a backup web generation system entirely within Emacs that doesn&amp;rsquo;t rely on external tools like Hugo and might fit into my overarching &lt;strong>Emacs Enhancements&lt;/strong> project concept.&lt;/p>
&lt;p>My &lt;strong>Emacs Enhancements&lt;/strong> project focuses on replacing significant external packages that I commonly use with a collection of simple Elisp functions and built-in functionality. This approach is ideal for situations where I run Emacs on an air-gapped system or even on Windows, where I have found package management to be somewhat unreliable. You can check out my progress on &lt;a href="https://github.com/captainflasmr/Emacs-enhanced">GitHub&lt;/a>.&lt;/p>
&lt;p>So, how do I replace Hugo/ox-hugo? Well, I finally got around to experimenting with the powerful &lt;code>org-publish&lt;/code> system. I say &amp;ldquo;finally&amp;rdquo; because I’ve had my eye on this for quite a while, but at no point in the past did I have the time to invest in learning it. Now, during the festive period, I have my laptop and potentially some downtime so maybe now is the time!&lt;/p>
&lt;p>This post documents my work in progress journey of setting up an Emacs-based publishing workflow using &lt;code>org-publish&lt;/code> to export to HTML.&lt;/p>
&lt;h2 id="why-org-publish-over-ox-hugo">&lt;strong>Why Org-Publish over ox-hugo?&lt;/strong>&lt;/h2>
&lt;p>While ox-hugo provides extensive functionality to structure and export content seamlessly for Hugo-based static sites, I found these motivations to explore org-publish:&lt;/p>
&lt;ol>
&lt;li>&lt;strong>Native Emacs Solution&lt;/strong>: Keeping everything within the Emacs ecosystem.&lt;/li>
&lt;li>&lt;strong>Backup&lt;/strong>: Ensures I have a non-Hugo-dependent HTML web site generation backup.&lt;/li>
&lt;li>&lt;strong>Customizability&lt;/strong>: Leverage fine-grained control over publishing directory structures and formats from within Emacs elisp.&lt;/li>
&lt;/ol>
&lt;p>That said, this isn&amp;rsquo;t a replacement for ox-hugo, but an experiment to see how efficient a pure Emacs approach can be.&lt;/p>
&lt;h2 id="project-setup-overview">&lt;strong>Project Setup Overview&lt;/strong>&lt;/h2>
&lt;ol>
&lt;li>&lt;strong>Using a Monolithic Org File&lt;/strong>: My starting file, &lt;code>emacs--all.org&lt;/code>, contains all blog posts under separate headings.&lt;/li>
&lt;li>&lt;strong>Split the Org File into Multiple Smaller Files&lt;/strong>: For compatibility with &lt;code>org-publish&lt;/code>, I wrote a utility to split the file. This allows each blog post to be processed independently.&lt;/li>
&lt;li>&lt;strong>Use &lt;code>org-publish-project-alist&lt;/code>&lt;/strong>: Define a multi-step pipeline for splitting, exporting HTML, and processing images.&lt;/li>
&lt;/ol>
&lt;p>Here’s how I tackled each step in detail:&lt;/p>
&lt;h2 id="step-1-splitting-the-monolithic-org-file">&lt;strong>Step 1: Splitting the Monolithic Org File&lt;/strong>&lt;/h2>
&lt;p>The first hurdle was enabling &lt;code>org-publish&lt;/code> to work with my single Org file. Since &lt;code>org-publish&lt;/code> usually expects individual files to process, I created a custom function, &lt;code>my-org-publish-split-headings&lt;/code>. This function splits the top-level headings marked as &lt;code>DONE&lt;/code> into separate files, each prefixed with the date from a property (e.g., &lt;code>:EXPORT_HUGO_LASTMOD:&lt;/code>).&lt;/p>
&lt;h3 id="splitting-file-logic">Splitting File Logic&lt;/h3>
&lt;p>Hopefully, this defun is commented well enough to be almost self-documenting!&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-emacs-lisp" data-lang="emacs-lisp">&lt;span style="display:flex;">&lt;span>(defun my-org-publish-split-headings (plist filename pub-dir)
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#e6db74">&amp;#34;Split an Org file into separate files, each corresponding to a top-level heading
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#e6db74">that is marked as DONE.
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#e6db74">
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#e6db74">Each file name is prefixed with the date in YYYYMMDD format extracted from the
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#e6db74">:EXPORT_HUGO_LASTMOD: property. PLIST is the property list for the publishing
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#e6db74">process, FILENAME is the input Org file, and PUB-DIR is the publishing directory.&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> (with-temp-buffer
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> (&lt;span style="color:#a6e22e">insert-file-contents&lt;/span> filename) &lt;span style="color:#75715e">;; Load the content of the current Org file&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> (let ((heading-level &lt;span style="color:#ae81ff">1&lt;/span>) &lt;span style="color:#75715e">;; Level of the top-level heading to split by&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> prev-start heading-title sanitized-title output-file lastmod-date)
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#75715e">;; Iterate over all top-level headings&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">format&lt;/span> &lt;span style="color:#e6db74">&amp;#34;^\\*\\{%d\\} \\(?:\\([[:upper:]]+\\) \\)?\\(.*\\)&amp;#34;&lt;/span> heading-level) &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> (let ((todo-keyword (match-string &lt;span style="color:#ae81ff">1&lt;/span>)) &lt;span style="color:#75715e">;; Extract the TODO keyword (if it exists)&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> (heading-title (match-string &lt;span style="color:#ae81ff">2&lt;/span>))) &lt;span style="color:#75715e">;; Extract the title of the heading&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#75715e">;; Process only headings marked as DONE&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> (when (and todo-keyword (&lt;span style="color:#a6e22e">string-equal&lt;/span> todo-keyword &lt;span style="color:#e6db74">&amp;#34;DONE&amp;#34;&lt;/span>))
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> (setq prev-start (&lt;span style="color:#a6e22e">match-beginning&lt;/span> &lt;span style="color:#ae81ff">0&lt;/span>)) &lt;span style="color:#75715e">;; Start of the current heading&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> (setq sanitized-title (when heading-title
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> (replace-regexp-in-string &lt;span style="color:#e6db74">&amp;#34;[^a-zA-Z0-9_-]&amp;#34;&lt;/span> &lt;span style="color:#e6db74">&amp;#34;_&amp;#34;&lt;/span> heading-title))) &lt;span style="color:#75715e">;; Sanitize title&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#75715e">;; Extract the :EXPORT_HUGO_LASTMOD: property for the current section&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> (when (&lt;span style="color:#a6e22e">re-search-forward&lt;/span> &lt;span style="color:#e6db74">&amp;#34;:EXPORT_HUGO_LASTMOD: +\\(&amp;lt;.+&amp;gt;\\)&amp;#34;&lt;/span> (save-excursion (&lt;span style="color:#a6e22e">re-search-forward&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 style="color:#a6e22e">point&lt;/span>)) &lt;span style="color:#66d9ef">t&lt;/span>)
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> (let* ((raw-lastmod (match-string &lt;span style="color:#ae81ff">1&lt;/span>)) &lt;span style="color:#75715e">;; Extract the timestamp string (e.g., &amp;#34;&amp;lt;2024-12-08 08:37&amp;gt;&amp;#34;)&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> (date-elements (when (&lt;span style="color:#a6e22e">string-match&lt;/span> &lt;span style="color:#e6db74">&amp;#34;&amp;lt;\\([0-9]+\\)-\\([0-9]+\\)-\\([0-9]+\\)&amp;#34;&lt;/span> raw-lastmod)
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> (&lt;span style="color:#a6e22e">list&lt;/span> (match-string &lt;span style="color:#ae81ff">1&lt;/span> raw-lastmod) &lt;span style="color:#75715e">;; Year&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> (match-string &lt;span style="color:#ae81ff">2&lt;/span> raw-lastmod) &lt;span style="color:#75715e">;; Month&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> (match-string &lt;span style="color:#ae81ff">3&lt;/span> raw-lastmod))))) &lt;span style="color:#75715e">;; Day&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> (setq lastmod-date (when date-elements
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> (&lt;span style="color:#a6e22e">apply&lt;/span> &lt;span style="color:#a6e22e">#&amp;#39;concat&lt;/span> date-elements))))))
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#75715e">;; Default to &amp;#34;00000000&amp;#34; if no valid lastmod-date is found&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> (setq lastmod-date (or lastmod-date &lt;span style="color:#e6db74">&amp;#34;00000000&amp;#34;&lt;/span>))
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#75715e">;; Find the end of this section (right before the next top-level heading)&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> (let ((section-end (save-excursion
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> (or (&lt;span style="color:#a6e22e">re-search-forward&lt;/span> (&lt;span style="color:#a6e22e">format&lt;/span> &lt;span style="color:#e6db74">&amp;#34;^\\*\\{%d\\} &amp;#34;&lt;/span> heading-level) &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">point-max&lt;/span>))))) &lt;span style="color:#75715e">;; End of current section or end of file&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#75715e">;; Only proceed if sanitized title exists and is valid&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> (when (and sanitized-title (not (string-empty-p sanitized-title)))
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#75715e">;; Create the output file name (prepend the date)&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> (setq output-file (&lt;span style="color:#a6e22e">expand-file-name&lt;/span> (&lt;span style="color:#a6e22e">format&lt;/span> &lt;span style="color:#e6db74">&amp;#34;%s-%s.org&amp;#34;&lt;/span> lastmod-date sanitized-title) pub-dir))
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#75715e">;; Write the section content (from prev-start to section-end)&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> (&lt;span style="color:#a6e22e">write-region&lt;/span> prev-start section-end output-file)
&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;Wrote %s&amp;#34;&lt;/span> output-file)))))))
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#75715e">;; Return nil to indicate successful processing&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#66d9ef">nil&lt;/span>))
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>I will go into more detail about how I set this up in a future post and how I decided on a split org-file naming convention.&lt;/p>
&lt;h2 id="step-2-org-publish-configuration">&lt;strong>Step 2: Org-Publish Configuration&lt;/strong>&lt;/h2>
&lt;figure>&lt;img src="https://www.emacs.dyerdwelling.family/emacs/20241226125955-emacs--Exploring-Emacs-Based-Static-Website-Publishing-with-Org-Publish.jpg" width="100%">
&lt;/figure>
&lt;p>After splitting the file, I defined the publishing pipeline in &lt;code>org-publish-project-alist&lt;/code>. Here’s what each part does:&lt;/p>
&lt;ol>
&lt;li>&lt;strong>&lt;code>split-emacs&lt;/code>&lt;/strong>:
&lt;ul>
&lt;li>Runs the custom splitting function (&lt;code>my-org-publish-split-headings&lt;/code>).&lt;/li>
&lt;li>Takes the original monolithic Org file and generates smaller Org files in a designated directory.&lt;/li>
&lt;/ul>
&lt;/li>
&lt;li>&lt;strong>&lt;code>blog-posts-emacs&lt;/code>&lt;/strong>:
&lt;ul>
&lt;li>Processes these generated Org files and exports them to HTML.&lt;/li>
&lt;li>Adds metadata such as preamble, postamble, and custom &lt;code>.css&lt;/code> links for styling.&lt;/li>
&lt;li>Automatically generates a &amp;ldquo;sitemap&amp;rdquo; for blog indexing.&lt;/li>
&lt;/ul>
&lt;/li>
&lt;li>&lt;strong>&lt;code>images-emacs&lt;/code>&lt;/strong>:
&lt;ul>
&lt;li>Publishes related images to the target directory.&lt;/li>
&lt;/ul>
&lt;/li>
&lt;li>&lt;strong>&lt;code>blog&lt;/code>&lt;/strong>:
&lt;ul>
&lt;li>Meta-project to combine all the phases (&lt;code>split-emacs&lt;/code>, &lt;code>blog-posts-emacs&lt;/code>, &lt;code>images-emacs&lt;/code>).&lt;/li>
&lt;/ul>
&lt;/li>
&lt;/ol>
&lt;h3 id="org-publish-project-alist">&lt;code>org-publish-project-alist&lt;/code>&lt;/h3>
&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-emacs-lisp" data-lang="emacs-lisp">&lt;span style="display:flex;">&lt;span>(require &lt;span style="color:#e6db74">&amp;#39;ox-publish&lt;/span>)
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#75715e">;;&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>(setq org-publish-project-alist
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#f92672">&amp;#39;&lt;/span>((&lt;span style="color:#e6db74">&amp;#34;split-emacs&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> :base-directory &lt;span style="color:#e6db74">&amp;#34;~/DCIM/content&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> :base-extension &lt;span style="color:#e6db74">&amp;#34;org&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> :publishing-directory &lt;span style="color:#e6db74">&amp;#34;~/DCIM/content/split/emacs&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> :exclude &lt;span style="color:#e6db74">&amp;#34;.*&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> :include (&lt;span style="color:#e6db74">&amp;#34;emacs--all.org&amp;#34;&lt;/span>)
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> :publishing-function my-org-publish-split-headings
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> :recursive &lt;span style="color:#66d9ef">nil&lt;/span>)
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> (&lt;span style="color:#e6db74">&amp;#34;blog-posts-emacs&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> :base-directory &lt;span style="color:#e6db74">&amp;#34;~/DCIM/content/split/emacs&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> :base-extension &lt;span style="color:#e6db74">&amp;#34;org&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> :publishing-directory &lt;span style="color:#e6db74">&amp;#34;~/publish/hugo-emacs/site/static/public_html&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> :publishing-function org-html-publish-to-html
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> :recursive &lt;span style="color:#66d9ef">t&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> :section-numbers &lt;span style="color:#66d9ef">nil&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> :with-toc &lt;span style="color:#66d9ef">nil&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> :html-preamble &lt;span style="color:#66d9ef">t&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> :html-postamble &lt;span style="color:#66d9ef">t&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> :auto-sitemap &lt;span style="color:#66d9ef">t&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> :sitemap-filename &lt;span style="color:#e6db74">&amp;#34;index.org&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> :sitemap-title &lt;span style="color:#e6db74">&amp;#34;the DyerDwelling&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> :html-head &lt;span style="color:#e6db74">&amp;#34;&amp;lt;link rel=\&amp;#34;stylesheet\&amp;#34;
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#e6db74"> href=\&amp;#34;../assets/css//bootstrap.css\&amp;#34;
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#e6db74"> type=\&amp;#34;text/css\&amp;#34;/&amp;gt;\n
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#e6db74"> &amp;lt;link rel=\&amp;#34;stylesheet\&amp;#34;
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#e6db74"> href=\&amp;#34;../assets/css//style-ignore.css\&amp;#34;
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#e6db74"> type=\&amp;#34;text/css\&amp;#34;/&amp;gt;&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> :sitemap-function my-sitemap-format
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> :sitemap-sort-files alphabetically)
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> (&lt;span style="color:#e6db74">&amp;#34;images-emacs&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> :base-directory &lt;span style="color:#e6db74">&amp;#34;~/DCIM/content/emacs&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> :base-extension &lt;span style="color:#e6db74">&amp;#34;jpg\\|gif\\|png&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> :recursive &lt;span style="color:#66d9ef">t&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> :publishing-directory &lt;span style="color:#e6db74">&amp;#34;~/publish/hugo-emacs/site/static/public_html/emacs&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> :publishing-function org-publish-attachment)
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> (&lt;span style="color:#e6db74">&amp;#34;blog&amp;#34;&lt;/span> &lt;span style="color:#75715e">;; Meta-project to combine phases&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> :components (&lt;span style="color:#e6db74">&amp;#34;split-emacs&amp;#34;&lt;/span> &lt;span style="color:#e6db74">&amp;#34;images-emacs&amp;#34;&lt;/span> &lt;span style="color:#e6db74">&amp;#34;blog-posts-emacs&amp;#34;&lt;/span>))))
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;h2 id="conclusion">Conclusion&lt;/h2>
&lt;p>Here is the generated site: &lt;a href="https://www.emacs.dyerdwelling.family/public_html/">https://www.emacs.dyerdwelling.family/public_html/&lt;/a>&lt;/p>
&lt;p>This was a simple introduction to my approach to using &lt;code>org-publish&lt;/code> to provide an alternative web publishing option for my blog, while I&amp;rsquo;m still refining this workflow, the combination of a monolithic Org file for writing and &lt;code>org-publish&lt;/code> for exporting HTML is already proving quite effective as a pure Emacs-powered alternative.&lt;/p></description></item><item><title>Calculate Number Of Days Between Two Dates</title><link>https://www.emacs.dyerdwelling.family/emacs/20241219132107-emacs--calculate-number-of-days-between-two-dates/</link><pubDate>Thu, 19 Dec 2024 13:21:00 +0000</pubDate><author>James Dyer</author><guid>https://www.emacs.dyerdwelling.family/emacs/20241219132107-emacs--calculate-number-of-days-between-two-dates/</guid><description>&lt;p>Just a very quick one today.&lt;/p>
&lt;p>I recently needed to find the number of days between two dates. I thought this would be easy in Emacs, and indeed it was, but as with most things in Emacs, you need to know exactly what you&amp;rsquo;re doing. Here is the method I used:&lt;/p>
&lt;pre tabindex="0">&lt;code class="language-nil" data-lang="nil"> M-x =calendar=
Navigate to the start date
Set mark
Navigate to the end date
M-x =calendar-count-days-region=
OR
M-=
&lt;/code>&lt;/pre>&lt;p>Note that the count is inclusive of the mark, the documentation says:&lt;/p>
&lt;blockquote>
&lt;p>It is bound to M-=.&lt;/p>
&lt;p>Count the number of days (inclusive) between point and the mark.&lt;/p>
&lt;/blockquote>
&lt;figure>&lt;img src="https://www.emacs.dyerdwelling.family/emacs/20241219132107-emacs--Calculate-Number-Of-Days-Between-Two-Dates.jpg" width="100%">
&lt;/figure>
&lt;p>That is all. 🙂&lt;/p></description></item><item><title>Emacs Quick Window Pt 3 - jumping between two windows</title><link>https://www.emacs.dyerdwelling.family/emacs/20241213115239-emacs--emacs-core-window-jumping-between-two-windows/</link><pubDate>Fri, 13 Dec 2024 20:15:00 +0000</pubDate><author>James Dyer</author><guid>https://www.emacs.dyerdwelling.family/emacs/20241213115239-emacs--emacs-core-window-jumping-between-two-windows/</guid><description>&lt;p>The original implementation of &lt;code>my/quick-window-jump&lt;/code> worked perfectly for multi-window setups. It enabled you to assign character labels to each window, display them as overlays within the windows themselves, and jump to your desired window by typing its corresponding key. However, for setups with just two windows (a very common scenario in Emacs), this process felt unnecessarily complicated. Why go through the entire label-assignment process when a single key press could suffice?&lt;/p>
&lt;figure>&lt;img src="https://www.emacs.dyerdwelling.family/emacs/20241213115239-emacs--Emacs-core-window-jumping-between-two-windows.jpg" width="100%">
&lt;/figure>
&lt;p>This latest tweak to &lt;code>my/quick-window-jump&lt;/code> introduces a simple improvement and in fact is currently what &lt;code>ace-window&lt;/code> does: &lt;strong>direct switching for two windows&lt;/strong>. If there are exactly two windows open, the function now skips the overhead of key prompts and overlay creation. Instead, it switches directly to the other window, this seems more logical to me, why the extra key when it is obvious where the point is going to jump to?&lt;/p>
&lt;p>Here’s the core logic for handling two windows:&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>(let* ((&lt;span style="color:#a6e22e">window-list&lt;/span> (&lt;span style="color:#a6e22e">window-list&lt;/span> &lt;span style="color:#66d9ef">nil&lt;/span> &lt;span style="color:#e6db74">&amp;#39;no-mini&lt;/span>)))
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> (if (&lt;span style="color:#a6e22e">=&lt;/span> (&lt;span style="color:#a6e22e">length&lt;/span> &lt;span style="color:#a6e22e">window-list&lt;/span>) &lt;span style="color:#ae81ff">2&lt;/span>)
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#75715e">;; If there are only two windows, switch to the other directly.&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> (&lt;span style="color:#a6e22e">select-window&lt;/span> (&lt;span style="color:#a6e22e">other-window-for-scrolling&lt;/span>))
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#75715e">;; Otherwise, show the key selection interface.&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#f92672">...&lt;/span>))
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>and here is the updated function:&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/quick-window-jump ()
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#e6db74">&amp;#34;Jump to a window by typing its assigned character label.
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#e6db74"> If there are only two windows, jump directly to the other window.&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* ((&lt;span style="color:#a6e22e">window-list&lt;/span> (&lt;span style="color:#a6e22e">window-list&lt;/span> &lt;span style="color:#66d9ef">nil&lt;/span> &lt;span style="color:#e6db74">&amp;#39;no-mini&lt;/span>)))
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> (if (&lt;span style="color:#a6e22e">=&lt;/span> (&lt;span style="color:#a6e22e">length&lt;/span> &lt;span style="color:#a6e22e">window-list&lt;/span>) &lt;span style="color:#ae81ff">2&lt;/span>)
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#75715e">;; If there are only two windows, switch to the other one directly.&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> (&lt;span style="color:#a6e22e">select-window&lt;/span> (&lt;span style="color:#a6e22e">other-window-for-scrolling&lt;/span>))
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#75715e">;; Otherwise, show the key selection interface.&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> (let* ((my/quick-window-overlays &lt;span style="color:#66d9ef">nil&lt;/span>)
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> (sorted-windows (&lt;span style="color:#a6e22e">sort&lt;/span> &lt;span style="color:#a6e22e">window-list&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> (lambda (w1 w2)
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> (let ((edges1 (&lt;span style="color:#a6e22e">window-edges&lt;/span> w1))
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> (edges2 (&lt;span style="color:#a6e22e">window-edges&lt;/span> w2)))
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> (or (&lt;span style="color:#a6e22e">&amp;lt;&lt;/span> (&lt;span style="color:#a6e22e">car&lt;/span> edges1) (&lt;span style="color:#a6e22e">car&lt;/span> edges2))
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> (and (&lt;span style="color:#a6e22e">=&lt;/span> (&lt;span style="color:#a6e22e">car&lt;/span> edges1) (&lt;span style="color:#a6e22e">car&lt;/span> edges2))
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> (&lt;span style="color:#a6e22e">&amp;lt;&lt;/span> (cadr edges1) (cadr edges2))))))))
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> (window-keys (seq-take &lt;span style="color:#f92672">&amp;#39;&lt;/span>(&lt;span style="color:#e6db74">&amp;#34;j&amp;#34;&lt;/span> &lt;span style="color:#e6db74">&amp;#34;k&amp;#34;&lt;/span> &lt;span style="color:#e6db74">&amp;#34;l&amp;#34;&lt;/span> &lt;span style="color:#e6db74">&amp;#34;;&amp;#34;&lt;/span> &lt;span style="color:#e6db74">&amp;#34;a&amp;#34;&lt;/span> &lt;span style="color:#e6db74">&amp;#34;s&amp;#34;&lt;/span> &lt;span style="color:#e6db74">&amp;#34;d&amp;#34;&lt;/span> &lt;span style="color:#e6db74">&amp;#34;f&amp;#34;&lt;/span>)
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> (&lt;span style="color:#a6e22e">length&lt;/span> sorted-windows)))
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> (window-map (cl-pairlis window-keys sorted-windows)))
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> (setq my/quick-window-overlays
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> (&lt;span style="color:#a6e22e">mapcar&lt;/span> (lambda (entry)
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> (let* ((key (&lt;span style="color:#a6e22e">car&lt;/span> entry))
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> (window (&lt;span style="color:#a6e22e">cdr&lt;/span> entry))
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> (start (&lt;span style="color:#a6e22e">window-start&lt;/span> window))
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> (overlay (&lt;span style="color:#a6e22e">make-overlay&lt;/span> start start (&lt;span style="color:#a6e22e">window-buffer&lt;/span> window))))
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> (&lt;span style="color:#a6e22e">overlay-put&lt;/span> overlay &lt;span style="color:#e6db74">&amp;#39;after-string&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">format&lt;/span> &lt;span style="color:#e6db74">&amp;#34;[%s]&amp;#34;&lt;/span> key)
&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:#f92672">&amp;#39;&lt;/span>(:foreground &lt;span style="color:#e6db74">&amp;#34;white&amp;#34;&lt;/span> :background &lt;span style="color:#e6db74">&amp;#34;blue&amp;#34;&lt;/span> :weight bold)))
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> (&lt;span style="color:#a6e22e">overlay-put&lt;/span> overlay &lt;span style="color:#e6db74">&amp;#39;window&lt;/span> window)
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> overlay))
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> window-map))
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> (let ((key (read-key (&lt;span style="color:#a6e22e">format&lt;/span> &lt;span style="color:#e6db74">&amp;#34;Select window [%s]: &amp;#34;&lt;/span> (string-join window-keys &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">mapc&lt;/span> &lt;span style="color:#a6e22e">#&amp;#39;delete-overlay&lt;/span> my/quick-window-overlays)
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> (setq my/quick-window-overlays &lt;span style="color:#66d9ef">nil&lt;/span>)
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> (when-let ((&lt;span style="color:#a6e22e">selected-window&lt;/span> (&lt;span style="color:#a6e22e">cdr&lt;/span> (&lt;span style="color:#a6e22e">assoc&lt;/span> (&lt;span style="color:#a6e22e">char-to-string&lt;/span> key) window-map))))
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> (&lt;span style="color:#a6e22e">select-window&lt;/span> &lt;span style="color:#a6e22e">selected-window&lt;/span>)))))))
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div></description></item><item><title>Emacs Core Window Jumping With Visual Feedback</title><link>https://www.emacs.dyerdwelling.family/emacs/20241209085935-emacs--emacs-core-window-jumping-visual-feedback/</link><pubDate>Tue, 10 Dec 2024 14:15:00 +0000</pubDate><author>James Dyer</author><guid>https://www.emacs.dyerdwelling.family/emacs/20241209085935-emacs--emacs-core-window-jumping-visual-feedback/</guid><description>&lt;p>Already I think I can improve &lt;code>my/quick-window-jump&lt;/code>: which was a window jumping mechanism I created in my previous post which uses a unique key identifier for window navigation just like &lt;code>ace-window&lt;/code> but condensed into a minimal elisp defun implementation.&lt;/p>
&lt;p>While the original implementation worked, I recently revisited and refined it to improve its modularity, visual feedback, and overall usability.&lt;/p>
&lt;p>The main headline of the improvement is porting the &lt;code>ace-window&lt;/code> window label identifier navigation mechanism using overlays!, see below:&lt;/p>
&lt;figure>&lt;img src="https://www.emacs.dyerdwelling.family/emacs/20241209085935-emacs--Emacs-core-window-jumping-visual-feedback.jpg" width="100%">
&lt;/figure>
&lt;p>In this post, I’ll detail the evolution of &lt;code>my/quick-window-jump&lt;/code>, highlighting the changes made to simplify the code, provide immediate visual indicators, and improve the end-user experience.&lt;/p>
&lt;hr>
&lt;h2 id="the-original-implementation">The Original Implementation&lt;/h2>
&lt;h3 id="original-code">Original Code&lt;/h3>
&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-emacs-lisp" data-lang="emacs-lisp">&lt;span style="display:flex;">&lt;span>(defun my/quick-window-jump ()
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#e6db74">&amp;#34;Jump to a window by typing its assigned character label.
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#e6db74">Windows are labeled starting from the top-left window and proceed top to bottom left to right.&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* ((&lt;span style="color:#a6e22e">window-list&lt;/span> (my/get-windows)) &lt;span style="color:#75715e">; Get sorted list of windows&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> (window-keys (seq-take &lt;span style="color:#f92672">&amp;#39;&lt;/span>(&lt;span style="color:#e6db74">&amp;#34;j&amp;#34;&lt;/span> &lt;span style="color:#e6db74">&amp;#34;k&amp;#34;&lt;/span> &lt;span style="color:#e6db74">&amp;#34;l&amp;#34;&lt;/span> &lt;span style="color:#e6db74">&amp;#34;;&amp;#34;&lt;/span> &lt;span style="color:#e6db74">&amp;#34;a&amp;#34;&lt;/span> &lt;span style="color:#e6db74">&amp;#34;s&amp;#34;&lt;/span> &lt;span style="color:#e6db74">&amp;#34;d&amp;#34;&lt;/span> &lt;span style="color:#e6db74">&amp;#34;f&amp;#34;&lt;/span>) &lt;span style="color:#75715e">; Assign key labels&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> (&lt;span style="color:#a6e22e">length&lt;/span> &lt;span style="color:#a6e22e">window-list&lt;/span>)))
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> (window-map (cl-pairlis window-keys &lt;span style="color:#a6e22e">window-list&lt;/span>)) &lt;span style="color:#75715e">; Create key-to-window map&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> (key (read-key (&lt;span style="color:#a6e22e">format&lt;/span> &lt;span style="color:#e6db74">&amp;#34;Select window [%s]: &amp;#34;&lt;/span> (string-join window-keys &lt;span style="color:#e6db74">&amp;#34;, &amp;#34;&lt;/span>)))))
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> (if-let ((&lt;span style="color:#a6e22e">selected-window&lt;/span> (&lt;span style="color:#a6e22e">cdr&lt;/span> (&lt;span style="color:#a6e22e">assoc&lt;/span> (&lt;span style="color:#a6e22e">char-to-string&lt;/span> key) window-map)))) &lt;span style="color:#75715e">; Jump to selected window&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> (&lt;span style="color:#a6e22e">select-window&lt;/span> &lt;span style="color:#a6e22e">selected-window&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;No window assigned to key: %c&amp;#34;&lt;/span> key))))
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>(defun my/get-windows ()
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#e6db74">&amp;#34;Return a list of windows in the current frame, ordered from top to bottom, left to right.&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> (&lt;span style="color:#a6e22e">sort&lt;/span> (&lt;span style="color:#a6e22e">window-list&lt;/span> &lt;span style="color:#66d9ef">nil&lt;/span> &lt;span style="color:#e6db74">&amp;#39;no-mini&lt;/span>)
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> (lambda (w1 w2)
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> (let ((edges1 (&lt;span style="color:#a6e22e">window-edges&lt;/span> w1))
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> (edges2 (&lt;span style="color:#a6e22e">window-edges&lt;/span> w2)))
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> (or (&lt;span style="color:#a6e22e">&amp;lt;&lt;/span> (&lt;span style="color:#a6e22e">car&lt;/span> edges1) (&lt;span style="color:#a6e22e">car&lt;/span> edges2)) &lt;span style="color:#75715e">; Compare top edges&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> (and (&lt;span style="color:#a6e22e">=&lt;/span> (&lt;span style="color:#a6e22e">car&lt;/span> edges1) (&lt;span style="color:#a6e22e">car&lt;/span> edges2)) &lt;span style="color:#75715e">; If equal, compare left edges&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> (&lt;span style="color:#a6e22e">&amp;lt;&lt;/span> (cadr edges1) (cadr edges2))))))))
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>While functional, several downsides existed in this implementation:&lt;/p>
&lt;ul>
&lt;li>&lt;strong>Separation of Logic:&lt;/strong> The &lt;code>my/get-windows&lt;/code> function added an extra layer of abstraction that wasn’t strictly necessary, as sorting logic could be directly embedded into the main function.&lt;/li>
&lt;li>&lt;strong>Lack of Visual Feedback:&lt;/strong> The function provided no immediate indicator of which windows corresponded to which keys. Users had to guess or manually map the output of the key labels to the positions in their frame layout.&lt;/li>
&lt;/ul>
&lt;hr>
&lt;h2 id="the-new-and-improved-implementation">The New and Improved Implementation&lt;/h2>
&lt;p>The updated version of &lt;code>my/quick-window-jump&lt;/code> refines the original function by:&lt;/p>
&lt;ol>
&lt;li>
&lt;p>&lt;strong>Inlining Window Sorting Logic:&lt;/strong> Sorting logic is now part of the main function rather than relying on a separate helper, simplifying maintenance and reducing cognitive overhead.&lt;/p>
&lt;/li>
&lt;li>
&lt;p>&lt;strong>Adding Overlay Labels:&lt;/strong> Temporary overlays are added to each window, displaying the assigned key visually within the actual window. This makes it easier for users to identify which key corresponds to which window without having to manually figure it out.&lt;/p>
&lt;/li>
&lt;/ol>
&lt;p>Here’s the updated code:&lt;/p>
&lt;h3 id="improved-code">Improved Code&lt;/h3>
&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-emacs-lisp" data-lang="emacs-lisp">&lt;span style="display:flex;">&lt;span>(defun my/quick-window-jump ()
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#e6db74">&amp;#34;Jump to a window by typing its assigned character label.
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#e6db74">Windows are labeled starting from the top-left window and proceeding top to bottom, then left to right.&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* ((my/quick-window-overlays &lt;span style="color:#66d9ef">nil&lt;/span>) &lt;span style="color:#75715e">; Temporary list for overlays&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#75715e">;; Sort windows by position (top-to-bottom, left-to-right)&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> (&lt;span style="color:#a6e22e">window-list&lt;/span> (&lt;span style="color:#a6e22e">sort&lt;/span> (&lt;span style="color:#a6e22e">window-list&lt;/span> &lt;span style="color:#66d9ef">nil&lt;/span> &lt;span style="color:#e6db74">&amp;#39;no-mini&lt;/span>)
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> (lambda (w1 w2)
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> (let ((edges1 (&lt;span style="color:#a6e22e">window-edges&lt;/span> w1))
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> (edges2 (&lt;span style="color:#a6e22e">window-edges&lt;/span> w2)))
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> (or (&lt;span style="color:#a6e22e">&amp;lt;&lt;/span> (&lt;span style="color:#a6e22e">car&lt;/span> edges1) (&lt;span style="color:#a6e22e">car&lt;/span> edges2))
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> (and (&lt;span style="color:#a6e22e">=&lt;/span> (&lt;span style="color:#a6e22e">car&lt;/span> edges1) (&lt;span style="color:#a6e22e">car&lt;/span> edges2))
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> (&lt;span style="color:#a6e22e">&amp;lt;&lt;/span> (cadr edges1) (cadr edges2))))))))
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#75715e">;; Assign key labels to windows&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> (window-keys (seq-take &lt;span style="color:#f92672">&amp;#39;&lt;/span>(&lt;span style="color:#e6db74">&amp;#34;j&amp;#34;&lt;/span> &lt;span style="color:#e6db74">&amp;#34;k&amp;#34;&lt;/span> &lt;span style="color:#e6db74">&amp;#34;l&amp;#34;&lt;/span> &lt;span style="color:#e6db74">&amp;#34;;&amp;#34;&lt;/span> &lt;span style="color:#e6db74">&amp;#34;a&amp;#34;&lt;/span> &lt;span style="color:#e6db74">&amp;#34;s&amp;#34;&lt;/span> &lt;span style="color:#e6db74">&amp;#34;d&amp;#34;&lt;/span> &lt;span style="color:#e6db74">&amp;#34;f&amp;#34;&lt;/span>)
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> (&lt;span style="color:#a6e22e">length&lt;/span> &lt;span style="color:#a6e22e">window-list&lt;/span>)))
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> (window-map (cl-pairlis window-keys &lt;span style="color:#a6e22e">window-list&lt;/span>))) &lt;span style="color:#75715e">; Create map of keys to windows&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#75715e">;; Add overlays to display key labels in each window&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> (setq my/quick-window-overlays
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> (&lt;span style="color:#a6e22e">mapcar&lt;/span> (lambda (entry)
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> (let* ((key (&lt;span style="color:#a6e22e">car&lt;/span> entry))
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> (window (&lt;span style="color:#a6e22e">cdr&lt;/span> entry))
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> (start (&lt;span style="color:#a6e22e">window-start&lt;/span> window)) &lt;span style="color:#75715e">; Start position of window&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> (overlay (&lt;span style="color:#a6e22e">make-overlay&lt;/span> start start (&lt;span style="color:#a6e22e">window-buffer&lt;/span> window)))) &lt;span style="color:#75715e">; Create overlay&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> (&lt;span style="color:#a6e22e">overlay-put&lt;/span> overlay &lt;span style="color:#e6db74">&amp;#39;after-string&lt;/span> &lt;span style="color:#75715e">; Add a visual label&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">format&lt;/span> &lt;span style="color:#e6db74">&amp;#34;[%s]&amp;#34;&lt;/span> key)
&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:#f92672">&amp;#39;&lt;/span>(:foreground &lt;span style="color:#e6db74">&amp;#34;white&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> :background &lt;span style="color:#e6db74">&amp;#34;blue&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> :weight bold)))
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> (&lt;span style="color:#a6e22e">overlay-put&lt;/span> overlay &lt;span style="color:#e6db74">&amp;#39;window&lt;/span> window) &lt;span style="color:#75715e">; Associate overlay with window&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> overlay))
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> window-map))
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#75715e">;; Read key input from user&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> (let ((key (read-key (&lt;span style="color:#a6e22e">format&lt;/span> &lt;span style="color:#e6db74">&amp;#34;Select window [%s]: &amp;#34;&lt;/span> (string-join window-keys &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:#75715e">;; Clear overlays and reset&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> (&lt;span style="color:#a6e22e">mapc&lt;/span> &lt;span style="color:#a6e22e">#&amp;#39;delete-overlay&lt;/span> my/quick-window-overlays)
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> (setq my/quick-window-overlays &lt;span style="color:#66d9ef">nil&lt;/span>)
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#75715e">;; Select window based on key or show error&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> (when-let ((&lt;span style="color:#a6e22e">selected-window&lt;/span> (&lt;span style="color:#a6e22e">cdr&lt;/span> (&lt;span style="color:#a6e22e">assoc&lt;/span> (&lt;span style="color:#a6e22e">char-to-string&lt;/span> key) window-map))))
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> (&lt;span style="color:#a6e22e">select-window&lt;/span> &lt;span style="color:#a6e22e">selected-window&lt;/span>)))))
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;hr>
&lt;h2 id="key-improvements">Key Improvements&lt;/h2>
&lt;ol>
&lt;li>
&lt;p>&lt;strong>Eliminating Redundancy&lt;/strong>
By folding the &lt;code>my/get-windows&lt;/code> functionality into &lt;code>my/quick-window-jump&lt;/code>, the updated function is now self-contained. This reduces dependency on external helpers and makes the logic easier to follow.&lt;/p>
&lt;/li>
&lt;li>
&lt;p>&lt;strong>Enhancing User Experience with Visual Overlays&lt;/strong>
The use of temporary overlays provides immediate, intuitive feedback by displaying key mappings directly inside the appropriate windows. This reduces guesswork and makes navigation significantly faster.&lt;/p>
&lt;p>Each window is temporarily labeled with a key, using the following visual attributes:&lt;/p>
&lt;ul>
&lt;li>&lt;strong>Foreground:&lt;/strong> White for contrast.&lt;/li>
&lt;li>&lt;strong>Background:&lt;/strong> Blue for prominence.&lt;/li>
&lt;li>&lt;strong>Bold Font:&lt;/strong> For easy readability.&lt;/li>
&lt;/ul>
&lt;/li>
&lt;/ol>
&lt;hr>
&lt;h2 id="why-these-changes-matter">Why These Changes Matter&lt;/h2>
&lt;p>Well I guess they don&amp;rsquo;t really, it&amp;rsquo;s just for me and its just for fun! 😀&lt;/p>
&lt;ol>
&lt;li>
&lt;p>&lt;strong>Reduced Mental Burden:&lt;/strong> The new approach lets users navigate windows without having to remember or deduce key-to-window mappings. Visual feedback ensures immediate understanding of the layout.&lt;/p>
&lt;/li>
&lt;li>
&lt;p>&lt;strong>Improved Modularity:&lt;/strong> By embedding all functionality into a single function and handling cleanup directly, the updated implementation is more modular and self-contained.&lt;/p>
&lt;/li>
&lt;li>
&lt;p>&lt;strong>Enhanced Readability and Maintainability:&lt;/strong> Fewer moving parts and a more streamlined design make the function easier to maintain or extend in the future.&lt;/p>
&lt;/li>
&lt;/ol></description></item><item><title>Core Emacs Init Without External Packages</title><link>https://www.emacs.dyerdwelling.family/emacs/20241206143221-emacs--emacs-core-emacs-init-without-external-packages/</link><pubDate>Fri, 06 Dec 2024 15:00:00 +0000</pubDate><author>James Dyer</author><guid>https://www.emacs.dyerdwelling.family/emacs/20241206143221-emacs--emacs-core-emacs-init-without-external-packages/</guid><description>&lt;p>I thought I would share a simple concept that I have found very beneficial: creating an Emacs init file that remains almost fully functional for my use but doesn&amp;rsquo;t include any external packages!&lt;/p>
&lt;figure>&lt;img src="https://www.emacs.dyerdwelling.family/emacs/20241206143221-emacs--Emacs-Core-Emacs-Init-Without-External-Packages.jpg" width="100%">
&lt;/figure>
&lt;p>This setup benefits two main use cases. The first is when using Emacs at work without internet access, and the second is configuring it for use on Windows (with an internet connection). For some reason, my Windows version always seems to struggle to connect to MELPA/ELPA for package downloads, prompting me to resort to an initial complete local repository download. Even then, I still encounter some inconsistencies. I don&amp;rsquo;t often use the native Windows Emacs version anyway, and now that WSL is mature enough, I know a fully functioning version of Emacs can run seamlessly.&lt;/p>
&lt;p>Anyway enough with the blithering.&lt;/p>
&lt;p>I have realized that over the years, I have accumulated many Elisp snippets that can actually replace several packages I have downloaded from MELPA.&lt;/p>
&lt;p>For example, while I couldn&amp;rsquo;t live without &lt;strong>ace-window&lt;/strong>, I only primarily use it for the window-jumping functionality. Now that I am familiar with the typical positions of the key identifiers in my window layout, I thought I would simply write my own implementation in Elisp:&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/quick-window-jump ()
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#e6db74">&amp;#34;Jump to a window by typing its assigned character label.
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#e6db74"> Windows are labeled starting from the top-left window and proceed top to bottom left to right.&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* ((&lt;span style="color:#a6e22e">window-list&lt;/span> (my/get-windows))
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> (window-keys (seq-take &lt;span style="color:#f92672">&amp;#39;&lt;/span>(&lt;span style="color:#e6db74">&amp;#34;j&amp;#34;&lt;/span> &lt;span style="color:#e6db74">&amp;#34;k&amp;#34;&lt;/span> &lt;span style="color:#e6db74">&amp;#34;l&amp;#34;&lt;/span> &lt;span style="color:#e6db74">&amp;#34;;&amp;#34;&lt;/span> &lt;span style="color:#e6db74">&amp;#34;a&amp;#34;&lt;/span> &lt;span style="color:#e6db74">&amp;#34;s&amp;#34;&lt;/span> &lt;span style="color:#e6db74">&amp;#34;d&amp;#34;&lt;/span> &lt;span style="color:#e6db74">&amp;#34;f&amp;#34;&lt;/span>)
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> (&lt;span style="color:#a6e22e">length&lt;/span> &lt;span style="color:#a6e22e">window-list&lt;/span>)))
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> (window-map (cl-pairlis window-keys &lt;span style="color:#a6e22e">window-list&lt;/span>))
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> (key (read-key (&lt;span style="color:#a6e22e">format&lt;/span> &lt;span style="color:#e6db74">&amp;#34;Select window [%s]: &amp;#34;&lt;/span> (string-join window-keys &lt;span style="color:#e6db74">&amp;#34;, &amp;#34;&lt;/span>)))))
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> (if-let ((&lt;span style="color:#a6e22e">selected-window&lt;/span> (&lt;span style="color:#a6e22e">cdr&lt;/span> (&lt;span style="color:#a6e22e">assoc&lt;/span> (&lt;span style="color:#a6e22e">char-to-string&lt;/span> key) window-map))))
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> (&lt;span style="color:#a6e22e">select-window&lt;/span> &lt;span style="color:#a6e22e">selected-window&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;No window assigned to key: %c&amp;#34;&lt;/span> key))))
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>(defun my/get-windows ()
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#e6db74">&amp;#34;Return a list of windows in the current frame, ordered from top to bottom, left to right.&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> (&lt;span style="color:#a6e22e">sort&lt;/span> (&lt;span style="color:#a6e22e">window-list&lt;/span> &lt;span style="color:#66d9ef">nil&lt;/span> &lt;span style="color:#e6db74">&amp;#39;no-mini&lt;/span>)
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> (lambda (w1 w2)
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> (let ((edges1 (&lt;span style="color:#a6e22e">window-edges&lt;/span> w1))
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> (edges2 (&lt;span style="color:#a6e22e">window-edges&lt;/span> w2)))
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> (or (&lt;span style="color:#a6e22e">&amp;lt;&lt;/span> (&lt;span style="color:#a6e22e">car&lt;/span> edges1) (&lt;span style="color:#a6e22e">car&lt;/span> edges2)) &lt;span style="color:#75715e">; Compare top edges&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> (and (&lt;span style="color:#a6e22e">=&lt;/span> (&lt;span style="color:#a6e22e">car&lt;/span> edges1) (&lt;span style="color:#a6e22e">car&lt;/span> edges2)) &lt;span style="color:#75715e">; If equal, compare left edges&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> (&lt;span style="color:#a6e22e">&amp;lt;&lt;/span> (cadr edges1) (cadr edges2))))))))
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>(global-set-key (kbd &lt;span style="color:#e6db74">&amp;#34;M-a&amp;#34;&lt;/span>) &lt;span style="color:#a6e22e">#&amp;#39;&lt;/span>my/quick-window-jump)
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>So the &lt;code>ace-window&lt;/code> jumping functionality is now usable in an environment without access to MELPA!&lt;/p>
&lt;p>I thought I would present my &amp;ldquo;no package&amp;rdquo; Emacs config piece by piece one blog post at a time, showcasing 90% of the functionality I typically use day-to-day and how I have managed to either write some elisp, found out a built-in option, or adapted my workflow in some way.&lt;/p>
&lt;p>I still have my &amp;ldquo;full-fat&amp;rdquo; version, of course, but I have now based it on the &lt;code>core&lt;/code> version (hence the name) and added extra things like &lt;code>magit&lt;/code>, etc.&lt;/p>
&lt;p>However, &lt;code>magit&lt;/code> is a good example on something I think I might be able to remove soon to switch to the built-in Emacs VC. I successfully use it at work with both Git and Subversion!&lt;/p>
&lt;p>If you are interested in seeing the full &amp;ldquo;no fat&amp;rdquo; version, have a ganders at: &lt;a href="https://github.com/captainflasmr/Emacs-core">https://github.com/captainflasmr/Emacs-core&lt;/a>&lt;/p>
&lt;p>There will be more to come on this topic in the coming weeks&amp;hellip; 😀&lt;/p></description></item><item><title>Shrinking and Widening Org Tables</title><link>https://www.emacs.dyerdwelling.family/emacs/20241128130253-emacs--shrinking-widening-org-tables/</link><pubDate>Thu, 28 Nov 2024 13:02:00 +0000</pubDate><author>James Dyer</author><guid>https://www.emacs.dyerdwelling.family/emacs/20241128130253-emacs--shrinking-widening-org-tables/</guid><description>&lt;p>This post is more of a note to myself, something I can store in my single emacs blog org file, so if I forget again, I can quickly search.&lt;/p>
&lt;p>I keep forgetting the keybinding to shrink and expand an org table.&lt;/p>
&lt;p>I often define tables to have a narrower width than content using the &amp;lt;num&amp;gt; concept in the top line of the table, but when I want to expand, I just can&amp;rsquo;t remember the keybinding.&lt;/p>
&lt;p>A search in &lt;code>*help*&lt;/code> for &lt;code>org-table-shrink&lt;/code> and &lt;code>org-table-expand&lt;/code> reveals nothing!&lt;/p>
&lt;figure>&lt;img src="https://www.emacs.dyerdwelling.family/emacs/20241128130253-emacs--Shrinking-Widening-Org-Tables.jpg" width="100%">
&lt;/figure>
&lt;p>Anyway, here is a note to myself:&lt;/p>
&lt;pre tabindex="0">&lt;code class="language-nil" data-lang="nil"> C-c TAB (translated from C-c &amp;lt;tab&amp;gt;) runs the command org-ctrl-c-tab
(found in org-mode-map), which is an interactive native-compiled
Lisp function in ‘org.el’.
It is bound to C-c TAB.
(org-ctrl-c-tab &amp;amp;optional ARG)
Toggle columns width in a table, or show children. Call
‘org-table-toggle-column-width’ if point is in a table. Otherwise
provide a compact view of the children. ARG is the level to hide.
&lt;/code>&lt;/pre>&lt;p>For clarity, within an Org table, pressing &lt;code>C-c &amp;lt;TAB&amp;gt;&lt;/code> will toggle shrink/expand the state of the column that the point is currently in.&lt;/p>
&lt;p>&lt;code>C-u C-c &amp;lt;TAB&amp;gt;&lt;/code> will shrink all columns AND&lt;/p>
&lt;p>&lt;code>C-u C-u C-c &amp;lt;TAB&amp;gt;&lt;/code> will expand all columns&lt;/p>
&lt;p>Right, that is a note, just for me 😀&lt;/p></description></item><item><title>Reducing Friction when Copying Whole Buffer To Kill Ring</title><link>https://www.emacs.dyerdwelling.family/emacs/20241118102423-emacs--copying-buffer-to-kill-ring/</link><pubDate>Mon, 18 Nov 2024 10:24:00 +0000</pubDate><author>James Dyer</author><guid>https://www.emacs.dyerdwelling.family/emacs/20241118102423-emacs--copying-buffer-to-kill-ring/</guid><description>&lt;p>Just a quick one today.&lt;/p>
&lt;p>In keeping with the ongoing effort to reduce friction in the venerable Emacs text editor, I realized that a common action I often perform is copying the entire contents of the buffer, usually for pasting elsewhere.&lt;/p>
&lt;p>To perform this I have chained together some emacs commands, namely &lt;code>(mark-whole-buffer)&lt;/code> and then &lt;code>(kill-ring-save)&lt;/code>&lt;/p>
&lt;figure>&lt;img src="https://www.emacs.dyerdwelling.family/emacs/20241118102423-emacs--Copying-Buffer-To-Kill-Ring.jpg" width="100%">
&lt;/figure>
&lt;p>The problem with pushing the buffer to the kill ring in this manner is that I lose the current cursor position/point and end up using &lt;code>isearch&lt;/code> to navigate my way back. Strangely, it is only recently that I have found this annoying!&lt;/p>
&lt;p>There are a few options to address this:&lt;/p>
&lt;ul>
&lt;li>Use Emacs marks&lt;/li>
&lt;li>Create a macro&lt;/li>
&lt;li>Create a defun&lt;/li>
&lt;/ul>
&lt;p>Initially I tried the setting &lt;code>mark&lt;/code> option meaning that I &lt;code>C-&amp;lt;SPC&amp;gt; C-&amp;lt;SPC&amp;gt;&lt;/code> to set the mark at the current position and then &lt;code>C-u C-&amp;lt;SPC&amp;gt;&lt;/code> to pop back to my previous mark set. The only issue is that &lt;code>(mark-whole-buffer)&lt;/code> creates a mark at the end of the selected region and my first mark pop would be to this position, so I have to mark pop again.&lt;/p>
&lt;p>The benefit of this approach is that I will start becoming more familiar with setting marks and navigating more efficiently within Emacs, which I really think I should learn. However, it all feels a little clunky, and you know what? I&amp;rsquo;m just going to write a simple elisp defun and bind it.&lt;/p>
&lt;p>&lt;code>save-excursion&lt;/code>, in this case, can be extremely useful!&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/copy-buffer-to-kill-ring ()
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#e6db74">&amp;#34;Copy the entire buffer to the kill ring without changing the point.&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> (save-excursion
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> (kill-ring-save (&lt;span style="color:#a6e22e">point-min&lt;/span>) (&lt;span style="color:#a6e22e">point-max&lt;/span>))))
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>(bind-key* (kbd &lt;span style="color:#e6db74">&amp;#34;M-s z&amp;#34;&lt;/span>) &lt;span style="color:#a6e22e">#&amp;#39;&lt;/span>my/copy-buffer-to-kill-ring)
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div></description></item><item><title>Org Table From Org Headings using a Babel Block</title><link>https://www.emacs.dyerdwelling.family/emacs/20241110085851-emacs--babel-block-generating-org-table-from-org-headings/</link><pubDate>Tue, 12 Nov 2024 20:20:00 +0000</pubDate><author>James Dyer</author><guid>https://www.emacs.dyerdwelling.family/emacs/20241110085851-emacs--babel-block-generating-org-table-from-org-headings/</guid><description>&lt;p>In this post, I&amp;rsquo;ll walk you through how I use an Org Babel block to generate a dynamic Org table based on Org headings.&lt;/p>
&lt;p>This approach is handy for anyone who wishes to programmatically extract information from an Org file (such as TODO states, tags, and content) and automatically format it as a neatly structured Org table. You can then export this table to various formats &amp;mdash; like HTML, Markdown, or LaTeX &amp;mdash; with built-in Org mode support.&lt;/p>
&lt;p>At work, I&amp;rsquo;m currently using Emacs and Confluence, so my idea was to figure out how to get an org file into a Confluence post in a structured manner. For the particular task I&amp;rsquo;m working on, I&amp;rsquo;ve decided that I would like to convert an org file into a table. The table format essentially flattens the information presented by org structured text, and putting it as a table in Confluence also has the advantage of column sorting and filtering.&lt;/p>
&lt;p>This seems for the task in hand the most efficient way of representing a certain set of data. I will have the advantage of always working within a familiar org document at the basic text level, leveraging all those years of Emacs muscle memory and as always forming the markup base for a myriad of export options.&lt;/p>
&lt;p>However, I&amp;rsquo;m going to devise another method of export, lets see if I can find an effectual way to convert an org file into a table that can be efficiently used in Confluence.&lt;/p>
&lt;p>I did some research and thought that maybe the org column mode could be somewhat useful. It&amp;rsquo;s tabular, right? Can&amp;rsquo;t I just grab that data and put it somewhere? Well, from what I can gather, it&amp;rsquo;s mainly for more easily representing properties and uses overlays, which are not conducive to easy export.&lt;/p>
&lt;p>A dynamic block looks quite interesting and seems to be more for representing some form of dynamic data that is consistently updated and is tied to an org document. I think a pattern of tabular representation can be generated using this form and I might take a look at this in the future but for now it doesn&amp;rsquo;t quite seem like the incremental learning opportunity I&amp;rsquo;m looking for.&lt;/p>
&lt;p>And another option?, well that is to play around with an ox org back-end?, for example generating an html table and tailoring to my own needs. This might be a bit too advanced for me at this stage, I think I will stick with the approach I decided on below which is to create an org babel block and generate a table through the output header parameter &lt;code>:results table&lt;/code> mechanism.&lt;/p>
&lt;p>Of course I am familiar with org babel blocks, it is how I generate my Emacs init file through the tangle mechanism, but I didn&amp;rsquo;t quite realise just how powerful it could be. My idea here is to parse the current org buffer with a babel block and output an org table simply as a string which would be interpreted as an org table in org-mode, that would work right?. Well yes, yes it did work, I essentially just kept appending row strings as you would see in a typical org table, separated by the pipe character. The code however was not particularly efficient, I would gather (push) all the relevant org items onto a list and then loop over them to construct the org table string. As far as I was aware at the time the only babel mechanism for generation was though a form of pushing textual data to stdout which would then appear in the current buffer to be interpreted in whichever way you want according to the mode.&lt;/p>
&lt;p>I stumbled on to a better solution however. I had already done the hard work of constructing a list of lists, with each sublist representing a row, it turns out that I can just return this list from the babel block and if I have the babel output header parameters set as &lt;code>:results table&lt;/code> the list will be interpreted as a table!&lt;/p>
&lt;p>Well lets try this out&amp;hellip;&lt;/p>
&lt;p>Note: the following examples will all be a babel block with the following header parameters defined:&lt;/p>
&lt;pre tabindex="0">&lt;code class="language-nil" data-lang="nil">#+begin_src emacs-lisp :results table :exports both
&lt;/code>&lt;/pre>&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-emacs-lisp" data-lang="emacs-lisp">&lt;span style="display:flex;">&lt;span>(let ((rows &lt;span style="color:#f92672">&amp;#39;&lt;/span>()))
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> (push (&lt;span style="color:#a6e22e">list&lt;/span> &lt;span style="color:#e6db74">&amp;#34;1: first row&amp;#34;&lt;/span> &lt;span style="color:#e6db74">&amp;#34;2: first row&amp;#34;&lt;/span>) rows)
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> rows)
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;pre tabindex="0">&lt;code class="language-nil" data-lang="nil">| 1: first row | 2: first row |
&lt;/code>&lt;/pre>&lt;p>That is a table with a single row!&lt;/p>
&lt;p>Lets expand&amp;hellip;&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-emacs-lisp" data-lang="emacs-lisp">&lt;span style="display:flex;">&lt;span>(let ((rows &lt;span style="color:#f92672">&amp;#39;&lt;/span>()))
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> (push (&lt;span style="color:#a6e22e">list&lt;/span> &lt;span style="color:#e6db74">&amp;#34;1: first row&amp;#34;&lt;/span> &lt;span style="color:#e6db74">&amp;#34;2: first row&amp;#34;&lt;/span>) rows)
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> (push (&lt;span style="color:#a6e22e">list&lt;/span> &lt;span style="color:#e6db74">&amp;#34;1: second row&amp;#34;&lt;/span> &lt;span style="color:#e6db74">&amp;#34;2: second row&amp;#34;&lt;/span>) rows)
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> rows)
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;pre tabindex="0">&lt;code class="language-nil" data-lang="nil">| 1: second row | 2: second row |
| 1: first row | 2: first row |
&lt;/code>&lt;/pre>&lt;p>So now I have a simple mechanism for adding multiple rows.&lt;/p>
&lt;p>Hang on!, the rows are not the order I expected, lets reverse.&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-emacs-lisp" data-lang="emacs-lisp">&lt;span style="display:flex;">&lt;span>(let ((rows &lt;span style="color:#f92672">&amp;#39;&lt;/span>()))
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> (push (&lt;span style="color:#a6e22e">list&lt;/span> &lt;span style="color:#e6db74">&amp;#34;1: first row&amp;#34;&lt;/span> &lt;span style="color:#e6db74">&amp;#34;2: first row&amp;#34;&lt;/span>) rows)
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> (push (&lt;span style="color:#a6e22e">list&lt;/span> &lt;span style="color:#e6db74">&amp;#34;1: second row&amp;#34;&lt;/span> &lt;span style="color:#e6db74">&amp;#34;2: second row&amp;#34;&lt;/span>) rows)
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> (&lt;span style="color:#a6e22e">reverse&lt;/span> rows))
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;pre tabindex="0">&lt;code class="language-nil" data-lang="nil">| 1: first row | 2: first row |
| 1: second row | 2: second row |
&lt;/code>&lt;/pre>&lt;p>What about the table header?, this took a while to figure out, but I think I have it.&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-emacs-lisp" data-lang="emacs-lisp">&lt;span style="display:flex;">&lt;span>(let ((rows &lt;span style="color:#f92672">&amp;#39;&lt;/span>()))
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> (push (&lt;span style="color:#a6e22e">list&lt;/span> &lt;span style="color:#e6db74">&amp;#34;1: first row&amp;#34;&lt;/span> &lt;span style="color:#e6db74">&amp;#34;2: first row&amp;#34;&lt;/span>) rows)
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> (push (&lt;span style="color:#a6e22e">list&lt;/span> &lt;span style="color:#e6db74">&amp;#34;1: second row&amp;#34;&lt;/span> &lt;span style="color:#e6db74">&amp;#34;2: second row&amp;#34;&lt;/span>) rows)
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> (setq rows (&lt;span style="color:#a6e22e">reverse&lt;/span> rows))
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> (push &lt;span style="color:#e6db74">&amp;#39;hline&lt;/span> rows)
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> rows)
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;pre tabindex="0">&lt;code class="language-nil" data-lang="nil">|---------------+---------------|
| 1: first row | 2: first row |
| 1: second row | 2: second row |
&lt;/code>&lt;/pre>&lt;p>Well that is the header line, but there is no header!!&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-emacs-lisp" data-lang="emacs-lisp">&lt;span style="display:flex;">&lt;span>(let ((rows &lt;span style="color:#f92672">&amp;#39;&lt;/span>())
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> (header (&lt;span style="color:#a6e22e">list&lt;/span> &lt;span style="color:#e6db74">&amp;#34;col1&amp;#34;&lt;/span> &lt;span style="color:#e6db74">&amp;#34;col2&amp;#34;&lt;/span>)))
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> (push (&lt;span style="color:#a6e22e">list&lt;/span> &lt;span style="color:#e6db74">&amp;#34;1: first row&amp;#34;&lt;/span> &lt;span style="color:#e6db74">&amp;#34;2: first row&amp;#34;&lt;/span>) rows)
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> (push (&lt;span style="color:#a6e22e">list&lt;/span> &lt;span style="color:#e6db74">&amp;#34;1: second row&amp;#34;&lt;/span> &lt;span style="color:#e6db74">&amp;#34;2: second row&amp;#34;&lt;/span>) rows)
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> (setq rows (&lt;span style="color:#a6e22e">reverse&lt;/span> rows))
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> (push &lt;span style="color:#e6db74">&amp;#39;hline&lt;/span> rows)
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> (&lt;span style="color:#a6e22e">cons&lt;/span> header rows))
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;pre tabindex="0">&lt;code class="language-nil" data-lang="nil">| col1 | col2 |
|---------------+---------------|
| 1: first row | 2: first row |
| 1: second row | 2: second row |
&lt;/code>&lt;/pre>&lt;p>I think I have figured it out now, so the next aspect I need to consider is how to pick up the org elements. It seems a common approach is to use &lt;code>org-map-entries&lt;/code> which steps though each headline, seemingly actually within the buffer itself and some helper functions are available which can be used to extract the data. For example, &lt;code>org-element-at-point&lt;/code>, &lt;code>org-outline-level&lt;/code>, &lt;code>org-get-tags&lt;/code>, e.t.c, I am aware that there is a more formal API type of method where a syntactical tree can be made available through &lt;code>org-element-parse-buffer&lt;/code> but that maybe is for another time. Lets move ahead with the org babel, table output, org list construction through org-map-entries implementation.&lt;/p>
&lt;h2 id="the-task-collecting-headings-and-outputting-in-a-table">The Task - Collecting Headings and Outputting in a Table&lt;/h2>
&lt;p>The goal is simple: outline tasks with various headings, nesting levels, TODO states, tags, and content in an Org file, and use that information to generate an Org table using an &lt;strong>Emacs Lisp Org Babel block&lt;/strong>. This allows us to extract metadata from headings into a well-structured table with a corresponding header row.&lt;/p>
&lt;p>Each Org heading will be converted into a table row in the following format:&lt;/p>
&lt;ul>
&lt;li>&lt;strong>Title&lt;/strong>: The full heading, including indentation to represent the level hierarchy.&lt;/li>
&lt;li>&lt;strong>TODO State&lt;/strong>: The TODO keyword (like TODO, DONE, or other custom states) associated with the heading.&lt;/li>
&lt;li>&lt;strong>Tags&lt;/strong>: Any Org tags associated with the heading.&lt;/li>
&lt;li>&lt;strong>Contents&lt;/strong>: The content that immediately follows the heading.&lt;/li>
&lt;/ul>
&lt;h2 id="the-code">The Code&lt;/h2>
&lt;p>The following Emacs Lisp code in an Org Babel block which scans the Org file, collects the required information from all headings, and formats it into a table. Let&amp;rsquo;s break it down step-by-step:&lt;/p>
&lt;h3 id="org-babel-block-automatic-table-generation">Org Babel Block: Automatic Table Generation&lt;/h3>
&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-emacs-lisp" data-lang="emacs-lisp">&lt;span style="display:flex;">&lt;span>(let ((rows)
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> (header (&lt;span style="color:#a6e22e">list&lt;/span> &lt;span style="color:#e6db74">&amp;#34;Title&amp;#34;&lt;/span> &lt;span style="color:#e6db74">&amp;#34;TODO&amp;#34;&lt;/span> &lt;span style="color:#e6db74">&amp;#34;Tags&amp;#34;&lt;/span> &lt;span style="color:#e6db74">&amp;#34;Contents&amp;#34;&lt;/span>))
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> (table-rows &lt;span style="color:#f92672">&amp;#39;&lt;/span>())
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> (max-level &lt;span style="color:#ae81ff">0&lt;/span>))
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> (org-map-entries
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> (lambda ()
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> (let* ((entry (org-element-at-point))
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> (heading (org-get-heading &lt;span style="color:#66d9ef">t&lt;/span> &lt;span style="color:#66d9ef">t&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> (level (org-outline-level))
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> (tags (org-get-tags))
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> (todo (org-get-todo-state))
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> (contents &lt;span style="color:#e6db74">&amp;#34;&amp;#34;&lt;/span>))
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> (org-end-of-meta-data &lt;span style="color:#66d9ef">nil&lt;/span>)
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> (when (&lt;span style="color:#a6e22e">eq&lt;/span> (org-element-type (org-element-at-point)) &lt;span style="color:#e6db74">&amp;#39;paragraph&lt;/span>)
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> (let ((start (&lt;span style="color:#a6e22e">point&lt;/span>)))
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> (org-next-visible-heading &lt;span style="color:#ae81ff">1&lt;/span>)
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> (setq contents (&lt;span style="color:#a6e22e">buffer-substring-no-properties&lt;/span> start (&lt;span style="color:#a6e22e">point&lt;/span>)))
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> (dolist (pattern &lt;span style="color:#f92672">&amp;#39;&lt;/span>(&lt;span style="color:#e6db74">&amp;#34;^#\\+begin.*&amp;#34;&lt;/span> &lt;span style="color:#e6db74">&amp;#34;^#\\+end.*&amp;#34;&lt;/span> &lt;span style="color:#e6db74">&amp;#34;\n+&amp;#34;&lt;/span>))
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> (setq contents (replace-regexp-in-string pattern
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> (if (string= pattern &lt;span style="color:#e6db74">&amp;#34;\n+&amp;#34;&lt;/span>) &lt;span style="color:#e6db74">&amp;#34; &amp;#34;&lt;/span> &lt;span style="color:#e6db74">&amp;#34;&amp;#34;&lt;/span>)
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> (string-trim contents))))))
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> (setq max-level (&lt;span style="color:#a6e22e">max&lt;/span> max-level level))
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> (push (&lt;span style="color:#a6e22e">list&lt;/span> (&lt;span style="color:#a6e22e">concat&lt;/span> (&lt;span style="color:#a6e22e">make-string&lt;/span> (&lt;span style="color:#a6e22e">*&lt;/span> (&lt;span style="color:#a6e22e">1-&lt;/span> level) &lt;span style="color:#ae81ff">2&lt;/span>) &lt;span style="color:#ae81ff">45&lt;/span>) &lt;span style="color:#e6db74">&amp;#34; &amp;#34;&lt;/span> heading)
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> (or todo &lt;span style="color:#e6db74">&amp;#34;&amp;#34;&lt;/span>) (string-join tags &lt;span style="color:#e6db74">&amp;#34;:&amp;#34;&lt;/span>) (or contents &lt;span style="color:#e6db74">&amp;#34;&amp;#34;&lt;/span>)) rows))))
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> (setq rows (&lt;span style="color:#a6e22e">reverse&lt;/span> rows))
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> (push &lt;span style="color:#e6db74">&amp;#39;hline&lt;/span> rows)
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> (&lt;span style="color:#a6e22e">cons&lt;/span> header rows))
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;h2 id="how-it-works">How It Works&lt;/h2>
&lt;ul>
&lt;li>
&lt;p>&lt;strong>&lt;code>org-map-entries&lt;/code>&lt;/strong>: This function iterates through all headings in the Org document. Each heading that it encounters produces metadata, such as the heading title, level in the hierarchy, TODO state, tags, and the content following the heading.&lt;/p>
&lt;/li>
&lt;li>
&lt;p>&lt;strong>Content Sanitization&lt;/strong>: For headings with associated content, newline characters and code block markers (like &lt;code>#+begin_src&lt;/code> and &lt;code>#+end_src&lt;/code>) are removed. This ensures that the content is condensed into a single line for insertion into the Org table.&lt;/p>
&lt;/li>
&lt;li>
&lt;p>&lt;strong>&lt;code>heading&lt;/code>&lt;/strong>: This variable stores the full heading, formatted with indentations based on its nesting level (&lt;code>level&lt;/code>) to visually represent its position in the Org structure.&lt;/p>
&lt;/li>
&lt;li>
&lt;p>&lt;strong>Output Format&lt;/strong>: The information is collected into a list (&lt;code>rows&lt;/code>). The list contains individual lists representing rows of the table. A &amp;ldquo;horizontal line&amp;rdquo; (&lt;code>'hline&lt;/code>) is inserted to act as a row separator.&lt;/p>
&lt;/li>
&lt;li>
&lt;p>Finally, the first list in &lt;code>rows&lt;/code> is a header row (&lt;code>header&lt;/code>), which includes the column titles: &amp;ldquo;Title&amp;rdquo;, &amp;ldquo;TODO&amp;rdquo;, &amp;ldquo;Tags&amp;rdquo;, and &amp;ldquo;Contents&amp;rdquo;.&lt;/p>
&lt;/li>
&lt;/ul>
&lt;p>When evaluated (&lt;code>C-c C-c&lt;/code>) within this org source for the blog post the code block outputs a table like the one below, note that it will also include the example Org Structure defined in the next section.&lt;/p>
&lt;figure>&lt;img src="https://www.emacs.dyerdwelling.family/emacs/20241110085851-emacs--Babel-Block-Generating-Org-Table-From-Org-Headings/2024-11-10-13-54-46.jpg" width="100%">
&lt;/figure>
&lt;h2 id="example-org-structure">Example Org Structure&lt;/h2>
&lt;p>Here&amp;rsquo;s an example Org structure that would produce part of the table above:&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">**&lt;/span>&lt;span style="color:#960050;background-color:#1e0010"> TODO&lt;/span> one :tag1:tag2:
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#75715e">***&lt;/span> DOING sub
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>content 1
&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">#+begin_src&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&amp;gt;&amp;gt;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>code start
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>more code &amp;gt;&amp;gt;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#75715e">#+end_src&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>after code
&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">****&lt;/span> further into the tree!&lt;span style="font-style:italic"> :subtag:&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>first line, no space before!
&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">*****&lt;/span> even further down the hole!&lt;span style="font-style:italic"> :hole_tag:&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>No escape!!
&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">**&lt;/span>&lt;span style="color:#960050;background-color:#1e0010"> TODO&lt;/span> two :tag_two:
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>content 2
&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">**&lt;/span> three
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#75715e">SCHEDULED: &lt;/span>&lt;span style="color:#75715e">&amp;lt;2024-11-10 Sun&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;h2 id="exporting-the-table">Exporting the Table&lt;/h2>
&lt;p>Once this table is generated, it will correctly render within Org mode, respecting the Org table formatting rules. You can also export it to other formats (like HTML or Markdown) using standard Org export commands (such as &lt;code>C-c C-e h&lt;/code> or &lt;code>C-c C-e m&lt;/code>).&lt;/p>
&lt;p>For example, exporting to &lt;strong>HTML&lt;/strong> will produce a structured HTML table with the column headers formatted as table header (&lt;code>&amp;lt;th&amp;gt;&lt;/code>) elements, and the data rows and horizontal lines correctly converted into formatting tags.&lt;/p>
&lt;figure>&lt;img src="https://www.emacs.dyerdwelling.family/emacs/20241110085851-emacs--Babel-Block-Generating-Org-Table-From-Org-Headings/2024-11-10-13-53-37.jpg" width="100%">
&lt;/figure>
&lt;h2 id="conclusion">Conclusion&lt;/h2>
&lt;p>By leveraging Emacs Lisp and Org Babel, we&amp;rsquo;ve created a highly flexible way to extract information from Org headings and output it as a well-structured table. This method not only saves time when working with large or hierarchical documents, but it also provides a powerful way to export Org-based data to various formats based on your needs.&lt;/p>
&lt;p>This approach can be extended further to include other metadata (like scheduled dates, deadlines, or custom properties) or more advanced formatting options for both Org and export formats.&lt;/p>
&lt;p>&lt;strong>Next Steps&lt;/strong>: Experiment with Org Babel to explore even more advanced use cases, for example I would like to format the table in a more flexible manner, for example to specify the width and maybe the cell / row colour based on content.&lt;/p></description></item><item><title>Generate Current Year tag in an Org Capture Template</title><link>https://www.emacs.dyerdwelling.family/emacs/20241007121110-emacs--org-capture-dynamically-setting-year/</link><pubDate>Fri, 01 Nov 2024 08:30:00 +0000</pubDate><author>James Dyer</author><guid>https://www.emacs.dyerdwelling.family/emacs/20241007121110-emacs--org-capture-dynamically-setting-year/</guid><description>&lt;p>A crucial aspect of maintaining organized and up-to-date notes is the use of Org Capture templates.&lt;/p>
&lt;p>I have currently always hard-coded/set the current year in my org capture templates for use when exporting/constructing my web pages and as a tag for some filtering / web index generation. Of course the main disadvantage of this is that I have to remember to update the year each year, of which I often fail miserably. ☹️&lt;/p>
&lt;figure>&lt;img src="https://www.emacs.dyerdwelling.family/emacs/20241007121110-emacs--Org-Capture-Dynamically-Setting-Year.jpg" width="100%">
&lt;/figure>
&lt;p>Can I get an org capture template to auto generate just the year as a tag like other capture template attributes?&lt;/p>
&lt;p>Well yes, yes you can, and there a couple of ways to achieve this.&lt;/p>
&lt;p>Firstly there is the org capture family of format strings which inserts a date when the capture is triggered. This is a bit much for me and instead of generating just a string for the year, each specifier will generate a full timestamp:&lt;/p>
&lt;ul>
&lt;li>&lt;strong>%U&lt;/strong> — Inactive Timestamp (Full Date and Time)&lt;/li>
&lt;li>&lt;strong>%T&lt;/strong> — Active Timestamp (Full Date and Time)&lt;/li>
&lt;li>&lt;strong>%t&lt;/strong> — Active Timestamp (Current Date Only)&lt;/li>
&lt;li>&lt;strong>%u&lt;/strong> — Inactive Timestamp (Current Date Only)&lt;/li>
&lt;/ul>
&lt;p>However there is another format specifier, which can just insert the year, simply use &lt;strong>&lt;code>%&amp;lt;%Y&amp;gt;&lt;/code>&lt;/strong> which is part of a pretty standard common date or time string format options, for example, here are some useful format specifiers that can be used:&lt;/p>
&lt;ul>
&lt;li>`%Y`: Year (e.g., `2023`)&lt;/li>
&lt;li>`%m`: Month (e.g., `10` for October)&lt;/li>
&lt;li>`%d`: Day (e.g., `05`)&lt;/li>
&lt;li>`%H`: Hour (24-hour clock)&lt;/li>
&lt;li>`%M`: Minute&lt;/li>
&lt;li>`%S`: Seconds&lt;/li>
&lt;li>`%A`: Full weekday name (e.g., `Monday`)&lt;/li>
&lt;li>`%B`: Full month name (e.g., `October`)&lt;/li>
&lt;/ul>
&lt;p>Well that was easy, so to insert a year tag I can just wrap colons around %&amp;lt;%Y&amp;gt;&lt;/p>
&lt;p>Just for fun, lets try something a little more advanced to achieve the same goal but this time allowing much more flexibility.&lt;/p>
&lt;p>A particularly useful functional aspect of an org template is that the dynamic generation of the current year can be achieved using some elisp. Lets use the &lt;code>(format-time-string)&lt;/code> function.&lt;/p>
&lt;h2 id="understanding--format-time-string-y">Understanding &lt;code>(format-time-string &amp;quot;%Y&amp;quot;)&lt;/code>&lt;/h2>
&lt;p>Before diving into the implementation, let&amp;rsquo;s break down what &lt;code>(format-time-string &amp;quot;%Y&amp;quot;)&lt;/code> does:&lt;/p>
&lt;ul>
&lt;li>
&lt;p>&lt;strong>&lt;code>format-time-string&lt;/code>&lt;/strong>: This is an Emacs Lisp function that formats the current time and date into a string based on a specified format.&lt;/p>
&lt;/li>
&lt;li>
&lt;p>&lt;strong>&lt;code>&amp;quot;%Y&amp;quot;&lt;/code>&lt;/strong>: This format code specifically fetches the four-digit representation of the current year. For example, in 2023, it would insert &amp;ldquo;2023&amp;rdquo;, and conforms to the date specifiers defined above for the more simple capture template format definition.&lt;/p>
&lt;/li>
&lt;/ul>
&lt;p>By embedding this function into my Org Capture templates, the inclusion of the current year is neatly automated, this not only reduces manual updates but also ensures consistency across my entries.&lt;/p>
&lt;h2 id="implementing-dynamic-year-in-capture-templates">Implementing Dynamic Year in Capture Templates&lt;/h2>
&lt;p>Here&amp;rsquo;s an example Org Capture template that uses &lt;code>(format-time-string &amp;quot;%Y&amp;quot;)&lt;/code> to dynamically manage the year for Emacs-related entries:&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>(&lt;span style="color:#e6db74">&amp;#34;e&amp;#34;&lt;/span> &lt;span style="color:#e6db74">&amp;#34;Emacs&amp;#34;&lt;/span> plain
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> (file+function
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#e6db74">&amp;#34;~/DCIM/content/emacs--all.org&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> my-capture-top-level)
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#e6db74">&amp;#34;* TODO %^{title} :emacs:%(format-time-string \&amp;#34;%Y\&amp;#34;):&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> :PROPERTIES:
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> :EXPORT_FILE_NAME: %&amp;lt;%Y%m%d%H%M%S&amp;gt;-emacs--%\\1
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> :EXPORT_HUGO_SECTION: emacs
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> :EXPORT_HUGO_LASTMOD: &amp;lt;%&amp;lt;%Y-%m-%d %H:%M&amp;gt;&amp;gt;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> :EXPORT_HUGO_CUSTOM_FRONT_MATTER+: :thumbnail /emacs/%&amp;lt;%Y%m%d%H%M%S&amp;gt;-emacs--%\\1.jpg
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> :END:
&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:#e6db74">&amp;#34; :prepend t :jump-to-captured t)
&lt;/span>&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;h3 id="key-features-of-this-template">Key Features of this Template&lt;/h3>
&lt;ul>
&lt;li>&lt;strong>Automatic Year Insertion&lt;/strong>:
&lt;ul>
&lt;li>The directive &lt;code>:%(format-time-string \&amp;quot;%Y\&amp;quot;):&lt;/code>, within the headline, ensures that the current year is automatically included as a tag whenever you create a new entry. This functionality is particularly useful for organizing tasks by year, making it easier to retrieve and filter information based on time.&lt;/li>
&lt;/ul>
&lt;/li>
&lt;li>&lt;strong>Enhanced Organization&lt;/strong>:
&lt;ul>
&lt;li>By integrating the current year automatically, you maintain a systematic arrangement of notes and tasks, minimizing the need for manual adjustments every new year.&lt;/li>
&lt;/ul>
&lt;/li>
&lt;li>&lt;strong>Ease of Use&lt;/strong>:
&lt;ul>
&lt;li>This template is set up for simplicity and re-usability. Once implemented, it requires no further changes for year-specific adjustments, allowing you to focus on content generation rather than template management.&lt;/li>
&lt;/ul>
&lt;/li>
&lt;/ul>
&lt;h2 id="benefits-of-dynamic-year-management">Benefits of Dynamic Year Management&lt;/h2>
&lt;ul>
&lt;li>&lt;strong>Time-Saving&lt;/strong>: No need to modify the template annually, thus saving valuable time.&lt;/li>
&lt;li>&lt;strong>Consistent Records&lt;/strong>: Automatic year tagging provides a consistent way of tracking entries over time.&lt;/li>
&lt;li>&lt;strong>Improved Searchability&lt;/strong>: Facilitates year-based searches and statistics generation, enhancing the utility of your Org files.&lt;/li>
&lt;/ul></description></item><item><title>selected-window-accent-mode with window colour blending options</title><link>https://www.emacs.dyerdwelling.family/emacs/20241021172036-emacs--enhanced-selected-window-accent-functionality-with-blending-options/</link><pubDate>Tue, 22 Oct 2024 08:40:00 +0100</pubDate><author>James Dyer</author><guid>https://www.emacs.dyerdwelling.family/emacs/20241021172036-emacs--enhanced-selected-window-accent-functionality-with-blending-options/</guid><description>&lt;p>I have enhanced &lt;code>selected-window-accent&lt;/code> functionality with blending options to blend colourize the selected window (see below):&lt;/p>
&lt;img src="https://raw.githubusercontent.com/captainflasmr/selected-window-accent-mode/main/demos/selected-window-accent-mode-demo-blend.gif" width="80%" />
&lt;br />
&lt;br />
&lt;p>This means that not only will the mode-line, fringes and header be accent coloured, but also the whole selected window background colour, tinted to the alpha chosen.&lt;/p>
&lt;p>The features added are as follows:&lt;/p>
&lt;ul>
&lt;li>Introduced &lt;code>selected-window-accent-use-blend-background&lt;/code> and &lt;code>selected-window-accent-use-blend-alpha&lt;/code> to allow blending of the accent colour with the background of the selected window a selected alpha amount.&lt;/li>
&lt;li>Introduced new toggle functions and added to transient:
&lt;ul>
&lt;li>&lt;code>selected-window-accent-toggle-blend-background&lt;/code>&lt;/li>
&lt;li>&lt;code>selected-window-accent-toggle-pywal&lt;/code>&lt;/li>
&lt;/ul>
&lt;/li>
&lt;li>Added function &lt;code>selected-window-accent-blend-colors&lt;/code> to support blending of two colours.&lt;/li>
&lt;li>Updated &lt;code>selected-window-accent--set-foreground-color&lt;/code> and &lt;code>selected-window-accent&lt;/code> functions to incorporate new blending feature.&lt;/li>
&lt;li>Improved existing functions for setting and toggling accent features, ensuring consistent style and formatting.&lt;/li>
&lt;li>Cleaned up indentation and formatting inconsistencies across the file for better readability.&lt;/li>
&lt;/ul>
&lt;p>Example use of new selected window blending functionality:&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 selected-window-accent-mode
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> :config (selected-window-accent-mode &lt;span style="color:#ae81ff">1&lt;/span>)
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> :custom
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> (selected-window-accent-fringe-thickness &lt;span style="color:#ae81ff">10&lt;/span>)
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> (selected-window-accent-use-blend-background &lt;span style="color:#66d9ef">t&lt;/span>)
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> (selected-window-accent-use-blend-alpha &lt;span style="color:#ae81ff">0.2&lt;/span>)
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> (selected-window-accent-tab-accent &lt;span style="color:#66d9ef">t&lt;/span>)
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> (selected-window-accent-custom-color &lt;span style="color:#e6db74">&amp;#34;cyan4&amp;#34;&lt;/span>)
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> (selected-window-accent-mode-style &lt;span style="color:#e6db74">&amp;#39;default&lt;/span>))
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;figure>&lt;img src="https://www.emacs.dyerdwelling.family/emacs/selected-window-accent-mode-07.jpg" width="100%">
&lt;/figure>
&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 selected-window-accent-mode
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> :config (selected-window-accent-mode &lt;span style="color:#ae81ff">1&lt;/span>)
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> :custom
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> (selected-window-accent-fringe-thickness &lt;span style="color:#ae81ff">10&lt;/span>)
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> (selected-window-accent-use-blend-background &lt;span style="color:#66d9ef">t&lt;/span>)
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> (selected-window-accent-use-blend-alpha &lt;span style="color:#ae81ff">0.1&lt;/span>)
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> (selected-window-accent-tab-accent &lt;span style="color:#66d9ef">t&lt;/span>)
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> (selected-window-accent-custom-color &lt;span style="color:#e6db74">&amp;#34;orange&amp;#34;&lt;/span>)
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> (selected-window-accent-mode-style &lt;span style="color:#e6db74">&amp;#39;subtle&lt;/span>))
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;figure>&lt;img src="https://www.emacs.dyerdwelling.family/emacs/selected-window-accent-mode-08.jpg" width="100%">
&lt;/figure></description></item><item><title>Transforming Dired File Paths into Org Links</title><link>https://www.emacs.dyerdwelling.family/emacs/20240918111443-emacs--transforming-dired-file-paths-into-org-links-with-emacs-lisp/</link><pubDate>Thu, 17 Oct 2024 15:36:00 +0100</pubDate><author>James Dyer</author><guid>https://www.emacs.dyerdwelling.family/emacs/20240918111443-emacs--transforming-dired-file-paths-into-org-links-with-emacs-lisp/</guid><description>&lt;p>I am of course using org mode for organizing my tasks, notes and other paraphernalia.&lt;/p>
&lt;figure>&lt;img src="https://www.emacs.dyerdwelling.family/emacs/20240918111443-emacs--Transforming-Dired-File-Paths-into-Org-Links-with-Emacs-Lisp.jpg" width="100%">
&lt;/figure>
&lt;p>In addition, I generate static web pages from org using &lt;strong>&lt;code>hugo&lt;/code>&lt;/strong>, with each org heading representing a single post.&lt;/p>
&lt;p>I often find myself needing to refer to files stored in a directory, especially image files that can be org embedded and then passed through to Hugo by the &lt;code>ox-hugo&lt;/code> package.&lt;/p>
&lt;p>Navigating to the file and manually generating the appropriate org link can be just a little cumbersome - to initially pick up the file name and then insert the relevant org attributes.&lt;/p>
&lt;p>&lt;code>dired-copy-filename-as-kill&lt;/code> is a function I often use in &lt;code>dired&lt;/code>, which copies the filename under point. I can then use something like &lt;code>tempel&lt;/code> to insert the attributes. However, while the combination of copying the filename in &lt;code>dired&lt;/code> and then remembering the &lt;code>tempel&lt;/code> shortcode is quite efficient, I think this could be accomplished more cleanly with some Elisp and a &lt;code>dired-mode&lt;/code> mapping.&lt;/p>
&lt;p>Here is a typical embedded image form, which restricts the image within the org document to 300 pixels and to 100% width when exported through hugo to my web site.&lt;/p>
&lt;pre tabindex="0">&lt;code class="language-nil" data-lang="nil">#+attr_org: :width 300px
#+attr_html: :width 100%
[[file:static/emacs/20240918111443-emacs--Transforming-Dired-File-Paths-into-Org-Links-with-Emacs-Lisp.jpg]]
&lt;/code>&lt;/pre>&lt;p>Lets walk through a custom function, &lt;code>my/dired-file-to-org-link&lt;/code>, which transforms the &lt;code>dired&lt;/code> file path under cursor/point into an org link and copies it to the kill ring for easy pasting.&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/dired-file-to-org-link ()
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#e6db74">&amp;#34;Transform the file path under the cursor in Dired to an Org mode
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#e6db74">link and copy to kill ring.&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 ((file-path (dired-get-file-for-visit)))
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> (if file-path
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> (let* ((relative-path (file-relative-name file-path
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> (project-root (project-current &lt;span style="color:#66d9ef">t&lt;/span>))))
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> (org-link (&lt;span style="color:#a6e22e">concat&lt;/span> &lt;span style="color:#e6db74">&amp;#34;#+attr_org: :width 300px\n&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#e6db74">&amp;#34;#+attr_html: :width 100%\n&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#e6db74">&amp;#34;file:&amp;#34;&lt;/span> relative-path &lt;span style="color:#e6db74">&amp;#34;\n&amp;#34;&lt;/span>)))
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> (kill-new org-link)
&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;Copied to kill ring: %s&amp;#34;&lt;/span> org-link))
&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;No file under the cursor&amp;#34;&lt;/span>))))
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>When &lt;code>my/dired-file-to-org-link&lt;/code> is called in a dired buffer, it gets the file path under the point, transforms it into an Org link with specific attributes, and copies the link to the kill ring. This enables you to quickly paste it into your Org file.&lt;/p>
&lt;p>I map this to the letter &lt;code>b&lt;/code> in the &lt;code>dired-mode&lt;/code> mapping, which for me mnemonically refers to (b)log and the b key is not one I currently use in dired.&lt;/p>
&lt;p>Note also the use of project.el with the project functions, as I have a project top level defined for my website content, allowing for an easily generated relative path name.&lt;/p>
&lt;p>Therefore for example, &lt;code>20240918111443-emacs--Transforming-Dired-File-Paths-into-Org-Links-with-Emacs-Lisp.jpg&lt;/code> from dired in an &lt;code>emacs&lt;/code> directory is transformed into :&lt;/p>
&lt;pre tabindex="0">&lt;code class="language-nil" data-lang="nil">#+attr_org: :width 300px
#+attr_html: :width 100%
[[file:static/emacs/20240918111443-emacs--Transforming-Dired-File-Paths-into-Org-Links-with-Emacs-Lisp.jpg]]
&lt;/code>&lt;/pre></description></item><item><title>Simple Directory and File Creation Using Vertico Completion Exit With Input</title><link>https://www.emacs.dyerdwelling.family/emacs/20241004102654-emacs--simple-directory-creation-using-vertico-exit-with-input/</link><pubDate>Wed, 09 Oct 2024 20:30:00 +0100</pubDate><author>James Dyer</author><guid>https://www.emacs.dyerdwelling.family/emacs/20241004102654-emacs--simple-directory-creation-using-vertico-exit-with-input/</guid><description>&lt;p>In my previous post about creating simple functions to create a new directory and file in &lt;code>dired&lt;/code>, I received an interesting comment suggesting another way to bypass the potential messiness of completion and pass through the literal text input.&lt;/p>
&lt;figure>&lt;img src="https://www.emacs.dyerdwelling.family/emacs/20241004102654-emacs--Simple-Directory-Creation-Using-Vertico-Exit-With-Input.jpg" width="100%">
&lt;/figure>
&lt;p>My last post :&lt;/p>
&lt;p>&lt;a href="https://www.emacs.dyerdwelling.family/emacs/20240922201246-emacs--efficient-directory-and-file-management-with-dired-in-emacs/">Simple Directory and File Creation in Dired&lt;/a>&lt;/p>
&lt;p>It is a subtlety of most if not all completion systems, and that is to exit with any input, hence exiting without having to necessarily match and complete.&lt;/p>
&lt;p>Given this, and given that I&amp;rsquo;m using the completion framework of &lt;code>vertico&lt;/code>, I thought I would revisit the vertico github manual : &lt;a href="https://github.com/minad/vertico">https://github.com/minad/vertico&lt;/a>&lt;/p>
&lt;p>and I find :&lt;/p>
&lt;p>&lt;strong>M-RET -&amp;gt; vertico-exit-input&lt;/strong>&lt;/p>
&lt;blockquote>
&lt;p>vertico-exit-input exits with the minibuffer input instead. Exiting with the current input is needed when you want to create a new buffer or a new file with find-file or switch-to-buffer. As an alternative to pressing M-RET, move the selection up to the input prompt by pressing the up arrow key and then press RET.&lt;/p>
&lt;/blockquote>
&lt;p>Well this is exactly the issue I was having and why I wrote my two little simple functions. Now if I build in M-RET into my muscle memory (which already seems pretty natural), I can refine my Emacs init, while adhering more to vanilla Emacs concepts. Also as I now am being more idiomatic, I suspect the M-RET keybinding may just come in handy at some point in the future.&lt;/p>
&lt;p>So, now rebinding &lt;code>dired-mode-map&lt;/code> as follows:&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>(&lt;span style="color:#e6db74">&amp;#34;_&amp;#34;&lt;/span> &lt;span style="color:#f92672">.&lt;/span> dired-create-empty-file)
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>This is all that is required; note that &lt;code>dired-create-directory&lt;/code> is already bound to my preferred keybinding.&lt;/p>
&lt;p>So continues the evolution of my Emacs understanding!&lt;/p>
&lt;p>As a side-ish note, I have found that writing this blog provides not only an opportunity to explore a topic further and thoroughly understand it, thereby enabling me to articulate the subject fully, but also to receive comments and discover subtle nuances that I hadn&amp;rsquo;t realized existed or hadn&amp;rsquo;t thought to explore!&lt;/p></description></item><item><title>Syncing Tab Theme After Theme Load</title><link>https://www.emacs.dyerdwelling.family/emacs/20241001092329-emacs--syncing-tab-theme-after-theme-load/</link><pubDate>Tue, 01 Oct 2024 09:50:00 +0100</pubDate><author>James Dyer</author><guid>https://www.emacs.dyerdwelling.family/emacs/20241001092329-emacs--syncing-tab-theme-after-theme-load/</guid><description>&lt;p>Previously I wrote about the incompatibility of some Emacs themes with the tab-bar and my temporary fix:&lt;/p>
&lt;p>&lt;a href="https://www.emacs.dyerdwelling.family/emacs/20240817082349-emacs--syncing-tab-bar-to-theme/">Syncing Tab Bar To Theme&lt;/a>&lt;/p>
&lt;figure>&lt;img src="https://www.emacs.dyerdwelling.family/emacs/20241001092329-emacs--Syncing-Tab-Theme-After-Theme-Load.jpg" width="100%">
&lt;/figure>
&lt;p>The tab bar was introduced in Emacs 27.1, during which I created a function to apply some simple logic to make the tabs look moderately decent and fit in with the new theme. The only thing I couldn&amp;rsquo;t figure out was how to activate the function after a theme load, as there didn&amp;rsquo;t seem to be a hook.&lt;/p>
&lt;p>Well, actually there does seem to be such a hook and it seems to have gone into Emacs 29. As with a lot of these little tweaks I found this on reddit in the emacs subreddit:&lt;/p>
&lt;p>&lt;a href="https://www.reddit.com/r/emacs/comments/1ft3wwm/did_the_much_needed_theme_change_hook_arrive_with/">https://www.reddit.com/r/emacs/comments/1ft3wwm/did_the_much_needed_theme_change_hook_arrive_with/&lt;/a>&lt;/p>
&lt;p>From my previous post I wrote :&lt;/p>
&lt;blockquote>
&lt;p>Now that the &lt;code>defun&lt;/code> is available, how to activate it? My first thought was to add a hook for when a theme is loaded, but there doesn&amp;rsquo;t seem to be such a feature!? I also tried advising around `load-theme`, but that didn’t go well either, it worked for themes that were not loaded, but for those that were, a change in state was not detected, and the tab theme tweak was not applied.&lt;/p>
&lt;/blockquote>
&lt;p>The solution is to simply add the following to your init file. Now that I know about this, I might even find some other use for it.&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/after-theme-loaded(theme)
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> (my/sync-tab-bar-to-theme))
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>(add-hook &lt;span style="color:#e6db74">&amp;#39;enable-theme-functions&lt;/span> &lt;span style="color:#e6db74">&amp;#39;my/after-theme-loaded&lt;/span>)
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>with the original &lt;code>my/sync-tab-bar-to-theme&lt;/code> as follows:&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/sync-tab-bar-to-theme ()
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#e6db74">&amp;#34;Synchronize tab-bar faces with the current theme.&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 ((default-bg (face-background &lt;span style="color:#e6db74">&amp;#39;default&lt;/span>))
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> (default-fg (face-foreground &lt;span style="color:#e6db74">&amp;#39;default&lt;/span>))
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> (inactive-fg (face-foreground &lt;span style="color:#e6db74">&amp;#39;mode-line-inactive&lt;/span>))) &lt;span style="color:#75715e">;; Fallback to mode-line-inactive&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> (custom-set-faces
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#f92672">`&lt;/span>(tab-bar ((&lt;span style="color:#66d9ef">t&lt;/span> (:inherit default :background &lt;span style="color:#f92672">,&lt;/span>default-bg :foreground &lt;span style="color:#f92672">,&lt;/span>default-fg))))
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#f92672">`&lt;/span>(tab-bar-tab ((&lt;span style="color:#66d9ef">t&lt;/span> (:inherit default :background &lt;span style="color:#f92672">,&lt;/span>default-fg :foreground &lt;span style="color:#f92672">,&lt;/span>default-bg))))
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#f92672">`&lt;/span>(tab-bar-tab-inactive ((&lt;span style="color:#66d9ef">t&lt;/span> (:inherit default :background &lt;span style="color:#f92672">,&lt;/span>default-bg :foreground &lt;span style="color:#f92672">,&lt;/span>inactive-fg)))))))
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>My tab theme fix is now activated on every theme load, and I no longer have to rely on muscle memory to make Emacs look colour-coherent when changing themes, which I do a lot!! 😀&lt;/p></description></item><item><title>Simple Directory and File Creation in Dired</title><link>https://www.emacs.dyerdwelling.family/emacs/20240922201246-emacs--efficient-directory-and-file-management-with-dired-in-emacs/</link><pubDate>Tue, 24 Sep 2024 20:00:00 +0100</pubDate><author>James Dyer</author><guid>https://www.emacs.dyerdwelling.family/emacs/20240922201246-emacs--efficient-directory-and-file-management-with-dired-in-emacs/</guid><description>&lt;p>Today I&amp;rsquo;ll simply delve into two custom functions that I&amp;rsquo;ve had in my init file for quite some time, &lt;code>my/dired-create-directory&lt;/code> and &lt;code>my/dired-create-empty-file&lt;/code>&lt;/p>
&lt;figure>&lt;img src="https://www.emacs.dyerdwelling.family/emacs/20240922201246-emacs--Efficient-Directory-and-File-Management-with-Dired-in-Emacs.jpg" width="100%">
&lt;/figure>
&lt;p>I use them frequently enough that I thought they were worth mentioning and they augment my &lt;code>dired&lt;/code> workflow when mapped to &lt;code>dired-mode-map&lt;/code> keybindings.&lt;/p>
&lt;p>For many years I would create a new file or folder in one of two ways, through the command line, or a GUI file explorer (with either a click in or bound F2 to edit the name), Emacs comes with it&amp;rsquo;s own directory manager so why not augment it with these features?&lt;/p>
&lt;p>What about &lt;code>find-file&lt;/code> I hear you say?, just press C-x C-f. This will prompt for a filename using the current directory of the current buffer and if you type a unique name it will create the file for you. Unfortunately though this will not always work in a simple predictable manner when the minibuffer completion mechanism kicks in.&lt;/p>
&lt;p>&lt;strong>my/dired-create-directory&lt;/strong>&lt;/p>
&lt;p>This custom function simplifies creating a new directory by prompting for a name in the minibuffer, bypassing the default completion mechanism to avoid unwanted name suggestions.&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-emacs-lisp" data-lang="emacs-lisp">&lt;span style="display:flex;">&lt;span>(defun my/dired-create-directory ()
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#e6db74">&amp;#34;Wrapper to dired-create-directory to avoid minibuffer completion.&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 ((search-term
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> (&lt;span style="color:#a6e22e">read-from-minibuffer&lt;/span> &lt;span style="color:#e6db74">&amp;#34;Dir : &amp;#34;&lt;/span>)))
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> (dired-create-directory search-term)))
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;ul>
&lt;li>&lt;strong>Purpose:&lt;/strong> This function is a wrapper around the &lt;code>dired-create-directory&lt;/code> function, designed to avoid the potential confusion of minibuffer completion and directly ask for the directory name.&lt;/li>
&lt;li>&lt;strong>Usage:&lt;/strong> When invoked, it prompts the user to input a directory name and creates it using the &lt;code>dired-create-directory&lt;/code> function.&lt;/li>
&lt;/ul>
&lt;p>&lt;strong>my/dired-create-empty-file&lt;/strong>&lt;/p>
&lt;p>Similarly, this custom function facilitates the creation of an empty file by asking for the filename in the minibuffer without the default completion.&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-emacs-lisp" data-lang="emacs-lisp">&lt;span style="display:flex;">&lt;span>(defun my/dired-create-empty-file ()
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#e6db74">&amp;#34;Wrapper to dired-create-empty-file to avoid minibuffer completion.&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 ((search-term
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> (&lt;span style="color:#a6e22e">read-from-minibuffer&lt;/span> &lt;span style="color:#e6db74">&amp;#34;File : &amp;#34;&lt;/span>)))
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> (dired-create-empty-file search-term)))
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;ul>
&lt;li>&lt;strong>Purpose:&lt;/strong> This function wraps around &lt;code>dired-create-empty-file&lt;/code>, providing a straightforward prompt for creating a new file.&lt;/li>
&lt;li>&lt;strong>Usage:&lt;/strong> When triggered, it asks the user to input a file name and then creates an empty file with that name.&lt;/li>
&lt;/ul>
&lt;p>These functions are bound to &lt;code>dired-mode-map&lt;/code> as follows:&lt;/p>
&lt;ul>
&lt;li>&lt;code>_&lt;/code>: Create an empty file using &lt;code>my/dired-create-empty-file&lt;/code>&lt;/li>
&lt;li>&lt;code>+&lt;/code>: Create a new directory using &lt;code>my/dired-create-directory&lt;/code>&lt;/li>
&lt;/ul>
&lt;p>This gives me a good ergonomic LShift+&amp;lt;Right hand of number row&amp;gt; combination and is therefore not too taxing on the hands and each key command are just next to each-other to emphasize their similar nature.&lt;/p>
&lt;p>In addition the dired mode map keybinding for creating a new directory is bound to + so there is consistency there too.&lt;/p>
&lt;p>I suspect there are probably many other ways to achieve this functionality in Emacs, in this case I am specifically relying on &lt;code>dired&lt;/code> function calls here but there is also the &lt;code>make-directory&lt;/code> function and the &lt;code>shell-command&lt;/code> for example if you want to drop in to using the operating systems tool set.&lt;/p></description></item><item><title>Adding Disk Usage Reporting to Emacs Dired</title><link>https://www.emacs.dyerdwelling.family/emacs/20240918092253-emacs--adding-disk-usage-reporting-to-emacs-dired-mode/</link><pubDate>Wed, 18 Sep 2024 09:30:00 +0100</pubDate><author>James Dyer</author><guid>https://www.emacs.dyerdwelling.family/emacs/20240918092253-emacs--adding-disk-usage-reporting-to-emacs-dired-mode/</guid><description>&lt;p>Just a quick one today. One of the great strengths of Emacs is its extensibility, I have mentioned before in adding the ability to disk report on the size of a directory in &lt;code>dired&lt;/code> but I thought I would quickly revisit the topic, demonstrating the function I currently use.&lt;/p>
&lt;p>This functionality will allow you to run disk usage reports directly within Dired, using the &lt;code>du -hc&lt;/code> command. This will make it easier to figure out how much space directories are taking up.&lt;/p>
&lt;p>The aim of this function is to run the Linux command &lt;code>du -hc&lt;/code> on the directory currently under the cursor in Dired mode. The command &lt;code>du -hc&lt;/code> provides a human-readable summary of disk usage for the specified directory.&lt;/p>
&lt;p>Here&amp;rsquo;s the function that accomplishes this task:&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/dired-du ()
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#e6db74">&amp;#34;Run &amp;#39;du -hc&amp;#39; on the directory under the cursor in Dired.&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 ((current-dir (dired-get-file-for-visit)))
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> (if (&lt;span style="color:#a6e22e">file-directory-p&lt;/span> current-dir)
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> (dired-do-async-shell-command &lt;span style="color:#e6db74">&amp;#34;du -hc&amp;#34;&lt;/span> &lt;span style="color:#66d9ef">nil&lt;/span> (&lt;span style="color:#a6e22e">list&lt;/span> current-dir))
&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;The current point is not a directory.&amp;#34;&lt;/span>))))
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;figure>&lt;img src="https://www.emacs.dyerdwelling.family/emacs/20240918092253-emacs--Adding-Disk-Usage-Reporting-to-Emacs-Dired-Mode.jpg" width="100%">
&lt;/figure>
&lt;p>and that is it!&lt;/p></description></item><item><title>New Package arscript-mode</title><link>https://www.emacs.dyerdwelling.family/emacs/20240906074332-emacs--new-package-arscript-mode/</link><pubDate>Sat, 07 Sep 2024 08:55:00 +0100</pubDate><author>James Dyer</author><guid>https://www.emacs.dyerdwelling.family/emacs/20240906074332-emacs--new-package-arscript-mode/</guid><description>&lt;p>As an avid ArtRage user for almost 10 years I have in the past delved into the subtle art of editing arscript files:&lt;/p>
&lt;figure>&lt;img src="https://www.emacs.dyerdwelling.family/emacs/20240906074332-emacs--New-Package-arscript-mode.jpg" width="100%">
&lt;/figure>
&lt;blockquote>
&lt;p>ArtRage arscript files are specialized script files used by the ArtRage painting software to automate and record various painting actions and effects. These files encapsulate sequences of drawing commands, tool selections, color settings, and other actions that can be replayed to recreate the painting process. The arscript format includes structured elements such as keywords, modifiers, structured tags, and numerical values, allowing for detailed specification of both high-level actions and granular details like brush strokes and layer manipulations. The format aims to facilitate complex artistic workflows by enabling repeatable and shareable painting routines, making it an essential tool for digital artists using ArtRage.&lt;/p>
&lt;/blockquote>
&lt;p>They are designed to me modifiable, so what better way to modify them than with Emacs!&lt;/p>
&lt;p>I created an &lt;code>arscript&lt;/code> major mode with the idea to eventually add text manipulation functions to tweak a painting playback.&lt;/p>
&lt;h2 id="summary">Summary&lt;/h2>
&lt;p>&lt;code>arscript-mode&lt;/code> is an Emacs major mode designed to facilitate the editing of arscript files, providing syntax highlighting and other useful editing features tailored specifically for the arscript file format.&lt;/p>
&lt;h2 id="screenshot">Screenshot&lt;/h2>
&lt;figure>&lt;img src="https://www.emacs.dyerdwelling.family/emacs/arscript-mode-00.jpg" width="100%">
&lt;/figure>
&lt;h2 id="links">Links&lt;/h2>
&lt;p>&lt;a href="https://www.artrage.com">https://www.artrage.com&lt;/a>&lt;/p>
&lt;p>&lt;a href="https://www.artrage.com/manuals/scripts/">https://www.artrage.com/manuals/scripts/&lt;/a>&lt;/p>
&lt;p>&lt;a href="https://www.artrage.com/manuals/scripts/script-editing-tips/artrage-scripting-guide/">https://www.artrage.com/manuals/scripts/script-editing-tips/artrage-scripting-guide/&lt;/a>&lt;/p>
&lt;h2 id="whats-new">Whats New&lt;/h2>
&lt;h3 id="v0-dot-1-dot-0">v0.1.0&lt;/h3>
&lt;p>First version&lt;/p>
&lt;h2 id="features">Features&lt;/h2>
&lt;ul>
&lt;li>Syntax highlighting for arscript-specific keywords, modifiers, and structures.&lt;/li>
&lt;li>Custom indentation logic for arscript code blocks.&lt;/li>
&lt;li>Auto-detection of .arscript files to automatically enable the mode.&lt;/li>
&lt;/ul>
&lt;h2 id="installation">Installation&lt;/h2>
&lt;h3 id="use-package--melpa">use-package (MELPA)&lt;/h3>
&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 arscript-mode)
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;h3 id="use-package--emacs-30-plus--directly-from-github">use-package (emacs 30+) directly from github&lt;/h3>
&lt;p>Put the following into your emacs init file:&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 arscript-mode
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> :vc (:fetcher github :repo &lt;span style="color:#e6db74">&amp;#34;captainflasmr/arscript-mode&amp;#34;&lt;/span>))
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;h3 id="from-source">From source&lt;/h3>
&lt;p>Download the `.el` file and place it in your Emacs `load-path` or in a specific source directory &amp;ldquo;~/source/repos/arscript-mode&amp;rdquo;&lt;/p>
&lt;p>Then either manually load it or add it to your configuration to be loaded at startup.&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>(require &lt;span style="color:#e6db74">&amp;#39;arscript-mode&lt;/span>)
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>OR&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 arscript-mode
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> :load-path &lt;span style="color:#e6db74">&amp;#34;~/source/repos/arscript-mode&amp;#34;&lt;/span>)
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;h2 id="usage">Usage&lt;/h2>
&lt;p>Opening any .arscript file with Emacs should automatically enable &lt;code>arscript-mode&lt;/code>, providing you with syntax highlighting and indentation support for editing arscript files.&lt;/p>
&lt;p>Example arscript file:&lt;/p>
&lt;pre tabindex="0">&lt;code class="language-arscript" data-lang="arscript">//===========================================================================
//===========================================================================
// ArtRage Script File.
//===========================================================================
//===========================================================================
//===========================================================================
// Version Block - Script version and ArtRage version:
//===========================================================================
&amp;lt;Version&amp;gt;
ArtRage Version: ArtRage 3 4
ArtRage Build: 4.5.3
Professional Edition: Yes
Script Version: 1
&amp;lt;/Version&amp;gt;
//===========================================================================
// Header block - Info about the painting/person who generated this script:
//===========================================================================
&amp;lt;Header&amp;gt;
// === Project data
Painting Name: &amp;#34;Willow&amp;#34;
Painting Width: 2456
Painting Height: 2206
Painting DPI: 200
Mask Edge Map Width: 1280
Mask Edge Map Height: 800
// === Author data
Author Name: &amp;#34;&amp;#34;
Script Name: &amp;#34;&amp;#34;
Comment: &amp;#34;&amp;#34;
Script Type: &amp;#34;&amp;#34;
Script Feature Flags: 0x000000034
&amp;lt;/Header&amp;gt;
//===========================================================================
// ArtRage project features. Sets the startup state of the script:
//===========================================================================
&amp;lt;StartupFeatures&amp;gt;
Script Startup Features: {
}// End of Script startup feature binary data.
&amp;lt;/StartupFeatures&amp;gt;
//===========================================================================
// Script data follows:
//===========================================================================
&amp;lt;Events&amp;gt;
Wait: 0.000s EvType: Command CommandID: CID_SetClearCanvas ParamType: flag Value: { true }
Wait: 14.031s EvType: Command CommandID: LoadReferenceImage Idx: 0 Reference Image: {
}// End of reference image binary data.
Wait: 2.694s EvType: Command CommandID: ReferenceImageXForm Idx: 0 Loc: (0.182031, 0.2) Size: (320, 236) Scale: 1 Rot: 0 Off: (0, 0)
Wait: 0.682s EvType: Command CommandID: ReferenceImageXForm Idx: 0 Loc: (0.179688, 0.45) Size: (320, 236) Scale: 1 Rot: 0 Off: (0, 0)
Wait: 2.298s EvType: Command CommandID: ReferenceImageXForm Idx: 0 Loc: (0.180078, 0.45) Size: (803, 600) Scale: 1 Rot: 0 Off: (0, 0)
Wait: 2.678s EvType: Command CommandID: ReferenceImageXForm Idx: 0 Loc: (0.279297, 0.395) Size: (803, 600) Scale: 1 Rot: 0 Off: (0, 0)
Wait: 7.316s EvType: Command CommandID: ReferenceImageXForm Idx: 0 Loc: (0.278906, 0.395) Size: (864, 646) Scale: 1 Rot: 0 Off: (0, 0)
Wait: 12.245s EvType: Command CommandID: ReferenceImageXForm Idx: 0 Loc: (0.278906, 0.395) Size: (139, 100) Scale: 1 Rot: 0 Off: (0, 0)
Wait: 0.000s EvType: Command CommandID: SetForeColour ParamType: Pixel Value: { 0x0FF7386A0 }
Wait: 3.341s EvType: Command CommandID: CID_SetClearCanvas ParamType: flag Value: { true }
Wait: 3.819s EvType: Command CommandID: ReferenceImageXForm Idx: 0 Loc: (0.278906, 0.395) Size: (684, 520) Scale: 1 Rot: 0 Off: (0, 0)
Wait: 8.354s EvType: Command CommandID: ReferenceImageXForm Idx: 0 Loc: (0.278906, 0.395) Size: (161, 119) Scale: 1 Rot: 0 Off: (0, 0)
Wait: 1.659s EvType: Command CommandID: CanvasXForm Scale: 0.0840503 Rot: 0 Off: (537, 307)
Wait: 1.123s EvType: Command CommandID: CanvasXForm Scale: 0.0840503 Rot: 0 Off: (497, 259)
&amp;lt;StrokeEvent&amp;gt;
&amp;lt;StrokeHeader&amp;gt;
&amp;lt;EventPt&amp;gt; Wait: 1.116s Loc: (1054.6, 527.3) Pr: 0.0521997 Ti: 0.489467 Ro: 1.27568 Fw: 1 Bt: 0 Rv: NO Iv: NO &amp;lt;/EventPt&amp;gt;
&amp;lt;Recorded&amp;gt; Yes &amp;lt;/Recorded&amp;gt;
&amp;lt;RandSeed&amp;gt; 0x000000000, 0x000000000 &amp;lt;/RandSeed&amp;gt;
&amp;lt;Smooth&amp;gt; Count: 3
Loc: (1054.6, 527.3) Pr: 0.132196 Ti: 1 Ro: 0 Fw: 1 Bt: 0
Loc: (1086.56, 527.3) Pr: 0.132196 Ti: 1 Ro: 0 Fw: 1 Bt: 0
Loc: (1102.54, 527.3) Pr: 0.132196 Ti: 1 Ro: 0 Fw: 1 Bt: 0
&amp;lt;/Smooth&amp;gt;
&amp;lt;PrevA&amp;gt; Loc: (-679.912, 774.652) Pr: 0.135925 Ti: 1 Ro: 0 Fw: 1 Bt: 0 &amp;lt;/PrevA&amp;gt;
&amp;lt;PrevB&amp;gt; Loc: (-505.365, 600.105) Pr: 0.174106 Ti: 1 Ro: 0 Fw: 1 Bt: 0 &amp;lt;/PrevB&amp;gt;
&amp;lt;OldHd&amp;gt; Loc: (1086.56, 527.3) Pr: 0 Ti: 0.477897 Ro: 1.27511 Fw: 1 Bt: 0 Dr: (-0.919609, -0.392834) Hd: (0.392834, -0.919609) &amp;lt;/OldHd&amp;gt;
&amp;lt;NewHd&amp;gt; Loc: (1054.6, 527.3) Pr: 0 Ti: 0.489467 Ro: 1.27568 Fw: 1 Bt: 0 Dr: (-0.99682, -0.0796825) Hd: (0.0796825, -0.99682) &amp;lt;/NewHd&amp;gt;
&amp;lt;/StrokeHeader&amp;gt;
Wait: 0.000s Loc: (1054.6, 535.29) Pr: 0.0569451 Ti: 0.489467 Ro: 1.27568 Fw: 1 Bt: 0 Rv: NO Iv: NO
Wait: 0.002s Loc: (1054.6, 543.279) Pr: 0.0237271 Ti: 0.489467 Ro: 1.27568 Fw: 1 Bt: 0 Rv: NO Iv: NO
&amp;lt;/StrokeEvent&amp;gt;
&lt;/code>&lt;/pre>&lt;h2 id="customization">Customization&lt;/h2>
&lt;p>Currently, &lt;code>arscript-mode&lt;/code> provides a basic set of features optimized for general usage. Future versions may include customizable options based on user feedback.&lt;/p>
&lt;h2 id="contributing">Contributing&lt;/h2>
&lt;p>Contributions to &lt;code>arscript-mode&lt;/code> are welcome! Whether it&amp;rsquo;s bug reports, feature suggestions, or code contributions, feel free to reach out or submit pull requests on GitHub.&lt;/p>
&lt;h2 id="roadmap">ROADMAP&lt;/h2>
&lt;h3 id="review-syntactical-keyword-highlighting-based-on-contents-of-arscript-pdf-manual">&lt;span class="org-todo todo TODO">TODO&lt;/span> review syntactical keyword highlighting based on contents of arscript pdf manual&lt;/h3>
&lt;h3 id="add-text-transformation-functions-to-affect-arscript-and-hence-artrage-playback">&lt;span class="org-todo todo TODO">TODO&lt;/span> add text transformation functions to affect arscript and hence ArtRage playback&lt;/h3>
&lt;h2 id="alternatives">Alternatives&lt;/h2>
&lt;p>As far as I can tell this is the first Emacs major mode supporting the arscript format.&lt;/p></description></item><item><title>Streamlining eshell with popper, capf-autosuggest, and Enhanced Autocompletion</title><link>https://www.emacs.dyerdwelling.family/emacs/20240827210257-emacs--enhancing-eshell-to-be-more-fishy/</link><pubDate>Fri, 30 Aug 2024 11:20:00 +0100</pubDate><author>James Dyer</author><guid>https://www.emacs.dyerdwelling.family/emacs/20240827210257-emacs--enhancing-eshell-to-be-more-fishy/</guid><description>&lt;p>In this post, I will describe the small enhancements and tweaks I have applied to &lt;code>eshell&lt;/code> to make it feel more like a typical linux terminal using the fish shell and especially the inline autosuggestion feature that fish has out-of-the-box.&lt;/p>
&lt;figure>&lt;img src="https://www.emacs.dyerdwelling.family/emacs/20240827210257-emacs--Enhancing-Eshell-To-Be-More-Fishy/2024-08-30-08-27-30.jpg" width="100%">
&lt;/figure>
&lt;p>For clarity, the autosuggestions in fish enable autocompletion suggestion previews as you type, for example :&lt;/p>
&lt;figure>&lt;img src="https://www.emacs.dyerdwelling.family/emacs/20240827210257-emacs--Enhancing-Eshell-To-Be-More-Fishy/2024-08-30-08-52-32.jpg" width="100%">
&lt;/figure>
&lt;figure>&lt;img src="https://www.emacs.dyerdwelling.family/emacs/20240827210257-emacs--Enhancing-Eshell-To-Be-More-Fishy/2024-08-30-08-52-48.jpg" width="100%">
&lt;/figure>
&lt;figure>&lt;img src="https://www.emacs.dyerdwelling.family/emacs/20240827210257-emacs--Enhancing-Eshell-To-Be-More-Fishy/2024-08-30-08-52-57.jpg" width="100%">
&lt;/figure>
&lt;p>Fish will suggest a single command to the right of the cursor dynamically in grey, its just like the terminal built-in readline C-r/C-s, but inline and for your most recent command based on the text input. As a bonus of course, fish will have readline built in and therefore a familiar Emacs keybinding &lt;code>C-e&lt;/code> will complete the suggested completion for you!&lt;/p>
&lt;p>Over the years, I have progressed through possibly all the terminal shells, from csh and ksh through to bash and zsh, and now I use fish. Similarly to the popularity of more native-based light-weight Emacs plugins, such as vertico, corfu, and cape, the fish shell is lightweight and targets important functions right out of the box.&lt;/p>
&lt;p>For years, I relied on readline shell functionality, with the keybinding C-r bringing up a command history which I could navigate. It is much like vertico, and although efficient, I now prefer the fish method of inline completion autosuggestion preview as I feel I can get to my desired commands faster.&lt;/p>
&lt;p>With eshell, there is the native completion TAB based system but it is clunky, so lets see how we can make it more fishy! 😀 🐠&lt;/p>
&lt;h2 id="inline-fish-like-autosuggestion">Inline Fish-like autosuggestion&lt;/h2>
&lt;figure>&lt;img src="https://www.emacs.dyerdwelling.family/emacs/20240827210257-emacs--Enhancing-Eshell-To-Be-More-Fishy/2024-08-30-07-51-34.jpg" width="100%">
&lt;/figure>
&lt;p>I&amp;rsquo;m going to dive straight in to enabling inline autosuggest preview for &lt;code>eshell&lt;/code> to emulate Fish-like inline completion as described earlier. I use a package called &lt;code>capf-autosuggest&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>(use-package capf-autosuggest
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> :hook
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> (eshell-mode &lt;span style="color:#f92672">.&lt;/span> capf-autosuggest-mode))
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>There are other similar packages out there, but so far I have found this to be the best and seems to work out-of-the-box. It is shell (comint) based, so it won&amp;rsquo;t work anywhere else in Emacs, but that is fine for me as I have plenty of other completion mechanisms to choose from.&lt;/p>
&lt;p>Of course you will need to have an &lt;code>eshell&lt;/code> history to use effectively, but it will also autosuggest commands found on your PATH.&lt;/p>
&lt;p>&lt;em>Note : that as of Emacs 30 there is a new function - &lt;code>completion-preview-mode&lt;/code> which looks as though it does something similar to &lt;code>capf-autosuggest&lt;/code> but for all buffers!&lt;/em>&lt;/p>
&lt;blockquote>
&lt;p>This mode automatically shows and updates the completion preview according to the text around point.&lt;/p>
&lt;/blockquote>
&lt;p>I haven&amp;rsquo;t tested this fully yet but when Emacs 30 comes out I will probably have a closer look.&lt;/p>
&lt;h2 id="creating-custom-eshell-buffers">Creating Custom eshell Buffers&lt;/h2>
&lt;p>There are other &lt;code>eshell&lt;/code> tweaks I make to generally improve my workflow and I will now describe how I create eshell buffers. Below is a function that allows the creation of eshell buffers with defined names, making it easier to manage multiple shell sessions.&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/shell-create (name)
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#e6db74">&amp;#34;Create a custom-named eshell buffer with NAME.&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> (interactive &lt;span style="color:#e6db74">&amp;#34;sName: &amp;#34;&lt;/span>)
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> (eshell &lt;span style="color:#e6db74">&amp;#39;new&lt;/span>)
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> (let ((new-buffer-name (&lt;span style="color:#a6e22e">concat&lt;/span> &lt;span style="color:#e6db74">&amp;#34;*eshell-&amp;#34;&lt;/span> name &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">rename-buffer&lt;/span> new-buffer-name &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>(global-set-key (kbd &lt;span style="color:#e6db74">&amp;#34;M-o s&amp;#34;&lt;/span>) &lt;span style="color:#a6e22e">#&amp;#39;&lt;/span>my/shell-create)
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>The reason for this is that I gain greater control over eshell buffer naming, which makes using tools like Popper (see below) and creating multiple eshells simpler. For example, opening an initial eshell in Emacs is easy, just run &lt;code>eshell&lt;/code>, however, to create another unique eshell, you must remember to pass in a universal argument (which I always forget); otherwise, you will just switch to the existing eshell. So this function will always create a new shell, I can always switch between eshell buffers as normal through Emacs anyway.&lt;/p>
&lt;h2 id="toggling-eshell-with-popper">Toggling eshell with Popper&lt;/h2>
&lt;p>Popper provides a pop-up window manager, making it easy to toggle eshell in and out of view. It has been a recent discovery, but now I can&amp;rsquo;t live without it.&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 popper
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> :init
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> (setq popper-reference-buffers
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#f92672">&amp;#39;&lt;/span>(&lt;span style="color:#e6db74">&amp;#34;\\*eshell.*&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> flymake-diagnostics-buffer-mode
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> help-mode
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> compilation-mode))
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> (popper-mode &lt;span style="color:#ae81ff">1&lt;/span>)
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> (popper-echo-mode &lt;span style="color:#ae81ff">1&lt;/span>)
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> :custom
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> (popper-window-height &lt;span style="color:#ae81ff">15&lt;/span>))
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>(bind-key* (kbd &lt;span style="color:#e6db74">&amp;#34;C-;&amp;#34;&lt;/span>) &lt;span style="color:#a6e22e">#&amp;#39;&lt;/span>popper-toggle)
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>Since I control my eshell naming, I can set up Popper with the requisite regex to pop up eshell when it is opened or created.&lt;/p>
&lt;p>What I like about Popper is that it does what it says: it pops windows in and out. I also like to pop Flymake, Help, and Compilation windows, this keeps Emacs clean and uncluttered, and if, for example, I build something, if I need a terminal, or need some linting diagnostics, I can just quickly pop in and out and switch between each pop.&lt;/p>
&lt;p>As a note, if more than one window has been &amp;ldquo;poppered&amp;rdquo; then switching between each &amp;ldquo;pop&amp;rdquo; is easy through a keybinding M-&amp;lt;number&amp;gt;, which is something I also use all the time.&lt;/p>
&lt;h2 id="general-eshell-configuration">General eshell Configuration&lt;/h2>
&lt;p>Here is my general eshell setup. I don&amp;rsquo;t think there is anything controversial here, but as you can see, I have enabled a hook to my own shell setup function, and yes, I know I could add capf-autosuggest-mode to this, but for clarity in this post, I kept it specifically in the capf-autosuggest section.&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 eshell
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> :config
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> (setq eshell-scroll-to-bottom-on-input &lt;span style="color:#66d9ef">t&lt;/span>)
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> (setq-local tab-always-indent &lt;span style="color:#e6db74">&amp;#39;complete&lt;/span>)
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> (setq eshell-history-size &lt;span style="color:#ae81ff">10000&lt;/span>)
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> (setq eshell-save-history-on-exit &lt;span style="color:#66d9ef">t&lt;/span>) &lt;span style="color:#75715e">;; Enable history saving on exit&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> (setq eshell-hist-ignoredups &lt;span style="color:#66d9ef">t&lt;/span>) &lt;span style="color:#75715e">;; Ignore duplicates&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> :hook
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> (eshell-mode &lt;span style="color:#f92672">.&lt;/span> my/eshell-hook))
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>Yes, I know, the history size is ridiculous! (default is 128) but why not build up a huge collection of my past commands for a more robust autosuggest mechanism?!&lt;/p>
&lt;h2 id="eshell-completion-using-consult">eshell completion using consult&lt;/h2>
&lt;figure>&lt;img src="https://www.emacs.dyerdwelling.family/emacs/20240827210257-emacs--Enhancing-Eshell-To-Be-More-Fishy/2024-08-30-08-28-45.jpg" width="100%">
&lt;/figure>
&lt;p>What about other completion mechanisms?, for example emulating the C-r/C-s readline functionality?&lt;/p>
&lt;p>For a while I used consult to bring up a &lt;code>completing-read&lt;/code> searchable eshell history via &lt;code>consult-history&lt;/code>, and although it does work well I found I wasn&amp;rsquo;t really using it. However, I do still have it enabled:&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>(&lt;span style="color:#a6e22e">define-key&lt;/span> eshell-hist-mode-map (kbd &lt;span style="color:#e6db74">&amp;#34;M-r&amp;#34;&lt;/span>) &lt;span style="color:#a6e22e">#&amp;#39;&lt;/span>consult-history))
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;h2 id="ui-completion-with-corfu-and-cape">UI completion with Corfu and Cape&lt;/h2>
&lt;p>Now for some completion UI setup to augment the capf-autosuggest functionality - we are in Emacs after all!&lt;/p>
&lt;p>In this case I am choosing to use &lt;code>corfu&lt;/code> but &lt;code>company&lt;/code> is another option. The idea here is to bring up a typical &lt;code>corfu&lt;/code> completion popup UI when typing, for example:&lt;/p>
&lt;figure>&lt;img src="https://www.emacs.dyerdwelling.family/emacs/20240827210257-emacs--Enhancing-Eshell-To-Be-More-Fishy/2024-08-30-08-27-30.jpg" width="100%">
&lt;/figure>
&lt;p>Here you can see it working along-side &lt;code>capf-autosuggest&lt;/code>&lt;/p>
&lt;p>Here is the config:&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 cape)
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>(defun my/eshell-hook ()
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#e6db74">&amp;#34;Set up eshell hook for completions.&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> (setq-local completion-styles &lt;span style="color:#f92672">&amp;#39;&lt;/span>(basic partial-completion))
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> (setq-local corfu-auto &lt;span style="color:#66d9ef">t&lt;/span>)
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> (corfu-mode)
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> (setq-local completion-at-point-functions
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> (&lt;span style="color:#a6e22e">list&lt;/span> (cape-capf-super
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#a6e22e">#&amp;#39;&lt;/span>pcomplete-completions-at-point
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#a6e22e">#&amp;#39;&lt;/span>cape-history)))
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> (&lt;span style="color:#a6e22e">define-key&lt;/span> eshell-hist-mode-map (kbd &lt;span style="color:#e6db74">&amp;#34;M-r&amp;#34;&lt;/span>) &lt;span style="color:#a6e22e">#&amp;#39;&lt;/span>consult-history))
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>I decided to have a simple set of completion styles, none of those fancy fuzzy, orderless, flex shenanigans as I can generally string a few characters of the command I want to use to quickly generate a suitable matching command.&lt;/p>
&lt;p>&lt;code>cape&lt;/code> at this point becomes useful as it exposes the function &lt;code>cape-history&lt;/code> to &lt;code>corfu&lt;/code>. Once set up, typing in eshell now brings up the completion popup and you would use as expected. This for me has now replaced &lt;code>consult-history&lt;/code> as it feels more integrated and faster for my workflow while producing the same functionality.&lt;/p>
&lt;p>&lt;em>Note: I have explicitly turned on corfu autocompletion as in general I like to have it turned off. I have learnt that things can get tricky and laggy with autocompletion turned on when using eglot and communicating over an LSP, so I prefer to bind to a key and call the UI on demand. Also I&amp;rsquo;m a little old school in that I don&amp;rsquo;t like a busy work window, I&amp;rsquo;m not used to seeing a dynamic autocompletion appear when I am coding and would rather manually leverage this functionality when required.&lt;/em>&lt;/p>
&lt;h2 id="combining-all-of-it">Combining all of it!&lt;/h2>
&lt;p>The &lt;code>corfu&lt;/code> functionality described above can run nicely along-side &lt;code>capf-autosuggest&lt;/code>. For some, this may seem cluttered and you may want to choose one or the other, but for me, for the moment, this suits my workflow.&lt;/p>
&lt;h2 id="conclusion">Conclusion&lt;/h2>
&lt;p>By integrating &lt;code>popper&lt;/code>, &lt;code>capf-autosuggest&lt;/code>, &lt;code>corfu&lt;/code>, and &lt;code>cape&lt;/code> into my eshell setup, I think I may have finally been able to almost completely fully transition terminal usage to emacs through &lt;code>eshell&lt;/code>.&lt;/p>
&lt;p>In addition, if I am running Emacs on a system that doesn&amp;rsquo;t have the fish shell available, for example windows, or a restricted linux development environment, then eshell can now increase my efficiency on the command line.&lt;/p></description></item><item><title>Syncing Tab Bar To Theme</title><link>https://www.emacs.dyerdwelling.family/emacs/20240817082349-emacs--syncing-tab-bar-to-theme/</link><pubDate>Sat, 24 Aug 2024 11:00:00 +0100</pubDate><author>James Dyer</author><guid>https://www.emacs.dyerdwelling.family/emacs/20240817082349-emacs--syncing-tab-bar-to-theme/</guid><description>&lt;p>Using tabs as part of my workflow has enabled a nice encapsulation and collection of files per tab that I can quickly switch to.&lt;/p>
&lt;p>An issue I consistently run into however is tab theming. I&amp;rsquo;m a constant theme switcher, not just in Emacs but also operating system wide, and I often switch wallpapers.&lt;/p>
&lt;p>Unfortunately, with the advent of tabs in version 27.1, many Emacs themes lack styles for the new tab faces, resulting in a wonky disjointed appearance.&lt;/p>
&lt;p>For example:&lt;/p>
&lt;figure>&lt;img src="https://www.emacs.dyerdwelling.family/emacs/2024-08-24-10-55-54.jpg" width="100%">
&lt;/figure>
&lt;p>Elisp can come to the rescue again of course, this is my attempt at making the tabs fit in a little better with an incompatible theme:&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/sync-tab-bar-to-theme ()
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#e6db74">&amp;#34;Synchronize tab-bar faces with the current theme.&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 ((default-bg (face-background &lt;span style="color:#e6db74">&amp;#39;default&lt;/span>))
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> (default-fg (face-foreground &lt;span style="color:#e6db74">&amp;#39;default&lt;/span>))
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> (inactive-fg (face-foreground &lt;span style="color:#e6db74">&amp;#39;mode-line-inactive&lt;/span>)))
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> (custom-set-faces
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#f92672">`&lt;/span>(tab-bar ((&lt;span style="color:#66d9ef">t&lt;/span> (:inherit default :background &lt;span style="color:#f92672">,&lt;/span>default-bg :foreground &lt;span style="color:#f92672">,&lt;/span>default-fg))))
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#f92672">`&lt;/span>(tab-bar-tab ((&lt;span style="color:#66d9ef">t&lt;/span> (:inherit default :background &lt;span style="color:#f92672">,&lt;/span>default-fg :foreground &lt;span style="color:#f92672">,&lt;/span>default-bg))))
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#f92672">`&lt;/span>(tab-bar-tab-inactive ((&lt;span style="color:#66d9ef">t&lt;/span> (:inherit default :background &lt;span style="color:#f92672">,&lt;/span>default-bg :foreground &lt;span style="color:#f92672">,&lt;/span>inactive-fg)))))))
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>which does its best to fit tab themes to the loaded theme, to give something like:&lt;/p>
&lt;figure>&lt;img src="https://www.emacs.dyerdwelling.family/emacs/2024-08-24-10-56-26.jpg" width="100%">
&lt;/figure>
&lt;p>There is a bit of logic here, but essentially, for the whole tab-bar theming, I just pick up the foreground and background faces defined in the theme and for the selected tab &lt;code>tab-bar-tab&lt;/code>, the default theme foreground switches to ensure the selected tab will always stand out.&lt;/p>
&lt;p>For inactive tabs, I set the foreground to that used in the &lt;code>mode-line-inactive&lt;/code>.&lt;/p>
&lt;p>Now that the &lt;code>defun&lt;/code> is available, how to activate it? My first thought was to add a hook for when a theme is loaded, but there doesn&amp;rsquo;t seem to be such a feature!? I also tried advising around `load-theme`, but that didn’t go well either, it worked for themes that were not loaded, but for those that were, a change in state was not detected, and the tab theme tweak was not applied.&lt;/p>
&lt;p>My current solution is simply to make the function interactive and build up some muscle memory to run it every time I switch themes and the tab theming looks wonky. I guess I could wrap around &lt;code>load-theme&lt;/code> with my own theme loading function, then activate &lt;code>my/sync-tab-bar-to-theme&lt;/code> afterwards, but I would rather keep the default theme selection function and run this function later.&lt;/p>
&lt;p>P.S I also have a little idea to perhaps add this functionality to &lt;a href="https://github.com/captainflasmr/selected-window-accent-mode">selected-window-accent-mode&lt;/a> as this is related to clearly defining a selected element in Emacs.&lt;/p></description></item><item><title>Integrate Pywal Colors into Selected-Window-Accent-Mode for Wallpaper Accent Matching in Emacs</title><link>https://www.emacs.dyerdwelling.family/emacs/20240816203918-emacs--selected-window-accent-mode-added-pywal-accent-based-on-wallpaper/</link><pubDate>Fri, 16 Aug 2024 20:49:00 +0100</pubDate><author>James Dyer</author><guid>https://www.emacs.dyerdwelling.family/emacs/20240816203918-emacs--selected-window-accent-mode-added-pywal-accent-based-on-wallpaper/</guid><description>&lt;p>I have updated my emacs package &lt;a href="https://github.com/captainflasmr/selected-window-accent-mode">&lt;strong>selected-window-accent-mode&lt;/strong>&lt;/a>&lt;/p>
&lt;figure>&lt;img src="https://www.emacs.dyerdwelling.family/emacs/20240816203918-emacs--selected-window-accent-mode-added-pywal-accent-based-on-wallpaper.jpg" width="100%">
&lt;/figure>
&lt;blockquote>
&lt;p>The Selected Window Accent Mode is an Emacs package designed to visually distinguish the currently selected window by applying a unique accent color to its fringes, mode line, header line, and margins.&lt;/p>
&lt;/blockquote>
&lt;h2 id="v0-dot-9-dot-0">v0.9.0 &lt;span class="timestamp-wrapper">&lt;span class="timestamp">&amp;lt;2024-08-10 Sat&amp;gt;&lt;/span>&lt;/span>&lt;/h2>
&lt;p>Added Pywal color integration to selected-window-accent-mode to allow the accent color to be consistent with the wallpaper of the operating system.&lt;/p>
&lt;blockquote>
&lt;p>Pywal is a tool on linux that generates color schemes from images, allowing users to apply these colors to their desktop and application themes for a cohesive and dynamic look.&lt;/p>
&lt;/blockquote>
&lt;p>&lt;strong>Note: will only work on linux.&lt;/strong>&lt;/p>
&lt;ul>
&lt;li>Introduced `selected-window-accent-use-pywal` option to use a Pywal-generated color palette.&lt;/li>
&lt;li>Update logic in `selected-window-accent` function to use Pywal color if enabled.&lt;/li>
&lt;li>Require `json` package for handling Pywal palette.&lt;/li>
&lt;/ul>
&lt;p>Example use:&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 selected-window-accent-use-pywal &lt;span style="color:#66d9ef">t&lt;/span>)
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>When enabled, &lt;code>color1&lt;/code> is selected as the accent color, taken from the file : &lt;code>~/.cache/wal/colors.json&lt;/code> which is a generic color file typically generated by pywal.&lt;/p>
&lt;p>This means that the accent color can be more tied in with the current wallpaper once pywal is connected to the wallpaper change mechanism of your operating system.&lt;/p>
&lt;p>Note that this feature will require an external pywal generation step, triggered on wallpaper change, examples given below.&lt;/p>
&lt;p>See below for an example &lt;code>colors.json&lt;/code> file&lt;/p>
&lt;p>Technically any tool that can write to a json file in a similar format will have a carry through affect on the accent color associated with this package. Here is an example :&lt;/p>
&lt;pre tabindex="0">&lt;code class="language-js-json" data-lang="js-json">{
&amp;#34;wallpaper&amp;#34;: &amp;#34;/home/jdyer/.last_wallpaper.jpg&amp;#34;,
&amp;#34;alpha&amp;#34;: &amp;#34;100&amp;#34;,
&amp;#34;special&amp;#34;: {
&amp;#34;background&amp;#34;: &amp;#34;#0d1a05&amp;#34;,
&amp;#34;foreground&amp;#34;: &amp;#34;#c8cfdb&amp;#34;,
&amp;#34;cursor&amp;#34;: &amp;#34;#c8cfdb&amp;#34;
},
&amp;#34;colors&amp;#34;: {
&amp;#34;color0&amp;#34;: &amp;#34;#0d1a05&amp;#34;,
&amp;#34;color1&amp;#34;: &amp;#34;#8FA944&amp;#34;,
&amp;#34;color2&amp;#34;: &amp;#34;#165E97&amp;#34;,
&amp;#34;color3&amp;#34;: &amp;#34;#5C7391&amp;#34;,
&amp;#34;color4&amp;#34;: &amp;#34;#748AA6&amp;#34;,
&amp;#34;color5&amp;#34;: &amp;#34;#7498C4&amp;#34;,
&amp;#34;color6&amp;#34;: &amp;#34;#8D9BB1&amp;#34;,
&amp;#34;color7&amp;#34;: &amp;#34;#c8cfdb&amp;#34;,
&amp;#34;color8&amp;#34;: &amp;#34;#8c9099&amp;#34;,
&amp;#34;color9&amp;#34;: &amp;#34;#8FA944&amp;#34;,
&amp;#34;color10&amp;#34;: &amp;#34;#165E97&amp;#34;,
&amp;#34;color11&amp;#34;: &amp;#34;#5C7391&amp;#34;,
&amp;#34;color12&amp;#34;: &amp;#34;#748AA6&amp;#34;,
&amp;#34;color13&amp;#34;: &amp;#34;#7498C4&amp;#34;,
&amp;#34;color14&amp;#34;: &amp;#34;#8D9BB1&amp;#34;,
&amp;#34;color15&amp;#34;: &amp;#34;#c8cfdb&amp;#34;
}
}
&lt;/code>&lt;/pre>&lt;p>On linux, install &lt;code>python-pywal&lt;/code> using your package manager of choice.&lt;/p>
&lt;p>At which point you can generate a &lt;code>colors.json&lt;/code> with the following commands:&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-bash" data-lang="bash">&lt;span style="display:flex;">&lt;span>wal -c
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>wal -i ~/.last_wallpaper.jpg -q -n
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;h3 id="wallpaper-change-example">Wallpaper Change Example&lt;/h3>
&lt;p>Changing a wallpaper and hooking &lt;code>pywal&lt;/code> up accordingly, which technically should work for both Wayland and X11.&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-bash" data-lang="bash">&lt;span style="display:flex;">&lt;span>&lt;span style="color:#75715e">#!/bin/bash
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#75715e">&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>wallpaper_path&lt;span style="color:#f92672">=&lt;/span>&lt;span style="color:#e6db74">&amp;#34;&lt;/span>&lt;span style="color:#66d9ef">$(&lt;/span>find ~/wallpaper-dir -type f | shuf -n 1&lt;span style="color:#66d9ef">)&lt;/span>&lt;span style="color:#e6db74">&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>echo $wallpaper_path &amp;gt; ~/.last_wallpaper_path
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>cp -f $wallpaper_path ~/.last_wallpaper.jpg
&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:#66d9ef">if&lt;/span> &lt;span style="color:#f92672">[[&lt;/span> $XDG_SESSION_TYPE &lt;span style="color:#f92672">==&lt;/span> &lt;span style="color:#e6db74">&amp;#34;x11&amp;#34;&lt;/span> &lt;span style="color:#f92672">]]&lt;/span>; &lt;span style="color:#66d9ef">then&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> feh --bg-scale &lt;span style="color:#e6db74">&amp;#34;&lt;/span>$wallpaper_path&lt;span style="color:#e6db74">&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#66d9ef">fi&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:#66d9ef">if&lt;/span> &lt;span style="color:#f92672">[[&lt;/span> $XDG_SESSION_TYPE &lt;span style="color:#f92672">==&lt;/span> &lt;span style="color:#e6db74">&amp;#34;wayland&amp;#34;&lt;/span> &lt;span style="color:#f92672">]]&lt;/span>; &lt;span style="color:#66d9ef">then&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> swww img &lt;span style="color:#e6db74">&amp;#34;&lt;/span>$wallpaper_path&lt;span style="color:#e6db74">&amp;#34;&lt;/span> --transition-step &lt;span style="color:#ae81ff">20&lt;/span> --transition-fps&lt;span style="color:#f92672">=&lt;/span>&lt;span style="color:#ae81ff">20&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#66d9ef">fi&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"># lets run pywal for colour scheme generation&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>wal -c
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>wal -i ~/.last_wallpaper.jpg -q -n
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div></description></item><item><title>Ready Player Mode different Utilities for Video and Audio!</title><link>https://www.emacs.dyerdwelling.family/emacs/20240811115938-emacs--ready-player-mode-different-utilities-for-video-and-audio/</link><pubDate>Sun, 11 Aug 2024 16:00:00 +0100</pubDate><author>James Dyer</author><guid>https://www.emacs.dyerdwelling.family/emacs/20240811115938-emacs--ready-player-mode-different-utilities-for-video-and-audio/</guid><description>&lt;p>Regarding my previous post, you can now disregard my little Frankensteinian elisp effort!:&lt;/p>
&lt;p>&lt;a href="https://www.emacs.dyerdwelling.family/emacs/20240810072449-emacs--ready-player-mode-tweak/">Ready Player Mode with a little elisp Tweak&lt;/a>&lt;/p>
&lt;p>Thanks to a quick turnaround from Author: Alvaro Ramirez &lt;a href="https://xenodium.com">https://xenodium.com&lt;/a> URL: &lt;a href="https://github.com/xenodium/ready-player">https://github.com/xenodium/ready-player&lt;/a> Version: 0.4.1 now directly caters for the ability to use different utilities for video and audio, as thus:&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 ready-player
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> :init
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> (ready-player-mode &lt;span style="color:#ae81ff">1&lt;/span>)
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> :custom
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> (ready-player-thumbnail-max-pixel-height &lt;span style="color:#ae81ff">200&lt;/span>)
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> (ready-player-autoplay &lt;span style="color:#66d9ef">nil&lt;/span>)
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> (ready-player-repeat &lt;span style="color:#66d9ef">t&lt;/span>)
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> (ready-player-shuffle &lt;span style="color:#66d9ef">t&lt;/span>)
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> (ready-player-open-playback-commands
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#f92672">&amp;#39;&lt;/span>((ready-player-is-audio-p &lt;span style="color:#e6db74">&amp;#34;mplayer&amp;#34;&lt;/span>)
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> (ready-player-is-video-p &lt;span style="color:#e6db74">&amp;#34;mpv&amp;#34;&lt;/span>))))
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>Really nice!! 😀&lt;/p>
&lt;figure>&lt;img src="https://www.emacs.dyerdwelling.family/emacs/20240810072449-emacs--Ready-Player-Mode-Tweak.jpg" width="100%">
&lt;/figure></description></item><item><title>Ready Player Mode with a little elisp Tweak</title><link>https://www.emacs.dyerdwelling.family/emacs/20240810072449-emacs--ready-player-mode-tweak/</link><pubDate>Sat, 10 Aug 2024 09:42:00 +0100</pubDate><author>James Dyer</author><guid>https://www.emacs.dyerdwelling.family/emacs/20240810072449-emacs--ready-player-mode-tweak/</guid><description>&lt;p>I&amp;rsquo;m really enjoying &lt;strong>ready-player&lt;/strong> at the moment, so much so I think it might end up replacing &lt;strong>emms&lt;/strong>.&lt;/p>
&lt;figure>&lt;img src="https://www.emacs.dyerdwelling.family/emacs/20240810072449-emacs--Ready-Player-Mode-Tweak.jpg" width="100%">
&lt;/figure>
&lt;p>I listen to music in a very simple, almost old-fashioned way, album by album, and I have my music collection well organized, with each album in a separate directory and in a way, &lt;code>emms&lt;/code> was a little too much.&lt;/p>
&lt;p>&lt;code>ready-player&lt;/code> is well integrated with dired, so opening an audio file, for example, will kickstart the listening process for me. I like to have shuffle on (&lt;code>ready-player-shuffle&lt;/code>), and with &lt;code>ready-player-repeat&lt;/code> if there is more media to play in the current directory then it will enter a continuous play mode.&lt;/p>
&lt;p>Here are my current settings:&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 ready-player
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> :init
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> (ready-player-mode &lt;span style="color:#ae81ff">1&lt;/span>)
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> :custom
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> (ready-player-thumbnail-max-pixel-height &lt;span style="color:#ae81ff">200&lt;/span>)
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> (ready-player-autoplay &lt;span style="color:#66d9ef">nil&lt;/span>)
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> (ready-player-repeat &lt;span style="color:#66d9ef">t&lt;/span>)
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> (ready-player-shuffle &lt;span style="color:#66d9ef">t&lt;/span>)
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> (ready-player-open-playback-commands
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#f92672">&amp;#39;&lt;/span>(
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> (&lt;span style="color:#e6db74">&amp;#34;mpv&amp;#34;&lt;/span> &lt;span style="color:#e6db74">&amp;#34;--audio-display=no&amp;#34;&lt;/span>)
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> (&lt;span style="color:#e6db74">&amp;#34;mplayer&amp;#34;&lt;/span>)
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> (&lt;span style="color:#e6db74">&amp;#34;ffplay&amp;#34;&lt;/span>)
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> (&lt;span style="color:#e6db74">&amp;#34;vlc&amp;#34;&lt;/span>)
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> )))
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>The only little issue I have run into so far (and I encountered something similar with &lt;code>emms&lt;/code>) is to figure out which media player back-end works best with my set-up, I am running SwayWM on Arch.&lt;/p>
&lt;p>For some reason &lt;code>emms&lt;/code> would only run well with &lt;code>vlc&lt;/code> (found by trial and error). With &lt;code>ready-player&lt;/code> the default &lt;code>mpv&lt;/code> worked well in most cases except it wouldn&amp;rsquo;t automatically move on to the next audio track when &lt;code>ready-player-repeat&lt;/code> was set.&lt;/p>
&lt;p>I am happy with &lt;code>ready-player&lt;/code> opening videos through &lt;code>mpv&lt;/code> by default and would only really want to ever use &lt;code>mpv&lt;/code> for video playback.&lt;/p>
&lt;p>So first things first, lets establish which back-end gives me a continuous audio playback.&lt;/p>
&lt;p>With a little trial and error (simply swapping around the back-ends) I found that &lt;code>mplayer&lt;/code> was the one that worked for me, &lt;code>mplayer&lt;/code> can play videos too but my muscle memory is so mpv-centric that I always want to be using mpv for video playback.&lt;/p>
&lt;p>With &lt;code>ready-player&lt;/code> as far as I can tell there is just a single list of potential playback commands which applies to all media types. &lt;code>ready-player&lt;/code> is in its early stages of development so I wouldn&amp;rsquo;t be surprised if at some stage this might get added for more flexibility/customization. But for me it doesn&amp;rsquo;t really matter and in fact gives me the opportunity to flex my elisp know-how.&lt;/p>
&lt;p>Can I write something to overcome this issue?&lt;/p>
&lt;p>Emacs is essentially just an elisp machine anyway, so lets write some elisp to augment the current &lt;code>ready-player&lt;/code> functionality to suit my multiple playback-end needs, here is some elisp:&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 set-ready-player-commands ()
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#e6db74">&amp;#34;Set `ready-player-open-playback-commands` based on file extension.&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> (let ((file-extension (file-name-extension (&lt;span style="color:#a6e22e">buffer-file-name&lt;/span>))))
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> (setq ready-player-open-playback-commands
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> (cond
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> ((&lt;span style="color:#a6e22e">member&lt;/span> file-extension &lt;span style="color:#f92672">&amp;#39;&lt;/span>(&lt;span style="color:#e6db74">&amp;#34;mp4&amp;#34;&lt;/span> &lt;span style="color:#e6db74">&amp;#34;mkv&amp;#34;&lt;/span> &lt;span style="color:#e6db74">&amp;#34;mov&amp;#34;&lt;/span> &lt;span style="color:#e6db74">&amp;#34;avi&amp;#34;&lt;/span>))
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#f92672">&amp;#39;&lt;/span>((&lt;span style="color:#e6db74">&amp;#34;mpv&amp;#34;&lt;/span> &lt;span style="color:#e6db74">&amp;#34;--audio-display=no&amp;#34;&lt;/span>)
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> (&lt;span style="color:#e6db74">&amp;#34;mplayer&amp;#34;&lt;/span>)
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> (&lt;span style="color:#e6db74">&amp;#34;ffplay&amp;#34;&lt;/span>)
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> (&lt;span style="color:#e6db74">&amp;#34;vlc&amp;#34;&lt;/span>)))
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> ((&lt;span style="color:#a6e22e">equal&lt;/span> file-extension &lt;span style="color:#e6db74">&amp;#34;mp3&amp;#34;&lt;/span>)
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#f92672">&amp;#39;&lt;/span>((&lt;span style="color:#e6db74">&amp;#34;mplayer&amp;#34;&lt;/span>)
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> (&lt;span style="color:#e6db74">&amp;#34;mpv&amp;#34;&lt;/span> &lt;span style="color:#e6db74">&amp;#34;--audio-display=no&amp;#34;&lt;/span>)
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> (&lt;span style="color:#e6db74">&amp;#34;ffplay&amp;#34;&lt;/span>)
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> (&lt;span style="color:#e6db74">&amp;#34;vlc&amp;#34;&lt;/span>)))
&lt;/span>&lt;/span>&lt;span style="display:flex;">&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:#f92672">&amp;#39;&lt;/span>((&lt;span style="color:#e6db74">&amp;#34;mpv&amp;#34;&lt;/span> &lt;span style="color:#e6db74">&amp;#34;--audio-display=no&amp;#34;&lt;/span>)
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> (&lt;span style="color:#e6db74">&amp;#34;vlc&amp;#34;&lt;/span>)
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> (&lt;span style="color:#e6db74">&amp;#34;ffplay&amp;#34;&lt;/span>)
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> (&lt;span style="color:#e6db74">&amp;#34;mplayer&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>(add-hook &lt;span style="color:#e6db74">&amp;#39;find-file-hook&lt;/span> &lt;span style="color:#e6db74">&amp;#39;set-ready-player-commands&lt;/span>)
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>Simply, I just added some extra &lt;code>ready-player&lt;/code> setup whenever a buffer is loaded from a file, essentially when I select it from dired.&lt;/p>
&lt;p>I set the playback commands based on the extension, so for a video file it can use &lt;code>mpv&lt;/code>, for an mp3 it prefers &lt;code>mplayer&lt;/code>, with a fallback for any other type of file. I guess I could have been a little more flexible in defining a wider range of audio file, but I generally only listen to mp3.&lt;/p>
&lt;p>With that addressed, &lt;code>ready-player&lt;/code> now seems to give me much less friction than &lt;code>emms&lt;/code>, with &lt;code>emms&lt;/code> I would have to perform roughly the following steps to even begin listening to a music album:&lt;/p>
&lt;ul>
&lt;li>first initialise - add files so emms can load in metadata&lt;/li>
&lt;li>browse to select music album&lt;/li>
&lt;li>push to a playlist&lt;/li>
&lt;li>jump to playlist to perform track functions&lt;/li>
&lt;/ul>
&lt;p>I would have to remember &lt;code>emms&lt;/code> commands, or build up muscle memory, but with &lt;code>ready-player&lt;/code> I just need to navigate to the relevant music folder using &lt;code>dired&lt;/code>, open a file and I&amp;rsquo;m ready to go!, and the front-end is like any standard media player which makes more sense to me.&lt;/p>
&lt;p>If I want to modify playback then I&amp;rsquo;m quite happy to jump to the playback buffer which can be accomplished quickly through vertico by fuzzy finding &amp;ldquo;ready&amp;rdquo;, or by pushing &lt;code>ready-player&lt;/code> to its own tab.&lt;/p>
&lt;p>I think I will continue to play around with this cool little package.&lt;/p></description></item><item><title>Efficient File Searching in Emacs: Leveraging completing-read with Customizable Methods</title><link>https://www.emacs.dyerdwelling.family/emacs/20240804075952-emacs--finding-files-using-completing-read/</link><pubDate>Sun, 04 Aug 2024 09:05:00 +0100</pubDate><author>James Dyer</author><guid>https://www.emacs.dyerdwelling.family/emacs/20240804075952-emacs--finding-files-using-completing-read/</guid><description>&lt;p>I thought I would share a little bit of elisp that I&amp;rsquo;ve been using for a while now that allows me to quickly find a file. It leverages &lt;code>completing-read&lt;/code>, allows the selection of the find method and will search recursively.&lt;/p>
&lt;p>When called you can select which search type you prefer:&lt;/p>
&lt;figure>&lt;img src="https://www.emacs.dyerdwelling.family/emacs/20240804075952-emacs--Finding-Files-Using-Completing-Read.jpg" width="100%">
&lt;/figure>
&lt;p>You can select the built-in &lt;code>find-name-dired&lt;/code> but as you see I can also choose an external find tool such as &lt;code>find&lt;/code>, &lt;code>fd&lt;/code> or &lt;code>rg&lt;/code> and it should be flexible enough to add in any others by expanding &lt;code>find-options&lt;/code> in the function below:&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/find-file ()
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#e6db74">&amp;#34;Find file from current directory in many different ways.&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* ((find-options &lt;span style="color:#f92672">&amp;#39;&lt;/span>((&lt;span style="color:#e6db74">&amp;#34;find -type f -printf \&amp;#34;$PWD/%p\\0\&amp;#34;&amp;#34;&lt;/span> &lt;span style="color:#f92672">.&lt;/span> :string)
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> (&lt;span style="color:#e6db74">&amp;#34;fd --absolute-path --type f -0&amp;#34;&lt;/span> &lt;span style="color:#f92672">.&lt;/span> :string)
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> (&lt;span style="color:#e6db74">&amp;#34;rg --follow --files --null&amp;#34;&lt;/span> &lt;span style="color:#f92672">.&lt;/span> :string)
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> (&lt;span style="color:#e6db74">&amp;#34;find-name-dired&amp;#34;&lt;/span> &lt;span style="color:#f92672">.&lt;/span> :command)))
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> (selection (&lt;span style="color:#a6e22e">completing-read&lt;/span> &lt;span style="color:#e6db74">&amp;#34;Select : &amp;#34;&lt;/span> find-options))
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> (metadata &lt;span style="color:#f92672">&amp;#39;&lt;/span>((category &lt;span style="color:#f92672">.&lt;/span> file)))
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> (file-list)
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> (file))
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> (pcase (alist-get selection find-options &lt;span style="color:#66d9ef">nil&lt;/span> &lt;span style="color:#66d9ef">nil&lt;/span> &lt;span style="color:#a6e22e">#&amp;#39;&lt;/span>string=)
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> (:command
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> (&lt;span style="color:#a6e22e">call-interactively&lt;/span> (&lt;span style="color:#a6e22e">intern&lt;/span> selection)))
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> (:string
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> (setq file-list (split-string (shell-command-to-string selection) &lt;span style="color:#e6db74">&amp;#34;\0&amp;#34;&lt;/span> &lt;span style="color:#66d9ef">t&lt;/span>))
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> (setq file (&lt;span style="color:#a6e22e">completing-read&lt;/span> (&lt;span style="color:#a6e22e">format&lt;/span> &lt;span style="color:#e6db74">&amp;#34;Find file in %s: &amp;#34;&lt;/span> (abbreviate-file-name default-directory))
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> (lambda (str pred action)
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> (if (&lt;span style="color:#a6e22e">eq&lt;/span> action &lt;span style="color:#e6db74">&amp;#39;metadata&lt;/span>)
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#f92672">`&lt;/span>(metadata &lt;span style="color:#f92672">.&lt;/span> &lt;span style="color:#f92672">,&lt;/span>metadata)
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> (complete-with-action action file-list str pred)))
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#66d9ef">nil&lt;/span> &lt;span style="color:#66d9ef">t&lt;/span> &lt;span style="color:#66d9ef">nil&lt;/span> &lt;span style="color:#e6db74">&amp;#39;file-name-history&lt;/span>)))
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> (when file (find-file (&lt;span style="color:#a6e22e">expand-file-name&lt;/span> file))))))
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>To be completely honest, I don&amp;rsquo;t really use Emacs&amp;rsquo; built-in find functions, such as &lt;code>find-file&lt;/code> and &lt;code>find-name-dired&lt;/code>. Here are a few reasons why:&lt;/p>
&lt;ul>
&lt;li>Speed and Efficiency
&lt;ul>
&lt;li>Tools like &lt;code>fd&lt;/code> and &lt;code>rg&lt;/code> are incredibly fast and efficient.&lt;/li>
&lt;li>They allow easy filtering through a simple &lt;code>.ignore&lt;/code> file configuration.&lt;/li>
&lt;/ul>
&lt;/li>
&lt;/ul>
&lt;!--listend-->
&lt;ul>
&lt;li>Enhanced File Search
&lt;ul>
&lt;li>I can leverage &lt;code>completing-read&lt;/code> to process the file list.&lt;/li>
&lt;li>Using &lt;code>vertico&lt;/code>, I gain the advantage of fuzzy completion, making file search more efficient and user-friendly.&lt;/li>
&lt;/ul>
&lt;/li>
&lt;/ul>
&lt;!--listend-->
&lt;ul>
&lt;li>Alternative Methods
&lt;ul>
&lt;li>I seldom need to find a single file using &lt;code>find-file&lt;/code>&lt;/li>
&lt;li>Instead, I rely on bookmarks, &lt;code>dired&lt;/code> and &lt;code>recentf&lt;/code>&lt;/li>
&lt;/ul>
&lt;/li>
&lt;/ul>
&lt;!--listend-->
&lt;ul>
&lt;li>Dired Benefits
&lt;ul>
&lt;li>While &lt;code>find-name-dired&lt;/code> offers the benefits of a &lt;code>dired&lt;/code> buffer, I generally don&amp;rsquo;t need to perform specific actions on files found this way.&lt;/li>
&lt;li>Most of the time, I just need to jump to a file quickly.&lt;/li>
&lt;li>A dired bufer can be generated through &lt;code>embark-export&lt;/code> anyway when going through a file fuzzy search at any point.&lt;/li>
&lt;/ul>
&lt;/li>
&lt;/ul>
&lt;!--listend-->
&lt;ul>
&lt;li>Customization and Flexibility
&lt;ul>
&lt;li>External tools often provide more customization options, which align better with my workflow.&lt;/li>
&lt;li>These tools integrate seamlessly with my Emacs setup, enhancing my overall productivity.&lt;/li>
&lt;/ul>
&lt;/li>
&lt;/ul>
&lt;p>By considering these reasons, I&amp;rsquo;ve found that external tools better fit my workflow compared to the built-in Emacs find functions when wanting to find a file quickly.&lt;/p>
&lt;p>&lt;strong>P.S.&lt;/strong> I still have a fondness for the basic &lt;code>find&lt;/code> command, which I continue to use, particularly on the command line and especially when operating through SSH. However, I still can&amp;rsquo;t remember how to prune directories!, but then who can? 😀&lt;/p></description></item><item><title>Sending Dired Directories to Meld for Directory Comparison</title><link>https://www.emacs.dyerdwelling.family/emacs/20240728141344-emacs--sending-dired-directories-to-meld/</link><pubDate>Sun, 28 Jul 2024 16:43:00 +0100</pubDate><author>James Dyer</author><guid>https://www.emacs.dyerdwelling.family/emacs/20240728141344-emacs--sending-dired-directories-to-meld/</guid><description>&lt;p>A common activity for a Software Engineer is to compare two directories, especially those that might require a level of merging. My preferred tool of choice in this instance is &lt;strong>Meld&lt;/strong>&lt;/p>
&lt;figure>&lt;img src="https://www.emacs.dyerdwelling.family/emacs/20240728141344-emacs--Sending-Dired-Directories-to-Meld.jpg" width="100%">
&lt;/figure>
&lt;blockquote>
&lt;p>Meld is a visual diff and merge tool that is widely used for comparing files, directories, and version-controlled projects.&lt;/p>
&lt;/blockquote>
&lt;p>For &lt;strong>Meld&lt;/strong>, the workflow is generally straightforward: simply open a file explorer alongside and drag and drop the required directories.&lt;/p>
&lt;p>But what about comparing directories seamlessly as part of the Emacs ecosystem I hear you say? 🫢&lt;/p>
&lt;p>Well, I am trying to incorporate &lt;code>ztree-diff&lt;/code> into my workflow, but after a few minutes of grappling with it, I always reach for Meld as I know it well (yes, I know this isn&amp;rsquo;t really the Emacs way, in which an initial investment of blood, sweat and toil eventually reaps dividends). I will persist with &lt;code>ztree-diff&lt;/code>, but for now, to at least get a more seamless directory comparison experience from within Emacs I will have to just fall back on the tried and tested method of invoking an external application, and in this case by sending directories marked in &lt;code>dired&lt;/code> to &lt;strong>Meld&lt;/strong>, here is some elisp:&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>(require &lt;span style="color:#e6db74">&amp;#39;cl-lib&lt;/span>)
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>(defun my/dired-meld-diff-all-dwim ()
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#e6db74">&amp;#34;Compare all marked directories in all visible Dired buffers using Meld.
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#e6db74"> The order of directories respects the order suggested by `dired-dwim-target`.&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 ((files ()))
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> (dolist (window (&lt;span style="color:#a6e22e">window-list&lt;/span>))
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> (with-current-buffer (&lt;span style="color:#a6e22e">window-buffer&lt;/span> window)
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> (when (and (derived-mode-p &lt;span style="color:#e6db74">&amp;#39;dired-mode&lt;/span>)
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> (dired-get-marked-files))
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> (setq files (&lt;span style="color:#a6e22e">append&lt;/span> files (dired-get-marked-files))))))
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> (if (or (&lt;span style="color:#a6e22e">&amp;lt;=&lt;/span> (&lt;span style="color:#a6e22e">length&lt;/span> files) &lt;span style="color:#ae81ff">1&lt;/span>)
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> (not (cl-every &lt;span style="color:#e6db74">&amp;#39;file-directory-p&lt;/span> files)))
&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;Please mark at least two directories.&amp;#34;&lt;/span>)
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> (&lt;span style="color:#a6e22e">apply&lt;/span> &lt;span style="color:#e6db74">&amp;#39;start-process&lt;/span> &lt;span style="color:#e6db74">&amp;#34;meld&amp;#34;&lt;/span> &lt;span style="color:#66d9ef">nil&lt;/span> &lt;span style="color:#e6db74">&amp;#34;meld&amp;#34;&lt;/span> files))))
&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">define-key&lt;/span> dired-mode-map (kbd &lt;span style="color:#e6db74">&amp;#34;C-c m&amp;#34;&lt;/span>) &lt;span style="color:#e6db74">&amp;#39;my/dired-meld-diff-all-dwim&lt;/span>)
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>&lt;em>*edit: &lt;span class="timestamp-wrapper">&lt;span class="timestamp">&amp;lt;2024-07-29 Mon&amp;gt; &lt;/span>&lt;/span> every is now obsolete, replaced with cl-every&lt;/em>&lt;/p>
&lt;p>This method has the advantage of honouring &lt;code>dired-dwim-target&lt;/code> through the &lt;code>(window-list)&lt;/code> function which I am now pretty familiar with as I often use &lt;code>dired-do-copy&lt;/code> and &lt;code>dired-do-rename&lt;/code>&lt;/p>
&lt;p>So how does this work?, well now I can split windows, bring up some dired buffers, mark directories to compare, and then make sure I am in the dired buffer that is to appear on the LHS of the Meld comparison before I then run the command. The destination directory would then be defined according to &lt;code>dired-dwim-target&lt;/code> which I have set to &lt;code>t&lt;/code> which is the next dired visible buffer in a clockwise direction, but can be adjusted as desired.&lt;/p>
&lt;p>With a little experimentation I found out that &lt;code>(dired-get-marked-files)&lt;/code> retrieves the dired item that the cursor is over, therefore each directory doesn&amp;rsquo;t necessary even need to be explicitly marked!, so you could just split with two dired buffers, leave the cursor over the destination directory, switch to the source dired buffer and leave the cursor over the source directory before running the command above.&lt;/p>
&lt;p>More experimentation reveals that even three directories can be sent to Meld for comparison!, just open the dired buffers, hover cursor or mark, make sure they are visible, set up in the clockwise order and then focus the cursor on the source directory.&lt;/p>
&lt;p>Is it a little weird that I&amp;rsquo;m talking about experimentation when this is a function I wrote myself? 😀 A three directory comparison is not something I have ever used but I deliberately left the function open and flexible enough and it doesn&amp;rsquo;t really do any harm&amp;hellip;&lt;/p></description></item><item><title>Selected Window Accent Mode v0.8.0</title><link>https://www.emacs.dyerdwelling.family/emacs/20240718103550-emacs--selected-window-accent-mode-v-0-8-0/</link><pubDate>Fri, 19 Jul 2024 11:15:00 +0100</pubDate><author>James Dyer</author><guid>https://www.emacs.dyerdwelling.family/emacs/20240718103550-emacs--selected-window-accent-mode-v-0-8-0/</guid><description>&lt;h2 id="whats-new">Whats New&lt;/h2>
&lt;p>What&amp;rsquo;s New in Selected Window Accent Mode v0.8.0 - (selected-window-accent-mode) which is my window accent package in MELPA for Emacs, here is a quick summary before I dive into the new features:&lt;/p>
&lt;blockquote>
&lt;p>The Selected Window Accent Mode is an Emacs package designed to visually distinguish the currently selected window by applying a unique accent color to its fringes, mode line, header line, and margins.&lt;/p>
&lt;/blockquote>
&lt;p>&lt;a href="https://github.com/captainflasmr/selected-window-accent-mode">https://github.com/captainflasmr/selected-window-accent-mode&lt;/a>&lt;/p>
&lt;h2 id="transient-map-for-quick-access">Transient Map for Quick Access&lt;/h2>
&lt;p>In the latest release of Selected Window Accent Mode, version 0.8.0, users of Emacs 28.1 and above can enjoy the convenience of a transient map. This new feature allows you to access a transient menu by simply pressing `C-c w`:&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>(global-set-key (kbd &lt;span style="color:#e6db74">&amp;#34;C-c w&amp;#34;&lt;/span>) &lt;span style="color:#e6db74">&amp;#39;selected-window-accent-transient&lt;/span>)
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>Upon invoking this key binding, the following transient menu will be displayed:&lt;/p>
&lt;figure>&lt;img src="https://www.emacs.dyerdwelling.family/emacs/20240718103550-emacs--Selected-Window-Accent-Mode-v-0-8-0.jpg" width="100%">
&lt;/figure>
&lt;h2 id="release-highlights">Release Highlights&lt;/h2>
&lt;h3 id="v0-dot-8-dot-0-2024-07-15">v0.8.0 | 2024-07-15&lt;/h3>
&lt;ul>
&lt;li>&lt;strong>&lt;strong>Transient Menu&lt;/strong>&lt;/strong>: A transient menu has been added for interactive adjustments, simplifying the process of tweaking settings on the fly. Refer to the README for detailed usage instructions.&lt;/li>
&lt;li>&lt;strong>&lt;strong>Emacs Dependency&lt;/strong>&lt;/strong>: The Emacs dependency has been upgraded to version 28.1.&lt;/li>
&lt;li>&lt;strong>&lt;strong>Custom Foreground Accent Color&lt;/strong>&lt;/strong>: A new variable, `selected-window-accent-fg-color`, has been introduced for setting a custom foreground accent color.&lt;/li>
&lt;li>&lt;strong>&lt;strong>Enhanced Customization Options&lt;/strong>&lt;/strong>:
&lt;ul>
&lt;li>`selected-window-accent-foreground-adjust-factor`&lt;/li>
&lt;li>`selected-window-accent&amp;ndash;use-complementary-color`&lt;/li>
&lt;li>`selected-window-accent&amp;ndash;foreground-invert-state`&lt;/li>
&lt;li>`selected-window-accent&amp;ndash;foreground-offset`&lt;/li>
&lt;/ul>
&lt;/li>
&lt;li>&lt;strong>&lt;strong>Foreground Brightness Functions&lt;/strong>&lt;/strong>:
&lt;ul>
&lt;li>`selected-window-accent-flip-foreground-color`&lt;/li>
&lt;li>`selected-window-accent-increment-foreground-color`&lt;/li>
&lt;li>`selected-window-accent-decrement-foreground-color`&lt;/li>
&lt;li>`selected-window-accent-toggle-complementary-color`&lt;/li>
&lt;li>`selected-window-accent-toggle-tab-accent`&lt;/li>
&lt;li>`selected-window-accent-toggle-smart-borders`&lt;/li>
&lt;/ul>
&lt;/li>
&lt;li>&lt;strong>&lt;strong>Configuration Export&lt;/strong>&lt;/strong>: A new function, `selected-window-accent-output-selected-window-accent-settings`, allows you to output the current settings for easy copying and pasting into your Emacs init file.&lt;/li>
&lt;li>&lt;strong>&lt;strong>Miscellaneous&lt;/strong>&lt;/strong>: Various improvements and refactoring have been made to enhance the overall functionality and maintainability.&lt;/li>
&lt;/ul>
&lt;h3 id="v0-dot-7-dot-0-2024-07-09">v0.7.0 | 2024-07-09&lt;/h3>
&lt;ul>
&lt;li>&lt;strong>&lt;strong>Compatibility Fixes&lt;/strong>&lt;/strong>: Addressed issues with other packages and restored modeline height when switching between modes.&lt;/li>
&lt;li>&lt;strong>&lt;strong>Issue Resolutions&lt;/strong>&lt;/strong>:
&lt;ul>
&lt;li>ISSUE #4: Resolved compatibility issues with other packages.&lt;/li>
&lt;li>ISSUE #3: Fixed problems causing the package to break fringes.&lt;/li>
&lt;/ul>
&lt;/li>
&lt;/ul>
&lt;p>With these updates, Selected Window Accent Mode continues to offer increased customization and improved user experience. Stay tuned for more exciting features in the upcoming versions! 😀&lt;/p></description></item><item><title>Emacs Blog 2 Year Anniversary - First Post Revisit - Create Local Offline ELPA MELPA ORG</title><link>https://www.emacs.dyerdwelling.family/emacs/20240713203037-emacs--local-elpa-melpa-org-revisited-2yr-anniversary/</link><pubDate>Mon, 15 Jul 2024 21:00:00 +0100</pubDate><author>James Dyer</author><guid>https://www.emacs.dyerdwelling.family/emacs/20240713203037-emacs--local-elpa-melpa-org-revisited-2yr-anniversary/</guid><description>&lt;p>I&amp;rsquo;ve noticed that my Emacs blog is now almost 2 years old!&lt;/p>
&lt;figure>&lt;img src="https://www.emacs.dyerdwelling.family/emacs/20240713203037-emacs--Local-ELPA-MELPA-ORG-Revisited-2yr-Anniversary.jpg" width="100%">
&lt;/figure>
&lt;p>Is there something absurd about writing about a text editor in that very text editor for such a long period? - I think the answer to this is clearly&amp;hellip;, no!&lt;/p>
&lt;p>My first post was on 2022-07-15 and this will be blog number 97.&lt;/p>
&lt;p>And yes before you ask, how do I know this to be true?, well I found out the Emacs way!&lt;/p>
&lt;p>I recorded its inception date in org-agenda, so I saw this date creep up this week :&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">**&lt;/span> blog anni&lt;span style="font-style:italic"> :james:web:&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&amp;lt;&lt;span style="color:#e6db74">2022-07-15 .+1y&lt;/span>&amp;gt;
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>The date of my first post is in an org drawer in my single Emacs blog org file:&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>:EXPORT_HUGO_LASTMOD: 2022-07-15
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>and the blog number?, well, I org-copy-visible&amp;rsquo;d my org blog file of all the top level org headers, pasted into the &lt;strong>scratch&lt;/strong> buffer, flush-line&amp;rsquo;d out all the TODO entries, then finally entered (display-line-numbers-mode) to get the number of lines (I suspect there might be an easier way to do this 😀)&lt;/p>
&lt;p>I also wrote this post ahead of time and to remind me when to post I created an org schedule entry as follows:&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">**&lt;/span>&lt;span style="color:#960050;background-color:#1e0010"> TODO&lt;/span> post 2 yr emacs blog post
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#75715e">SCHEDULED: &lt;/span>&lt;span style="color:#75715e">&amp;lt;2024-07-15 Mon&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>and appeared in the agenda as :&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>Monday 15 July 2024 W29
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>aab--calendar:Scheduled: TODO post 2 yr emacs blog post
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>and in (cfw:open-org-calendar) as :&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:#f92672">+-------------------------+&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#e6db74">| 15 (3) |&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#e6db74">| TODO post 2 yr emacs |&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#e6db74">| blog anni |&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#e6db74">| |&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#e6db74">| |&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#e6db74">| |&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#f92672">+-------------------------+&lt;/span>
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>Living an Emacs life and especially partially through org means that any and all information can be easily extracted let alone initially logged, the basic text format is so simple and uniform that all the data items above were pretty much extracted using the same mechanism through Emacs.&lt;/p>
&lt;p>Anyway enough of all that, in celebration of being able to maintain some level of blogging discipline for 2 years, I decided to revisit my very first post, which was on how to create an offline version of the major Emacs repositories, namely:&lt;/p>
&lt;ul>
&lt;li>ELPA&lt;/li>
&lt;li>MELPA&lt;/li>
&lt;li>ORG&lt;/li>
&lt;/ul>
&lt;p>As it turns out this post is still quite relevant as from time to time, I need an offline version for an Emacs setup without an internet connection.&lt;/p>
&lt;p>So lets re-post!, just for fun!, including the bash script that I still use:&lt;/p>
&lt;hr>
&lt;blockquote>
&lt;p>Steps to locally download emacs packages for offline installation.&lt;/p>
&lt;/blockquote>
&lt;h2 id="local-elpa-melpa-org">Local ELPA MELPA ORG&lt;/h2>
&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-bash" data-lang="bash">&lt;span style="display:flex;">&lt;span>&lt;span style="color:#75715e">#! /bin/bash
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#75715e">&lt;/span>cd ~
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>mkdir -p emacs-pkgs/melpa
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>mkdir -p emacs-pkgs/elpa
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>echo
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>echo &lt;span style="color:#e6db74">&amp;#34;updating MELPA...&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>echo
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>rsync -avz --delete --progress rsync://melpa.org/packages/ ~/emacs-pkgs/melpa/.
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>echo
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>echo &lt;span style="color:#e6db74">&amp;#34;updating ELPA...&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>echo
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>rsync -avz --delete --progress elpa.gnu.org::elpa/. ~/emacs-pkgs/elpa
&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"># org (currently no rsync support)&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>echo
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>echo &lt;span style="color:#e6db74">&amp;#34;updating ORG...&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>echo
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>cd ~/emacs-pkgs
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>git clone https://git.savannah.gnu.org/git/emacs/org-mode.git
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#75715e"># wget -r -l1 -nc -np https://orgmode.org/elpa&lt;/span>
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>I then copy the emacs-pkgs directory to the offline target machine and change the default package manager archives to point to these packages.&lt;/p>
&lt;p>Modify &lt;strong>.emacs&lt;/strong> in the following manner commenting out the online package communication:&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>&lt;span style="color:#75715e">;; (setq package-archives &amp;#39;((&amp;#34;melpa&amp;#34; . &amp;#34;https://melpa.org/packages/&amp;#34;)&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#75715e">;; (&amp;#34;org&amp;#34; . &amp;#34;https://orgmode.org/elpa/&amp;#34;)&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#75715e">;; (&amp;#34;elpa&amp;#34; . &amp;#34;https://elpa.gnu.org/packages/&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>(setq package-archives &lt;span style="color:#f92672">&amp;#39;&lt;/span>((&lt;span style="color:#e6db74">&amp;#34;melpa&amp;#34;&lt;/span>&lt;span style="color:#f92672">.&lt;/span> &lt;span style="color:#e6db74">&amp;#34;~/emacs-pkgs/melpa&amp;#34;&lt;/span>)
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> (&lt;span style="color:#e6db74">&amp;#34;org&amp;#34;&lt;/span> &lt;span style="color:#f92672">.&lt;/span> &lt;span style="color:#e6db74">&amp;#34;~/emacs-pkgs/elpa&amp;#34;&lt;/span>)
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> (&lt;span style="color:#e6db74">&amp;#34;elpa&amp;#34;&lt;/span> &lt;span style="color:#f92672">.&lt;/span> &lt;span style="color:#e6db74">&amp;#34;~/emacs-pkgs/org-mode/lisp&amp;#34;&lt;/span>)))
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div></description></item><item><title>Creating a Spelling Transient</title><link>https://www.emacs.dyerdwelling.family/emacs/20240712082430-emacs--spelling-powerthesaurus/</link><pubDate>Fri, 12 Jul 2024 08:25:00 +0100</pubDate><author>James Dyer</author><guid>https://www.emacs.dyerdwelling.family/emacs/20240712082430-emacs--spelling-powerthesaurus/</guid><description>&lt;p>I really want to finally get to grips with my spelling / dictionary set-up.&lt;/p>
&lt;p>I&amp;rsquo;m happy with &lt;code>jinx&lt;/code> instead of &lt;code>flyspell&lt;/code>. I like &lt;code>powerthesaurus&lt;/code> and, of course, &lt;code>dictionary-lookup-definition&lt;/code>. It is mainly the keybindings I want to finally settle on. I have been moving them around for a while now but haven&amp;rsquo;t really established something comfortable.&lt;/p>
&lt;p>jjjNow Emacs 29 comes with &lt;code>transient&lt;/code>, which is the keyboard-driven interface used by Magit, I am going to see if I can fit my spelling keybindings into a more menu-driven system. I have tried Hydra in the past but found I wasn&amp;rsquo;t really using it. Now that &lt;code>transient&lt;/code> is built-in and the syntax seems simple, let&amp;rsquo;s give it a go!&lt;/p>
&lt;p>Here are my original keybindings:&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 jinx)
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>(use-package powerthesaurus)
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>(global-set-key (kbd &lt;span style="color:#e6db74">&amp;#34;M-s y&amp;#34;&lt;/span>) &lt;span style="color:#e6db74">&amp;#39;powerthesaurus-lookup-synonyms-dwim&lt;/span>)
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>(global-set-key (kbd &lt;span style="color:#e6db74">&amp;#34;M-s a&amp;#34;&lt;/span>) &lt;span style="color:#e6db74">&amp;#39;powerthesaurus-lookup-antonyms-dwim&lt;/span>)
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>(global-set-key (kbd &lt;span style="color:#e6db74">&amp;#34;M-s x&amp;#34;&lt;/span>) &lt;span style="color:#e6db74">&amp;#39;jinx-mode&lt;/span>)
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>(global-set-key (kbd &lt;span style="color:#e6db74">&amp;#34;M-s c&amp;#34;&lt;/span>) &lt;span style="color:#e6db74">&amp;#39;jinx-correct&lt;/span>)
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>(global-set-key (kbd &lt;span style="color:#e6db74">&amp;#34;M-s d&amp;#34;&lt;/span>) &lt;span style="color:#e6db74">&amp;#39;dictionary-lookup-definition&lt;/span>)
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>and converted into :&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 jinx)
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>(use-package powerthesaurus
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> :init
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> (require &lt;span style="color:#e6db74">&amp;#39;transient&lt;/span>)
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> (transient-define-prefix my/transient-spelling ()
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#e6db74">&amp;#34;Spelling commands&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> [&lt;span style="color:#e6db74">&amp;#34;Spelling&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> [&lt;span style="color:#e6db74">&amp;#34;Lookups&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> (&lt;span style="color:#e6db74">&amp;#34;y&amp;#34;&lt;/span> &lt;span style="color:#e6db74">&amp;#34;Synonyms&amp;#34;&lt;/span> powerthesaurus-lookup-synonyms-dwim)
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> (&lt;span style="color:#e6db74">&amp;#34;a&amp;#34;&lt;/span> &lt;span style="color:#e6db74">&amp;#34;Antonyms&amp;#34;&lt;/span> powerthesaurus-lookup-antonyms-dwim)]
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> [&lt;span style="color:#e6db74">&amp;#34;Spelling Tools&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> (&lt;span style="color:#e6db74">&amp;#34;x&amp;#34;&lt;/span> &lt;span style="color:#e6db74">&amp;#34;Jinx&amp;#34;&lt;/span> jinx-mode)
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> (&lt;span style="color:#e6db74">&amp;#34;c&amp;#34;&lt;/span> &lt;span style="color:#e6db74">&amp;#34;Jinx correct&amp;#34;&lt;/span> jinx-correct)]
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> [&lt;span style="color:#e6db74">&amp;#34;Dictionary&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> (&lt;span style="color:#e6db74">&amp;#34;d&amp;#34;&lt;/span> &lt;span style="color:#e6db74">&amp;#34;Lookup&amp;#34;&lt;/span> dictionary-lookup-definition)]
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> [&lt;span style="color:#e6db74">&amp;#34;Miscellaneous&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> (&lt;span style="color:#e6db74">&amp;#34;q&amp;#34;&lt;/span> &lt;span style="color:#e6db74">&amp;#34;Quit&amp;#34;&lt;/span> transient-quit-one)]])
&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 s&amp;#34;&lt;/span> &lt;span style="color:#f92672">.&lt;/span> my/transient-spelling))
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>and produces the following menu:&lt;/p>
&lt;figure>&lt;img src="https://www.emacs.dyerdwelling.family/emacs/20240712082430-emacs--Spelling-Powerthesaurus.jpg" width="100%">
&lt;/figure>
&lt;p>Well that was pretty simple, lets see how this goes and if I might then think about translating some more of my keybindings to transient menus.&lt;/p></description></item><item><title>WOWEE v0.3.3</title><link>https://www.emacs.dyerdwelling.family/emacs/20240705073835-emacs--wowee-v0.3/</link><pubDate>Fri, 05 Jul 2024 07:58:00 +0100</pubDate><author>James Dyer</author><guid>https://www.emacs.dyerdwelling.family/emacs/20240705073835-emacs--wowee-v0.3/</guid><description>&lt;p>More tinkering and enhancements to some AutoHotKey scripts with WOWEE to allow some emacs keybindings throughout the realm of Windows.&lt;/p>
&lt;figure>&lt;img src="https://www.emacs.dyerdwelling.family/emacs/20240705073835-emacs--WOWEE-v0.3.3.jpg" width="100%">
&lt;/figure>
&lt;hr>
&lt;div class="ox-hugo-toc toc local">
&lt;ul>
&lt;li>&lt;a href="https://www.emacs.dyerdwelling.family/emacs/20240705073835-emacs--wowee-v0.3/#ergonomic-keyboard-layout-options">Ergonomic keyboard layout options&lt;/a>&lt;/li>
&lt;li>&lt;a href="https://www.emacs.dyerdwelling.family/emacs/20240705073835-emacs--wowee-v0.3/#whats-new">Whats New&lt;/a>&lt;/li>
&lt;li>&lt;a href="https://www.emacs.dyerdwelling.family/emacs/20240705073835-emacs--wowee-v0.3/#kanban">kanban&lt;/a>&lt;/li>
&lt;/ul>
&lt;/div>
&lt;!--endtoc-->
&lt;hr>
&lt;blockquote>
&lt;p>WOWEE is a set of AutoHotKey scripts designed to bring Emacs-like commands and keybindings to the Windows operating system. Based on the concept of EWOW (Emacs Way of Operating Windows), WOWEE allows you to use Emacs-style navigation and commands throughout your Windows environment.&lt;/p>
&lt;/blockquote>
&lt;p>I have this on github : &lt;a href="https://github.com/captainflasmr/wowee">https://github.com/captainflasmr/wowee&lt;/a>&lt;/p>
&lt;p>As I have been playing ergo for a little while with Emacs I decided to bring in some of this functionality, for example some common ergo keybindings.&lt;/p>
&lt;h2 id="ergonomic-keyboard-layout-options">Ergonomic keyboard layout options&lt;/h2>
&lt;p>Additional AHK files that can be run in parallel with the main wowee.ahk file if you would prefer some extra ergonomic mappings.&lt;/p>
&lt;p>For example it is common to map the Caps Lock key to the Control key to avoid the dreaded Emacs pinky, which can be performed by running up :&lt;/p>
&lt;pre tabindex="0">&lt;code class="language-nil" data-lang="nil">wowee--caps_to_ctrl.ahk
&lt;/code>&lt;/pre>&lt;p>I prefer to map the right hand Alt key to the Control key by running:&lt;/p>
&lt;pre tabindex="0">&lt;code class="language-nil" data-lang="nil">wowee--ralt_to_ctrl.ahk
&lt;/code>&lt;/pre>&lt;p>Both can also be run if desired!&lt;/p>
&lt;h2 id="whats-new">Whats New&lt;/h2>
&lt;h3 id="version-0-dot-3-dot-3">Version 0.3.3 &lt;span class="timestamp-wrapper">&lt;span class="timestamp">&amp;lt;2024-07-01 Mon&amp;gt;&lt;/span>&lt;/span>&lt;/h3>
&lt;p>A little support for Notepad++:&lt;/p>
&lt;ul>
&lt;li>isearch&lt;/li>
&lt;li>comment line&lt;/li>
&lt;/ul>
&lt;h3 id="version-0-dot-3-dot-2">Version 0.3.2 &lt;span class="timestamp-wrapper">&lt;span class="timestamp">&amp;lt;2024-07-01 Mon&amp;gt;&lt;/span>&lt;/span>&lt;/h3>
&lt;p>Separated individual ergonomic key-mappings as thus:&lt;/p>
&lt;ul>
&lt;li>wowee&amp;ndash;caps_to_ctrl.ahk&lt;/li>
&lt;li>wowee&amp;ndash;ralt_to_ctrl.ahk&lt;/li>
&lt;/ul>
&lt;p>These are extra files that can be run in addition to the main wowee.ahk for some common ergonomic keyboard Emacs mappings.&lt;/p>
&lt;h3 id="version-0-dot-3-dot-1">Version 0.3.1 &lt;span class="timestamp-wrapper">&lt;span class="timestamp">&amp;lt;2024-06-30 Sun&amp;gt;&lt;/span>&lt;/span>&lt;/h3>
&lt;p>This commit has added windows, frames support&lt;/p>
&lt;ul>
&lt;li>kill frame (C-x c)&lt;/li>
&lt;li>split window vertically (C-x 3)&lt;/li>
&lt;li>maximize window (C-x 1)&lt;/li>
&lt;li>next window (C-x o)&lt;/li>
&lt;li>suspend frame (C-z)&lt;/li>
&lt;li>reorganisation of kanban&lt;/li>
&lt;li>reorganisation of commands.ahk to better group&lt;/li>
&lt;/ul>
&lt;h2 id="kanban">kanban&lt;/h2>
&lt;p>This kanban board is keeping track of those original EWOW functions.&lt;/p>
&lt;p>The IGNORE column includes functions that I am definitely not going to implement.&lt;/p>
&lt;p>The EXTRA section are additional commands that I am adding.&lt;/p>
&lt;p>There is a prefix type indicator for each command referring to the specific type:&lt;/p>
&lt;p>(C) - Core Emacs commands
(VS) - Visual Studio specific
(VC) - VSCode specific
(N++) - NotePad++ specific
(E) - Extra&lt;/p>
&lt;p>and any command with an asterisk is an unconventional Emacs keybinding which will probably get fixed sometime in the future!&lt;/p>
&lt;table>
&lt;thead>
&lt;tr>
&lt;th>TODO&lt;/th>
&lt;th>DOING&lt;/th>
&lt;th>DONE&lt;/th>
&lt;th>IGNORE&lt;/th>
&lt;/tr>
&lt;/thead>
&lt;tbody>
&lt;tr>
&lt;td>(C) REGION COMMANDS&lt;/td>
&lt;td>(VS) VISUAL STUDIO&lt;/td>
&lt;td>(C) MOTION COMMANDS&lt;/td>
&lt;td>(C) JUMPING AROUND COMMANDS&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>- (C) mark_word&lt;/td>
&lt;td>- (VS) recenter_line (C-l)&lt;/td>
&lt;td>- (C) forward_char (C-f)&lt;/td>
&lt;td>- (C) scroll_left&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>- (C) yank_pop&lt;/td>
&lt;td>- (VS) indent_line (C-i)&lt;/td>
&lt;td>- (C) backward_char (C-b)&lt;/td>
&lt;td>- (C) scroll_right&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>- (C) kill_region&lt;/td>
&lt;td>(VC) VSCODE&lt;/td>
&lt;td>- (C) forward_word (M-f)&lt;/td>
&lt;td>(C) NEWLINE AND INDENT COMMANDS&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>- (C) delete_backward_char&lt;/td>
&lt;td>- (VC) isearch_backwards (C-r)&lt;/td>
&lt;td>- (C) backward_word (M-b)&lt;/td>
&lt;td>- (C) open_line&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>- (C) kill_word&lt;/td>
&lt;td>- (VC) isearch_forwards (C-s)&lt;/td>
&lt;td>- (C) next_line (C-n)&lt;/td>
&lt;td>(C) EDIT COMMANDS&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>- (C) kill_whole_line&lt;/td>
&lt;td>- (VC) comment_line (C-;)&lt;/td>
&lt;td>- (C) previous_line (C-p)&lt;/td>
&lt;td>- (C) transpose_chars&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>(C) NEWLINE AND INDENT COMMANDS&lt;/td>
&lt;td>- (VC) recenter_line (C-l)&lt;/td>
&lt;td>(C) JUMPING AROUND COMMANDS&lt;/td>
&lt;td>- (C) transpose_words&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>- (C) newline&lt;/td>
&lt;td>(N++) NOTEPAD++&lt;/td>
&lt;td>- (C) scroll_down (C-v)&lt;/td>
&lt;td>- (C) transpose_lines&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>- (C) delete_indentation&lt;/td>
&lt;td>(E) PROGRAM SPECIFIC PRESETS&lt;/td>
&lt;td>- (C) scroll_up (M-v)&lt;/td>
&lt;td>(C) CASE CONVERSION COMMANDS&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>(C) EDIT COMMANDS&lt;/td>
&lt;td>(E) ERGO PRESET SELECTION&lt;/td>
&lt;td>- (C) move_beginning_of_line (C-a)&lt;/td>
&lt;td>- (C) upcase_region&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>- (C) redo&lt;/td>
&lt;td>&lt;/td>
&lt;td>- (C) move_end_of_line (C-e)&lt;/td>
&lt;td>- (C) downcase_region&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>- (C) query_replace&lt;/td>
&lt;td>&lt;/td>
&lt;td>- (C) beginning_of_buffer (M-&amp;lt;)&lt;/td>
&lt;td>- (C) upcase_word&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>- (C) overwrite_mode&lt;/td>
&lt;td>&lt;/td>
&lt;td>- (C) end_of_buffer (M-&amp;gt;)&lt;/td>
&lt;td>- (C) downcase_word&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>- (E) isearch-yank-word-or-char&lt;/td>
&lt;td>&lt;/td>
&lt;td>- (C) goto_line (M-g g)&lt;/td>
&lt;td>- (C) capitalize_word&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>(C) INSERT PAIRS COMMANDS&lt;/td>
&lt;td>&lt;/td>
&lt;td>(C) REGION COMMANDS&lt;/td>
&lt;td>(C) MACRO RECORDING COMMANDS&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>- (C) insert_parentheses&lt;/td>
&lt;td>&lt;/td>
&lt;td>- (E) mark_whole_line (M-s ,)&lt;/td>
&lt;td>(C) ADD IGNORE FRAMES&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>- (C) insert_comment&lt;/td>
&lt;td>&lt;/td>
&lt;td>- (C) mark_whole_buffer (C-x h)&lt;/td>
&lt;td>(C) ADD MOUSE EVENTS&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>- (C) indent_new_comment_line&lt;/td>
&lt;td>&lt;/td>
&lt;td>- (C) kill_ring_save (M-w)&lt;/td>
&lt;td>&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>(C) OTHER COMMANDS&lt;/td>
&lt;td>&lt;/td>
&lt;td>- (C) yank (C-y)&lt;/td>
&lt;td>&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>- (C) shell&lt;/td>
&lt;td>&lt;/td>
&lt;td>- (C) delete_char (C-d)&lt;/td>
&lt;td>&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>- (C) shell_command&lt;/td>
&lt;td>&lt;/td>
&lt;td>- (C) backward_kill_word (M-BKSP)&lt;/td>
&lt;td>&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>- (C) facemenu&lt;/td>
&lt;td>&lt;/td>
&lt;td>- (C) kill_line (C-k)&lt;/td>
&lt;td>&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>- (C) help&lt;/td>
&lt;td>&lt;/td>
&lt;td>(C) NEWLINE AND INDENT COMMANDS&lt;/td>
&lt;td>&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>(C) SYSTEM COMMANDS&lt;/td>
&lt;td>&lt;/td>
&lt;td>- (C) indent_for_tab_command (C-i)&lt;/td>
&lt;td>&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>- (C) ignore&lt;/td>
&lt;td>&lt;/td>
&lt;td>(C) EDIT COMMANDS&lt;/td>
&lt;td>&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>- (C) repeat&lt;/td>
&lt;td>&lt;/td>
&lt;td>- (C) undo_only (C-/)&lt;/td>
&lt;td>&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>(C) UNIVERSAL ARGUMENT COMMANDS&lt;/td>
&lt;td>&lt;/td>
&lt;td>- (C) search_forward (C-s)&lt;/td>
&lt;td>&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>(C) FILES COMMANDS&lt;/td>
&lt;td>&lt;/td>
&lt;td>- (C) search_backward (C-r)&lt;/td>
&lt;td>&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>- (C) write_file&lt;/td>
&lt;td>&lt;/td>
&lt;td>(C) SYSTEM COMMANDS&lt;/td>
&lt;td>&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>- (C) find_file&lt;/td>
&lt;td>&lt;/td>
&lt;td>- (C) set_mark_command (C-SPC)&lt;/td>
&lt;td>&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>- (C) dired&lt;/td>
&lt;td>&lt;/td>
&lt;td>- (C) set_c-x_command (C-x)&lt;/td>
&lt;td>&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>(C) WINDOWS FRAMES COMMANDS&lt;/td>
&lt;td>&lt;/td>
&lt;td>- (C) keyboard_quit (C-g)&lt;/td>
&lt;td>&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>- (C) delete_window&lt;/td>
&lt;td>&lt;/td>
&lt;td>(C) FILES COMMANDS&lt;/td>
&lt;td>&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>- (C) split_window&lt;/td>
&lt;td>&lt;/td>
&lt;td>- (*C) save_buffer (C-x s)&lt;/td>
&lt;td>&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>- (C) previous_window&lt;/td>
&lt;td>&lt;/td>
&lt;td>(C) WINDOWS FRAMES COMMANDS&lt;/td>
&lt;td>&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>(C) ADD HOOKS&lt;/td>
&lt;td>&lt;/td>
&lt;td>- (*C) kill_frame (C-x c)&lt;/td>
&lt;td>&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>(C) ADD GENERIC GOTO LINE&lt;/td>
&lt;td>&lt;/td>
&lt;td>- (C) next_window (C-x o)&lt;/td>
&lt;td>&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>(C) ADD KILL RING&lt;/td>
&lt;td>&lt;/td>
&lt;td>- (C) suspend_frame (C-z)&lt;/td>
&lt;td>&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>(VC) VSCODE&lt;/td>
&lt;td>&lt;/td>
&lt;td>(C) ADD C-X (C-x)&lt;/td>
&lt;td>&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>- (VC) indent_line (C-i)&lt;/td>
&lt;td>&lt;/td>
&lt;td>(VS) VISUAL STUDIO&lt;/td>
&lt;td>&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>&lt;/td>
&lt;td>&lt;/td>
&lt;td>- (VS) isearch_backwards (C-r)&lt;/td>
&lt;td>&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>&lt;/td>
&lt;td>&lt;/td>
&lt;td>- (VS) isearch_forwards (C-s)&lt;/td>
&lt;td>&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>&lt;/td>
&lt;td>&lt;/td>
&lt;td>- (VS) comment_line (C-;)&lt;/td>
&lt;td>&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>&lt;/td>
&lt;td>&lt;/td>
&lt;td>- (VS) split window vert (C-x 3)&lt;/td>
&lt;td>&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>&lt;/td>
&lt;td>&lt;/td>
&lt;td>(N++) NOTEPAD++&lt;/td>
&lt;td>&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>&lt;/td>
&lt;td>&lt;/td>
&lt;td>- (N++) isearch_backwards (C-r)&lt;/td>
&lt;td>&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>&lt;/td>
&lt;td>&lt;/td>
&lt;td>- (N++) isearch_forwards (C-s)&lt;/td>
&lt;td>&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>&lt;/td>
&lt;td>&lt;/td>
&lt;td>- (N++) comment_line (C-;)&lt;/td>
&lt;td>&lt;/td>
&lt;/tr>
&lt;/tbody>
&lt;/table></description></item><item><title>WOWEE v0.3.0</title><link>https://www.emacs.dyerdwelling.family/emacs/20240628101906-emacs--wowee-v0.3/</link><pubDate>Fri, 28 Jun 2024 10:19:00 +0100</pubDate><author>James Dyer</author><guid>https://www.emacs.dyerdwelling.family/emacs/20240628101906-emacs--wowee-v0.3/</guid><description>&lt;p>I have been continuing to tinker with this project over the last week&amp;hellip;&lt;/p>
&lt;figure>&lt;img src="https://www.emacs.dyerdwelling.family/emacs/20240623081827-emacs--WOWEE---Windows-Operating-With-Emacs-Enhancements.jpg" width="100%">
&lt;/figure>
&lt;p>&lt;a href="https://github.com/captainflasmr/wowee">https://github.com/captainflasmr/wowee&lt;/a>&lt;/p>
&lt;h2 id="overview">Overview&lt;/h2>
&lt;p>WOWEE is a set of AutoHotKey scripts designed to bring Emacs-like commands and keybindings to the Windows operating system. Based on the concept of EWOW (Emacs Way of Operating Windows), WOWEE allows you to use Emacs-style navigation and commands throughout your Windows environment.&lt;/p>
&lt;h2 id="whats-new">Whats New&lt;/h2>
&lt;h3 id="version-0-dot-3-dot-0">Version 0.3.0 &lt;span class="timestamp-wrapper">&lt;span class="timestamp">[2024-06-28 Fri]&lt;/span>&lt;/span>&lt;/h3>
&lt;p>This commit has split out ahk files into apps, core and init:&lt;/p>
&lt;ul>
&lt;li>Added comment-line&lt;/li>
&lt;li>Added mark-whole-buffer&lt;/li>
&lt;li>Refined isearch&lt;/li>
&lt;li>Added application specific bindings in keybinds-apps.ahk for Visual Studio and VSCode&lt;/li>
&lt;li>Update README to reflect design changes&lt;/li>
&lt;li>Added beginning_of_buffer and end_of_buffer core mappings&lt;/li>
&lt;li>Added mark_whole_buffer core mapping&lt;/li>
&lt;/ul>
&lt;h3 id="version-0-dot-2-dot-0">Version 0.2.0 &lt;span class="timestamp-wrapper">&lt;span class="timestamp">[2024-06-26 Wed]&lt;/span>&lt;/span>&lt;/h3>
&lt;p>This commit introduces several new functions to enhance the usability and
functionality of the `commands.ahk` script. Notable additions include:&lt;/p>
&lt;ul>
&lt;li>Implementation of `isearch_backward` and `isearch_forward` functions for improved search capabilities, allowing for backwards and forwards in-document searches with initialization and continuation logic.&lt;/li>
&lt;li>Addition of `recenter_line`, `indent_line`, and `backward_kill_word` functions to enrich text editing commands.&lt;/li>
&lt;li>Enhancement of `select_line` and `goto_line` functions for better line selection and navigation.&lt;/li>
&lt;li>The `quit_g` function extends quitting capabilities to also reset the `selecting` and `searching` states.&lt;/li>
&lt;li>Modifications to `next_lines` and `previous_lines` to increase the number of lines navigated with each command from 3 to 6, improving scroll efficiency.&lt;/li>
&lt;li>Update to `kill_ring_save` to include an escape key press, ensuring a more comprehensive cleanup post-operation.&lt;/li>
&lt;li>Improved the README.org kanban to more easily differentiate between command sections and to define those commands I am not implementing (IGNORE) and those additional commands (EXTRA)&lt;/li>
&lt;/ul>
&lt;h3 id="version-0-dot-1-dot-0">Version 0.1.0 &lt;span class="timestamp-wrapper">&lt;span class="timestamp">[2024-06-19 Wed]&lt;/span>&lt;/span>&lt;/h3>
&lt;p>Initial version&lt;/p>
&lt;h2 id="features">Features&lt;/h2>
&lt;ul>
&lt;li>Emacs-like commands and keybindings in Windows&lt;/li>
&lt;li>Based on EWOW – Emacs Way of Operating Windows&lt;/li>
&lt;li>Written in AutoHotKey v2&lt;/li>
&lt;li>Includes various Emacs style navigation commands&lt;/li>
&lt;li>Ergonomic keyboard layout options&lt;/li>
&lt;li>Easily add your own keybindings by modifying AHK source files&lt;/li>
&lt;li>&lt;strong>Bindings bespoke to individual applications&lt;/strong>&lt;/li>
&lt;/ul>
&lt;h3 id="my-current-thoughts">My current thoughts&lt;/h3>
&lt;h4 id="bindings-bespoke-to-individual-applications">Bindings bespoke to individual applications&lt;/h4>
&lt;ul>
&lt;li>
&lt;p>I am playing around with the idea of application specific bindings within the keybinds.ahk file, for example 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-ahk" data-lang="ahk">&lt;span style="display:flex;">&lt;span>&lt;span style="color:#75715e"> ;; -------------
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#75715e"> ;; Visual Studio
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#75715e"> ;; -------------
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#75715e"> ;; Shortcut rebindings:
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#75715e"> ;;
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#75715e"> ;; Control+p Control+s : Text Editor : Incremental Search
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#75715e"> ;; Control+p Control+r : Text Editor : Incremental Search Reverse
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#75715e"> ;; Control+p Control+l : Text Editor : Scroll Recenter
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#75715e"> ;; Control+p Control+f : Text Editor : Format selection&lt;/span>&lt;span style="color:#960050;background-color:#1e0010">
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#960050;background-color:#1e0010">
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#960050;background-color:#1e0010">&lt;/span> #HotIf &lt;span style="color:#a6e22e">WinActive&lt;/span>(&lt;span style="color:#e6db74">&amp;#34;ahk_class VisualStudio&amp;#34;&lt;/span>)&lt;span style="color:#960050;background-color:#1e0010">
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#960050;background-color:#1e0010">&lt;/span> ^r::isearch_backward(&lt;span style="color:#e6db74">&amp;#34;{Shift down}{F3}{Shift up}&amp;#34;&lt;/span> &lt;span style="color:#e6db74">&amp;#34;^p^r&amp;#34;&lt;/span>)&lt;span style="color:#960050;background-color:#1e0010">
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#960050;background-color:#1e0010">&lt;/span> ^s::isearch_forward(&lt;span style="color:#e6db74">&amp;#34;{F3}&amp;#34;&lt;/span> &lt;span style="color:#e6db74">&amp;#34;^p^s&amp;#34;&lt;/span>)&lt;span style="color:#960050;background-color:#1e0010">
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#960050;background-color:#1e0010">&lt;/span> ^l::recenter_line()&lt;span style="color:#960050;background-color:#1e0010">
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#960050;background-color:#1e0010">&lt;/span> ^i::indent_line()&lt;span style="color:#960050;background-color:#1e0010">
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#960050;background-color:#1e0010">&lt;/span> ^+i::indent_line()&lt;span style="color:#960050;background-color:#1e0010">
&lt;/span>&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>These will be placed in a separate ahk file called keybinds-apps.ahk.&lt;/p>
&lt;p>The comments would indicate which commands require re-bindings, or redefining the shortcuts. For example, in Visual Studio, Control+I is an incremental search which in emacs is used for multiple actions including indentation, cycling, e.t.c, so at times rather than relying on either default application keybindings or the general windows keybindings an explicit mapping may be required for full functionality.&lt;/p>
&lt;p>So as part of the application definition, why not add in the re-mappings in a comment!&lt;/p>
&lt;p>Also I might have to create some more generic functions to accept arguments defining application specific commands, as demonstrated above with isearch_forward and isearch_backward.&lt;/p>
&lt;p>As part of this I might have to expand the kanban per application, or maybe separate kanbans on which functions have been implemented.&lt;/p>
&lt;/li>
&lt;/ul>
&lt;h3 id="kanban">kanban&lt;/h3>
&lt;p>This kanban board is keeping track of those original EWOW functions.&lt;/p>
&lt;p>The IGNORE column includes functions that I am definitely not going to implement.&lt;/p>
&lt;p>The EXTRA section are additional commands that I am adding.&lt;/p>
&lt;table>
&lt;thead>
&lt;tr>
&lt;th>TODO&lt;/th>
&lt;th>DOING&lt;/th>
&lt;th>DONE&lt;/th>
&lt;th>IGNORE&lt;/th>
&lt;/tr>
&lt;/thead>
&lt;tbody>
&lt;tr>
&lt;td>CORE REGION COMMANDS&lt;/td>
&lt;td>&amp;mdash;- VISUAL STUDIO &amp;mdash;-&lt;/td>
&lt;td>CORE MOTION COMMANDS&lt;/td>
&lt;td>- scroll_left&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>- mark_word&lt;/td>
&lt;td>- recenter_line&lt;/td>
&lt;td>- forward_char&lt;/td>
&lt;td>- scroll_right&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>- kill_region&lt;/td>
&lt;td>- indent_line&lt;/td>
&lt;td>- backward_char&lt;/td>
&lt;td>- open_line&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>- yank_pop&lt;/td>
&lt;td>&amp;mdash;- VSCODE &amp;mdash;-&lt;/td>
&lt;td>- forward_word&lt;/td>
&lt;td>- transpose_chars&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>- delete_backward_char&lt;/td>
&lt;td>- isearch_backwards&lt;/td>
&lt;td>- backward_word&lt;/td>
&lt;td>- transpose_words&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>- kill_word&lt;/td>
&lt;td>- isearch_forwards&lt;/td>
&lt;td>- next_line&lt;/td>
&lt;td>- transpose_lines&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>- kill_whole_line&lt;/td>
&lt;td>ADD PROGRAM SPECIFIC PRESETS&lt;/td>
&lt;td>- previous_line&lt;/td>
&lt;td>CORE CASE CONVERSION COMMANDS&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>CORE NEWLINE AND INDENT COMMANDS&lt;/td>
&lt;td>&lt;/td>
&lt;td>CORE JUMPING AROUND COMMANDS&lt;/td>
&lt;td>- upcase_region&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>- newline&lt;/td>
&lt;td>&lt;/td>
&lt;td>- scroll_down&lt;/td>
&lt;td>- downcase_region&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>- delete_indentation&lt;/td>
&lt;td>&lt;/td>
&lt;td>- scroll_up&lt;/td>
&lt;td>- upcase_word&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>CORE EDIT COMMANDS&lt;/td>
&lt;td>&lt;/td>
&lt;td>- move_beginning_of_line&lt;/td>
&lt;td>- downcase_word&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>- redo&lt;/td>
&lt;td>&lt;/td>
&lt;td>- move_end_of_line&lt;/td>
&lt;td>- capitalize_word&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>- query_replace&lt;/td>
&lt;td>&lt;/td>
&lt;td>- beginning_of_buffer&lt;/td>
&lt;td>CORE MACRO RECORDING COMMANDS&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>- overwrite_mode&lt;/td>
&lt;td>&lt;/td>
&lt;td>- end_of_buffer&lt;/td>
&lt;td>CORE ADD IGNORE FRAMES&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>CORE INSERT PAIRS COMMANDS&lt;/td>
&lt;td>&lt;/td>
&lt;td>- goto_line&lt;/td>
&lt;td>CORE ADD MOUSE EVENTS&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>- insert_parentheses&lt;/td>
&lt;td>&lt;/td>
&lt;td>- mark_whole_line&lt;/td>
&lt;td>&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>- insert_comment&lt;/td>
&lt;td>&lt;/td>
&lt;td>- mark_whole_buffer&lt;/td>
&lt;td>&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>- indent_new_comment_line&lt;/td>
&lt;td>&lt;/td>
&lt;td>- kill_ring_save&lt;/td>
&lt;td>&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>CORE OTHER COMMANDS&lt;/td>
&lt;td>&lt;/td>
&lt;td>- yank&lt;/td>
&lt;td>&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>- shell&lt;/td>
&lt;td>&lt;/td>
&lt;td>- delete_char&lt;/td>
&lt;td>&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>- shell_command&lt;/td>
&lt;td>&lt;/td>
&lt;td>- backward_kill_word&lt;/td>
&lt;td>&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>- facemenu&lt;/td>
&lt;td>&lt;/td>
&lt;td>- kill_line&lt;/td>
&lt;td>&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>- help&lt;/td>
&lt;td>&lt;/td>
&lt;td>- indent_for_tab_command&lt;/td>
&lt;td>&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>CORE SYSTEM COMMANDS&lt;/td>
&lt;td>&lt;/td>
&lt;td>- undo_only&lt;/td>
&lt;td>&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>- ignore&lt;/td>
&lt;td>&lt;/td>
&lt;td>- search_forward&lt;/td>
&lt;td>&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>- repeat&lt;/td>
&lt;td>&lt;/td>
&lt;td>- search_backward&lt;/td>
&lt;td>&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>CORE DIGIT ARGUMENT COMMANDS&lt;/td>
&lt;td>&lt;/td>
&lt;td>- set_mark_command&lt;/td>
&lt;td>&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>CORE FILES COMMANDS&lt;/td>
&lt;td>&lt;/td>
&lt;td>- set_cx_command&lt;/td>
&lt;td>&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>- write_file&lt;/td>
&lt;td>&lt;/td>
&lt;td>- keyboard_quit&lt;/td>
&lt;td>&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>- find_file&lt;/td>
&lt;td>&lt;/td>
&lt;td>- save_buffer&lt;/td>
&lt;td>&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>- dired&lt;/td>
&lt;td>&lt;/td>
&lt;td>CORE ADD C-X&lt;/td>
&lt;td>&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>CORE WINDOWS FRAMES COMMANDS&lt;/td>
&lt;td>&lt;/td>
&lt;td>- isearch_backwards&lt;/td>
&lt;td>&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>- kill_frame&lt;/td>
&lt;td>&lt;/td>
&lt;td>- isearch_forwards&lt;/td>
&lt;td>&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>- delete_window&lt;/td>
&lt;td>&lt;/td>
&lt;td>- comment_line&lt;/td>
&lt;td>&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>- split_window&lt;/td>
&lt;td>&lt;/td>
&lt;td>- comment_line&lt;/td>
&lt;td>&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>- next_window&lt;/td>
&lt;td>&lt;/td>
&lt;td>&lt;/td>
&lt;td>&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>- previous_window&lt;/td>
&lt;td>&lt;/td>
&lt;td>&lt;/td>
&lt;td>&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>- suspend_frame&lt;/td>
&lt;td>&lt;/td>
&lt;td>&lt;/td>
&lt;td>&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>CORE ADD HOOKS&lt;/td>
&lt;td>&lt;/td>
&lt;td>&lt;/td>
&lt;td>&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>ADD GENERIC GOTO LINE&lt;/td>
&lt;td>&lt;/td>
&lt;td>&lt;/td>
&lt;td>&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>CORE ADD KILL RING&lt;/td>
&lt;td>&lt;/td>
&lt;td>&lt;/td>
&lt;td>&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>- recenter_line&lt;/td>
&lt;td>&lt;/td>
&lt;td>&lt;/td>
&lt;td>&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>- indent_line&lt;/td>
&lt;td>&lt;/td>
&lt;td>&lt;/td>
&lt;td>&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>EXTRA ERGO PRESET SELECTION&lt;/td>
&lt;td>&lt;/td>
&lt;td>&lt;/td>
&lt;td>&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>EXTRA&lt;/td>
&lt;td>&lt;/td>
&lt;td>&lt;/td>
&lt;td>&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>- ctrl_mapping&lt;/td>
&lt;td>&lt;/td>
&lt;td>&lt;/td>
&lt;td>&lt;/td>
&lt;/tr>
&lt;/tbody>
&lt;/table>
&lt;h2 id="usage">Usage&lt;/h2>
&lt;ol>
&lt;li>&lt;strong>&lt;strong>Install AutoHotKey&lt;/strong>&lt;/strong>: Download and install AutoHotKey from &lt;a href="https://www.autohotkey.com/">AutoHotKey&amp;rsquo;s official website&lt;/a>.&lt;/li>
&lt;li>&lt;strong>&lt;strong>Run WOWEE&lt;/strong>&lt;/strong>: Double-click on the `wowee.ahk` script to start WOWEE. Once running, Emacs commands will be available in your Windows environment.&lt;/li>
&lt;li>&lt;strong>&lt;strong>Quit WOWEE&lt;/strong>&lt;/strong>: To quit WOWEE, right-click the AutoHotKey icon in the task tray and select &amp;ldquo;Exit.&amp;rdquo;&lt;/li>
&lt;/ol>
&lt;h2 id="configuration">Configuration&lt;/h2>
&lt;p>WOWEE is composed of several AutoHotKey scripts, each serving a specific purpose to replicate Emacs functionalities:&lt;/p>
&lt;h3 id="wowee-dot-ahk">wowee.ahk&lt;/h3>
&lt;p>The top level script to be run, contains the following scripts along with some potential ergonomic key-mappings which if not desired can be commented out:&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-ahk" data-lang="ahk">&lt;span style="display:flex;">&lt;span>RAlt::Control&lt;span style="color:#960050;background-color:#1e0010">
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#960050;background-color:#1e0010">&lt;/span>CapsLock::Control&lt;span style="color:#960050;background-color:#1e0010">
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#960050;background-color:#1e0010">&lt;/span>#Include fundamental&lt;span style="color:#f92672">.&lt;/span>ahk&lt;span style="color:#960050;background-color:#1e0010">
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#960050;background-color:#1e0010">&lt;/span>#Include commands&lt;span style="color:#f92672">.&lt;/span>ahk&lt;span style="color:#960050;background-color:#1e0010">
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#960050;background-color:#1e0010">&lt;/span>#Include keybinds&lt;span style="color:#f92672">-&lt;/span>apps&lt;span style="color:#f92672">.&lt;/span>ahk&lt;span style="color:#960050;background-color:#1e0010">
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#960050;background-color:#1e0010">&lt;/span>#HotIf &lt;span style="color:#f92672">!&lt;/span>&lt;span style="color:#a6e22e">WinActive&lt;/span>(&lt;span style="color:#e6db74">&amp;#34;ahk_class Emacs&amp;#34;&lt;/span>)&lt;span style="color:#960050;background-color:#1e0010">
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#960050;background-color:#1e0010">&lt;/span>#Include keybinds&lt;span style="color:#f92672">-&lt;/span>core&lt;span style="color:#f92672">.&lt;/span>ahk&lt;span style="color:#960050;background-color:#1e0010">
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#960050;background-color:#1e0010">&lt;/span>#Include keybinds&lt;span style="color:#f92672">-&lt;/span>init&lt;span style="color:#f92672">.&lt;/span>ahk&lt;span style="color:#960050;background-color:#1e0010">
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#960050;background-color:#1e0010">&lt;/span>#HotIf&lt;span style="color:#960050;background-color:#1e0010">
&lt;/span>&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;h3 id="fundamental-dot-ahk">fundamental.ahk&lt;/h3>
&lt;p>This script provides a set of fundamental functions and variables that are used to implement Emacs-like commands and keybindings.&lt;/p>
&lt;h3 id="commands-dot-ahk">commands.ahk&lt;/h3>
&lt;p>This script includes the basic implementation of Emacs commands.&lt;/p>
&lt;h3 id="commands-util-dot-ahk">commands_util.ahk&lt;/h3>
&lt;p>This script contains simple utility functions used by the command scripts.&lt;/p>
&lt;h3 id="keybinds-core-dot-ahk">keybinds-core.ahk&lt;/h3>
&lt;p>This script defines the default core keybindings for Emacs-like commands, which will be a fallback for any windows application.&lt;/p>
&lt;h3 id="keybinds-apps-dot-ahk">keybinds-apps.ahk&lt;/h3>
&lt;p>This script defines the application specific commands, typically commands that have been remapped to accommodate the chosen commands.&lt;/p>
&lt;p>Comments within the ahk file indicate the potential re-mappings.&lt;/p>
&lt;h3 id="keybinds-init-dot-ahk">keybinds-init.ahk&lt;/h3>
&lt;p>Analogous to the init.el file or Emacs init file in which all the users bespoke keybindings will exist. An example of my preferred keybindings has been supplied.&lt;/p></description></item><item><title>WOWEE - Windows Operating With Emacs Enhancements</title><link>https://www.emacs.dyerdwelling.family/emacs/20240623081827-emacs--wowee---windows-operating-with-emacs-enhancements/</link><pubDate>Mon, 24 Jun 2024 21:00:00 +0100</pubDate><author>James Dyer</author><guid>https://www.emacs.dyerdwelling.family/emacs/20240623081827-emacs--wowee---windows-operating-with-emacs-enhancements/</guid><description>&lt;p>My Emacs usage on Windows seems to be more prevalent at the moment, and I&amp;rsquo;m having to drop into Visual Studio for C# development.&lt;/p>
&lt;p>Emacs keybindings are so ingrained into my muscle memory that it would be rather pleasant to leverage this digital advantage when plonking around Windows for all applications.&lt;/p>
&lt;p>In Visual Studio this apparently, like most IDE&amp;rsquo;s, can be accomplished via a plug-in, but I think I would like a more wholistic versatile approach, by for example, having Emacs keybindings applied at a global Windows level.&lt;/p>
&lt;p>So how do I accomplish this?, I prodded the interwebs and firstly came across &lt;strong>XKeymacs&lt;/strong>:&lt;/p>
&lt;blockquote>
&lt;p>XKeymacs provides key bindings like Emacs for applications running
on Microsoft Windows. You can also configure bindings for each
application.&lt;/p>
&lt;/blockquote>
&lt;p>On running the binary it looks very comprehensive and has a nice intuitive GUI. However I would like a project I could get my teeth into and I was struggling to build it in a modern version of Visual Studio, this may be something I&amp;rsquo;ll have another look at in the future. Possibly though my quick abandonment of this idea was more related to an idea I had regarding AutoHotKey (a scripting language which can provide easy keyboard shortcut mapping) and a language that I am already familiar with.&lt;/p>
&lt;p>As it turns out there is already an AutoHotKey project that suits my needs and that is &lt;strong>EWOW &amp;ndash; Emacs Way of Operating Windows&lt;/strong>, it is defined as follows:&lt;/p>
&lt;blockquote>
&lt;ul>
&lt;li>allows Emacs-like commands and keybinds (almost) everywhere in
Windows
&lt;ul>
&lt;li>
&lt;p>keyboard macros&lt;/p>
&lt;/li>
&lt;li>
&lt;p>prefix digit-argument&lt;/p>
&lt;ul>
&lt;li>ex. C-3 C-n -&amp;gt; go 3 lines down&lt;/li>
&lt;/ul>
&lt;/li>
&lt;li>
&lt;p>Emacs-style region selection (i.e. set-mark-command)&lt;/p>
&lt;/li>
&lt;li>
&lt;p>&amp;hellip; etc&lt;/p>
&lt;/li>
&lt;/ul>
&lt;/li>
&lt;/ul>
&lt;/blockquote>
&lt;p>It is essentially a set of AutoHotkey scripts and my AHK familiarity could mean this to be an ideal project to take a closer look at and to have a tinker around.&lt;/p>
&lt;p>This project also seems to be well thought out and comprehensive and even provides bonus functionality that I wouldn&amp;rsquo;t have expected, like hooks and even macros!!&lt;/p>
&lt;p>It is written however in AutoHotkey V1 and although still currently available it is deprecated and v2 is considered now the main supported version. V2 is a significant rewrite of the the AHK syntax and to convert EWOW from v1 to v2 might take some significant effort. I had an initial look to see what was involved and used it as an exercise to start to understand both the magnitude of the porting task and to get familiar generally with EWOW. More than anything though this activity surfaced an idea for a new project aimed at implementing Emacs-like commands on Windows, focusing especially on the commands I find myself wanting to use most frequently.&lt;/p>
&lt;p>My initial idea is to lean heavily on EWOW by taking the basic structure and design principles which could accommodate some future augmentation, such as hooks, macros e.t.c and to declare a form of AHK bankruptcy and start almost from scratch and progress from an AHK V2 base. Initially I will try and add the most common simple commands using EWOW as a guide and then build up over a period of time to something with more bells on it.&lt;/p>
&lt;p>I have created a new project with a nod to the name of EWOW, it is defined below and I will look to gradually introduce more Emacs functionality over the next few months and especially focussing on those commands I keep reaching for when faffing around with Visual Studio.&lt;/p>
&lt;p>I have also added a kanban board (using org-kanban, which I will discuss more in a future post) that lists all the EWOW implemented key functions and indicates which ones have been ported. As I become more familiar with this project, I will start to define its scope more clearly. This will likely include deciding on the functionality I choose not to implement. I would like to focus as much as possible on simplicity, with an ergonomic approach in mind. For example, I&amp;rsquo;m considering options such as mapping Caps and RAlt to Control, providing the option for a Vim-style keybinding, and the ability to create presets through textual modifications of AHK files.&lt;/p>
&lt;hr>
&lt;h2 id="wowee-windows-operating-with-emacs-enhancements">WOWEE - Windows Operating With Emacs Enhancements&lt;/h2>
&lt;figure>&lt;img src="https://www.emacs.dyerdwelling.family/emacs/20240623081827-emacs--WOWEE---Windows-Operating-With-Emacs-Enhancements.jpg" width="100%">
&lt;/figure>
&lt;p>&lt;a href="https://github.com/captainflasmr/wowee">https://github.com/captainflasmr/wowee&lt;/a>&lt;/p>
&lt;p>WOWEE is a set of AutoHotKey scripts designed to bring Emacs-like commands and keybindings to the Windows operating system. Based on the concept of EWOW (Emacs Way of Operating Windows), WOWEE allows you to use Emacs-style navigation and commands throughout your Windows environment.&lt;/p>
&lt;h2 id="features">Features&lt;/h2>
&lt;ul>
&lt;li>Emacs-like commands and keybindings in Windows&lt;/li>
&lt;li>Based on EWOW – Emacs Way of Operating Windows&lt;/li>
&lt;li>Written in AutoHotKey v2&lt;/li>
&lt;li>Includes various Emacs style navigation commands&lt;/li>
&lt;/ul>
&lt;h3 id="kanban">kanban&lt;/h3>
&lt;table>
&lt;thead>
&lt;tr>
&lt;th>TODO&lt;/th>
&lt;th>DOING&lt;/th>
&lt;th>DONE&lt;/th>
&lt;/tr>
&lt;/thead>
&lt;tbody>
&lt;tr>
&lt;td>scroll_left&lt;/td>
&lt;td>jumping around commands&lt;/td>
&lt;td>motion commands&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>scroll_right&lt;/td>
&lt;td>&lt;/td>
&lt;td>forward_char&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>goto_line&lt;/td>
&lt;td>&lt;/td>
&lt;td>backward_char&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>region commands&lt;/td>
&lt;td>&lt;/td>
&lt;td>forward_word&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>mark_word&lt;/td>
&lt;td>&lt;/td>
&lt;td>backward_word&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>mark_whole_line&lt;/td>
&lt;td>&lt;/td>
&lt;td>next_line&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>mark_whole_buffer&lt;/td>
&lt;td>&lt;/td>
&lt;td>previous_line&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>kill_region&lt;/td>
&lt;td>&lt;/td>
&lt;td>scroll_down&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>yank_pop&lt;/td>
&lt;td>&lt;/td>
&lt;td>scroll_up&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>delete_backward_char&lt;/td>
&lt;td>&lt;/td>
&lt;td>move_beginning_of_line&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>kill_word&lt;/td>
&lt;td>&lt;/td>
&lt;td>move_end_of_line&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>backward_kill_word&lt;/td>
&lt;td>&lt;/td>
&lt;td>beginning_of_buffer&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>kill_whole_line&lt;/td>
&lt;td>&lt;/td>
&lt;td>end_of_buffer&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>newline and indent commands&lt;/td>
&lt;td>&lt;/td>
&lt;td>kill_ring_save&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>newline&lt;/td>
&lt;td>&lt;/td>
&lt;td>yank&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>open_line&lt;/td>
&lt;td>&lt;/td>
&lt;td>delete_char&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>indent_for_tab_command&lt;/td>
&lt;td>&lt;/td>
&lt;td>kill_line&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>delete_indentation&lt;/td>
&lt;td>&lt;/td>
&lt;td>undo_only&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>edit commands&lt;/td>
&lt;td>&lt;/td>
&lt;td>set_mark_command&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>redo&lt;/td>
&lt;td>&lt;/td>
&lt;td>set_cx_command&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>transpose_chars&lt;/td>
&lt;td>&lt;/td>
&lt;td>keyboard_quit&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>transpose_words&lt;/td>
&lt;td>&lt;/td>
&lt;td>save_buffer&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>transpose_lines&lt;/td>
&lt;td>&lt;/td>
&lt;td>&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>query_replace&lt;/td>
&lt;td>&lt;/td>
&lt;td>&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>search_forward&lt;/td>
&lt;td>&lt;/td>
&lt;td>&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>overwrite_mode&lt;/td>
&lt;td>&lt;/td>
&lt;td>&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>case conversion commands&lt;/td>
&lt;td>&lt;/td>
&lt;td>&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>upcase_region&lt;/td>
&lt;td>&lt;/td>
&lt;td>&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>downcase_region&lt;/td>
&lt;td>&lt;/td>
&lt;td>&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>upcase_word&lt;/td>
&lt;td>&lt;/td>
&lt;td>&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>downcase_word&lt;/td>
&lt;td>&lt;/td>
&lt;td>&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>capitalize_word&lt;/td>
&lt;td>&lt;/td>
&lt;td>&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>insert pairs commands&lt;/td>
&lt;td>&lt;/td>
&lt;td>&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>insert_parentheses&lt;/td>
&lt;td>&lt;/td>
&lt;td>&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>insert_comment&lt;/td>
&lt;td>&lt;/td>
&lt;td>&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>indent_new_comment_line&lt;/td>
&lt;td>&lt;/td>
&lt;td>&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>other commands&lt;/td>
&lt;td>&lt;/td>
&lt;td>&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>shell&lt;/td>
&lt;td>&lt;/td>
&lt;td>&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>shell_command&lt;/td>
&lt;td>&lt;/td>
&lt;td>&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>facemenu&lt;/td>
&lt;td>&lt;/td>
&lt;td>&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>help&lt;/td>
&lt;td>&lt;/td>
&lt;td>&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>system commands&lt;/td>
&lt;td>&lt;/td>
&lt;td>&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>ignore&lt;/td>
&lt;td>&lt;/td>
&lt;td>&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>repeat&lt;/td>
&lt;td>&lt;/td>
&lt;td>&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>digit argument commands&lt;/td>
&lt;td>&lt;/td>
&lt;td>&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>macro recording commands&lt;/td>
&lt;td>&lt;/td>
&lt;td>&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>files commands&lt;/td>
&lt;td>&lt;/td>
&lt;td>&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>write_file&lt;/td>
&lt;td>&lt;/td>
&lt;td>&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>find_file&lt;/td>
&lt;td>&lt;/td>
&lt;td>&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>dired&lt;/td>
&lt;td>&lt;/td>
&lt;td>&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>windows frames commands&lt;/td>
&lt;td>&lt;/td>
&lt;td>&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>kill_frame&lt;/td>
&lt;td>&lt;/td>
&lt;td>&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>delete_window&lt;/td>
&lt;td>&lt;/td>
&lt;td>&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>split_window&lt;/td>
&lt;td>&lt;/td>
&lt;td>&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>next_window&lt;/td>
&lt;td>&lt;/td>
&lt;td>&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>previous_window&lt;/td>
&lt;td>&lt;/td>
&lt;td>&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>suspend_frame&lt;/td>
&lt;td>&lt;/td>
&lt;td>&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>add hooks&lt;/td>
&lt;td>&lt;/td>
&lt;td>&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>add ignore frames&lt;/td>
&lt;td>&lt;/td>
&lt;td>&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>add goto line&lt;/td>
&lt;td>&lt;/td>
&lt;td>&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>add kill ring&lt;/td>
&lt;td>&lt;/td>
&lt;td>&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>add mouse events&lt;/td>
&lt;td>&lt;/td>
&lt;td>&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>add C-x&lt;/td>
&lt;td>&lt;/td>
&lt;td>&lt;/td>
&lt;/tr>
&lt;/tbody>
&lt;/table>
&lt;h2 id="usage">Usage&lt;/h2>
&lt;ol>
&lt;li>&lt;strong>&lt;strong>Install AutoHotKey&lt;/strong>&lt;/strong>: Download and install AutoHotKey from &lt;a href="https://www.autohotkey.com/">AutoHotKey&amp;rsquo;s official website&lt;/a>.&lt;/li>
&lt;li>&lt;strong>&lt;strong>Run WOWEE&lt;/strong>&lt;/strong>: Double-click on the `wowee.ahk` script to start WOWEE. Once running, Emacs commands will be available in your Windows environment.&lt;/li>
&lt;li>&lt;strong>&lt;strong>Quit WOWEE&lt;/strong>&lt;/strong>: To quit WOWEE, right-click the AutoHotKey icon in the task tray and select &amp;ldquo;Exit.&amp;rdquo;&lt;/li>
&lt;/ol>
&lt;h2 id="configuration">Configuration&lt;/h2>
&lt;p>WOWEE is composed of several AutoHotKey scripts, each serving a specific purpose to replicate Emacs functionalities:&lt;/p>
&lt;h3 id="fundamental-dot-ahk">fundamental.ahk&lt;/h3>
&lt;p>This script provides a set of fundamental functions and variables that are used to implement Emacs-like commands and keybindings.&lt;/p>
&lt;h3 id="commands-dot-ahk">commands.ahk&lt;/h3>
&lt;p>This script includes the basic implementation of Emacs commands.&lt;/p>
&lt;h3 id="commands-util-dot-ahk">commands_util.ahk&lt;/h3>
&lt;p>This script contains simple utility functions used by the command scripts.&lt;/p>
&lt;h3 id="keybinds-dot-ahk">keybinds.ahk&lt;/h3>
&lt;p>This script defines the default keybindings for Emacs-like commands.&lt;/p>
&lt;h2 id="installation-and-setup">Installation and Setup&lt;/h2>
&lt;ol>
&lt;li>&lt;strong>&lt;strong>Download WOWEE&lt;/strong>&lt;/strong>: Download the WOWEE scripts from the repository.&lt;/li>
&lt;li>&lt;strong>&lt;strong>Extract Files&lt;/strong>&lt;/strong>: Extract the files to a directory of your choice.&lt;/li>
&lt;li>&lt;strong>&lt;strong>Run the Script&lt;/strong>&lt;/strong>: Double-click `wowee.ahk` to start using WOWEE.&lt;/li>
&lt;/ol>
&lt;h2 id="customization">Customization&lt;/h2>
&lt;h3 id="editing-keybindings">Editing Keybindings&lt;/h3>
&lt;p>You can customize the keybindings by editing the `keybinds.ahk` file. Open the file in any text editor and modify the keybindings according to your preferences. Refer to the AutoHotKey documentation for the syntax and available key options.&lt;/p>
&lt;h3 id="adding-new-commands">Adding New Commands&lt;/h3>
&lt;p>To add new commands, you can edit the `commands.ahk` and `commands_util.ahk` files. Define your new commands and utility functions, and then bind them to keys in `keybinds.ahk`.&lt;/p>
&lt;h2 id="troubleshooting">Troubleshooting&lt;/h2>
&lt;p>If you encounter any issues while using WOWEE, try the following steps:&lt;/p>
&lt;ol>
&lt;li>&lt;strong>&lt;strong>Check AutoHotKey Version&lt;/strong>&lt;/strong>: Ensure you have the latest version of AutoHotKey installed.&lt;/li>
&lt;li>&lt;strong>&lt;strong>Script Errors&lt;/strong>&lt;/strong>: If there are errors in the script, AutoHotKey will usually display a message with details. Use this information to debug and fix the issue.&lt;/li>
&lt;li>&lt;strong>&lt;strong>Conflicting Programs&lt;/strong>&lt;/strong>: Some programs might have conflicting keybindings. Try closing other programs to see if the issue is resolved.&lt;/li>
&lt;/ol>
&lt;h2 id="contributing">Contributing&lt;/h2>
&lt;p>Contributions to WOWEE are welcome! If you have suggestions for improvements or want to add new features, feel free to submit a pull request.&lt;/p>
&lt;ol>
&lt;li>Fork the repository&lt;/li>
&lt;li>Create your feature branch (`git checkout -b feature/YourFeature`)&lt;/li>
&lt;li>Commit your changes (`git commit -am &amp;lsquo;Add your feature&amp;rsquo;`)&lt;/li>
&lt;li>Push to the branch (`git push origin feature/YourFeature`)&lt;/li>
&lt;li>Create a new pull request&lt;/li>
&lt;/ol>
&lt;h2 id="license">License&lt;/h2>
&lt;p>WOWEE is licensed under the MIT License. See the LICENSE file for more details.&lt;/p>
&lt;h2 id="acknowledgements">Acknowledgements&lt;/h2>
&lt;p>Special thanks to the creator of EWOW, from whom I have derived significant inspiration: &lt;a href="https://github.com/zk-phi/ewow">https://github.com/zk-phi/ewow&lt;/a>&lt;/p>
&lt;p>Special thanks to the creators of AutoHotKey and the Emacs community for their inspiration and contributions to keyboard efficiency.&lt;/p>
&lt;h2 id="contact">Contact&lt;/h2>
&lt;p>For any questions or issues, please open an issue on the GitHub repository or contact the maintainer at &lt;a href="mailto:captainflasmr@gmail.com">captainflasmr@gmail.com&lt;/a>&lt;/p>
&lt;hr>
&lt;p>Enjoy using WOWEE and bring the power of Emacs navigation to your Windows experience!&lt;/p></description></item><item><title>Using A Mechanical Keyboard, Literally On A Laptop!</title><link>https://www.emacs.dyerdwelling.family/emacs/20240414121636-emacs--using-a-mechanical-keyboard-on-a-laptop/</link><pubDate>Sat, 08 Jun 2024 16:55:00 +0100</pubDate><author>James Dyer</author><guid>https://www.emacs.dyerdwelling.family/emacs/20240414121636-emacs--using-a-mechanical-keyboard-on-a-laptop/</guid><description>&lt;p>The next stage on my quest to mitigate any RSI issues when using Emacs on a laptop is to see if it is a viable option to use a mechanical keyboard on a laptop.&lt;/p>
&lt;p>When I mean &amp;ldquo;on a laptop&amp;rdquo; I literally mean &lt;strong>ON&lt;/strong> the laptop!!&lt;/p>
&lt;p>My idea is to buy a smallish portable mechanical keyboard and just plonk it (technical term) over my current laptop keyboard.&lt;/p>
&lt;p>At the moment I know nothing about mechanical keyboards, I just know from an RSI perspective they are generally a good idea. I do miss the feeling of key travel and I am aware at times I&amp;rsquo;m tapping too hard on my laptop keyboard due to their shallow depth.&lt;/p>
&lt;p>So here is the keyboard in its fully installed glory - extreme plonkification!&lt;/p>
&lt;figure>&lt;img src="https://www.emacs.dyerdwelling.family/emacs/20240414121636-emacs--Using-A-Mechanical-Keyboard-On-A-Laptop.jpg" width="100%">
&lt;/figure>
&lt;p>I however encountered notable difficulties that depended largely on the placement of the keyboard&amp;rsquo;s feet in relation to the laptop&amp;rsquo;s built-in keyboard. Spamming of a single key press was a common issue and was due to my built-in laptop still being enabled when the mech-keyboard was plugged in.&lt;/p>
&lt;p>Simply solved however with a bash script to disable the laptop keyboard when the mech is plugged in.&lt;/p>
&lt;p>Note: this will be SwayWM specific but can easily be adapted by replacing the swaymsg command.&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-bash" data-lang="bash">&lt;span style="display:flex;">&lt;span>KEYBOARD_CONNECTED&lt;span style="color:#f92672">=&lt;/span>&lt;span style="color:#ae81ff">0&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:#66d9ef">while&lt;/span> :
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#66d9ef">do&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#66d9ef">if&lt;/span> &lt;span style="color:#f92672">[[&lt;/span> -L &lt;span style="color:#e6db74">&amp;#34;/dev/input/by-id/usb-SEMICO_USB_Gaming_Keyboard-event-kbd&amp;#34;&lt;/span> &lt;span style="color:#f92672">]]&lt;/span>; &lt;span style="color:#66d9ef">then&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#66d9ef">if&lt;/span> &lt;span style="color:#f92672">[[&lt;/span> $KEYBOARD_CONNECTED &lt;span style="color:#f92672">==&lt;/span> &lt;span style="color:#ae81ff">0&lt;/span> &lt;span style="color:#f92672">]]&lt;/span>; &lt;span style="color:#66d9ef">then&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> KEYBOARD_CONNECTED&lt;span style="color:#f92672">=&lt;/span>&lt;span style="color:#ae81ff">1&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> notify-send -t &lt;span style="color:#ae81ff">3000&lt;/span> &lt;span style="color:#e6db74">&amp;#34;KEYBOARD CONNECTED!&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> swaymsg input 1:1:AT_Translated_Set_2_keyboard events disabled
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#66d9ef">fi&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#66d9ef">else&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#66d9ef">if&lt;/span> &lt;span style="color:#f92672">[[&lt;/span> $KEYBOARD_CONNECTED &lt;span style="color:#f92672">==&lt;/span> &lt;span style="color:#ae81ff">1&lt;/span> &lt;span style="color:#f92672">]]&lt;/span>; &lt;span style="color:#66d9ef">then&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> KEYBOARD_CONNECTED&lt;span style="color:#f92672">=&lt;/span>&lt;span style="color:#ae81ff">0&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> notify-send -t &lt;span style="color:#ae81ff">3000&lt;/span> &lt;span style="color:#e6db74">&amp;#34;KEYBOARD DISCONNECTED!&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> swaymsg input 1:1:AT_Translated_Set_2_keyboard events enabled
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#66d9ef">fi&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#66d9ef">fi&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> sleep &lt;span style="color:#ae81ff">2&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#66d9ef">done&lt;/span>
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>Now I can use Emacs on a laptop without the drawbacks of using a shallow laptop keyboard.&lt;/p>
&lt;p>The next ergo improvement might now be trying to find a better resting hand position as the new mech-keyboard is raised by an inch or so, but that will be for another ergpost&amp;hellip;&lt;/p></description></item><item><title>New Package, xkb-mode, to edit X Keyboard Extension Files</title><link>https://www.emacs.dyerdwelling.family/emacs/20240524160249-emacs--new-package-xkb-mode/</link><pubDate>Fri, 24 May 2024 16:12:00 +0100</pubDate><author>James Dyer</author><guid>https://www.emacs.dyerdwelling.family/emacs/20240524160249-emacs--new-package-xkb-mode/</guid><description>&lt;p>Based on my recent forays into ergonomic key-mapping in Emacs for SwayWM, and effectively for any Wayland compositor or X11-based system using the X Keyboard Extension (XKB) standard, I realized that there doesn&amp;rsquo;t seem to be an Emacs mode to edit these keyboard configuration files (*.xkb). This is surprising, considering they have been around for more than 30 years!&lt;/p>
&lt;p>So I wrote one, and its on MELPA.&lt;/p>
&lt;hr>
&lt;div class="ox-hugo-toc toc local">
&lt;ul>
&lt;li>&lt;a href="https://www.emacs.dyerdwelling.family/emacs/20240524160249-emacs--new-package-xkb-mode/#summary">Summary&lt;/a>&lt;/li>
&lt;li>&lt;a href="https://www.emacs.dyerdwelling.family/emacs/20240524160249-emacs--new-package-xkb-mode/#features">Features&lt;/a>&lt;/li>
&lt;li>&lt;a href="https://www.emacs.dyerdwelling.family/emacs/20240524160249-emacs--new-package-xkb-mode/#installation">Installation&lt;/a>&lt;/li>
&lt;li>&lt;a href="https://www.emacs.dyerdwelling.family/emacs/20240524160249-emacs--new-package-xkb-mode/#usage">Usage&lt;/a>&lt;/li>
&lt;li>&lt;a href="https://www.emacs.dyerdwelling.family/emacs/20240524160249-emacs--new-package-xkb-mode/#customization">Customization&lt;/a>&lt;/li>
&lt;li>&lt;a href="https://www.emacs.dyerdwelling.family/emacs/20240524160249-emacs--new-package-xkb-mode/#contributing">Contributing&lt;/a>&lt;/li>
&lt;li>&lt;a href="https://www.emacs.dyerdwelling.family/emacs/20240524160249-emacs--new-package-xkb-mode/#license">License&lt;/a>&lt;/li>
&lt;li>&lt;a href="https://www.emacs.dyerdwelling.family/emacs/20240524160249-emacs--new-package-xkb-mode/#contact">Contact&lt;/a>&lt;/li>
&lt;li>&lt;a href="https://www.emacs.dyerdwelling.family/emacs/20240524160249-emacs--new-package-xkb-mode/#issues">ISSUES&lt;/a>&lt;/li>
&lt;li>&lt;a href="https://www.emacs.dyerdwelling.family/emacs/20240524160249-emacs--new-package-xkb-mode/#todos-roadmap">TODOs / ROADMAP&lt;/a>&lt;/li>
&lt;li>&lt;a href="https://www.emacs.dyerdwelling.family/emacs/20240524160249-emacs--new-package-xkb-mode/#testing">Testing&lt;/a>&lt;/li>
&lt;/ul>
&lt;/div>
&lt;!--endtoc-->
&lt;hr>
&lt;h2 id="summary">Summary&lt;/h2>
&lt;p>A Major mode for editing X Keyboard Extension (XKB) files&lt;/p>
&lt;p>xkb-mode is an Emacs major mode designed to facilitate the editing of XKB files, providing syntax highlighting and other useful editing features tailored specifically for XKB file format. Whether you&amp;rsquo;re customizing keyboard layouts or diving into the details of X Keyboard Extension configurations, xkb-mode aims to make the task more efficient and enjoyable.&lt;/p>
&lt;h3 id="whats-new">Whats New&lt;/h3>
&lt;p>Version 0.2.0&lt;/p>
&lt;ul>
&lt;li>Correct version from 0.6.0 to 0.2.0 to reflect the actual stage of
development.&lt;/li>
&lt;li>Remove the &amp;lsquo;Alternatives&amp;rsquo; section, streamlining the introduction.&lt;/li>
&lt;li>Improve code formatting for better readability and maintenance. This
includes more consistent use of newlines and arrangement of font lock
faces.&lt;/li>
&lt;li>Use `#&amp;rsquo;` prefix for function symbols as a best practice, explicitly
marking `xkb-indent-line` as a function, enhancing code clarity.&lt;/li>
&lt;/ul>
&lt;h3 id="screenshot">Screenshot&lt;/h3>
&lt;figure>&lt;img src="https://www.emacs.dyerdwelling.family/emacs/20240524160249-emacs--New-Package-xkb-mode.jpg" width="100%">
&lt;/figure>
&lt;h2 id="features">Features&lt;/h2>
&lt;ul>
&lt;li>Syntax highlighting for XKB-specific keywords, modifiers, and structures.&lt;/li>
&lt;li>Custom indentation logic for XKB code blocks.&lt;/li>
&lt;li>Auto-detection of .xkb files to automatically enable the mode.&lt;/li>
&lt;/ul>
&lt;h2 id="installation">Installation&lt;/h2>
&lt;p>To install xkb-mode, you can use the following methods:&lt;/p>
&lt;h3 id="use-package--melpa">use-package (MELPA)&lt;/h3>
&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 xkb-mode)
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;h3 id="use-package--emacs-29">use-package (emacs 29)&lt;/h3>
&lt;p>Put the following into your emacs init file:&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 xkb-mode
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> :vc (:fetcher github :repo &lt;span style="color:#e6db74">&amp;#34;captainflasmr/xkb-mode&amp;#34;&lt;/span>))
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;h3 id="from-source">from source&lt;/h3>
&lt;p>Download the `.el` file and place it in your Emacs `load-path`.&lt;/p>
&lt;p>Then either manually load it or add it to your configuration to be loaded at startup.&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>(require &lt;span style="color:#e6db74">&amp;#39;xkb-mode&lt;/span>)
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;h2 id="usage">Usage&lt;/h2>
&lt;p>Opening any .xkb file with Emacs should automatically enable xkb-mode, providing you with syntax highlighting and indentation support for editing XKB files.&lt;/p>
&lt;h2 id="customization">Customization&lt;/h2>
&lt;p>Currently, xkb-mode provides a basic set of features optimized for general usage. Future versions may include customizable options based on user feedback.&lt;/p>
&lt;h2 id="contributing">Contributing&lt;/h2>
&lt;p>Contributions to xkb-mode are welcome! Whether it&amp;rsquo;s bug reports, feature suggestions, or code contributions, feel free to reach out or submit pull requests on GitHub.&lt;/p>
&lt;h2 id="license">License&lt;/h2>
&lt;p>xkb-mode is available under the terms of the GNU General Public License v3.0. See the included LICENSE file for more details.&lt;/p>
&lt;h2 id="contact">Contact&lt;/h2>
&lt;p>For any questions or suggestions, please contact James Dyer at &lt;a href="mailto:captainflasmr@gmail.com">captainflasmr@gmail.com&lt;/a>.&lt;/p>
&lt;p>Visit our GitHub repository: &lt;a href="https://github.com/captainflasmr/xkb-mode">https://github.com/captainflasmr/xkb-mode&lt;/a> for more information and updates.&lt;/p>
&lt;p>This README provides a concise but comprehensive overview of what the `xkb-mode` package is, how to get it installed, and how to use it, alongside encouraging community contributions and providing licensing info.&lt;/p>
&lt;h2 id="issues">ISSUES&lt;/h2>
&lt;p>NONE&lt;/p>
&lt;h2 id="todos-roadmap">TODOs / ROADMAP&lt;/h2>
&lt;p>TODO function to set xkb file to latched sticky keys&lt;/p>
&lt;p>TODO function to set xkb file locked sticky keys&lt;/p>
&lt;p>TODO function to map RAlt to Ctrl&lt;/p>
&lt;h2 id="testing">Testing&lt;/h2>
&lt;p>See CHANGELOG.org&lt;/p></description></item><item><title>Writing Elisp to Find Available Keybindings in a Sway Config</title><link>https://www.emacs.dyerdwelling.family/emacs/20240516070645-emacs--elisp-help-find-free-bindings-in-sway/</link><pubDate>Thu, 16 May 2024 18:25:00 +0100</pubDate><author>James Dyer</author><guid>https://www.emacs.dyerdwelling.family/emacs/20240516070645-emacs--elisp-help-find-free-bindings-in-sway/</guid><description>&lt;p>I thought I would provide an example of how I typically use elisp to make my life easier (well eventually - I still need to write the function after all!)&lt;/p>
&lt;pre tabindex="0">&lt;code class="language-plantuml" data-lang="plantuml">@startwbs
!theme materia
* Sway config
** parse file
** collect $mod keybindings
** sort
** output
@endwbs
&lt;/code>&lt;/pre>&lt;p>In the example below, I wanted to find a simple elispy method to determine which Sway keybindings are available, specifically those that utilize the $mod key (typically Mod4/Super). Running out of available $mod keybindings in Sway, and then realizing upon reload that I have inadvertently added a duplicate keybinding, can be quite frustrating.&lt;/p>
&lt;p>When this happens, I usually then have to resort to scrolling through the config file or using `isearch` to determine which keybindings are available. This can be tedious and is made more difficult in that my keybindings are not listed alphabetically but are grouped by function.&lt;/p>
&lt;p>I am having a similar problem with Emacs but that is where the package &lt;strong>free-keys&lt;/strong> comes in handy, listing the free and available keybindings.&lt;/p>
&lt;hr>
&lt;p>I initially wanted to create something similar to &lt;strong>free-keys&lt;/strong> for Sway, but by parsing the Sway configuration file and generating a report.&lt;/p>
&lt;p>A Sway config file is typically defined in the following format:&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-cfg" data-lang="cfg">&lt;span style="display:flex;">&lt;span>&lt;span style="color:#75715e"># Set Defaults&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#a6e22e">set $mod Mod4&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#a6e22e">set $left h&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#a6e22e">set $down j&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#a6e22e">set $up k&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#a6e22e">set $right l&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"># Key bindings&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#a6e22e">bindsym $mod+b exec toggle_waybar.sh&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#a6e22e">bindsym $mod+c exec screen-record.sh&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#a6e22e">bindsym $mod+e exec thunar ~/DCIM/Camera&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#a6e22e">bindsym $mod+m exec emacs&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#a6e22e">bindsym $mod+n exec firefox&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#a6e22e">bindsym $mod+p exec wl-color-picker&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#a6e22e">bindsym $mod+q kill&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#a6e22e">bindsym $mod+return exec $term&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"># Change window focus&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#a6e22e">bindsym $mod+Left focus left&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#a6e22e">bindsym $mod+Down focus down&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#a6e22e">bindsym $mod+Up focus up&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#a6e22e">bindsym $mod+Right focus right&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#a6e22e">bindsym $mod+$left focus left&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#a6e22e">bindsym $mod+$down focus down&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#a6e22e">bindsym $mod+$up focus up&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#a6e22e">bindsym $mod+$right focus right&lt;/span>
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>For now, to keep things simple, I will focus solely on writing elisp to list the current Sway keybindings that use Mod4 or any alias for Mod4 in alphabetical order. Then, by visually scanning and leveraging the human brain&amp;rsquo;s familiarity with the alphabet, I can determine which keybindings are unused!&lt;/p>
&lt;p>For example, my new elisp function could yield the following typical output for the sway snippet defined above:&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-cfg" data-lang="cfg">&lt;span style="display:flex;">&lt;span>&lt;span style="color:#a6e22e">Mod4+Down&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#a6e22e">Mod4+Left&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#a6e22e">Mod4+Right&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#a6e22e">Mod4+Up&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#a6e22e">Mod4+b&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#a6e22e">Mod4+c&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#a6e22e">Mod4+e&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#a6e22e">Mod4+h&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#a6e22e">Mod4+j&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#a6e22e">Mod4+k&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#a6e22e">Mod4+l&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#a6e22e">Mod4+m&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#a6e22e">Mod4+n&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#a6e22e">Mod4+p&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#a6e22e">Mod4+q&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#a6e22e">Mod4+return&lt;/span>
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>Now I should be able to more easily gauge which bindings are not used.&lt;/p>
&lt;p>Of course my full Sway config has many more Mod4 keybindings and when running the elisp function defined below I realised I now only have 3 alphabetical mappings left for $mod - eeek!, but at least I now know what they are and don&amp;rsquo;t have to go trawling through the config anymore.&lt;/p>
&lt;p>&amp;hellip;wait, um, just as a side note, while I&amp;rsquo;m editing this blog post, it has just occurred to me (pardon the pun), can I use &lt;strong>occur&lt;/strong>, push to a buffer then re-order, mmmm&amp;hellip;.., well lets ignore that thought for now, I want to dig into some elisp, its a learning experience after all!!&lt;/p>
&lt;p>Here is the elisp:&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 swaywm-list-mod-bindsyms (path)
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#e6db74">&amp;#34;List all bindsyms that start with $mod or its resolved value in the SwayWM config file at PATH.&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> (interactive &lt;span style="color:#e6db74">&amp;#34;fSway config file path: &amp;#34;&lt;/span>)
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> (with-temp-buffer
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> (&lt;span style="color:#a6e22e">insert-file-contents&lt;/span> path)
&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> (let ((vars &lt;span style="color:#66d9ef">nil&lt;/span>)
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> (bindsyms &lt;span style="color:#66d9ef">nil&lt;/span>))
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#75715e">;; Collect variable definitions&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;^set \\$\\([a-zA-Z0-9_]+\\) \\(.*\\)$&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> (let ((var (match-string-no-properties &lt;span style="color:#ae81ff">1&lt;/span>))
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> (value (match-string-no-properties &lt;span style="color:#ae81ff">2&lt;/span>)))
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> (setq vars (&lt;span style="color:#a6e22e">cons&lt;/span> (&lt;span style="color:#a6e22e">cons&lt;/span> var value) vars))))
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#75715e">;; Prepare to translate $mod and other keys&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> (let* ((&lt;span style="color:#a6e22e">mod&lt;/span> (&lt;span style="color:#a6e22e">cdr&lt;/span> (&lt;span style="color:#a6e22e">assoc&lt;/span> &lt;span style="color:#e6db74">&amp;#34;mod&amp;#34;&lt;/span> vars)))
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> (left (&lt;span style="color:#a6e22e">cdr&lt;/span> (&lt;span style="color:#a6e22e">assoc&lt;/span> &lt;span style="color:#e6db74">&amp;#34;left&amp;#34;&lt;/span> vars)))
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> (down (&lt;span style="color:#a6e22e">cdr&lt;/span> (&lt;span style="color:#a6e22e">assoc&lt;/span> &lt;span style="color:#e6db74">&amp;#34;down&amp;#34;&lt;/span> vars)))
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> (up (&lt;span style="color:#a6e22e">cdr&lt;/span> (&lt;span style="color:#a6e22e">assoc&lt;/span> &lt;span style="color:#e6db74">&amp;#34;up&amp;#34;&lt;/span> vars)))
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> (right (&lt;span style="color:#a6e22e">cdr&lt;/span> (&lt;span style="color:#a6e22e">assoc&lt;/span> &lt;span style="color:#e6db74">&amp;#34;right&amp;#34;&lt;/span> vars)))
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> (mod-re (&lt;span style="color:#a6e22e">format&lt;/span> &lt;span style="color:#e6db74">&amp;#34;\\(%s\\|$mod\\)&amp;#34;&lt;/span> (&lt;span style="color:#a6e22e">regexp-quote&lt;/span> &lt;span style="color:#a6e22e">mod&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:#75715e">;; Collect all bindings that start with $mod or its resolved value&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">format&lt;/span> &lt;span style="color:#e6db74">&amp;#34;^bindsym %s\\+\\([^ ]+\\) \\(.*\\)$&amp;#34;&lt;/span> mod-re) &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> (let* ((mod-key (match-string-no-properties &lt;span style="color:#ae81ff">1&lt;/span>))
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> (action (match-string-no-properties &lt;span style="color:#ae81ff">2&lt;/span>))
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> (full-key (&lt;span style="color:#a6e22e">concat&lt;/span> &lt;span style="color:#a6e22e">mod&lt;/span> &lt;span style="color:#e6db74">&amp;#34;+&amp;#34;&lt;/span> action)))
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#75715e">;; Replace variable references in keys&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> (setq full-key (replace-regexp-in-string &lt;span style="color:#e6db74">&amp;#34;\\$left&amp;#34;&lt;/span> left full-key))
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> (setq full-key (replace-regexp-in-string &lt;span style="color:#e6db74">&amp;#34;\\$down&amp;#34;&lt;/span> down full-key))
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> (setq full-key (replace-regexp-in-string &lt;span style="color:#e6db74">&amp;#34;\\$up&amp;#34;&lt;/span> up full-key))
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> (setq full-key (replace-regexp-in-string &lt;span style="color:#e6db74">&amp;#34;\\$right&amp;#34;&lt;/span> right full-key))
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#75715e">;; Replace $mod with the actual mod key or keep as $mod for clarity&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> (setq full-key (replace-regexp-in-string &lt;span style="color:#e6db74">&amp;#34;\\$mod&amp;#34;&lt;/span> &lt;span style="color:#a6e22e">mod&lt;/span> full-key))
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#75715e">;; Collect the key-action pair&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> (setq bindsyms (&lt;span style="color:#a6e22e">cons&lt;/span> (&lt;span style="color:#a6e22e">format&lt;/span> &lt;span style="color:#e6db74">&amp;#34;%s&amp;#34;&lt;/span> full-key) bindsyms)))))
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#75715e">;; Return reversed to maintain order&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> (&lt;span style="color:#a6e22e">mapconcat&lt;/span> &lt;span style="color:#e6db74">&amp;#39;identity&lt;/span> (&lt;span style="color:#a6e22e">sort&lt;/span> (&lt;span style="color:#a6e22e">nreverse&lt;/span> bindsyms) &lt;span style="color:#e6db74">&amp;#39;string-lessp&lt;/span>) &lt;span style="color:#e6db74">&amp;#34;\n&amp;#34;&lt;/span>))))
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>and to call just by:&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>(swaywm-list-mod-bindsyms &lt;span style="color:#e6db74">&amp;#34;~/.config/sway/config.d/default&amp;#34;&lt;/span>)
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>or interactively.&lt;/p>
&lt;p>I might evolve this a little more over the coming weeks (and actually maybe this is where &lt;strong>occur&lt;/strong> and in-house Emacsing may at last have its limitations!). There is potentially a lot to do, for example some such improvements could be :&lt;/p>
&lt;ul>
&lt;li>iterate over multiple files to accommodate a typical distributed Sway configuration set&lt;/li>
&lt;li>show bindings that are free - just like &lt;strong>free-keys&lt;/strong>&lt;/li>
&lt;li>make more generic for multiple different types of config files containing bindings such as :
&lt;ul>
&lt;li>hyprland&lt;/li>
&lt;li>other tiling window managers that typically define bindings in config files&lt;/li>
&lt;li>emacs ?!&lt;/li>
&lt;/ul>
&lt;/li>
&lt;li>translate all aliases not just the common $left, $right, $up, $down&lt;/li>
&lt;/ul>
&lt;hr>
&lt;p>Here is a mini manual for this function :&lt;/p>
&lt;hr>
&lt;div class="ox-hugo-toc toc local">
&lt;ul>
&lt;li>&lt;a href="https://www.emacs.dyerdwelling.family/emacs/20240516070645-emacs--elisp-help-find-free-bindings-in-sway/#manual-for-swaywm-list-mod-bindsyms">Manual for swaywm-list-mod-bindsyms&lt;/a>
&lt;ul>
&lt;li>&lt;a href="https://www.emacs.dyerdwelling.family/emacs/20240516070645-emacs--elisp-help-find-free-bindings-in-sway/#synopsis">Synopsis&lt;/a>&lt;/li>
&lt;li>&lt;a href="https://www.emacs.dyerdwelling.family/emacs/20240516070645-emacs--elisp-help-find-free-bindings-in-sway/#description">Description&lt;/a>&lt;/li>
&lt;li>&lt;a href="https://www.emacs.dyerdwelling.family/emacs/20240516070645-emacs--elisp-help-find-free-bindings-in-sway/#parameters">Parameters&lt;/a>&lt;/li>
&lt;li>&lt;a href="https://www.emacs.dyerdwelling.family/emacs/20240516070645-emacs--elisp-help-find-free-bindings-in-sway/#usage">Usage&lt;/a>&lt;/li>
&lt;li>&lt;a href="https://www.emacs.dyerdwelling.family/emacs/20240516070645-emacs--elisp-help-find-free-bindings-in-sway/#example">Example&lt;/a>&lt;/li>
&lt;li>&lt;a href="https://www.emacs.dyerdwelling.family/emacs/20240516070645-emacs--elisp-help-find-free-bindings-in-sway/#notes">Notes&lt;/a>&lt;/li>
&lt;/ul>
&lt;/li>
&lt;/ul>
&lt;/div>
&lt;!--endtoc-->
&lt;hr>
&lt;h2 id="manual-for-swaywm-list-mod-bindsyms">Manual for swaywm-list-mod-bindsyms&lt;/h2>
&lt;p>List and sort bindsym commands from a SwayWM configuration file that are assigned to the mod key.&lt;/p>
&lt;h3 id="synopsis">Synopsis&lt;/h3>
&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>(swaywm-list-mod-bindsyms PATH)
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;h3 id="description">Description&lt;/h3>
&lt;p>The `swaywm-list-mod-bindsyms` function parses a specified SwayWM configuration file and extracts all `bindsym` commands that begin with the modifier key (usually designated as `$mod` in SwayWM configurations). The resolution of the `$mod` key, along with other potential sway variables like `$left`, `$down`, `$up`, and `$right`, is dynamically handled according to their definitions within the file. The commands are then returned as a single string, with each bindsym command on a new line, sorted alphabetically.&lt;/p>
&lt;p>This function is particularly useful for users of the Sway Window Manager who wish to quickly audit their keybindings associated with the mod key — a common requirement for optimizing workflow efficiency or for documentation purposes.&lt;/p>
&lt;h3 id="parameters">Parameters&lt;/h3>
&lt;ul>
&lt;li>`PATH`: Path to the SwayWM configuration file to be parsed.&lt;/li>
&lt;/ul>
&lt;h3 id="usage">Usage&lt;/h3>
&lt;p>To use `swaywm-list-mod-bindsyms`, call the function with the path to your SwayWM configuration file as its argument. For interactive use:&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>M-x swaywm-list-mod-bindsyms
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>When prompted, enter the full path to your SwayWM configuration file. The output will be shown, depending on the Emacs settings, either directly in the minibuffer (for shorter lists) or in a separate buffer for longer outputs.&lt;/p>
&lt;h3 id="example">Example&lt;/h3>
&lt;p>Assuming there&amp;rsquo;s a SwayWM configuration file located at `~/sway/config`, you would use the function as follows:&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>M-x swaywm-list-mod-bindsyms RET ~/sway/config RET
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>The returned value will be a sorted list of all `bindsym` commands associated with the `$mod` key or its resolved value (e.g., `Mod4`) from the file, neatly formatted for easy inspection.&lt;/p>
&lt;h3 id="notes">Notes&lt;/h3>
&lt;ul>
&lt;li>
&lt;p>This function assumes a standard formatting of the SwayWM configuration file, as deviations might affect the parsing logic.&lt;/p>
&lt;/li>
&lt;li>
&lt;p>The sorting is done in a case-sensitive alphabetical order, following Emacs&amp;rsquo; `string-lessp` function conventions.&lt;/p>
&lt;/li>
&lt;li>
&lt;p>All modifications to variables like `$mod`, `$left`, `$down`, `$up`, and `$right` are dynamically accounted for based on their set values in the configuration file.&lt;/p>
&lt;hr>
&lt;/li>
&lt;/ul></description></item><item><title>Emacs Vim Navigation Without Evil</title><link>https://www.emacs.dyerdwelling.family/emacs/20240504183551-emacs--emacs-vim-navigation-without-evil/</link><pubDate>Sat, 11 May 2024 21:40:00 +0100</pubDate><author>James Dyer</author><guid>https://www.emacs.dyerdwelling.family/emacs/20240504183551-emacs--emacs-vim-navigation-without-evil/</guid><description>&lt;p>Every now and then I find it necessary to use Vim! (mainly for work) - was that clickbait on an Emacs blog?! 😀&lt;/p>
&lt;hr>
&lt;div class="ox-hugo-toc toc local">
&lt;ul>
&lt;li>&lt;a href="https://www.emacs.dyerdwelling.family/emacs/20240504183551-emacs--emacs-vim-navigation-without-evil/#introduction">Introduction&lt;/a>&lt;/li>
&lt;li>&lt;a href="https://www.emacs.dyerdwelling.family/emacs/20240504183551-emacs--emacs-vim-navigation-without-evil/#modal-editing-and-emacs">Modal Editing and Emacs&lt;/a>&lt;/li>
&lt;li>&lt;a href="https://www.emacs.dyerdwelling.family/emacs/20240504183551-emacs--emacs-vim-navigation-without-evil/#my-non-modal-solution">My Non-Modal Solution&lt;/a>&lt;/li>
&lt;li>&lt;a href="https://www.emacs.dyerdwelling.family/emacs/20240504183551-emacs--emacs-vim-navigation-without-evil/#conclusion">Conclusion&lt;/a>&lt;/li>
&lt;/ul>
&lt;/div>
&lt;!--endtoc-->
&lt;hr>
&lt;pre tabindex="0">&lt;code class="language-plantuml" data-lang="plantuml">!pragma layout smetana
hide footbox
box &amp;#34;Emacs Vim Navigation Without Evil&amp;#34; #lightblue
h -&amp;gt; j
j -&amp;gt; k
k -&amp;gt; l
end box
&lt;/code>&lt;/pre>&lt;h2 id="introduction">Introduction&lt;/h2>
&lt;p>I therefore like to have the muscle memory for basic Vim navigation keybindings already built up as that for me is half the battle when using Vim and I also appreciate the efficiency and natural feel of the navigation keybindings `hjkl`.&lt;/p>
&lt;p>In light of this I have decided to incorporate this Vim navigation paradigm into Emacs. My muscle memory is already fully baked and attuned to the Emacs default navigation keys so why not learn something new? with the benefits of an already oven warm familiar feel if I do have to use Vim and maybe anything else that might support vim keybindings (which is not uncommon), for example &lt;code>pacseek&lt;/code> and seemingly most browser key navigation add-ons.&lt;/p>
&lt;h2 id="modal-editing-and-emacs">Modal Editing and Emacs&lt;/h2>
&lt;p>Of course before delving a little further I shall have to talk about the herd of elephants in the room&amp;hellip; and that is the current plethora of Emacs modal editing packages, such as `evil-mode`, `Meow` et al. They have been developed to bridge the modal gap, for example, `evil-mode` is a comprehensive emulation layer that replicates Vim&amp;rsquo;s keybindings and modes, offering Vim users a more familiar experience within Emacs. On the other hand, `Meow` presents a more streamlined approach, designed with simplicity in mind, aiming to provide the efficiency of modal editing without mirroring Vim&amp;rsquo;s functionality in its entirety.&lt;/p>
&lt;h2 id="my-non-modal-solution">My Non-Modal Solution&lt;/h2>
&lt;p>Despite the advantages of these packages, I wanted to find a method that involves neither the full adoptation of `evil-mode` nor the simplified modal editing of `Meow` or anything similar. My goal here is to utilize Vim&amp;rsquo;s `hjkl` navigation keys within Emacs in a non-modal context, thereby retaining Emacs&amp;rsquo;s modeless editing advantages while enjoying the familiarity and comfort of Vim&amp;rsquo;s navigation system.&lt;/p>
&lt;p>To achieve this, I tapped into the power of Emacs&amp;rsquo;s keybinding customization capabilities.&lt;/p>
&lt;p>Simply, I reassigned the original Emacs functions defined by `M-hjkl`. This allows a Vim-style navigation in Emacs&amp;rsquo;s default mode-less environment using the following keybindings:&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>(bind-key* (kbd &lt;span style="color:#e6db74">&amp;#34;M-h&amp;#34;&lt;/span>) &lt;span style="color:#a6e22e">#&amp;#39;backward-char&lt;/span>)
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>(bind-key* (kbd &lt;span style="color:#e6db74">&amp;#34;M-j&amp;#34;&lt;/span>) &lt;span style="color:#a6e22e">#&amp;#39;&lt;/span>next-line)
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>(bind-key* (kbd &lt;span style="color:#e6db74">&amp;#34;M-k&amp;#34;&lt;/span>) &lt;span style="color:#a6e22e">#&amp;#39;&lt;/span>previous-line)
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>(bind-key* (kbd &lt;span style="color:#e6db74">&amp;#34;M-l&amp;#34;&lt;/span>) &lt;span style="color:#a6e22e">#&amp;#39;forward-char&lt;/span>)
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>The original functions bound to these keys were seldom used in my workflow so I didn&amp;rsquo;t mind replacing them:&lt;/p>
&lt;ul>
&lt;li>`M-h` (`mark-paragraph`) found a new home at `M-s h`.&lt;/li>
&lt;li>`M-j` (`default-indent-new-line`), which I rarely used, was easily replaced by simply using `tab` or `C-i`.&lt;/li>
&lt;li>`M-k` (`kill-sentence`), another feature I never utilized, was made redundant by the use of `kill-line`.&lt;/li>
&lt;li>`M-l` (`downcase-word`), also rarely used, didn’t find a new binding as it wasn’t needed in my daily tasks.&lt;/li>
&lt;/ul>
&lt;p>The only disadvantage is that it requires holding down the Meta (or Alt) key with my left hand, but ergonomically that doesn&amp;rsquo;t seem too bad as that will be the only requirement for my left hand as my right will be busy with the navigation.&lt;/p>
&lt;h2 id="conclusion">Conclusion&lt;/h2>
&lt;p>While `evil-mode` and `Meow` offer powerful modal editing solutions that closely mimic or simplify Vim&amp;rsquo;s interface within Emacs, my approach demonstrates an alternative path. By creatively reassigning keybindings, I have integrated Vim&amp;rsquo;s efficient navigation into Emacs without adopting a modal editing framework, blending the best of both worlds to enhance my text editing efficiency. This approach underscores the adaptability of Emacs, proving it to be an incredibly versatile tool that can accommodate a wide range of user preferences and workflows.&lt;/p></description></item><item><title>How To Map RAlt to Ctrl for Emacs</title><link>https://www.emacs.dyerdwelling.family/emacs/20240425213402-emacs--how-to-map-ralt-to-ctrl/</link><pubDate>Sat, 04 May 2024 15:30:00 +0100</pubDate><author>James Dyer</author><guid>https://www.emacs.dyerdwelling.family/emacs/20240425213402-emacs--how-to-map-ralt-to-ctrl/</guid><description>&lt;p>In a recent post I was talking about the benefits of mapping the RAlt key to the Ctrl key and this set up is so far still feeling very comfortable.&lt;/p>
&lt;p>However, there are many ways to set up the mapping, so below are instructions on how to map the right Alt key (RAlt) to Ctrl for different platforms. These are just the methods I have used in the past, I&amp;rsquo;m sure there are a multitude of other options out there.&lt;/p>
&lt;hr>
&lt;div class="ox-hugo-toc toc local">
&lt;ul>
&lt;li>&lt;a href="https://www.emacs.dyerdwelling.family/emacs/20240425213402-emacs--how-to-map-ralt-to-ctrl/#linux">Linux&lt;/a>&lt;/li>
&lt;li>&lt;a href="https://www.emacs.dyerdwelling.family/emacs/20240425213402-emacs--how-to-map-ralt-to-ctrl/#windows">Windows&lt;/a>&lt;/li>
&lt;li>&lt;a href="https://www.emacs.dyerdwelling.family/emacs/20240425213402-emacs--how-to-map-ralt-to-ctrl/#linux-or-windows">Linux or Windows&lt;/a>&lt;/li>
&lt;/ul>
&lt;/div>
&lt;!--endtoc-->
&lt;hr>
&lt;p>Before you dive in, here is a rough plantuml diagram to give you a top level diagrammatical overview of what I am describing below (note: kmonad is not included):&lt;/p>
&lt;p>&lt;a id="code-snippet--workflow1">&lt;/a>&lt;/p>
&lt;pre tabindex="0">&lt;code class="language-plantuml" data-lang="plantuml">!pragma layout smetana
title: How To Map Ralt to Ctrl Activity Diagram
start
:Start;
if (OS == &amp;#34;Linux&amp;#34;) then (yes)
if (Environment == &amp;#34;X11 or Wayland&amp;#34;) then (yes)
:Generate xkb file;
note right
xkbcomp $DISPLAY -xkb keymap.xkb
end note
:Modify keymap.xkb;
note right
Add modifier_map and key definitions
end note
:Load modified file;
note right
xkbcomp keymap.xkb $DISPLAY
end note
:For persistence,\nadd to startup applications or\n.bashrc/.profile;
endif
if (Environment == &amp;#34;X11&amp;#34;) then (yes)
:Choose Tool;
if (Tool == &amp;#34;xmodmap&amp;#34;) then (yes)
:Create/Edit .Xmodmap;
:Add keycode modification;
:Apply changes;
note right
xmodmap ~/.Xmodmap
end note
else (no)
:Run setxkbmap command;
note right
setxkbmap -option ctrl:ralt_rctrl
end note
endif
:For persistence,\nadd to startup applications or\n.bashrc/.profile;
endif
if (Environment == &amp;#34;Wayland TWM&amp;#34;) then (yes)
:Modify sway/hyprland config;
note right
Depending on the window manager, apply the necessary `input` configurations.
end note
:For persistence, ensure\nconfig is loaded on login;
endif
else (no)
:Install AutoHotkey;
:Create and Edit Script;
:Run Script;
note right
Script contains key remappings like RAlt::Ctrl
end note
:For persistence, place in &amp;#34;Startup&amp;#34; folder;
endif
stop
&lt;/code>&lt;/pre>&lt;h2 id="linux">Linux&lt;/h2>
&lt;h3 id="x11-or-wayland">X11 or Wayland&lt;/h3>
&lt;h4 id="xkb--x-keyboard-extension">xkb (X Keyboard Extension)&lt;/h4>
&lt;p>First generate an xkb file which defines all keybindings for the current layout by running the follwing command:&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>xkbcomp $DISPLAY -xkb keymap.xkb
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>Add to the keymap.xkb file along with the other defined modifier_maps:&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>modifier_map Mod1 { &amp;lt;RALT&amp;gt; };
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>and set the definition of key to:&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>key &amp;lt;RALT&amp;gt; { [ Alt_L, Meta_L ] };
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>Now load the modified file back in again:&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>xkbcomp keymap.xkb $DISPLAY
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>If you want it to be applied every time you log in, you can add this command to your startup applications or add it to your .bashrc or .profile file.&lt;/p>
&lt;h3 id="x11-only">X11 only&lt;/h3>
&lt;h4 id="xmodmap">xmodmap&lt;/h4>
&lt;p>Xmodmap is a little archaic these days and is seen as being a little deprecated and soon to become obsolete (especially with the incoming Wayland protocol) but especially on X11 it can still be used if you don&amp;rsquo;t want to fiddle around with xkb files.&lt;/p>
&lt;p>To map the right Alt key (RAlt) to Ctrl using xmodmap in Linux, you can follow these steps:&lt;/p>
&lt;p>Create a file named .Xmodmap in your home directory if it doesn&amp;rsquo;t exist already.&lt;/p>
&lt;p>Add the following line to the file:&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>keycode &amp;lt;keycode_of_RAlt&amp;gt; = Control_L
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>Replace with the keycode of your right Alt key.&lt;/p>
&lt;p>To find the keycode, you can use the xev command. Open a terminal and run:&lt;/p>
&lt;p>xev&lt;/p>
&lt;p>This will open a small window. Move your cursor into that window and press the right Alt key. Look for the keycode in the terminal output. It should look something like keycode 108 (maybe exactly like!)&lt;/p>
&lt;p>To apply the changes, run:&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>xmodmap ~/.Xmodmap
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>Now your right Alt key should behave like the Ctrl key. Keep in mind that this mapping will only persist for your current session. If you want it to be applied every time you log in, you can add the xmodmap ~/.Xmodmap command to your startup applications or add it to your .bashrc or .profile file.&lt;/p>
&lt;h4 id="setxkbmap">setxkbmap&lt;/h4>
&lt;p>Simply run the following command:&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>setxkbmap -option ctrl:ralt_rctrl
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>If you want it to be applied every time you log in, you can add this command to your startup applications or add it to your .bashrc or .profile file.&lt;/p>
&lt;h3 id="wayland-tiling-window-managers">Wayland Tiling Window Managers&lt;/h3>
&lt;h4 id="sway">Sway&lt;/h4>
&lt;p>Perform the xkb steps defined above and then modify the sway config file as follows:&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>input type:keyboard {
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> xkb_file keymap.xkb
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>}
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>alternatively modify the sway config file as follow:&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>input type:keyboard {
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> xkb_options ctrl:ralt_rctrl
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>}
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>As this is modifying the basic config file it will be guaranteed to be loaded every time you log in.&lt;/p>
&lt;h4 id="hyprland">Hyprland&lt;/h4>
&lt;p>As the xkb steps above and then modify the hyprland config file as follows:&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>input {
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> kb_file = keymap.xkb
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>}
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>alternatively modify the hyprland config file as follow:&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>input {
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> kb_options = caps:ctrl_modifier
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>}
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>As this is modifying the basic config file it will be guaranteed to be loaded every time you log in.&lt;/p>
&lt;hr>
&lt;h2 id="windows">Windows&lt;/h2>
&lt;h3 id="autohotkey">AutoHotKey&lt;/h3>
&lt;p>Install AutoHotkey if you haven&amp;rsquo;t already. You can download it from the official website: &lt;a href="https://www.autohotkey.com/">https://www.autohotkey.com/&lt;/a>&lt;/p>
&lt;p>Once AutoHotkey is installed, right-click on your desktop or in a folder, hover over &amp;ldquo;New,&amp;rdquo; and select &amp;ldquo;AutoHotkey Script.&amp;rdquo; Name the script whatever you like, for example, RAlt_to_Ctrl.ahk.&lt;/p>
&lt;p>Right-click the newly created script file and select &amp;ldquo;Edit Script&amp;rdquo; to open it in your default text editor.&lt;/p>
&lt;p>Add the following line to the script:&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>RAlt::Ctrl
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>Save the script and close the text editor.&lt;/p>
&lt;p>Double-click the script file to run it. You should see a green &amp;ldquo;H&amp;rdquo; icon in the system tray indicating that the script is running.&lt;/p>
&lt;p>Now, whenever you press the right Alt key, it will function as the Ctrl key. To stop the remapping, right-click the AutoHotkey icon in the system tray and select &amp;ldquo;Exit.&amp;rdquo;&lt;/p>
&lt;p>This remapping will persist until you close the script or disable AutoHotkey. If you want the remapping to be applied automatically every time you start your computer, you can place a shortcut to the script in the &amp;ldquo;Startup&amp;rdquo; folder of your Start Menu.&lt;/p>
&lt;hr>
&lt;h2 id="linux-or-windows">Linux or Windows&lt;/h2>
&lt;h3 id="kmonad">kmonad&lt;/h3>
&lt;p>Install kmonad if you haven&amp;rsquo;t already. You can find installation
instructions on the GitHub repository: &lt;a href="https://github.com/kmonad/kmonad">https://github.com/kmonad/kmonad&lt;/a>&lt;/p>
&lt;p>Once kmonad is installed, create a configuration file called kmonad.hs&lt;/p>
&lt;h4 id="linux">linux&lt;/h4>
&lt;p>Add the following lines :&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>(defcfg
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> input (device-file &amp;#34;/dev/input/by-path/&amp;lt;device&amp;gt;&amp;#34;)
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> output (uinput-sink &amp;#34;My KMonad output&amp;#34;)
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> ;; Comment this if you want unhandled events not to be emitted
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> fallthrough true
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> ;; Set this to false to disable any command-execution in KMonad
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> allow-cmd true
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> )
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>(defsrc ralt)
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>(deflayer default lctrl)
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>Now on the command line run up:&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>kmonad kmonad.hs
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>To ensure that the mapping persists place the command to your startup
applications or add it to your .bashrc or .profile file.&lt;/p>
&lt;h4 id="windows">windows&lt;/h4>
&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>(defcfg
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> input (low-level-hook)
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> output (send-event-sink)
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> fallthrough true)
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>(defsrc ralt)
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>(deflayer default lctrl)
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>Now create a batch script called kmonad.bat and add:&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>kmonad.exe kmonad.hs
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>This remapping will persist until you close kmonad.exe. If you want the
remapping to be applied automatically every time you start your
computer, you can place a shortcut to the script in the &amp;ldquo;Startup&amp;rdquo; folder
of your Start Menu.&lt;/p>
&lt;hr>
&lt;p>This post may end up turning into some kind of living document when I find out other ways to perform the mapping, for example I might start to look at Kanata and also see if PowerToys can perform this mapping on Windows.&lt;/p></description></item><item><title>My Emacs Key Workflow Moments Over The Last 30 Years</title><link>https://www.emacs.dyerdwelling.family/emacs/20240424142938-emacs--emacs-over-the-last-30-years/</link><pubDate>Fri, 26 Apr 2024 15:00:00 +0100</pubDate><author>James Dyer</author><guid>https://www.emacs.dyerdwelling.family/emacs/20240424142938-emacs--emacs-over-the-last-30-years/</guid><description>&lt;p>I am just at the moment reflecting on how my experience with Emacs over the last 30 years has evolved. I thought that just for fun I would list all the key moments along my Emacs journey regarding work-flow efficiency breakthroughs, roughly in year order!.&lt;/p>
&lt;p>I think you can see that my investment of time has spiked in recent years, probably spurred on by writing this blog!&lt;/p>
&lt;p>Firstly I went through the barren years&amp;hellip; :&lt;/p>
&lt;p>&lt;a id="code-snippet--workflow1">&lt;/a>&lt;/p>
&lt;pre tabindex="0">&lt;code class="language-plantuml" data-lang="plantuml">!pragma layout smetana
!theme mars
skinparam backgroundColor #eeeeee
skinparam sequenceArrowThickness 1
skinparam DefaultFontSize 20
skinparam roundcorner 20
hide footbox
-&amp;gt; 1994 : learning emacs at University
-&amp;gt; 2000 : mapped caps lock to ctrl
-&amp;gt; 2002 : desktop save mode
-&amp;gt; 2010 : using dired to replace native file explorer
-&amp;gt; 2016 : elisp defuns for common tasks
-&amp;gt; 2016 : savehist-mode
-&amp;gt; 2016 : winner mode
-&amp;gt; 2017 : ivy
-&amp;gt; 2018 : dabbrev
-&amp;gt; 2019 : rebinding keys to match usage frequency
-&amp;gt; 2019 : imenu for navigation
-&amp;gt; 2019 : macros for repetitive tasks
&lt;/code>&lt;/pre>&lt;figure>&lt;img src="https://www.emacs.dyerdwelling.family/emacs/20240424142938-emacs--Emacs-Over-The-Last-30-Years_d3.jpg" width="100%">
&lt;/figure>
&lt;p>and then when I started to get a little more serious about Emacs :&lt;/p>
&lt;p>&lt;a id="code-snippet--workflow2">&lt;/a>&lt;/p>
&lt;pre tabindex="0">&lt;code class="language-plantuml" data-lang="plantuml">!pragma layout smetana
!theme mars
skinparam backgroundColor #eeeeee
skinparam sequenceArrowThickness 1
skinparam DefaultFontSize 20
skinparam roundcorner 20
hide footbox
-&amp;gt; 2021 : grep with deadgrep
-&amp;gt; 2021 : magit
-&amp;gt; 2021 : leader key definition M-&amp;lt;key&amp;gt; - to take load off ctrl
-&amp;gt; 2021 : hippie expand
-&amp;gt; 2022 : vertico, orderless, marginalia
-&amp;gt; 2022 : embark for quick actions
-&amp;gt; 2023 : tempel - unique 2 characters to insert common templates
-&amp;gt; 2023 : vim cursor movement (without evil) - so fingers remain on home row
-&amp;gt; 2023 : moving to tabs to switch between windows setups
-&amp;gt; 2023 : tab bar history
-&amp;gt; 2024 : sticky keys - single key presses, no key chording
-&amp;gt; 2024 : using a mechanical keyboard
-&amp;gt; 2024 : mapped ralt to ctrl - to take complete load off even the caps lock remapping
-&amp;gt; 2024 : capf-autosuggest in [e]shell to get a more fish like experience
-&amp;gt; 2024 : org mode speed keys - single key presses for common org commands
&lt;/code>&lt;/pre>&lt;p>Note that I have endeavoured to incorporate some level of completion framework into my work-flow, systems such as &lt;strong>company&lt;/strong> and &lt;strong>corfu&lt;/strong> but I always end up just using hippie-expand wrapped around dabbrev.&lt;/p>
&lt;p>I think this is why I have always been drawn to Emacs, it is just me and a text editor, there is no clutter and no distractions, although there is so much more to Emacs than just a text editor, everything else is hidden away. I don&amp;rsquo;t really want inline completion popups as I type, I don&amp;rsquo;t need a menu bar, scroll bars, a source code tree and I tend to always work on a single monitor so the saving on screen real estate is very beneficial to the way I work.&lt;/p>
&lt;p>I am a Software Engineer now of 30 years, I am stuck in my ways, stuck with Emacs, but I am happy, productive and efficient!&lt;/p>
&lt;p>P.S. and yes I&amp;rsquo;m starting to play around with plantuml 🙂&lt;/p></description></item><item><title>Transitioning RAlt to Ctrl - Enhancing Emacs Keybindings</title><link>https://www.emacs.dyerdwelling.family/emacs/20240417211640-emacs--transitioning-ralt-to-ctrl-enhancing-emacs-keybindings/</link><pubDate>Sat, 20 Apr 2024 10:12:00 +0100</pubDate><author>James Dyer</author><guid>https://www.emacs.dyerdwelling.family/emacs/20240417211640-emacs--transitioning-ralt-to-ctrl-enhancing-emacs-keybindings/</guid><description>&lt;p>In the midst of adjusting my Emacs keybindings for a more ergonomic workflow, I&amp;rsquo;ve experimented with various configurations. For example, I duplicated the right Alt key (RAlt) to function as an additional Alt key allowing for versatile key presses across the keyboard based on command keybinding locations.&lt;/p>
&lt;figure>&lt;img src="https://www.emacs.dyerdwelling.family/emacs/20240417211640-emacs--Transitioning-RAlt-to-Ctrl-Enhancing-Emacs-Keybindings.jpg" width="100%">
&lt;/figure>
&lt;p>The RAlt key is certainly accessible with a little curl under of the right thumb and a modifier key activator I hadn&amp;rsquo;t even considered before.&lt;/p>
&lt;p>For instance, I found combinations like M-n and M-p already comfortable with a left hand / right hand split, but with RAlt for activating Meta/Alt a right hand / left hand split became possible for certain commands, notably M-f and M-w which I often use. This theoretically already felt a more comfortable setup.&lt;/p>
&lt;p>However, after a few months, I realized I couldn&amp;rsquo;t quite find a way to transition to the RAlt remapping; I simply wasn&amp;rsquo;t using it. Yet, the initial experiment hinted at the potential of shifting modifier load to the right hand, especially leveraging the strong thumb for ergonomic advantages.&lt;/p>
&lt;p>Then it struck me: what if I map RAlt to Ctrl?, I immediately questioned why I hadn&amp;rsquo;t tried it earlier!&lt;/p>
&lt;p>The transition so far has unveiled the following (note: I may have got carried away with the detail here, but in a way its fun to weigh up the pros and cons!):&lt;/p>
&lt;p>&lt;strong>Benefits&lt;/strong>&lt;/p>
&lt;ul>
&lt;li>Splitting common prefixes like C-x and C-c between hands.&lt;/li>
&lt;li>Enhanced comfort in isearch with split hand C-r and C-s.&lt;/li>
&lt;li>Improved buffer window navigation, with RLR (Right Left Right) pattern for C-x o.&lt;/li>
&lt;li>Simplified buffer window splits with C-x &amp;lt;num&amp;gt;. Although there are typically two consecutive left hand key activations, sticky keys allows just two individual key taps of x then &amp;lt;num&amp;gt;.&lt;/li>
&lt;li>Streamlined actions like the common C-c C-c, with the option of holding RAlt allowing easy left-hand tapping of c, c&lt;/li>
&lt;li>Reassigned C-o (never used) to the (other-window) so I can replace my ever fluctuating window keybinding navigation to something more simple using just one hand, plus I can hold down the Ralt and tap through the windows.&lt;/li>
&lt;li>C-g was always a bit of stretch - especially before sticky keys, but now I have a RL combination.&lt;/li>
&lt;li>Simplified Emacs exit with C-x C-c (although why would I ever need this?!) holding RAlt for x,c tap tap.&lt;/li>
&lt;li>A locked sticky key Ctrl double tap now doesn&amp;rsquo;t activate a double whammy of left hand pinky lateral movement.&lt;/li>
&lt;li>Saving can be easier, C-x C-s again is a right hand modifier hold with an x,s tap&lt;/li>
&lt;li>Another common command (which in fact I had to rebind as it was too taxing on my left hand) is now much more comfortable, namely C-x C-e (eval-last-sexp) in the same way as saving mentioned above.&lt;/li>
&lt;li>I am finding myself instinctively using C-j a lot more as naturally my right thumb is now very close to RAlt and my right index finger is always on j&lt;/li>
&lt;li>Reverting buffers is much easier, It is bound to C-x x g (revert-buffer-quick)&lt;/li>
&lt;li>I am finding myself starting to revert back to the vanilla Emacs keybindings rather than my adapted ones mainly I suspect that C-x and C-c is much easier now to access.&lt;/li>
&lt;/ul>
&lt;p>&lt;strong>Negatives&lt;/strong>&lt;/p>
&lt;ul>
&lt;li>I will have to adjust to the new position of the keybindings, and some like the undo with C-/ feed a bit strange due to the close proximity between Ralt and /&lt;/li>
&lt;li>Keyboard configuration is required to map Ralt to Control.&lt;/li>
&lt;/ul>
&lt;p>I&amp;rsquo;ll evaluate this setup&amp;rsquo;s efficacy over the next few months and I may contemplate not just the possibility of minimizing Ctrl key usage on the left hand but potentially eliminating it altogether! My left hand pinky would then never have to attempt lateral movement again!&lt;/p></description></item><item><title>Waybar Toggling Sticky Key Keymaps</title><link>https://www.emacs.dyerdwelling.family/emacs/20240414-emacs--waybar-toggling-sticky-key-types/</link><pubDate>Sun, 14 Apr 2024 11:55:00 +0100</pubDate><author>James Dyer</author><guid>https://www.emacs.dyerdwelling.family/emacs/20240414-emacs--waybar-toggling-sticky-key-types/</guid><description>&lt;p>For my previous post I was talking about a software visual indicator to discern which key has been locked in a sticky key situation. For example there are typically two modes of stickiness, being latched and locked, by default I had set up the locked variant as I thought it would be more useful for Emacs and in fact I have found this to be the case. For example, double tapping the Control key allows nice easy single key navigation via &amp;rsquo;n&amp;rsquo; &amp;lsquo;p&amp;rsquo; &amp;lsquo;f&amp;rsquo; and &amp;lsquo;b&amp;rsquo;, possible page down with &amp;lsquo;v&amp;rsquo; and to delete lines I can use &amp;lsquo;k&amp;rsquo; .e.t.c.&lt;/p>
&lt;figure>&lt;img src="https://www.emacs.dyerdwelling.family/emacs/2024-04-14-10-55-03.jpg" width="100%">
&lt;/figure>
&lt;p>I&amp;rsquo;m sure I will find more bonuses in Emacs with this key locking mechanism and of course I can always tweak my keybindings, for example maybe a page up could be a Ctrl-&amp;lt;key&amp;gt; so I can have even more power navigating with a single &amp;lt;key&amp;gt;.&lt;/p>
&lt;p>But how does a locked key setup work in a different program? and especially the software I use for my digital art, namely Krita. I have a USB numpad peripheral in which I map each key to a Krita shortcut, for example common actions such as colour picking, brush resize, canvas flip, undo / redo e.t.c. I have found this to actually be a nice cheaper option than one of the more specialised digital art controllers and more flexible as I will always be able to use it on linux with keymapping software like kmonad or kanata.&lt;/p>
&lt;p>It didn&amp;rsquo;t take long to find out that I was running into difficulties with Krita and the locked sticky setup. For example colour picking is quite a common activity in digital art and it just so happens that for Krita it is bound by default to the Ctrl key! In addition Shift resizes the brush too. I found that quite often I was tapping the Ctrl and or Shift consecutively which was locking the modifier key which would have some unwanted side effects and would start to get frustrating as I fumble around to unlock the relevant modifier thus disrupting my flow. I quickly came to the conclusion that a locked sticky was not viable for my use within Krita.&lt;/p>
&lt;p>So how could I craft a solution to this problem?&lt;/p>
&lt;p>In my previous post, I might have unintentionally already laid the groundwork as I have created both a latched and a locked sticky key variant in the xkb file format. Perhaps I could develop a toggling mechanism between the locked and latched states, switching to latched state when I use Krita.&lt;/p>
&lt;p>Just like before, I think Waybar would be ideal to perform the toggle with a click (which could also be a stylus click from a pen) and to then display the current status.&lt;/p>
&lt;p>Initially I created the following bash script to perform the toggling of the keymaps :&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-bash" data-lang="bash">&lt;span style="display:flex;">&lt;span>&lt;span style="color:#75715e">#!/bin/bash
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#75715e">&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#75715e"># Define the paths to your keymap files&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>KEYMAP_SWAY&lt;span style="color:#f92672">=&lt;/span>~/.config/keymap_sway.xkb
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>KEYMAP_LOCKED&lt;span style="color:#f92672">=&lt;/span>~/.config/keymap_with_locked_modifiers.xkb
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>KEYMAP_STICKY&lt;span style="color:#f92672">=&lt;/span>~/.config/keymap_with_sticky_modifiers.xkb
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>CURRENT_KEYMAP_PATH&lt;span style="color:#f92672">=&lt;/span>~/.config/keymap_current
&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 the current keymap is set, if not use the sway keymap&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#66d9ef">if&lt;/span> &lt;span style="color:#f92672">[[&lt;/span> ! -f &lt;span style="color:#e6db74">&amp;#34;&lt;/span>$CURRENT_KEYMAP_PATH&lt;span style="color:#e6db74">&amp;#34;&lt;/span> &lt;span style="color:#f92672">]]&lt;/span>; &lt;span style="color:#66d9ef">then&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> echo &lt;span style="color:#e6db74">&amp;#34;&lt;/span>$KEYMAP_SWAY&lt;span style="color:#e6db74">&amp;#34;&lt;/span> &amp;gt; &lt;span style="color:#e6db74">&amp;#34;&lt;/span>$CURRENT_KEYMAP_PATH&lt;span style="color:#e6db74">&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#66d9ef">fi&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>CURRENT_KEYMAP&lt;span style="color:#f92672">=&lt;/span>&lt;span style="color:#66d9ef">$(&lt;/span>cat &lt;span style="color:#e6db74">&amp;#34;&lt;/span>$CURRENT_KEYMAP_PATH&lt;span style="color:#e6db74">&amp;#34;&lt;/span>&lt;span style="color:#66d9ef">)&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"># Swap the keymaps&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#66d9ef">if&lt;/span> &lt;span style="color:#f92672">[[&lt;/span> &lt;span style="color:#e6db74">&amp;#34;&lt;/span>$CURRENT_KEYMAP&lt;span style="color:#e6db74">&amp;#34;&lt;/span> &lt;span style="color:#f92672">=&lt;/span> &lt;span style="color:#e6db74">&amp;#34;&lt;/span>$KEYMAP_LOCKED&lt;span style="color:#e6db74">&amp;#34;&lt;/span> &lt;span style="color:#f92672">]]&lt;/span>; &lt;span style="color:#66d9ef">then&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> cp -f &lt;span style="color:#e6db74">&amp;#34;&lt;/span>$KEYMAP_STICKY&lt;span style="color:#e6db74">&amp;#34;&lt;/span> &lt;span style="color:#e6db74">&amp;#34;&lt;/span>$KEYMAP_SWAY&lt;span style="color:#e6db74">&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> echo &lt;span style="color:#e6db74">&amp;#34;&lt;/span>$KEYMAP_STICKY&lt;span style="color:#e6db74">&amp;#34;&lt;/span> &amp;gt; &lt;span style="color:#e6db74">&amp;#34;&lt;/span>$CURRENT_KEYMAP_PATH&lt;span style="color:#e6db74">&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#66d9ef">else&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> cp -f &lt;span style="color:#e6db74">&amp;#34;&lt;/span>$KEYMAP_LOCKED&lt;span style="color:#e6db74">&amp;#34;&lt;/span> &lt;span style="color:#e6db74">&amp;#34;&lt;/span>$KEYMAP_SWAY&lt;span style="color:#e6db74">&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> echo &lt;span style="color:#e6db74">&amp;#34;&lt;/span>$KEYMAP_LOCKED&lt;span style="color:#e6db74">&amp;#34;&lt;/span> &amp;gt; &lt;span style="color:#e6db74">&amp;#34;&lt;/span>$CURRENT_KEYMAP_PATH&lt;span style="color:#e6db74">&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#66d9ef">fi&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"># Reload Sway configuration&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>swaymsg reload
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>Note that at first I tried to use the xkbcomp command but by all accounts only works on X11 so with wayland and in particular Sway I had to find a workaround to reload a toggled keymap - which is where swaymsg reload comes in. The solution is a little hacky but will work and involves swapping around files on disk and then forcing a reload of the sway configuration file.&lt;/p>
&lt;p>As always with toggling, the key is usually working out the current toggle state in order to know what to toggle to. I decided to use the old fashioned method of a file on disk containing the path to the current keymap!&lt;/p>
&lt;p>In the sway configuration file I can now attach a keybinding to the new toggle script :&lt;/p>
&lt;pre tabindex="0">&lt;code class="language-nil" data-lang="nil">bindsym $mod+y exec keymap-toggle.sh
&lt;/code>&lt;/pre>&lt;p>&amp;lsquo;y&amp;rsquo; for sticky seems appropriate methinks.&lt;/p>
&lt;p>Now for updating waybar to reflect the status. In this case as in the modifier LEDs of my last post I will have to create some custom modules and link it to a content producing bash script.&lt;/p>
&lt;p>A keymap_monitor.sh script is as follows:&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-bash" data-lang="bash">&lt;span style="display:flex;">&lt;span>&lt;span style="color:#75715e">#!/bin/bash
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#75715e">&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>CURRENT_KEYMAP_PATH&lt;span style="color:#f92672">=&lt;/span>~/.config/keymap_current
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>CURRENT_KEYMAP&lt;span style="color:#f92672">=&lt;/span>&lt;span style="color:#66d9ef">$(&lt;/span>cat &lt;span style="color:#e6db74">&amp;#34;&lt;/span>$CURRENT_KEYMAP_PATH&lt;span style="color:#e6db74">&amp;#34;&lt;/span> | grep &lt;span style="color:#e6db74">&amp;#34;sticky&amp;#34;&lt;/span>&lt;span style="color:#66d9ef">)&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:#66d9ef">if&lt;/span> &lt;span style="color:#f92672">[[&lt;/span> $CURRENT_KEYMAP &lt;span style="color:#f92672">]]&lt;/span>; &lt;span style="color:#66d9ef">then&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> echo &lt;span style="color:#e6db74">&amp;#34;{\&amp;#34;text\&amp;#34;: \&amp;#34;STICKY\&amp;#34;, \&amp;#34;class\&amp;#34;: \&amp;#34;active\&amp;#34;}&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#66d9ef">else&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> echo &lt;span style="color:#e6db74">&amp;#34;{\&amp;#34;text\&amp;#34;: \&amp;#34;LOCKED\&amp;#34;, \&amp;#34;class\&amp;#34;: \&amp;#34;inactive\&amp;#34;}&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#66d9ef">fi&lt;/span>
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>and the waybar module :&lt;/p>
&lt;pre tabindex="0">&lt;code class="language-nil" data-lang="nil"> &amp;#34;custom/togglesticky&amp;#34;: {
&amp;#34;exec&amp;#34;: &amp;#34;keymap-monitor.sh&amp;#34;,
&amp;#34;interval&amp;#34;: 1,
&amp;#34;return-type&amp;#34;: &amp;#34;json&amp;#34;,
&amp;#34;on-click&amp;#34;: &amp;#34;keymap-toggle.sh&amp;#34;,
}
&lt;/code>&lt;/pre>&lt;p>Note the return-type type is json and the echo statements in the keymap_monitor.sh script have formatted the return data in the json format meaning the waybar module can process the string effectively. Note also a css class is set up which will link to the associated waybar css style sheet and potentially offer a nice flexible look for each mode if desired.&lt;/p>
&lt;p>&amp;ldquo;on-click&amp;rdquo;: &amp;ldquo;keymap-toggle.sh&amp;rdquo; will allow the toggle to be actioned and the monitor script will be called every second as per the interval setting to poll the current toggle state.&lt;/p>
&lt;p>Well that should be about it!, when I now open up Krita I can just tap on the LOCKED indicator on waybar which will then display STICKY (I decided to go with STICKY rather than LATCHED) This now indicates that I have changed the keymap, at which point double tapping any modifier keys as part of my arting shenanigans will now not lock the associated modifier and cause annoyance down the line.&lt;/p>
&lt;figure>&lt;img src="https://www.emacs.dyerdwelling.family/emacs/20240414101304-emacs--Waybar-Toggling-Sticky-Key-Types/2024-04-14-10-55-19.jpg" width="100%">
&lt;/figure>
&lt;p>I might even eventually find some icons or my preferred method emojis to make things look more graphically pleasing, but with all these things I always initially look to get most of the way there and in a working state before I start polishing it.&lt;/p></description></item><item><title>Waybar Sticky Key LED indicators on a Laptop</title><link>https://www.emacs.dyerdwelling.family/emacs/20240323-emacs--waybar-sway-locked-sticky-indicators/</link><pubDate>Sat, 30 Mar 2024 16:43:00 +0000</pubDate><author>James Dyer</author><guid>https://www.emacs.dyerdwelling.family/emacs/20240323-emacs--waybar-sway-locked-sticky-indicators/</guid><description>&lt;p>From my previous post regarding setting up sticky keys mainly for &lt;strong>Control&lt;/strong>, &lt;strong>Alt&lt;/strong> and &lt;strong>Shift&lt;/strong> in Emacs to remove the dependence on key chording I just wanted to scratch another itch, and that was to provide a modifier LED indicator type experience on a laptop that you would commonly get on a full size keyboard.&lt;/p>
&lt;figure>&lt;img src="https://www.emacs.dyerdwelling.family/emacs/2024-03-30-10-27-49-banner.jpg" width="100%">
&lt;/figure>
&lt;p>For example most laptops typically have a single LED indicator on the Caps Lock key with a full size keyboard having Num Lock, Caps Lock and Scroll Lock specific LEDs and generally in that order.&lt;/p>
&lt;p>How would I accomplish this more granular display on a laptop that only has a single Caps Lock indicator LED?.&lt;/p>
&lt;p>My previous solution of activating the Caps Lock LED when any modifier was activated works to an extent. However, you still don&amp;rsquo;t know which specific modifier is active, so to clear a locked modifier you might still have to go hunting around the modifiers to find out which one has been activated.&lt;/p>
&lt;p>This so far is the only negative I have found using Sticky Keys in that occasionally a modifier key becomes accidentally locked (I suppose stuck!) and it takes a while to figure out which one.&lt;/p>
&lt;p>In a highly configurable environment like Sway where you pretty much have to build most things for yourself the flexibility offered can allow me to craft a software solution by modifying the bar to visually indicate the current locked modifiers. So when stickies get stuck a quick glance will tell you which modifiers are active and hence which ones to deactivate.&lt;/p>
&lt;p>For now I will just focus on &lt;strong>waybar&lt;/strong> which is a customizable bar for Wayland compositors although setting up something like Polybar for X11 would be very similar.&lt;/p>
&lt;p>I think the way to achieve this is to figure out how to access the current state of LED indicators which signify which sticky modifiers are set. Currently any modifier on my laptop activates the Caps Lock LED indicator and although specifically a hardware LED, each modifier can be stored separately, and through xkb (X Keyboard Extension) we can store each LED state in the following locations if set up correctly:&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-bash" data-lang="bash">&lt;span style="display:flex;">&lt;span>/sys/class/leds/input2::capslock
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>/sys/class/leds/input2::numlock
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>/sys/class/leds/input2::scrolllock
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>My first step is to split the modifiers, setting each internal device accordingly in &lt;strong>/sys/class/leds&lt;/strong>, so the original xkb file will change from:&lt;/p>
&lt;pre tabindex="0">&lt;code class="language-nil" data-lang="nil">indicator &amp;#34;Caps Lock&amp;#34; {
!allowExplicit;
whichModState= locked;
modifiers= Control+Mod1+Shift;
};
&lt;/code>&lt;/pre>&lt;p>to&lt;/p>
&lt;pre tabindex="0">&lt;code class="language-nil" data-lang="nil">indicator &amp;#34;Caps Lock&amp;#34; {
!allowExplicit;
whichModState= locked;
modifiers= Control;
};
indicator &amp;#34;Num Lock&amp;#34; {
!allowExplicit;
whichModState= locked;
modifiers= Mod1;
};
indicator &amp;#34;Scroll Lock&amp;#34; {
whichModState= locked;
modifiers= Shift;
};
&lt;/code>&lt;/pre>&lt;p>which is the way I would activate each LED for a full size keyboard.&lt;/p>
&lt;p>Next is to access each LED value so I can pass it through to waybar for display. I will have to access the LED states more directly, hence in linux pulling values straight from a directory called /sys/class/leds&lt;/p>
&lt;p>The following bash script will pass out a JSON formatted string which waybar will be able to process in a custom module:&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-bash" data-lang="bash">&lt;span style="display:flex;">&lt;span>&lt;span style="color:#75715e">#!/bin/bash
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#75715e">&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#75715e"># Define the paths to the LED brightness indicators&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>caps_lock_led&lt;span style="color:#f92672">=&lt;/span>&lt;span style="color:#e6db74">&amp;#34;/sys/class/leds/input2::capslock/brightness&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>num_lock_led&lt;span style="color:#f92672">=&lt;/span>&lt;span style="color:#e6db74">&amp;#34;/sys/class/leds/input2::numlock/brightness&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>scroll_lock_led&lt;span style="color:#f92672">=&lt;/span>&lt;span style="color:#e6db74">&amp;#34;/sys/class/leds/input2::scrolllock/brightness&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"># Function to output the JSON format for Waybar&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>output_json&lt;span style="color:#f92672">()&lt;/span> &lt;span style="color:#f92672">{&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> local text&lt;span style="color:#f92672">=&lt;/span>&lt;span style="color:#e6db74">&amp;#34;&lt;/span>$1&lt;span style="color:#e6db74">&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> local active&lt;span style="color:#f92672">=&lt;/span>&lt;span style="color:#e6db74">&amp;#34;&lt;/span>$2&lt;span style="color:#e6db74">&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#66d9ef">if&lt;/span> &lt;span style="color:#f92672">[&lt;/span> &lt;span style="color:#e6db74">&amp;#34;&lt;/span>$active&lt;span style="color:#e6db74">&amp;#34;&lt;/span> &lt;span style="color:#f92672">=&lt;/span> &lt;span style="color:#e6db74">&amp;#34;1&amp;#34;&lt;/span> &lt;span style="color:#f92672">]&lt;/span>; &lt;span style="color:#66d9ef">then&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> echo &lt;span style="color:#e6db74">&amp;#34;{\&amp;#34;text\&amp;#34;: \&amp;#34;&lt;/span>$text&lt;span style="color:#e6db74">\&amp;#34;, \&amp;#34;class\&amp;#34;: \&amp;#34;active\&amp;#34;}&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#66d9ef">else&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> echo &lt;span style="color:#e6db74">&amp;#34;{\&amp;#34;text\&amp;#34;: \&amp;#34;&lt;/span>$text&lt;span style="color:#e6db74">\&amp;#34;, \&amp;#34;class\&amp;#34;: \&amp;#34;inactive\&amp;#34;}&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#66d9ef">fi&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#f92672">}&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 the command-line argument and output the respective LED status&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#66d9ef">case&lt;/span> &lt;span style="color:#e6db74">&amp;#34;&lt;/span>$1&lt;span style="color:#e6db74">&amp;#34;&lt;/span> in
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> --caps&lt;span style="color:#f92672">)&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> caps_state&lt;span style="color:#f92672">=&lt;/span>&lt;span style="color:#66d9ef">$(&lt;/span>cat &lt;span style="color:#e6db74">&amp;#34;&lt;/span>$caps_lock_led&lt;span style="color:#e6db74">&amp;#34;&lt;/span>&lt;span style="color:#66d9ef">)&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> output_json &lt;span style="color:#e6db74">&amp;#34;Ctl&amp;#34;&lt;/span> &lt;span style="color:#e6db74">&amp;#34;&lt;/span>$caps_state&lt;span style="color:#e6db74">&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> --num&lt;span style="color:#f92672">)&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> num_state&lt;span style="color:#f92672">=&lt;/span>&lt;span style="color:#66d9ef">$(&lt;/span>cat &lt;span style="color:#e6db74">&amp;#34;&lt;/span>$num_lock_led&lt;span style="color:#e6db74">&amp;#34;&lt;/span>&lt;span style="color:#66d9ef">)&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> output_json &lt;span style="color:#e6db74">&amp;#34;Alt&amp;#34;&lt;/span> &lt;span style="color:#e6db74">&amp;#34;&lt;/span>$num_state&lt;span style="color:#e6db74">&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> --scroll&lt;span style="color:#f92672">)&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> scroll_state&lt;span style="color:#f92672">=&lt;/span>&lt;span style="color:#66d9ef">$(&lt;/span>cat &lt;span style="color:#e6db74">&amp;#34;&lt;/span>$scroll_lock_led&lt;span style="color:#e6db74">&amp;#34;&lt;/span>&lt;span style="color:#66d9ef">)&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> output_json &lt;span style="color:#e6db74">&amp;#34;Sft&amp;#34;&lt;/span> &lt;span style="color:#e6db74">&amp;#34;&lt;/span>$scroll_state&lt;span style="color:#e6db74">&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:#f92672">)&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> echo &lt;span style="color:#e6db74">&amp;#34;Usage: &lt;/span>$0&lt;span style="color:#e6db74"> [--caps | --num | --scroll]&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> exit &lt;span style="color:#ae81ff">1&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:#66d9ef">esac&lt;/span>
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>Each LED is associated with its own device, and conveniently, the brightness file is simply a text file containing either a 0 or 1 to indicate whether the LED is active or inactive. To facilitate integration with custom modules in Waybar, and to link them into its CSS I will need to output each module as JSON, assigning each key modifier a class set to either &amp;lsquo;active&amp;rsquo; or &amp;lsquo;inactive&amp;rsquo;.&lt;/p>
&lt;p>I will have to write a some custom waybar modules so in the associated modules.json :&lt;/p>
&lt;pre tabindex="0">&lt;code class="language-nil" data-lang="nil"> &amp;#34;custom/caps_lock&amp;#34;: {
&amp;#34;exec&amp;#34;: &amp;#34;led-monitor.sh --caps&amp;#34;,
&amp;#34;interval&amp;#34;: 1,
&amp;#34;return-type&amp;#34;: &amp;#34;json&amp;#34;
},
&amp;#34;custom/num_lock&amp;#34;: {
&amp;#34;exec&amp;#34;: &amp;#34;led-monitor.sh --num&amp;#34;,
&amp;#34;interval&amp;#34;: 1,
&amp;#34;return-type&amp;#34;: &amp;#34;json&amp;#34;
},
&amp;#34;custom/scroll_lock&amp;#34;: {
&amp;#34;exec&amp;#34;: &amp;#34;led-monitor.sh --scroll&amp;#34;,
&amp;#34;interval&amp;#34;: 1,
&amp;#34;return-type&amp;#34;: &amp;#34;json&amp;#34;
}
&lt;/code>&lt;/pre>&lt;p>Each button has to be a separate module with each having its own name and LED state which enables the CSS to access each LED individually.&lt;/p>
&lt;p>I have kept the styling simple for now but of course the style can be anything you like, so here is an associated CSS :&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-css" data-lang="css">&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#75715e">/* General LED styling */&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>#custom-caps_lock&lt;span style="color:#f92672">,&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>#custom-num_lock&lt;span style="color:#f92672">,&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>#custom-scroll_lock {
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#66d9ef">padding&lt;/span>: &lt;span style="color:#ae81ff">0&lt;/span>&lt;span style="color:#66d9ef">em&lt;/span> &lt;span style="color:#ae81ff">0.2&lt;/span>&lt;span style="color:#66d9ef">em&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>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#75715e">/* Active LED styling */&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>#custom-caps_lock.&lt;span style="color:#a6e22e">active&lt;/span>&lt;span style="color:#f92672">,&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>#custom-num_lock.&lt;span style="color:#a6e22e">active&lt;/span>&lt;span style="color:#f92672">,&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>#custom-scroll_lock.&lt;span style="color:#a6e22e">active&lt;/span> {
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#66d9ef">background-color&lt;/span>: &lt;span style="color:#f92672">@&lt;/span>recording-color;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>}
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>Here I have put a general padding on the right and left and then defined a simple highlight background colour for when the JSON script above returns active.&lt;/p>
&lt;p>These modules can be positioned anywhere along waybar, I typically like to have mine on the right as thus :&lt;/p>
&lt;figure>&lt;img src="https://www.emacs.dyerdwelling.family/emacs/20240323111726-emacs--Waybar-Sway-Locked-Sticky-Indicators/2024-03-30-10-27-10.jpg" width="100%">
&lt;/figure>
&lt;p>with modifier activation of Alt and Shift shown as:&lt;/p>
&lt;figure>&lt;img src="https://www.emacs.dyerdwelling.family/emacs/20240323111726-emacs--Waybar-Sway-Locked-Sticky-Indicators/2024-03-30-10-27-49.jpg" width="100%">
&lt;/figure>
&lt;p>So in the case above if my keys are doing weird things I can quickly glance at waybar and see that Alt and Shift are currently Sticky activated thus I can deactivate by selecting each to remove the visual indicators and carry on my merry little way!&lt;/p></description></item><item><title>Eliminating Key Chords in Emacs under Linux with Sticky Keys</title><link>https://www.emacs.dyerdwelling.family/emacs/20240309130457-emacs--kmonad-sway-kbd-map-locking/</link><pubDate>Fri, 22 Mar 2024 21:39:00 +0000</pubDate><author>James Dyer</author><guid>https://www.emacs.dyerdwelling.family/emacs/20240309130457-emacs--kmonad-sway-kbd-map-locking/</guid><description>&lt;p>I spend many hours living in emacs and a large proportion of this time is using a laptop. Recently I have been thinking about my hands and how to protect them from any future pain or RSI.&lt;/p>
&lt;figure>&lt;img src="https://www.emacs.dyerdwelling.family/emacs/20240309130457-emacs--kmonad-sway-kbd-map-locking.jpg" width="100%">
&lt;/figure>
&lt;p>Emacs out of the box can be a little, lets say &lt;del>tortuous&lt;/del> awkward. For example I&amp;rsquo;ve always found the default keybindings for switching windows (`C-x o`) and saving buffers (`C-x C-s`) to be particularly uncomfortable, a feeling exacerbated by their frequent use. Also, key presses in Emacs typically involve key chording, this means that not only do you have to contort your hand into awkward positions, but you also have to press keys simultaneously thus reducing the possibility for natural hand movement during key activation.&lt;/p>
&lt;p>The concept of C-x, especially for a Control key not mapped to the Caps Lock can really take its toll over a number of years; as of yet I am fortunate not to have yet experienced any hand pain but from what I&amp;rsquo;ve read, it&amp;rsquo;s not uncommon for discomfort to start to set in after many years of daily keyboard use, so I really want to take measures now.&lt;/p>
&lt;p>In a previous post I had started to discuss this topic :&lt;/p>
&lt;p>&lt;a href="https://www.emacs.dyerdwelling.family/emacs/20240308115556-emacs--sticky-keys-on-sway-using-kmonad/">Sticky Keys on Sway using kmonad&lt;/a>&lt;/p>
&lt;p>I attempted to use the cross platform tool &lt;strong>kmonad&lt;/strong> to apply Sticky Key functionality thus obviating the need for multiple key chord activation in emacs. For the most part a basic latched sticky concept works well and is easy to set up, however when using emacs I think a &lt;strong>locking&lt;/strong> sticky modifier would be a much better fit, which is where a modifier will be locked on a double tap and then unlocked with a single tap.&lt;/p>
&lt;p>For instance, if I could lock the Control key, navigating a buffer could be done with single key presses using &amp;rsquo;n&amp;rsquo;, &amp;lsquo;p&amp;rsquo;, &amp;lsquo;b&amp;rsquo;, and &amp;lsquo;f&amp;rsquo;. Deleting multiple lines, a task I frequently do, would then require only individual presses of the &amp;lsquo;k&amp;rsquo; key. To mark a selection, I could simply tap the space-bar and then use single navigation keys to expand the selection. A single tap of the Control key would then deactivate the lock.&lt;/p>
&lt;p>Unfortunately &lt;strong>kmonad&lt;/strong> doesn&amp;rsquo;t yet support this kind of sticky key locking functionality, however my attempts at finding a workaround led me to stumble into the murky arcane world of the x keyboard extension!&lt;/p>
&lt;p>An XKB (X Keyboard Extension) file format is essentially a descriptive language that allows you to define the behaviour of the keyboard on linux. This includes the mapping of physical keys to symbols (characters or functions), modifying the action of keys depending on other keys that are held down (modifiers like Shift, Ctrl, Alt) and was primarily developed for the X11 windowing system.&lt;/p>
&lt;p>I run a Wayland compositor called &lt;strong>Sway&lt;/strong> as my daily driver and it just so happens that Wayland is compatible with the x keyboard extension standard!&lt;/p>
&lt;p>Below are some instructions on how to set up Sway with Sticky keys to help eliminate the need for key chords in emacs. These instructions have been applied to the Sticky Keys section in the emacs wiki : &lt;a href="https://www.emacswiki.org/emacs/StickyModifiers#h5o-8">https://www.emacswiki.org/emacs/StickyModifiers#h5o-8&lt;/a>&lt;/p>
&lt;p>Note that the following instructions can also work for X11 desktop environments as the x keyboard extension has been the de facto standard for many years on X11 and in fact I have successfully set up Sticky Keys in the same manner for the i3 window manager.&lt;/p>
&lt;hr>
&lt;p>&lt;strong>Setting up Sticky Keys through XKB&lt;/strong>&lt;/p>
&lt;p>Firstly run the bash script below to create two sticky keyboard variant files:&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-bash" data-lang="bash">&lt;span style="display:flex;">&lt;span>&lt;span style="color:#75715e">#!/bin/bash
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#75715e">&lt;/span>DST&lt;span style="color:#f92672">=&lt;/span>$HOME/.config
&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"># Reset keyboard layout (to your preferred language)&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>setxkbmap gb
&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"># Apply sticky modifiers to a file&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>xkbcomp $DISPLAY -xkb - | &lt;span style="color:#ae81ff">\
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#ae81ff">&lt;/span> sed &lt;span style="color:#e6db74">&amp;#39;s|SetMods|LatchMods|g&amp;#39;&lt;/span> &amp;gt; &lt;span style="color:#ae81ff">\
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#ae81ff">&lt;/span> $DST/keymap_with_sticky_modifiers.xkb
&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"># Reset keyboard layout (to your preferred language)&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>setxkbmap gb
&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"># Apply locked modifiers to a file&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>xkbcomp $DISPLAY -xkb - | &lt;span style="color:#ae81ff">\
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#ae81ff">&lt;/span> sed &lt;span style="color:#e6db74">&amp;#39;s|SetMods|LatchMods|g&amp;#39;&lt;/span> | &lt;span style="color:#ae81ff">\
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#ae81ff">&lt;/span> sed &lt;span style="color:#e6db74">&amp;#39;s|,clearLocks);|,clearLocks,latchToLock);|g&amp;#39;&lt;/span> &amp;gt; &lt;span style="color:#ae81ff">\
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#ae81ff">&lt;/span> $DST/keymap_with_locked_modifiers.xkb
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;ul>
&lt;li>
&lt;p>keymap_with_sticky_modifiers.xkb - a latched sticky modifier setup in which a modifier is activated by a following key press.&lt;/p>
&lt;/li>
&lt;li>
&lt;p>keymap_with_locked_modifiers.xkb - a locking sticky modifier setup in which a modifier will be locked on a double tap and then unlocked with a single tap.&lt;/p>
&lt;/li>
&lt;/ul>
&lt;p>At this point you can activate Sticky Keys by loading the new xkb keymap in the Sway config as follows:&lt;/p>
&lt;pre tabindex="0">&lt;code class="language-nil" data-lang="nil">input &amp;#34;type:keyboard&amp;#34; {
xkb_file $HOME/.config/keymap_with_locked_modifiers.xkb
}
&lt;/code>&lt;/pre>&lt;p>Note that for X11 you can use the xkbcomp command as thus:&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-bash" data-lang="bash">&lt;span style="display:flex;">&lt;span>xkbcomp $HOME/.config/keymap_with_locked_modifiers.xkb $DISPLAY
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>Optionally modify the xkb file directly after generation to use the “Caps Lock” keyboard indicator LED for indicating a sticky modifier is active, like so :&lt;/p>
&lt;pre tabindex="0">&lt;code class="language-nil" data-lang="nil">indicator &amp;#34;Caps Lock&amp;#34; {
!allowExplicit;
whichModState= locked;
modifiers= Control+Mod1+Shift;
};
&lt;/code>&lt;/pre>&lt;p>This turns on the Caps Lock LED whenever a Control, Alt (Mod1) or Shift key is sticky locked giving a visual hint to any locked keys. Although useful on a laptop if you are using a full sized keyboard each modifier can be linked to its own indicator LED, for example:&lt;/p>
&lt;pre tabindex="0">&lt;code class="language-nil" data-lang="nil">indicator &amp;#34;Caps Lock&amp;#34; {
!allowExplicit;
whichModState= locked;
modifiers= Control;
};
indicator &amp;#34;Num Lock&amp;#34; {
!allowExplicit;
whichModState= locked;
modifiers= Mod1;
};
indicator &amp;#34;Scroll Lock&amp;#34; {
whichModState= locked;
modifiers= Shift;
};
&lt;/code>&lt;/pre>&lt;p>As part of this keyboard redefinition it might also be useful to remap the Caps Lock key to the Control modifier. One way to achieve this in Sway is to modify the config file as follows:&lt;/p>
&lt;pre tabindex="0">&lt;code class="language-nil" data-lang="nil">input type:keyboard {
xkb_file $HOME/.config/keymap_with_locked_modifiers.xkb
xkb_options ctrl:nocaps
}
&lt;/code>&lt;/pre>&lt;p>However, as we are redefining our keyboard layout we can directly modify the xkb configuration file in the following manner:&lt;/p>
&lt;p>replace:&lt;/p>
&lt;pre tabindex="0">&lt;code class="language-nil" data-lang="nil"> key &amp;lt;CAPS&amp;gt; { [ Caps_Lock ] };
&lt;/code>&lt;/pre>&lt;p>with&lt;/p>
&lt;pre tabindex="0">&lt;code class="language-nil" data-lang="nil"> key &amp;lt;CAPS&amp;gt; { [ Control_L ] };
&lt;/code>&lt;/pre>&lt;p>replace:&lt;/p>
&lt;pre tabindex="0">&lt;code class="language-nil" data-lang="nil"> modifier_map Lock { &amp;lt;CAPS&amp;gt; };
&lt;/code>&lt;/pre>&lt;p>with&lt;/p>
&lt;pre tabindex="0">&lt;code class="language-nil" data-lang="nil"> modifier_map Control { &amp;lt;CAPS&amp;gt; };
&lt;/code>&lt;/pre>&lt;p>For bonus points, and to ensure that the right Alt modifier functions like a regular Alt thereby distributing the keys more evenly between the left and right hands, perform the following changes:&lt;/p>
&lt;p>add:&lt;/p>
&lt;pre tabindex="0">&lt;code class="language-nil" data-lang="nil"> modifier_map Mod1 { &amp;lt;RALT&amp;gt; };
&lt;/code>&lt;/pre>&lt;p>set the definition of key &amp;lt;RALT&amp;gt; to:&lt;/p>
&lt;pre tabindex="0">&lt;code class="language-nil" data-lang="nil"> key &amp;lt;RALT&amp;gt; { [ Alt_L, Meta_L ] };
&lt;/code>&lt;/pre>&lt;hr>
&lt;p>With this setup, I can now effectively press single keys in succession to trigger functionalities in Emacs that would normally require key chording. Even pressing C-g has become naturally easier; I&amp;rsquo;m now pressing Ctrl, then not having to stretch my index finger across at the same time as my pinky is now released from the control key.&lt;/p></description></item><item><title>My Dotfile</title><link>https://www.emacs.dyerdwelling.family/emacs/20240314162853-emacs--my-dotfile/</link><pubDate>Thu, 14 Mar 2024 16:28:00 +0000</pubDate><author>James Dyer</author><guid>https://www.emacs.dyerdwelling.family/emacs/20240314162853-emacs--my-dotfile/</guid><description>&lt;p>Simply a link to my &lt;a href="static/emacs/emacs--init.html">emacs literate config file&lt;/a> 😀&lt;/p>
&lt;figure>&lt;img src="https://www.emacs.dyerdwelling.family/emacs/20240314162853-emacs--My-Dotfile.jpg" width="100%">
&lt;/figure></description></item><item><title>kmonad Sticky Keys on Sway to Help Prevent RSI</title><link>https://www.emacs.dyerdwelling.family/emacs/20240308115556-emacs--sticky-keys-on-sway-using-kmonad/</link><pubDate>Fri, 08 Mar 2024 20:48:00 +0000</pubDate><author>James Dyer</author><guid>https://www.emacs.dyerdwelling.family/emacs/20240308115556-emacs--sticky-keys-on-sway-using-kmonad/</guid><description>&lt;p>I primarily use Emacs on a laptop, and unfortunately, Emacs isn&amp;rsquo;t inherently designed for ergonomic use. Coupled with the less-than-ideal typing experience of continuous laptop use, I&amp;rsquo;m considering adopting preemptive measures to protect my hands from potential strain injuries, including the infamous emacs pinky and RSI.&lt;/p>
&lt;figure>&lt;img src="https://www.emacs.dyerdwelling.family/emacs/20240308115556-emacs--Sticky-Keys-on-Sway-using-kmonad.jpg" width="100%">
&lt;/figure>
&lt;p>My awareness of this issue heightened after watching numerous informative coding videos by Xah Lee, from which I&amp;rsquo;ve gleaned valuable tips on making simple yet effective adjustments to safeguard my hands.&lt;/p>
&lt;p>One recommended strategy was relocating the Ctrl key to the Caps Lock position, a common suggestion that I had already implemented a few years ago. Another was to use a mechanical or ergonomic keyboard, which I have not considered feasible for my needs as I have a strong preference for using a laptop, particularly for its versatility as a digital art tablet with a 2-in-1 design.&lt;/p>
&lt;p>Nonetheless, there&amp;rsquo;s another solution within my reach: Sticky Keys. Although I&amp;rsquo;ve been predominantly aware of Sticky Keys through the inconvenient pop-up in Windows triggered by prolonged keypresses, Xah Lee&amp;rsquo;s insights shed light on its potential benefits in alleviating the strain caused by emacs&amp;rsquo; key chord demands. My only concern is that my preferred Linux window manager, Sway, lacks built-in support for Sticky Keys, unlike Gnome and KDE.&lt;/p>
&lt;p>Fortunately, there is a tool I am already using right under my nose that I had overlooked and that is &lt;strong>kmonad&lt;/strong>.&lt;/p>
&lt;p>Just for clarification, Sticky Keys allow the user to perform key combinations by pressing keys in sequence rather than simultaneously hence splitting emacs key chords into individual keypresses.&lt;/p>
&lt;p>Here is a little summary of what &lt;strong>kmonad&lt;/strong> can do:&lt;/p>
&lt;blockquote>
&lt;p>KMonad offers advanced customization features such as layers, multi-tap, tap-hold, and much more. These features are usually available at the hardware level on the QMK-firmware enabled keyboards. However, KMonad allows you to enjoy such features in virtually any keyboard by low-level system manipulations.
&lt;br />
&lt;br />
KMonad lets you map any keyboard button to any keymap. Want to swap the useless Caps Lock key with the Escape key? Want to have your modifiers such as Shift and Control on your home row, without breaking your normal typing flow? Want a modifier that is combination of Alt + Ctrl + Super + Shift? You can do all of those and much more!&lt;/p>
&lt;/blockquote>
&lt;p>I am already using kmonad to map the caps lock key to ctrl but on reading the excellent tutorial file I realised that in the latest version, namely v0.4.2 &lt;span class="timestamp-wrapper">&lt;span class="timestamp">&amp;lt;2023-10-07 Sat&amp;gt;&lt;/span>&lt;/span>, it included sticky-key functionality!&lt;/p>
&lt;p>Here is a little kmonad documentation regarding sticky keys:&lt;/p>
&lt;blockquote>
&lt;p>KMonad also supports so called &amp;ldquo;sticky keys&amp;rdquo;. These are keys that will behave as if they were pressed after just tapping them. This behaviour wears off after the next button is pressed, which makes them ideal for things like a quick control or shift. For example, tapping a sticky and then pressing `abc&amp;rsquo; will result in `Abc&amp;rsquo;.
&lt;br />
&lt;br />
You can create these keys with the `sticky-key&amp;rsquo; keyword:
&lt;br />
&lt;br />
(defalias
slc (sticky-key 500 lctl))
&lt;br />
&lt;br />
The number after `sticky-key&amp;rsquo; is the timeout you want, in milliseconds. If a key is tapped and that time has passed, it won&amp;rsquo;t act like it&amp;rsquo;s pressed down when we receive the next keypress.
&lt;br />
&lt;br />
It is also possible to combine sticky keys. For example, to get a sticky shift+control you can do
&lt;br />
&lt;br />
(defalias
ssc (around
(sticky-key 500 lsft)
(sticky-key 500 lctl)))&lt;/p>
&lt;/blockquote>
&lt;p>The Sway kmonad configuration below will activate a sticky key for all my linux modifier keys along with mapping the caps lock to the ctrl key:&lt;/p>
&lt;pre tabindex="0">&lt;code class="language-nil" data-lang="nil">(defcfg
input (device-file &amp;#34;/dev/input/by-path/platform-i8042-serio-0-event-kbd&amp;#34;)
output (uinput-sink &amp;#34;My KMonad output&amp;#34;)
;; Comment this if you want unhandled events not to be emitted
fallthrough true
;; Set this to false to disable any command-execution in KMonad
allow-cmd true
)
(defsrc caps lctl lsft rsft lalt ralt lmet)
(deflayer mine
(sticky-key 2000 lctl)
(sticky-key 2000 lctl)
(sticky-key 2000 lsft)
(sticky-key 2000 rsft)
(sticky-key 2000 lalt)
scrlck
(sticky-key 2000 lmet))
&lt;/code>&lt;/pre>&lt;p>For us emacs types the configuration file also looks a bit lispy! 😀&lt;/p>
&lt;p>Kmonad is also cross platform so it will run on windows too and for my next post I&amp;rsquo;m going to look to replace my current Autohotkey setup in windows with kmonad and see if it works as well as it does in linux!&lt;/p></description></item><item><title>Unified Interface for Switching Contexts - Switch to Thing</title><link>https://www.emacs.dyerdwelling.family/emacs/20240305160708-emacs--unified-interface-for-switching-contexts/</link><pubDate>Tue, 05 Mar 2024 17:56:00 +0000</pubDate><author>James Dyer</author><guid>https://www.emacs.dyerdwelling.family/emacs/20240305160708-emacs--unified-interface-for-switching-contexts/</guid><description>&lt;p>Now I have ditched &lt;code>save-desktop&lt;/code> for &lt;code>recentf&lt;/code> which gives me a faster startup time but still being able to quickly access my most common files (i.e. those I have most recently opened) I have realised that I would like to have quick access to other Emacs resources after a startup.&lt;/p>
&lt;figure>&lt;img src="https://www.emacs.dyerdwelling.family/emacs/20240305160708-emacs--Unified-Interface-For-Switching-Contexts.jpg" width="100%">
&lt;/figure>
&lt;p>This has led me to creating a unified interface of sorts to switch to different contexts through a single keybinding. If you think that sounds vague, well you are correct!, I shall try and explain myself.&lt;/p>
&lt;hr>
&lt;p>&lt;code>save-desktop&lt;/code> has served me well for many many years as a quick method to call up my last session including recreating buffers in their last positions and thus repopulating the ibuffer list and re-enabling the default Emacs &lt;code>switch-to-buffer&lt;/code> mechanism.&lt;/p>
&lt;p>Unfortunately as I am now fiddling around with elisp it means that restarting Emacs has become much more commonplace and I don&amp;rsquo;t really want to wait around for &lt;code>save-desktop&lt;/code> to recreate itself after each reload. In addition there were the odd peculiarities that I could never seem to resolve.&lt;/p>
&lt;p>Out of the box &lt;code>recentf&lt;/code> has its own basic opening mechanism not tied into anything particularly modern with a list of files populated for selection in a separate buffer, but &lt;code>consult-recent-file&lt;/code> solves this by giving me a nice completing-read experience.&lt;/p>
&lt;p>So now I can recall my most recent files in a nice completing-read way how about preserving a familiar switching experience, I could bind &lt;code>consult-recent-file&lt;/code> to &lt;code>C-x b&lt;/code>, hence &lt;code>switch-to-buffer&lt;/code> for continuity, but then of course I can&amp;rsquo;t switch to buffer! should I have separate keybinding for &lt;code>consult-recent-file&lt;/code>?, well maybe, but what else would I like to quickly switch to?&lt;/p>
&lt;p>Well of course buffers! I shouldn&amp;rsquo;t get carried away without forgetting the Emacs staple switching mechanism and in fact &lt;code>switch-to-buffer&lt;/code> is perfectly fine and is completing-read enabled (I could also use &lt;code>consult-buffer&lt;/code>)&lt;/p>
&lt;p>Bookmarks! (can I start a sentence like that?). This is a very underrated feature that I have become more accustomed to using and of course the USP being that a bookmark can reference a file, info page, directory, or pretty much anything you would want to access in Emacs. Well that has a separate selection mechanism too via &lt;code>bookmark-bmenu-list&lt;/code> which displays a list of existing bookmarks in a separate buffer. Again the brilliant &lt;code>consult-bookmark&lt;/code> comes to the rescue converting the bookmark mechanism into a completing-read, but how do I access this list?, another keybinding?, well maybe.&lt;/p>
&lt;p>I am also one of those people that switches themes very often, like several times a day, well how do I do this?, &lt;code>load-theme&lt;/code>, &lt;code>customize-themes&lt;/code>?. Out of the box theme selecting has a separate selection mechanism too which displays a list of existing themes in a separate buffer. Again the brilliant &lt;code>consult-theme&lt;/code> comes to the rescue giving a nice completing-read selection mechanism, but how do I access this list?, another keybinding?, well maybe.&lt;/p>
&lt;p>So I have four contexts to switch to and I don&amp;rsquo;t want a single keybinding for each one so can I merge all of these contexts into a single completing-read under a single keybinding, with the completion mechanism of choice performing the heavy lifting to give me a fast unified method of quick switching? (and breathe! 😵‍💫), the answer of course is - &lt;em>&amp;ldquo;this is Emacs dummy, of course you can!&amp;rdquo;&lt;/em>.&lt;/p>
&lt;p>I thought this would also be a perfect opportunity to further my understanding of elisp by tackling this problem myself, which is how I came up with &lt;code>my/switch-to-thing&lt;/code>, which is described below:&lt;/p>
&lt;hr>
&lt;h2 id="my-switch-to-thing">my/switch-to-thing&lt;/h2>
&lt;p>The provided Emacs Lisp function `my/switch-to-thing` offers a unified interface within Emacs to quickly switch contexts. It enables a user to perform one of several actions based on the user&amp;rsquo;s selection from a prompted completion list. The specific actions that can be taken are:&lt;/p>
&lt;ol>
&lt;li>
&lt;p>&lt;strong>&lt;strong>Switching to an Open Buffer&lt;/strong>&lt;/strong>: If the selection matches the name of an open buffer, the function switches the current window to display that buffer.&lt;/p>
&lt;/li>
&lt;li>
&lt;p>&lt;strong>&lt;strong>Opening a Recent File&lt;/strong>&lt;/strong>: If the selection matches an entry in the list of recently opened files, that file is opened in the current window.&lt;/p>
&lt;/li>
&lt;li>
&lt;p>&lt;strong>&lt;strong>Jumping to a Bookmark&lt;/strong>&lt;/strong>: If the selection matches a bookmark&amp;rsquo;s name, Emacs navigates to that bookmark.&lt;/p>
&lt;/li>
&lt;li>
&lt;p>&lt;strong>&lt;strong>Changing the Emacs Theme&lt;/strong>&lt;/strong>: If the selection starts with the prefix &amp;ldquo;Theme: &amp;ldquo;, the function interprets the rest of the selection as the name of a theme. It then loads and applies that theme.&lt;/p>
&lt;/li>
&lt;/ol>
&lt;p>Here is a break down of its behavior:&lt;/p>
&lt;ul>
&lt;li>
&lt;p>&lt;strong>&lt;strong>Initialization&lt;/strong>&lt;/strong>: It first creates lists of current buffer names (`buffers`), recent files (`recent-files`), bookmark names (`bookmarks`), and available themes formatted with a &amp;ldquo;Theme: &amp;quot; prefix (`themes`). These lists are then concatenated into `all-options`, forming a comprehensive list of choices for the user.&lt;/p>
&lt;/li>
&lt;li>
&lt;p>&lt;strong>&lt;strong>User Input&lt;/strong>&lt;/strong>: It prompts the user with a &amp;ldquo;`Switch to:`&amp;rdquo; message using `completing-read`, offering autocomplete functionality based on the `all-options` list. This prompt also integrates with Emacs&amp;rsquo; `file-name-history` for enhanced usability.&lt;/p>
&lt;/li>
&lt;li>
&lt;p>&lt;strong>&lt;strong>Action Selection&lt;/strong>&lt;/strong>: Based on the user&amp;rsquo;s choice (`selection`), the function uses `pcase` to pattern-match and decide the action:&lt;/p>
&lt;ul>
&lt;li>If the selection is an open buffer, it switches to that buffer (`switch-to-buffer`).&lt;/li>
&lt;li>If the selection is a bookmark, it jumps to that bookmark (`bookmark-jump`).&lt;/li>
&lt;li>If the selection is prefixed with &amp;ldquo;Theme: &amp;ldquo;, it extracts the theme name, converts it back to a symbol, and loads that theme (`load-theme`).&lt;/li>
&lt;li>If none of the above conditions are met, it interprets the selection as a file path/name and attempts to open it (`find-file`).&lt;/li>
&lt;/ul>
&lt;/li>
&lt;/ul>
&lt;p>This function effectively encapsulates multiple navigation and customization actions within Emacs into a single command, streamlining the user&amp;rsquo;s workflow by providing a centralized interface for common tasks.&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/switch-to-thing ()
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#e6db74">&amp;#34;Switch to a buffer, open a recent file, jump to a bookmark, or change the theme from a unified interface.&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* ((buffers (&lt;span style="color:#a6e22e">mapcar&lt;/span> &lt;span style="color:#a6e22e">#&amp;#39;buffer-name&lt;/span> (&lt;span style="color:#a6e22e">buffer-list&lt;/span>)))
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> (recent-files recentf-list)
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> (bookmarks (bookmark-all-names))
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> (themes (custom-available-themes))
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> (all-options (&lt;span style="color:#a6e22e">append&lt;/span> buffers recent-files bookmarks
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> (&lt;span style="color:#a6e22e">mapcar&lt;/span> (lambda (theme) (&lt;span style="color:#a6e22e">concat&lt;/span> &lt;span style="color:#e6db74">&amp;#34;Theme: &amp;#34;&lt;/span> (&lt;span style="color:#a6e22e">symbol-name&lt;/span> theme))) themes)))
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> (selection (&lt;span style="color:#a6e22e">completing-read&lt;/span> &lt;span style="color:#e6db74">&amp;#34;Switch to: &amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> (lambda (str pred action)
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> (if (&lt;span style="color:#a6e22e">eq&lt;/span> action &lt;span style="color:#e6db74">&amp;#39;metadata&lt;/span>)
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#f92672">&amp;#39;&lt;/span>(metadata &lt;span style="color:#f92672">.&lt;/span> ((category &lt;span style="color:#f92672">.&lt;/span> file)))
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> (complete-with-action action all-options str pred)))
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#66d9ef">nil&lt;/span> &lt;span style="color:#66d9ef">t&lt;/span> &lt;span style="color:#66d9ef">nil&lt;/span> &lt;span style="color:#e6db74">&amp;#39;file-name-history&lt;/span>)))
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> (pcase selection
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> ((pred (lambda (sel) (&lt;span style="color:#a6e22e">member&lt;/span> sel buffers))) (switch-to-buffer selection))
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> ((pred (lambda (sel) (&lt;span style="color:#a6e22e">member&lt;/span> sel bookmarks))) (bookmark-jump selection))
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> ((pred (lambda (sel) (string-prefix-p &lt;span style="color:#e6db74">&amp;#34;Theme: &amp;#34;&lt;/span> sel)))
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> (load-theme (&lt;span style="color:#a6e22e">intern&lt;/span> (&lt;span style="color:#a6e22e">substring&lt;/span> selection (&lt;span style="color:#a6e22e">length&lt;/span> &lt;span style="color:#e6db74">&amp;#34;Theme: &amp;#34;&lt;/span>))) &lt;span style="color:#66d9ef">t&lt;/span>))
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> (_ (find-file selection)))))
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div></description></item><item><title>selected-window-accent-mode now on MELPA</title><link>https://www.emacs.dyerdwelling.family/emacs/20240208164549-emacs-selected-window-accent-mode-now-on-melpa/</link><pubDate>Fri, 09 Feb 2024 14:58:00 +0000</pubDate><author>James Dyer</author><guid>https://www.emacs.dyerdwelling.family/emacs/20240208164549-emacs-selected-window-accent-mode-now-on-melpa/</guid><description>&lt;p>The &lt;strong>selected-window-accent-mode&lt;/strong> is now present on &lt;strong>MELPA&lt;/strong> and is my first Emacs package!&lt;/p>
&lt;p>It is designed to visually distinguish the currently selected window by applying a unique accent color to its fringes, mode line, header line, and margins.&lt;/p>
&lt;figure>&lt;img src="https://www.emacs.dyerdwelling.family/emacs/selected-window-accent-mode-00.jpg" width="100%">
&lt;/figure>
&lt;p>See &lt;a href="https://github.com/captainflasmr/selected-window-accent-mode">https://github.com/captainflasmr/selected-window-accent-mode&lt;/a> for more information and examples.&lt;/p>
&lt;h2 id="whats-new">Whats New&lt;/h2>
&lt;p>&lt;strong>Version 0.6.0&lt;/strong>&lt;/p>
&lt;p>&lt;strong>DONE&lt;/strong> &lt;code>ISSUE #1&lt;/code> Do not apply highlighting when frame only contains 1 window when &lt;code>selected-window-accent-smart-borders&lt;/code> is set&lt;/p>
&lt;p>&lt;strong>DONE&lt;/strong> define accent color saturation adjustment&lt;/p>
&lt;p>&lt;strong>DONE&lt;/strong> define accent color darken adjustment&lt;/p>
&lt;p>&lt;strong>DONE&lt;/strong> highlight selected tab with same accent color&lt;/p>
&lt;p>&lt;strong>DONE&lt;/strong> add to MELPA&lt;/p>
&lt;h2 id="quick-start">Quick Start&lt;/h2>
&lt;p>To use left and bottom accent based on the themes highlight colour:&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 selected-window-accent-mode
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> :config (selected-window-accent-mode &lt;span style="color:#ae81ff">1&lt;/span>)
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> :custom
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> (selected-window-accent-fringe-thickness &lt;span style="color:#ae81ff">10&lt;/span>)
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> (selected-window-accent-custom-color &lt;span style="color:#66d9ef">nil&lt;/span>)
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> (selected-window-accent-mode-style &lt;span style="color:#e6db74">&amp;#39;subtle&lt;/span>))
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>OR define your own colour:&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 selected-window-accent-mode
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> :config (selected-window-accent-mode &lt;span style="color:#ae81ff">1&lt;/span>)
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> :custom
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> (selected-window-accent-fringe-thickness &lt;span style="color:#ae81ff">10&lt;/span>)
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> (selected-window-accent-custom-color &lt;span style="color:#e6db74">&amp;#34;#427900&amp;#34;&lt;/span>)
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> (selected-window-accent-mode-style &lt;span style="color:#e6db74">&amp;#39;subtle&lt;/span>))
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;h2 id="alternative-window-highlighting-packages">Alternative window highlighting packages&lt;/h2>
&lt;p>There exist a few Emacs packages that perform window highlighting but that don&amp;rsquo;t quite provide the feature set of selected-window-accent.&lt;/p>
&lt;p>selected-window-accent focusses more on clearly but non-intrusively highlighting the currently selected/focussed window by highlighting aspects of the window border without having to modify the appearance of non-selected windows, hence more akin to a tiling window manager.&lt;/p>
&lt;h3 id="dimmer">dimmer&lt;/h3>
&lt;p>&amp;ldquo;This package provides a minor mode that indicates which buffer is currently active by dimming the faces in the other buffers.&amp;rdquo;&lt;/p>
&lt;p>This is the closest in functionality to selected-window-accent, the difference being that dimmer dims non selected windows rather than accent the selected window.&lt;/p>
&lt;p>dimmer can be used in conjunction and will complement selected-window-accent to further enhance the emphasizing of the selected window.&lt;/p>
&lt;h3 id="hiwin">hiwin&lt;/h3>
&lt;p>&amp;ldquo;This package provides a minor-mode to change the background colour of the non active window.&amp;rdquo;&lt;/p>
&lt;p>It uses overlays to highlight non active windows, so is similar to dimmer but is less subtle in its highlighting mechanism and hasn&amp;rsquo;t been updated in excess of 10 years.&lt;/p>
&lt;h3 id="color-theme-buffer-local">color-theme-buffer-local&lt;/h3>
&lt;p>&amp;ldquo;This package lets you set a color-theme on a per-buffer basis.&amp;rdquo;&lt;/p>
&lt;p>Unlike dimmer and hiwin this package isn&amp;rsquo;t related to the concept of a selected window but more of defining different themes for different windows to distinguish them.&lt;/p>
&lt;h3 id="solaire-mode">solaire-mode&lt;/h3>
&lt;p>&amp;ldquo;This package is designed to visually distinguish &amp;ldquo;real&amp;rdquo; buffers (i.e. file-visiting code buffers where you do most of your work) from &amp;ldquo;unreal&amp;rdquo; buffers (like popups, sidebars, log buffers, terminals, etc) by giving the latter a slightly different &amp;ndash; often darker &amp;ndash; background&amp;rdquo;&lt;/p>
&lt;p>Unlike dimmer and hiwin this package isn&amp;rsquo;t related to the concept of a selected window but more of distinguishing between collections of IDE like elements within Emacs.&lt;/p>
&lt;h2 id="roadmap-improvements">Roadmap / Improvements&lt;/h2>
&lt;p>&lt;strong>TODO&lt;/strong> add darken desaturated and tab highlight examples to README&lt;/p>
&lt;p>&lt;strong>TODO&lt;/strong> define accent color hue adjustment&lt;/p>
&lt;p>&lt;strong>TODO&lt;/strong> define compensating margin&lt;/p>
&lt;p>&lt;strong>TODO&lt;/strong> Incorporate &lt;code>mode-line-active&lt;/code> and &lt;code>mode-line-inactive&lt;/code> somehow as this would make more sense especially in the &amp;lsquo;default mode.&lt;/p>
&lt;p>&lt;strong>TODO&lt;/strong> header-line not shown on window split - I have a funny feeling this could be very difficult, if not impossible!&lt;/p>
&lt;p>&lt;strong>TODO&lt;/strong> restore modeline height when switching between modes&lt;/p>
&lt;p>&lt;strong>TODO&lt;/strong> adjust the not selected-window margin to avoid little window navigation. disruption, hence translating a fringe pixel width to a number of margin characters, not quite sure how I am going to do this yet.&lt;/p>
&lt;p>&lt;strong>TODO&lt;/strong> excess selected-window disruption in header-line. (not sure I can do much about this)&lt;/p>
&lt;p>&lt;strong>TODO&lt;/strong> define which theme face attribute to use as the main accent color - Currently the default is to use the &lt;code>highlight&lt;/code> face&lt;/p>
&lt;p>&lt;strong>WATCH&lt;/strong> possible overheads of updating visual elements for each window?&lt;/p>
&lt;p>&lt;strong>WATCH&lt;/strong> careful with removing header-line on all windows, for example magit commit window and probably some others may need to add some logic depending on mode.&lt;/p></description></item><item><title>Winner Undo to Tab Bar History</title><link>https://www.emacs.dyerdwelling.family/emacs/20240114145517-emacs--winner-undo-to-tab-bar-history/</link><pubDate>Sat, 03 Feb 2024 10:56:00 +0000</pubDate><author>James Dyer</author><guid>https://www.emacs.dyerdwelling.family/emacs/20240114145517-emacs--winner-undo-to-tab-bar-history/</guid><description>&lt;p>Now I am using the &lt;code>tab-bar&lt;/code> workflow I noticed that &lt;code>winner-mode&lt;/code> was not working per tab in that the winner undo would return to the previous state of another tab, pretty annoying! 😕&lt;/p>
&lt;figure>&lt;img src="https://www.emacs.dyerdwelling.family/emacs/20240114145517-emacs--Winner-Undo-To-Tab-Bar-History.jpg" width="100%">
&lt;/figure>
&lt;p>But there is an easy fix, replace &lt;code>(winner-mode 1)&lt;/code> with &lt;code>(tab-bar-history-mode 1)&lt;/code>&lt;/p>
&lt;p>and for example I replaced my following keybindings:&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>(global-set-key (kbd &lt;span style="color:#e6db74">&amp;#34;M-u&amp;#34;&lt;/span>) &lt;span style="color:#e6db74">&amp;#39;winner-undo&lt;/span>)
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>(global-set-key (kbd &lt;span style="color:#e6db74">&amp;#34;M-i&amp;#34;&lt;/span>) &lt;span style="color:#e6db74">&amp;#39;winner-redo&lt;/span>)
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>with&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>(global-set-key (kbd &lt;span style="color:#e6db74">&amp;#34;M-u&amp;#34;&lt;/span>) &lt;span style="color:#e6db74">&amp;#39;tab-bar-history-back&lt;/span>)
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>(global-set-key (kbd &lt;span style="color:#e6db74">&amp;#34;M-i&amp;#34;&lt;/span>) &lt;span style="color:#e6db74">&amp;#39;tab-bar-history-forward&lt;/span>)
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>Ironically I had just been reading a related section in &lt;strong>Mastering Emacs&lt;/strong> and this configuration was recommended to avoid confusion and general vexation.&lt;/p>
&lt;p>I would also recommend increasing the number of tab bar history elements remembered, the default is 10 and I quickly found myself running out.&lt;/p>
&lt;p>Therefore I put in:&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 tab-bar-history-limit &lt;span style="color:#ae81ff">100&lt;/span>)
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div></description></item><item><title>Opening Frequently Used Files More Efficiently using consult</title><link>https://www.emacs.dyerdwelling.family/emacs/20240127113907-emacs--disabling-consult-preview-selectively/</link><pubDate>Sat, 27 Jan 2024 21:14:00 +0000</pubDate><author>James Dyer</author><guid>https://www.emacs.dyerdwelling.family/emacs/20240127113907-emacs--disabling-consult-preview-selectively/</guid><description>&lt;p>Periodically, I find myself in situations where I restart Emacs frequently, such as when tweaking my configuration or simply experimenting. During these phases, to achieve a swift start-up, I often resorted to clearing my &lt;strong>&lt;code>ibuffer&lt;/code>&lt;/strong> as I tended to have more of an automatic &lt;strong>&lt;code>desktop-save&lt;/code>&lt;/strong> type workflow for restoring Emacs sessions. However this would leave me with the task of manually reconstructing my previous session by reopening files.&lt;/p>
&lt;figure>&lt;img src="https://www.emacs.dyerdwelling.family/emacs/20240127113907-emacs--Disabling-Consult-Preview-Selectively.jpg" width="100%">
&lt;/figure>
&lt;p>There are of course &lt;strong>&lt;code>dired&lt;/code>&lt;/strong> functions, like &lt;strong>&lt;code>find-name-dired&lt;/code>&lt;/strong> to more quickly locate files and although I have a good grasp of where my files are, this process became somewhat repetitive and time-consuming.&lt;/p>
&lt;p>I realized that my sessions tend to involve the same set of files, sparking a need for a more streamlined approach.&lt;/p>
&lt;p>This lead me to transition to the built-in &lt;strong>&lt;code>recentf&lt;/code>&lt;/strong> and then &lt;strong>&lt;code>consult-recent-file&lt;/code>&lt;/strong> with &lt;code>(setq recentf-max-saved-items 200)&lt;/code> covering most of the files I would typically want to open.&lt;/p>
&lt;p>Emacs starts up now in under 2 seconds and wont have a dependency on the number of buffers I had open on a previous session. If I want to edit a recent file I can open &lt;strong>&lt;code>consult-recent-file&lt;/code>&lt;/strong> bringing up a &lt;code>completing-read&lt;/code> list of my recently opened files and because I have &lt;code>(savehist-mode 1)&lt;/code> my very recently opened files will always appear at the top - quite handy!&lt;/p>
&lt;p>The only aspect of &lt;strong>&lt;code>consult-recent-file&lt;/code>&lt;/strong> that I am not too keen on as with most consult functions is the file preview when traversing through the recent file list as it seems to be slowing down the opening process due to a series of perceptible delays. Is it possible to tweak this behaviour in Emacs? Absolutely! So far, I&amp;rsquo;ve incorporated the following consult-related packages:&lt;/p>
&lt;ul>
&lt;li>consult-recent-file&lt;/li>
&lt;li>consult-theme&lt;/li>
&lt;li>consult-outline&lt;/li>
&lt;li>consult-imenu&lt;/li>
&lt;/ul>
&lt;p>and I think the only preview that I require is &lt;strong>&lt;code>consult-theme&lt;/code>&lt;/strong> leading to the following configuration:&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>(consult-customize
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> consult-theme :preview-key &lt;span style="color:#f92672">&amp;#39;&lt;/span>(:debounce &lt;span style="color:#ae81ff">0.2&lt;/span> any)
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> consult-recent-file consult-outline consult-imenu consult-history :preview-key &lt;span style="color:#66d9ef">nil&lt;/span>)
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div></description></item><item><title>Emacs asynchronous copying using dired-async-mode</title><link>https://www.emacs.dyerdwelling.family/emacs/20240120084016-emacs--dired-async-mode/</link><pubDate>Sat, 20 Jan 2024 09:38:00 +0000</pubDate><author>James Dyer</author><guid>https://www.emacs.dyerdwelling.family/emacs/20240120084016-emacs--dired-async-mode/</guid><description>&lt;p>For a while now I&amp;rsquo;ve been using an &lt;code>rsync&lt;/code> based &lt;code>dired-copy&lt;/code> replacement for large copy asynchronous operations within emacs. It is not uncommon for me to want to copy large files in emacs and rather than waiting for the operation to finish I leveraged &lt;code>async-shell-command&lt;/code> to perform an rsync copy as thus:&lt;/p>
&lt;figure>&lt;img src="https://www.emacs.dyerdwelling.family/emacs/20240120084016-emacs--Dired-Async-Mode.jpg" width="100%">
&lt;/figure>
&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>(&lt;span style="color:#a6e22e">define-key&lt;/span> dired-mode-map (kbd &lt;span style="color:#e6db74">&amp;#34;C&amp;#34;&lt;/span>) &lt;span style="color:#e6db74">&amp;#39;my/rsync&lt;/span>)
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>(defun my/rsync (dest)
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#e6db74">&amp;#34;Rsync copy.&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> (&lt;span style="color:#a6e22e">list&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> (&lt;span style="color:#a6e22e">expand-file-name&lt;/span> (read-file-name &lt;span style="color:#e6db74">&amp;#34;rsync to:&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> (dired-dwim-target-directory)))))
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> (let ((files (dired-get-marked-files &lt;span style="color:#66d9ef">nil&lt;/span> current-prefix-arg))
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> (command &lt;span style="color:#e6db74">&amp;#34;rsync -arvz --progress --no-g &amp;#34;&lt;/span>))
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> (dolist (file files)
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> (setq command (&lt;span style="color:#a6e22e">concat&lt;/span> command (shell-quote-argument file) &lt;span style="color:#e6db74">&amp;#34; &amp;#34;&lt;/span>)))
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> (setq command (&lt;span style="color:#a6e22e">concat&lt;/span> command (shell-quote-argument dest)))
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> (async-shell-command command &lt;span style="color:#e6db74">&amp;#34;*rsync*&amp;#34;&lt;/span>)
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> (dired-unmark-all-marks)
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> (other-window &lt;span style="color:#ae81ff">1&lt;/span>)
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> (&lt;span style="color:#a6e22e">sleep-for&lt;/span> &lt;span style="color:#ae81ff">1&lt;/span>)
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> (dired-revert)
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> (revert-buffer &lt;span style="color:#66d9ef">nil&lt;/span> &lt;span style="color:#66d9ef">t&lt;/span> &lt;span style="color:#66d9ef">nil&lt;/span>)))
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>Generally this has worked pretty well for me and as a plus I could see the progress in a buffer named &lt;code>*rsync*&lt;/code> (although I would typically dismiss it quickly using &lt;code>winner-undo&lt;/code> or maybe adding something to &lt;code>display-buffer-alist&lt;/code>)&lt;/p>
&lt;p>After watching the always excellent and informative video from &lt;a href="https://www.youtube.com/@emacselements">Emacs Elements&lt;/a> - &lt;a href="https://www.youtube.com/watch?v=1jCNrpp_STM&amp;t=80s">Best Way to Sort and Play Videos Is with Emacs&lt;/a> it opened my eyes to a built-in feature of emacs 29, namely &lt;code>dired-async-mode&lt;/code> (Do dired actions asynchronously) which seems to pretty much do what it says.&lt;/p>
&lt;p>To activate, rather than having to map all those dired commands to async versions, the following just needs to be set up :&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>(dired-async-mode &lt;span style="color:#ae81ff">1&lt;/span>)
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>Now when a copy via dired is activated it will happen asynchronously! - and also with other dired commands like rename but really it is generally a copy that will take the time.&lt;/p>
&lt;p>Out of the box a normal copy in dired using &lt;code>C&lt;/code> activates the asynchronous task and the modeline displays something like the following in all windows :&lt;/p>
&lt;pre tabindex="0">&lt;code class="language-nil" data-lang="nil">[1 Async job(s) running]
&lt;/code>&lt;/pre>&lt;p>When finished the modeline updates with a copy complete message and then after three seconds disappears.&lt;/p>
&lt;p>Three seconds you say? well how do I know that?, well I played around a little with the modeline finish message as of course this can be configured, this was my initial attempt:&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/dired-async-mode-line-message (text face &lt;span style="color:#66d9ef">&amp;amp;rest&lt;/span> args)
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#e6db74">&amp;#34;Notify end of async operation in &lt;/span>&lt;span style="color:#e6db74">`mode-line&amp;#39;&lt;/span>&lt;span style="color:#e6db74">.&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> (let* ((my/message &lt;span style="color:#e6db74">&amp;#34;Dired Async has finished!!&amp;#34;&lt;/span>)
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> (mode-line-format (&lt;span style="color:#a6e22e">concat&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#e6db74">&amp;#34; &amp;#34;&lt;/span> (&lt;span style="color:#a6e22e">propertize&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> my/message
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#e6db74">&amp;#39;face&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#f92672">&amp;#39;&lt;/span>(:background &lt;span style="color:#e6db74">&amp;#34;#ff0000&amp;#34;&lt;/span> :foreground &lt;span style="color:#e6db74">&amp;#34;#ffffff&amp;#34;&lt;/span> :inherit bold)
&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> my/message)
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> (&lt;span style="color:#a6e22e">force-mode-line-update&lt;/span>)
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> (sit-for &lt;span style="color:#ae81ff">8&lt;/span>)
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> (&lt;span style="color:#a6e22e">force-mode-line-update&lt;/span>)))
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>(setq dired-async-message-function &lt;span style="color:#e6db74">&amp;#39;my/dired-async-mode-line-message&lt;/span>)
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>I just felt I could try something a little more prominent to indicate the end of a copy but in the end I kept the default setup.&lt;/p>
&lt;p>The only issue I have observed thus far is that on an initial emacs startup the modeline for all windows displays &lt;strong>[0 Async job(s) running]&lt;/strong> and only seems to disappear after the first copy. It is a little annoying and even with the use of the &lt;code>diminish&lt;/code> package my modeline can look a little cluttered. I might need to look at this.&lt;/p>
&lt;hr>
&lt;p>Update &lt;strong>&lt;span class="timestamp-wrapper">&lt;span class="timestamp">&amp;lt;2024-01-20 Sat 12:05&amp;gt;&lt;/span>&lt;/span>&lt;/strong>
Actually I do know why the above is happening, when I was messing around with the &lt;code>my/dired-async-mode-line-message&lt;/code> function above I turned on &lt;code>(dired-async--modeline-mode 1)&lt;/code> ooops! well now I have commented it out no modeline async message initially appears! - I thought I would just leave all this stuff in here 😀&lt;/p></description></item><item><title>My first emacs package - *selected-window-accent-mode*</title><link>https://www.emacs.dyerdwelling.family/emacs/20240106143432-emacs--selected-window-accent-mode/</link><pubDate>Sat, 06 Jan 2024 21:00:00 +0000</pubDate><author>James Dyer</author><guid>https://www.emacs.dyerdwelling.family/emacs/20240106143432-emacs--selected-window-accent-mode/</guid><description>&lt;p>From my previous two posts regarding defining a Selected Window Accent some interesting posts from &lt;a href="https://irreal.org/blog">irreal&lt;/a> arose, especially:&lt;/p>
&lt;p>&lt;a href="https://irreal.org/blog/?p=11867#comment-6354017310">Marking The Active Window&lt;/a>&lt;/p>
&lt;p>Where it looks like the simple &lt;code>mode-line-active&lt;/code> and &lt;code>mode-line-inactive&lt;/code> was a good way to indicate the current focussed window, and I do agree.&lt;/p>
&lt;p>However to practice my elisp and to create my first emacs package I thought I would bring together my preference which is to have a tiling window manager type of focus involving typically a border around the current window while leveraging the usual customization options that come with emacs.&lt;/p>
&lt;p>I have put it on &lt;a href="https://github.com/captainflasmr/selected-window-accent-mode">github/captainflasmr&lt;/a> for now and below is the &lt;strong>README&lt;/strong> for a package called &lt;strong>&lt;code>selected-window-accent-mode&lt;/code>&lt;/strong>&lt;/p>
&lt;p>It&amp;rsquo;s a bit rough around the edges (pardon the pun!) but I think it might be a good starting point for further improvements.&lt;/p>
&lt;hr>
&lt;h2 id="selected-window-accent-mode">selected-window-accent-mode&lt;/h2>
&lt;h3 id="summary">Summary&lt;/h3>
&lt;p>The Selected Window Accent Mode is an Emacs package designed to visually distinguish the currently selected window by applying a unique accent color to its fringes, mode line, header line, and margins.&lt;/p>
&lt;figure>&lt;img src="https://www.emacs.dyerdwelling.family/emacs/selected-window-accent-mode-00.jpg" width="100%">
&lt;/figure>
&lt;hr>
&lt;h3 id="quick-start--emacs-29">Quick Start (emacs 29)&lt;/h3>
&lt;p>Add the following to the emacs init for a tiling window manager feel (see image above):&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 selected-window-accent-mode
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> :vc (:fetcher github :repo &lt;span style="color:#e6db74">&amp;#34;captainflasmr/selected-window-accent-mode&amp;#34;&lt;/span>)
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> :custom
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> (selected-window-accent-fringe-thickness &lt;span style="color:#ae81ff">10&lt;/span>)
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> (selected-window-accent-custom-color &lt;span style="color:#e6db74">&amp;#34;#916941&amp;#34;&lt;/span>)
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> (selected-window-accent-mode-style &lt;span style="color:#e6db74">&amp;#39;tiling&lt;/span>))
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>(selected-window-accent-mode &lt;span style="color:#ae81ff">1&lt;/span>)
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;hr>
&lt;h3 id="installation">Installation&lt;/h3>
&lt;h4 id="use-package--emacs-29">use-package (emacs 29)&lt;/h4>
&lt;p>Put the following into your emacs init file:&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 selected-window-accent-mode
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> :vc (:fetcher github :repo &lt;span style="color:#e6db74">&amp;#34;captainflasmr/selected-window-accent-mode&amp;#34;&lt;/span>))
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;h4 id="use-package--melpa">use-package (MELPA)&lt;/h4>
&lt;ul>
&lt;li>TODO (see roadmap below)&lt;/li>
&lt;/ul>
&lt;h4 id="from-source">from source&lt;/h4>
&lt;p>Download the `.el` file and place it in your Emacs `load-path`.&lt;/p>
&lt;p>Then either manually load it or add it to your configuration to be loaded at startup.&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>(require &lt;span style="color:#e6db74">&amp;#39;selected-window-accent-mode&lt;/span>)
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;hr>
&lt;h3 id="usage">Usage&lt;/h3>
&lt;p>Interactively Toggle the mode on and off &lt;code>M-x selected-window-accent-mode&lt;/code>&lt;/p>
&lt;p>Interactively change the current style &lt;code>M-x switch-selected-window-accent-style&lt;/code> which will present a &lt;code>completing-read&lt;/code> selection in the minibuffer&lt;/p>
&lt;p>The styles that are currently supported :&lt;/p>
&lt;ul>
&lt;li>default&lt;/li>
&lt;li>tiling&lt;/li>
&lt;li>subtle&lt;/li>
&lt;/ul>
&lt;p>see &lt;strong>roadmap&lt;/strong> below for a description.&lt;/p>
&lt;p>Typically I have bound these two interactive functions to a new keymap where I keep all my emacs visual change functions.&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>(defvar my-win-keymap (&lt;span style="color:#a6e22e">make-sparse-keymap&lt;/span>))
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>(global-set-key (kbd &lt;span style="color:#e6db74">&amp;#34;M-o&amp;#34;&lt;/span>) my-win-keymap)
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>(&lt;span style="color:#a6e22e">define-key&lt;/span> my-win-keymap (kbd &lt;span style="color:#e6db74">&amp;#34;a&amp;#34;&lt;/span>) &lt;span style="color:#e6db74">&amp;#39;selected-window-accent-mode&lt;/span>)
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>(&lt;span style="color:#a6e22e">define-key&lt;/span> my-win-keymap (kbd &lt;span style="color:#e6db74">&amp;#34;y&amp;#34;&lt;/span>) &lt;span style="color:#e6db74">&amp;#39;switch-selected-window-accent-style&lt;/span>)
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;hr>
&lt;h3 id="examples">Examples&lt;/h3>
&lt;h4 id="example-1-default-custom-color">Example 1 - Default / custom color&lt;/h4>
&lt;figure>&lt;img src="https://www.emacs.dyerdwelling.family/emacs/selected-window-accent-mode-01.jpg" width="100%">
&lt;/figure>
&lt;p>To enable the accent mode automatically upon starting Emacs, add the following line to your `.emacs` or `init.el` file:&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 selected-window-accent-mode
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> :vc (:fetcher github :repo &lt;span style="color:#e6db74">&amp;#34;captainflasmr/selected-window-accent-mode&amp;#34;&lt;/span>)
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> :custom
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> (selected-window-accent-custom-color &lt;span style="color:#e6db74">&amp;#34;goldenrod&amp;#34;&lt;/span>)
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> (selected-window-accent-mode-style &lt;span style="color:#e6db74">&amp;#39;default&lt;/span>))
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>(selected-window-accent-mode &lt;span style="color:#ae81ff">1&lt;/span>)
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>This will accent the modeline only for the selected window with the &lt;code>goldenrod&lt;/code> color.&lt;/p>
&lt;hr>
&lt;h4 id="example-2-tiling-custom-color-custom-fringe-thickness">Example 2 - Tiling / custom color / custom fringe thickness&lt;/h4>
&lt;figure>&lt;img src="https://www.emacs.dyerdwelling.family/emacs/selected-window-accent-mode-02.jpg" width="100%">
&lt;/figure>
&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 selected-window-accent-fringe-thickness &lt;span style="color:#ae81ff">6&lt;/span>)
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>(setq selected-window-accent-custom-color &lt;span style="color:#e6db74">&amp;#34;#4179b2&amp;#34;&lt;/span>)
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>(setq selected-window-accent-mode-style &lt;span style="color:#e6db74">&amp;#39;tiling&lt;/span>)
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>(selected-window-accent-mode &lt;span style="color:#ae81ff">1&lt;/span>)
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>This will accent the full outline of the window with the color #4175b2 more akin to a tiling window manager.&lt;/p>
&lt;hr>
&lt;h4 id="example-3-tiling-theme-highlight-color">Example 3 - Tiling / theme highlight color&lt;/h4>
&lt;figure>&lt;img src="https://www.emacs.dyerdwelling.family/emacs/selected-window-accent-mode-03.jpg" width="100%">
&lt;/figure>
&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 selected-window-accent-custom-color &lt;span style="color:#66d9ef">nil&lt;/span>)
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>(setq selected-window-accent-mode-style &lt;span style="color:#e6db74">&amp;#39;tiling&lt;/span>)
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>(selected-window-accent-mode &lt;span style="color:#ae81ff">1&lt;/span>)
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>This will accent the full outline of the window with the &lt;code>highlight&lt;/code> color taken from the current theme.&lt;/p>
&lt;hr>
&lt;h4 id="example-4-subtle-custom-fringe-thickness--thick">Example 4 - Subtle / custom fringe thickness (thick)&lt;/h4>
&lt;figure>&lt;img src="https://www.emacs.dyerdwelling.family/emacs/selected-window-accent-mode-04.jpg" width="100%">
&lt;/figure>
&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 selected-window-accent-fringe-thickness &lt;span style="color:#ae81ff">40&lt;/span>)
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>(setq selected-window-accent-custom-color &lt;span style="color:#66d9ef">nil&lt;/span>)
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>(setq selected-window-accent-mode-style &lt;span style="color:#e6db74">&amp;#39;subtle&lt;/span>)
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>(selected-window-accent-mode &lt;span style="color:#ae81ff">1&lt;/span>)
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>This will accent the modeline and just the left fringe and in this case be quite a pronounced thick accent.&lt;/p>
&lt;hr>
&lt;h3 id="customization">Customization&lt;/h3>
&lt;p>Can be done through the customization interface:&lt;/p>
&lt;p>&lt;strong>Selected Window Accent Group group:&lt;/strong>&lt;/p>
&lt;p>Customization group for the selected-window-accent package.&lt;/p>
&lt;p>&lt;strong>Selected Window Accent Custom Color&lt;/strong>&lt;/p>
&lt;p>Custom accent color for the selected window. Set this variable to change the accent color.&lt;/p>
&lt;ul>
&lt;li>&lt;code>None&lt;/code> - color will be using the current &lt;code>highlight&lt;/code> face&lt;/li>
&lt;li>&lt;code>Custom Color&lt;/code> - input color name or Hex&lt;/li>
&lt;/ul>
&lt;p>&lt;strong>Selected Window Accent Fringe Thickness:&lt;/strong> Integer:&lt;/p>
&lt;p>The thickness of the fringes in pixels.&lt;/p>
&lt;p>&lt;strong>Selected Window Accent Mode&lt;/strong>: &lt;code>Boolean&lt;/code>: Toggle&lt;/p>
&lt;p>Non-nil if Selected-Window-Accent mode is enabled&lt;/p>
&lt;p>&lt;strong>Selected Window Accent Mode Style&lt;/strong>&lt;/p>
&lt;p>Current style for accenting the selected window.&lt;/p>
&lt;ul>
&lt;li>&lt;code>default&lt;/code> - just modeline accent&lt;/li>
&lt;li>&lt;code>tiling&lt;/code> - window border accent&lt;/li>
&lt;li>&lt;code>subtle&lt;/code> - left and modeline accent&lt;/li>
&lt;/ul>
&lt;hr>
&lt;h3 id="minor-mode">Minor Mode&lt;/h3>
&lt;p>The &lt;code>selected-window-accent-mode&lt;/code> is a global minor mode that you can toggle to enable or disable the accenting of the selected window.&lt;/p>
&lt;p>When enabled, it distinguishes the selected window with a special accent color.&lt;/p>
&lt;hr>
&lt;h3 id="hooks">Hooks&lt;/h3>
&lt;p>Two hooks are used to automatically update the window accents when the window configuration or state changes:&lt;/p>
&lt;ul>
&lt;li>window-configuration-change-hook&lt;/li>
&lt;li>window-state-change-hook&lt;/li>
&lt;/ul>
&lt;p>These are added when the &lt;code>selected-window-accent-mode&lt;/code> is enabled and removed when disabled.&lt;/p>
&lt;hr>
&lt;h3 id="bugs">BUGS&lt;/h3>
&lt;p>The current version is pretty rough and probably definitely pre-alpha.&lt;/p>
&lt;p>Fix these to get to a tagged Version 0.1.&lt;/p>
&lt;p>In order of priority&lt;/p>
&lt;ul>
&lt;li>&lt;strong>TODO&lt;/strong> header-line not shown on window split.&lt;/li>
&lt;li>&lt;strong>TODO&lt;/strong> adjust the not selected-window margin to avoid little window navigation. disruption, hence translating a fringe pixel width to a number of margin characters, not quite sure how I am going to do this yet.&lt;/li>
&lt;li>&lt;strong>TODO&lt;/strong> Incorporate &lt;code>mode-line-active&lt;/code> and &lt;code>mode-line-inactive&lt;/code> somehow as this would make more sense especially in the &amp;lsquo;default mode.&lt;/li>
&lt;li>&lt;strong>TODO&lt;/strong> excess selected-window disruption in header-line.&lt;/li>
&lt;li>&lt;strong>WATCHING&lt;/strong> careful with removing header-line on all windows, for example magit commit window and probably some others may need to add some logic depending on mode.&lt;/li>
&lt;/ul>
&lt;hr>
&lt;h3 id="roadmap">ROADMAP&lt;/h3>
&lt;h4 id="1-dot-add-to-melpa">1. add to MELPA&lt;/h4>
&lt;h4 id="2-dot-define-more-custom-variables">2. define more custom variables:&lt;/h4>
&lt;ul>
&lt;li>accent color saturation adjustment&lt;/li>
&lt;li>accent color darken adjustment&lt;/li>
&lt;li>accent color hue adjustment&lt;/li>
&lt;/ul>
&lt;h4 id="3-dot-define-which-theme-face-attribute-to-use-as-the-main-accent-color">3. define which theme face attribute to use as the main accent color&lt;/h4>
&lt;p>Currently the default is to use the &lt;code>highlight&lt;/code> face&lt;/p>
&lt;h4 id="4-dot-doing-implement-accent-styles">4. &lt;strong>DOING&lt;/strong> implement accent styles&lt;/h4>
&lt;ul>
&lt;li>&lt;strong>DONE&lt;/strong> &lt;code>default&lt;/code> - &lt;em>bottom&lt;/em> - full height modeline&lt;/li>
&lt;li>&lt;strong>DOING&lt;/strong> &lt;code>tiling&lt;/code> - &lt;em>top/right/bottom/left&lt;/em> - typically a squished modeline and header line to a general accent thickness to provide a typical tiling window manager focussed outline experience&lt;/li>
&lt;li>&lt;strong>DOING&lt;/strong> &lt;code>subtle&lt;/code> - &lt;em>left/bottom&lt;/em>&lt;/li>
&lt;li>&lt;strong>TODO&lt;/strong> &lt;code>full&lt;/code> - &lt;em>top/right/bottom/left&lt;/em> - full height modeline (currently implemented as &lt;code>tiling&lt;/code> but will be moved when tiling is more &amp;ldquo;tiling&amp;rdquo;)&lt;/li>
&lt;/ul></description></item><item><title>Improvements to Selected Window Accent</title><link>https://www.emacs.dyerdwelling.family/emacs/20231230172829-emacs--selected-window-accent-improvements/</link><pubDate>Mon, 01 Jan 2024 21:12:00 +0000</pubDate><author>James Dyer</author><guid>https://www.emacs.dyerdwelling.family/emacs/20231230172829-emacs--selected-window-accent-improvements/</guid><description>&lt;p>Given my previous post regarding accenting the current window:&lt;/p>
&lt;p>&lt;a href="https://www.emacs.dyerdwelling.family/emacs/20231221210441-emacs--selected-window-accent/">Selected Window Accent&lt;/a>&lt;/p>
&lt;blockquote>
&lt;p>&amp;ldquo;This routine provides a coloured left fringe accent on the selected window to emphasize the current working window.&amp;rdquo;&lt;/p>
&lt;/blockquote>
&lt;p>I had a little issue with &lt;code>visual-fill-column-mode&lt;/code> in that :&lt;/p>
&lt;blockquote>
&lt;p>Note that I also had to set the margins to make the window traversal less visually disturbing and it seems to affect &lt;code>visual-fill-column-mode&lt;/code> in the fact that it doesn&amp;rsquo;t work, but maybe I can just try and not use it or eventually figure it out, but for now this is a starting point and I&amp;rsquo;m sure I will refine it over time.&lt;/p>
&lt;/blockquote>
&lt;p>With some trial and error the issue was that my new accent routine applied margins that overrode &lt;code>visual-fill-column-mode&lt;/code>, causing display issues on window navigation.&lt;/p>
&lt;p>So to fix I just re-activated &lt;code>visual-fill-column-mode&lt;/code> if it was active.&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 selected-window-accent ()
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> (interactive)
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> (set-face-background &lt;span style="color:#e6db74">&amp;#39;fringe&lt;/span> &lt;span style="color:#e6db74">&amp;#34;#77002e&amp;#34;&lt;/span>)
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> (walk-windows
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> (lambda (window)
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> (if (&lt;span style="color:#a6e22e">eq&lt;/span> window (&lt;span style="color:#a6e22e">selected-window&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> (&lt;span style="color:#a6e22e">set-window-margins&lt;/span> window &lt;span style="color:#ae81ff">1&lt;/span> &lt;span style="color:#ae81ff">0&lt;/span>)
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> (with-selected-window window
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> (if (&lt;span style="color:#a6e22e">eq&lt;/span> visual-fill-column-mode &lt;span style="color:#66d9ef">t&lt;/span>)
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> (visual-fill-column-mode &lt;span style="color:#66d9ef">t&lt;/span>)))
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> (&lt;span style="color:#a6e22e">set-window-fringes&lt;/span> window &lt;span style="color:#ae81ff">10&lt;/span> &lt;span style="color:#ae81ff">0&lt;/span> &lt;span style="color:#66d9ef">t&lt;/span> &lt;span style="color:#66d9ef">nil&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> (&lt;span style="color:#a6e22e">set-window-margins&lt;/span> window &lt;span style="color:#ae81ff">2&lt;/span> &lt;span style="color:#ae81ff">0&lt;/span>)
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> (with-selected-window window
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> (if (&lt;span style="color:#a6e22e">eq&lt;/span> visual-fill-column-mode &lt;span style="color:#66d9ef">t&lt;/span>)
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> (visual-fill-column-mode &lt;span style="color:#66d9ef">t&lt;/span>)))
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> (&lt;span style="color:#a6e22e">set-window-fringes&lt;/span> window &lt;span style="color:#ae81ff">0&lt;/span> &lt;span style="color:#ae81ff">0&lt;/span> &lt;span style="color:#66d9ef">t&lt;/span> &lt;span style="color:#66d9ef">nil&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>&lt;/span>&lt;span style="display:flex;">&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>(add-hook &lt;span style="color:#e6db74">&amp;#39;window-configuration-change-hook&lt;/span> &lt;span style="color:#e6db74">&amp;#39;selected-window-accent&lt;/span>)
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>(add-hook &lt;span style="color:#e6db74">&amp;#39;window-state-change-hook&lt;/span> &lt;span style="color:#e6db74">&amp;#39;selected-window-accent&lt;/span>)
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;figure>&lt;img src="https://www.emacs.dyerdwelling.family/emacs/20231221210441-emacs--Selected-Window-Accent-2.jpg" width="100%">
&lt;/figure></description></item><item><title>Selected Window Accent</title><link>https://www.emacs.dyerdwelling.family/emacs/20231221210441-emacs--selected-window-accent/</link><pubDate>Fri, 22 Dec 2023 20:55:00 +0000</pubDate><author>James Dyer</author><guid>https://www.emacs.dyerdwelling.family/emacs/20231221210441-emacs--selected-window-accent/</guid><description>&lt;p>Borrowing from the concept of a tiling window manager I thought that emacs could better indicate the currently selected window by a form of highlight/emphasis like the following:&lt;/p>
&lt;figure>&lt;img src="https://www.emacs.dyerdwelling.family/emacs/20231221210441-emacs--Selected-Window-Accent-2.jpg" width="100%">
&lt;/figure>
&lt;blockquote>
&lt;p>&amp;ldquo;This routine provides a coloured left fringe accent on the selected window to emphasize the current working window.&amp;rdquo;&lt;/p>
&lt;/blockquote>
&lt;p>Since I have been using more of an emacs tab-bar based work-flow I found myself spending a second or two looking for my current window when switching tabs. I would therefore like to more clearly visually identify the current working window (i.e, which window has my cursor in it) by the aid of some kind of accent or emphasis as is often the case in a tiling window manager. A blinking cursor could help with this, but I tend to prefer a non blinking block and I find &lt;code>hl-line-mode&lt;/code> too distracting.&lt;/p>
&lt;p>I thought I would just cobble together a proof of concept, nothing fancy, programatically inefficient, but hopefully an effective solution that works well enough while adhering to the KISS principle.&lt;/p>
&lt;hr>
&lt;p>There are packages out there, for example &lt;code>pulsar&lt;/code> and &lt;code>beacon&lt;/code> for pulsing or emphasizing the cursor area when window navigating or &lt;code>auto-dim-other-buffers&lt;/code> which face dims aspects of the entire window set for emphasis and actually I like its description:&lt;/p>
&lt;blockquote>
&lt;p>The ‘auto-dim-other-buffers-mode’ is a global minor mode which makes windows
without focus less prominent. With many windows in a frame, the idea is that
this mode helps recognise which is the selected window by providing
a non-intrusive but still noticeable visual indicator.&lt;/p>
&lt;/blockquote>
&lt;p>which is pretty much what I am looking to achieve but by using an accent rather than dimming.&lt;/p>
&lt;p>So the solution?, overlays didn&amp;rsquo;t seem to fit and was more character point font based where I was initially looking to render some form of coloured graphical border overlay which is typically apparent in a standard tiling manager.&lt;/p>
&lt;p>After a little investigation I decided on a simple (quick and dirty) solution and that is to utilise the left hand fringe.&lt;/p>
&lt;p>I generally don&amp;rsquo;t ever have fringes in my emacs session and in fact set &lt;code>(set-fringe-mode '(0 . 0))&lt;/code>.&lt;/p>
&lt;p>The function &lt;code>set-fringe-margins&lt;/code> allows a left and right pixel width value to be set for an individual window which is a good starting point. After a little investigation and trial and error I came up with the following which seemed to work well for me:&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/selected-window-accent ()
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> (interactive)
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> (set-face-background &lt;span style="color:#e6db74">&amp;#39;fringe&lt;/span> &lt;span style="color:#e6db74">&amp;#34;#77002e&amp;#34;&lt;/span>)
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> (walk-windows
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> (lambda (window)
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> (if (&lt;span style="color:#a6e22e">eq&lt;/span> window (&lt;span style="color:#a6e22e">selected-window&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> (&lt;span style="color:#a6e22e">set-window-margins&lt;/span> window &lt;span style="color:#ae81ff">1&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:#a6e22e">set-window-fringes&lt;/span> window &lt;span style="color:#ae81ff">16&lt;/span> &lt;span style="color:#ae81ff">0&lt;/span> &lt;span style="color:#66d9ef">t&lt;/span> &lt;span style="color:#66d9ef">nil&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> (&lt;span style="color:#a6e22e">set-window-margins&lt;/span> window &lt;span style="color:#ae81ff">3&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:#a6e22e">set-window-fringes&lt;/span> window &lt;span style="color:#ae81ff">0&lt;/span> &lt;span style="color:#ae81ff">0&lt;/span> &lt;span style="color:#66d9ef">t&lt;/span> &lt;span style="color:#66d9ef">nil&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>&lt;/span>&lt;span style="display:flex;">&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>(add-hook &lt;span style="color:#e6db74">&amp;#39;window-state-change-hook&lt;/span> &lt;span style="color:#e6db74">&amp;#39;my/selected-window-accent&lt;/span>)
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>On a window change notification (for example, standard window navigation) all windows are traversed with the current window explicitly displaying the left fringe only to a defined colour. All the other non selected windows have their fringes removed or in this case the pixel sizes set to zero.&lt;/p>
&lt;p>What we end up with here is a window accent on the left hand side of the selected window and when in combination with potentially a sensible mode-line configuration (maybe in the same accent colour) I think will serve as a decent visual window indicator. The width and colour can be simply modified to taste within the function itself, this is a very simple solution.&lt;/p>
&lt;p>Note that I also had to set the margins to make the window traversal less visually disturbing and it seems to affect &lt;code>visual-fill-column-mode&lt;/code> in the fact that it doesn&amp;rsquo;t work, but maybe I can just try and not use it or eventually figure it out, but for now this is a starting point and I&amp;rsquo;m sure I will refine it over time.&lt;/p></description></item><item><title>Redefining mark-paragraph and mark-word</title><link>https://www.emacs.dyerdwelling.family/emacs/20231209092556-emacs--redefining-mark-paragraph-and-mark-word/</link><pubDate>Sat, 09 Dec 2023 11:50:00 +0000</pubDate><author>James Dyer</author><guid>https://www.emacs.dyerdwelling.family/emacs/20231209092556-emacs--redefining-mark-paragraph-and-mark-word/</guid><description>&lt;p>I can&amp;rsquo;t say that I&amp;rsquo;m completely happy with the way emacs marks some elements, namely :&lt;/p>
&lt;hr>
&lt;div class="ox-hugo-toc toc local">
&lt;ul>
&lt;li>&lt;a href="https://www.emacs.dyerdwelling.family/emacs/20231209092556-emacs--redefining-mark-paragraph-and-mark-word/#mark-paragraph">mark-paragraph&lt;/a>&lt;/li>
&lt;li>&lt;a href="https://www.emacs.dyerdwelling.family/emacs/20231209092556-emacs--redefining-mark-paragraph-and-mark-word/#mark-word">mark-word&lt;/a>&lt;/li>
&lt;/ul>
&lt;/div>
&lt;!--endtoc-->
&lt;hr>
&lt;h2 id="mark-paragraph">mark-paragraph&lt;/h2>
&lt;p>The default mark-paragraph visually selects a paragraph as follows:&lt;/p>
&lt;figure>&lt;img src="https://www.emacs.dyerdwelling.family/emacs/20231209092556-emacs--Redefining-Mark-Paragraph-and-Mark-Word/2023-12-09-09-45-35.jpg" width="100%">
&lt;/figure>
&lt;p>but I think I would rather have the cursor show at the bottom of the selected paragraph, it just feels more natural to me, so as thus:&lt;/p>
&lt;figure>&lt;img src="https://www.emacs.dyerdwelling.family/emacs/20231209092556-emacs--Redefining-Mark-Paragraph-and-Mark-Word/2023-12-09-09-46-26.jpg" width="100%">
&lt;/figure>
&lt;p>To achieve this I wrote the following with a rebind to the default &lt;code>mark-paragraph&lt;/code> keybinding or in &lt;code>orgs&lt;/code> case the &lt;code>mark-element&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>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>(defun my/mark-paragraph ()
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#e6db74">&amp;#34;redefinition of mark-paragraph&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> (&lt;span style="color:#a6e22e">forward-char&lt;/span>)
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> (backward-paragraph)
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> (push-mark)
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> (forward-paragraph)
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> (setq mark-active &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>(global-set-key (kbd &lt;span style="color:#e6db74">&amp;#34;M-H&amp;#34;&lt;/span>) &lt;span style="color:#e6db74">&amp;#39;my/mark-paragraph&lt;/span>)
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>I had to &lt;code>forward-char&lt;/code> initially to step on to the paragraph if my cursor was just before the paragraph as this felt natural to me so that the &lt;code>backward-paragraph&lt;/code> won&amp;rsquo;t jump to the previous paragraph.&lt;/p>
&lt;p>So just a simple function with a few tweaks here and there to make the paragraph marking feel a little more natural, probably subconsciously taking cues from my time working with many different text editors and as always with emacs it is pretty awesome that this kind of low level finely grained functionality can be modified to such and extent.&lt;/p>
&lt;h2 id="mark-word">mark-word&lt;/h2>
&lt;p>Next up is &lt;code>mark-word&lt;/code>. I&amp;rsquo;m finding myself wanting to naturally just mark the current word my cursor is within and currently I feel I&amp;rsquo;m having to work quite hard to achieve this by a combination of backward/forward-word and marking.&lt;/p>
&lt;p>The default &lt;code>mark-word&lt;/code> &lt;code>(M-@)&lt;/code> seems to only mark to the end of a word, as thus:&lt;/p>
&lt;figure>&lt;img src="https://www.emacs.dyerdwelling.family/emacs/20231209092556-emacs--Redefining-Mark-Paragraph-and-Mark-Word/2023-12-09-10-10-40.jpg" width="100%">
&lt;/figure>
&lt;p>I would want something more like:&lt;/p>
&lt;figure>&lt;img src="https://www.emacs.dyerdwelling.family/emacs/20231209092556-emacs--Redefining-Mark-Paragraph-and-Mark-Word/2023-12-09-10-10-57.jpg" width="100%">
&lt;/figure>
&lt;p>So I created the following function and rebound to the default &lt;code>mark-word&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/mark-word ()
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#e6db74">&amp;#34;redefinition of mark-word&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> (backward-to-word &lt;span style="color:#ae81ff">1&lt;/span>)
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> (forward-to-word &lt;span style="color:#ae81ff">1&lt;/span>)
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> (push-mark)
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> (&lt;span style="color:#a6e22e">forward-word&lt;/span>)
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> (setq mark-active &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>(global-set-key (kbd &lt;span style="color:#e6db74">&amp;#34;M-@&amp;#34;&lt;/span>) &lt;span style="color:#e6db74">&amp;#39;my/mark-word&lt;/span>)
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>This was trickier than I first thought as when I manually mark a word through a combination of key-presses I actually apply a little logic in that if the cursor is on the first character I don&amp;rsquo;t backward-word as this would take me to the previous word!&lt;/p>
&lt;p>I might be able to work out if the cursor is on the first character of a word?, but the function starts to become unnecessarily complicated.&lt;/p>
&lt;p>To solve this I used &lt;code>backward-to-word&lt;/code> which moves backward until the end of a word. This will work irrespective of where my cursor is within the current word and then forwarding back on to the current word so my cursor is on the first character. At this point I can now mark and &lt;code>forward-word&lt;/code> to select the word. Yes I have used word many times here, word, word, word, word, WORD!!!! 😀&lt;/p>
&lt;p>Now typing all this out I wonder if there is a simple function to just move the cursor to the start of a word, if there is I couldn&amp;rsquo;t seem to find it, but as always I have learnt a lot along the way!&lt;/p>
&lt;p>&lt;strong>[Edited : &lt;span class="timestamp-wrapper">&lt;span class="timestamp">&amp;lt;2023-12-10 Sun&amp;gt;&lt;/span>&lt;/span>]&lt;/strong>&lt;/p>
&lt;p>Actually it is possible (of course!) to work out if the cursor is on the first character of a word (see comment section), so the amended function would look 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-elisp" data-lang="elisp">&lt;span style="display:flex;">&lt;span>(defun my/mark-word ()
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#e6db74">&amp;#34;redefinition of mark-word&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> (if (not (&lt;span style="color:#a6e22e">looking-at&lt;/span> &lt;span style="color:#e6db74">&amp;#34;\\&amp;lt;&amp;#34;&lt;/span>))
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> (backward-word))
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> (push-mark)
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> (&lt;span style="color:#a6e22e">forward-word&lt;/span>)
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> (setq mark-active &lt;span style="color:#66d9ef">t&lt;/span>))
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>I feel like this is a better solution.&lt;/p></description></item><item><title>Describe Character / Face Under Cursor</title><link>https://www.emacs.dyerdwelling.family/emacs/20230819202723-emacs--describe-character-under-cursor/</link><pubDate>Sat, 02 Dec 2023 09:13:00 +0000</pubDate><author>James Dyer</author><guid>https://www.emacs.dyerdwelling.family/emacs/20230819202723-emacs--describe-character-under-cursor/</guid><description>&lt;p>Every now and then I find myself tweaking the look of emacs and have started to build up a list of my own common faces in &lt;code>custom-set-faces&lt;/code> to suit my needs, for example here is my current setup:&lt;/p>
&lt;figure class="emacs-img">&lt;img src="https://www.emacs.dyerdwelling.family/emacs/20230819202723-emacs--Describe-Character-Under-Cursor.jpg">
&lt;/figure>
&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>(custom-set-faces
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#75715e">;; custom-set-faces was added by Custom.&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#75715e">;; If you edit it by hand, you could mess it up, so be careful.&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#75715e">;; Your init file should contain only one such instance.&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#75715e">;; If there is more than one, they won&amp;#39;t work right.&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#f92672">&amp;#39;&lt;/span>(cursor ((&lt;span style="color:#66d9ef">t&lt;/span> (:background &lt;span style="color:#e6db74">&amp;#34;#ffffff&amp;#34;&lt;/span> :inverse-video &lt;span style="color:#66d9ef">t&lt;/span>))))
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#f92672">&amp;#39;&lt;/span>(ediff-current-diff-A ((&lt;span style="color:#66d9ef">t&lt;/span> (:extend &lt;span style="color:#66d9ef">t&lt;/span> :background &lt;span style="color:#e6db74">&amp;#34;#b5daeb&amp;#34;&lt;/span> :foreground &lt;span style="color:#e6db74">&amp;#34;#000000&amp;#34;&lt;/span>))))
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#f92672">&amp;#39;&lt;/span>(ediff-even-diff-A ((&lt;span style="color:#66d9ef">t&lt;/span> (:background &lt;span style="color:#e6db74">&amp;#34;#bafbba&amp;#34;&lt;/span> :foreground &lt;span style="color:#e6db74">&amp;#34;#000000&amp;#34;&lt;/span> :extend &lt;span style="color:#66d9ef">t&lt;/span>))))
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#f92672">&amp;#39;&lt;/span>(ediff-fine-diff-A ((&lt;span style="color:#66d9ef">t&lt;/span> (:background &lt;span style="color:#e6db74">&amp;#34;#f4bd92&amp;#34;&lt;/span> :foreground &lt;span style="color:#e6db74">&amp;#34;#000000&amp;#34;&lt;/span> :extend &lt;span style="color:#66d9ef">t&lt;/span>))))
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#f92672">&amp;#39;&lt;/span>(ediff-odd-diff-A ((&lt;span style="color:#66d9ef">t&lt;/span> (:background &lt;span style="color:#e6db74">&amp;#34;#b8fbb8&amp;#34;&lt;/span> :foreground &lt;span style="color:#e6db74">&amp;#34;#000000&amp;#34;&lt;/span> :extend &lt;span style="color:#66d9ef">t&lt;/span>))))
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#f92672">&amp;#39;&lt;/span>(org-block ((&lt;span style="color:#66d9ef">t&lt;/span> (:inherit fixed-pitch))))
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#f92672">&amp;#39;&lt;/span>(org-code ((&lt;span style="color:#66d9ef">t&lt;/span> (:inherit (shadow fixed-pitch)))))
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#f92672">&amp;#39;&lt;/span>(org-date ((&lt;span style="color:#66d9ef">t&lt;/span> (:inherit fixed-pitch))))
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#f92672">&amp;#39;&lt;/span>(org-document-info ((&lt;span style="color:#66d9ef">t&lt;/span> (:foreground &lt;span style="color:#e6db74">&amp;#34;#8f4800&amp;#34;&lt;/span>))))
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#f92672">&amp;#39;&lt;/span>(org-document-info-keyword ((&lt;span style="color:#66d9ef">t&lt;/span> (:inherit (shadow fixed-pitch)))))
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#f92672">&amp;#39;&lt;/span>(org-indent ((&lt;span style="color:#66d9ef">t&lt;/span> (:inherit (org-hide fixed-pitch)))))
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#f92672">&amp;#39;&lt;/span>(org-link ((&lt;span style="color:#66d9ef">t&lt;/span> (:foreground &lt;span style="color:#e6db74">&amp;#34;#5555ff&amp;#34;&lt;/span> :underline &lt;span style="color:#66d9ef">t&lt;/span>))))
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#f92672">&amp;#39;&lt;/span>(org-meta-line ((&lt;span style="color:#66d9ef">t&lt;/span> (:inherit (font-lock-comment-face fixed-pitch)))))
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#f92672">&amp;#39;&lt;/span>(org-tag ((&lt;span style="color:#66d9ef">t&lt;/span> (:inherit (shadow fixed-pitch) :weight regular :height &lt;span style="color:#ae81ff">0.7&lt;/span>))))
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#f92672">&amp;#39;&lt;/span>(widget-button ((&lt;span style="color:#66d9ef">t&lt;/span> (:inherit fixed-pitch :weight regular))))
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#f92672">&amp;#39;&lt;/span>(window-divider ((&lt;span style="color:#66d9ef">t&lt;/span> (:foreground &lt;span style="color:#e6db74">&amp;#34;black&amp;#34;&lt;/span>))))
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#f92672">&amp;#39;&lt;/span>(vertical-border ((&lt;span style="color:#66d9ef">t&lt;/span> (:foreground &lt;span style="color:#e6db74">&amp;#34;#000000&amp;#34;&lt;/span>)))))
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>To help me work out the name of a certain face I use:&lt;/p>
&lt;p>&lt;code>(describe-char)&lt;/code>&lt;/p>
&lt;p>typically accessed through &lt;code>C-x =&lt;/code> (what-cursor-position) with the prefix argument, then accessing customize menu, saving changes, looking up the face changed in the init file and then copy and paste to my custom-set-faces above.&lt;/p></description></item><item><title>Quick Search Through Org Headers using Consult Outline</title><link>https://www.emacs.dyerdwelling.family/emacs/20231014134633-emacs--quick-search-through-org-headers-using-consult-outline/</link><pubDate>Sat, 18 Nov 2023 15:30:00 +0000</pubDate><author>James Dyer</author><guid>https://www.emacs.dyerdwelling.family/emacs/20231014134633-emacs--quick-search-through-org-headers-using-consult-outline/</guid><description>&lt;p>I&amp;rsquo;ve been on the lookout for an efficient way to swiftly scan through org headings exclusively for a specified input string, akin to the functionality of &lt;code>isearch&lt;/code>. This would enable me to promptly navigate to a past blog post which is typically stored under an org heading.&lt;/p>
&lt;p>By default, a standard &lt;code>isearch&lt;/code> examines the entire file, a behaviour that in this case I don&amp;rsquo;t really want. I specifically need to limit the search to org headings only, allowing me to swiftly navigate to an org heading.&lt;/p>
&lt;p>The best method I have found is to use &lt;code>consult-outline&lt;/code> which I have bound to &lt;code>C-o&lt;/code>&lt;/p>
&lt;figure class="emacs-img">&lt;img src="https://www.emacs.dyerdwelling.family/emacs/20231014134633-emacs--Quick-Search-Through-Org-Headers-Using-Consult-Outline.jpg">
&lt;/figure>
&lt;p>I had a play around with various org filtering options like &lt;code>org-goto&lt;/code> and &lt;code>org-agenda&lt;/code> but they didn&amp;rsquo;t really quite do what I wanted.&lt;/p>
&lt;p>As it turns out leveraging the built-in &lt;code>outline-mode&lt;/code> through &lt;code>consult-outline&lt;/code> also comes with the additional benefit of being applicable to source code files.&lt;/p>
&lt;p>Also in combination with &lt;code>embark-collect&lt;/code> it means I can create a separate outline buffer which can simply be &lt;code>isearch&lt;/code>&amp;rsquo;d through to refine my search further.&lt;/p>
&lt;p>I seem to be using &lt;code>embark-collect&lt;/code> more often these days so I bound a key to the mini-buffer map so I won&amp;rsquo;t necessarily need to go through the embark despatching mechanism each time I want to redirect the mini-buffer contents to a proper buffer:&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>(&lt;span style="color:#a6e22e">define-key&lt;/span> minibuffer-local-map (kbd &lt;span style="color:#e6db74">&amp;#34;C-c e&amp;#34;&lt;/span>) &lt;span style="color:#e6db74">&amp;#39;embark-collect&lt;/span>)
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>I have also added &lt;code>consult-imenu&lt;/code> to my arsenal of search weapons which I have bound to &lt;code>M-o&lt;/code>. For example I have a very clearly defined commenting format in emacs init and sway configuration files so to jump through a file I can use &lt;code>imenu&lt;/code> with &lt;code>consult&lt;/code> instead.&lt;/p></description></item><item><title>Window Divider Mode</title><link>https://www.emacs.dyerdwelling.family/emacs/20231006165956-emacs--divider-mode/</link><pubDate>Sun, 05 Nov 2023 16:01:00 +0000</pubDate><author>James Dyer</author><guid>https://www.emacs.dyerdwelling.family/emacs/20231006165956-emacs--divider-mode/</guid><description>&lt;p>I was playing around with the look of emacs and thought it might be nice to have more control over the border between windows (I have now learnt that this is referred to as a window divider!)&lt;/p>
&lt;figure>&lt;img src="https://www.emacs.dyerdwelling.family/emacs/20231006165956-emacs--Divider-Mode.jpg" width="100%">
&lt;/figure>
&lt;p>Although I could change the colour using &lt;code>custom-set-faces&lt;/code> &lt;code>(vertical-border ((t (:foreground &amp;quot;#444444&amp;quot;))))&lt;/code> I wanted the default thin dividing window line to be larger, but initially I couldn&amp;rsquo;t seem to figure out how do change this.&lt;/p>
&lt;p>I tried the vertical-border face &lt;code>:width&lt;/code> &lt;code>:height&lt;/code> at varying weights and in fact through the custom settings I toggled on and off pretty much everything, but to no avail.&lt;/p>
&lt;p>This eventually led me to the built-in &lt;code>window-divider-mode&lt;/code> which actually specifically handles the cosmetic divider issues, so I set the following:&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 window-divider-default-bottom-width &lt;span style="color:#ae81ff">8&lt;/span>)
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>(setq window-divider-default-right-width &lt;span style="color:#ae81ff">8&lt;/span>)
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>(setq window-divider-default-places &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>(window-divider-mode &lt;span style="color:#ae81ff">1&lt;/span>)
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>I decided that if I was going to have a window divider then I might as well have it &amp;ldquo;around&amp;rdquo; every window, hence the default-places set to &lt;code>t&lt;/code> rather than a very specific position, &lt;code>right-only&lt;/code> or &lt;code>bottom-only&lt;/code>&lt;/p></description></item><item><title>Digital Art Using Artist Mode #1</title><link>https://www.emacs.dyerdwelling.family/emacs/20231027211128-emacs--famous-film-using-artist-mode/</link><pubDate>Fri, 27 Oct 2023 21:31:00 +0100</pubDate><author>James Dyer</author><guid>https://www.emacs.dyerdwelling.family/emacs/20231027211128-emacs--famous-film-using-artist-mode/</guid><description>&lt;p>I have been using &lt;strong>ArtRage&lt;/strong>, &lt;strong>Krita&lt;/strong> and &lt;strong>Infinite Painter&lt;/strong> for many years now but I thought I would try that other well known digital art tool&amp;hellip; &lt;strong>emacs&lt;/strong>!&lt;/p>
&lt;p>Using the built-in artist-mode now I really can truly live in emacs full time!&lt;/p>
&lt;figure>&lt;img src="https://www.emacs.dyerdwelling.family/emacs/20231027211128-emacs--Famous-Film-Using-Artist-Mode.jpg" width="100%">
&lt;/figure>
&lt;p>Here is my emacs time-lapse! : &lt;a href="https://youtu.be/1JZ6ljIRGus">https://youtu.be/1JZ6ljIRGus&lt;/a> 😀&lt;/p>
&lt;div style="position: relative; padding-bottom: 56.25%; height: 0; overflow: hidden;">
&lt;iframe allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share" allowfullscreen="allowfullscreen" loading="eager" referrerpolicy="strict-origin-when-cross-origin" src="https://www.youtube.com/embed/1JZ6ljIRGus?autoplay=0&amp;controls=1&amp;end=0&amp;loop=0&amp;mute=0&amp;start=0" style="position: absolute; top: 0; left: 0; width: 100%; height: 100%; border:0;" title="YouTube video"
>&lt;/iframe>
&lt;/div></description></item><item><title>Better Syntax Highlighting Sway Configuration Files</title><link>https://www.emacs.dyerdwelling.family/emacs/20231022195943-emacs--syntax-highlighting-of-swaywm-configuration-files/</link><pubDate>Sun, 22 Oct 2023 21:20:00 +0100</pubDate><author>James Dyer</author><guid>https://www.emacs.dyerdwelling.family/emacs/20231022195943-emacs--syntax-highlighting-of-swaywm-configuration-files/</guid><description>&lt;p>I have been delving into the nuts and bolts of the &lt;strong>Sway window manager&lt;/strong> lately and especially its fork &lt;strong>SwayFX&lt;/strong> which adds a little eye candy in the style of &lt;strong>Hyprland&lt;/strong>.&lt;/p>
&lt;p>This has led to lots of sway config file editing and hence trying to work out the best way in emacs to work with them.&lt;/p>
&lt;p>By default a typical sway/config file has its own configuration format and emacs tries its best to figure out which best mode to use and settles on &lt;code>conf-space-mode&lt;/code>. This is a basic starter mode for a config that is space separated and gives me a rudimentary level of highlighting, for example :&lt;/p>
&lt;figure>&lt;img src="https://www.emacs.dyerdwelling.family/emacs/20231022195943-emacs--Syntax-Highlighting-Of-SwayWM-Configuration-Files/2023-10-22-20-14-13.jpg" width="100%">
&lt;/figure>
&lt;p>I would like indentation to be a little more intelligent and currently this mode applies an ever increasing indent but I shall figure that out at a later date.&lt;/p>
&lt;p>I was generally not unhappy with &lt;code>conf-space-mode&lt;/code> as it gave me a rudimentary keyword form of highlighting and typically as a sway configuration file doesn&amp;rsquo;t need to be particularly structured I wasn&amp;rsquo;t ever really indenting anyway, but lets see if I can improve the syntax highlighting.&lt;/p>
&lt;p>The first port of call is &lt;code>list-packages&lt;/code> and searching for &lt;code>sway&lt;/code>:&lt;/p>
&lt;pre tabindex="0">&lt;code class="language-nil" data-lang="nil"> sway - Communication with the Sway window manager
sway-lang-mode - Major mode for sway
&lt;/code>&lt;/pre>&lt;p>Unfortunately both don&amp;rsquo;t suit my use case and in fact the second is for the sway programming language of which this is not!.&lt;/p>
&lt;p>Well how about &lt;code>treesitter&lt;/code>?, a big hard nope for this as well.&lt;/p>
&lt;p>Next up is a little ace up my sleeve and one that continues to prove advantageous when working with the Sway tiling manager. It is the fact that SwayWM is a drop in replacement for i3wm. Well if the sway config files are of the same format as i3 config files then surely I can use some kind of i3 emacs mode? and so it proved to be the case!&lt;/p>
&lt;pre tabindex="0">&lt;code class="language-nil" data-lang="nil"> i3wm-config-mode - Better syntax highlighting for i3wm&amp;#39;s config file
&lt;/code>&lt;/pre>&lt;p>Explicitly running this mode gives me:&lt;/p>
&lt;figure>&lt;img src="https://www.emacs.dyerdwelling.family/emacs/20231022195943-emacs--Syntax-Highlighting-Of-SwayWM-Configuration-Files/2023-10-22-20-32-51.jpg" width="100%">
&lt;/figure>
&lt;p>Much nicer! and comes with the following &lt;code>auto-mode-alist&lt;/code> definition:&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>(&lt;span style="color:#e6db74">&amp;#34;i3/config\\&amp;#39;&amp;#34;&lt;/span> &lt;span style="color:#f92672">.&lt;/span> i3wm-config-mode)
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>which of course requires a little adjustment to auto associate this new i3 mode with my sway files. I came up with the following:&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>(add-to-list &lt;span style="color:#e6db74">&amp;#39;auto-mode-alist&lt;/span> &lt;span style="color:#f92672">&amp;#39;&lt;/span>(&lt;span style="color:#e6db74">&amp;#34;/sway/.*config.*/&amp;#34;&lt;/span> &lt;span style="color:#f92672">.&lt;/span> i3wm-config-mode))
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>(add-to-list &lt;span style="color:#e6db74">&amp;#39;auto-mode-alist&lt;/span> &lt;span style="color:#f92672">&amp;#39;&lt;/span>(&lt;span style="color:#e6db74">&amp;#34;/sway/config\\&amp;#39;&amp;#34;&lt;/span> &lt;span style="color:#f92672">.&lt;/span> i3wm-config-mode))
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>It took me a little while to figure out the &lt;code>regex&lt;/code> as there are a few subtleties, for example it is recommended to always use &lt;strong>\\&amp;rsquo;&lt;/strong> rather than &lt;strong>$&lt;/strong> to eliminate the confusion around new lines.&lt;/p>
&lt;p>Note that I put an extra directory slash at the regex start to stop the mode being applied to any other potential directories that might end in &lt;strong>sway&lt;/strong> which I feel might be a more common future occurrence than a config directory containing &lt;strong>i3&lt;/strong>.&lt;/p></description></item><item><title>More Improvements to Dired Duplicate Here</title><link>https://www.emacs.dyerdwelling.family/emacs/20231013153639-emacs--more-flexible-duplicate-thing-function/</link><pubDate>Fri, 13 Oct 2023 16:10:00 +0100</pubDate><author>James Dyer</author><guid>https://www.emacs.dyerdwelling.family/emacs/20231013153639-emacs--more-flexible-duplicate-thing-function/</guid><description>&lt;p>On a previous post I created an elisp function to quickly duplicate a file or directory in &lt;code>dired&lt;/code>, by default it would copy the &lt;code>dired&lt;/code> item under the cursor to an &lt;code>old&lt;/code> suffix or append a number based on the universal argument.&lt;/p>
&lt;p>&lt;a href="https://www.emacs.dyerdwelling.family/emacs/20230606213531-emacs--dired-duplicate-here-revisited/">Dired Duplicate Here Revisited&lt;/a>&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/dired-duplicate-file (arg)
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#e6db74">&amp;#34;Duplicate the current file in Dired.&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> (interactive &lt;span style="color:#e6db74">&amp;#34;p&amp;#34;&lt;/span>)
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> (let ((filename (dired-get-filename)))
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> (setq target (&lt;span style="color:#a6e22e">concat&lt;/span> (file-name-sans-extension filename)
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#e6db74">&amp;#34;-old&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> (if (&lt;span style="color:#a6e22e">&amp;gt;&lt;/span> arg &lt;span style="color:#ae81ff">1&lt;/span>) (&lt;span style="color:#a6e22e">number-to-string&lt;/span> arg))
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> (file-name-extension filename &lt;span style="color:#66d9ef">t&lt;/span>)))
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> (if (&lt;span style="color:#a6e22e">file-directory-p&lt;/span> filename)
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> (copy-directory filename target)
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> (&lt;span style="color:#a6e22e">copy-file&lt;/span> filename target))
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> )
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> )
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>This worked well for a while but now I want something a little more robust and after working within Krita for a while I decided that I would like to implement its incremental save naming convention.&lt;/p>
&lt;p>It&amp;rsquo;s pretty simple really, just an incremented integer, zero padded to a width of 3, inserted just before the extension:&lt;/p>
&lt;figure>&lt;img src="https://www.emacs.dyerdwelling.family/emacs/20231013153639-emacs--More-Flexible-Duplicate-Thing-Function.jpg" width="100%">
&lt;/figure>
&lt;p>So I just need to apply a little counter logic but still have the potential to pass in the universal argument and of course avoid overriding any existing backup as it did before.&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/dired-duplicate-file (arg)
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#e6db74">&amp;#34;Duplicate a file from dired with an incremented number.
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#e6db74"> If ARG is provided, it sets the counter.&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> (interactive &lt;span style="color:#e6db74">&amp;#34;p&amp;#34;&lt;/span>)
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> (let* ((file (dired-get-file-for-visit))
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> (dir (&lt;span style="color:#a6e22e">file-name-directory&lt;/span> file))
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> (name (&lt;span style="color:#a6e22e">file-name-nondirectory&lt;/span> file))
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> (base-name (file-name-sans-extension name))
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> (extension (file-name-extension name &lt;span style="color:#66d9ef">t&lt;/span>))
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> (counter (if arg (&lt;span style="color:#a6e22e">prefix-numeric-value&lt;/span> arg) &lt;span style="color:#ae81ff">1&lt;/span>))
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> (new-file))
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> (while (and (setq new-file
&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;%s%s_%03d%s&amp;#34;&lt;/span> dir base-name counter extension))
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> (&lt;span style="color:#a6e22e">file-exists-p&lt;/span> new-file))
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> (setq counter (&lt;span style="color:#a6e22e">1+&lt;/span> counter)))
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> (if (&lt;span style="color:#a6e22e">file-directory-p&lt;/span> file)
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> (copy-directory file new-file)
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> (&lt;span style="color:#a6e22e">copy-file&lt;/span> file new-file))
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> (dired-revert)))
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>I can now create any number of dired file/directory backups quickly from &lt;code>dired&lt;/code> (well up to 100).&lt;/p></description></item><item><title>More Improvements To My Weight Loss Org Table</title><link>https://www.emacs.dyerdwelling.family/emacs/20231006172548-emacs--improving-weight-loss-org-table/</link><pubDate>Fri, 06 Oct 2023 18:59:00 +0100</pubDate><author>James Dyer</author><guid>https://www.emacs.dyerdwelling.family/emacs/20231006172548-emacs--improving-weight-loss-org-table/</guid><description>&lt;p>More improvements to my weight loss table, this time I have added the following:&lt;/p>
&lt;figure class="emacs-img">&lt;img src="https://www.emacs.dyerdwelling.family/emacs/20231006172548-emacs--Improving-Weight-Loss-Org-Table.jpg">
&lt;/figure>
&lt;ol>
&lt;li>
&lt;p>The first column now auto populates an incremental integer&lt;/p>
&lt;/li>
&lt;li>
&lt;p>Extra column to display the average loss&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:#f92672">+PLOT: title:&amp;#34;Weight Loss&amp;#34; ind:1 deps:(4) type:2d with:lines set:&amp;#34;yrange [150:220]&amp;#34;
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#f92672"> | | date | stn | pnd | lss | tot | bar | av-loss |
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#f92672"> |---+&lt;/span>------------------&lt;span style="color:#f92672">+-------+&lt;/span>-----&lt;span style="color:#f92672">+-----+&lt;/span>-----&lt;span style="color:#f92672">+----------------+&lt;/span>---------|
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#e6db74">| 0 | &amp;lt;2023-08-18 Fri&amp;gt; | 15:5 | 215 | | | WWWWWWWWWWWWWH | |&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#e6db74">| 1 | &amp;lt;2023-08-25 Fri&amp;gt; | 14:9 | 205 | -10 | -10 | WWWWWWWWWWWV | 10.0 |&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#e6db74">| 2 | &amp;lt;2023-09-01 Fri&amp;gt; | 14:2 | 198 | -7 | -17 | WWWWWWWWWW; | 8.5 |&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#e6db74">| 3 | &amp;lt;2023-09-08 Fri&amp;gt; | 13:11 | 193 | -5 | -22 | WWWWWWWWW: | 7.3 |&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#e6db74">| 4 | &amp;lt;2023-09-15 Fri&amp;gt; | 13:10 | 192 | -1 | -23 | WWWWWWWWW | 5.8 |&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#e6db74">| 5 | &amp;lt;2023-09-22 Fri&amp;gt; | 13:9 | 191 | -1 | -24 | WWWWWWWWV | 4.8 |&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#e6db74">| | | | | | | | |&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> #+TBLFM: $1&lt;span style="color:#a6e22e">=@#-2::$4=&lt;/span>&amp;#39;(convert-weight $3)::@3$5..@&amp;gt;$5=$4-@-1$4::@3$6..@&amp;gt;$6&lt;span style="color:#a6e22e">=vsum(@$5..@3$5)::$7=&lt;/span>&amp;#39;(orgtbl-ascii-draw $4 150 220 15)::@3$8..@&amp;gt;$8=abs(vmean(@3$5..@$5));%0.1f
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;ol>
&lt;li>
&lt;p>On my last org weight table post a comment pointed out that there was a better way to populate the first integer column, so rather than a key-press &lt;code>S-RET (org-table-copy-down)&lt;/code> rows can be auto populated using the &lt;code>$1=@#-2&lt;/code> equation in the &lt;code>#+TBLFM&lt;/code> line.&lt;/p>
&lt;/li>
&lt;li>
&lt;p>I thought it might be interesting to plot he average weight loss to gain a more general perspective and to see the average weekly value settle down over a period of time. For this I added&lt;/p>
&lt;p>&lt;code>@3$8..@&amp;gt;$8=abs(vmean(@3$5..@$5));%0.1f&lt;/code>&lt;/p>
&lt;p>which leverages the calc functions of &lt;code>vmean&lt;/code> to average out the previous loss values and then apply an absolute value using the function &lt;code>abs&lt;/code>.&lt;/p>
&lt;p>I also found that I needed to find a way to display the table value to 1.d.p which was achieved by &lt;code>%0.1f&lt;/code>&lt;/p>
&lt;/li>
&lt;/ol>
&lt;/li>
&lt;/ol></description></item><item><title>Recursively Listing Files in Size Order using find-name-dired</title><link>https://www.emacs.dyerdwelling.family/emacs/20231001210123-emacs--dired-listing-files-largest-first/</link><pubDate>Sun, 01 Oct 2023 21:31:00 +0100</pubDate><author>James Dyer</author><guid>https://www.emacs.dyerdwelling.family/emacs/20231001210123-emacs--dired-listing-files-largest-first/</guid><description>&lt;p>For a while now I have been looking for a simple method in emacs for trimming down my largest files, usually this means locating those large image files and compressing them as I like keeping my media compressed or deleting any large files I didn&amp;rsquo;t know were lurking around my system.&lt;/p>
&lt;figure class="emacs-img">&lt;img src="https://www.emacs.dyerdwelling.family/emacs/20231001210123-emacs--Dired-Listing-Files-Largest-First.jpg">
&lt;/figure>
&lt;p>The obvious choice for this is &lt;code>find-name-dired&lt;/code> but for me always had the annoying habit of reordering the final result after finishing the recursive search. Even if I set the &lt;code>find-ls-option&lt;/code> correctly (not necessarily an easy task) to sort by size the final dired buffer output would always keep resetting to sort by file name.&lt;/p>
&lt;p>It took me a while delving into the nuts and bolts of &lt;code>find-name-dired&lt;/code> but I finally realised that after processing using the &lt;code>find-ls-option&lt;/code> a function &lt;code>find-dired-refine-function&lt;/code> is called which by default is set to &lt;code>find-dired-sort-by-filename&lt;/code> which:&lt;/p>
&lt;blockquote>
&lt;p>Sorts entries in &lt;strong>Find&lt;/strong> buffer by file name lexicographically.&lt;/p>
&lt;/blockquote>
&lt;p>Why this does this I have no idea (perhaps it is because the output of the find command ends up in a dired buffer which would always generally make sense to sort by name?), but as I will only ever want to use &lt;code>find-name-dired&lt;/code> to list files in size order and then to prune any or compress them using standard &lt;code>dired&lt;/code> commands then I can just do the following:&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 find-dired-refine-function &lt;span style="color:#e6db74">&amp;#39;nil&lt;/span>)
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>(setq find-ls-option (&lt;span style="color:#a6e22e">cons&lt;/span> &lt;span style="color:#e6db74">&amp;#34;-exec ls -lSh {} +&amp;#34;&lt;/span> &lt;span style="color:#e6db74">&amp;#34;-lSh&amp;#34;&lt;/span>))
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>Which will honour the intended &lt;code>find-ls-option&lt;/code> to do exactly what it says on the tin and that is to:&lt;/p>
&lt;pre tabindex="0">&lt;code class="language-nil" data-lang="nil">-l : long list as =dired= will always need this form of listing
-S : Sort by file size
-h : Show size in human readable form, which doesn&amp;#39;t seem to affect the sorting
&lt;/code>&lt;/pre>&lt;p>For me this seems to be a more transparent method and of course I guess other standard &lt;code>ls&lt;/code> options can now be passed through to &lt;code>dired&lt;/code>, for example for files in order of time, you would pass &lt;code>-t&lt;/code>&lt;/p></description></item><item><title>Plotting Other Org Tables</title><link>https://www.emacs.dyerdwelling.family/emacs/20230923092356-emacs--adding-row-number-column-in-org-table/</link><pubDate>Tue, 26 Sep 2023 21:30:00 +0100</pubDate><author>James Dyer</author><guid>https://www.emacs.dyerdwelling.family/emacs/20230923092356-emacs--adding-row-number-column-in-org-table/</guid><description>&lt;p>I&amp;rsquo;m currently in the process of learning how to create graphical plots from org tables using &lt;code>gnuplot&lt;/code>. I&amp;rsquo;ve noticed that it&amp;rsquo;s generally more straightforward to extract x-axis data from an org table column with incrementing numbers, as opposed to relying on &lt;code>gnuplot&lt;/code> to potentially sort out data from an existing column (which may not be plot-friendly)&lt;/p>
&lt;p>Generally my existing org tables do not have such an incrementing integer column, so how do I quickly create and populate such a column?, well actually it is super easy, barely an inconvenience!&lt;/p>
&lt;p>For example take the following table that represents a little flutter on the horses, how would I go about plotting the &lt;em>Total&lt;/em> column and therefore clearly visualise the downward sloping line 😀&lt;/p>
&lt;pre tabindex="0">&lt;code class="language-nil" data-lang="nil">| Date | Horse | Stk | Ret | Prof | Total |
|--------+----------------+-----+-------+-------+--------|
| | | | | 0 | 306.9 |
| 27 May | aggagio | 15 | 7.68 | -7.32 | 299.58 |
| | red derek | 15 | 0 | -15 | 284.58 |
| 03 Jun | dear my friend | 10 | 0 | -10 | 274.58 |
| | the foxes | 10 | 32 | 22 | 296.58 |
| | sprewell | 10 | 16 | 6 | 302.58 |
| | dancing poet | 15 | 16.73 | 1.73 | 304.31 |
&lt;/code>&lt;/pre>&lt;p>Firstly simply &lt;code>M-S-&amp;lt;right&amp;gt;&lt;/code> to insert a table column:&lt;/p>
&lt;pre tabindex="0">&lt;code class="language-nil" data-lang="nil">| | Date | Horse | Stk | Ret | Prof | Total |
|---+--------+----------------+-----+-------+-------+--------|
| | | | | | 0 | 306.9 |
| | 27 May | aggagio | 15 | 7.68 | -7.32 | 299.58 |
| | | red derek | 15 | 0 | -15 | 284.58 |
| | 03 Jun | dear my friend | 10 | 0 | -10 | 274.58 |
| | | the foxes | 10 | 32 | 22 | 296.58 |
| | | sprewell | 10 | 16 | 6 | 302.58 |
| | | dancing poet | 15 | 16.73 | 1.73 | 304.31 |
&lt;/code>&lt;/pre>&lt;p>Now move the cursor to the starting point and put in an initial integer:&lt;/p>
&lt;pre tabindex="0">&lt;code class="language-nil" data-lang="nil">| | Date | Horse | Stk | Ret | Prof | Total |
|---+--------+----------------+-----+-------+-------+--------|
| 1 | | | | | 0 | 306.9 |
| | 27 May | aggagio | 15 | 7.68 | -7.32 | 299.58 |
| | | red derek | 15 | 0 | -15 | 284.58 |
| | 03 Jun | dear my friend | 10 | 0 | -10 | 274.58 |
| | | the foxes | 10 | 32 | 22 | 296.58 |
| | | sprewell | 10 | 16 | 6 | 302.58 |
| | | dancing poet | 15 | 16.73 | 1.73 | 304.31 |
&lt;/code>&lt;/pre>&lt;p>Now just simply &lt;code>S-Enter (org-table-copy-down)&lt;/code> which will fill an incremented number downwards as far as you want:&lt;/p>
&lt;pre tabindex="0">&lt;code class="language-nil" data-lang="nil">| | Date | Horse | Stk | Ret | Prof | Total |
|---+--------+----------------+-----+-------+-------+--------|
| 1 | | | | | 0 | 306.9 |
| 2 | 27 May | aggagio | 15 | 7.68 | -7.32 | 299.58 |
| 3 | | red derek | 15 | 0 | -15 | 284.58 |
| 4 | 03 Jun | dear my friend | 10 | 0 | -10 | 274.58 |
| 5 | | the foxes | 10 | 32 | 22 | 296.58 |
| 6 | | sprewell | 10 | 16 | 6 | 302.58 |
| 7 | | dancing poet | 15 | 16.73 | 1.73 | 304.31 |
&lt;/code>&lt;/pre>&lt;p>Now I can use a #+PLOT header to reference the first column for the x axis:&lt;/p>
&lt;pre tabindex="0">&lt;code class="language-nil" data-lang="nil">#+PLOT: title:&amp;#34;Betting&amp;#34; ind:1 deps:(7) type:2d with:lines set:&amp;#34;yrange [250:350]&amp;#34;
| | Date | Horse | Stk | Ret | Prof | Total |
|---+--------+----------------+-----+-------+-------+--------|
| 1 | | | | | 0 | 306.9 |
| 2 | 27 May | aggagio | 15 | 7.68 | -7.32 | 299.58 |
| 3 | | red derek | 15 | 0 | -15 | 284.58 |
| 4 | 03 Jun | dear my friend | 10 | 0 | -10 | 274.58 |
| 5 | | the foxes | 10 | 32 | 22 | 296.58 |
| 6 | | sprewell | 10 | 16 | 6 | 302.58 |
| 7 | | dancing poet | 15 | 16.73 | 1.73 | 304.31 |
&lt;/code>&lt;/pre>&lt;p>which will produce the following plot:&lt;/p>
&lt;figure>&lt;img src="https://www.emacs.dyerdwelling.family/emacs/20230923092356-emacs--Adding-Row-Number-Column-In-Org-Table.jpg" width="100%">
&lt;/figure>
&lt;p>Not such a downward spiral as I first thought!&lt;/p></description></item><item><title>Plotting Org Table Weight Loss Using gnuplot</title><link>https://www.emacs.dyerdwelling.family/emacs/20230922125005-emacs--plotting-org-table-weight-loss/</link><pubDate>Fri, 22 Sep 2023 13:40:00 +0100</pubDate><author>James Dyer</author><guid>https://www.emacs.dyerdwelling.family/emacs/20230922125005-emacs--plotting-org-table-weight-loss/</guid><description>&lt;p>Now I have a weight loss org table defined and therefore an easy way to track the total amount of weight loss each week I had an idea for an improvement with some form of a satisfying graphical representation.&lt;/p>
&lt;p>I have been aware of &lt;code>gnuplot&lt;/code> for while now and I think this is an opportunity to give it a try through &lt;code>org-mode&lt;/code>&lt;/p>
&lt;p>The first step is a simple &lt;code>(use-package gnuplot)&lt;/code>&lt;/p>
&lt;p>here is the original table:&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:#e6db74">| date | weight | pounds | loss | total |&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#e6db74">|------------------+--------+--------+------+-------|&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#e6db74">| &amp;lt;2023-08-18 Fri&amp;gt; | 15:5 | 215 | | |&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#e6db74">| &amp;lt;2023-08-25 Fri&amp;gt; | 14:9 | 205 | -10 | -10 |&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#e6db74">| &amp;lt;2023-09-01 Fri&amp;gt; | 14:2 | 198 | -7 | -17 |&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#e6db74">| &amp;lt;2023-09-08 Fri&amp;gt; | 13:11 | 193 | -5 | -22 |&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#75715e">#+TBLFM&lt;/span>&lt;span style="color:#75715e">: $3=&amp;#39;(convert-weight $2)::@3$4..@&amp;gt;$4=$3-@-1$3::@3$5..@&amp;gt;$5=vsum(@$4..@3$4)&lt;/span>
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>and with a little investigation and trial and error I came up with:&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">#+PLOT&lt;/span>&lt;span style="color:#75715e">: title:&amp;#34;Weight Loss&amp;#34; ind:1 deps:(4) type:2d with:lines set:&amp;#34;yrange [150:220]&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#e6db74">| | date | stn | pnd | lss | tot | bar |&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#e6db74">|---+------------------+-------+-----+-----+-----+----------------|&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#e6db74">| 0 | &amp;lt;2023-08-18 Fri&amp;gt; | 15:5 | 215 | | | WWWWWWWWWWWWWH |&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#e6db74">| 1 | &amp;lt;2023-08-25 Fri&amp;gt; | 14:9 | 205 | -10 | -10 | WWWWWWWWWWWV |&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#e6db74">| 2 | &amp;lt;2023-09-01 Fri&amp;gt; | 14:2 | 198 | -7 | -17 | WWWWWWWWWW; |&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#e6db74">| 3 | &amp;lt;2023-09-08 Fri&amp;gt; | 13:11 | 193 | -5 | -22 | WWWWWWWWW: |&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#e6db74">| 4 | &amp;lt;2023-09-15 Fri&amp;gt; | 13:10 | 192 | -1 | -23 | WWWWWWWWW |&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#e6db74">| 5 | &amp;lt;2023-09-22 Fri&amp;gt; | 13:9 | 191 | -1 | -24 | WWWWWWWWV |&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#75715e">#+TBLFM&lt;/span>&lt;span style="color:#75715e">: $4=&amp;#39;(convert-weight $3)::@3$5..@&amp;gt;$5=$4-@-1$4::@3$6..@&amp;gt;$6=vsum(@$5..@3$5)::$7=&amp;#39;(orgtbl-ascii-draw $4 150 220 15)&lt;/span>
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>The first thing to point out is that as I was learning more about org tables I came across the &lt;code>orgtbl-ascii-draw&lt;/code> function which visually is quite self explanatory as shown in the bar column. The main arguments are the x axis range (150-220) and the span in characters (15)&lt;/p>
&lt;p>Now on to the main plotting change and that is defining a &lt;code>#+PLOT&lt;/code> header setting out all the parameters I want fed into gnuplot from the org table.&lt;/p>
&lt;p>Again most of the defines are self explanatory and the only issue I ran into was trying to get plot lines to be rendered based off the original first date column. The histograms display did work and correctly listed each week with the corresponding bar value but lines only displayed a single vertical line at a 2023 value.&lt;/p>
&lt;p>I am guessing in the case of a line display the data is plotted in a more non discrete manner and the date was parsed as best it could and just plucked out the first part of the date string which was the year. I decided not to investigate this and instead simply added another column with a simple numbering scheme and set my x axis off of that. I only want to see a plot over a number of weeks so I don&amp;rsquo;t care about the display of the date.&lt;/p>
&lt;p>So now lets generate the gnuplot! which can be accomplished by running &lt;code>C-c &amp;quot; g (org-plot/gnuplot)&lt;/code>:&lt;/p>
&lt;figure>&lt;img src="https://www.emacs.dyerdwelling.family/emacs/20230922125005-emacs--Plotting-Org-Table-Weight-Loss.jpg" width="100%">
&lt;/figure></description></item><item><title>Opening Files Externally from dired</title><link>https://www.emacs.dyerdwelling.family/emacs/20230529112814-emacs--opening-files-externally-natively/</link><pubDate>Sat, 16 Sep 2023 16:43:00 +0100</pubDate><author>James Dyer</author><guid>https://www.emacs.dyerdwelling.family/emacs/20230529112814-emacs--opening-files-externally-natively/</guid><description>&lt;p>I have been using &lt;strong>&lt;code>C-RET&lt;/code>&lt;/strong> or &lt;strong>&lt;code>W&lt;/code>&lt;/strong> in &lt;code>dired&lt;/code> for a while now to open a file externally via &lt;code>browse-url-of-dired-file&lt;/code>.&lt;/p>
&lt;figure class="emacs-img">&lt;img src="https://www.emacs.dyerdwelling.family/emacs/20230529112814-emacs--Opening-Files-Externally-Natively.jpg">
&lt;/figure>
&lt;p>I was never quite sure how that worked but it just worked, however now it doesn&amp;rsquo;t work so I need to do something about it.&lt;/p>
&lt;p>I suspect that the reason it doesn&amp;rsquo;t work now is that I have been hopping around different window managers / compositors. My tried and tested workhorse environment is generally kde plasma on arch where the &lt;code>browse-url-of-dired-file&lt;/code> was working just fine, but now I have switched to &lt;strong>sway / wayland&lt;/strong> or &lt;strong>i3 / x11&lt;/strong> this functionality seems to be broken.&lt;/p>
&lt;p>I have an idea it&amp;rsquo;s probably related to the window manager and the way each one handles default applications through the XDG portal. I think I need to find a more agnostic opening method.&lt;/p>
&lt;p>My first thought was the &lt;strong>&lt;code>openwith&lt;/code>&lt;/strong> package but this had the side effect of opening every single file externally (especially when coming back from a &lt;code>desktop-save&lt;/code>) which is not something I find desirable. I prefer the default dired mechanism for opening a file in emacs itself and I want an explicit mechanism to push a file to a native external application.&lt;/p>
&lt;p>My next thought was &lt;code>dired-do-shell-command&lt;/code> which can be activated by &lt;code>!&lt;/code> or &lt;code>X&lt;/code> in &lt;code>dired&lt;/code>. This brings up a default opening application called &lt;code>xloadimage&lt;/code> which I&amp;rsquo;m assuming runs through possibly an XDG default application opening mechanism linked to the running window manager. Well I don&amp;rsquo;t really want to set a default application for each window manager / environment so I&amp;rsquo;m not sure that this is right approach either.&lt;/p>
&lt;p>My next thought was how does &lt;code>dired&lt;/code> determine which opening mechanism to use, I mean where does &lt;code>xloadimage&lt;/code> come from?&lt;/p>
&lt;p>Well the answer is twofold, firstly &lt;code>dired-guess-shell-alist-default&lt;/code> and secondly &lt;code>dired-guess-shell-alist-user&lt;/code>&lt;/p>
&lt;p>The first list includes many lines in an alist format, for example:&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>(&lt;span style="color:#e6db74">&amp;#34;\\.jpe?g\\&amp;#39;&amp;#34;&lt;/span> &lt;span style="color:#e6db74">&amp;#34;xloadimage&amp;#34;&lt;/span>)
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>I don&amp;rsquo;t really want to touch the default settings so lets have a look at the user variant which is described as follows:&lt;/p>
&lt;blockquote>
&lt;p>User-defined alist of rules for suggested commands.
These rules take precedence over the predefined rules in the variable
‘dired-guess-shell-alist-default’ (to which they are prepended).&lt;/p>
&lt;/blockquote>
&lt;p>I therefore appended the prepended list! (even though it was empty anyway)&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 dired-guess-shell-alist-user
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> (&lt;span style="color:#a6e22e">append&lt;/span> &lt;span style="color:#f92672">&amp;#39;&lt;/span>((&lt;span style="color:#e6db74">&amp;#34;\\.\\(jpg\\|jpeg\\|png\\|gif\\|bmp\\)$&amp;#34;&lt;/span> &lt;span style="color:#e6db74">&amp;#34;gwenview&amp;#34;&lt;/span>)
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> (&lt;span style="color:#e6db74">&amp;#34;\\.\\(mp4\\|mkv\\|avi\\|mov\\|wmv\\|flv\\|mpg\\)$&amp;#34;&lt;/span> &lt;span style="color:#e6db74">&amp;#34;mpv&amp;#34;&lt;/span>))
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> dired-guess-shell-alist-user))
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>Now to the testing stage&amp;hellip;&lt;/p>
&lt;hr>
&lt;p>I seem to have an intermittent problem when running &lt;code>! (dired-do-shell-command)&lt;/code> in that the file isn&amp;rsquo;t always opened in gwenview, however mpv opens every single time!. I literally have no idea what is going on here but &lt;code>&amp;amp; (dired-do-async-shell-command)&lt;/code> opens every time! so lets use that then, it might be better to run the file opening asynchronously anyway.&lt;/p>
&lt;p>However, another issue presents itself. By default the async shell window always opens a new buffer / window and is polluting my emacs window layout!!!!. Well certainly I can&amp;rsquo;t have that so I decide to use the &lt;code>display-buffer-alist&lt;/code> as follows:&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>(defvar go-away-repl-regexp
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> (rx bos &lt;span style="color:#e6db74">&amp;#34;*&amp;#34;&lt;/span> (or &lt;span style="color:#e6db74">&amp;#34;Async&amp;#34;&lt;/span>)
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> (zero-or-more nonl))
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#e6db74">&amp;#34;Regexp for matching windows to disappear&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>(add-to-list &lt;span style="color:#e6db74">&amp;#39;display-buffer-alist&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#f92672">`&lt;/span>(&lt;span style="color:#f92672">,&lt;/span>go-away-repl-regexp
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> display-buffer-no-window
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> (inhibit-same-window &lt;span style="color:#f92672">.&lt;/span> &lt;span style="color:#66d9ef">t&lt;/span>)))
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>Done.&lt;/p></description></item><item><title>Org Table to Calculate Weight Loss</title><link>https://www.emacs.dyerdwelling.family/emacs/20230910100202-emacs--weight-loss-tables/</link><pubDate>Tue, 12 Sep 2023 21:11:00 +0100</pubDate><author>James Dyer</author><guid>https://www.emacs.dyerdwelling.family/emacs/20230910100202-emacs--weight-loss-tables/</guid><description>&lt;p>For a while now I have been using org tables to represent and calculate pieces of data, obviating the need to open up a normal spreadsheet.&lt;/p>
&lt;figure class="emacs-img">&lt;img src="https://www.emacs.dyerdwelling.family/emacs/20230910100202-emacs--Weight-Loss-Tables.jpg">
&lt;/figure>
&lt;p>Recently I have been wanting to create a table to keep track of weight loss and this is what I came up with (with example weights):&lt;/p>
&lt;pre tabindex="0">&lt;code class="language-nil" data-lang="nil">| date | weight | pounds | loss | total |
|------------------+--------+--------+------+-------|
| &amp;lt;2023-08-18 Fri&amp;gt; | 15:5 | 215 | | |
| &amp;lt;2023-08-25 Fri&amp;gt; | 14:9 | 205 | -10 | -10 |
| &amp;lt;2023-09-01 Fri&amp;gt; | 14:2 | 198 | -7 | -17 |
| &amp;lt;2023-09-08 Fri&amp;gt; | 13:11 | 193 | -5 | -22 |
#+TBLFM: $3=&amp;#39;(convert-weight $2)::@3$4..@&amp;gt;$4=$3-@-1$3::@3$5..@&amp;gt;$5=vsum(@$4..@3$4)
&lt;/code>&lt;/pre>&lt;p>The initial difficulty was my British attachment to the imperial system which means always defining a human weight in stone and pounds. I had to initially define the following function to parse the weight string value into a numeric total pounds representation.&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 convert-weight (weight)
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> (let* ((parts (split-string weight &lt;span style="color:#e6db74">&amp;#34;:&amp;#34;&lt;/span>))
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> (stone (&lt;span style="color:#a6e22e">string-to-number&lt;/span> (&lt;span style="color:#a6e22e">car&lt;/span> parts)))
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> (pounds (&lt;span style="color:#a6e22e">string-to-number&lt;/span> (cadr parts))))
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> (&lt;span style="color:#a6e22e">+&lt;/span> (&lt;span style="color:#a6e22e">*&lt;/span> stone &lt;span style="color:#ae81ff">14&lt;/span>) pounds)))
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>After the date and weight is inserted the total pounds can be calculated as thus :&lt;/p>
&lt;pre tabindex="0">&lt;code class="language-nil" data-lang="nil">$3=&amp;#39;(convert-weight $2)
&lt;/code>&lt;/pre>&lt;p>which is calculated for every data row.&lt;/p>
&lt;p>I can now more easily calculate the loss each week with a simple org table equation and hence a total cumulative loss.&lt;/p>
&lt;p>I think I can use &lt;code>calc&lt;/code> in some way in an org table but for me this method seems easier and I have complete control over my weight input string.&lt;/p>
&lt;p>The next issue is one I have run across many times with org tables and that is catering for a first line calculation. This is where I have to start getting my head around cell referencing.&lt;/p>
&lt;p>For this I found the &lt;code>C-} (org-table-toggle-coordinate-overlays)&lt;/code> very useful which explicitly shows the cell reference positions. The row column identifier often catches me out as the row specification only counts data lines and not things like headline fillers.&lt;/p>
&lt;p>Org seems to prefer the @ROWS$COLUMN convention and although this doesn&amp;rsquo;t feel intuitive to me (I prefer it the other way) I thought I would stick with it and just adjust my thinking.&lt;/p>
&lt;p>The loss calculation although a simple one needs to only take place on those data rows that have a previous weekly weight value, namely all those data rows not in the first data line.&lt;/p>
&lt;p>Therefore I specifiy a range:&lt;/p>
&lt;pre tabindex="0">&lt;code class="language-nil" data-lang="nil">@3$4..@&amp;gt;$4=$3-@-1$3
&lt;/code>&lt;/pre>&lt;p>Note the &lt;code>@&amp;gt;&lt;/code> which references the last row.&lt;/p>
&lt;p>Note: I think I could make this more flexible by specifying an offset from the hline, namely &lt;code>@I+1&lt;/code>&lt;/p>
&lt;p>Finally I need to add in a running cumulative total and again I had to consider not calculating on the first data line:&lt;/p>
&lt;pre tabindex="0">&lt;code class="language-nil" data-lang="nil">@3$5..@&amp;gt;$5=vsum(@$4..@3$4)
&lt;/code>&lt;/pre>&lt;p>So that&amp;rsquo;s just about it for ongoing measurements. I just need to insert a new row at the bottom of the table, insert a date and current weight and on recalculation all the work is done for me!&lt;/p></description></item><item><title>My Evolving Modeline</title><link>https://www.emacs.dyerdwelling.family/emacs/20230902114449-emacs--my-evolving-modeline/</link><pubDate>Sat, 02 Sep 2023 12:32:00 +0100</pubDate><author>James Dyer</author><guid>https://www.emacs.dyerdwelling.family/emacs/20230902114449-emacs--my-evolving-modeline/</guid><description>&lt;p>I am a tinkerer and of course emacs is a perfect vehicle for this mentality. A prime example of this is my constant evolving modeline. For many years the default modeline hasn&amp;rsquo;t presented itself as being a problem, but is it optimal for me?, the answer I have now realised is no, and I think I can do better for my use cases.&lt;/p>
&lt;figure class="emacs-img">&lt;img src="https://www.emacs.dyerdwelling.family/emacs/20230902114449-emacs--My-Evolving-Modeline.jpg">
&lt;/figure>
&lt;p>So after a period of introspection (regarding emacs! 🙂) I think I can now define a clear set of criteria for my perfect modeline:&lt;/p>
&lt;ol>
&lt;li>
&lt;p>Show the full pathname&lt;/p>
&lt;p>By default, emacs displays the buffer name, but during coding sessions, I often prefer to see the full file path, especially since I sometimes work with multiple copies of files with the same name.&lt;/p>
&lt;/li>
&lt;li>
&lt;p>Clearly indicate when a file has been modified&lt;/p>
&lt;p>For when I accidentally insert a &amp;rsquo;n&amp;rsquo; or &amp;lsquo;p&amp;rsquo; character in a buffer (I think you can guess how this happens!) it will be immediately apparent and I will be less likely to break my emacs config.&lt;/p>
&lt;/li>
&lt;li>
&lt;p>Have control over the faces&lt;/p>
&lt;p>Define my own sizing and colouring for an active and inactive buffer modeline to make clearer my current selected buffer. Otherwise I will be at the whim of my current theme which may not always be optimal.&lt;/p>
&lt;/li>
&lt;li>
&lt;p>Efficient information display&lt;/p>
&lt;p>Fit as much information in a compact manner as possible, I don&amp;rsquo;t want to be hunting around for buffer information, for example the column number and major modes should always be displayed.&lt;/p>
&lt;/li>
&lt;li>
&lt;p>Version control menu should be available&lt;/p>
&lt;p>I frequently use git/magit for version control and prefer to keep the menu bar disabled. This choice sometimes leads me to search for built-in VC (Version Control) commands within emacs. For instance, registering a new file in magit can be tricky since untracked files seem to only display top-level directories unless a subdirectory file has been registered. To include all files in a directory, I rely on vc-register, but I don&amp;rsquo;t always remember the key chord. Therefore, having a VC menu in the mode line is immensely helpful.&lt;/p>
&lt;/li>
&lt;li>
&lt;p>Simplicity&lt;/p>
&lt;p>This includes keeping the modeline elisp definition as simple as possible for easier maintainability / transparency and never allowing extra items to creep in like email or miscellaneous information.&lt;/p>
&lt;/li>
&lt;/ol>
&lt;p>Given all this I have come up with the following:&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-default mode-line-modified
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#f92672">&amp;#39;&lt;/span>(:eval (if (and (&lt;span style="color:#a6e22e">buffer-file-name&lt;/span>) (&lt;span style="color:#a6e22e">buffer-modified-p&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:#e6db74">&amp;#34; * Modified &amp;#34;&lt;/span> &lt;span style="color:#e6db74">&amp;#39;face&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#f92672">&amp;#39;&lt;/span>(:background &lt;span style="color:#e6db74">&amp;#34;#e20023&amp;#34;&lt;/span> :foreground &lt;span style="color:#e6db74">&amp;#34;#ffffff&amp;#34;&lt;/span>)) &lt;span style="color:#e6db74">&amp;#34;&amp;#34;&lt;/span>)))
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>(set-face-attribute &lt;span style="color:#e6db74">&amp;#39;mode-line-active&lt;/span> &lt;span style="color:#66d9ef">nil&lt;/span> :height &lt;span style="color:#ae81ff">125&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> :background &lt;span style="color:#e6db74">&amp;#34;#ffffff&amp;#34;&lt;/span> :foreground &lt;span style="color:#e6db74">&amp;#34;#000000&amp;#34;&lt;/span>)
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>(set-face-attribute &lt;span style="color:#e6db74">&amp;#39;mode-line-inactive&lt;/span> &lt;span style="color:#66d9ef">nil&lt;/span> :height &lt;span style="color:#ae81ff">110&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> :background &lt;span style="color:#e6db74">&amp;#34;#000000&amp;#34;&lt;/span> :foreground &lt;span style="color:#e6db74">&amp;#34;#ffffff&amp;#34;&lt;/span>)
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>(set-face-attribute &lt;span style="color:#e6db74">&amp;#39;mode-line&lt;/span> &lt;span style="color:#66d9ef">nil&lt;/span> :height &lt;span style="color:#ae81ff">120&lt;/span>)
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>(set-face-attribute &lt;span style="color:#e6db74">&amp;#39;mode-line-inactive&lt;/span> &lt;span style="color:#66d9ef">nil&lt;/span> :height &lt;span style="color:#ae81ff">120&lt;/span>)
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>(setq-default mode-line-format
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#f92672">&amp;#39;&lt;/span>(&lt;span style="color:#e6db74">&amp;#34;%e&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> mode-line-modified
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> (:eval
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> (if (&lt;span style="color:#a6e22e">buffer-file-name&lt;/span>)
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> (if (mode-line-window-selected-p)
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> (&lt;span style="color:#a6e22e">propertize&lt;/span> (&lt;span style="color:#a6e22e">format&lt;/span> &lt;span style="color:#e6db74">&amp;#34;%s &amp;#34;&lt;/span> (abbreviate-file-name (&lt;span style="color:#a6e22e">buffer-file-name&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:#f92672">&amp;#39;&lt;/span>(:background &lt;span style="color:#e6db74">&amp;#34;#6b91c0&amp;#34;&lt;/span> :foreground &lt;span style="color:#e6db74">&amp;#34;#ffffff&amp;#34;&lt;/span> :inherit bold))
&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;%s &amp;#34;&lt;/span> (abbreviate-file-name (&lt;span style="color:#a6e22e">buffer-file-name&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>&lt;/span>&lt;span style="display:flex;">&lt;span> )
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> (:eval
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> (if (mode-line-window-selected-p)
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> (&lt;span style="color:#a6e22e">propertize&lt;/span> &lt;span style="color:#e6db74">&amp;#34;%o %4l %2c %b&amp;#34;&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:#f92672">&amp;#39;&lt;/span>(:background &lt;span style="color:#e6db74">&amp;#34;#b8b8b8&amp;#34;&lt;/span> :foreground &lt;span style="color:#e6db74">&amp;#34;#000000&amp;#34;&lt;/span>))
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#e6db74">&amp;#34;%o %4l %2c %b&amp;#34;&lt;/span>))
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> (vc-mode vc-mode)
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> mode-line-modes
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> )
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> )
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>(setq mode-line-compact &lt;span style="color:#66d9ef">t&lt;/span>)
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>I have tried to use the built-in modeline variables where possible and use &lt;code>mode-line-compact&lt;/code> which replaces repeating spaces with a single space.&lt;/p>
&lt;p>I found the best way to indicate a modified file and for it to clearly stand out on the modeline is by colour and string length, but I still wanted to keep the asterisk convention as this is quite common.&lt;/p>
&lt;p>I decided to subtly change the size of the active and inactive modeline for extra emphasis and this can be massively exaggerated to taste.&lt;/p></description></item><item><title>ahk-mode and Updating To AutoHotKey Version 2</title><link>https://www.emacs.dyerdwelling.family/emacs/20230825115829-emacs--updating-to-autohotkey-v2/</link><pubDate>Fri, 25 Aug 2023 14:10:00 +0100</pubDate><author>James Dyer</author><guid>https://www.emacs.dyerdwelling.family/emacs/20230825115829-emacs--updating-to-autohotkey-v2/</guid><description>&lt;p>I have now managed to set up my external USB numeric keypad on Linux using &lt;code>kmonad&lt;/code> which lets me use any regular plugged-in number keypad as a shortcut tool for my digital art.&lt;/p>
&lt;figure class="emacs-img">&lt;img src="https://www.emacs.dyerdwelling.family/emacs/20230825115829-emacs--Updating-To-AutoHotKey-V2.jpg">
&lt;/figure>
&lt;p>This comes in very handy when painting in Krita and ArtRage (through Wine) and maybe one day I will expand this functionality to GIMP when version 3.0 comes out!&lt;/p>
&lt;p>I would like the same keymapping functionality to be reflected on Windows (as ArtRage is fundamentally a Windows application). Technically &lt;code>kmonad&lt;/code> should work but I&amp;rsquo;m going to use &lt;code>AutoHotKey&lt;/code> as I&amp;rsquo;m much more familiar with it.&lt;/p>
&lt;p>I have a problem though, my old &lt;code>autohotkey&lt;/code> scripts don&amp;rsquo;t work with the latest version of &lt;code>AutoHotKey&lt;/code> (version 2). On further investigation it seems that &lt;em>version 2&lt;/em> is a significant update and includes the simplification and shift of the syntax.&lt;/p>
&lt;p>Well I guess I had better update my scripts then!.&lt;/p>
&lt;p>On Windows by default an &lt;code>ahk&lt;/code> extension offers to open &lt;strong>notepad&lt;/strong> which initially I did as I thought the changes would be simple enough and actually notepad isn&amp;rsquo;t too bad these days what with the multiple tabs, dark mode and even multiple undos!. However I quickly became frustrated with the lack of syntax highlighting / indenting e.t.c. I have used a specific &lt;code>ahk&lt;/code> editor in the past, something like &lt;code>SciTE4AutoHotkey&lt;/code> but this time I thought I would break out &lt;code>emacs&lt;/code> and see what it had to offer.&lt;/p>
&lt;p>Initially loading the &lt;code>ahk&lt;/code> script started fundamental mode and of course had no syntax highlighting. Running &lt;code>list-packages&lt;/code> and then a quick search for &lt;em>AutoHotkey&lt;/em> brought up the only package available, which was &lt;strong>&lt;code>ahk-mode&lt;/code>&lt;/strong>. This package seems pretty straightforward and although it was last updated in 2016 (so won&amp;rsquo;t cater too well with the AutoHotkey v2 shift in syntax) the syntax highlighting, commenting and indenting were just fine enough for me. It also seems to be well integrated with &lt;code>company-mode&lt;/code> and although I&amp;rsquo;m not a great fan of &lt;code>company-mode&lt;/code> in general in this case it might actually be useful.&lt;/p>
&lt;p>I was now able to comfortably convert my scripts to AutoHotKey v2, for example:&lt;/p>
&lt;p>&lt;strong>version 1&lt;/strong>&lt;/p>
&lt;pre tabindex="0">&lt;code class="language-nil" data-lang="nil">#IfWinActive, ahk_class ArtRage 3
NumpadDiv:: ^z
NumpadMult:: ^y
NumpadHome::Shift
NumpadUp:: Alt
NumpadLeft:: Space
NumpadRight::
GetKeyState, state, h
if state = D
Send {h up}
else
Send {h down}
return
NumpadEnd:: Control
NumpadPgDn:: RButton
&lt;/code>&lt;/pre>&lt;p>&lt;strong>version 2&lt;/strong>&lt;/p>
&lt;pre tabindex="0">&lt;code class="language-nil" data-lang="nil">#HotIf WinActive(&amp;#34;ahk_class ArtRage 3&amp;#34;)
NumpadDiv:: ^z
NumpadMult:: ^y
NumpadHome::Shift
NumpadUp:: Alt
NumpadLeft:: Space
NumpadRight::
{
if GetKeyState(&amp;#34;h&amp;#34;)
Send &amp;#34;{h up}&amp;#34;
else
Send &amp;#34;{h down}&amp;#34;
}
NumpadEnd:: Control
NumpadPgDn:: RButton
&lt;/code>&lt;/pre>&lt;p>While I was in amongst the scripts I decided to add in something extra and that is to map the &lt;strong>Capslock&lt;/strong> key to the &lt;strong>Control&lt;/strong> key; a not uncommon mapping for a regular emacs user.&lt;/p>
&lt;p>So I added in the following line and plonked the script into the Windows startup folder:&lt;/p>
&lt;pre tabindex="0">&lt;code class="language-nil" data-lang="nil">Capslock::Control
&lt;/code>&lt;/pre></description></item><item><title>Reverting Buffers</title><link>https://www.emacs.dyerdwelling.family/emacs/20230821125951-emacs--reverting-buffers/</link><pubDate>Mon, 21 Aug 2023 12:59:00 +0100</pubDate><author>James Dyer</author><guid>https://www.emacs.dyerdwelling.family/emacs/20230821125951-emacs--reverting-buffers/</guid><description>&lt;p>It is not uncommon for me to want to revert my current buffer.&lt;/p>
&lt;figure class="emacs-img">&lt;img src="https://www.emacs.dyerdwelling.family/emacs/20230821125951-emacs--Reverting-Buffers.jpg">
&lt;/figure>
&lt;p>For a long while I have used &lt;code>(find-alternate-file)&lt;/code> which by default is bound to &lt;code>C-x C-v&lt;/code>&lt;/p>
&lt;p>Recently I switched to mapping this keybinding to &lt;code>revert-buffer&lt;/code> but now I have found out about:&lt;/p>
&lt;pre tabindex="0">&lt;code class="language-nil" data-lang="nil">(revert-buffer-quick &amp;amp;optional AUTO-SAVE)
Like ‘revert-buffer’, but asks for less confirmation.
If the current buffer is visiting a file, and the buffer is not
modified, no confirmation is required.
&lt;/code>&lt;/pre>&lt;p>It is bound to &lt;code>C-x x g&lt;/code> and has the added benefit of not always asking for confirmation!&lt;/p>
&lt;p>I will try and add this new command to my muscle memory but shall I just unbind my old &lt;code>revert-buffer&lt;/code> key-mapping or rebind &lt;code>C-x C-v&lt;/code> to this new function also?, in other words should I fully commit?&lt;/p></description></item><item><title>Fun With Emacs Theming Using Alpha Background on Wayland</title><link>https://www.emacs.dyerdwelling.family/emacs/20230819105507-emacs--fun-with-emacs-theming-using-alpha-background-wayland-sway/</link><pubDate>Sat, 19 Aug 2023 10:55:00 +0100</pubDate><author>James Dyer</author><guid>https://www.emacs.dyerdwelling.family/emacs/20230819105507-emacs--fun-with-emacs-theming-using-alpha-background-wayland-sway/</guid><description>&lt;p>Simply put here is me playing around with emacs transparency using &lt;code>alpha-background&lt;/code> to set up &lt;strong>sway&lt;/strong> the way I want it to look - I think there is some kind of well known term for this! (no &lt;code>neofetch&lt;/code> involved 🙂)&lt;/p>
&lt;figure>&lt;img src="https://www.emacs.dyerdwelling.family/emacs/20230819105507-emacs--Fun-With-Emacs-Theming-Using-Alpha-Background-Wayland-Sway/2023-08-14-21-08-41.jpg" width="100%">
&lt;/figure>
&lt;figure>&lt;img src="https://www.emacs.dyerdwelling.family/emacs/20230819105507-emacs--Fun-With-Emacs-Theming-Using-Alpha-Background-Wayland-Sway/2023-08-16-14-02-05.jpg" width="100%">
&lt;/figure>
&lt;figure>&lt;img src="https://www.emacs.dyerdwelling.family/emacs/20230819105507-emacs--Fun-With-Emacs-Theming-Using-Alpha-Background-Wayland-Sway/2023-08-15-20-18-36.jpg" width="100%">
&lt;/figure>
&lt;figure>&lt;img src="https://www.emacs.dyerdwelling.family/emacs/20230819105507-emacs--Fun-With-Emacs-Theming-Using-Alpha-Background-Wayland-Sway/2023-08-15-20-30-04.jpg" width="100%">
&lt;/figure>
&lt;figure>&lt;img src="https://www.emacs.dyerdwelling.family/emacs/20230819105507-emacs--Fun-With-Emacs-Theming-Using-Alpha-Background-Wayland-Sway/2023-08-15-20-40-05.jpg" width="100%">
&lt;/figure>
&lt;figure>&lt;img src="https://www.emacs.dyerdwelling.family/emacs/20230819105507-emacs--Fun-With-Emacs-Theming-Using-Alpha-Background-Wayland-Sway/2023-08-16-13-01-00.jpg" width="100%">
&lt;/figure>
&lt;figure>&lt;img src="https://www.emacs.dyerdwelling.family/emacs/20230819105507-emacs--Fun-With-Emacs-Theming-Using-Alpha-Background-Wayland-Sway/2023-08-16-13-01-39.jpg" width="100%">
&lt;/figure>
&lt;figure>&lt;img src="https://www.emacs.dyerdwelling.family/emacs/20230819105507-emacs--Fun-With-Emacs-Theming-Using-Alpha-Background-Wayland-Sway/2023-08-16-20-23-10.jpg" width="100%">
&lt;/figure>
&lt;figure>&lt;img src="https://www.emacs.dyerdwelling.family/emacs/20230819105507-emacs--Fun-With-Emacs-Theming-Using-Alpha-Background-Wayland-Sway/2023-08-17-22-00-35.jpg" width="100%">
&lt;/figure></description></item><item><title>No Bell For Me</title><link>https://www.emacs.dyerdwelling.family/emacs/20230817170149-emacs--no-bell-for-me/</link><pubDate>Thu, 17 Aug 2023 17:01:00 +0100</pubDate><author>James Dyer</author><guid>https://www.emacs.dyerdwelling.family/emacs/20230817170149-emacs--no-bell-for-me/</guid><description>&lt;p>Now I have set &lt;code>(pixel-scroll-precision-mode 1)&lt;/code> which comes with emacs 29 this has led to an unexpected issue in that I now often get the following messages which cause a bell sound to be generated each time:&lt;/p>
&lt;figure class="emacs-img">&lt;img src="https://www.emacs.dyerdwelling.family/emacs/20230817170149-emacs--No-Bell-For-Me.jpg">
&lt;/figure>
&lt;pre tabindex="0">&lt;code class="language-nil" data-lang="nil">pixel-scroll-precision-scroll-up-page: Beginning of buffer [22 times]
pixel-scroll-precision-scroll-down-page: End of buffer [54 times]
&lt;/code>&lt;/pre>&lt;p>I had turned to &lt;code>(setq visible-bell t)&lt;/code> in the past but this generates an annoying flash each time rather than an annoying bell sound.&lt;/p>
&lt;p>There is a solution to this of course and that is &lt;code>(setq ring-bell-function 'ignore)&lt;/code> in that we are silent when a visual bell is rung, ah pure bliss 🙂🔔&lt;/p></description></item><item><title>Emacs 29.1 Transparency Alpha On Sway</title><link>https://www.emacs.dyerdwelling.family/emacs/20230812141900-emacs--emacs-29-1-transparency-alpha-on-sway/</link><pubDate>Sat, 12 Aug 2023 15:36:00 +0100</pubDate><author>James Dyer</author><guid>https://www.emacs.dyerdwelling.family/emacs/20230812141900-emacs--emacs-29-1-transparency-alpha-on-sway/</guid><description>&lt;p>I have just been been going through the new features added to emacs 29.1 and have been trying out the transparency or &lt;code>alpha-background&lt;/code> on &lt;code>sway&lt;/code>.&lt;/p>
&lt;blockquote>
&lt;p>&amp;ldquo;&lt;code>alpha-background&lt;/code> controls the opacity of the text background when running on a
composited display.&amp;rdquo;.&lt;/p>
&lt;/blockquote>
&lt;p>I was keenly looking for an emacs improvement to the &lt;code>swaywm&lt;/code> built in compositing method which in the case of emacs would make transparent the whole window including text which of course isn&amp;rsquo;t ideal.&lt;/p>
&lt;p>I had defined the following in my &lt;code>sway&lt;/code> config file:&lt;/p>
&lt;pre tabindex="0">&lt;code class="language-nil" data-lang="nil">for_window [class=&amp;#34;Emacs&amp;#34;] opacity 0.92
&lt;/code>&lt;/pre>&lt;p>&lt;code>0.92&lt;/code> seemed to be the perfect balance between wallpaper visibility and text legibility.&lt;/p>
&lt;p>I am now going to compare the emacs &lt;code>alpha-background&lt;/code> method with an equivalent &lt;code>sway&lt;/code> emacs compositing opacity.&lt;/p>
&lt;p>So for emacs I am going to set something like:&lt;/p>
&lt;pre tabindex="0">&lt;code class="language-nil" data-lang="nil">(set-frame-parameter nil &amp;#39;alpha-background 70)
(add-to-list &amp;#39;default-frame-alist &amp;#39;(alpha-background . 70))
&lt;/code>&lt;/pre>&lt;p>See the following images for a comparison (can you guess which is which?) - I would suggest opening each image in separate browser tabs and comparing that way.&lt;/p>
&lt;p>Note/Disclaimer: I chose opacity values that displayed an equivalent wallpaper visibility through emacs and as it turned out the opacity values were different which I suspect relates to the way the &lt;code>sway&lt;/code> compositor is processing its opacity value rather than the way emacs does it but the demonstration fundamentally should still be a valid one.&lt;/p>
&lt;figure>&lt;img src="https://www.emacs.dyerdwelling.family/emacs/20230812141900-emacs--Emacs-29-1-Transparency-Alpha-On-Sway-Old.jpg" width="100%">
&lt;/figure>
&lt;figure>&lt;img src="https://www.emacs.dyerdwelling.family/emacs/20230812141900-emacs--Emacs-29-1-Transparency-Alpha-On-Sway.jpg" width="100%">
&lt;/figure>
&lt;p>and here is a more detailed comparison on a selected screen region:&lt;/p>
&lt;figure>&lt;img src="https://www.emacs.dyerdwelling.family/emacs/20230812141900-emacs--Emacs-29-1-Transparency-Alpha-On-Sway-Old-s.jpg" width="100%">
&lt;/figure>
&lt;figure>&lt;img src="https://www.emacs.dyerdwelling.family/emacs/20230812141900-emacs--Emacs-29-1-Transparency-Alpha-On-Sway-s.jpg" width="100%">
&lt;/figure>
&lt;p>Suffice it to say that this is a very nice improvement and certainly something I shall be using from now on.&lt;/p></description></item><item><title>Saving My Favourite Wallpapers</title><link>https://www.emacs.dyerdwelling.family/emacs/20230811043547-emacs--saving-favourite-backgrounds/</link><pubDate>Fri, 11 Aug 2023 05:22:00 +0100</pubDate><author>James Dyer</author><guid>https://www.emacs.dyerdwelling.family/emacs/20230811043547-emacs--saving-favourite-backgrounds/</guid><description>&lt;p>I am using the &lt;code>styli.sh&lt;/code> script as my wallpaper changer in &lt;strong>sway&lt;/strong> and with a little transparency and wallpapers from &lt;strong>unsplash&lt;/strong> I can quickly switch randomly through my wallpaper images and get things looking pretty nice.&lt;/p>
&lt;figure class="emacs-img">&lt;img src="https://www.emacs.dyerdwelling.family/emacs/20230811043547-emacs--Saving-Favourite-Backgrounds.jpg">
&lt;/figure>
&lt;p>I tend to switch a few times before I find one that I like, but that image tends to disappear when I restart my laptop and the wallpaper gets reset. What I would like to do is to have the ability to send the current wallpaper to a wallpaper favourite folder to build up a chosen set of images.&lt;/p>
&lt;p>The first step to achieving this would be to add a couple of lines to the &lt;code>styli.sh&lt;/code> bash script to copy the current wallpaper image to a defined location. I am guessing that sway stores this image somewhere but as &lt;code>styli.sh&lt;/code> handles all the variables and logic I might as well add in the following:&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-bash" data-lang="bash">&lt;span style="display:flex;">&lt;span>swaymsg output &lt;span style="color:#e6db74">&amp;#34;*&amp;#34;&lt;/span> bg &lt;span style="color:#e6db74">&amp;#34;&lt;/span>$WALLPAPER&lt;span style="color:#e6db74">&amp;#34;&lt;/span> &lt;span style="color:#e6db74">&amp;#34;&lt;/span>$MODE&lt;span style="color:#e6db74">&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>rm ~/wallpaper-faves*.&lt;span style="color:#f92672">{&lt;/span>jpg,jpeg,png,gif&lt;span style="color:#f92672">}&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>cp &lt;span style="color:#e6db74">&amp;#34;&lt;/span>$WALLPAPER&lt;span style="color:#e6db74">&amp;#34;&lt;/span> ~/wallpaper-faves-&lt;span style="color:#e6db74">${&lt;/span>WALLPAPER##*/&lt;span style="color:#e6db74">}&lt;/span>
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>and now for some emacs elisp to move this wallpaper image into a favourite folder :&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/copy-background-to-faves ()
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#e6db74">&amp;#34;Copy the current sway background to wallpaper faves folder&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* ((source-folder &lt;span style="color:#e6db74">&amp;#34;/path/to/home/&amp;#34;&lt;/span>)
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> (faves-folder &lt;span style="color:#e6db74">&amp;#34;/path/to/wallpaper/faves/&amp;#34;&lt;/span>)
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> (image-files (&lt;span style="color:#a6e22e">directory-files&lt;/span> source-folder &lt;span style="color:#66d9ef">nil&lt;/span> &lt;span style="color:#e6db74">&amp;#34;^wallpaper-faves.*\\.\\(jpg\\|jpeg\\|png\\|gif\\)$&amp;#34;&lt;/span> &lt;span style="color:#66d9ef">nil&lt;/span> &lt;span style="color:#66d9ef">nil&lt;/span>)))
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> (dolist (image-file image-files)
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> (&lt;span style="color:#a6e22e">rename-file&lt;/span> (&lt;span style="color:#a6e22e">concat&lt;/span> source-folder image-file) (&lt;span style="color:#a6e22e">concat&lt;/span> faves-folder image-file) &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>&lt;/span>&lt;span style="display:flex;">&lt;span> )
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>Now over time my &lt;code>faves-folder&lt;/code> will build up with all my cherished wallpapers and I can begin the process again!&lt;/p></description></item><item><title>Simplifying My Modeline</title><link>https://www.emacs.dyerdwelling.family/emacs/20230806091105-emacs--simplifying-my-modeline/</link><pubDate>Sun, 06 Aug 2023 09:22:00 +0100</pubDate><author>James Dyer</author><guid>https://www.emacs.dyerdwelling.family/emacs/20230806091105-emacs--simplifying-my-modeline/</guid><description>&lt;p>After watching the latest informative video from Protesilaos Stavrou (Prot):&lt;/p>
&lt;figure class="emacs-img">&lt;img src="https://www.emacs.dyerdwelling.family/emacs/20230806091105-emacs--Simplifying-My-Modeline.jpg">
&lt;/figure>
&lt;p>&lt;a href="https://www.youtube.com/watch?v=Qf_DLPIA9Cs&amp;t=4s">Emacs: write custom mode line&lt;/a>&lt;/p>
&lt;p>I decided to try and create my own simple mode line incorporating the features I regularly glance at throughout a day. Having &lt;code>mu4e&lt;/code> and &lt;code>magit&lt;/code> details on my mode line plus other mysterious characters seems a bit much.&lt;/p>
&lt;p>After adapting the examples given in the video I created the following:&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-default mode-line-format
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#f92672">&amp;#39;&lt;/span>(&lt;span style="color:#e6db74">&amp;#34;%e&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#e6db74">&amp;#34; %o &amp;#34;&lt;/span>
&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> my-modeline-buffer-name
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> my-modeline-major-mode))
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>(defvar-local my-modeline-buffer-name
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#f92672">&amp;#39;&lt;/span>(:eval
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> (when (mode-line-window-selected-p)
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> (&lt;span style="color:#a6e22e">propertize&lt;/span> (&lt;span style="color:#a6e22e">format&lt;/span> &lt;span style="color:#e6db74">&amp;#34; %s &amp;#34;&lt;/span> (&lt;span style="color:#a6e22e">buffer-name&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:#f92672">&amp;#39;&lt;/span>(&lt;span style="color:#66d9ef">t&lt;/span> :background &lt;span style="color:#e6db74">&amp;#34;#3355bb&amp;#34;&lt;/span> :foreground &lt;span style="color:#e6db74">&amp;#34;white&amp;#34;&lt;/span> :inherit bold))))
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#e6db74">&amp;#34;Mode line construct to display the buffer name.&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">put&lt;/span> &lt;span style="color:#e6db74">&amp;#39;my-modeline-buffer-name&lt;/span> &lt;span style="color:#e6db74">&amp;#39;risky-local-variable&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>(defvar-local my-modeline-major-mode
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#f92672">&amp;#39;&lt;/span>(:eval
&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">propertize&lt;/span> &lt;span style="color:#e6db74">&amp;#34;λ&amp;#34;&lt;/span> &lt;span style="color:#e6db74">&amp;#39;face&lt;/span> &lt;span style="color:#e6db74">&amp;#39;shadow&lt;/span>)
&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">propertize&lt;/span> (&lt;span style="color:#a6e22e">capitalize&lt;/span> (&lt;span style="color:#a6e22e">symbol-name&lt;/span> major-mode)) &lt;span style="color:#e6db74">&amp;#39;face&lt;/span> &lt;span style="color:#e6db74">&amp;#39;bold&lt;/span>)))
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#e6db74">&amp;#34;Mode line construct to display the major mode.&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">put&lt;/span> &lt;span style="color:#e6db74">&amp;#39;my-modeline-major-mode&lt;/span> &lt;span style="color:#e6db74">&amp;#39;risky-local-variable&lt;/span> &lt;span style="color:#66d9ef">t&lt;/span>)
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>I left in &lt;strong>&lt;code>%e&lt;/code>&lt;/strong> as suggested to show an error message if memory was full.&lt;/p>
&lt;p>&lt;strong>&lt;code>%o&lt;/code>&lt;/strong> for how far through the buffer I am. I have my scroll bars disabled but always find it useful to see where I am in a file especially when coding or within a large org file (for example when applying some repetitive macros seeing 80% through the buffer is quite motivating!)&lt;/p>
&lt;p>&lt;strong>&lt;code>%*&lt;/code>&lt;/strong> to indicate if a file has been modified using the asterisk convention which is common in many other programs and has the side-effect of removing all other cryptic characters that I don&amp;rsquo;t understand. In the future I can always add some back in again but at that point I will (hopefully) understand what I am reintroducing.&lt;/p>
&lt;p>Finally it is just the case of adding the buffer name and major mode which comes straight from the examples given in the video.&lt;/p>
&lt;p>I especially like the control over the file name colour profile in combination with the use of the &lt;code>mode-line-window-selected-p&lt;/code> function (introduced as part of emacs 29) which helps to identify the buffer in focus.&lt;/p>
&lt;p>As with all these things I will use it and adapt accordingly and hopefully not feel the need to add everything back in again!, I think I like this simplicity for now.&lt;/p></description></item><item><title>Showing Org Agenda For The Year</title><link>https://www.emacs.dyerdwelling.family/emacs/20230722194000-emacs--showing-org-agenda-for-the-year/</link><pubDate>Fri, 04 Aug 2023 06:21:00 +0100</pubDate><author>James Dyer</author><guid>https://www.emacs.dyerdwelling.family/emacs/20230722194000-emacs--showing-org-agenda-for-the-year/</guid><description>&lt;p>I am just starting to include more of my org files into &lt;code>org-agenda&lt;/code>. By default &lt;code>C-c a a&lt;/code> gives a show for the next 7 days but I think for now I would like something of a more calendarish overview with a long form look of scheduled and completed tasks.&lt;/p>
&lt;figure class="emacs-img">&lt;img src="https://www.emacs.dyerdwelling.family/emacs/20230722194000-emacs--Showing-Org-Agenda-For-The-Year.jpg">
&lt;/figure>
&lt;p>I thought I would write an &lt;code>elisp&lt;/code> function:&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 display-year-agenda (&lt;span style="color:#66d9ef">&amp;amp;optional&lt;/span> year)
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#e6db74">&amp;#34;Display an agenda entry for a whole year.&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> (interactive (&lt;span style="color:#a6e22e">list&lt;/span> (&lt;span style="color:#a6e22e">read-string&lt;/span> &lt;span style="color:#e6db74">&amp;#34;Enter the year: &amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> (&lt;span style="color:#a6e22e">format-time-string&lt;/span> &lt;span style="color:#e6db74">&amp;#34;%Y&amp;#34;&lt;/span> (&lt;span style="color:#a6e22e">current-time&lt;/span>)))))
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> (setq year (&lt;span style="color:#a6e22e">string-to-number&lt;/span> year))
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> (org-agenda-list)
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> (org-agenda-year-view year)
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> (setq this-year (&lt;span style="color:#a6e22e">string-to-number&lt;/span> (&lt;span style="color:#a6e22e">format-time-string&lt;/span> &lt;span style="color:#e6db74">&amp;#34;%Y&amp;#34;&lt;/span> (&lt;span style="color:#a6e22e">current-time&lt;/span>))))
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> (when (&lt;span style="color:#a6e22e">=&lt;/span> year this-year)
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> (org-agenda-goto-today)
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> (recenter-top-bottom &lt;span style="color:#ae81ff">10&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">;; Bind a key for easy access&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>(global-set-key (kbd &lt;span style="color:#e6db74">&amp;#34;C-c y&amp;#34;&lt;/span>) &lt;span style="color:#e6db74">&amp;#39;display-year-agenda&lt;/span>)
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>The default prompt is the current year but with the ability to enter any year desired. I also thought that if calling up this current year then I would want to highlight today and recenter a little.&lt;/p>
&lt;p>As with all things I will give this a go for a while and see if it fits nicely into my workflow.&lt;/p></description></item><item><title>Shrinking Media With The Help Of Emacs</title><link>https://www.emacs.dyerdwelling.family/emacs/20230730102057-emacs--shrinking-media-with-the-help-of-emacs/</link><pubDate>Sun, 30 Jul 2023 15:28:00 +0100</pubDate><author>James Dyer</author><guid>https://www.emacs.dyerdwelling.family/emacs/20230730102057-emacs--shrinking-media-with-the-help-of-emacs/</guid><description>&lt;p>Often I find myself refining my collection of photos and family videos, usually with the following process:&lt;/p>
&lt;figure class="emacs-img">&lt;img src="https://www.emacs.dyerdwelling.family/emacs/20230730102057-emacs--Shrinking-Media-With-The-Help-Of-Emacs.jpg">
&lt;/figure>
&lt;ul>
&lt;li>removing media clutter&lt;/li>
&lt;li>removing any duplicates&lt;/li>
&lt;li>tagging as necessary&lt;/li>
&lt;li>compressing where reasonable&lt;/li>
&lt;li>renaming to a more &lt;code>denote&lt;/code> format&lt;/li>
&lt;/ul>
&lt;p>Emacs and the associated muscle memory greatly helps with this process.&lt;/p>
&lt;p>Firstly though I leverage other applications, for example, duplicate removal and tagging takes place through &lt;code>digikam&lt;/code>, media clutter through &lt;code>thunar&lt;/code> and &lt;code>gthumb&lt;/code>&lt;/p>
&lt;p>My emacs process is then:&lt;/p>
&lt;ul>
&lt;li>
&lt;p>open media directory in &lt;code>dired&lt;/code>&lt;/p>
&lt;/li>
&lt;li>
&lt;p>sort by size using &lt;code>C-u s S&lt;/code> - the big S is setting the ls format by size&lt;/p>
&lt;/li>
&lt;li>
&lt;p>&lt;code>M-&amp;lt;&lt;/code> &lt;code>(beginning-of-buffer)&lt;/code> - so I can see the largest files&lt;/p>
&lt;/li>
&lt;li>
&lt;p>if I want to squish a video I will probably want to preview it first in &lt;strong>mpv&lt;/strong>, so I do this asynchronously using &lt;code>&amp;amp;&lt;/code> &lt;code>(dired-do-async-shell-command)&lt;/code> and relying on my defined &lt;code>dired-guess-shell-alist-user&lt;/code> setup and then selecting &lt;code>i&lt;/code> (mpv shortcut) to have a quick peek at the original dimensions. Typically if the video is 1920x1080 then I like to halve the dimensions which when running through &lt;code>ffmpeg&lt;/code> saves about 80-90% on disk space.&lt;/p>
&lt;p>I have my own &lt;code>ffmpeg&lt;/code> bash scripts which I call through &lt;code>dwim-shell-command&lt;/code> so a command search for something like &lt;strong>dw sh&lt;/strong> &lt;code>my/dwim-video-shrink&lt;/code> will dired execute the file under cursor or over a list of marked files.&lt;/p>
&lt;/li>
&lt;li>
&lt;p>for images I again use &lt;code>dwim-shell-command&lt;/code> with a quick command search for something like &lt;strong>dw cr&lt;/strong> &lt;code>my/dwim-picture-crush&lt;/code> which isn&amp;rsquo;t always as brutal as it might suggest but a script that I regularly modify when compressing images, setting the compression parameters appropriately.&lt;/p>
&lt;p>If I need to quickly inspect the image then I could open it in emacs but I prefer again to open in gthumb using &lt;code>&amp;amp;&lt;/code>&lt;/p>
&lt;/li>
&lt;li>
&lt;p>Possibly at the end of a directory compress I might check the directory size by &lt;code>?&lt;/code> in &lt;code>dired&lt;/code> which I have mapped to an elisp &lt;code>my/get-file-size&lt;/code> function that runs the &lt;code>async-shell-command&lt;/code> &lt;code>du -&lt;/code>&lt;/p>
&lt;/li>
&lt;li>
&lt;p>and of course my files can be easily renamed through &lt;code>wdired&lt;/code>&lt;/p>
&lt;/li>
&lt;/ul>
&lt;p>As I am using a tiling window manager all of this can be accomplished keyboard only and on a single screen!&lt;/p></description></item><item><title>Defining Categories in Org Files for Hugo</title><link>https://www.emacs.dyerdwelling.family/emacs/20230513130655-emacs--hugo-adding-categories/</link><pubDate>Fri, 21 Jul 2023 13:02:00 +0100</pubDate><author>James Dyer</author><guid>https://www.emacs.dyerdwelling.family/emacs/20230513130655-emacs--hugo-adding-categories/</guid><description>&lt;p>I use Hugo to generate my web site and I made a decision early on to use &lt;code>ox-hugo&lt;/code> withing emacs and to manage a single large &lt;code>org&lt;/code> file with each subheading a blog post and each subheading tag representing&amp;hellip; well, tags! I was aware of the concept of defining categories but decided to sort that out at a later date until I really understood what I was doing - this is a common thing that I do.&lt;/p>
&lt;figure class="emacs-img">&lt;img src="https://www.emacs.dyerdwelling.family/emacs/20230513130655-emacs--Hugo-Adding-Categories.jpg">
&lt;/figure>
&lt;p>Well now I think I understand what I&amp;rsquo;m doing and now I want to create categories for my web site and as it turns out managing my posts within org files means that this can be achieved very easily.&lt;/p>
&lt;p>For example, I have the following set up in my org file:&lt;/p>
&lt;pre tabindex="0">&lt;code class="language-nil" data-lang="nil">* Emacs [0/0] :emacs:linux:
** DONE Initial focus in Occur Buffer :occur:elisp:2023:
** DONE Cursor Blinking Rate :2023:
&lt;/code>&lt;/pre>&lt;p>This means that the subheadings / posts under the top level &lt;code>Emacs&lt;/code> heading inherit the &lt;code>emacs&lt;/code> and &lt;code>linux&lt;/code> tags and then define any extra specific to each post as desired. As these tags are defined at the top level then you could almost say they are defining a more broad definition, lets say a category!&lt;/p>
&lt;p>So how do I change these top level tags to categories?, well I define the following:&lt;/p>
&lt;pre tabindex="0">&lt;code class="language-nil" data-lang="nil">* Emacs [0/0] :@emacs:@linux:
&lt;/code>&lt;/pre>&lt;p>It is just a very slight change to the org tag definition after which I just need to re-export all my blog posts / subheadings using the &lt;code>ox-hugo&lt;/code> dispatcher.&lt;/p>
&lt;p>The generated markdown files now contain the following front matter which Hugo can now process accordingly:&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>tags = [&amp;#34;occur&amp;#34;, &amp;#34;elisp&amp;#34;, &amp;#34;2023&amp;#34;]
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>categories = [&amp;#34;emacs&amp;#34;, &amp;#34;linux&amp;#34;]
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>Simple! and in fact you can see an example of how the categories are incorporated in this very web site!&lt;/p></description></item><item><title>Porting Dolphin Context Sensitive Scripts To Thunar</title><link>https://www.emacs.dyerdwelling.family/emacs/20230709144332-emacs--porting-dolphin-scripts-to-thunar/</link><pubDate>Mon, 10 Jul 2023 20:03:00 +0100</pubDate><author>James Dyer</author><guid>https://www.emacs.dyerdwelling.family/emacs/20230709144332-emacs--porting-dolphin-scripts-to-thunar/</guid><description>&lt;p>I have decided to switch my linux file manager from Dolphin to Thunar and this means transferring the context sensitive menus I have set up through KDE Plasma to using Thunar&amp;rsquo;s custom actions.&lt;/p>
&lt;figure class="emacs-img">&lt;img src="https://www.emacs.dyerdwelling.family/emacs/20230709144332-emacs--Porting-Dolphin-Scripts-To-Thunar.jpg">
&lt;/figure>
&lt;p>I quickly figured out that I can just modify &lt;code>$HOME/.config/Thunar/uca.xml&lt;/code> to add in new Thunar actions, so all I need to do is to firstly work out how to perform the translation from Dolphin to Thunar actions and then how to set up emacs in such a way that would get me to a quick solution.&lt;/p>
&lt;p>Dolphin relies on files within &lt;code>$HOME/.local/share/kservices5/ServiceMenus&lt;/code> to define the context menus and I have separate files for each MIME type:&lt;/p>
&lt;ul>
&lt;li>AudioMenu.desktop&lt;/li>
&lt;li>PictureMenu.desktop&lt;/li>
&lt;li>VideoMenu.desktop&lt;/li>
&lt;/ul>
&lt;p>Each one has an actions list near the top defining a link to multiple script command definitions throughout the file. So I think all I need to do is to extract out the actions, for example :&lt;/p>
&lt;pre tabindex="0">&lt;code class="language-nil" data-lang="nil">Actions=PictureConvert;PictureInfo;PictureRotateLeft;PictureRotateRight;PictureScale;PictureAutoColour;PictureCrush;PictureMontage;PictureRotateFlip;PictureUpscale;PictureGetText;PictureOrientation;PictureCorrect;Picture2pdf
&lt;/code>&lt;/pre>&lt;p>and apply them to the &lt;code>uca.xml&lt;/code> file using an example I had already created as a prototype :&lt;/p>
&lt;pre tabindex="0">&lt;code class="language-nil" data-lang="nil">&amp;lt;action&amp;gt;
&amp;lt;icon&amp;gt;&amp;lt;/icon&amp;gt;
&amp;lt;name&amp;gt;PictureConvert&amp;lt;/name&amp;gt;
&amp;lt;submenu&amp;gt;&amp;lt;/submenu&amp;gt;
&amp;lt;unique-id&amp;gt;1688724015024216-1&amp;lt;/unique-id&amp;gt;
&amp;lt;command&amp;gt;ServiceConsole PictureConvert %F&amp;lt;/command&amp;gt;
&amp;lt;description&amp;gt;PictureConvert&amp;lt;/description&amp;gt;
&amp;lt;range&amp;gt;*&amp;lt;/range&amp;gt;
&amp;lt;patterns&amp;gt;*&amp;lt;/patterns&amp;gt;
&amp;lt;image-files/&amp;gt;
&amp;lt;/action&amp;gt;
&lt;/code>&lt;/pre>&lt;p>The &lt;code>ServiceConsole&lt;/code> script is just a wrapper around each bash script and lets me choose the terminal, shell and anything else I would like to wrap around:&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-bash" data-lang="bash">&lt;span style="display:flex;">&lt;span>&lt;span style="color:#75715e">#! /bin/bash
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#75715e">&lt;/span>konsole -e &lt;span style="color:#e6db74">&amp;#34;/bin/bash -c &amp;#39;&lt;/span>$*&lt;span style="color:#e6db74">; echo; echo &amp;#34;&lt;/span>Finished&lt;span style="color:#e6db74">&amp;#34;; read input&amp;#39;&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>Now I know what needs to be done I think I will create a window in emacs on the left containing the Thunar custom action file (&lt;code>uca.xml&lt;/code>) and &lt;code>*scratch*&lt;/code> on the right and then define a macro to step through each action and apply the script names to the &lt;code>uca.xml&lt;/code> file.&lt;/p>
&lt;p>I can now extract the &lt;code>Actions&lt;/code> list from each file, paste it into the scratch buffer and then &lt;code>forward-word&lt;/code> copy to the kill ring in turn and for each script name copy and paste to a new copy of the xml example template.&lt;/p>
&lt;p>The only other aspect I need to pay attention to is the transfer of the MIME types to &lt;code>&amp;lt;image-files/&amp;gt; &amp;lt;audio-files/&amp;gt; &amp;lt;video-files/&amp;gt;&lt;/code>&lt;/p>
&lt;p>There is a &lt;code>&amp;lt;unique-id&amp;gt;&lt;/code> set for each action but I think that is more of an internal Thunar Id and I could just copy and paste the existing Id as part of my macro and it still works.&lt;/p>
&lt;p>So for a task that I thought would take quite a while and would be convoluted in the end turned out to be pretty trivial and this was mainly due to at first spending some time working out how to translate the configuration files (with a little prototyping) and then applying the transformation using emacs.&lt;/p>
&lt;p>These context sensitive menu bash scripts transforming image, video and audio files can also be run off the command line and I can run them in emacs using the &lt;strong>dwim-shell-command&lt;/strong> package. This means that I now have a consistent media transformation set of tools across all my file managers!&lt;/p>
&lt;p>I will go into more detail on my use of &lt;strong>dwim-shell-command&lt;/strong> in a future blog post ;)&lt;/p></description></item><item><title>Dired Duplicate Here Revisited</title><link>https://www.emacs.dyerdwelling.family/emacs/20230606213531-emacs--dired-duplicate-here-revisited/</link><pubDate>Tue, 04 Jul 2023 21:13:00 +0100</pubDate><author>James Dyer</author><guid>https://www.emacs.dyerdwelling.family/emacs/20230606213531-emacs--dired-duplicate-here-revisited/</guid><description>&lt;p>It is not uncommon for me to want the ability to quickly duplicate a file, especially if I am hacking around and just want a quick snapshot of a working file.&lt;/p>
&lt;p>Previously I was using &lt;code>dired&lt;/code> in a slightly convoluted manner, namely:&lt;/p>
&lt;ul>
&lt;li>open dired&lt;/li>
&lt;li>cursor over the desired item to rename&lt;/li>
&lt;li>&lt;code>w&lt;/code> (dired-copy-filename-as-kill)&lt;/li>
&lt;li>&lt;code>C&lt;/code> (dired-do-copy)&lt;/li>
&lt;li>paste from the kill ring (yank)&lt;/li>
&lt;li>modify the filename as desired&lt;/li>
&lt;li>return&lt;/li>
&lt;/ul>
&lt;p>But I never actually used this functionality so I think its time for a different approach.&lt;/p>
&lt;p>Since I have recently been delving into &lt;strong>elisp&lt;/strong> I thought I would write an interactive function:&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/dired-duplicate-file (arg)
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#e6db74">&amp;#34;Duplicate the current file in Dired.&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> (interactive &lt;span style="color:#e6db74">&amp;#34;p&amp;#34;&lt;/span>)
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> (let ((filename (dired-get-filename)))
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> (setq target (&lt;span style="color:#a6e22e">concat&lt;/span> (file-name-sans-extension filename)
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#e6db74">&amp;#34;-old&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> (if (&lt;span style="color:#a6e22e">&amp;gt;&lt;/span> arg &lt;span style="color:#ae81ff">1&lt;/span>) (&lt;span style="color:#a6e22e">number-to-string&lt;/span> arg))
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> (file-name-extension filename &lt;span style="color:#66d9ef">t&lt;/span>)))
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> (if (&lt;span style="color:#a6e22e">file-directory-p&lt;/span> filename)
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> (copy-directory filename target)
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> (&lt;span style="color:#a6e22e">copy-file&lt;/span> filename target))
&lt;/span>&lt;/span>&lt;span style="display:flex;">&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>&lt;/span>&lt;span style="display:flex;">&lt;span>(&lt;span style="color:#a6e22e">define-key&lt;/span> dired-mode-map (kbd &lt;span style="color:#e6db74">&amp;#34;C-c d&amp;#34;&lt;/span>) &lt;span style="color:#e6db74">&amp;#39;my/dired-duplicate-file&lt;/span>)
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>The function first gets the name of the current file in the Dired buffer using `dired-get-filename`. It then creates a new filename for the duplicated file by appending &amp;ldquo;-old&amp;rdquo; to the base filename (i.e., without the extension), followed by the number specified by the `arg` prefix argument, and the original file extension.&lt;/p>
&lt;p>For example, if the `arg` argument is 3, and the original filename is &amp;ldquo;example.txt&amp;rdquo;, the duplicated filename would become &amp;ldquo;example-old3.txt&amp;rdquo;.&lt;/p>
&lt;p>The function then checks if the file is a directory using `file-directory-p`. If the file is a directory, it copies the entire directory to the new target directory. If the file is a regular file, it copies the file to the new target file.&lt;/p>
&lt;p>The function is interactive, meaning it can be invoked with a key press or command invocation. The function does not return a value.&lt;/p>
&lt;hr>
&lt;p>Could you tell I used ChatGPT to describe this function?, well it was accurate, very precise, but very clinical, possibly useful for the quick documenting of written functions though.&lt;/p>
&lt;p>I prefer the following more human approach:&lt;/p>
&lt;blockquote>
&lt;p>By default a quick flick of the fingers would duplicate rename a file or directory via emacs &lt;code>dired&lt;/code> adding the keyword &lt;code>old&lt;/code> and potentially a numeric value by means of the universal argument.&lt;/p>
&lt;/blockquote>
&lt;p>Most of the time I just want a quick single backup but for more duplicates there is the option of a quick C-u (adds a 4) and a C-u C-u (a 16) e.t.c&lt;/p>
&lt;figure>&lt;img src="https://www.emacs.dyerdwelling.family/emacs/20230606213531-emacs--Dired-Duplicate-Here-Revisited.jpg" width="300px">
&lt;/figure>
&lt;p>That should do me for now, lets see how I get on!&lt;/p></description></item><item><title>Cycling Colours in CSS and Other Files</title><link>https://www.emacs.dyerdwelling.family/emacs/20230623095629-emacs--toggle-colours-css/</link><pubDate>Fri, 23 Jun 2023 11:04:00 +0100</pubDate><author>James Dyer</author><guid>https://www.emacs.dyerdwelling.family/emacs/20230623095629-emacs--toggle-colours-css/</guid><description>&lt;p>I try and keep a simple colour scheme definition for my web site using some CSS variables:&lt;/p>
&lt;figure class="emacs-img">&lt;img src="https://www.emacs.dyerdwelling.family/emacs/20230623095629-emacs--Toggle-Colours-CSS.jpg">
&lt;/figure>
&lt;pre tabindex="0">&lt;code class="language-nil" data-lang="nil">--theme-fg: #2b2d3a;
--theme-bg: #fffbef;
--theme-alt: #ffa500;
&lt;/code>&lt;/pre>&lt;p>&lt;code>rainbow-mode&lt;/code> of course helps to show the colours in situ, but sometimes if I am changing the colour scheme I use &lt;code>list-colors-display&lt;/code> and then copy and paste the hex value. But should I use the name instead?, it is tempting as the colour would be very apparent and readable in the CSS file but then it is difficult to just incrementally tweak a hex value to get the colours to look just right. I also like to use orange, well I always seem to revert back to it but I can never remember the hex value.&lt;/p>
&lt;p>There is an in-built function in emacs which can help me out with all this called &lt;code>css-cycle-color-format&lt;/code> and is bound to C-c C-f&lt;/p>
&lt;p>With the point over the colour, potentially three different CSS color formats are cycled, by name (if possible), hexadecimal, and rgb()/rgba().&lt;/p>
&lt;p>For example I could cycle through orange as thus:&lt;/p>
&lt;pre tabindex="0">&lt;code class="language-nil" data-lang="nil">--theme-alt: orange;
--theme-alt: #ffa500;
--theme-alt: rgb(255, 165, 0);
--theme-alt: orange;
&lt;/code>&lt;/pre>&lt;p>What about a colour / hex value in a non CSS file I hear you say, well although &lt;code>css-cycle-color-format&lt;/code> is bound to &lt;code>css-mode-map&lt;/code> it can be called for other files and in other modes. For example in &lt;code>init.el&lt;/code> calling &lt;code>css-cycle-color-format&lt;/code> seems to work so I guess it could just be rebound to any &lt;code>mode-map&lt;/code> as desired.&lt;/p>
&lt;p>Also if you have a CSS file with a profusion of hex colour values the &lt;code>css-cycle-color-format&lt;/code> command could find the name value for you, for example as a test, putting in a colour that is obviously red :&lt;/p>
&lt;pre tabindex="0">&lt;code class="language-nil" data-lang="nil">--theme-alt: #ff0000;
&lt;/code>&lt;/pre>&lt;p>cycles to:&lt;/p>
&lt;pre tabindex="0">&lt;code class="language-nil" data-lang="nil">--theme-alt: red;
&lt;/code>&lt;/pre></description></item><item><title>Revert Git Permission Changes On Repository Transfer</title><link>https://www.emacs.dyerdwelling.family/emacs/20230617200412-emacs--revert-git-permission-changes-on-transfer/</link><pubDate>Sat, 17 Jun 2023 20:30:00 +0100</pubDate><author>James Dyer</author><guid>https://www.emacs.dyerdwelling.family/emacs/20230617200412-emacs--revert-git-permission-changes-on-transfer/</guid><description>&lt;p>I recently reinstalled my laptop and copied across my git repositories from an external backup drive. However most of my repositories were flagged by &lt;code>magit&lt;/code> as having been &lt;em>updated&lt;/em> mainly with the following issue on multiple files:&lt;/p>
&lt;pre tabindex="0">&lt;code class="language-nil" data-lang="nil">old mode 100644
new mode 100755
&lt;/code>&lt;/pre>&lt;p>I found a fix for a single repository on &lt;code>stackoverflow&lt;/code> but I have quite a few repositories now, so I wrote the following bash script borrowing the core git diff command fix and then ran it in &lt;code>eshell&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-bash" data-lang="bash">&lt;span style="display:flex;">&lt;span>&lt;span style="color:#75715e">#!/bin/bash
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#75715e">&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>LIST&lt;span style="color:#f92672">=&lt;/span>&lt;span style="color:#e6db74">&amp;#34;DCIM/Art/Content/ArtAssets
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#e6db74"> DCIM/Art/Content/ArtRage
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#e6db74"> DCIM/Art/Content/ArtRagePenTool
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#e6db74"> DCIM/Art/Content/ArtRageTabletFriend
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#e6db74"> DCIM/Art/Content/InfinitePainter
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#e6db74"> DCIM/Art/Content/Krita
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#e6db74"> DCIM/content
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#e6db74"> bin
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#e6db74"> publish&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:#66d9ef">for&lt;/span> item in $LIST; &lt;span style="color:#66d9ef">do&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> echo $item
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> cd ~/$item
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> git diff -p -R --no-ext-diff --no-color &lt;span style="color:#ae81ff">\
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#ae81ff">&lt;/span> | grep -E &lt;span style="color:#e6db74">&amp;#34;^(diff|(old|new) mode)&amp;#34;&lt;/span> --color&lt;span style="color:#f92672">=&lt;/span>never &lt;span style="color:#ae81ff">\
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#ae81ff">&lt;/span> | git apply
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#66d9ef">done&lt;/span>
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>This is where the integrated nature of emacs can come in useful, although the &lt;code>stackoverflow&lt;/code> example was the git diff line I wanted to perform this for each of my repositories and to achieve this I just simply grabbed the Path column output of &lt;code>magit-list-repositories&lt;/code> using &lt;code>rectangle-mark-mode&lt;/code> and trimmed down using some macros so I could iterate through each repository directory and run the command each time to clean up.&lt;/p>
&lt;figure class="emacs-img">&lt;img src="https://www.emacs.dyerdwelling.family/emacs/20230617200412-emacs--Revert-Git-Permission-Changes-On-Transfer.jpg">
&lt;/figure></description></item><item><title>Magit Status To Show Tracked Files</title><link>https://www.emacs.dyerdwelling.family/emacs/20230604121310-emacs--magit-insert-tracked-files/</link><pubDate>Thu, 08 Jun 2023 11:05:00 +0100</pubDate><author>James Dyer</author><guid>https://www.emacs.dyerdwelling.family/emacs/20230604121310-emacs--magit-insert-tracked-files/</guid><description>&lt;p>While currently piecing together my git repositories and figuring out which files to commit I am invariably going to have some &lt;strong>Untracked files&lt;/strong> but I also would like to see the &lt;strong>Tracked files&lt;/strong> in &lt;code>magit-status&lt;/code>&lt;/p>
&lt;p>Well emacs being emacs this can be easily achieved, I added the following to my &lt;code>use-package magit&lt;/code> declaration.&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>:config
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>(magit-add-section-hook
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#e6db74">&amp;#39;magit-status-sections-hook&lt;/span> &lt;span style="color:#e6db74">&amp;#39;magit-insert-tracked-files&lt;/span> &lt;span style="color:#66d9ef">nil&lt;/span> &lt;span style="color:#e6db74">&amp;#39;append&lt;/span>)
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;figure class="emacs-img">&lt;img src="https://www.emacs.dyerdwelling.family/emacs/20230604121310-emacs--Magit-Insert-Tracked-Files.jpg">
&lt;/figure></description></item><item><title>Quickly Deleting Duplicate Blank Lines</title><link>https://www.emacs.dyerdwelling.family/emacs/20230603114025-emacs--quickly-deleting-duplicate-lines/</link><pubDate>Sat, 03 Jun 2023 21:38:00 +0100</pubDate><author>James Dyer</author><guid>https://www.emacs.dyerdwelling.family/emacs/20230603114025-emacs--quickly-deleting-duplicate-lines/</guid><description>&lt;p>I am currently hacking around with my org files and in fact macro removing quite a few unnecessary lines. However this has had the side effect of leaving some significant holes in the form of blank lines. Sometimes just two duplicate blank lines and sometimes more!&lt;/p>
&lt;figure class="emacs-img">&lt;img src="https://www.emacs.dyerdwelling.family/emacs/20230603114025-emacs--Quickly-Deleting-Duplicate-Lines.jpg">
&lt;/figure>
&lt;p>I found that I can quickly trim them down by using &lt;code>delete-duplicate-lines&lt;/code> and making sure the identical lines must be adjacent argument is set by passing in a &lt;strong>C-u C-u&lt;/strong> prefix.&lt;/p>
&lt;p>So the process is:&lt;/p>
&lt;ul>
&lt;li>open the org file&lt;/li>
&lt;li>&lt;code>mark-whole-buffer&lt;/code>&lt;/li>
&lt;li>&lt;strong>C-u C-u&lt;/strong> &lt;code>delete-duplicate-lines&lt;/code>&lt;/li>
&lt;/ul>
&lt;p>and that&amp;rsquo;s it!, the key here is the prefix argument otherwise all the blank lines will be deleted which is not what I want.&lt;/p>
&lt;p>Of course this method would delete all duplicate adjacent lines and not just the blank ones so I guess you would generally need to be a little careful, but I know I don&amp;rsquo;t have any of these and if I am not too sure then I can just inspect a git diff.&lt;/p></description></item><item><title>Insert Unique Log Message</title><link>https://www.emacs.dyerdwelling.family/emacs/20230523204523-emacs--insert-unique-log-message/</link><pubDate>Mon, 29 May 2023 12:00:00 +0100</pubDate><author>James Dyer</author><guid>https://www.emacs.dyerdwelling.family/emacs/20230523204523-emacs--insert-unique-log-message/</guid><description>&lt;p>I had tried to implement a debugging logging/print method myself using macros but hadn&amp;rsquo;t really achieved the level of elegance outlined in &lt;a href="https://xenodium.com/sprinkle-me-logs/">https://xenodium.com/sprinkle-me-logs/&lt;/a>&lt;/p>
&lt;figure class="emacs-img">&lt;img src="https://www.emacs.dyerdwelling.family/emacs/20230523204523-emacs--Insert-Unique-Log-Message.jpg">
&lt;/figure>
&lt;p>I added a couple of programming modes to the function defined in the post above and have now incorporated it into my workflow:&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>((&lt;span style="color:#a6e22e">equal&lt;/span> major-mode &lt;span style="color:#e6db74">&amp;#39;ada-mode&lt;/span>)
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> (&lt;span style="color:#a6e22e">cons&lt;/span> (&lt;span style="color:#a6e22e">format&lt;/span> &lt;span style="color:#e6db74">&amp;#34;Ada.Text_Io.Put_Line (\&amp;#34;%s: \\([0-9]+\\)\&amp;#34;);&amp;#34;&lt;/span> word)
&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;Ada.Text_Io.Put_Line (\&amp;#34;%s: %%s\&amp;#34;);&amp;#34;&lt;/span> word)))
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>((&lt;span style="color:#a6e22e">equal&lt;/span> major-mode &lt;span style="color:#e6db74">&amp;#39;c++-mode&lt;/span>)
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> (&lt;span style="color:#a6e22e">cons&lt;/span> (&lt;span style="color:#a6e22e">format&lt;/span> &lt;span style="color:#e6db74">&amp;#34;std::cout &amp;lt;&amp;lt; \&amp;#34;%s: \\([0-9]+\\)\&amp;#34; &amp;lt;&amp;lt; std::endl;&amp;#34;&lt;/span> word)
&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;std::cout &amp;lt;&amp;lt; \&amp;#34;%s: %%s\&amp;#34; &amp;lt;&amp;lt; std::endl;&amp;#34;&lt;/span> word)))
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>For some reason I always seem to tend to ribald statements within my code, something like &lt;strong>poop&lt;/strong> or some other unsavoury variant, I just need to remember to tidy these up later on!&lt;/p>
&lt;p>Oh and I added an old fashioned emacs badge to the top of this blog just for fun! as technically it is kinda true as in this web page and of course as in me as a human 😀&lt;/p></description></item><item><title>Initial focus in Occur Buffer</title><link>https://www.emacs.dyerdwelling.family/emacs/20230503211610-emacs--isearch-occur-advice-window-focus/</link><pubDate>Thu, 11 May 2023 20:43:00 +0100</pubDate><author>James Dyer</author><guid>https://www.emacs.dyerdwelling.family/emacs/20230503211610-emacs--isearch-occur-advice-window-focus/</guid><description>&lt;p>Just a quick one today!&lt;/p>
&lt;p>I am finding &lt;code>occur&lt;/code> extremely useful, from building an index from my emacs init file to searching through org headers to generally just having my &lt;code>isearch&lt;/code> all there in a single window.&lt;/p>
&lt;figure class="emacs-img">&lt;img src="https://www.emacs.dyerdwelling.family/emacs/20230503211610-emacs--Isearch-Occur-Advice-Window-Focus.jpg">
&lt;/figure>
&lt;p>However I would rather the cursor would jump to the &lt;code>*Occur*&lt;/code> buffer when invoked as it just feels a little more natural, so I added the following:&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>(advice-add &lt;span style="color:#e6db74">&amp;#39;isearch-occur&lt;/span> :after
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#f92672">&amp;#39;&lt;/span>(lambda (origin &lt;span style="color:#66d9ef">&amp;amp;rest&lt;/span> args)
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> (isearch-exit)
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> (&lt;span style="color:#a6e22e">select-window&lt;/span> (&lt;span style="color:#a6e22e">get-buffer-window&lt;/span> &lt;span style="color:#e6db74">&amp;#34;*Occur*&amp;#34;&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>&lt;/span>&lt;/code>&lt;/pre>&lt;/div></description></item><item><title>Cursor Blinking Rate</title><link>https://www.emacs.dyerdwelling.family/emacs/20230406200632-emacs--cursor-blinking-rate/</link><pubDate>Fri, 28 Apr 2023 13:00:00 +0100</pubDate><author>James Dyer</author><guid>https://www.emacs.dyerdwelling.family/emacs/20230406200632-emacs--cursor-blinking-rate/</guid><description>&lt;p>Sometimes I can find a blinking cursor distracting and somewhat expectant!, so currently I am favouring a solid non blinking cursor while still being able to easily locate my cursor using &lt;code>hl-line-mode&lt;/code>&lt;/p>
&lt;figure class="emacs-img">&lt;img src="https://www.emacs.dyerdwelling.family/emacs/20230406200632-emacs--Cursor-Blinking-Rate.jpg">
&lt;/figure>
&lt;p>I have tried &lt;strong>beacon&lt;/strong> and &lt;strong>pulsar&lt;/strong> in the past but have found that a simple line highlight nicely serves my purpose.&lt;/p>
&lt;p>This however led to a weird issue at work with my emacs setup and although I am not quite sure why it is happening (although I have my theories) I have found a workaround.&lt;/p>
&lt;p>As at home I am always tinkering with my emacs init file and I think at some point I decided to turn off cursor blinking. This coincided with a new virtual machine setup and hence a new emacs install. At that point I started to notice that sometimes the emacs window wouldn&amp;rsquo;t refresh until I either gave it some keyboard input or strangely I just wiggled my mouse :)&lt;/p>
&lt;p>I could reproduce this issue by restarting emacs from a clean start (cleaning out buffers and the desktop file) and switching immediately to a permanent register pointing to a file. Also I would notice that sometimes when I &lt;code>dired-jump&lt;/code>&amp;rsquo;d I would have to tap a key to get the emacs window to refresh.&lt;/p>
&lt;p>After a period of trial and error I narrowed the culprit down to :&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>(blink-cursor-mode &lt;span style="color:#ae81ff">-1&lt;/span>)
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>When I turned on a blinking cursor there would be no such emacs refreshing issue.&lt;/p>
&lt;p>I came up with a theory that in a setup involving VM Machines/X11/linux window manager/compositor/emacs/host graphics driver/and so on, there may be a graphical optimization that requires a specific event to occur for an emacs window to refresh. It&amp;rsquo;s possible that the reason everything works smoothly with a blinking cursor is because the cursor is constantly disappearing and reappearing, which triggers a refresh event.&lt;/p>
&lt;p>So&amp;hellip;. the question is how do I preserve a non blinking cursor and still get an emacs window to consistently refresh?, well I dug into the blinking cursor options and set the following:&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>(blink-cursor-mode &lt;span style="color:#ae81ff">1&lt;/span>)
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>(setq blink-cursor-interval &lt;span style="color:#ae81ff">0.001&lt;/span>)
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>(setq blink-cursor-blinks &lt;span style="color:#ae81ff">1&lt;/span>)
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>A single initial blink of the cursor would trigger a reliable display refresh but would be imperceptible to the human eye thus providing a perceived non blinking cursor!&lt;/p>
&lt;p>Oh and a final thing, has anyone noticed that by default an emacs cursor blinks but 10 times and then stops?, I&amp;rsquo;ve got to say it was only when I was digging around the blink-cursor variables and actively scrutinising the cursor blink behaviour that I noticed. I had assumed that it just kept blinking forever!&lt;/p></description></item><item><title>Imenu Indexing My emacs Init File</title><link>https://www.emacs.dyerdwelling.family/emacs/20230414111409-emacs--indexing-emacs-init/</link><pubDate>Fri, 14 Apr 2023 14:34:00 +0100</pubDate><author>James Dyer</author><guid>https://www.emacs.dyerdwelling.family/emacs/20230414111409-emacs--indexing-emacs-init/</guid><description>&lt;p>After implementing my simple occur indexing in my last post :&lt;/p>
&lt;p>&lt;a href="https://www.emacs.dyerdwelling.family/emacs/20230308203648-emacs--indexing-my-emacs-init-file/">Indexing My Emacs Init File&lt;/a>&lt;/p>
&lt;p>A suggestion was made to put this into an &lt;code>imenu&lt;/code>.&lt;/p>
&lt;p>I thought that was rather a good idea and it would also give me the opportunity to explore &lt;code>imenu&lt;/code>&lt;/p>
&lt;p>I came up with the following to add to the emacs init file :&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>(add-hook &lt;span style="color:#e6db74">&amp;#39;emacs-lisp-mode-hook&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> (setq imenu-sort-function &lt;span style="color:#e6db74">&amp;#39;imenu--sort-by-name&lt;/span>)
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> (setq imenu-generic-expression
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#f92672">&amp;#39;&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:#e6db74">&amp;#34;^;;[[:space:]]+-&amp;gt; \\(.*\\)$&amp;#34;&lt;/span> &lt;span style="color:#ae81ff">1&lt;/span>)
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> (&lt;span style="color:#e6db74">&amp;#34;defun&amp;#34;&lt;/span> &lt;span style="color:#e6db74">&amp;#34;^.*([[:space:]]*defun[[:space:]]+\\([[:word:]-/]+\\)&amp;#34;&lt;/span> &lt;span style="color:#ae81ff">1&lt;/span>)
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> (&lt;span style="color:#e6db74">&amp;#34;use-package&amp;#34;&lt;/span> &lt;span style="color:#e6db74">&amp;#34;^.*([[:space:]]*use-package[[:space:]]+\\([[:word:]-]+\\)&amp;#34;&lt;/span> &lt;span style="color:#ae81ff">1&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>&lt;/span>&lt;span style="display:flex;">&lt;span> (imenu-add-menubar-index)))
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>which produces the following:&lt;/p>
&lt;figure>&lt;img src="https://www.emacs.dyerdwelling.family/emacs/20230414111409-emacs--indexing-emacs-init.jpg" width="100%">
&lt;/figure>
&lt;p>I thought for good measure I would also add in some menus for &lt;code>defuns&lt;/code> and &lt;code>use-package&lt;/code> declarations (just for fun!) with my main defined sections forming the top level due to the MENU-TITLE being set to nil in the &lt;code>imenu-generic-expression&lt;/code> variable.&lt;/p>
&lt;p>This implementation has the added benefit of integrating nicely into the local completion system. For example I use &lt;code>vertico&lt;/code> and running &lt;code>imenu&lt;/code> calls up the defined sections in the mini-buffer and as an extra benefit running &lt;code>consult-imenu&lt;/code> does its consult thing to quickly step through the sections or of course to complete. As I have defined section names for &lt;code>defun&lt;/code> and &lt;code>use-package&lt;/code> the completion search can search on these too to quickly narrow things down.&lt;/p>
&lt;p>I had never looked into &lt;code>imenu&lt;/code> before and it was quite an interesting learning experience and in fact I went all the way to defining my own &lt;code>imenu-create-index-function&lt;/code> where I can create my own alist index in any way I want to, that was until I found a more simple method!&lt;/p></description></item><item><title>Indexing My Emacs Init File</title><link>https://www.emacs.dyerdwelling.family/emacs/20230308203648-emacs--indexing-my-emacs-init-file/</link><pubDate>Wed, 12 Apr 2023 14:36:00 +0100</pubDate><author>James Dyer</author><guid>https://www.emacs.dyerdwelling.family/emacs/20230308203648-emacs--indexing-my-emacs-init-file/</guid><description>&lt;p>Since I keep all my emacs configuration in a single &lt;code>.emacs&lt;/code> file and in a hyper organised manner it means I have my init file split into different sections, for example :&lt;/p>
&lt;ul>
&lt;li>platform&lt;/li>
&lt;li>packages&lt;/li>
&lt;li>mail&lt;/li>
&lt;li>calendar&lt;/li>
&lt;li>completion&lt;/li>
&lt;li>save-desktop&lt;/li>
&lt;li>keys&lt;/li>
&lt;li>modes&lt;/li>
&lt;li>setqs&lt;/li>
&lt;/ul>
&lt;hr>
&lt;p>with each section delimited by a comment of the form:&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>&lt;span style="color:#75715e">;;&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#75715e">;; -&amp;gt; platform`&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#75715e">;;&lt;/span>
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>Now I am using &lt;code>occur&lt;/code> more often, then why not write a function to produce a nice little Occur buffer containing an index of my init file sections, as thus:&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/index ()
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> (interactive)
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> (beginning-of-buffer)
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> (occur &lt;span style="color:#e6db74">&amp;#34;;;[[:space:]]-&amp;gt;&amp;#34;&lt;/span>))
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>I avoided the self index reference by explicitly using the &lt;strong>[:space:]&lt;/strong> variant for the occur regex.&lt;/p>
&lt;p>and this gives me the following:&lt;/p>
&lt;figure>&lt;img src="https://www.emacs.dyerdwelling.family/emacs/20230308203648-emacs--Indexing-My-Emacs-Init-File.jpg" width="100%">
&lt;/figure>
&lt;p>The selection of each item takes me to the relevant section of my emacs configuration and as I have &lt;strong>f8&lt;/strong> bound to &lt;code>next-error&lt;/code> I can easily step through each section if I want to.&lt;/p></description></item><item><title>Sorting Org Tags using Org Mode!</title><link>https://www.emacs.dyerdwelling.family/emacs/20230406210733-emacs--more-sorting-tags-options/</link><pubDate>Fri, 07 Apr 2023 16:04:00 +0100</pubDate><author>James Dyer</author><guid>https://www.emacs.dyerdwelling.family/emacs/20230406210733-emacs--more-sorting-tags-options/</guid><description>&lt;p>Well as always a little more time with emacs a little feedback and then finding more about org I have now figured out (I think) how I can better sort tags in an org file. In my previous post:&lt;/p>
&lt;figure class="emacs-img">&lt;img src="https://www.emacs.dyerdwelling.family/emacs/20230406210733-emacs--More-Sorting-Tags-Options.jpg">
&lt;/figure>
&lt;p>&lt;a href="https://www.emacs.dyerdwelling.family/emacs/20230310120116-emacs--sorting-org-tags/">Sorting Org Tags&lt;/a>&lt;/p>
&lt;p>I made the following comment:&lt;/p>
&lt;blockquote>
&lt;p>I had assumed that org-mode came with the built-in ability to sort tags but I couldn&amp;rsquo;t find any evidence of this&lt;/p>
&lt;/blockquote>
&lt;p>Well as it turns out there is evidence of this! and it takes the form of:&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 org-tags-sort-function &lt;span style="color:#e6db74">&amp;#39;org-string-collate-greaterp&lt;/span>)
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>This is tied into &lt;code>C-c C-q (org-set-tags-command)&lt;/code> of which I hadn&amp;rsquo;t yet discovered but now I think I shall bring into my muscle memory. I had previously just been manually adding tags!&lt;/p>
&lt;p>Further interwebs hunting and I came across the following function:&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/org-sort-tags ()
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#e6db74">&amp;#34;On a heading sort the tags.&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> (when (org-at-heading-p)
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> (org-set-tags (&lt;span style="color:#a6e22e">sort&lt;/span> (org-get-tags) &lt;span style="color:#a6e22e">#&amp;#39;&lt;/span>string&amp;lt;))))
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>which more concisely accomplishes my original intention and will work when the cursor is anywhere on an org heading and therefore a macro or presumably an org-* type iterator would neatly take care of the whole file for me.&lt;/p>
&lt;p>Although I went down a little rabbit hole I found a few things down there, elisp improvement, digging more around org-mode and bewilderingly wondering where my journey will end. Of course with emacs it never will, my little head will just peep out at the daylight sun every now and then but my little noggin will be wiser and more proficient 😃&lt;/p></description></item><item><title>Sorting Org Tags</title><link>https://www.emacs.dyerdwelling.family/emacs/20230310120116-emacs--sorting-org-tags/</link><pubDate>Sat, 01 Apr 2023 12:55:00 +0100</pubDate><author>James Dyer</author><guid>https://www.emacs.dyerdwelling.family/emacs/20230310120116-emacs--sorting-org-tags/</guid><description>&lt;p>I use a package called &lt;strong>org-rainbow-tags&lt;/strong> which adds random colours to org tags to provide a consistent colour between identical tags. This helps to identify common tags throughout the file but has the side effect of emphasising the lack of my coherent tag ordering.&lt;/p>
&lt;figure class="emacs-img">&lt;img src="https://www.emacs.dyerdwelling.family/emacs/20230310120116-emacs--Sorting-Org-Tags.jpg">
&lt;/figure>
&lt;p>I would like to order the tags consistently, just for my own peace of mind! 😀&lt;/p>
&lt;p>I had assumed tkhat org-mode came with the built-in ability to sort tags but I couldn&amp;rsquo;t find any evidence of this so I decided to create a method using my own function. My preferred default method is in descending order as I commonly use a year tag which I would always like to be on the right hand side.&lt;/p>
&lt;p>Just select the region containing the tags and run my function passing in the universal argument if you fancy ordering the other way!&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/sort-org-tags-region (beg end &lt;span style="color:#66d9ef">&amp;amp;optional&lt;/span> reversed)
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#e6db74">&amp;#34;In active region sort tags alphabetically in descending order.
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#e6db74"> With prefix argument REVERSE order.&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> (interactive &lt;span style="color:#e6db74">&amp;#34;r\nP&amp;#34;&lt;/span>)
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> (unless (region-active-p) (&lt;span style="color:#a6e22e">user-error&lt;/span> &lt;span style="color:#e6db74">&amp;#34;No active region to sort!&amp;#34;&lt;/span>))
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> (let* ((str (s-trim (&lt;span style="color:#a6e22e">buffer-substring-no-properties&lt;/span> beg end)))
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> (wrd (split-string str &lt;span style="color:#e6db74">&amp;#34;:&amp;#34;&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> (new
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> (&lt;span style="color:#a6e22e">concat&lt;/span>
&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> (s-join &lt;span style="color:#e6db74">&amp;#34;:&amp;#34;&lt;/span> (&lt;span style="color:#a6e22e">sort&lt;/span> wrd (if reversed &lt;span style="color:#a6e22e">#&amp;#39;&lt;/span>string&amp;lt; &lt;span style="color:#a6e22e">#&amp;#39;&lt;/span>string&amp;gt;)))
&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>&lt;/span>&lt;span style="display:flex;">&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> beg)
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> (&lt;span style="color:#a6e22e">delete-region&lt;/span> beg end)
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> (&lt;span style="color:#a6e22e">insert&lt;/span> new)
&lt;/span>&lt;/span>&lt;span style="display:flex;">&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>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>j&lt;/p></description></item><item><title>Trimming ArtRage Playback Scripts using Emacs</title><link>https://www.emacs.dyerdwelling.family/emacs/20230309201924-emacs--trimming-artrage-playback-scripts/</link><pubDate>Tue, 21 Mar 2023 14:30:00 +0000</pubDate><author>James Dyer</author><guid>https://www.emacs.dyerdwelling.family/emacs/20230309201924-emacs--trimming-artrage-playback-scripts/</guid><description>&lt;p>Emacs isn&amp;rsquo;t my only obsession, I like to create digital art and for that I use ArtRage. The interesting thing about ArtRage is that each brush stroke or editing action can be recorded in a text file or script for later playback. I use this facility for creating time-lapses.&lt;/p>
&lt;p>The ArtRage manual describes the following:&lt;/p>
&lt;blockquote>
&lt;p>&lt;strong>Script Files&lt;/strong>&lt;br />
ArtRage scripts are simple Unicode UTF16 text files which can be edited using Notepad, Notepad+++, TextEdit, or any similar program&lt;/p>
&lt;/blockquote>
&lt;p>&amp;ldquo;similar program&amp;rdquo;?! now let me think&amp;hellip;.. what do I have available :)&lt;/p>
&lt;p>For a nice clean time-lapse ArtRage now has the ability to filter out the rest of the UI elements and leave the canvas fixed in the center of the screen. For the most part this works very smoothly however sometimes the recording hasn&amp;rsquo;t quite registered some strokes properly or some reference files have moved, in this case I may get an error such as:&lt;/p>
&lt;figure>&lt;img src="https://www.emacs.dyerdwelling.family/emacs/Screenshot_20230309_203530.jpg" width="100%">
&lt;/figure>
&lt;p>At which point it is time to break out emacs and delve into the recorded text script file. For a typical piece of art the script can get quite large, for my last portrait it was 174M with 605012 lines. How does emacs handle this?, well it had no problem and opened it in a split second!&lt;/p>
&lt;p>I think to resolve this current issue around &lt;code>ReferenceImageXForm&lt;/code> I am just going to remove all occurrences as for a time-lapse playback I don&amp;rsquo;t usually want to show the references anyway.&lt;/p>
&lt;p>First of all lets &lt;code>isearch&lt;/code> for ReferenceImageXForm, now that I have &lt;code>isearch-lazy-count&lt;/code> turned on it gives me 1/36 in the minibuffer. Lets just step through each one and remove the whole line.&lt;/p>
&lt;p>Also I think I would like to remove the initial reference image load which starts off as:&lt;/p>
&lt;pre tabindex="0">&lt;code class="language-nil" data-lang="nil">Wait: 33.293s EvType: Command CommandID:
LoadReferenceImage Idx: 0 Reference Image: {
&lt;/code>&lt;/pre>&lt;p>and then has many lines of image data and then finishes with an enclosing brace. Of course this is easy to remove in emacs by leveraging:&lt;/p>
&lt;pre tabindex="0">&lt;code class="language-nil" data-lang="nil">(kill-sexp &amp;amp;optional ARG INTERACTIVE)
Kill the sexp (balanced expression) following point.
&lt;/code>&lt;/pre>&lt;p>now a quick save and playback and the timelapse runs perfectly.&lt;/p>
&lt;p>I remember a while ago when I was not quite aware of the capabilities of emacs and I was more Windows bound I had to look for specific text editors that could handle large files and then save them in the requisite Unicode UTF16 format. They were slow, it was a hassle, I couldn&amp;rsquo;t define any macros e.t.c, but emacs just does this out of the box!&lt;/p>
&lt;p>Maybe I should petition the ArtRage team to reference emacs in their documentation!&lt;/p>
&lt;figure class="emacs-img">&lt;img src="https://www.emacs.dyerdwelling.family/emacs/20230309201924-emacs--Trimming-ArtRage-Playback-Scripts.jpg">
&lt;/figure></description></item><item><title>Editing org files on an Android device - Part 1</title><link>https://www.emacs.dyerdwelling.family/emacs/20230312193655-emacs--editing-files-on-a-phone-and-tablet/</link><pubDate>Sun, 12 Mar 2023 21:08:00 +0000</pubDate><author>James Dyer</author><guid>https://www.emacs.dyerdwelling.family/emacs/20230312193655-emacs--editing-files-on-a-phone-and-tablet/</guid><description>&lt;p>Now that there is a build for emacs on Android I thought I would try and develop an emacs workflow between my Galaxy Note 8 / Galaxy Tab S7+ and my linux laptop.&lt;/p>
&lt;p>At the moment I am using Markor on my phone and this serves my needs adequately but of course I am missing that org file support.&lt;/p>
&lt;p>My current process involves &lt;strong>syncthing&lt;/strong> to push my org files around my laptop / phone and tablet. I also push other things that are regularly backed up and this does include my &lt;strong>.emacs&lt;/strong> config file.&lt;/p>
&lt;p>So the relevant files are already present on my device the next thing to do is to install emacs from F-Droid and grant emacs file permissions.&lt;/p>
&lt;p>My phone is running Android version 9 and my tablet version 13 so the method to get permissions set up for Emacs was slightly different in each case.&lt;/p>
&lt;p>On my phone from the settings I selected Apps -&amp;gt; top right menu (App permissions) -&amp;gt; Storage -&amp;gt; Emacs to On&lt;/p>
&lt;p>On my tablet from the settings I selected Apps -&amp;gt; top right menu (Special access) -&amp;gt; All files access -&amp;gt; Emacs to On&lt;/p>
&lt;p>Next up is to set up my configuration file, in this case I am going to create a symbolic link using &lt;code>dired&lt;/code>. First of course is to run up emacs and then navigate to my syncthing&amp;rsquo;d &lt;code>.emacs&lt;/code> file which in my case is in the following location:&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-bash" data-lang="bash">&lt;span style="display:flex;">&lt;span>/storage/emulated/0/DCIM/Linux
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>The android emacs menu bar is quite responsive and the menu appears in a nice large touch friendly font so I can just &lt;strong>File -&amp;gt; Open Directory&amp;hellip;&lt;/strong> at which point an on screen keyboard pops up and I can input the path.&lt;/p>
&lt;p>Note : Something I discovered early on was that because I have KDE Connect installed across all my devices and Clipboard sync is turned on it means that the clipboard is shared between devices. This means that anything that is copied from within emacs on my laptop appears on my phone or tablet, so I could just type out the path on my laptop, copy it to the kill ring and it appears on the Samsung Keyboard clipboard field ready to paste into emacs!&lt;/p>
&lt;p>Now the path has been input &lt;code>dired&lt;/code> shows the directory containing all my files including my &lt;code>.emacs&lt;/code> file. Now I select the line containing the file &lt;strong>(yes, using my finger!!)&lt;/strong> then from the top menu : &lt;strong>Operate -&amp;gt; Simlink to&amp;hellip;&lt;/strong> and input the following:&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-bash" data-lang="bash">&lt;span style="display:flex;">&lt;span>/data/data/org.gnu.emacs/files/.emacs
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>Now restart emacs or maybe &lt;strong>Emacs-Lisp -&amp;gt; Evaluate Buffer&lt;/strong>&lt;/p>
&lt;p>When I first attempted this my default &lt;code>.emacs&lt;/code> gave a prodigious number of errors and I realised that for the moment android emacs doesn&amp;rsquo;t have the ability to download packages from melpa or elpa. Not too much of a problem for me though as my default config is tending towards vanilla anyway so for the moment I decided to create a purely android version of my .emacs config by stripping out everything I didn&amp;rsquo;t think I needed with an eye on creating a generic version across not only android but windows too. Currently I have a few &lt;code>(when (string-equal system-type &amp;quot;windows-nt&amp;quot;)&lt;/code> and for android I just need something similar but with &lt;strong>android&lt;/strong> as the defining string.&lt;/p>
&lt;p>Technically just setting the storage permissions for emacs on my device would have been enough to open an org file and edit it, but now I have a symbolic link to my .emacs config and it is synched to all my devices it means I now have the ability to modify it anywhere and although I currently have an android specific version it won&amp;rsquo;t be too long before I have a single generic config for all my devices / operating systems.&lt;/p>
&lt;p>The next step will be to find an on-screen keyboard to fit a good emacs editing workflow - a sneak peek below of Keyboard Designer but that will be for part 2!&lt;/p>
&lt;figure>&lt;img src="https://www.emacs.dyerdwelling.family/emacs/20230312193655-emacs--Editing-files-on-a-phone-and-tablet-pt1.jpg" width="300px">
&lt;/figure></description></item><item><title>Replacing deadgrep with consult-ripgrep</title><link>https://www.emacs.dyerdwelling.family/emacs/20230225134207-emacs--trying-out-consult-ripgrep-to-replace-deadgrep/</link><pubDate>Wed, 08 Mar 2023 21:08:00 +0000</pubDate><author>James Dyer</author><guid>https://www.emacs.dyerdwelling.family/emacs/20230225134207-emacs--trying-out-consult-ripgrep-to-replace-deadgrep/</guid><description>&lt;p>I have been evolving my way through many differing ways of grepping recently from standard built-in greps to a few ripgrep front ends until I finally settled on &lt;code>deadgrep&lt;/code>&lt;/p>
&lt;figure class="emacs-img">&lt;img src="https://www.emacs.dyerdwelling.family/emacs/20230225134207-emacs--Trying-out-consult-ripgrep-to-replace-deadgrep.jpg">
&lt;/figure>
&lt;p>I am currently an &lt;strong>ivy&lt;/strong> user but as everyone seems to be talking about
&lt;strong>vertico&lt;/strong> and the associated completion stack I thought I would give it a
try and therefore &lt;code>consult-ripgrep&lt;/code> to see it can improve on my &lt;code>deadgrep&lt;/code>
setup.&lt;/p>
&lt;p>Previously I have created a couple of wrappers around deadgrep:&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/deadgrep ()
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> (interactive)
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> (if (&lt;span style="color:#a6e22e">equal&lt;/span> major-mode &lt;span style="color:#e6db74">&amp;#39;dired-mode&lt;/span>)
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> (setq search-term
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> (&lt;span style="color:#a6e22e">read-from-minibuffer&lt;/span> &lt;span style="color:#e6db74">&amp;#34;Search : &amp;#34;&lt;/span>))
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> (setq search-term
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> (&lt;span style="color:#a6e22e">read-from-minibuffer&lt;/span> &lt;span style="color:#e6db74">&amp;#34;Search : &amp;#34;&lt;/span> (thing-at-point &lt;span style="color:#e6db74">&amp;#39;symbol&lt;/span>)))
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> )
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> (deadgrep search-term home-dir)
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> )
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>(defun my/grep ()
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> (interactive)
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> (if (&lt;span style="color:#a6e22e">equal&lt;/span> major-mode &lt;span style="color:#e6db74">&amp;#39;dired-mode&lt;/span>)
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> (setq search-term
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> (&lt;span style="color:#a6e22e">read-from-minibuffer&lt;/span> &lt;span style="color:#e6db74">&amp;#34;Search : &amp;#34;&lt;/span>))
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> (setq search-term
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> (&lt;span style="color:#a6e22e">read-from-minibuffer&lt;/span> &lt;span style="color:#e6db74">&amp;#34;Search : &amp;#34;&lt;/span> (thing-at-point &lt;span style="color:#e6db74">&amp;#39;symbol&lt;/span>)))
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> )
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> (deadgrep search-term)
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> )
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>As a universal argument was passed to deadgrep which had a side effect of initially pausing the search, this meant I split my grepping between &lt;code>S-f12&lt;/code> and &lt;code>M-f12&lt;/code> one for a project grep and one for a local directory grep.&lt;/p>
&lt;p>As part of trying out &lt;code>consult-ripgrep&lt;/code> I think I would like to rewrite the functions above and I would want a quick and simple method to revert back to using deadgrep.&lt;/p>
&lt;p>I settled on the following:&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/project-root ()
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> (interactive)
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#e6db74">&amp;#34;Guess the project root of the given FILE-PATH.&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> (let ((root default-directory)
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> (project (project-current)))
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> (when project
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> (cond ((&lt;span style="color:#a6e22e">fboundp&lt;/span> &lt;span style="color:#e6db74">&amp;#39;project-root&lt;/span>)
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> (setq root (project-root project)))))))
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>(defun my/grep (arg)
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> (interactive &lt;span style="color:#e6db74">&amp;#34;p&amp;#34;&lt;/span>)
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> (if (&lt;span style="color:#a6e22e">equal&lt;/span> major-mode &lt;span style="color:#e6db74">&amp;#39;dired-mode&lt;/span>)
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> (setq search-term
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> (&lt;span style="color:#a6e22e">read-from-minibuffer&lt;/span> &lt;span style="color:#e6db74">&amp;#34;Search : &amp;#34;&lt;/span>))
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> (setq search-term
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> (&lt;span style="color:#a6e22e">read-from-minibuffer&lt;/span> &lt;span style="color:#e6db74">&amp;#34;Search : &amp;#34;&lt;/span> (thing-at-point &lt;span style="color:#e6db74">&amp;#39;symbol&lt;/span>)))
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> )
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> (if (&lt;span style="color:#a6e22e">&amp;gt;&lt;/span> arg &lt;span style="color:#ae81ff">1&lt;/span>) &lt;span style="color:#75715e">;; if C-u has been activated&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> (consult-ripgrep default-directory search-term)
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> (consult-ripgrep (my/project-root) search-term)))
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#75715e">;; (progn&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#75715e">;; (setq current-prefix-arg nil)&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#75715e">;; (deadgrep search-term default-directory)&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#75715e">;; )&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#75715e">;; (deadgrep search-term (my/project-root))))&lt;/span>
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>To switch between deadgrep / consult-ripgrep I just need to uncomment / comment in and out the relevant bits and then re-evaluate.&lt;/p>
&lt;p>As you can see I created a local function to find the project root directory if one exists and to then pass the result to the deadgrep / consult-ripgrep command meaning that they will both perform identically from a search directory perspective.&lt;/p>
&lt;p>Note how I overcame the pass-through of the universal argument to deadgrep by just resetting the &lt;code>current-prefix-arg&lt;/code> after I had already used the universal argument logic within the function.&lt;/p>
&lt;p>I can now free up my original &lt;code>M-f12&lt;/code> binding which searched from the local &lt;code>default-directory&lt;/code> as I can &lt;code>C-u&lt;/code> to the &lt;code>S-f12&lt;/code> binding.&lt;/p>
&lt;p>I&amp;rsquo;m not too sure about consult-ripgrep yet but the setup above gives me the following benefits:&lt;/p>
&lt;ol>
&lt;li>A common local find project root function that might become useful for future functions&lt;/li>
&lt;li>Flexibility with the universal argument meaning I could pass in multiple
universal arguments or maybe even a numeric argument for enhanced
functionality&lt;/li>
&lt;li>Able to quickly switch between ripgrep implementations by commenting / un-commenting&lt;/li>
&lt;li>Frees up a keybinding&lt;/li>
&lt;/ol></description></item><item><title>Using Macros to Help Code Debugging</title><link>https://www.emacs.dyerdwelling.family/emacs/20230224195709-emacs--quick-debug-coding-insert/</link><pubDate>Thu, 02 Mar 2023 20:08:00 +0000</pubDate><author>James Dyer</author><guid>https://www.emacs.dyerdwelling.family/emacs/20230224195709-emacs--quick-debug-coding-insert/</guid><description>&lt;p>Often I find myself requiring the most rudimentary of methods of debugging and that is to push text to standard output from within a running executable to indicate the logical structure of a program.&lt;/p>
&lt;figure class="emacs-img">&lt;img src="https://www.emacs.dyerdwelling.family/emacs/20230224195709-emacs--Quick-Debug-Coding-Insert.jpg">
&lt;/figure>
&lt;p>This situation may come about if I can&amp;rsquo;t debug a dll or generally actually stopping a program to debug creates an &lt;strong>&amp;ldquo;observer effect&amp;rdquo;&lt;/strong>&lt;/p>
&lt;p>Sometimes the logical structure can be somewhat complex consisting of a plethora of &lt;code>if&lt;/code> statements and nested &lt;code>for&lt;/code> loops, e.t.c and may take me a tedious while to apply the necessary debug statements. But emacs macros can speed up this process drastically!&lt;/p>
&lt;p>I often code in Ada and the text to &lt;code>stdout&lt;/code> mechanism is generally something like:&lt;/p>
&lt;p>&lt;code>ada.text_io.put_line(&amp;quot;Yes Ada is a language!&amp;quot;);&lt;/code>&lt;/p>
&lt;p>My macro starts with a simple search for blank lines using &lt;code>C-M-s&lt;/code> &lt;code>(isearch-forward-regexp)&lt;/code> for the regex &lt;strong>&lt;code>^$&lt;/code>&lt;/strong>&lt;/p>
&lt;p>Next I would like to have a unique string for each debug statement and this can be accomplished by using &lt;code>(kmacro-insert-counter)&lt;/code> via &lt;code>C-x C-k C-i&lt;/code>&lt;/p>
&lt;p>For example to debug the following dummy code:&lt;/p>
&lt;pre tabindex="0">&lt;code class="language-nil" data-lang="nil">with Ada.Text_Io;
procedure Ada_Main is
Ftt : Boolean := True;
Count : Integer := 0;
procedure Call_Routine is
begin
null;
end Call_Routine;
procedure Call_Another_Routine is
begin
null;
end Call_Another_Routine;
begin
Ada.Text_Io.Put_Line(&amp;#34;Program Starting&amp;#34;);
if Ftt then
Call_Routine;
else
Call_Another_Routine;
end if;
while Count &amp;lt; 3 loop
Call_Routine;
Count := Count + 1;
end loop;
Ada.Text_Io.Put_Line(&amp;#34;Program Finished&amp;#34;);
end Ada_Main;
&lt;/code>&lt;/pre>&lt;p>which just outputs the following:&lt;/p>
&lt;pre tabindex="0">&lt;code class="language-nil" data-lang="nil">Program Starting
Program Finished
&lt;/code>&lt;/pre>&lt;p>To debugify this simple program, I can record a macro and perform the following steps:&lt;/p>
&lt;ol>
&lt;li>perform &lt;strong>(isearch-forward-regexp)&lt;/strong> for &lt;code>^$&lt;/code>&lt;/li>
&lt;li>type in my debug output statement - in this case an Ada one&lt;/li>
&lt;li>insert an incremented counter&lt;/li>
&lt;li>complete the debug statement.&lt;/li>
&lt;/ol>
&lt;p>On a macro repeat I will now have a unique text_io for each blank line giving me an indication of the code path. I can kick off my macro from any point in the program and stop at any time hence below:&lt;/p>
&lt;pre tabindex="0">&lt;code class="language-nil" data-lang="nil">with Ada.Text_Io;
procedure Ada_Main is
Ftt : Boolean := True;
Count : Integer := 0;
procedure Call_Routine is
begin
null;
end Call_Routine;
procedure Call_Another_Routine is
begin
null;
end Call_Another_Routine;
begin
Ada.Text_Io.Put_Line(&amp;#34;Program Starting&amp;#34;);
Ada.Text_Io.Put_Line(&amp;#34;##1&amp;#34;);
if Ftt then
Call_Routine;
else
Call_Another_Routine;
end if;
Ada.Text_Io.Put_Line(&amp;#34;##2&amp;#34;);
while Count &amp;lt; 3 loop
Ada.Text_Io.Put_Line(&amp;#34;##3&amp;#34;);
Call_Routine;
Ada.Text_Io.Put_Line(&amp;#34;##4&amp;#34;);
Count := Count + 1;
Ada.Text_Io.Put_Line(&amp;#34;##5&amp;#34;);
end loop;
Ada.Text_Io.Put_Line(&amp;#34;##6&amp;#34;);
Ada.Text_Io.Put_Line(&amp;#34;Program Finished&amp;#34;);
Ada.Text_Io.Put_Line(&amp;#34;##7&amp;#34;);
end Ada_Main;
&lt;/code>&lt;/pre>&lt;p>and running now gives me:&lt;/p>
&lt;pre tabindex="0">&lt;code class="language-nil" data-lang="nil">Program Starting
##1
##2
##3
##4
##5
##3
##4
##5
##3
##4
##5
##6
Program Finished
##7
&lt;/code>&lt;/pre>&lt;p>Which may help me to figure out what is going wrong with my program (although in this case nothing was really going wrong!)&lt;/p>
&lt;p>For large complicated source code to have such a fast mechanism such as this can be very useful and it is amazing at just how quickly a program fault can be identified by using this simple and almost primordial method.&lt;/p>
&lt;p>This of course could be improved in many different ways, for example to not rely on blank lines and possibly force insert debug statements based on semi colons or maybe to decipher the syntactical structure (tree sitter?) and insert the debug statements in a more coherent and comprehensive manner. But this is only a rudimentary quick method to roughly locate a programs issues and it is good enough for me.&lt;/p></description></item><item><title>Moving Away From Deft</title><link>https://www.emacs.dyerdwelling.family/emacs/20230204140603-emacs--moving-away-from-deft/</link><pubDate>Wed, 22 Feb 2023 20:33:00 +0000</pubDate><author>James Dyer</author><guid>https://www.emacs.dyerdwelling.family/emacs/20230204140603-emacs--moving-away-from-deft/</guid><description>&lt;p>As the title says, it is with a heavy heart that I have decided to move away from &lt;strong>deft&lt;/strong>. There are a few reasons for this:&lt;/p>
&lt;figure class="emacs-img">&lt;img src="https://www.emacs.dyerdwelling.family/emacs/20230204140603-emacs--Moving-Away-From-Deft/2023-02-04_14-32.jpg">
&lt;/figure>
&lt;ul>
&lt;li>All files I am interested in quickly locating and editing are now orgified (turned into org files)&lt;/li>
&lt;li>All these files are located in a single directory&lt;/li>
&lt;li>All these files have a sensible naming convention indicating the contents&lt;/li>
&lt;li>I am now much more familiar with dired&lt;/li>
&lt;li>I didn&amp;rsquo;t ever use the deft facility for creating new files, I prefer to
use dired&lt;/li>
&lt;li>I didn&amp;rsquo;t ever need to incrementally filter / search as I can use emacs / dired for this&lt;/li>
&lt;li>Deft took a couple of seconds to initially load&lt;/li>
&lt;li>My deft configuration was becoming a little bloated and I wanted to use
vanilla emacs where I could; see below for my former deft configuration:&lt;/li>
&lt;/ul>
&lt;!--listend-->
&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 deft
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> :bind (&lt;span style="color:#e6db74">&amp;#34;C-c d&amp;#34;&lt;/span> &lt;span style="color:#f92672">.&lt;/span> deft)
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> :commands (deft)
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> :config (setq deft-text-mode &lt;span style="color:#e6db74">&amp;#39;org-mode&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> deft-use-filename-as-title &lt;span style="color:#66d9ef">nil&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> deft-auto-save-interval &lt;span style="color:#ae81ff">0&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> deft-use-filter-string-for-filename &lt;span style="color:#66d9ef">t&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> deft-extensions &lt;span style="color:#f92672">&amp;#39;&lt;/span>(&lt;span style="color:#e6db74">&amp;#34;org&amp;#34;&lt;/span>)
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> deft-default-extension &lt;span style="color:#e6db74">&amp;#34;org&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> deft-time-format &lt;span style="color:#e6db74">&amp;#34;%Y-%m-%d %H:%M:%S&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> deft-new-file-format &lt;span style="color:#e6db74">&amp;#34;%Y-%m-%dT%H%M%S&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> deft-strip-summary-regexp &lt;span style="color:#e6db74">&amp;#34;\\([\n]\\|^#\\+.+:.*$\\)&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> deft-recursive &lt;span style="color:#66d9ef">t&lt;/span>))
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>It is time to move onto something a little more simple, namely:&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 deft-directory (&lt;span style="color:#a6e22e">concat&lt;/span> home-dir &lt;span style="color:#e6db74">&amp;#34;/DCIM/content&amp;#34;&lt;/span>))
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>(bind-key* (kbd &lt;span style="color:#e6db74">&amp;#34;C-c d&amp;#34;&lt;/span>) (lambda()(interactive)(dired deft-directory)))
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>and yes I decided to keep the venerable name of deft in memoriam.&lt;/p>
&lt;p>I think this is an example of over time learning that emacs has a lot of what you want already built in and with org mode it gently nudges you to organise files in a more coherent manner leading to a more simple agnostic digital way of life.&lt;/p></description></item><item><title>Commenting Un-commenting</title><link>https://www.emacs.dyerdwelling.family/emacs/20230215204855-emacs--commenting-uncommenting/</link><pubDate>Wed, 15 Feb 2023 20:57:00 +0000</pubDate><author>James Dyer</author><guid>https://www.emacs.dyerdwelling.family/emacs/20230215204855-emacs--commenting-uncommenting/</guid><description>&lt;p>After watching an interesting video by &lt;a href="https://www.youtube.com/watch?v=vTdbb7tsvQc">EmacsElements&lt;/a> regarding commenting and un-commenting I have to say that I wholeheartedly agree. I really don&amp;rsquo;t like the way &lt;code>comment-dwim&lt;/code> works and made me think back to one of the first elisp functions I commandeered from the interwebs :&lt;/p>
&lt;figure class="emacs-img">&lt;img src="https://www.emacs.dyerdwelling.family/emacs/20230215204855-emacs--Commenting-Uncommenting.jpg">
&lt;/figure>
&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/comment-or-uncomment ()
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#e6db74">&amp;#34;Comments or uncomments the current line or region.&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> (if (region-active-p)
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> (comment-or-uncomment-region
&lt;/span>&lt;/span>&lt;span style="display:flex;">&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> (comment-or-uncomment-region
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> (&lt;span style="color:#a6e22e">line-beginning-position&lt;/span>)(&lt;span style="color:#a6e22e">line-end-position&lt;/span>))))
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>and it is a command that I constantly use and had forgotten that it isn&amp;rsquo;t part of the emacs default functionality from a &lt;code>M-;&lt;/code>&lt;/p></description></item><item><title>Simple Flexible Scrolling</title><link>https://www.emacs.dyerdwelling.family/emacs/20230205185836-emacs--simple-flexible-scrolling/</link><pubDate>Wed, 08 Feb 2023 21:21:00 +0000</pubDate><author>James Dyer</author><guid>https://www.emacs.dyerdwelling.family/emacs/20230205185836-emacs--simple-flexible-scrolling/</guid><description>&lt;p>I have written before about smooth scrolling using &lt;strong>good-scroll&lt;/strong> and how I managed to find a semi satisfactory way of centering my cursor after a single scroll which meant I would then have a minimal amount of subsequent line movement to get to the line I want.&lt;/p>
&lt;figure class="emacs-img">&lt;img src="https://www.emacs.dyerdwelling.family/emacs/20230205185836-emacs--Simple-Flexible-Scrolling.jpg">
&lt;/figure>
&lt;p>However, I have since returned to a simple concept, more of a basic &lt;code>scroll-[up/down]-command&lt;/code> which in its bare form scrolls a whole page. Now for me this is too much and I still want to have the ability to control how many lines I scroll which will still indicate to my brain that text is scrolling so I can still take in the overall form of a file.&lt;/p>
&lt;p>I initially returned to my simple scrolling functions:&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 scroll-up-some ()
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> (interactive)
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> (scroll-up-command (window-some-height)))
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>(defun scroll-down-some ()
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> (interactive)
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> (scroll-down-command (window-some-height)))
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>(defun window-some-height ()
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> (&lt;span style="color:#a6e22e">max&lt;/span> &lt;span style="color:#ae81ff">1&lt;/span> &lt;span style="color:#a6e22e">/&lt;/span> (&lt;span style="color:#a6e22e">1-&lt;/span> (window-height(&lt;span style="color:#a6e22e">selected-window&lt;/span>))) &lt;span style="color:#ae81ff">4&lt;/span>))
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>which leveraged the number of lines as an argument to the &lt;code>scroll-[up/down]-command&lt;/code>&lt;/p>
&lt;p>I would however really like to simplify and still allow some centering, I therefore created the following bindings:&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>(bind-key* (kbd &lt;span style="color:#e6db74">&amp;#34;M-j&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> (next-line (&lt;span style="color:#a6e22e">/&lt;/span> (window-height) &lt;span style="color:#ae81ff">12&lt;/span>))
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> (&lt;span style="color:#a6e22e">recenter&lt;/span>)))
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>(bind-key* (kbd &lt;span style="color:#e6db74">&amp;#34;M-k&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> (previous-line (&lt;span style="color:#a6e22e">/&lt;/span> (window-height) &lt;span style="color:#ae81ff">12&lt;/span>))
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> (&lt;span style="color:#a6e22e">recenter&lt;/span>)))
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>The only issue I have with this implementation is that when I move to the end of a file and scroll upwards the first scroll &lt;code>recenter&lt;/code> is a bit jarring as moving to the &lt;code>end-of-buffer&lt;/code> is not typically centred and has any number of blank &amp;ldquo;lines&amp;rdquo; below it. Moving to &lt;code>beginning-of-buffer&lt;/code> has no such issue as the cursor moves right to the top and subsequent scroll down recenters do not activate any scrolling until the cursor is past the center point of the window.&lt;/p>
&lt;p>This issue however is easily fixed by:&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>(bind-key* (kbd &lt;span style="color:#e6db74">&amp;#34;M-&amp;gt;&amp;#34;&lt;/span>)
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> (lambda()(interactive)(end-of-buffer)(&lt;span style="color:#a6e22e">recenter&lt;/span>)))
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>which forces an initial &lt;code>recenter&lt;/code> whenever an &lt;code>end-of-buffer&lt;/code> keybinding is activated, meaning that the first scroll upwards is now seamless!&lt;/p></description></item><item><title>How to Display Google Calendar</title><link>https://www.emacs.dyerdwelling.family/emacs/20230204115142-emacs--displaying-a-calendar/</link><pubDate>Sat, 04 Feb 2023 13:20:00 +0000</pubDate><author>James Dyer</author><guid>https://www.emacs.dyerdwelling.family/emacs/20230204115142-emacs--displaying-a-calendar/</guid><description>&lt;p>Emacs is subsuming me! I have managed to get email up and running using &lt;strong>mu4e&lt;/strong> and rss using &lt;strong>elfeed&lt;/strong> and image viewing with &lt;strong>image-dired&lt;/strong>&lt;/p>
&lt;p>Next up is some form of calendar integration!&lt;/p>
&lt;p>Although I use Google Calendar I don&amp;rsquo;t rely on any google apps directly; an android app called &lt;strong>Simple Calendar&lt;/strong> is adequate for my needs and enables the thing I cherish most of all (except emacs of course!) and that is the ability to produce an offline copy. It can export to an &lt;code>ics&lt;/code> file, and I have developed a habit of exporting to an offline file every time I update my calendar. This means that I have some exported calendar files lying around, including of course the most recent one.&lt;/p>
&lt;p>Initially I attempted to use &lt;strong>org-gcal&lt;/strong> which potentially enables a two way communication with Google Calendar through their API, but I got lost in the setup and of course this is Google, at some stage it is likely they will either change their API / remove it / or charge for it.&lt;/p>
&lt;p>Anyway, do I really need to modify my calendar from within emacs?, I am quite comfortable using &lt;strong>Simple Calendar&lt;/strong> on my phone and if I really need to add calendar entries from my laptop then I always have &lt;strong>Thunderbird&lt;/strong> as a backup.&lt;/p>
&lt;p>So I may just be in a very fortunate position regarding an adequate level of emacs calendar integration :&lt;/p>
&lt;ol>
&lt;li>I only require read only&lt;/li>
&lt;li>I have an up-to-date &lt;code>ics&lt;/code> file available&lt;/li>
&lt;/ol>
&lt;p>I created the following function which opens up a nicely formatted calendar using the packages &lt;strong>calfw&lt;/strong> and &lt;strong>calfw-cal&lt;/strong> (and they didn&amp;rsquo;t require any additional setup).&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/calendar ()
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> (interactive)
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> (setq tmp-file (&lt;span style="color:#a6e22e">concat&lt;/span> home-dir &lt;span style="color:#e6db74">&amp;#34;/DCIM/Backup/tmp.org&amp;#34;&lt;/span>))
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> (&lt;span style="color:#a6e22e">delete-file&lt;/span> tmp-file)
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> (when (&lt;span style="color:#a6e22e">get-buffer&lt;/span> (&lt;span style="color:#a6e22e">file-name-nondirectory&lt;/span> tmp-file))
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> (&lt;span style="color:#a6e22e">kill-buffer&lt;/span> (&lt;span style="color:#a6e22e">file-name-nondirectory&lt;/span> tmp-file)))
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> (setq last-ics
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> (&lt;span style="color:#a6e22e">car&lt;/span> (&lt;span style="color:#a6e22e">directory-files&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> (&lt;span style="color:#a6e22e">concat&lt;/span> home-dir &lt;span style="color:#e6db74">&amp;#34;/DCIM/Backup&amp;#34;&lt;/span>)
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#e6db74">&amp;#39;full&lt;/span> &lt;span style="color:#e6db74">&amp;#34;\.ics$&amp;#34;&lt;/span> &lt;span style="color:#a6e22e">#&amp;#39;file-newer-than-file-p&lt;/span>)))
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> (icalendar-import-file last-ics tmp-file)
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> (cfw:open-diary-calendar)
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> (when (&lt;span style="color:#a6e22e">get-buffer&lt;/span> (&lt;span style="color:#a6e22e">file-name-nondirectory&lt;/span> last-ics))
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> (&lt;span style="color:#a6e22e">kill-buffer&lt;/span> (&lt;span style="color:#a6e22e">file-name-nondirectory&lt;/span> last-ics))))
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>and produces a calendar of the form:&lt;/p>
&lt;figure>&lt;img src="https://www.emacs.dyerdwelling.family/emacs/0230204115142-emacs--Displaying-A-Calendar/2023-02-03_12-59.jpg" width="300px">
&lt;/figure>
&lt;p>I could import everything directly into my &lt;code>diary-file&lt;/code> but I decided to be a little more flexible and potentially allow for multiple calendars by setting an &lt;code>include&lt;/code> directive in my diary file as thus:&lt;/p>
&lt;pre tabindex="0">&lt;code class="language-nil" data-lang="nil">#include &amp;#34;~/DCIM/Backup/tmp.org&amp;#34;
&lt;/code>&lt;/pre>&lt;p>The key to the &lt;code>my/calendar&lt;/code> function is to pull in the most recent &lt;code>ics&lt;/code> file and call the built in &lt;code>icalendar-import-file&lt;/code> to convert / import the &lt;code>ics&lt;/code> data into my diary file. The &lt;strong>cfw&lt;/strong> packages will then take care of the rest. The other parts of the function are just tidying up various buffers and files to make things a little cleaner.&lt;/p>
&lt;p>One little wrinkle I discovered and didn&amp;rsquo;t necessary solve to a satisfactory level was when I first call up &lt;code>cfw:open-diary-calendar&lt;/code> the calendar didn&amp;rsquo;t resize correctly to fit the window, a second call however seemed to fix this and with no overhead.&lt;/p>
&lt;p>One final addition was the following:&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>(add-to-list &lt;span style="color:#e6db74">&amp;#39;display-buffer-alist&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#f92672">`&lt;/span>(&lt;span style="color:#f92672">,&lt;/span>(rx(or &lt;span style="color:#e6db74">&amp;#34;Calendar&amp;#34;&lt;/span>))
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> display-buffer-in-direction
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> (direction &lt;span style="color:#f92672">.&lt;/span> right)
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> (dedicated &lt;span style="color:#f92672">.&lt;/span> &lt;span style="color:#66d9ef">t&lt;/span>)
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> (window &lt;span style="color:#f92672">.&lt;/span> root)
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> (window-width &lt;span style="color:#f92672">.&lt;/span> &lt;span style="color:#ae81ff">80&lt;/span>)))
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>Which gives me greater control of where the calendar window is opened&lt;/p></description></item><item><title>Using ripgrep within Projects</title><link>https://www.emacs.dyerdwelling.family/emacs/20230124195440-emacs--my-tentative-steps-to-working-with-projects/</link><pubDate>Tue, 24 Jan 2023 21:07:00 +0000</pubDate><author>James Dyer</author><guid>https://www.emacs.dyerdwelling.family/emacs/20230124195440-emacs--my-tentative-steps-to-working-with-projects/</guid><description>&lt;p>Given my recent forays into the world of grepping in emacs using &lt;code>deadgrep&lt;/code> (and hence ripgrep) and my use of &lt;code>find-file-rg&lt;/code> which feeds into my current completion system of &lt;strong>ivy&lt;/strong> I think the next step is to try to set up a project and to see if I can gain any advantages in my workflow.&lt;/p>
&lt;figure>&lt;img src="https://www.emacs.dyerdwelling.family/emacs/20230124195440-emacs--My-Tentative-Steps-to-Working-With-Projects.jpg">
&lt;/figure>
&lt;p>I am not yet going to dive head first into &lt;code>projectile&lt;/code> but dangle a tentative pinky into the deep project pools of the built in project system, namely EDE (Emacs Development Environment).&lt;/p>
&lt;p>As I have mentioned before I typically would only be working within a single directory hierarchy, hence my previous &lt;code>deadgrep&lt;/code> enhancement of using a defined &lt;code>setq home-dir&lt;/code> variable, but I think it might be an enhancement if I could leverage &lt;code>deadgreps&lt;/code> use of projects; for example, wandering around the deadgrep code I found the following:&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 deadgrep--project-root ()
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#e6db74">&amp;#34;Guess the project root of the given FILE-PATH.&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> (let ((root default-directory)
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> (project (project-current)))
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> (when project
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> (cond ((&lt;span style="color:#a6e22e">fboundp&lt;/span> &lt;span style="color:#e6db74">&amp;#39;project-root&lt;/span>)
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#75715e">;; This function was defined in Emacs 28.&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> (setq root (project-root project)))
&lt;/span>&lt;/span>&lt;span style="display:flex;">&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:#75715e">;; Older Emacsen.&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> (-when-let (roots (project-roots project))
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> (setq root (&lt;span style="color:#a6e22e">car&lt;/span> roots))))))
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> (when root
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> (deadgrep--lookup-override root))))
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>as far as I can tell the default-directory is the current directory of the buffer which can be displayed by &lt;code>M-x pwd&lt;/code> but it is the (project-current) that I am more interested in.&lt;/p>
&lt;p>A quick investigation and it seems I need to turn on &lt;strong>global-ede-mode&lt;/strong> and then run &lt;strong>ede-new&lt;/strong> in the top level directory of my prospective project. This results in the creation of a Project.ede file containing some project information allowing the setting up of a collection of source files and instructions on how to build them. For the moment I&amp;rsquo;m not too interested in building anything, but more on how to trigger &lt;code>deadgreps&lt;/code> project logic by enabling this directory hierarchy as a project.&lt;/p>
&lt;p>As part of creating this project file it seems &lt;strong>ede-project-directories&lt;/strong> has also been added to my init file and an extra menu has appeared on the menu bar called &lt;strong>Development&lt;/strong>&lt;/p>
&lt;p>Is this all I need?, well lets evaluate the following in the top level folder:&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>(project-current)
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>I seem to get a long list of project information, including the project top level directory. Moving down the directory hierarchy and reevaluating the same expression gives me the same directory!&lt;/p>
&lt;p>So I am assuming that in &lt;strong>my/deadgrep&lt;/strong> bespoke wrapper, if I now replace:&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>(deadgrep search-term home-dir)
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>with:&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>(deadgrep search-term)
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>if I create a project in my former &lt;strong>home-dir&lt;/strong> directory location then my former functionality will have been preserved but with the added flexibility of being able to define other project locations, and also apparently subdirectories! this is actually pretty neat :)&lt;/p>
&lt;p>Now I have defined a rudimentary project and seems to work well with &lt;code>deadgrep&lt;/code> will it work well with my other favourite &lt;code>ripgrep&lt;/code> wrapper, namely &lt;code>find-file-rg&lt;/code>? Well there does seem to be some code to accommodate projects too, namely:&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>(let* ((dir (if current-prefix-arg
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> (find-file-rg--read-dir)
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> (or (let ((project (project-current)))
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> (when project
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> (if (&lt;span style="color:#a6e22e">fboundp&lt;/span> &lt;span style="color:#e6db74">&amp;#39;project-root&lt;/span>)
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> (project-root project)
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> (&lt;span style="color:#a6e22e">cdr&lt;/span> project))))
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>and yes, it works!, so anywhere I am within the hierarchy of a project I can call up a list of files fed from &lt;strong>ripgrep &amp;ndash;files&lt;/strong> into &lt;strong>ivy&lt;/strong> and complete as normal.&lt;/p>
&lt;p>As a bonus &lt;code>find-file-rg&lt;/code> allows the passing of a universal argument and a &lt;strong>current-prefix-arg&lt;/strong> check enables the navigation to any directory.&lt;/p>
&lt;p>In summary then, I can now define a project, I can grep throughout the project from any file / directory within that project and I can pull up a list of files within the project all leveraging the power of &lt;strong>ripgrep&lt;/strong> through &lt;code>deadgrep&lt;/code> and &lt;code>find-file-rg&lt;/code>!&lt;/p></description></item><item><title>More flexible grepping with deadgrep</title><link>https://www.emacs.dyerdwelling.family/emacs/20230120181918-emacs--better-grepping-with-deadgrep/</link><pubDate>Fri, 20 Jan 2023 00:00:00 +0000</pubDate><author>James Dyer</author><guid>https://www.emacs.dyerdwelling.family/emacs/20230120181918-emacs--better-grepping-with-deadgrep/</guid><description>&lt;p>I seem to be grepping a lot recently and I think the way I use &lt;code>deadgrep&lt;/code> can be improved a little.&lt;/p>
&lt;figure class="emacs-img">&lt;img src="https://www.emacs.dyerdwelling.family/emacs/20230120181918-emacs--Better-Grepping-with-Deadgrep.jpg">
&lt;/figure>
&lt;p>Currently &lt;code>deadgrep&lt;/code> defaults to a recursive &lt;code>ripgrep&lt;/code> from the &lt;code>default-directory&lt;/code> which is generally the current directory of the buffer, but I find that by default I tend to mostly want to grep from a top level directory (yes I know, almost like a project!).&lt;/p>
&lt;p>I would like to have a typical &lt;strong>Find All References&lt;/strong> type of functionality from my grepping and not to rely on &lt;code>xref&lt;/code> as I will not necessarily ever know if any &lt;code>xref&lt;/code> functionality is supported for any file that I am working on and for the moment I am not using any connection to an LSP server. I would like a simple generic process that can be used across all files and I think &lt;code>deadgdrep&lt;/code> can help me out with this.&lt;/p>
&lt;p>I would like to bind to &lt;code>S-f12&lt;/code> to grep from the &amp;ldquo;project&amp;rdquo; top level with the search set to whatever string is under my cursor; this should enable a quick workflow involving jumping around files within the top level directory structure bouncing back and forth between the &lt;code>deadgrep&lt;/code> buffer.&lt;/p>
&lt;p>I have chosen the binding of &lt;code>S-f12&lt;/code> for consistency across IDEs as I am often required to use VSCode and Visual Studio for work.&lt;/p>
&lt;p>In addition I would like to replace my usual local directory grepping where I use &lt;code>grep&lt;/code> with &lt;code>deadgrep&lt;/code> for a more unified approach.&lt;/p>
&lt;p>So I created the following two functions:&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/deadgrep ()
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> (interactive)
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> (if (&lt;span style="color:#a6e22e">equal&lt;/span> major-mode &lt;span style="color:#e6db74">&amp;#39;dired-mode&lt;/span>)
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> (setq search-term
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> (&lt;span style="color:#a6e22e">read-from-minibuffer&lt;/span> &lt;span style="color:#e6db74">&amp;#34;Search : &amp;#34;&lt;/span>))
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> (setq search-term
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> (&lt;span style="color:#a6e22e">read-from-minibuffer&lt;/span> &lt;span style="color:#e6db74">&amp;#34;Search : &amp;#34;&lt;/span> (thing-at-point &lt;span style="color:#e6db74">&amp;#39;symbol&lt;/span>)))
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> )
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> (deadgrep search-term home-dir)
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> )
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&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 ()
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> (interactive)
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> (if (&lt;span style="color:#a6e22e">equal&lt;/span> major-mode &lt;span style="color:#e6db74">&amp;#39;dired-mode&lt;/span>)
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> (setq search-term
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> (&lt;span style="color:#a6e22e">read-from-minibuffer&lt;/span> &lt;span style="color:#e6db74">&amp;#34;Search : &amp;#34;&lt;/span>))
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> (setq search-term
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> (&lt;span style="color:#a6e22e">read-from-minibuffer&lt;/span> &lt;span style="color:#e6db74">&amp;#34;Search : &amp;#34;&lt;/span> (thing-at-point &lt;span style="color:#e6db74">&amp;#39;symbol&lt;/span>)))
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> )
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> (deadgrep search-term)
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> )
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>home-dir as you might guess is where my top level directory resides.&lt;/p>
&lt;p>I came to realise that when I am in a &lt;code>dired&lt;/code> buffer I don&amp;rsquo;t actually ever want to grep with the string under the cursor (which of course would most likely be a file or directory) but only when I am in a file.&lt;/p>
&lt;p>I toyed around with the idea of having a single (interactive &amp;ldquo;p&amp;rdquo;) function so it would accept a prefix command and then perform the following kind of logic:&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>((&lt;span style="color:#a6e22e">equal&lt;/span> current-prefix-arg &lt;span style="color:#66d9ef">nil&lt;/span>) &lt;span style="color:#75715e">; no C-u&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> (do top level grep))
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>((&lt;span style="color:#a6e22e">equal&lt;/span> current-prefix-arg &lt;span style="color:#f92672">&amp;#39;&lt;/span>(&lt;span style="color:#ae81ff">4&lt;/span>)) &lt;span style="color:#75715e">; C-u&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> (do local grep))
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>((&lt;span style="color:#a6e22e">equal&lt;/span> current-prefix-arg &lt;span style="color:#ae81ff">1&lt;/span>) &lt;span style="color:#75715e">; C-u 1&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> (do some other grepping))
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>however this had the unintended consequence of pushing through the prefix command to &lt;code>deadgrep&lt;/code> and therefore it would not start immediately but wait for user interaction. I couldn&amp;rsquo;t see a way round this so had to split my grepping into both a &lt;code>S-f12&lt;/code> and &lt;code>M-f12&lt;/code> for each function call; not much of a big deal.&lt;/p>
&lt;p>As I am just running a single deadgrep instance using:&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 deadgrep-max-buffers &lt;span style="color:#ae81ff">1&lt;/span>)
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>I also needed to add the following as I will always want to kill the current process if I am starting a new one.&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 kill-buffer-query-functions &lt;span style="color:#66d9ef">nil&lt;/span>)
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div></description></item><item><title>Using org-copy-visible in dired</title><link>https://www.emacs.dyerdwelling.family/emacs/20230115202613-emacs--dired-copy-just-directory-list-to-kill-ring/</link><pubDate>Sun, 15 Jan 2023 00:00:00 +0000</pubDate><author>James Dyer</author><guid>https://www.emacs.dyerdwelling.family/emacs/20230115202613-emacs--dired-copy-just-directory-list-to-kill-ring/</guid><description>&lt;p>Just a quick one.&lt;/p>
&lt;p>Often it seems I need a copy of a list of files / directories in plain text without any gubbins such as a path, permissions, date and all those shenanigans, basically &lt;code>basenaming&lt;/code>; for example:&lt;/p>
&lt;figure class="emacs-img">&lt;img src="https://www.emacs.dyerdwelling.family/emacs/20230115202613-emacs--Dired-Copy-Just-Directory-List-To-Kill-Ring.jpg">
&lt;/figure>
&lt;p>So how can I achieve this in emacs? I would really prefer to use &lt;code>dired&lt;/code> somehow rather than &lt;code>shell / ls&lt;/code> (which was my first thought)&lt;/p>
&lt;p>Below is my typical dired listing:&lt;/p>
&lt;pre tabindex="0">&lt;code class="language-nil" data-lang="nil">drwxr-xr-x 4 4.0K Jan 15 19:35 Backup
drwxr-xr-x 3 4.0K Jan 14 19:33 Camera
drwxr-xr-x 22 4.0K Jan 15 19:01 content
-rw-r--r-- 1 65 Dec 31 16:34 .directory
&lt;/code>&lt;/pre>&lt;p>Rectangle marking first came to mind but the paste seems to have a weird format and strangely inserts the text.&lt;/p>
&lt;p>So I came up with the following process:&lt;/p>
&lt;p>In &lt;code>dired&lt;/code>, select &lt;strong>&amp;rsquo;(&amp;rsquo;&lt;/strong> &lt;code>dired-hide-details-mode&lt;/code> which toggles off all the details and gives:&lt;/p>
&lt;pre tabindex="0">&lt;code class="language-nil" data-lang="nil">Backup
Camera
content
.directory
&lt;/code>&lt;/pre>&lt;p>This looks very promising and surely a simple &lt;strong>M-w&lt;/strong> (kill-ring-save) will work?. It doesn&amp;rsquo;t work. It still copies the full details :(&lt;/p>
&lt;p>However if org-copy-visible is used it does the same as in org mode in that it only copies the visible parts of the region. I had no idea that functions from one mode can be used in another!&lt;/p></description></item><item><title>Creating Album Art Thumbnails for EMMS</title><link>https://www.emacs.dyerdwelling.family/emacs/20230112122044-emacs--creating-album-art-thumbnails-for-emms/</link><pubDate>Fri, 13 Jan 2023 00:00:00 +0000</pubDate><author>James Dyer</author><guid>https://www.emacs.dyerdwelling.family/emacs/20230112122044-emacs--creating-album-art-thumbnails-for-emms/</guid><description>&lt;p>I have been looking for a music player on Linux for a while now but haven&amp;rsquo;t really settled on one; I have simple requirements:&lt;/p>
&lt;ul>
&lt;li>A view showing album art&lt;/li>
&lt;li>An easy way to play random tracks&lt;/li>
&lt;li>A quick method to skip a track&lt;/li>
&lt;/ul>
&lt;hr>
&lt;p>To date I have only been considering players with a graphical front end but it always seems a hassle to, open the program, look for some music, figure out how the shuffle and play work; and then what if I don&amp;rsquo;t like the current track playing?, my laptop isn&amp;rsquo;t really set up to quickly skip the track.&lt;/p>
&lt;p>Now I am delving further into the integrated environment of emacs I thought I would see what was on offer.&lt;/p>
&lt;p>A little research and I settled on &lt;strong>EMMS&lt;/strong>&lt;/p>
&lt;p>I will go into a deeper review after I have used it for a few months but mainly for this post I wanted to share a quick bash script I created to allow album art to be shown in EMMS.&lt;/p>
&lt;p>By default EMMS looks for a &lt;strong>cover.jpg&lt;/strong> file in the current music directory. My collection is neatly split into directories for each album with album art embedded into each mp3 track. This has given me the opportunity to write a bash script to automate the creation of these cover.jpg files.&lt;/p>
&lt;p>The script is as follows:&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-bash" data-lang="bash">&lt;span style="display:flex;">&lt;span>&lt;span style="color:#75715e">#!/bin/bash
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#75715e">&lt;/span>DIRS&lt;span style="color:#f92672">=&lt;/span>&lt;span style="color:#66d9ef">$(&lt;/span>find &lt;span style="color:#e6db74">&amp;#34;&lt;/span>$HOME&lt;span style="color:#e6db74">/MyMusicLibrary&amp;#34;&lt;/span> -type d -printf &lt;span style="color:#e6db74">&amp;#39;%p;&amp;#39;&lt;/span>&lt;span style="color:#66d9ef">)&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>export IFS&lt;span style="color:#f92672">=&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>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#66d9ef">for&lt;/span> dir in $DIRS; &lt;span style="color:#66d9ef">do&lt;/span> cd &lt;span style="color:#e6db74">&amp;#34;&lt;/span>$dir&lt;span style="color:#e6db74">&amp;#34;&lt;/span> files&lt;span style="color:#f92672">=(&lt;/span>*&lt;span style="color:#f92672">)&lt;/span> &lt;span style="color:#66d9ef">if&lt;/span> &lt;span style="color:#f92672">[[&lt;/span> &lt;span style="color:#e6db74">${&lt;/span>files[0]: -4&lt;span style="color:#e6db74">}&lt;/span> &lt;span style="color:#f92672">==&lt;/span> &lt;span style="color:#e6db74">&amp;#34;.mp3&amp;#34;&lt;/span> &lt;span style="color:#f92672">]]&lt;/span>; &lt;span style="color:#66d9ef">then&lt;/span> echo $dir ffmpeg -hide_banner -loglevel panic -stats -y &lt;span style="color:#ae81ff">\ &lt;/span>-i &lt;span style="color:#e6db74">&amp;#34;&lt;/span>&lt;span style="color:#e6db74">${&lt;/span>files[0]&lt;span style="color:#e6db74">}&lt;/span>&lt;span style="color:#e6db74">&amp;#34;&lt;/span> -an -c:v copy &lt;span style="color:#e6db74">&amp;#34;&lt;/span>$dir&lt;span style="color:#e6db74">/cover.jpg&amp;#34;&lt;/span> convert -resize 120x120 &lt;span style="color:#e6db74">&amp;#34;&lt;/span>$dir&lt;span style="color:#e6db74">/cover.jpg&amp;#34;&lt;/span> &lt;span style="color:#e6db74">&amp;#34;&lt;/span>$dir&lt;span style="color:#e6db74">/cover.jpg&amp;#34;&lt;/span> &lt;span style="color:#66d9ef">fi&lt;/span> &lt;span style="color:#66d9ef">done&lt;/span>
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>Of course change the &lt;code>&amp;quot;$HOME/MyMusicLibrary&amp;quot;&lt;/code> to your music library location and then in the emacs init file add the following:&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 emms-browser-covers &lt;span style="color:#e6db74">&amp;#39;emms-browser-cache-thumbnail-async&lt;/span>)
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>and this is an example of the result (my music collection is larger than this!):&lt;/p>
&lt;figure>&lt;img src="https://www.emacs.dyerdwelling.family/emacs/20230112122044-emacs--Creating-Album-Art-Thumbnails-for-EMMS.jpg" width="300px">
&lt;/figure></description></item><item><title>Dired Duplicate Here</title><link>https://www.emacs.dyerdwelling.family/emacs/20230110115530-emacs--dired-quick-item-duplication/</link><pubDate>Tue, 10 Jan 2023 00:00:00 +0000</pubDate><author>James Dyer</author><guid>https://www.emacs.dyerdwelling.family/emacs/20230110115530-emacs--dired-quick-item-duplication/</guid><description>&lt;p>&lt;code>dired&lt;/code> can do most things for me especially now I have my DWIM image conversion scripts working and &lt;code>image-dired&lt;/code> configured to my liking.&lt;/p>
&lt;figure class="emacs-img">&lt;img src="https://www.emacs.dyerdwelling.family/emacs/20230110115530-emacs--Dired-Quick-Item-Duplication.jpg">
&lt;/figure>
&lt;p>However sometimes I do just prefer using a GUI file manager; for example, drag and drop, image file preview e.t.c.&lt;/p>
&lt;p>I am currently using the Dolphin file manager on KDE and one context menu service that I have enabled and use often is the &lt;strong>Duplicate Here&lt;/strong> function. It is often that I like to back up a file / directory to a quick temporary directory especially when I am hacking around with the original and on windows it is often that I copy and then paste with Windows automatically applying a &lt;code>Copy&lt;/code> to the original name. Dolphin does something similar but applies a &lt;code>(1)&lt;/code> when it suggests a new name.&lt;/p>
&lt;p>Enough rambling!, simply I would like to reproduce this functionality in &lt;code>dired&lt;/code> but with the improvement of not adding in spaces to the new name and being able to define any unique naming convention I like.&lt;/p>
&lt;p>So in &lt;code>dired&lt;/code> to achieve this, simply:&lt;/p>
&lt;ul>
&lt;li>open dired&lt;/li>
&lt;li>cursor over the desired item to rename&lt;/li>
&lt;li>&lt;code>w&lt;/code> (dired-copy-filename-as-kill)&lt;/li>
&lt;li>&lt;code>C&lt;/code> (dired-do-copy)&lt;/li>
&lt;li>paste from the kill ring (yank)&lt;/li>
&lt;li>modify the filename as desired&lt;/li>
&lt;li>return&lt;/li>
&lt;/ul>
&lt;p>pretty simple and uses all standard &lt;code>dired&lt;/code> functionality and of course can be converted to a macro for a certain reproducible naming conventions and then maybe an attachment to a keybinding.&lt;/p>
&lt;p>Note that it might be worth adding the following to your emacs init file so no confirmation will be required when copying directories:&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 dired-recursive-copies &lt;span style="color:#e6db74">&amp;#39;always&lt;/span>)
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div></description></item><item><title>Merging org files for Hugo static site</title><link>https://www.emacs.dyerdwelling.family/emacs/20230109113207-emacs--tidying-hugo-org-files-using-emacs/</link><pubDate>Mon, 09 Jan 2023 00:00:00 +0000</pubDate><author>James Dyer</author><guid>https://www.emacs.dyerdwelling.family/emacs/20230109113207-emacs--tidying-hugo-org-files-using-emacs/</guid><description>&lt;p>I have just started the process of reducing the number of org files I maintain for my web site. I now have a greater understanding of how &lt;strong>Hugo&lt;/strong> handles these files and I think can both simplify and make them more flexible.&lt;/p>
&lt;p>Hugo is my static site generator of choice and although it supports org files directly I prefer to generate to a markdown file as an intermediate step using a file by file evaluation of &lt;code>org-hugo-auto-export-mode&lt;/code>. This means that each org subtree I modify automatically generates an &lt;strong>md&lt;/strong> file to a defined folder dictated by &lt;code>#+hugo_section&lt;/code> in the org header.&lt;/p>
&lt;p>For example, I have a few org files that generate my art blog; one for videos, finished art, galleries and now AI. Each org file has a single header &lt;code>#+hugo_section&lt;/code> defining the destination export folder. I think now I would like a bit more flexibility with the ability to push a subtree to a folder of my choosing so any extra sections can more easily be added. Plus, if I merge I would only have to maintain a single org file (although a larger one!).&lt;/p>
&lt;p>To achieve this I will have to add an extra property to the existing org drawer for each subtree using &lt;code>:EXPORT_HUGO_SECTION:&lt;/code> and then to adapt my &lt;code>org-capture&lt;/code> templates.&lt;/p>
&lt;p>Now of course to add the subtree property emacs can certainly help me out.&lt;/p>
&lt;p>I would like to insert typically a drawer property line for each subtree such as:&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>:EXPORT_HUGO_SECTION: art--all
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>However of course things are not quite so simple. It seems that some subtrees already have such property lines even though the destination folder is ultimately the same as &lt;code>#+hugo_section&lt;/code> in the header (another good reason to tidy up)&lt;/p>
&lt;p>So rather than a simple &lt;code>isearch&lt;/code> to an ever present subtree anchor point (for example &lt;code>:EXPORT_FILE_NAME:&lt;/code>) and then the insertion of the new line I will need to take into account those subtrees that already have &lt;code>:EXPORT_HUGO_SECTION:&lt;/code> and not insert a line.&lt;/p>
&lt;p>I considered edit mode in &lt;code>multi-occur&lt;/code> but as far as I can tell it doesn&amp;rsquo;t work too well when new lines are inserted.&lt;/p>
&lt;p>&lt;code>deadgrep&lt;/code> (ripgrep) also has similar functionality and will also show context around the search point which can be also edited in a similar manner to &lt;code>multi-occur&lt;/code> but all this is starting to feel too fiddly and I am starting to spend too much time investigating this!&lt;/p>
&lt;p>The grep/occur to edit buffer approach can work well in certain situations but this is a little more complex involving a small amount of logic. At this stage the same thought always occurs to me and I really wish it would occur to me earlier.&lt;/p>
&lt;blockquote>
&lt;p>what about macros?&lt;/p>
&lt;/blockquote>
&lt;p>The &lt;code>isearch&lt;/code> technique won&amp;rsquo;t quite work (as explained above), but what about &lt;code>isearch-regexp&lt;/code>?&lt;/p>
&lt;p>As the properties are always in the same order I could just insert the &lt;code>:EXPORT_HUGO_SECTION:&lt;/code> line if I find the following regex:&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>:EXPORT_FILE_NAME:.*
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>:EXPORT_HUGO_LASTMOD:
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>&lt;em>Note that the carriage return is achieved by &lt;code>C-q C-j&lt;/code>&lt;/em>&lt;/p>
&lt;p>Incorporate this into a macro and the cursor will only move to a point that doesn&amp;rsquo;t have a &lt;code>:EXPORT_HUGO_SECTION:&lt;/code> line and hence I can then insert and then repeat until my org file is suitable amended!&lt;/p>
&lt;figure class="emacs-img">&lt;img src="https://www.emacs.dyerdwelling.family/emacs/20230109113207-emacs--Tidying-Hugo-org-files-using-emacs.jpg">
&lt;/figure></description></item><item><title>Quick Bash Scripts Augmenting Org Files</title><link>https://www.emacs.dyerdwelling.family/emacs/20221221194247-emacs--bash-scripts-augmenting-org-files/</link><pubDate>Wed, 21 Dec 2022 00:00:00 +0000</pubDate><author>James Dyer</author><guid>https://www.emacs.dyerdwelling.family/emacs/20221221194247-emacs--bash-scripts-augmenting-org-files/</guid><description>&lt;p>This post isn&amp;rsquo;t strictly about what can be achieved within the emacs ecosystem but what can be achieved outside it while still binding to the workflow principles of org mode.&lt;/p>
&lt;p>I have yet to convert to any of the internal shells/terminal emulators within emacs, I have continued to Control-Alt-T (C-M-t) term my way around linux; although this is becoming less frequent due to my greater proficiency with &lt;strong>dired&lt;/strong>&lt;/p>
&lt;p>As my org journey evolves I have now realised that I have two files I rely on for my quick capturing needs. When an idea pops into my head I am usually emacs bound anyway so &lt;strong>org-capture&lt;/strong> is always there for me. But what if I am alfresco?, and maybe, just maybe I am in a terminal! Well I could quickly switch to emacs as it is 99% likely to be already running, but what if it is not?!&lt;/p>
&lt;p>I could open emacs and then do my thing but emacs now takes a few seconds to load up and then for me just to close it down again afterwards?! (as if I would) Isn&amp;rsquo;t there another way?, a better way? Could I idea push to an org file from the terminal with a quick flick of the fingers, could &lt;strong>bash&lt;/strong> help me out here?, so many questions&amp;hellip;&lt;/p>
&lt;p>Well the answer is that yes, bash can help me out.&lt;/p>
&lt;p>For me, any new items using org-capture are always put under the headline, hence using &lt;code>file+headline&lt;/code> in my capture template. How will this help?, well using the power of bash and the venerable &lt;strong>sed&lt;/strong> command I can search and replace the headline but replace with an extra piece of data, namely a string of my choosing, effectively placing my TODO or note at the top of the org file list!&lt;/p>
&lt;p>I thus came up with the following &lt;code>bash&lt;/code> scripts:&lt;/p>
&lt;h2 id="todo">todo&lt;/h2>
&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-bash" data-lang="bash">&lt;span style="display:flex;">&lt;span>&lt;span style="color:#75715e">#!/bin/bash
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#75715e">&lt;/span>&lt;span style="color:#66d9ef">if&lt;/span> &lt;span style="color:#f92672">[[&lt;/span> ! -z $@ &lt;span style="color:#f92672">]]&lt;/span>; &lt;span style="color:#66d9ef">then&lt;/span> sed --in-place &lt;span style="color:#e6db74">&amp;#39;s/* Tasks/* Tasks\n** TODO &amp;#39;&lt;/span>&lt;span style="color:#e6db74">&amp;#34;&lt;/span>&lt;span style="color:#66d9ef">$(&lt;/span>echo &lt;span style="color:#e6db74">${&lt;/span>@&lt;span style="color:#e6db74">}&lt;/span>&lt;span style="color:#66d9ef">)&lt;/span>&lt;span style="color:#e6db74">&amp;#34;&lt;/span>&lt;span style="color:#e6db74">&amp;#39;/g&amp;#39;&lt;/span> &lt;span style="color:#ae81ff">\ &lt;/span>&lt;span style="color:#e6db74">&amp;#34;~/DCIM/content/aa--todo.org&amp;#34;&lt;/span> &lt;span style="color:#66d9ef">fi&lt;/span>
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;h2 id="note">note&lt;/h2>
&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-bash" data-lang="bash">&lt;span style="display:flex;">&lt;span>&lt;span style="color:#75715e">#!/bin/bash
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#75715e">&lt;/span>&lt;span style="color:#66d9ef">if&lt;/span> &lt;span style="color:#f92672">[[&lt;/span> ! -z $@ &lt;span style="color:#f92672">]]&lt;/span>; &lt;span style="color:#66d9ef">then&lt;/span> sed --in-place &lt;span style="color:#e6db74">&amp;#39;s/* Notes/* Notes\n** &amp;#39;&lt;/span>&lt;span style="color:#e6db74">&amp;#34;&lt;/span>&lt;span style="color:#66d9ef">$(&lt;/span>echo &lt;span style="color:#e6db74">${&lt;/span>@&lt;span style="color:#e6db74">}&lt;/span>&lt;span style="color:#66d9ef">)&lt;/span>&lt;span style="color:#e6db74">&amp;#34;&lt;/span>&lt;span style="color:#e6db74">&amp;#39;/g&amp;#39;&lt;/span> &lt;span style="color:#ae81ff">\ &lt;/span>&lt;span style="color:#e6db74">&amp;#34;~/DCIM/content/aa--notes.org&amp;#34;&lt;/span> &lt;span style="color:#66d9ef">fi&lt;/span>
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>Here is my original todo org file:&lt;/p>
&lt;figure>&lt;img src="https://www.emacs.dyerdwelling.family/emacs/20221221194247-emacs--Bash-Scripts-Augmenting-Org-Files/2022-12-21_20-01.jpg" width="300px">
&lt;/figure>
&lt;p>On the command line I can now enter:&lt;/p>
&lt;pre tabindex="0">&lt;code class="language-nil" data-lang="nil">todo generate art slideshows
&lt;/code>&lt;/pre>&lt;p>and in a flash my org file would now 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-text" data-lang="text">&lt;/code>&lt;/pre>&lt;/div>&lt;figure>&lt;img src="https://www.emacs.dyerdwelling.family/emacs/20221221194247-emacs--Bash-Scripts-Augmenting-Org-Files/2022-12-21_20-01_1.jpg" width="300px">
&lt;/figure>
&lt;p>all nice and ready for the next time I open emacs!, and of course now I can also add notes, or anything else of course.&lt;/p>
&lt;p>My aim was for the scripts to be very simple, if you pass in no text / arguments no files will be augmented, generally the idea is just to pass in a single string, maybe multiple lines could be passed using &lt;code>\n&lt;/code> but that isn&amp;rsquo;t the way I wanted to use them.&lt;/p></description></item><item><title>RIP ripgrep-regexp, long live deadgrep!</title><link>https://www.emacs.dyerdwelling.family/emacs/20221207210603-emacs--rip-ripgrep-regexp-long-live-deadgrep/</link><pubDate>Wed, 07 Dec 2022 00:00:00 +0000</pubDate><author>James Dyer</author><guid>https://www.emacs.dyerdwelling.family/emacs/20221207210603-emacs--rip-ripgrep-regexp-long-live-deadgrep/</guid><description>&lt;p>Well this is quite a turn up for the books, I seem to have already quickly moved on from emacs &lt;strong>ripgrep-regexp&lt;/strong>! to something better and that is a package called &lt;strong>deadgrep&lt;/strong>&lt;/p>
&lt;figure class="emacs-img">&lt;img src="https://www.emacs.dyerdwelling.family/emacs/20221207210603-emacs--RIP-ripgrep-regexp-Long-Live-DeadGrep.jpg">
&lt;/figure>
&lt;p>Now why is this? I hear you all ask, your new grepping workflow seemed perfect, a process of file searching that could last the ages. Well as it turns out that age was more of a collection of weeks.&lt;/p>
&lt;p>I started to struggle with the compilation-mode output of &lt;strong>ripgrep-regexp&lt;/strong> in that a next-error call would only take me to the requisite file if I entered a directory name. I just wanted to spam F8/next-error my way through all resulted grepped files so I could get a general sense of which files contained my search as I don&amp;rsquo;t always know what I am looking for. The constant calling up of the directory to input was seriously disrupting my workflow and after some investigation I was struggling to find a way to resolve this.&lt;/p>
&lt;p>The main issue seemed to be &lt;strong>compilation-mode&lt;/strong>&lt;/p>
&lt;p>&lt;strong>compilation-mode&lt;/strong> seems to depend on &lt;strong>compilation-search-path&lt;/strong> to locate a file automatically in a buffer. This I suspect is generally ok when compilation-mode is tied into a build project as this path is built up automatically, for example building Ada files typically will parse a gpr file to construct search directories. Maybe projectile or project.el could help me resolve this, but as mentioned in previous posts currently a project concept doesn&amp;rsquo;t yet fit into my workflow.&lt;/p>
&lt;p>Well I guess I could create a list of search directories and add it to the &lt;strong>compilation-search-path&lt;/strong>, 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-elisp" data-lang="elisp">&lt;span style="display:flex;">&lt;span>(setq my-search-directories (&lt;span style="color:#a6e22e">append&lt;/span> &lt;span style="color:#f92672">&amp;#39;&lt;/span>(&lt;span style="color:#e6db74">&amp;#34;-/bin&amp;#34;&lt;/span> &lt;span style="color:#e6db74">&amp;#34;-/test&amp;#34;&lt;/span>) &lt;span style="color:#f92672">&amp;#39;&lt;/span>(&lt;span style="color:#e6db74">&amp;#34;-/content&amp;#34;&lt;/span> &lt;span style="color:#e6db74">&amp;#34;~/content/hugo&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>(setq compilation-search-path my-search-directories)
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>but this isn&amp;rsquo;t really very flexible, I would have to keep modifying this search directory list as my disk directory hierarchy evolves.&lt;/p>
&lt;p>What I need then is an emacs package that doesn&amp;rsquo;t rely on compilation-mode for its output and will just leverage the ripgrep output to directly locate files.&lt;/p>
&lt;p>This is where I found &lt;strong>deadgrep&lt;/strong> and in fact the package text immediately applied half a pot of emollient to the skin of my current rash of irritations :&lt;/p>
&lt;blockquote>
&lt;p>Perform text searches with the speed of ripgrep and the comfort of Emacs. This is a bespoke mode that does not rely on compilation-mode, but tries to be a perfect fit for ripgrep.&lt;/p>
&lt;/blockquote>
&lt;p>The out of the box behavior is to search for an input string entered in the minibuffer for all occurrences from the current directory. The output is a ripgrep output grouping grep finds together file by file.&lt;/p>
&lt;p>Some options appear on the top of the output buffer to provide some quick configuration ripgrep options, such as:&lt;/p>
&lt;pre tabindex="0">&lt;code class="language-nil" data-lang="nil">Search term: &amp;lt;search string input&amp;gt; change
Search type: string words regexp
Case: smart sensitive ignore
Directory: &amp;lt;search directory&amp;gt;
Files: all type glob
&lt;/code>&lt;/pre>&lt;p>and all the occurrences of the search string are highlighted.&lt;/p>
&lt;p>So the next step (pun intended) is to step through these results and see if they open a window/buffer for each search result.&lt;/p>
&lt;p>F8/next-error does the trick!, so a continual F8 opens the relevant grepped file result in a window/buffer and without the need to confirm a directory.&lt;/p>
&lt;p>So out of the box deadgrep seems to work perfectly for me, it is lightning fast (well I guess it would be). I can step through the results quickly, it respects my &lt;strong>.ignore&lt;/strong> file and as a bonus allows a quick changing of ripgrep configuration options!, what more could an emacs user want?! (don&amp;rsquo;t answer that!)&lt;/p></description></item><item><title>RIP grep, long live ripgrep!</title><link>https://www.emacs.dyerdwelling.family/emacs/20221130193223-emacs--rip-grep-long-live-ripgrep/</link><pubDate>Wed, 30 Nov 2022 00:00:00 +0000</pubDate><author>James Dyer</author><guid>https://www.emacs.dyerdwelling.family/emacs/20221130193223-emacs--rip-grep-long-live-ripgrep/</guid><description>&lt;p>I have come to the realisation that I can be a little more efficient when it comes to searching for text within files, or as it is known in software engineering circles, &lt;strong>grepping&lt;/strong>!&lt;/p>
&lt;figure class="emacs-img">&lt;img src="https://www.emacs.dyerdwelling.family/emacs/20221113093223-emacs--RIP-Grep-Long-Live-RipGrep.jpg">
&lt;/figure>
&lt;p>I am often looking for a string within a collection of files and mainly for a &lt;strong>Find All References&lt;/strong> type of functionality. Typically I would want to accept a string (could be regex) and a directory and the search to descend through all sub directories. Of course this could be achieved in many different ways :&lt;/p>
&lt;ol>
&lt;li>Open up an external terminal and grep -iR&lt;/li>
&lt;li>dired grepping using find-grep-dired&lt;/li>
&lt;li>Built in in grep-find function&lt;/li>
&lt;li>Is there a project.el way to recursively grep?&lt;/li>
&lt;li>Projectile grepping?&lt;/li>
&lt;/ol>
&lt;p>and probably many more that I don&amp;rsquo;t even know about.&lt;/p>
&lt;p>Initially I ran grep from an external terminal, examined the scrolling output and then hopped back into emacs.&lt;/p>
&lt;p>Not the greatest workflow.&lt;/p>
&lt;p>I guess running a shell or terminal emulator from within emacs may help but I still currently prefer to open an external console to interact with the command line.&lt;/p>
&lt;p>At times I have grepped within emacs using the built in &lt;strong>grep&lt;/strong> function which worked well but unfortunately didn&amp;rsquo;t allow recursive grepping although often I would only want to search in a local directory anyway.&lt;/p>
&lt;p>However the calling of this emacs function opened my eyes a little to the possibilities of emacs integrating with a search output, namely the use of &lt;strong>next-error&lt;/strong> to step through the file list.&lt;/p>
&lt;p>For me F8 is already bound to &lt;strong>next-error&lt;/strong> for debugging purposes and it seems that this can also be used to step through the results from an emacs grep call, meaning that I can quickly step through each file search result and open a new window buffer with the cursor resting on the grepped line.&lt;/p>
&lt;p>A quick look at the out of the box experience for &lt;strong>find-grep-dired&lt;/strong> was subdirectory recursive but just listed the files it found containing the text and not the context so pretty much a no go for me.&lt;/p>
&lt;p>The next thing to experiment with is &lt;strong>grep-find&lt;/strong> which seems basically to leverage a find command, typically in the following format:&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-shell" data-lang="shell">&lt;span style="display:flex;">&lt;span>find . -type f -exec grep -ni &amp;lt;string&amp;gt; /dev/null &lt;span style="color:#ae81ff">\{\}&lt;/span> +
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>An F8 takes me to the grepped line as it also seems to running in a grep output mode, so this is pretty good. There are however a couple of downsides:&lt;/p>
&lt;ol>
&lt;li>the command is non native emacs&lt;/li>
&lt;li>filtering&lt;/li>
&lt;/ol>
&lt;p>Working in a software version control system means that there can be quite a few directories I don&amp;rsquo;t really want to consider in my search query and generally I am just concerned to grep source code. It would be nice if there was a way to filter out files/directories, which not only would clean up the grep output but also speed up the search.&lt;/p>
&lt;p>As we know &lt;strong>find&lt;/strong> can be very annoying and fiddly when it comes to filtering and generally involves a syntactical nightmare of prunes, logic and parentheses, plus you can&amp;rsquo;t define all these exclusions in a single file like &lt;em>rsync&lt;/em> or &lt;em>tar&lt;/em> for example.&lt;/p>
&lt;p>Ok, so lets carry on, what is next?, it is projects&amp;hellip; Now I am going to cheat a little here, I currently don&amp;rsquo;t use any concept of a project or a collection of related files in emacs, I seem to have no need for this functionality at the moment and to be completely honest I have in the past tried to incorporate a project concept into my workflow but it just doesn&amp;rsquo;t seen to work for me, maybe a later date.&lt;/p>
&lt;p>Which leaves the elephant in the room (and the one that I have so far deliberately not mentioned to irk those of you who are currently rage typing with great vigour in the comment section), the large awesome skulking grep monster! and that of course is &lt;strong>ripgrep&lt;/strong>&lt;/p>
&lt;p>I was reluctant at first as it didn&amp;rsquo;t seem to come pre installed in any of my distros and when I ran it I got a whole slew of lines, why do I need lines of context around the search finds?. I don&amp;rsquo;t like this one bit, but hang on!, its fast, really fast, maybe I should open up the man page and spend a bit of time with it.&lt;/p>
&lt;p>A little time playing with &lt;strong>ripgrep&lt;/strong> I came to the realisation that this tool can be tailored to just show the single line of a grep search result and more than that it can use an exclusion file!, called &lt;strong>.ignore&lt;/strong>&lt;/p>
&lt;p>Ok so this tool looks pretty promising, so on to the next step, and of course that is to find an emacs package that can give me an integrated emacs ripgrep experience. So firing up &lt;strong>list-packages&lt;/strong> I settled on a package called (surprisingly), &lt;strong>ripgrep&lt;/strong> which is just a simple front end to &amp;ldquo;rg* (the installed ripgrep executable) and comes with a command &amp;ldquo;ripgrep-regexp* and leverages compilation-mode to display the results. As the output is compilation mode it means that the F8 next-error binding will just work out the box.&lt;/p>
&lt;p>After a little playing around I can start to tailor the &lt;strong>.ignore&lt;/strong> filter file to trim down the results and make the already lightning fast &lt;strong>ripgrep&lt;/strong> to be extra super duper turbo charged, and the results can be navigated by using F8 which currently sits nicely into my workflow.&lt;/p>
&lt;p>Now I have bound &lt;strong>ripgrep-regexp&lt;/strong> to F8 in emacs the workflow is much improved and I can now stay in emacs for longer when I want to run a &lt;strong>Find All References&lt;/strong> type of functionality&lt;/p></description></item><item><title>Expanding Text using abbrev and skeletons</title><link>https://www.emacs.dyerdwelling.family/emacs/20221123131342-emacs--expanding-text-using-abbrev/</link><pubDate>Wed, 23 Nov 2022 00:00:00 +0000</pubDate><author>James Dyer</author><guid>https://www.emacs.dyerdwelling.family/emacs/20221123131342-emacs--expanding-text-using-abbrev/</guid><description>&lt;p>My next investigation into trying to improve my emacs workflow is expanding entered text for repetitive tasks.&lt;/p>
&lt;figure class="emacs-img">&lt;img src="https://www.emacs.dyerdwelling.family/emacs/20221123131342-emacs--Expanding-Text-using-abbrev.jpg">
&lt;/figure>
&lt;p>I&amp;rsquo;m not yet sure how useful this will be for me as I typically copy and paste my way through the creation of text files, especially org files. Maybe org-capture could be a help?&lt;/p>
&lt;p>As always I want to try built in options to augment my vanilla emacs setup, so the obvious place to look is abbrev.&lt;/p>
&lt;p>This facility can be geared towards fixing common spelling &lt;em>misteaks&lt;/em> (ha!, see what I did there!) or expanding acronyms. I don&amp;rsquo;t really want to be using that aspect however as I read somewhere that you can link abbrevs to skels, my aim is to type a few unique characters which would autofill a common textual structure, for example PROPERTIES in an org file.&lt;/p>
&lt;p>What about yasnippet! I all hear you vehemently proclaim!, but of course this is not built in, so no I shall not be considering this option for now.&lt;/p>
&lt;p>I am not even familiar with defining a skeleton (skel), so what on earth am I thinking about jumping in like this for potentially a negligible return, well of course the answer is twofold:&lt;/p>
&lt;ol>
&lt;li>it is fun, this is of course emacs we are talking about.&lt;/li>
&lt;li>in the long run it will improve my productivity.&lt;/li>
&lt;/ol>
&lt;p>The usual way I approach absorbing something new is to first read around all the options and then to start simple. By simple I mean setting up some uncomplicated skeletons and elementary abbrevs to initially test my setup. Then after a while adjusting to this new way of working to gradually start learning the more complex and subtle aspects of the functionality.&lt;/p>
&lt;p>Firstly lets try and set up a simple properties skeleton, namely :&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>(define-skeleton org-hugo-properties-skel
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#e6db74">&amp;#34;org properties skeleton.&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#e6db74">&amp;#34;\n&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#e6db74">&amp;#34;:PROPERTIES:\n&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#e6db74">&amp;#34;:EXPORT_FILE_NAME:\n&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#e6db74">&amp;#34;:EXPORT_HUGO_LASTMOD: &amp;#34;&lt;/span> (&lt;span style="color:#a6e22e">format-time-string&lt;/span> &lt;span style="color:#e6db74">&amp;#34;%Y-%m-%d&amp;#34;&lt;/span>) &lt;span style="color:#e6db74">&amp;#34;\n&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#e6db74">&amp;#34;:EXPORT_HUGO_CUSTOM_FRONT_MATTER+: :thumbnail\n&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#e6db74">&amp;#34;:END:\n&amp;#34;&lt;/span>)
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>This is a typical setup for inserting a Hugo article into an org file ready for export, in fact this article is written using this framework.&lt;/p>
&lt;p>I can call this by defining keybinding such as :&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>(global-set-key (kbd &lt;span style="color:#e6db74">&amp;#34;C-c i p&amp;#34;&lt;/span>) &lt;span style="color:#e6db74">&amp;#39;org-hugo-properties-skel&lt;/span>)
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>but I suspect my tiny brain is currently full to capacity of emacs keybindings and the ones that are in my head need a little more time to settle in before I commit to any more.&lt;/p>
&lt;p>My overall idea is to define a family of common characters that I can insert for expansion as in this case I am talking about inserting a predefined set of text rather than some emacs function, so why commit to another keybinding?&lt;/p>
&lt;p>So lets set up &lt;strong>abbrev&lt;/strong> for this task, the first thing to do is to set up an &lt;strong>abbrev_defs&lt;/strong> file for my abbreviation definitions in my emacs init file as thus:&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 abbrev-file-name (&lt;span style="color:#a6e22e">concat&lt;/span> home-dir &lt;span style="color:#e6db74">&amp;#34;/content/abbrev_defs&amp;#34;&lt;/span>))
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>(setq-default abbrev-mode &lt;span style="color:#66d9ef">t&lt;/span>)
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>In this file I can define an elisp definition, by either linking a sequence of characters to a string or a skel, so I define thus :&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>(define-abbrev-table &lt;span style="color:#e6db74">&amp;#39;global-abbrev-table&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#f92672">&amp;#39;&lt;/span>(
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> (&lt;span style="color:#e6db74">&amp;#34;hugprop&amp;#34;&lt;/span> &lt;span style="color:#e6db74">&amp;#34;&amp;#34;&lt;/span> org-hugo-properties-skel)
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> ))
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>There are commands to insert, edit and perform various different operations on this file but really all I want for now is a simple text file that I edit in emacs to add in any new definitions.&lt;/p>
&lt;p>So typing &lt;strong>hugprop&lt;/strong> &amp;lt;space&amp;gt; gives me:&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>:PROPERTIES:
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>:EXPORT_FILE_NAME:
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>:EXPORT_HUGO_LASTMOD: &amp;lt;2022-12-11&amp;gt;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>:EXPORT_HUGO_CUSTOM_FRONT_MATTER+: :thumbnail
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>:END:
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#960050;background-color:#1e0010">#&lt;/span>+hugo: more
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>In the &lt;strong>define-abbrev-table&lt;/strong> I can also define a mapping to a simple string, for example:&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>(define-abbrev-table &lt;span style="color:#e6db74">&amp;#39;global-abbrev-table&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#f92672">&amp;#39;&lt;/span>(
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> (&lt;span style="color:#e6db74">&amp;#34;hugprop&amp;#34;&lt;/span> &lt;span style="color:#e6db74">&amp;#34;&amp;#34;&lt;/span> org-hugo-properties-skel)
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> (&lt;span style="color:#e6db74">&amp;#34;btw&amp;#34;&lt;/span> &lt;span style="color:#e6db74">&amp;#34;by the way&amp;#34;&lt;/span> &lt;span style="color:#66d9ef">nil&lt;/span>)
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> ))
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>So now I have figured this out I am ready to go!&lt;/p>
&lt;p>After a little while of using this method I was starting to struggle with updating the abbrev_defs and seeing its effects immediately in emacs. When I update to a new abbrev it means that I pretty much want to use it now!&lt;/p>
&lt;p>I couldn&amp;rsquo;t really figure out an easy method of instantly making the newly added abbrev command available (except restarting emacs of course). There will be a way but it wasn&amp;rsquo;t quickly apparent to me and I suspect it was related to the fact that I am not adding to the abbrev_defs using an emacs function.&lt;/p>
&lt;p>Before my investigations consumed too much time however I thought I would try something!.&lt;/p>
&lt;p>I am not a great fan of configuration files cluttering up my system and certainly with emacs configuration I really am aiming with my vanilla-ish emacs setup to keep everything in one init file and as concise as possible. So why can&amp;rsquo;t I move the &lt;strong>define-abbrev-table&lt;/strong> straight into my &lt;strong>.emacs&lt;/strong> file? and just reevaluate an updated abbrev table?&lt;/p>
&lt;p>Well the answer is that I can!&lt;/p>
&lt;p>Problem solved. So if I feel the need to add more skeletons they can now be defined next to the abbreviation table and I can update immediately with a reevaluation in my emacs init file.&lt;/p>
&lt;p>Next on my expanding text journey I think might take a look at &lt;strong>dabbrev&lt;/strong> and &lt;strong>hippie-expand&lt;/strong>&lt;/p></description></item><item><title>Revisiting Window Cut / Copy Files with DWIM</title><link>https://www.emacs.dyerdwelling.family/emacs/20221116103757-emacs--some-dwim-target-stuff/</link><pubDate>Wed, 16 Nov 2022 00:00:00 +0000</pubDate><author>James Dyer</author><guid>https://www.emacs.dyerdwelling.family/emacs/20221116103757-emacs--some-dwim-target-stuff/</guid><description>&lt;p>I previously wrote about wanting the ability in emacs to copy and paste files from one window to another just like a linux GUI file manager and after a little hunting around and experimentation I settled on putting together some elisp:&lt;/p>
&lt;p>&lt;a href="https://www.emacs.dyerdwelling.family/emacs/emacs--cut-copy-files-using-dired-buffers__emacs_linux/">Cut / Copy Files from Window to Window using Dired Buffers &lt;/a>&lt;/p>
&lt;p>This worked pretty well for a while although there were a couple of side effects that I hadn&amp;rsquo;t bargained for!&lt;/p>
&lt;p>The first was the bypassing of the trash/wastebasket, of course this can be easily remedied by for example installing trash-put as part of my copy but that is starting to add more lines to the elisp and I didn&amp;rsquo;t want to complicate things too much.&lt;/p>
&lt;p>Also the single rename functionality in &lt;strong>dired&lt;/strong> now didn&amp;rsquo;t work as I essentially remapped the basic functionality of the &lt;strong>R&lt;/strong> key to set up a flag for a paste operation and I have come to realise that I use the rename often as I would do in a GUI file explorer.&lt;/p>
&lt;p>All I want to do is to copy/move files from one emacs window to another, is that too much to ask?, and no, no it isn&amp;rsquo;t, as there is a much simpler way to achieve this without any of the foibles listed above.&lt;/p>
&lt;p>It is:&lt;/p>
&lt;figure>&lt;img src="https://www.emacs.dyerdwelling.family/emacs/20221116103757-emacs--some-dwim-target-stuff.jpg" width="300px">
&lt;/figure>
&lt;p>&lt;code>Dired tries to guess a default target directory&lt;/code>&lt;/p>
&lt;p>Yay!&lt;/p>
&lt;p>So just open a couple of dired windows, mark and operate and the directory dired suggests is the second or destination window dired directory.&lt;/p>
&lt;p>Now you may think my previous efforts were wasted but I would disagree with this assumption, I have started to get to grips with elisp, managed to get some elisp working and continued reconsidering my general emacs workflow which as we know will save me time in the long run.&lt;/p>
&lt;p>Oh and another thing, the power of writing a blog and trying to articulate what I think I know means that I have read further into the &lt;strong>Help&lt;/strong> for &lt;strong>dired-dwim-target&lt;/strong>. This helps to clarify a few things as sometimes I get a little muddled with renaming files and which window/buffer dired is actually being used, here is a Help snippet:&lt;/p>
&lt;pre tabindex="0">&lt;code class="language-nil" data-lang="nil">You can customize it to prefer either the next window with a Dired
buffer, or the most recently used window with a Dired buffer, or to
use any other function. When the value is a function, it will be
called with no arguments and is expected to return a list of
directories which will be used as defaults (i.e. default target and
&amp;#34;future history&amp;#34;) (though, ‘dired-dwim-target-defaults’ might modify
it a bit).
The value t prefers the next windows on the same frame.
&lt;/code>&lt;/pre>&lt;p>This is very much something I shall be looking into at a future date, but for now I have something that works and reduces the size of my &lt;strong>.emacs&lt;/strong> file.&lt;/p></description></item><item><title>Dired Ordering by Size</title><link>https://www.emacs.dyerdwelling.family/emacs/emacs--dired-ordering-by-size__emacs_linux_dired/</link><pubDate>Wed, 09 Nov 2022 00:00:00 +0000</pubDate><author>James Dyer</author><guid>https://www.emacs.dyerdwelling.family/emacs/emacs--dired-ordering-by-size__emacs_linux_dired/</guid><description>&lt;p>By default &lt;code>dired&lt;/code> orders its files in alphanumeric order and when &lt;strong>s&lt;/strong> is selected it sorts by date according to :&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>(dired-sort-toggle-or-edit &lt;span style="color:#66d9ef">&amp;amp;optional&lt;/span> ARG)
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>But recently I wanted to list files according to their size, which of course is a very common thing to do especially when you are undertaking a nice spring clean up.&lt;/p>
&lt;p>The solution is to modify the &lt;code>dired-listing-switches&lt;/code> to add in an &lt;strong>S&lt;/strong> argument which can be found by passing in the universal argument to &lt;strong>s&lt;/strong>, hence : &lt;code>C-u s&lt;/code>&lt;/p>
&lt;p>This presents the user with something like: &lt;code>-al&lt;/code> at which point you can just tack &lt;code>S&lt;/code> on the end to show &lt;code>-alS&lt;/code>&lt;/p>
&lt;p>as the ls man page states the following:&lt;/p>
&lt;pre tabindex="0">&lt;code class="language-nil" data-lang="nil"> -S sort by file size, largest first
&lt;/code>&lt;/pre>&lt;p>and this is what we get:&lt;/p>
&lt;figure>&lt;img src="https://www.emacs.dyerdwelling.family/emacs/emacs--dired-ordering-by-size__emacs_linux_dired/2022-11-23_16-15.jpg" width="300px">
&lt;/figure>
&lt;p>a nice ordered listing, it is not something I do often and generally the standard dired sort toggle is all I need, but it is nice to know that this is available.&lt;/p></description></item><item><title>Tidying up Dired Further</title><link>https://www.emacs.dyerdwelling.family/emacs/emacs--tidying-up-dired_further__emacs_linux_dired/</link><pubDate>Thu, 03 Nov 2022 00:00:00 +0000</pubDate><author>James Dyer</author><guid>https://www.emacs.dyerdwelling.family/emacs/emacs--tidying-up-dired_further__emacs_linux_dired/</guid><description>&lt;p>Something is still bugging me with my dired tidy up and I think it is wanting to add the ability to remove dot files.&lt;/p>
&lt;figure class="emacs-img">&lt;img src="https://www.emacs.dyerdwelling.family/emacs/emacs--tidying-up-dired_further__emacs_linux_dired/montage.jpg">
&lt;/figure>
&lt;p>I am currently using the Dolphin file manager on linux and by default I tend not to show the dot files for a cleaner output, generally the only dot file of course I really care about are the emacs ones and I know where they are!&lt;/p>
&lt;p>With some hunting around I copy pasted the following and it works perfectly:&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>&lt;span style="color:#75715e">;; hide dotfiles and firefox.tmp&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#75715e">;; Toggle Hidden Files in Emacs dired with C-x M-o&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>(setq dired-omit-files
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> (&lt;span style="color:#a6e22e">concat&lt;/span> dired-omit-files &lt;span style="color:#e6db74">&amp;#34;\\|^\\..+$\\|firefox.tmp$&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">;; dired omit&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>(add-hook &lt;span style="color:#e6db74">&amp;#39;dired-mode-hook&lt;/span> (lambda () (dired-omit-mode &lt;span style="color:#ae81ff">1&lt;/span>)))
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>I just now need to muscle memorise the key combination to toggle enable them again, hence:&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>C-x M-o
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>(dired-omit-mode &lt;span style="color:#66d9ef">&amp;amp;optional&lt;/span> ARG)
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>Toggle omission of uninteresting files in Dired (Dired-Omit mode)&lt;span style="color:#f92672">.&lt;/span>
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div></description></item><item><title>Tidying up Dired</title><link>https://www.emacs.dyerdwelling.family/emacs/emacs--tidying-up-dired__emacs_linux_dired/</link><pubDate>Sun, 23 Oct 2022 00:00:00 +0100</pubDate><author>James Dyer</author><guid>https://www.emacs.dyerdwelling.family/emacs/emacs--tidying-up-dired__emacs_linux_dired/</guid><description>&lt;p>Bit by bit I am getting to grips with &lt;code>dired&lt;/code> and using this for more operations on my files.&lt;/p>
&lt;p>The next step is to reduce the listing width so that a listing fits better in a smaller window.&lt;/p>
&lt;p>Here is the format of my current listing:&lt;/p>
&lt;figure>&lt;img src="https://www.emacs.dyerdwelling.family/emacs/emacs--tidying-up-dired__emacs_linux_dired/2022-11-23_15-32.jpg" width="300px">
&lt;/figure>
&lt;p>By default &lt;code>dired&lt;/code> uses &lt;code>-al&lt;/code>, which gives a standard long listing. Unfortunately for my work within emacs this is actually now too long, and also not just in emacs but in my terminal too.&lt;/p>
&lt;p>After much hunting around the man page including potentially formatting the output and selecting the listing fields manually, I came to the conclusion that it is mainly the &lt;em>group&lt;/em> and &lt;em>user&lt;/em> I want to remove for the moment, and fortunately there are a couple of arguments that can do this for me:&lt;/p>
&lt;pre tabindex="0">&lt;code class="language-nil" data-lang="nil"> -g like -l, but do not list owner
-G, --no-group
in a long listing, don&amp;#39;t print group names
&lt;/code>&lt;/pre>&lt;p>and while scrolling through the man page I also thought that making the sizes human readable would also be useful, so I have come up with the following in my &lt;code>.emacs&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>(setq dired-listing-switches &lt;span style="color:#e6db74">&amp;#34;-lGgha&amp;#34;&lt;/span>)
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>to give me the following dired output:&lt;/p>
&lt;figure>&lt;img src="https://www.emacs.dyerdwelling.family/emacs/emacs--tidying-up-dired__emacs_linux_dired/2022-11-23_15-24.jpg" width="300px">
&lt;/figure>
&lt;p>This is much better, but can I now go further?&lt;/p>
&lt;p>Well this is emacs, and you betcha!!!!&lt;/p>
&lt;p>I know of the command &lt;code>(&lt;/code> which when in dired mode hides the details and shows just the files. Wouldn&amp;rsquo;t it be great to have this enabled by default?&lt;/p>
&lt;p>A quick &lt;code>describe-key&lt;/code> gives me the following function:&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>(dired-hide-details-mode &lt;span style="color:#66d9ef">&amp;amp;optional&lt;/span> ARG)
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>But it is not clear how I can set this by default, there is nothing obvious in &lt;code>customize-group&lt;/code> for dired, so now to the interwebs&amp;hellip;&lt;/p>
&lt;p>It looks as though I will need to add to the &lt;code>dired-mode-hook&lt;/code> and set the details to be hidden when dired mode is activated as follows:&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>&lt;span style="color:#75715e">;; dired hide long listing by default&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>(defun my-dired-mode-setup ()
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#e6db74">&amp;#34;show less information in dired buffers&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> (dired-hide-details-mode &lt;span style="color:#ae81ff">1&lt;/span>))
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>(add-hook &lt;span style="color:#e6db74">&amp;#39;dired-mode-hook&lt;/span> &lt;span style="color:#e6db74">&amp;#39;my-dired-mode-setup&lt;/span>)
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>Which now gives me:&lt;/p>
&lt;figure>&lt;img src="https://www.emacs.dyerdwelling.family/emacs/emacs--tidying-up-dired__emacs_linux_dired/2022-11-23_15-23.jpg" width="300px">
&lt;/figure>
&lt;p>and now if I want show the details I just select &lt;code>(&lt;/code> so effectively generally showing details rather than hiding them.&lt;/p>
&lt;p>This makes more sense for my workflow in emacs, especially now my buffers and windows are getting a little more numerous and I am still really focussing on trying to use a single emacs frame on a laptop.&lt;/p></description></item><item><title>Centering the Cursor After a Scroll</title><link>https://www.emacs.dyerdwelling.family/emacs/emacs--centering-cursor-after-scroll__emacs_linux/</link><pubDate>Tue, 11 Oct 2022 00:00:00 +0100</pubDate><author>James Dyer</author><guid>https://www.emacs.dyerdwelling.family/emacs/emacs--centering-cursor-after-scroll__emacs_linux/</guid><description>&lt;p>Something has been nagging at me for a while now, I seem to spend a lot of time moving the cursor to the next or previous line within a window, spamming &lt;code>C-n&lt;/code> and &lt;code>C-p&lt;/code> to get where I want to within a file.&lt;/p>
&lt;figure class="emacs-img">&lt;img src="https://www.emacs.dyerdwelling.family/emacs/emacs--centering-cursor-after-scroll__emacs_linux.jpg">
&lt;/figure>
&lt;p>Why is this?&lt;/p>
&lt;p>Well I am scrolling quite often using &lt;code>C-v&lt;/code> and &lt;code>M-v&lt;/code> and this has a side effect on the cursor by pushing it to the top or bottom of the window. After I scroll eyeball a file to the desired location I would like my cursor to be vaguely in the center of the window as this is naturally where my eye would have been to identify the file location.&lt;/p>
&lt;p>Although I have upgraded the default emacs scrolling to smooth scrolling by using good-scroll I am still having this issue.&lt;/p>
&lt;p>One solution is to use &lt;code>M-r&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>(move-to-window-line-top-bottom &lt;span style="color:#66d9ef">&amp;amp;optional&lt;/span> ARG)
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>Position &lt;span style="color:#a6e22e">point&lt;/span> relative to window.
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>which moves the cursor first to the middle then the top and then to the bottom of the window. So after a scroll I can just muscle memory &lt;code>M-r&lt;/code>? Well I am scrolling quite a lot and the key combination is a little awkward to get to and I want to limit my key presses as much as possible.&lt;/p>
&lt;p>Another possible solution is to use &lt;strong>recenter-top-bottom&lt;/strong> but this will just add an extra scroll and the cursor position will be in the wrong place anyway.&lt;/p>
&lt;p>There may also be packages out there that will help but I am choosing to ignore this option for the moment due to wanting to keep vanilla emacs as much as possible (yes I know I keep banging on about this!)&lt;/p>
&lt;p>Lets see what I can do with some elisp.&lt;/p>
&lt;p>Before I discovered the wonder of emacs smooth scrolling using good-scroll I was using some elisp to have greater control over the number of lines the basic &lt;strong>scroll-&lt;/strong>-command* would scroll, so I had something like this:&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 window-some-height () (&lt;span style="color:#a6e22e">max&lt;/span> &lt;span style="color:#ae81ff">1&lt;/span> &lt;span style="color:#a6e22e">/&lt;/span> (&lt;span style="color:#a6e22e">1-&lt;/span> (window-height
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> (&lt;span style="color:#a6e22e">selected-window&lt;/span>))) &lt;span style="color:#ae81ff">4&lt;/span>)))
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>(defun scroll-up-some ()
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> (interactive)
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> (scroll-up-command (window-some-height))
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> )
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>(defun scroll-down-some ()
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> (interactive)
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> (scroll-down-command (window-some-height))
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> )
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>with the scroll-some functions bound to the standard scroll bindings.&lt;/p>
&lt;p>Now good-scroll is enabled it morphed into the following:&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 scroll-up-some ()
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> (interactive)
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> (good-scroll-up)
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> )
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>(defun scroll-down-some ()
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> (interactive)
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> (good-scroll-down)
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> )
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>as the scroll amount is defined by good-scroll.&lt;/p>
&lt;p>My idea here is to modify the scroll functions to add some post scroll cursor centering commands. Maybe I could use (move-to-window-line-top-bottom)!. Well I tried it, but it just didn&amp;rsquo;t seem to make any difference, I tried some delays, e.t.c. I suspect that the problem here is that good-scroll has a certain method to achieve its smooth scrolling which is incompatible with the function calls I am initially trying.&lt;/p>
&lt;p>Well I decided to keep throwing ideas at the scroll functions to see if I could somehow accidentally force a roughly consistent cursor centering after a scroll and see if something sticks (there may be a better term for this!)&lt;/p>
&lt;p>Scroll lock didn&amp;rsquo;t work!, margins didn&amp;rsquo;t work!&lt;/p>
&lt;p>Eventually I managed to find a function call that seemed to make a difference to the cursor position after a scroll but didn&amp;rsquo;t seem to position the cursor where I thought it should do given the documentation. The function is:&lt;/p>
&lt;pre tabindex="0">&lt;code class="language-nil" data-lang="nil"> (move-to-window-Line ARG)
Position point relative to window.
ARG nil means position point at center of window.
Else, ARG specifies vertical position within the window;
zero means top of window, negative means relative to bottom
of window, -1 meaning the last fully visible display line
of the window.
Value is the screen line of the window point moved to, counting
from the top of the window.
&lt;/code>&lt;/pre>&lt;p>Passing in a &amp;rsquo;nil parameter was close but the cursor was too skewed to either end (but at least had a consistent effect on the position of the cursor), however it had little or no effect for half height windows after a horizontal split.&lt;/p>
&lt;p>However, passing in different arguments to the &lt;strong>move-to-window-line&lt;/strong> function have me differing post scroll cursor locations giving me a licence to that well known tried and tested foolproof method of trial and error!&lt;/p>
&lt;p>Of course for a full height window I now had the perfect setup but what about those pesky half height and maybe smaller windows?. There is a valid argument that might say that smaller windows are generally so small that even if the cursor gets pushed to the top or bottom then it won&amp;rsquo;t take too much effort to reposition the cursor. I would say to an extent that is true but for a half height window I would still want to have a centering cursor option after a scroll.&lt;/p>
&lt;p>So again elisp to the rescue!, I modified my scroll functions as thus:&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 scroll-up-some ()
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> (interactive)
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> (good-scroll-up)
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> (if (&lt;span style="color:#a6e22e">&amp;lt;&lt;/span> &lt;span style="color:#ae81ff">50&lt;/span> (&lt;span style="color:#a6e22e">window-body-height&lt;/span>))
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> (move-to-window-Line &lt;span style="color:#ae81ff">-14&lt;/span>)
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> (move-to-window-Line &lt;span style="color:#ae81ff">-3&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>&lt;/span>&lt;span style="display:flex;">&lt;span>(defun scroll-down-some ()
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> (interactive)
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> (good-scroll-down)
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> (if (&amp;lt;50 (&lt;span style="color:#a6e22e">window-body-height&lt;/span>))
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> (move-to-window-Line &lt;span style="color:#ae81ff">14&lt;/span>)
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> (move-to-window-Line &lt;span style="color:#ae81ff">8&lt;/span>)
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#a6e22e">&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>The value of 50 was a window line height less than a full window height with a little taken off, but not too much to make it smaller than half height!&lt;/p>
&lt;p>Once I have the move-to-window-line values tweaked accordingly for my setup then in theory I have a decent functioning cursor centering solution. Of course there are some foibles, like what happens if I go full-screen with emacs, e.t.c but this seems to work well for me for the moment and I am now moving the cursor around less often within a buffer.&lt;/p>
&lt;p>As with all these things and especially emacs I&amp;rsquo;m sure a better simpler, probably built-in solution will present itself, but that may be for another day&amp;hellip;&lt;/p></description></item><item><title>Do You Remember?</title><link>https://www.emacs.dyerdwelling.family/emacs/emacs--do-you-remember__emacs_linux/</link><pubDate>Mon, 10 Oct 2022 00:00:00 +0100</pubDate><author>James Dyer</author><guid>https://www.emacs.dyerdwelling.family/emacs/emacs--do-you-remember__emacs_linux/</guid><description>&lt;p>I am currently keeping all my textual notes in a separate org file called appropriately, &lt;strong>notes.org&lt;/strong>. This is pretty much just a random rambling set of text containing bits and bobs that I don&amp;rsquo;t want to forget.&lt;/p>
&lt;figure class="emacs-img">&lt;img src="https://www.emacs.dyerdwelling.family/emacs/emacs--do-you-remember__emacs_linux.jpg">
&lt;/figure>
&lt;p>If I am mobile and want to jot something down I use an android app called &lt;strong>Markor&lt;/strong>, just open up the notes.org as a text file and add something in. &lt;strong>Syncthing&lt;/strong> means that this file is present on my laptop when I open it up in emacs.&lt;/p>
&lt;p>In emacs, I can open the file, edit it and save it, but it all seems a little too clunky to me. If I want to just register a thought or idea isn&amp;rsquo;t there a better way to accomplish this in emacs?, the answer of course as we all know is&amp;hellip; YES!&lt;/p>
&lt;p>I will add a note right now that am vaguely aware of org-capture but I get a sense that this might be too much in terms of functionality. I really just want a quick method to record some keystrokes in whatever form I see fit.&lt;/p>
&lt;p>I will again double down on my main requirement for introducing a new workflow in emacs and that is to use vanilla emacs where I can and that would involve either built-in functionality or maybe a little elisp.&lt;/p>
&lt;p>Lets list some packages and search through the &lt;strong>built-in&lt;/strong> emacs packages&amp;hellip; The following seems to present itself! :&lt;/p>
&lt;pre tabindex="0">&lt;code class="language-nil" data-lang="nil"> remember 2.0 built-in a mode for quickly jotting down things to remember
&lt;/code>&lt;/pre>&lt;p>This seems to be nice and simple and the Help file is quite informative. It is based around the concept of quickly recording something to remember and then organise at a later date. As it says:&lt;/p>
&lt;blockquote>
&lt;p>the initial &amp;ldquo;just remember this&amp;rdquo; impulse should be as close to simply throwing the data at Emacs as possible.&lt;/p>
&lt;/blockquote>
&lt;p>and as we know, emacs is very good at handling data.&lt;/p>
&lt;p>Out of the box, running &lt;code>M-x remember&lt;/code> brings up a blank &lt;strong>Remember&lt;/strong> window buffer encouraging you to &lt;code>C-c C-c to remember the data&lt;/code>, You can enter anything you want and with &lt;code>C-c C-c&lt;/code> the window disappears with your input appended to a text file. Perfect!&lt;/p>
&lt;p>By default a &lt;strong>notes&lt;/strong> file is stored in .emacs.d/ and I think I might just want to change this to point to my own &lt;strong>notes.org&lt;/strong>. By default the following is a typical entry appended to the bottom of a notes file:&lt;/p>
&lt;pre tabindex="0">&lt;code class="language-nil" data-lang="nil"> **Mon Oct 10 15:25:29 2022 (test)
test
&lt;/code>&lt;/pre>&lt;p>This initial form can be tweaked to conform to whatever notes format text file you have, in my case I added the following to my .emacs:&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>&lt;span style="color:#f92672">&amp;#39;&lt;/span>(remember-annotation-functions &lt;span style="color:#66d9ef">nil&lt;/span>)
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#f92672">&amp;#39;&lt;/span>(remember-data-file &lt;span style="color:#e6db74">&amp;#34;/home/usr/content/notes.org&amp;#34;&lt;/span>)
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#f92672">&amp;#39;&lt;/span>(remember-leader-text &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:#f92672">&amp;#39;&lt;/span>(remember-notes-initial-major-mode &lt;span style="color:#e6db74">&amp;#39;org-mode&lt;/span>)
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#f92672">&amp;#39;&lt;/span>(remember-time-format &lt;span style="color:#e6db74">&amp;#34;&amp;#34;&lt;/span>)
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>(global-set-key (kbd &lt;span style="color:#e6db74">&amp;#34;C-c n&amp;#34;&lt;/span>) &lt;span style="color:#e6db74">&amp;#39;remember&lt;/span>)
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>Now if a thought occurs and I don&amp;rsquo;t trust my aging brain to remember, a quick &lt;code>C-c n, text input, C-c C-c&lt;/code> flick of the fingers will record that information. The window is transitory and doesn&amp;rsquo;t disrupt my emacs session and the text org file will be updated and then synced to my phone for a later potential recall.&lt;/p>
&lt;p>There are other aspects of remember that I could explore, like what is a diary file?, but I don&amp;rsquo;t care too much about that, this works perfectly and is another example of the simple out-of-the-box amazingness of emacs!&lt;/p></description></item><item><title>Dired folder size</title><link>https://www.emacs.dyerdwelling.family/emacs/emacs--dired-folder-size__emacs_linux/</link><pubDate>Sat, 08 Oct 2022 00:00:00 +0100</pubDate><author>James Dyer</author><guid>https://www.emacs.dyerdwelling.family/emacs/emacs--dired-folder-size__emacs_linux/</guid><description>&lt;p>My &lt;code>dired&lt;/code> replacement of Dolphin / linux terminal continues. The next thing is something useful to me but uncommon and something that dired didn&amp;rsquo;t seem to be able to do out of the box.&lt;/p>
&lt;figure class="emacs-img">&lt;img src="https://www.emacs.dyerdwelling.family/emacs/emacs--dired-folder-size__emacs_linux.jpg">
&lt;/figure>
&lt;p>That is to be able to recursively display the size of a folder / files.&lt;/p>
&lt;p>Every now and again I like to keep my files and data under control especially as I am using syncthing to backup my core data to my phone.&lt;/p>
&lt;p>It is very easy to let a large download or maybe an emacs exported large file sneak through. My tools of choice have generally been the following and in time order:&lt;/p>
&lt;ul>
&lt;li>Terminal using &lt;code>du -h --max-depth 1 &amp;lt;folder&amp;gt;&lt;/code>&lt;/li>
&lt;li>Gnome Disk Usage Analyser (boabab)&lt;/li>
&lt;li>Filelight - now I have moved to KDE&lt;/li>
&lt;/ul>
&lt;p>So how do I achieve this?&lt;/p>
&lt;p>I could perhaps try something from emacs packages but as you already know my first port of call is to try and preserve vanilla emacs where possible which might mean in this case finding / or writing some elisp.&lt;/p>
&lt;p>The &lt;code>www.emacswiki.org&lt;/code> comes to the rescue once again! with yet another elisp snippet as thus:&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 dired-get-size ()
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> (interactive)
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> (let ((files (dired-get-marked-files)))
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> (with-temp-buffer
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> (&lt;span style="color:#a6e22e">apply&lt;/span> &lt;span style="color:#e6db74">&amp;#39;call-process&lt;/span> &lt;span style="color:#e6db74">&amp;#34;/usr/bin/du&amp;#34;&lt;/span> &lt;span style="color:#66d9ef">nil&lt;/span> &lt;span style="color:#66d9ef">t&lt;/span> &lt;span style="color:#66d9ef">nil&lt;/span> &lt;span style="color:#e6db74">&amp;#34;-sch&amp;#34;&lt;/span> files)
&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;Size of all marked files: %s&amp;#34;&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> (&lt;span style="color:#a6e22e">re-search-backward&lt;/span> &lt;span style="color:#e6db74">&amp;#34;\\(^[0-9.,]+[A-Za-z]+\\).*total$&amp;#34;&lt;/span>)
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> (match-string &lt;span style="color:#ae81ff">1&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">define-key&lt;/span> dired-mode-map (kbd &lt;span style="color:#e6db74">&amp;#34;?&amp;#34;&lt;/span>) &lt;span style="color:#e6db74">&amp;#39;dired-get-size&lt;/span>)
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>I don&amp;rsquo;t need anything too fancy here, for example a full dired integration like Solid Explorer on android but just showing the total size in the minibuffer is fine and why not leverage the existing operating system commands rather than completely depending on elisp!&lt;/p></description></item><item><title>Dired going Up Directories</title><link>https://www.emacs.dyerdwelling.family/emacs/emacs--dired-going-up-directories__emacs_linux/</link><pubDate>Wed, 28 Sep 2022 00:00:00 +0100</pubDate><author>James Dyer</author><guid>https://www.emacs.dyerdwelling.family/emacs/emacs--dired-going-up-directories__emacs_linux/</guid><description>&lt;p>Now &lt;code>dired&lt;/code> is becoming more ingrained into my muscle memory, navigating efficiently through the folder structure is becoming more prominent in my mind and it still doesn&amp;rsquo;t feel natural enough.&lt;/p>
&lt;figure class="emacs-img">&lt;img src="https://www.emacs.dyerdwelling.family/emacs/2022-09-28_20-35.jpg">
&lt;/figure>
&lt;p>I now don&amp;rsquo;t even think about using &lt;code>C-x d&lt;/code> and the &lt;em>Enter&lt;/em> key is fine for either opening a file or traversing into a directory. But what about moving up a directory!, also a very common action.&lt;/p>
&lt;p>The default defined key is &lt;code>^&lt;/code> and actually isn&amp;rsquo;t too bad and almost feels quite natural, &lt;em>almost&lt;/em>&amp;hellip;&lt;/p>
&lt;p>Currently to traverse windows and buffers I am using the &lt;code>M&lt;/code> key with comfortable key navigation hand positions as I am using these all the time. I feel like traversing up a directory should have the same feeling, and now I think about it, getting a feel for a hand position and hence an instinctual interaction with the keyboard is almost what emacs is all about (apart from the idea of extension by macros). Anyways, I digress&amp;hellip;&lt;/p>
&lt;p>What I would like to achieve is a quick command, no more than I am currently using for my window/buffer navigation, so a quick &lt;code>M&lt;/code> and another key, one that makes sense and is sensible. So that is why I think I will have to discount the default &lt;code>^&lt;/code> as it involves the shift key and actually trying to reach the tilda/6 key doesn&amp;rsquo;t feel that comfortable.&lt;/p>
&lt;p>Hence I present the following:&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>(&lt;span style="color:#a6e22e">define-key&lt;/span> dired-mode-map (kbd &lt;span style="color:#e6db74">&amp;#34;M-u&amp;#34;&lt;/span>) &lt;span style="color:#e6db74">&amp;#39;dired-up-directory&lt;/span>)
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>The hand position is very comfortable, it fits in with the rest of my window navigation keys and &lt;code>u&lt;/code> could very well mean &lt;strong>up!&lt;/strong>&lt;/p>
&lt;p>But there is something left, something that doesn&amp;rsquo;t feel right, or natural&amp;hellip; and that is switching to &lt;code>dired&lt;/code> from a file. Now I have this new defined key to traverse up a directory I have a general feeling that I should be using the same defined key to show &lt;code>dired&lt;/code> from when I am visiting a file in a buffer. It is very odd&amp;hellip; I can&amp;rsquo;t explain it, it just feels natural.&lt;/p>
&lt;p>If it feels right then I am just going to go ahead and do it, I think the best method is probably to define a macro as thus:&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>(global-set-key (kbd &lt;span style="color:#e6db74">&amp;#34;M-u&amp;#34;&lt;/span>) &lt;span style="color:#e6db74">&amp;#39;file-up-dir&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">fset&lt;/span> &lt;span style="color:#e6db74">&amp;#39;file-up-dir&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> (kmacro-lambda-form [&lt;span style="color:#e6db74">?\C&lt;/span>-x &lt;span style="color:#e6db74">?d&lt;/span> return ] &lt;span style="color:#ae81ff">0&lt;/span> &lt;span style="color:#e6db74">&amp;#34;%d&amp;#34;&lt;/span>))
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>The macro is just calling &lt;code>dired&lt;/code> and then &lt;strong>return&lt;/strong> to action the current directory. That is all I really want to do and for some reason in my own mind I seem to have extrapolated a file to be floating above / or below a directory so it almost makes sense to &amp;ldquo;go up&amp;rdquo; a directory to show the directory and hence &lt;code>dired&lt;/code>&lt;/p></description></item><item><title>Cut / Copy between Windows using Dired Buffers</title><link>https://www.emacs.dyerdwelling.family/emacs/emacs--cut-copy-files-using-dired-buffers__emacs_linux/</link><pubDate>Mon, 26 Sep 2022 00:00:00 +0100</pubDate><author>James Dyer</author><guid>https://www.emacs.dyerdwelling.family/emacs/emacs--cut-copy-files-using-dired-buffers__emacs_linux/</guid><description>&lt;p>The next step in my emacs journey is to move files around a little more easily, in fact more like a regular file explorer using the concept of file / folder selection copy and paste. That concept seems a little more natural to me than &lt;code>dired&lt;/code> file marking, renaming / copying and then entering the path of the destination address.&lt;/p>
&lt;figure class="emacs-img">&lt;img src="https://www.emacs.dyerdwelling.family/emacs/2022-09-26_13-39.jpg">
&lt;/figure>
&lt;p>After a little research the following options present themselves:&lt;/p>
&lt;ol>
&lt;li>Vanilla Dired
&lt;ul>
&lt;li>copy target directory to kill ring using &lt;code>0 w&lt;/code>&lt;/li>
&lt;li>mark source files&lt;/li>
&lt;li>copy or rename files and at the directory destination prompt paste in from
the kill ring&lt;/li>
&lt;/ul>
&lt;/li>
&lt;li>package Dired+&lt;/li>
&lt;li>Own lisp implementation&lt;/li>
&lt;li>package Ranger like&lt;/li>
&lt;li>DWIM&lt;/li>
&lt;/ol>
&lt;p>One main criteria I have for this functionality is that if possible I want to avoid installing any additional packages as I always initially try and look for a way using vanilla emacs.&lt;/p>
&lt;p>I have emacs on windows, at work and of course on Manjaro, all slightly different versions and I find relying on third party packages can cause a few problems especially on my work emacs where I have installed potentially incompatible versions of MELPA and emacs. I always like to keep a common as possible initialisation file if possible across all my emacs.&lt;/p>
&lt;p>So the choice is between options 1,3 or&lt;/p>
&lt;ol>
&lt;li>&lt;/li>
&lt;/ol>
&lt;p>Trying number 1 worked well enough but still felt a little clunky and I really wanted to avoid copying and pasting directory names. The number 5 &lt;strong>do what I mean&lt;/strong> concept seems decent enough but seems to mainly rely on opening a window side by side which restricts me as I am a serial window chopper!. Also I have in the back of my mind to explore the whole DWIM concept in the future, especially when related to running commands on selected files.&lt;/p>
&lt;p>Therefore the winner is number 3. &amp;ldquo;my own&amp;rdquo; lisp implementation. Of course the quotes mean that I am going to borrow and then modify an answer I found on the internet!&lt;/p>
&lt;p>The original lisp is as follows&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>(defvar your-dired-copy-list &lt;span style="color:#66d9ef">nil&lt;/span>)
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>(defun your-dired-copy ()
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> (interactive)
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> (setq your-dired-copy-list (dired-get-marked-files)))
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>(defun your-dired-paste ()
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> (interactive)
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> (when your-dired-copy-list
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> (shell-command
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> (&lt;span style="color:#a6e22e">mapconcat&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#a6e22e">#&amp;#39;&lt;/span>shell-quote-argument
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#f92672">`&lt;/span>(&lt;span style="color:#e6db74">&amp;#34;cp&amp;#34;&lt;/span> &lt;span style="color:#e6db74">&amp;#34;-r&amp;#34;&lt;/span> &lt;span style="color:#f92672">,@&lt;/span>your-dired-copy-list &lt;span style="color:#f92672">,&lt;/span>default-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> (setq your-dired-copy-list &lt;span style="color:#66d9ef">nil&lt;/span>)))
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>and then to &lt;code>define-key&lt;/code> add to the &lt;code>dired-mode-map&lt;/code>&lt;/p>
&lt;p>I feel that my emacs journey is creeping ever onwards and my eyes and heart are opening up to creating my own lisp functions that do things that I want emacs to do. This is a bit of an opportunity to poke a gentle toe into the clear crystal waters of lisp programming.&lt;/p>
&lt;p>I have already been exposed to lisp at university although at almost 30 years ago now. It maybe that it will be more of the 25 years of software engineering experience that will help me now!&lt;/p>
&lt;p>I think I understand the initial implementation, and I like the concept of leveraging a shell command and hence the functionality of the underlying operating system. But I also wish to move files and directories and not just copy them, so I need some form of flag on the &lt;strong>dired&lt;/strong> marked files action and inform the paste routine which command I wish to perform. So I created / modified my first lisp code as thus:&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>(defvar my-dired-copy-list &lt;span style="color:#66d9ef">nil&lt;/span>)
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>(defun my-dired-copy ()
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> (interactive)
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> (setq my-move-flag &lt;span style="color:#66d9ef">nil&lt;/span>)
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> (setq my-dired-copy-list (dired-get-marked-files)))
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>(defun my-dired-move ()
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> (interactive)
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> (setq my-move-flag &lt;span style="color:#66d9ef">t&lt;/span>)
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> (setq my-dired-copy-list (dired-get-marked-files)))
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>(defun my-dired-paste ()
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> (interactive)
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> (when my-dired-copy-list
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> (if my-move-flag
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> (shell-command
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> (&lt;span style="color:#a6e22e">mapconcat&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#a6e22e">#&amp;#39;&lt;/span>shell-quote-argument
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#f92672">`&lt;/span>(&lt;span style="color:#e6db74">&amp;#34;mv&amp;#34;&lt;/span> &lt;span style="color:#e6db74">&amp;#34;-f&amp;#34;&lt;/span> &lt;span style="color:#f92672">,@&lt;/span>my-dired-copy-list &lt;span style="color:#f92672">,&lt;/span>default-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>&lt;/span>&lt;span style="display:flex;">&lt;span> (shell-command
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> (&lt;span style="color:#a6e22e">mapconcat&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#a6e22e">#&amp;#39;&lt;/span>shell-quote-argument
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#f92672">`&lt;/span>(&lt;span style="color:#e6db74">&amp;#34;cp&amp;#34;&lt;/span> &lt;span style="color:#e6db74">&amp;#34;-r&amp;#34;&lt;/span> &lt;span style="color:#f92672">,@&lt;/span>my-dired-copy-list &lt;span style="color:#f92672">,&lt;/span>default-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>&lt;/span>&lt;span style="display:flex;">&lt;span> (setq my-dired-copy-list &lt;span style="color:#66d9ef">nil&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>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>and with the following key defines:&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>(&lt;span style="color:#a6e22e">define-key&lt;/span> dired-mode-map &lt;span style="color:#e6db74">&amp;#34;C&amp;#34;&lt;/span> &lt;span style="color:#e6db74">&amp;#39;my-dired-copy&lt;/span>)
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>(&lt;span style="color:#a6e22e">define-key&lt;/span> dired-mode-map &lt;span style="color:#e6db74">&amp;#34;R&amp;#34;&lt;/span> &lt;span style="color:#e6db74">&amp;#39;my-dired-move&lt;/span>)
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>(&lt;span style="color:#a6e22e">define-key&lt;/span> dired-mode-map &lt;span style="color:#e6db74">&amp;#34;Y&amp;#34;&lt;/span> &lt;span style="color:#e6db74">&amp;#39;my-dired-paste&lt;/span>)
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>So I just set a simple flag depending on if the file is to be moved or not and then IF&amp;rsquo;d my way to the relevant functionality. Using &lt;code>M&lt;/code> to move a file felt more natural but I kept the &lt;code>dired&lt;/code> renaming notation of &lt;code>R&lt;/code> for consistency and to avoid confusion with changing the marked files mode.&lt;/p>
&lt;p>This implementation has the side effect of removing the simple &lt;strong>dired&lt;/strong> single file rename functionality, but as I generally use &lt;code>wdired&lt;/code> anyway I don&amp;rsquo;t think this is too much of a problem.&lt;/p></description></item><item><title>Trimming Text With Macros</title><link>https://www.emacs.dyerdwelling.family/emacs/emacs--trimming-text-with-macros__emacs_linux/</link><pubDate>Sun, 25 Sep 2022 00:00:00 +0100</pubDate><author>James Dyer</author><guid>https://www.emacs.dyerdwelling.family/emacs/emacs--trimming-text-with-macros__emacs_linux/</guid><description>&lt;p>I am having a bet!, it is one of my few vices, and it shall be on the horses. I don&amp;rsquo;t often have a bet and in fact I generally only ever have a bet on a special occasion, like the grand national or a random parallel bet with my dad.&lt;/p>
&lt;p>Today is a parallel bet, yesterday my dad won over £650 and I have decided to leverage his expert equine knowledge to have a little flutter. But wait, I&amp;rsquo;m not going to do that, I am going to leverage my old javascript horse racing prediction program, the algorithm of which was based on something I wrote on my Amstrad CPC in the mid 80s.&lt;/p>
&lt;p>Now what has all this twaddle got to do with emacs I hear you say?, well my javascript program requires a very specific format of horse data and it is in fact a very simple one: &lt;code>number form horsename&lt;/code>&lt;/p>
&lt;p>My general aim in the past was to pretty much find a website displaying horse data for the chosen race and copy and paste into a web text input field which my javascript would parse and perform its magic.&lt;/p>
&lt;p>SportingLife looks like a good one (top of my startpage search). The data though of course is in the wrong order and there is a lot of it for each horse.&lt;/p>
&lt;p>My first thought with all these things is how can emacs help me out with this? Well a paste into the &lt;strong>scratch&lt;/strong> buffer gives me something like this:&lt;/p>
&lt;pre tabindex="0">&lt;code class="language-nil" data-lang="nil"> 1
(5)
Horse silk
Saratoga Goldb27
Age: 4| Weight: 10-2| J: Owen Lewis(5)| T: C Hills| OR: 85| CD
15/8
Has taken his form up a notch
Form: 556811|Timeform
2
(6)
Horse silk
Sword Beachb24
Age: 5| Weight: 9-13| J: George Bass| T: A King| OR: 82
28/1
Dual Windsor scorer in 2021
Form: 71165-0|Timeform
&lt;/code>&lt;/pre>&lt;p>I think the best way to approach this is to open a new window along side the scratch buffer and use this as my formatted output window, I will just create a random blank new file for now called &lt;em>tmp.txt&lt;/em>&lt;/p>
&lt;p>Then using a macro I can search around extracting each piece of relevant data for each horse and then just copy to the right hand pane.&lt;/p>
&lt;p>Once I have a procedure that works for a single horse then I can just end the macro and then &lt;code>C-x e&lt;/code> repeat the last keyboard macro and then spam &lt;code>e&lt;/code> until all the horses are parsed.&lt;/p>
&lt;figure>&lt;img src="https://www.emacs.dyerdwelling.family/emacs/emacs--trimming-text-with-macros__emacs_linux.jpg" width="300px">
&lt;/figure>
&lt;p>At which point I can copy the text from &lt;strong>tmp.txt&lt;/strong>, paste into the html text input and press &lt;strong>Find Winner&lt;/strong>, lets see if my old algorithm still holds up!&lt;/p></description></item><item><title>Finding Files With RipGrep</title><link>https://www.emacs.dyerdwelling.family/emacs/emacs--finding-files-with-ripgrep__emacs_linux/</link><pubDate>Wed, 21 Sep 2022 00:00:00 +0100</pubDate><author>James Dyer</author><guid>https://www.emacs.dyerdwelling.family/emacs/emacs--finding-files-with-ripgrep__emacs_linux/</guid><description>&lt;p>Now I am appreciating the power and simplicity of ripgrep and have it available on all my systems I was hunting around emacs to see the best way to leverage this tool for quick file searching.&lt;/p>
&lt;p>Working on a code base that is familiar to me is fine when locating files within emacs as of course I know where to go and when I am not I would typically open up a terminal and use &lt;code>find&lt;/code>&lt;/p>
&lt;p>As we know, &lt;code>find&lt;/code> can be a little picky and fiddly to use and it is not a straightforward process to define the files and folders I am not interested in when returning an efficient search.&lt;/p>
&lt;p>Now I have defined an &lt;strong>.ignore&lt;/strong> file to define which files and directories I am not interested in for a regular ripgrep I was wondering if I could also use this somehow to search for files within a project hierarchy and from within emacs.&lt;/p>
&lt;p>I have heard good things about projectile and in combination with a completion system it can bring up a searchable file list. This seems like a good solution but unfortunately I have never really found it fits in with my simple workflow, I have always failed to set it up properly and generally I don&amp;rsquo;t require the ability to switch between projects.&lt;/p>
&lt;p>After running &lt;code>list-packages&lt;/code> and literally searching for ripgrep, I came across a package called &lt;strong>find-file-rg&lt;/strong>, which leverages the &lt;code>rg --files&lt;/code> command to list all files in the directory defined but excluding from the ripgrep defined .ignore file.&lt;/p>
&lt;p>Fed into Ivy it means a list of potential files can be displayed and located using its completion system and all I need to define is the following:&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>(global-set-key [f4] &lt;span style="color:#e6db74">&amp;#39;find-file-rg&lt;/span>)
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>and hey presto!&lt;/p>
&lt;figure class="emacs-img">&lt;img src="https://www.emacs.dyerdwelling.family/emacs/emacs--finding-files-with-ripgrep__emacs_linux/2022-09-21_21-11.jpg">
&lt;/figure></description></item><item><title>Putting to Trash</title><link>https://www.emacs.dyerdwelling.family/emacs/emacs--putting-to-trash__emacs_linux/</link><pubDate>Thu, 15 Sep 2022 00:00:00 +0100</pubDate><author>James Dyer</author><guid>https://www.emacs.dyerdwelling.family/emacs/emacs--putting-to-trash__emacs_linux/</guid><description>&lt;p>I was recently reading a post about deleting files from within emacs and pushing them to the local Trash, this seems like a good idea especially now I am using dired more often.&lt;/p>
&lt;figure class="emacs-img">&lt;img src="https://www.emacs.dyerdwelling.family/emacs/emacs--putting-to-trash__emacs_linux.jpg">
&lt;/figure>
&lt;p>After using &lt;code>describe-function&lt;/code> and typing &lt;strong>&lt;em>trash&lt;/em>&lt;/strong> there was a single completion, namely &lt;code>move-file-to-trash&lt;/code> from this I figured out I should add the following my .emacs:&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 trash-directory &lt;span style="color:#e6db74">&amp;#34;~/.local/share/Trash/files&amp;#34;&lt;/span>)
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>(setq delete-by-moving-to-trash &lt;span style="color:#66d9ef">t&lt;/span>)
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>I am running this on OpenSuse so the location of the Trash folder may vary from system to system.&lt;/p>
&lt;p>For a more bespoke copying mechanism there is potentially the ability to define system-move-file-to-trash accepting &lt;strong>&lt;em>file&lt;/em>&lt;/strong> as an argument, but currently I don&amp;rsquo;t need anything to be that sophisticated.&lt;/p></description></item><item><title>Efficient Deletion and Insertion</title><link>https://www.emacs.dyerdwelling.family/emacs/emacs--efficient-deletion-and-insertion__emacs_linux/</link><pubDate>Sat, 10 Sep 2022 00:00:00 +0100</pubDate><author>James Dyer</author><guid>https://www.emacs.dyerdwelling.family/emacs/emacs--efficient-deletion-and-insertion__emacs_linux/</guid><description>&lt;p>As my emacs keybindings journey continues to evolve and to delete more efficiently with delete word it has lead to an interesting issue for me.&lt;/p>
&lt;figure class="emacs-img">&lt;img src="https://www.emacs.dyerdwelling.family/emacs/emacs--efficient-deletion__emacs_linux.jpg">
&lt;/figure>
&lt;p>Typically, especially in my coding life I will typically copy a string to the clipboard, then go in and delete the target area and insert from the clipboard/kill-ring. Now I have moved from deleting with the delete key to the the M-delete-word concept it seems that deleting words automatically puts this to the clipboard/kill-ring, so when I paste in my original copy it pastes back in the last killed words.&lt;/p>
&lt;p>Generally I only ever use kill-line when I want a kill to be copied to the kill-ring and I only ever use the word deletion to just delete in preparation for a yank.&lt;/p>
&lt;p>A bit of hunting around on the Interwebs lead to the definition of two new functions to be remapped to avoid the kill-ring.&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 delete-word-back (arg)
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#e6db74">&amp;#34;Delete characters backward until encountering the beginning of a word.
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#e6db74"> With argument ARG, do this that many times.&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> (interactive &lt;span style="color:#e6db74">&amp;#34;p&amp;#34;&lt;/span>)
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> (&lt;span style="color:#a6e22e">delete-region&lt;/span> (&lt;span style="color:#a6e22e">point&lt;/span>) (progn (backward-word arg) (&lt;span style="color:#a6e22e">point&lt;/span>))))
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>(defun delete-word-forward (arg)
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#e6db74">&amp;#34;Delete characters forward until encountering the end of a word.
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#e6db74"> With argument ARG, do this that many times.&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> (interactive &lt;span style="color:#e6db74">&amp;#34;p&amp;#34;&lt;/span>)
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> (&lt;span style="color:#a6e22e">delete-region&lt;/span> (&lt;span style="color:#a6e22e">point&lt;/span>) (progn (&lt;span style="color:#a6e22e">forward-word&lt;/span> arg) (&lt;span style="color:#a6e22e">point&lt;/span>))))
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div></description></item><item><title>Images to Blog Posts</title><link>https://www.emacs.dyerdwelling.family/emacs/emacs--images-to-blog-posts__emacs_linux/</link><pubDate>Sun, 28 Aug 2022 00:00:00 +0100</pubDate><author>James Dyer</author><guid>https://www.emacs.dyerdwelling.family/emacs/emacs--images-to-blog-posts__emacs_linux/</guid><description>&lt;p>I am just playing around with writing some sort of &amp;ldquo;techy&amp;rdquo; blog, trying to focus on a couple of my favourite things in the world, namely linux and emacs, so I thought I would do the best productive thing in the world and just start to type.&lt;/p>
&lt;p>I have set up this blog in a certain way using a static web site generator called Hugo, but more on that later down the road.&lt;/p>
&lt;p>For now I want to just focus on describing what I am tinkering around with at the moment regarding emacs, as yes, I am always tinkering, like a good &amp;lsquo;ol emacs user. Yes I am old, but more on that later down the road&amp;hellip;&lt;/p>
&lt;p>I am writing this post in org mode and am trying to figure out how to insert an image, of course the first point of call would be &lt;strong>org-download&lt;/strong>, I have tried this package a few times in the past but never quite got off the ground. I understand the concept and would love to be able to drag and drop an image straight into an org file.&lt;/p>
&lt;p>This of course can be accomplished using org-download but for publishing my blog it doesn&amp;rsquo;t quite cut the mustard. Hugo uses org files natively and rather than defining a &lt;strong>[[file:&lt;/strong> type org link it omits the file reference and forward slash starts a reference to the &lt;em>org-hugo-base-dir&lt;/em> which would be where my static image files reside.&lt;/p>
&lt;p>I had a bit of a hunt around and although I could define which directory the image would be dumped when draggging the file from the file explorer I would probably need to change this for each new post and even then I would need to remove the &lt;strong>file:&lt;/strong> from the link reference.&lt;/p>
&lt;p>So I found a simple method:&lt;/p>
&lt;ul>
&lt;li>In emacs dired the directory I want to drag the image&lt;/li>
&lt;li>Drag the image, the file is dropped nicely&lt;/li>
&lt;li>Use my favourite dired command at the moment &lt;strong>0 w&lt;/strong> to get the full path of the
file name&lt;/li>
&lt;li>yank the filename that is on the clipboard into the blog org, &amp;ldquo;M-d&amp;rdquo; the start of the path up to the Hugo base directory&lt;/li>
&lt;li>Wrap the link in double brackets&lt;/li>
&lt;/ul>
&lt;p>Although I said simple, the list of steps looks less simple but as part of the emacs workflow and my emacs muscle memory kicking in I think this provides at present a decent solution.&lt;/p>
&lt;p>Here is an image of me using the technique so there!:&lt;/p>
&lt;figure>&lt;img src="https://www.emacs.dyerdwelling.family/emacs/emacs--images-to-blog-posts-cover__emacs_linux/james.jpg" width="100%">
&lt;/figure>
&lt;p>It took me about 10 seconds!, not too shabby&lt;/p>
&lt;p>Next step, I might look to a good spellchecker, flycheck, aspell, hunspell and all that!&lt;/p></description></item><item><title>Profiling and Accidental Learning</title><link>https://www.emacs.dyerdwelling.family/emacs/emacs--profiling-and-accidental-learning__emacs_linux/</link><pubDate>Sat, 20 Aug 2022 00:00:00 +0100</pubDate><author>James Dyer</author><guid>https://www.emacs.dyerdwelling.family/emacs/emacs--profiling-and-accidental-learning__emacs_linux/</guid><description>&lt;p>My sacred emacs is taking 13 seconds to start up!&lt;/p>
&lt;figure class="emacs-img">&lt;img src="https://www.emacs.dyerdwelling.family/emacs/emacs--profiling-and-accidental-learning__emacs_linux.jpg">
&lt;/figure>
&lt;p>This is not good, I have read somewhere about a built in profiler, and indeed when I &lt;strong>ivy&lt;/strong> complete for a function &lt;strong>profiler&lt;/strong> I find a plethora of options. &lt;strong>profiler-start&lt;/strong> looks good, and with a little intersearching it all seems simple enough, except it looks as though it probably won&amp;rsquo;t solve my start up problems but will profile any commands I execute in emacs, so would solve for example an issue I had in the past with &lt;strong>deft&lt;/strong> starting up.&lt;/p>
&lt;p>I found a good option in &lt;strong>esup&lt;/strong>, added the following to my .emacs :&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 esup
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> :ensure &lt;span style="color:#66d9ef">t&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#75715e">;; To use MELPA Stable use &amp;#34;:pin melpa-stable&amp;#34;,&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> :pin melpa)
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>initially it didn&amp;rsquo;t work and gave me the following:&lt;/p>
&lt;pre tabindex="0">&lt;code class="language-nil" data-lang="nil"> error in process sentinel: Wrong type argument: (or eieio-object class), nil, obj
&lt;/code>&lt;/pre>&lt;p>but with a little research I managed to fix it with adding the following to my .emacs :&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>&lt;span style="color:#75715e">;; Work around a bug where esup tries to step into the byte-compiled&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#75715e">;; version of `cl-lib&amp;#39;, and fails horribly.&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>(setq esup-depth &lt;span style="color:#ae81ff">0&lt;/span>)
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>and on running &lt;strong>esup&lt;/strong> I get a nice report generated with the top offenders in my &lt;em>.emacs&lt;/em> &lt;strong>org-download&lt;/strong> was at the top:&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>&lt;span style="color:#f92672">.&lt;/span>emacs:36 0.264sec 22%
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>(use-package org-download)
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>I like the way it runs up a separate child emacs session to test out the timing on the init file and then generated a report.&lt;/p>
&lt;p>But overall the total time was &lt;em>1.166sec&lt;/em> there must be something else going on here. At which point I twigged and remembered that as I have &lt;strong>(desktop-save-mode 1)&lt;/strong> set it means that all my buffers are reloaded on a new emacs startup, once I cleaned all these out, which I do from time to time it only took a couple of second to run up!&lt;/p>
&lt;p>I think my lesson from this exercise is the amazing possibilities of learning through investigation and trying to fix issues and emacs is so configurable and so much fun to play around with I find myself learning more and more!&lt;/p></description></item><item><title>Download Local Emacs Packages</title><link>https://www.emacs.dyerdwelling.family/emacs/20220715142225-emacs--download-local-emacs-packages/</link><pubDate>Fri, 15 Jul 2022 00:00:00 +0100</pubDate><author>James Dyer</author><guid>https://www.emacs.dyerdwelling.family/emacs/20220715142225-emacs--download-local-emacs-packages/</guid><description>&lt;p>Steps to locally download emacs packages for offline installation.&lt;/p>
&lt;figure class="emacs-img">&lt;img src="https://www.emacs.dyerdwelling.family/emacs/20220715142225-emacs--Download-Local-Emacs-Packages.jpg">
&lt;/figure>
&lt;p>&amp;mdash; TOC&lt;/p>
&lt;div class="ox-hugo-toc toc local">
&lt;ul>
&lt;li>&lt;a href="https://www.emacs.dyerdwelling.family/emacs/20220715142225-emacs--download-local-emacs-packages/#local-elpa-melpa-org">Local ELPA MELPA ORG&lt;/a>&lt;/li>
&lt;li>&lt;a href="https://www.emacs.dyerdwelling.family/emacs/20220715142225-emacs--download-local-emacs-packages/#local-from-mirror">Local from Mirror&lt;/a>&lt;/li>
&lt;li>&lt;a href="https://www.emacs.dyerdwelling.family/emacs/20220715142225-emacs--download-local-emacs-packages/#extra">Extra&lt;/a>&lt;/li>
&lt;/ul>
&lt;/div>
&lt;!--endtoc-->
&lt;hr>
&lt;h2 id="local-elpa-melpa-org">Local ELPA MELPA ORG&lt;/h2>
&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-shell" data-lang="shell">&lt;span style="display:flex;">&lt;span>cd ~ mkdir -p emacs-pkgs/melpa mkdir -p emacs-pkgs/elpa
&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"># mirror the melpa emacs archive&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>echo
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>echo &lt;span style="color:#e6db74">&amp;#34;updating MELPA...&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>echo
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>rsync -avz --delete --progress rsync://melpa.org/packages/ ~/emacs-pkgs/melpa/.
&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"># elpa&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>echo
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>echo &lt;span style="color:#e6db74">&amp;#34;updating ELPA...&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>echo
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>rsync -avz --delete --progress elpa.gnu.org::elpa/. ~/emacs-pkgs/elpa
&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"># org (currently no rsync support)&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>echo
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>echo &lt;span style="color:#e6db74">&amp;#34;updating ORG...&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>echo
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>cd ~/emacs-pkgs
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>git clone https://git.savannah.gnu.org/git/emacs/org-mode.git
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>I then copy the emacs-pkgs directory to the offline target machine and change the default package manager archives to point to these packages.&lt;/p>
&lt;p>Modify &lt;strong>.emacs&lt;/strong> in the following manner commenting out the online package communication:&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>&lt;span style="color:#75715e">;; (setq package-archives &amp;#39;((&amp;#34;melpa&amp;#34; . &amp;#34;https://melpa.org/packages/&amp;#34;)&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#75715e">;; (&amp;#34;org&amp;#34; . &amp;#34;https://orgmode.org/elpa/&amp;#34;)&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#75715e">;; (&amp;#34;elpa&amp;#34; . &amp;#34;https://elpa.gnu.org/packages/&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>(setq package-archives &lt;span style="color:#f92672">&amp;#39;&lt;/span>((&lt;span style="color:#e6db74">&amp;#34;melpa&amp;#34;&lt;/span>&lt;span style="color:#f92672">.&lt;/span> &lt;span style="color:#e6db74">&amp;#34;~/emacs-pkgs/melpa&amp;#34;&lt;/span>)
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> (&lt;span style="color:#e6db74">&amp;#34;org&amp;#34;&lt;/span> &lt;span style="color:#f92672">.&lt;/span> &lt;span style="color:#e6db74">&amp;#34;~/emacs-pkgs/elpa&amp;#34;&lt;/span>)
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> (&lt;span style="color:#e6db74">&amp;#34;elpa&amp;#34;&lt;/span> &lt;span style="color:#f92672">.&lt;/span> &lt;span style="color:#e6db74">&amp;#34;~/emacs-pkgs/org-mode/lisp&amp;#34;&lt;/span>)))
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;h2 id="local-from-mirror">Local from Mirror&lt;/h2>
&lt;p>First, you need to clone this repository.&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-bash" data-lang="bash">&lt;span style="display:flex;">&lt;span>git clone --depth &lt;span style="color:#ae81ff">1&lt;/span> git@gitlab.com:d12frosted/elpa-mirror.git ~/.elpa-mirror
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>And then setup package-archives in your init.el file.&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 package-archives
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#f92672">`&lt;/span>((&lt;span style="color:#e6db74">&amp;#34;melpa&amp;#34;&lt;/span> &lt;span style="color:#f92672">.&lt;/span> &lt;span style="color:#f92672">,&lt;/span>(&lt;span style="color:#a6e22e">concat&lt;/span> user-home-directory &lt;span style="color:#e6db74">&amp;#34;.elpa-mirror/melpa/&amp;#34;&lt;/span>))
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> (&lt;span style="color:#e6db74">&amp;#34;org&amp;#34;&lt;/span> &lt;span style="color:#f92672">.&lt;/span> &lt;span style="color:#f92672">,&lt;/span>(&lt;span style="color:#a6e22e">concat&lt;/span> user-home-directory &lt;span style="color:#e6db74">&amp;#34;.elpa-mirror/org/&amp;#34;&lt;/span>))
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> (&lt;span style="color:#e6db74">&amp;#34;gnu&amp;#34;&lt;/span> &lt;span style="color:#f92672">.&lt;/span> &lt;span style="color:#f92672">,&lt;/span>(&lt;span style="color:#a6e22e">concat&lt;/span> user-home-directory &lt;span style="color:#e6db74">&amp;#34;.elpa-mirror/gnu/&amp;#34;&lt;/span>))))
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>&lt;a href="https://github.com/d12frosted/elpa-mirror">https://github.com/d12frosted/elpa-mirror&lt;/a>&lt;/p>
&lt;h2 id="extra">Extra&lt;/h2>
&lt;h3 id="ada-mode">ada-mode&lt;/h3>
&lt;p>ada-mode.el version 4.0 before AdaCore&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>(add-to-list &lt;span style="color:#e6db74">&amp;#39;load-path&lt;/span> &lt;span style="color:#e6db74">&amp;#34;~/emacs-pkgs/old/ada-mode-4.1&amp;#34;&lt;/span>)
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>(require &lt;span style="color:#e6db74">&amp;#39;ada-mode&lt;/span>)
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div></description></item></channel></rss>