Categories: geek » emacs

View topic page - RSS - Atom - Subscribe via email
Recommended links:

Emacs completion and handling accented characters with orderless

| emacs

I like using the orderless completion package for Emacs because it allows me to specify different parts of a completion candidate than any order I want. Because I'm learning French, I want commands like consult-line (which uses minibuffer completion) and completion-at-point (which uses in-buffer completion) to also match candidates where the words might have accented characters. For example, instead of having to type "utilisé" with the accented é, I want to type "utilise" and have it match both "utilise" and "utilisé".

(defvar my-orderless-accent-replacements
  '(("a" . "[aàáâãäå]")
    ("e" . "[eèéêë]")
    ("i" . "[iìíîï]")
    ("o" . "[oòóôõöœ]")
    ("u" . "[uùúûü]")
    ("c" . "[cç]")
    ("n" . "[nñ]"))) ; in case anyone needs ñ for Spanish

(defun my-orderless-accent-dispatch (pattern &rest _)
  (seq-reduce
   (lambda (prev val)
     (replace-regexp-in-string (car val) (cdr val) prev))
   my-orderless-accent-replacements
   pattern))

(use-package orderless
  :custom
  (completion-styles '(orderless basic))
  (completion-category-overrides '((file (styles basic partial-completion))))
  (orderless-style-dispatchers '(my-orderless-accent-dispatch orderless-affix-dispatch)))
2026-02-26_15-06-59.png
Figure 1: Screenshot of consult-line showing matching against accented characters
2026-02-26_15-08-34.png
Figure 2: Screenshot of completion-at-point matching "fev" with "février"

This is an entry for Emacs Carnival February 2026: Completion.

This is part of my Emacs configuration.
View Org source for this post

Sorting completion candidates, such as sorting Org headings by level

| emacs, org

: Made the code even neater with :key, included the old code as well

At this week's Emacs Berlin meetup, someone wanted to know how to change the order of completion candidates. Specifically, they wanted to list the top level Org Mode headings before the second level headings and so on. They were using org-ql to navigate Org headings, but since org-ql sorts its candidates by the number of matches according to the code in the org-ql-completing-read function, I wasn't quite sure how to get it to do what they wanted. (And I realized my org-ql setup was broken, so I couldn't fiddle with it live. Edit: Turns out I needed to update the peg package) Instead, I showed folks consult-org-heading which is part of the Consult package, which I like to use to jump around the headings in a single Org file. It's a short function that's easy to use as a starting point for something custom.

Here's some code that allows you to use consult-org-heading to jump to an Org heading in the current file with completions sorted by level.

(with-eval-after-load 'consult-org
  (advice-add
   #'consult-org--headings
   :filter-return
   (lambda (candidates)
     (sort candidates
           :key (lambda (o) (car (get-text-property 0 'consult-org--heading o)))))))
2026-02-26_13-42-58.png
Figure 1: Screenshot showing where the candidates transition from top-level headings to second-level headings

My previous approach defined a different function based on consult-org-heading, but using the advice feels a little cleaner because it will also make it work for any other function that uses consult-org--headings. I've included the old code in case you're curious. Here, we don't modify the function's behaviour using advice, we just make a new function (my-consult-org-heading) that calls another function that processes the results a little (my-consult-org--headings).

Old code, if you're curious
(defun my-consult-org--headings (prefix match scope &rest skip)
  (let ((candidates (consult-org--headings prefix match scope)))
    (sort candidates
          :lessp
          (lambda (a b)
            (let ((level-a (car (get-text-property 0 'consult-org--heading a)))
                  (level-b (car (get-text-property 0 'consult-org--heading b))))
              (cond
               ((< level-a level-b) t)
               ((< level-b level-a) nil)
               ((string< a b) t)
               ((string< b a) nil)))))))

(defun my-consult-org-heading (&optional match scope)
  "Jump to an Org heading.

MATCH and SCOPE are as in `org-map-entries' and determine which
entries are offered.  By default, all entries of the current
buffer are offered."
  (interactive (unless (derived-mode-p #'org-mode)
                 (user-error "Must be called from an Org buffer")))
  (let ((prefix (not (memq scope '(nil tree region region-start-level file)))))
    (consult--read
     (consult--slow-operation "Collecting headings..."
       (or (my-consult-org--headings prefix match scope)
           (user-error "No headings")))
     :prompt "Go to heading: "
     :category 'org-heading
     :sort nil
     :require-match t
     :history '(:input consult-org--history)
     :narrow (consult-org--narrow)
     :state (consult--jump-state)
     :annotate #'consult-org--annotate
     :group (and prefix #'consult-org--group)
     :lookup (apply-partially #'consult--lookup-prop 'org-marker))))

I also wanted to get this to work for C-u org-refile, which uses org-refile-get-location. This is a little trickier because the table of completion candidates is a list of cons cells that don't store the level, and it doesn't pass the metadata to completing-read to tell it not to re-sort the results. We'll just fake it by counting the number of "/", which is the path separator used if org-outline-path-complete-in-steps is set to nil.

(with-eval-after-load 'org
  (advice-add
   'org-refile-get-location
   :around
   (lambda (fn &rest args)
     (let ((completion-extra-properties
            '(:display-sort-function
              (lambda (candidates)
                (sort candidates
                      :key (lambda (s) (length (split-string s "/"))))))))
       (apply fn args)))))
2026-02-26_14-01-28.png
Figure 2: Screenshot of sorted refile entries

In general, if you would like completion candidates to be in a certain order, you can specify display-sort-function either by calling completing-read with a collection that's a lambda function instead of a table of completion candidates, or by overriding it with completion-category-overrides if there's a category you can use or completion-extra-properties if not.

Here's a short example of passing a lambda to a completion function (thanks to Manuel Uberti):

(defun mu-date-at-point (date)
  "Insert current DATE at point via `completing-read'."
  (interactive
   (let* ((formats '("%Y%m%d" "%F" "%Y%m%d%H%M" "%Y-%m-%dT%T"))
          (vals (mapcar #'format-time-string formats))
          (opts
           (lambda (string pred action)
             (if (eq action 'metadata)
                 '(metadata (display-sort-function . identity))
               (complete-with-action action vals string pred)))))
     (list (completing-read "Insert date: " opts nil t))))
  (insert date))

If you use consult--read from the Consult completion framework, there is a :sort property that you can set to either nil or your own function.

This entry is part of the Emacs Carnival for Feb 2026: Completion.

This is part of my Emacs configuration.
View Org source for this post

2026-02-23 Emacs news

| emacs, emacs-news

: Added m-x.app examples, moved el-init to AI category, added retrospective link.

Org Mode is a big part of why I enjoy Emacs, so I'm delighted that there's a new release out (Org 9.8). Thanks to all who contributed! If you would like to help out, Ihor is looking for several volunteers who can try to reproduce bugs and do initial feedback on the new patches.

Links from reddit.com/r/emacs, r/orgmode, r/spacemacs, Mastodon #emacs, Bluesky #emacs, Hacker News, lobste.rs, programming.dev, lemmy.world, lemmy.ml, planet.emacslife.com, YouTube, the Emacs NEWS file, Emacs Calendar, and emacs-devel. Thanks to Andrés Ramírez for emacs-devel links. Do you have an Emacs-related link or announcement? Please e-mail me at sacha@sachachua.com. Thank you!

View Org source for this post

2026-02-16 Emacs news

| emacs, emacs-news

Lots of cool stuff this week! I'm looking forward to checking out the new futur library for async programming, and the developments around embedding graphics in a canvas in Emacs look interesting too (see the Multimedia section). Also, the discussion about making beginner configuration easier could be neat once the wrinkles are ironed out. Enjoy!

Links from reddit.com/r/emacs, r/orgmode, r/spacemacs, Mastodon #emacs, Bluesky #emacs, Hacker News, lobste.rs, programming.dev, lemmy.world, lemmy.ml, planet.emacslife.com, YouTube, the Emacs NEWS file, Emacs Calendar, and emacs-devel. Thanks to Andrés Ramírez for emacs-devel links. Do you have an Emacs-related link or announcement? Please e-mail me at sacha@sachachua.com. Thank you!

View Org source for this post

2026-02-09 Emacs news

| emacs, emacs-news

Links from reddit.com/r/emacs, r/orgmode, r/spacemacs, Mastodon #emacs, Bluesky #emacs, Hacker News, lobste.rs, programming.dev, lemmy.world, lemmy.ml, planet.emacslife.com, YouTube, the Emacs NEWS file, Emacs Calendar, and emacs-devel. Thanks to Andrés Ramírez for emacs-devel links. Do you have an Emacs-related link or announcement? Please e-mail me at sacha@sachachua.com. Thank you!

View Org source for this post

2026-02-02 Emacs news

| emacs, emacs-news

Links from reddit.com/r/emacs, r/orgmode, r/spacemacs, Mastodon #emacs, Bluesky #emacs, Hacker News, lobste.rs, programming.dev, lemmy.world, lemmy.ml, planet.emacslife.com, YouTube, the Emacs NEWS file, Emacs Calendar, and emacs-devel. Thanks to Andrés Ramírez for emacs-devel links. Do you have an Emacs-related link or announcement? Please e-mail me at sacha@sachachua.com. Thank you!

View org source for this post

Emacs and French: Focus flycheck-grammalecte on the narrowed part of the buffer

Posted: - Modified: | emacs, french

: Fix flycheck-checkers.

After learning about French spellcheck and grammar checking from Emacs expliqué à mes enfants, I added flycheck-grammalecte to my config. Nudged by @lann@mastodon.zaclys.com, I finally got around to figuring out why my setup sometimes worked and sometimes didn't. When I checked flycheck-verify-setup, I noticed that grammalecte kept getting disabled. A little digging around showed me that it was getting disabled because of too many errors. That was because it was trying to work on my whole file instead of just the portion that I narrowed to with org-narrow-to-subtree (ooh, just noticed an org-toggle-narrow-to-subtree command). I like having all of my French journal entries in one file because I can use consult-line (which I've bound to M-g l) to quickly look up examples of where else I've used a word. So I needed to define a checker that runs only on the narrowed part of the buffer.

(defun my-flycheck-grammalecte-buffer (checker callback)
  (let* ((temp-file-name (make-temp-file "grammalecte"))
         (output-buffer (get-buffer-create temp-file-name))
         (buffer (current-buffer))
         (cmdline (delq nil `("python3"
                              ,(expand-file-name "flycheck_grammalecte.py"
                                                 grammalecte--site-directory)
                              ,(unless flycheck-grammalecte-report-spellcheck "-S")
                              ,(unless flycheck-grammalecte-report-grammar "-G")
                              ,(unless flycheck-grammalecte-report-apos "-A")
                              ,(unless flycheck-grammalecte-report-nbsp "-N")
                              ,(unless flycheck-grammalecte-report-esp "-W")
                              ,(unless flycheck-grammalecte-report-typo "-T")
                              (option-list "-f" flycheck-grammalecte-filters)
                              (eval (flycheck-grammalecte--prepare-arg-list
                                     "-f" flycheck-grammalecte-filters-by-mode))
                              (eval (flycheck-grammalecte--prepare-arg-list
                                     "-b" flycheck-grammalecte-borders-by-mode))
                              ,temp-file-name)))
         (args (mapcan (lambda (arg) (flycheck-substitute-argument arg checker)) cmdline))
         (command (flycheck--wrap-command (car args) (cdr args))))
    (write-region (buffer-string) nil temp-file-name)
    (make-process :name "grammalecte"
                  :buffer output-buffer
                  :command command
                  :sentinel
                  (lambda (process status)
                    (let ((errors (with-current-buffer (process-buffer process)
                                    (message "%s" (buffer-string))
                                    (flycheck-parse-with-patterns
                                     (buffer-string)
                                     checker
                                     (current-buffer)))))
                      (delete-file temp-file-name)
                      (kill-buffer output-buffer)
                      ;; offset
                      (funcall
                       callback
                       'finished
                       (let ((offset (save-excursion (goto-char (point-min))
                                                     (line-number-at-pos nil t))))
                         (mapcar
                          (lambda (err)
                            (let ((new-err (copy-flycheck-error err)))
                              (setf (cl-struct-slot-value 'flycheck-error 'buffer new-err)
                                    buffer)
                              (setf (cl-struct-slot-value 'flycheck-error 'line new-err)
                                    (+ (flycheck-error-line new-err)
                                       offset -1))
                              (setf (cl-struct-slot-value 'flycheck-error '-end-line new-err)
                                    (+ (flycheck-error-end-line new-err)
                                       offset -1))
                              new-err))
                          errors))))))))

(defun my-flycheck-grammalecte-setup ()
  "Build the flycheck checker, matching your taste."
  (interactive)
  (unless (grammalecte--version)
    (advice-add 'grammalecte-download-grammalecte :after-while
                #'flycheck-grammalecte--retry-setup))
  (grammalecte--augment-pythonpath-if-needed)
  (flycheck-define-generic-checker 'my-grammalecte-narrowed
    "Report Grammalecte errors, but only for the narrowed section."
    :start #'my-flycheck-grammalecte-buffer
    :modes flycheck-grammalecte-enabled-modes
    :predicate (lambda ()
                 (if (functionp flycheck-grammalecte-predicate)
                     (funcall flycheck-grammalecte-predicate)
                   t))
    :enabled #'grammalecte--version
    :verify #'flycheck-grammalecte--verify-setup)
  (setf (flycheck-checker-get 'my-grammalecte-narrowed 'error-patterns)
        (seq-map (lambda (p)
                   (cons (flycheck-rx-to-string `(and ,@(cdr p))
                                                'no-group)
                         (car p)))
                 flycheck-grammalecte--error-patterns))
  (add-to-list 'flycheck-checkers 'my-grammalecte-narrowed)
  (flycheck-grammalecte--patch-flycheck-mode-map))

After I use my-flycheck-grammalecte-setup, I can use flycheck-select-checker to select my-grammalecte-narrowed and then use flycheck-buffer to run it. Then it will underline all the number/gender agreement issues I usually have. It's nice that I can practise editing my text with this script before I run the text through an LLM (also via flycheck) for feedback on wording.

2026-01-30_22-20-20.png
Figure 1: Screenshot of grammalecte providing grammar feedback
This is part of my Emacs configuration.
View org source for this post