Configuring Consult Imenu Narrowing for Ruby
28 February 2024 | 2:17 pm

Summary: A quick walk through of a configuration for exposing another means of searching/navigating within code.

I use the Consult package. During Protesilaos Stavrou 📖 ’s Emacs: basics of search and replace video, he demonstrated the consult-imenu function for Emacs Lisp 📖 . In particular the semantic narrowing functionality.

The baseline Consult package declares consult-imenu-config Sidenote See Github’s consult-imenu-config source code. as follows:

(defcustom consult-imenu-config
  '((emacs-lisp-mode :toplevel "Functions"
                     :types ((?f "Functions" font-lock-function-name-face)
                             (?m "Macros"    font-lock-function-name-face)
                             (?p "Packages"  font-lock-constant-face)
                             (?t "Types"     font-lock-type-face)
                             (?v "Variables" font-lock-variable-name-face))))
  "Imenu configuration, faces and narrowing keys used by `consult-imenu'.

For each type a narrowing key and a name must be specified.  The
face is optional.  The imenu representation provided by the
backend usually puts functions directly at the toplevel.
`consult-imenu' moves them instead under the type specified by
  :type '(repeat (cons symbol plist))
  :group 'consult)

With that baseline configuration, when I call consult-imenu in an Emacs Lisp file, I see the symbols groups by the above types (e.g. Functions, Macros, Packages, Types, and Variables).

Configuring for Ruby

Here’s my configuration for Ruby mode and the sibling Ruby Tree Sitter 📖 mode.

(require 'consult-imenu)
(dolist (ruby '(ruby-mode ruby-ts-mode))
  (add-to-list 'consult-imenu-config
       :toplevel "Method"
       :types ((?p "Property" font-lock-variable-name-face)
                (?c "Class" font-lock-type-face)
                (?C "Constant" font-lock-type-face)
                (?M "Module" font-lock-type-face)
                (?m "Method" font-lock-function-name-face)))))

Without the configuration consult-imenu will show the above symbols but will not categorize them into groups that I can further narrow.

Let’s look at the following Ruby code.

class BasicProgram
  DEFAULT_HELLO = "hello"
  DEFAULT_WORLD = "world"

  def initialize(hello: DEFAULT_HELLO, world: DEFAULT_WORLD)
    @hello = hello
    @world = world

  attr_reader :hello, :world

  def to_s
    "#{hello} #{world}"


In the above code, consult-imenu leverages the outline functionality to categorize the information as follows, and then render in the mini-buffer:

  • Class
    • BasicProgram
  • Constant
    • BasicProgram DEFAULT_HELLO
    • BasicProgram DEFAULT_WORLD
  • Property
    • BasicProgram hello
    • BasicProgram world
  • Method
    • BasicProgram new
    • BasicProgram to_s

Within the minibuffer, I can then search as normal or narrow to a single category by typing the category key and space.

For example I could narrow to “Methods” by typing m then space. That narrows the above list to the following:

  • BasicProgram new
  • BasicProgram to_s

I could then type to further narrow the text.


I find that I navigate code in many different ways, and when I find a means that improves organizational overview as well as navigation, I take notice.

Reply by Email

A Journey of a Thousand Posts
26 February 2024 | 1:20 am

Summary: A reflection on the first 1000 posts for Take on Rules.

Anything that passes through memory becomes fiction.

For the past few days, in the hours just after sunrise, I have been hearing a noise. It sounded like someone next door using an impact driver.

This morning, while outdoors throwing Frisbee with my dogs I heard the noise coming from a chimney. A bird, sat perched, and was pecking on the thick aluminum sealing. Curious, I listened, and between it’s pecking, I heard it’s call.

One I had heard before, but with my limited birding experience did not recognize.

I opened up the Merlin Bird ID application and began recording. Sidenote See the homepage for the Merlin Bird ID. A minute or so later, and the application identified the bird’s song as a Northern Flicker.

While writing this post, the late February morning light casts long shadows on a yard pocked with remnants of a single night of snowfall. Birds dart and dance amongst young trees and brush: cardinals, blue jays, robins, and certainly more.

At the back door Lacey, our 2 year old border collie, looks on; curious of the activity of a fading winter. She knows that soon we’ll again head out for our mid-morning game of Frisbee.

Due to a warm winter, the buds threaten to burst. A reminder that nature too is confused of where and when we’re at. I worry of a possible late March or early April killing frost and what that might portend.

But for now, I sip my coffee and contemplate my one thousandth post. This will likely take the better part of a Sunday, as a flutter to and from my computer.

The First Step

On February 2, 2011 I started my personal blogging journey. At the time I was working for a university department who’s director encouraged all of us to blog.

In the years prior, the university department had rolled out a campus-wide WordPress 📖 blogging platform, and I had been writing professional blog posts on that platform.

The organizational policies for that platform were permissive; I could’ve used that platform for hobby related writing. But I chose not to write about games on my professional programming blog.

Instead, with nudging from my partner, I started my own personal blog. And with her help in brainstorming ideas, we named it Take on Rules. In A First Take, I wrote about my then narrow vision for this blog.

One Foot In Front of the Other

In the next few months, I saw a call for the April 2011 A to Z blogging challenge. Sidenote Tossing It Out’s A to Z challenge. A post on Blogger/Blogspot, a platform that I assume one day Google will kill. Twenty-six posts in one month, with each letter as a general prompt. Sidenote See Posts for 2011.

I completed the challenge, which expanded my consideration for what posts I might make.

This challenge followed on the professional rule of blogging: “Publish on a schedule.” Sidenote my take on that rule, for hobby writing in particular, is that it’s utter trash.

I ended up writing 126 posts that year; something I wouldn’t surpass until 2021, when I was in my first full year of both being an empty-nester and shedding a two hour daily commute; both of which remain true.

A Personal Shift

Both my partner and I have privately reflected that a personally transformative day, one in which I “leveled-up” as a human being, was during a moment I wrote about in Mustering into the GenCon Volunteer Corp. Sidenote I also love the sketch that I made. I need to remember to grab a pencil and paper more often; but the shiny allure of hacking on Emacs 📖 seems to be my present and sustained fixation. The personal shift was seeing a messy situation and being able and willing to help out.

I’m proud of that moment, because my small bit of volunteerism had an impact on other people; not just those attending but also those volunteering who were themselves overwhelmed.

I would use this energy and moment to explore new opportunities; both personally and professionally. My resume is, in part, a testimony to those changes. I was seeking more responsibility, leadership, and mentoring opportunities.

I was melding this new mindset with raising children now entering high school, helping launch my partner’s business, and moonlighting as a software mentor. All of which was a natural recipe for a drop-off in writing for my blog. My focus was elsewhere and I was de-prioritizing time for playing games and especially writing about the times I played games.

Also I had yet to make a liberating shift; one towards the everything and nothing principle in which “the website’s content means everything to the publisher, but it could mean nothing to the rest of the world.”

There are hints of that shift with posts such as my 2014 post Divorce - A Personal Experience and the 2015 post My take on a terrible law. But let’s turn back to the main thread.

Finding a New Groove

Between 2000 and 2005 or so, I had hosted a Monday night game night. By default, every Monday night folks would come to my house, we’d eat dinner, and then play games. It didn’t matter who was there, we’d play something.

I had learned, but not articulated, that making this a ritualized event helped transform my thinking past the fatiguing thoughts/planning of “when are we next getting together” into an exciting and anticipatory “it’s Monday and that means games.”

This ritualization lead me to start a short-lived drop-in weekly Dungeon Crawl Classics (DCC 📖) campaign. Sidenote See my Campaign: DCC Better World Gaming 📖 ; That campaign was a test of that intuited principle of having a standing weekly social event. Sidenote I have sinced used this to make new (and deepening) friendships; as well as start another Role Playing Game (RPG 📖) campaign.

I was finding creative freedom in focusing on smaller discrete adventure modules. Where adventure paths provide much of the milieu, stitching together disparate adventures was an energizing exercise. These were an opportunity for surprise and response, to then consider how I might fold the adventure into the larger milieu.

Everything and Nothing

On June 8, 2021, I committed to making Take on Rules an “everything and nothing” blog. I had been doing this, without naming it, but I had encountered the term and found resonance.

I had also completed a migration from WordPress to a static site generator. This provided an opportunity to sharpen my HTML 📖 and CSS 📖 skills, minimize my dependencies, and reconnect with the joy of hacking and programming.

Naming the concept of “everything and nothing” along with less time obligations per day paired with a blogging environment of my own making, by way of extending Emacs and writing additional Ruby 📖 scripts, reinvigorated my writing.

Online Gaming

During this time, I found a few kindred spirits with whom to play online RPGs . These were great games. I look fondly on my characters Captain James van Shaw Sidenote See Burning Warhammer: The Captain and the Witch (Alternate Character Creation). , Lord Antonious di Mari Sidenote See Burning Locusts: Character Creation. , Professor Connor Fendelborn IV Sidenote See Online Playtest of Lavender Hack by Phil Lewis. , Carric Caerdonel Sidenote See Upcoming Dungeons and Dragons Game. and others. And the games that I also ran.

One side-effect was that I began using my professional tools (e.g. a text editor) while playing these games. This helped me think more about the utility of my text editor, in day to day as well as.

This gaming experience contributed towards my developing a Personal Knowledge Management (PKM 📖) : first using Org-Roam 📖 then switching to Denote 📖 . Along the way, I wrote blog posts tagged with Emacs. This was my chance to share and learn. Folks began reaching out to me via email, and we’d have and tagged I was blogging about these changes under the emacs tag.

I had planned to participate in the Lore24 📖 , and did so for a bit. First publicly, then privately, and finally not at all. This was different from the A-Z Challenge of yore, the constraints greater, and the nature less satisfying.

Where Shall I Put the Thing

Off and on I’ve kept handwritten journals. Filling a small shelf with cursive writing. In years past, I would regularly carry one of these A5 sized journals everywhere I went. I’d write down meeting notes, poems, personal thoughts, or design diagrams. It was my place to put anything that didn’t belong on a blog.

As I moved to remote work, I shifted more of my day-job note taking to a computer. And with my gaming notes shifting to Org-Mode 📖 , the notes locations began collapsing. Yet my blog posts remained separate.

I had previously set about writing blog posts or writing personal documents. But in my Migration Plan for Org-Roam Notes to Denote, I started shifting to the idea of all writing being personal and then surfacing writing that I would publish on my blog.

This reframing and shift to blogging about everything meant that even more of my writing became both easier and “eligible” for publication.

Up Next

The very impulse to write springs from an inner chaos crying for order—for meaning.
Arthur Miller

As the moon rises on this waning day, I’m looking forward to continuing to write, and further developing my tools for writing.

Reply by Email

Emacs Function to Assign Org-Mode Property to Matching Criteria
24 February 2024 | 10:15 pm

Summary: Some Emacs Lisp to help me populate random information into my Campaign Status document. This delves into the org-element-map function and some querying of information to pick the right elements.

Yesterday, I wrote my Update on the Campaign Status Document. I had manually set the alignment of several Non-Player Characters (NPCs 📖) ; but I thought “Maybe I should instead clear those out and randomize?”

And I started thinking about how I might automatically update (or add) new properties.

What I wanted was to select a top-level headline, select a property name, and pick a random table. Sidenote As per my random-tables Emacs package 📖 . With those chosen, I then populate all immediate sub-headings with the property and a randomized roll on the table. Sidenote A “table” can be a dice expression.

Below is that implementation.

(defun jf/campaign/populate-property-draw-value (&optional headline property table)
  "Populate HEADLINE's direct children's empty PROPERTY with roll on given TABLE.

When given no HEADLINE, prompt for ones from the document.

When given no PROPERTY, prompt for a property.

When given no TABLE, prompt for an expression to evaluate via
  (let* ((level 1)
          (headline (or headline
                      (completing-read "Headline: "
                        (jf/org-get-headlines level) nil t)))
          (property (or property
          (table (or table
                   (completing-read "Table: "
            ;; Bind a function that will only output the results of the
            ;; table, excluding the expression that generated the
            ;; results.
            (lambda (expression results) (format "%s" results))))
    (unless (org--valid-property-p property)
      (user-error "Invalid property name: \"%s\"" property))
      ;; Now that we have the information, let’s start rolling on some
      ;; tables.
        ;; Because we will not be recalculating heading positions, we
        ;; need to reverse the list, as updates to the last element will
        ;; not alter the position of the next to last element.
        (subheading (reverse
                        (lambda (el)
                            (= ;; We want the children of the given
			       ;; level
                             (+ 1 level)
			     (org-element-property :level el))
                              ;; Match the text
                                (car (org-element-lineage el)))
                            ;; Don't clobber already set values
                            (not (org-element-property property el))
                            ;; We found a match now return it
        ;; We have finally found the element, now roll on the table and
        ;; populate.
          (goto-char (org-element-property :begin subheading))
          property (random-table/roll table))))))


On the surface, Org-Mode 📖 syntax appears to be a variant of Markdown. But the true beauty shines when you consider the additional structure and functionality. The Org Element API provides useful guidance. But even more valuable is being able to see the source code.

I was able to look at the org-set-property function to find a few bits of useful logic that would help me avoid repetition. And from there I have a reasonably robust function.

One refinement obvious refinement is that the table parameter could instead be a function, which is then called for each element.

Feb 25, 2024 update

The previous code worked for a small test case, but once I test against a more complicated situation, it failed.

I intuited there would be a problem, as the previous comments indicated; but in my haste I didn’t check and verify.

I have updated the code and references to reflect those changes.

Reply by Email

More News from this Feed See Full Web Site