Skip to content

YouTube Tutorial

Armin Darvish edited this page Jul 18, 2024 · 3 revisions

announcing consult-omni - Show Notes

video link: Emacs consult-omni package: a powerful omni-search and launcher in emacs

Background (video link)

You can find the consult-omni package here: consult-omni

I recommend watching the previous video on consult-web, as well. Here is a link to the video: Emacs consult-web package: Get web search results as well as omni searches inside emacs!

Understanding the design (video link)

Here are some important points to know about the package:

dynamic v.s. static commands

By default, you will see two different types of interactive commands for each source:

  • static commands

    Two Static Minibuffer Completing Read: 1- Gets a search term (with or without autosuggestion) 2- Get search results for candidates

    Results can be narrowed down by a filter (e.g. by orderless), but cannot be recalculated (e.g. for a new search term)

  • dynamic commands:

    One Dynamic Minibuffer Completing Read: 1- Get a search term and dynamically updates the results

async v.s. sync

In addition, in consult-omni, we have improved the collection process, so we so not have to block Emacs process when waiting for items to arrive because depending on the source, it may take a long time to get results for a given search term. For example, web searches require sending a web request to a server and collecting and parsing responses. Depending on the server, number of hits, network speed, … this may take milliseconds up to even several seconds. Therefore, it is preferable to run such request as an asynchronous process (a.k.a. without blocking Emacs process).

Here are examples to see the difference

consult-web-scholar v.s. consult-omni-scholar

  • sync process in consult-web
(consult-web-scholar "quantum sensors -- --count 200")
  • async process in consult-omni
(consult-omni-scholar "quantum sensors -- --count 500")

This means that we can run omni-search with multiple sources even if one source would return thousands of results.

  • example with grep:
(consult-omni-grep "emacs -- :dir ~/")
  • example with grep in a multi-source search (and different orders of sources)
(consult-omni-multi "emacs -- :dir ~/" nil '("gptel" "Wikipedia" "Brave" "grep"))
(consult-omni-multi "emacs -- :dir ~/" nil '("grep" "gptel" "Wikipedia" "Brave"))
  • let’s see another example without grouping to see the order of arrival.
(consult-omni-multi "emacs -- :n 3" nil '("GitHub" "Brave" "gptel" "Wikipedia"))
(consult-omni-multi "emacs -- :n 3 :g nil" nil '("GitHub" "Brave" "gptel" "Wikipedia"))

multi-source collections

Unlike consult-web, in consult-omni, we no longer provide extra interactive commands for multi-source searches. Instead we have modified consult-omni-multi, so the users can define their own custom interactive commands that fit their work flow.

Here are some examples:

scholar

(defun consult-omni-scholar (&optional initial no-callback &rest args)
  "Interactive “multi-source acadmic literature” search."

  (interactive "P")
  (consult-omni-multi initial "🎓 " '("PubMed" "Scopus" "Consult Notes Reference Roam Nodes" "Consult Notes Zettel Roam Nodes" "gptel") no-callback args))

local

(defun consult-omni-local (&optional initial no-callback &rest args)
  "Interactive “multi-source acadmic literature” search."

  (interactive "P")
  (consult-omni-multi initial "[consult-omni-local] Search:" '("ripgrep-all" "mdfind" "Notes Search" "Apps") no-callback args))

web

(defun consult-omni-web (&optional initial no-callback &rest args)
  "Interactive “multi-source acadmic literature” search."

  (interactive "P")
  (consult-omni-multi initial "󰾔 : " '("gptel" "Brave" "elfeed" "Wikipedia" "GitHub" "Invidious") no-callback args))
let binding can sometimes be useful (e.g. for consult-async-split style)
(defun consult-omni-web (&optional initial no-callback &rest args)
  "Interactive “multi-source acadmic literature” search."

  (interactive "P")
  (let ((consult-async-split-style 'comma))
  (consult-omni-multi initial "󰾔 : " '("gptel" "Brave" "elfeed" "Wikipedia" "GitHub" "Invidious") no-callback args)))

emacs

(defun consult-omni-emacs (&optional initial no-callback &rest args)
  "Interactive “multi-source acadmic literature” search."

  (interactive "P")
  (consult-omni-multi initial "[consult-omni-emacs] Search: " '("mu4e" "Org Agenda" "Notes Search" "Buffer") no-callback args))

omni

(defun consult-omni-omni (&optional initial no-callback &rest args)
  "Interactive omni search."

  (interactive "P")
  (consult-omni-multi initial "consult-omni-local" '("gptel" "Brave" "Wikipedia" "ripgrep-all" "mdfind" "Notes Search" "Apps" "calc" "agenda") no-callback args))

default

Note that you can still use consult-omni commands as a shorthand for your favorite interactive command by setting consult-omni-default-interactive-command:

(setq consult-omni-default-interactive-command #'consult-omni-omni)

Various Sources (video link)

Can see a list of all sources on the Repo’s README: consult-omni

Previous sources from consult-web

Google, Brave, Wikipedia, elfeed, gptel ,…

You can refer to the previous video on consult-web to see example of using these sources:

Emacs consult-web package: Get web search results as well as omni searches inside emacs!

Some New sources/Features

Finding Local Files with async commands (video link)

grep | ripgrep | ripgrep-all | git-grep

We now have sources for grep-like commands similar to what is provided in core consult- package. Of note is the addition of ripgrep-all, which uses ripgrep-all and allows searching inside PDFs, Office Documents, Zipped Folders, …

grep
(consult-grep)
(consult-omni-grep)
(consult-omni-grep "consult -- :g file")
ripgrep-all
(consult-omni-ripgrep-all "Hello World")
(consult-omni-ripgrep-all "consult")
(consult-omni-ripgrep-all "quantum sensors -- :dir ./examples/pdfs/")

find | fd | locate | mdfind

We also have sources for other command-line programs such as find, fd, …

(consult-omni-mdfind "consult")

man

There is also a new source for manual pages similar to consult-man:

(consult-omni-man "emacsclient")

GitHub (video link)

We have an adopted version of consult-gh-search-repos from my own consult-gh package.

(consult-gh-search-repos)
(consult-omni-github "consult-omni")

Emails (mu4e | notmuch) (video link)

We also have an adopted version of my consult-mu package:

(consult-mu "consult-web")
(consult-omni-mu4e "consult-web")

I have also added support for notmuch which uses notmuch-emacs package.

(consult-omni-notmuch "consult-web")

Notes (video link)

Similar to what we had in consult-web, you can use consult-notes from consult-omni, with addition of support for searching denote notes with consult-note. In addition I have added a new source called “Notes Search” with the interactive command consult-omni-notes-search that searches the contents of the note files using a grep-like command.

consult-notes

(consult-omni-consult-notes-org "macos")
(consult-omni-consult-notes-zettel-roam-nodes "emacs")
(consult-omni-consult-notes-denote "example")

notes search

A simple grep command with list of files and directories

(consult-notes-search-in-all-notes)
(setq consult-omni-notes-files (list (expand-file-name org-directory)
                                     (expand-file-name org-roam-directory)
                                     (expand-file-name denote-directory)
                                      ))
(setq consult-omni-notes-backend-command "grep")
(consult-omni-notes-search "emacs")
(consult-omni-notes-search "GNU Emacs")

Can use grep, ripgrep, or ripgrep-all back-ends

(setq consult-omni-notes-backend-command "rga")
(consult-omni-notes-search "GNU Emacs")

creating new notes

Furthermore, you can create a new note by selecting the minibuffer input itself:

(consult-omni-notes-search "a novel note")
(setq consult-omni--notes-new-func #'consult-omni--notes-new-capture-org-roam)

(consult-omni-notes-search "a novel note")
(setq consult-omni--notes-new-func #'consult-omni--notes-new-create-denote)

(consult-omni-notes-search "a novel note")

Agenda (video link)

There is also a new source to support searching Org Agenda items, that can also interpret simple phrases like today, tomorrow, next week, etc.

searching agenda items

(consult-omni-org-agenda)
(consult-omni-org-agenda "youtube")
(setq consult-omni-org-agenda-transform-prefix nil)
(consult-omni-org-agenda "today")
(setq consult-omni-org-agenda-transform-prefix "?")
(consult-omni-org-agenda "today")
(consult-omni-org-agenda "?around today")
(setq consult-omni-org-agenda-number-of-days-around 1)
(consult-omni-org-agenda "?around today")
(setq consult-omni-org-agenda-number-of-days-around 14)
(consult-omni-org-agenda "?around 1 week ago")
Phrase Meaning
yesterday yesterday
today today
tomorrow tomorrow
this/next/last week The current/next/previous week (Sun-Fri)
this/next/last work week The current/next/previous work week (Mon-Fri)
this/next/last month The current/next/previous calendar month
this/next/last year The current/next/previous calendar year
%d day(s)/week(s)/month(s)/year(s) ago %d days/weeks/months/years ago
%d day(s)/week(s)/month(s)/year(s) from now %d days/weeks/months/years from now
around date (phrase) +/- 7 (customizable) days from date (phrase)

new items open org capture

New tasks can be made by selecting the minibuffer input itself (calls org-capture)

(consult-omni-org-agenda "new item")

Calculators (video link)

Emacs calc

There is now a source to get results from Emacs’ built-in calculator which uses the calc-eval function.

basic math
(setq consult-omni-calc-regexp-pattern nil)
(consult-omni-calc)
(consult-omni-multi nil nil '("calc" "wikipedia" "gptel" "Brave"))
(setq consult-omni-calc-regexp-pattern "^=\\(.*\\)?")
(consult-omni-multi nil nil '("calc" "wikipedia" "gptel" "Brave"))
more complex math
(consult-omni-calc "=integ(x^2, x, 0, a)")
(consult-omni-multi "=integ(x^2, x, 0, a)" nil '("calc" "wikipedia" "gptel" "Brave"))
(consult-omni-calc "=$1*$2 -- :$ \"((display-pixel-width) (display-pixel-height))\"")
conversion
(consult-omni-calc "=50degF -- :c degC")
numi-cli

In addition, you can use the command line client of the numi package for getting calculator results and unit conversions from prompts that use natural language (e.g. "10 plus 2", "10F to C", "20USD to Euro",…)

(consult-omni-numi)

App Launcher (video link)

There is a new command for launching desktop applications that works on both MacOS and Linux machines and combines features from the following separate packages into a more universal tool:

launch apps (on Linux or MacOS)
(consult-omni-apps)

Note that for a more classic launcher view, you can do:

(consult-omni-apps-static ".*")

Or you can do:

(consult-omni-apps ".*#")

note that with the later dynamic command, you won’t get sorting based on history (i.e. putting the latest selections on top), but using the static command allows sorting by history if you have that setup (i.e. by using orderless)

open in find-file, finder, open in terminal, …

There are also some useful embark actions specifically for apps. Call the following command and use embark actions to open the file location in terminal or with find-file, ....

(consult-omni-apps)
open-with

There is also another interactive command to do open with on any file:

(consult-omni-open-with-app)
(keymap-set embark-file-map "X" #'consult-omni-open-with-app)

Dictionary (video link)

We now support getting results from the emacs built-in Dictionary as well:

(setq consult-omni-dict-predicate nil)
(consult-omni-dictionary)

You can use the provided default predicate function (or define your own custom one) for restricting querying the Dictionary, so you don’t get Dictionary results for every search you run. For example in the function below, we limit the dictionary results to search queries that start with the phrase: "define ":

(setq consult-omni-dict-predicate #'consult-omni-dict-default-pred-func)
(consult-omni-dictionary "omnipotent")
(setq consult-omni-dict-number-of-lines nil)
(consult-omni-dictionary "define omnipotent")
(setq consult-omni-dict-number-of-lines 1)
(consult-omni-dictionary "define omnipotent")
(consult-omni-dictionary "define nove")

Invidious (video link)

This is an open source alternative front-end to YouTube without rate-limits that you can use to search YouTube videos. See https://invidious.io/ for more details.

(consult-omni-invidious)

Other Sources (video link)

Note that some other sources (like "YouTube Subscription" , or "Reddit Posts", …) can be searched by using a work-around with elfeed. You can subscribe to YouTube or Reddit feeds on elfeed and then use the elfeed source in consult-omni to query those feeds. Take a look at the package elfeed-tube for examples on how to subscribe to YouTube channels in elfeed.

elfeed

(consult-omni-elfeed)

Browser-history (experimental) (video link)

We have support for using browser-hist.el by Ag Ibragimov to get results from browser’s history, however be aware that the browser-hist can sometimes fail in accessing or searching the local database files of the browsers.

(consult-omni-browser-history)

Other useful new features (video link)

Actions on new items

open search page on new items with web search

There is now the ability to go to the source search engine (like Brave, Bing, Google, …) by selecting the minibuffer input itself. Furthermore, in multi-source searches, if you select the minibuffer input itself, you get to choose a search engine (or can use a default one defined by the custom variable consult-omni-default-search-engine) to get results directly in your web browser instead of waiting for results in Emacs.

(consult-omni-brave)
(consult-omni-bing)
(consult-omni-duckduckgo-api)
(consult-omni-multi)
(setq consult-omni-default-search-engine "Perplexity")
(consult-omni-multi)

Narrowing

narrowing now works nicely

You can also use narrowing to narrow down the sources that you query. This allows using consult-omni-multi as the default starting point for all your searches and then using consult narrow down if you want to get results from a specific source.

(consult-omni-multi)
(let ((consult-async-split-style 'comma))
(consult-omni-multi))

Some Tips for Installation & Configuration (video link)

In the repo’s README, I provide several examples of configs with different levels of details and complexity that you can use. See here: Drop-in Example Configs.

Here are some more tips for setting things up:

Start with only loading a handful of sources for testing

;; Only load wikipedia source
(setq consult-omni-sources-modules-to-load (list 'consult-omni-wikipedia))

(consult-omni-sources-load-modules)

Be aware of the requirements and set-up for each source

Go over the sources you use and understand their requirements and configuration:

  • For example: consult-omni-notmuch depends on emacs notmuch package! consult-omni-numi requires numi-cli consult-omni-brave won’t work without an API key

Define new commands

Add new interactive commands as fits your work flow:

(defun consult-omni-scholar (&optional initial no-callback &rest args)
  "Interactive “multi-source acadmic literature” search."

  (interactive "P")
  (consult-omni-multi initial "🎓 " '("PubMed" "Scopus" "Consult Notes Reference Roam Nodes" "Consult Notes Zettel Roam Nodes" "gptel") no-callback args))
(defun consult-omni-autosuggest-at-point ()
  (interactive)
  (let ((input (or (thing-at-point 'url) (thing-at-point 'filename) (thing-at-point 'symbol) (thing-at-point 'sexp) (thing-at-point 'word))))
    (when (and (minibuffer-window-active-p (selected-window))
               (equal (substring input 0 1) (consult--async-split-initial nil)))
      (setq input (substring input 1)))
    (consult-omni-brave-autosuggest input)
    ))

autosuggest

Set Key bindings accordingly

For example in my setup, I use:

  • “M-” keys for changing sources, queries, …
  • “C-” keys for interaction with candidates
  • ”s-” keys for completion
(consult-omni-multi)

Desktop Integration (video link)

Furthermore if you run Emacs as a server (i.e. EmacsWiki: Emacs As Daemon), then you can use yequake to get on-demand frames anywhere on your desktop by using key shortcuts or gestures. Here are some simple examples on how to achieve that with custom functions (not using yequake).

create an omni-search tool

(defun consult-omni-demo-omni ()
  (interactive)
  (let* ((width (floor (* 0.6 (display-pixel-width))))
         (height (floor (* 0.6 (display-pixel-height))))
         (left  (floor (* 0.2 (display-pixel-width))))
         (top (floor (* 0.2 (display-pixel-height))))
         (params `((name . "demo-omni")
                   (width . ,(cons 'text-pixels width))
                   (height . ,(cons 'text-pixels height))
                   (left . ,left)
                   (top . ,top)
                   (minibuffer . t)))
         (frame (make-frame params)))
    (with-selected-frame frame
      (select-frame-set-input-focus frame)
      (consult-omni-multi))))

let’s add a minimal starting dashboard frame as well

Note that some of the code below is specific to my setup (for example turning display-line-numbers-mode off or setting fill-clolumn width, etc.

You can also take a look at enlight for simple custom dashboard buffers.

(defun consult-omni-demo-omni ()
  (interactive)
  (let* ((width (floor (* 0.8 (display-pixel-width))))
         (height (floor (* 0.8 (display-pixel-height))))
         (left  (floor (* 0.1 (display-pixel-width))))
         (top (floor (* 0.1 (display-pixel-height))))
         (params `((name . "demo-omni")
                   (width . ,(cons 'text-pixels width))
                   (height . ,(cons 'text-pixels height))
                   (left . ,left)
                   (top . ,top)
                   (alpha . (0.98 0.85))
                   (minibuffer . t)))
         (frame (make-frame params)))
    (with-selected-frame frame
      (select-frame-set-input-focus frame)
      (let* ((buffer (get-buffer-create "consult-omni-dashbaord")))
        (switch-to-buffer buffer)
        (setq-local fill-column 80)
        (display-line-numbers-mode -1)
        (erase-buffer)
        (insert "\n\n\n\n\n\n\n\n\n\n")
        (insert "\n\n\n")
        (insert (concat (propertize "What you " 'face 'font-lock-function-call-face)
                        (propertize "seek" 'face 'font-lock-variable-name-face)
                (propertize " is seeking you!" 'face 'font-lock-function-call-face)))
        (insert (concat "\n\n"
                        (propertize "preview:" 'face 'font-lock-doc-face)
                        "\s\s"
                        (propertize "C-o" 'face 'font-lock-type-face)
                        "\s\s\s\s\s\s\s\s"
                        (propertize "narrow:" 'face 'font-lock-doc-face)
                        "\s\s"
                        (propertize "M-o" 'face 'font-lock-type-face)
                        "\s\s\s\s\s\s\s\s"
                        (propertize "move:" 'face 'font-lock-doc-face)
                        "\s\s"
                        (propertize "C- j k h l" 'face 'font-lock-type-face)))
        (save-excursion
          (center-line -3))
        (recenter nil t)
        (text-scale-set 1.4)
        (setq-local cursor-type '(nil))
        (setq-local evil-normal-state-cursor '(nil))
        (setq-local evil-insert-state-cursor '(nil))
        (delete-other-windows)
        (redisplay)
        (consult-omni-multi)))))

Here is the script to call the function above from the terminal. note that we do not make a new frame because the function we evaluate will create a frame itself.

emcasclient --eval "(consult-omni-demo-omni)"

create a launcher

Here is an example for a launcher. note the (minibuffer . only) and also the (unwind-protect...) sections in the code:

(defun consult-omni-demo-launcher ()
(interactive)
(let* ((resize-mini-frames #'yequake-fit-frame-vertically)
       (vertico-count 30)
       (width (floor (* 0.8 (display-pixel-width))))
       (height (floor (* 0.8 (display-pixel-height))))
       (left  (floor (* 0.1 (display-pixel-width))))
       (top (floor (* 0.1 (display-pixel-height))))
       (params `((name . "demo-omni")
                (width . ,(cons 'text-pixels width))
                (height . ,(cons 'text-pixels height))
                (left . ,left)
                (top . ,top)
                (minibuffer . only)))
        (frame (make-frame params)))
      (with-selected-frame frame
        (select-frame-set-input-focus (selected-frame))
        (unwind-protect
            (progn (consult-omni-apps-static ".*" (propertize "" 'face 'consult-omni-path-face))
                   nil)
          (progn
            (when (frame-live-p frame) (delete-frame frame))
            nil)))))

and the script to call the function in the terminal:

emcasclient --eval "(consult-omni-demo-launcher)"

Extending Sources (video link)

Simple Example

make a new source

You can use the macro consult-omni-define-source to make new sources (i.e. define new interactive commands for a source). The simplest form would look like this:

;; Define a Demo Source
(consult-omni-define-source "Demo"
                            :type 'sync
                            :request #'consult-omni--demo-fetch-results)

request function (collects a list of candidates)

You can then define a collector function (which takes the input and returns the candidates) and add other functions for previewing or opening candidates. Here are examples that add more complexity in a step-by-step approach:

simple list of buffer
(defun consult-omni--demo-fetch-results ()
  (mapcar #'buffer-name (buffer-list)))
add input matching
(defun consult-omni--demo-fetch-results (input &rest args)
  (delq nil (mapcar (lambda (buffer) (if (string-match (format ".*%s.*" input) (buffer-name buffer)) (buffer-name buffer) nil)) (buffer-list))
  ))
add format
(defun consult-omni--demo-fetch-results (input &rest args)
  (delq nil (mapcar
             (lambda (buffer)
               (if (string-match (format ".*%s.*" input) (buffer-name buffer))
                   (let* ((name (propertize (format "%s" (buffer-name buffer)) 'face 'consult-omni-default-face))
                         (mode (propertize (format "%s" (buffer-local-value 'major-mode buffer)) 'face 'consult-omni-domain-face)))
                   (format "%s\t%s" name mode))
                 nil))
             (buffer-list))
  ))
add callback action
  • We need to add text-properties to the candidate string for looking up actions
(defun consult-omni--demo-fetch-results (input &rest args)
  (delq nil (mapcar
             (lambda (buffer)
               (if (string-match (format ".*%s.*" input) (buffer-name buffer))
                   (let* ((name (propertize (format "%s" (buffer-name buffer)) 'face 'consult-omni-default-face))
                         (mode (propertize (format "%s" (buffer-local-value 'major-mode buffer)) 'face 'consult-omni-domain-face))
                         (dir (propertize (format "%s" (buffer-local-value 'default-directory buffer)) 'face 'consult-omni-path-face)))
                   (propertize (format "%s\t%s\s\s%s" name mode dir) :source "Demo" :buffer (get-buffer buffer) :mode mode :dir dir))
                 nil))
             (buffer-list))
  ))
  • make a callback function
(defun consult-omni--demo-callback (cand)
  (if-let ((buffer (get-text-property 0 :buffer cand)))
      (switch-to-buffer buffer)))  ;;probably better to use (funcall #'consult-buffer-action buffer)
(consult-omni-define-source "Demo"
                            :type 'sync
                            :request #'consult-omni--demo-fetch-results
                            :on-callback #'consult-omni--demo-callback)
add preview
(defun consult-omni--demo-preview (cand)
  (if-let ((buffer (get-text-property 0 :buffer cand)))
    (funcall (consult--buffer-preview) 'preview buffer)))
(consult-omni-define-source "Demo"
                            :type 'sync
                            :request #'consult-omni--demo-fetch-results
                            :on-callback #'consult-omni--demo-callback
                            :on-preview #'consult-omni--demo-preview
                            :lookup #'consult-omni--lookup-function
                            :preview-key "C-o")
add callback function for new items
(defun consult-omni--demo-new (cand)
  (switch-to-buffer (get-buffer-create cand)))
(consult-omni-define-source "Demo"
                            :type 'sync
                            :request #'consult-omni--demo-fetch-results
                            :on-callback #'consult-omni--demo-callback
                            :on-preview #'consult-omni--demo-preview
                            :on-new #'consult-omni--demo-new
                            :lookup #'consult-omni--lookup-function
                            :preview-key "C-o")

More Advanced Examples

For a more general case, you can follow the steps below to make your custom sources:

Define necessary variables (e.g. API keys)

Define variables that are needed for your source (either for customization or for using in the functions,…)

Define a collector function

Similar to what we did for the demo above, go step by step and define a collector function that takes the input from the user and collects results including parsing and formatting. Note that depending on the source, you may need to use a different type (sync, dynamic, or async) for consult-omni to handle the collection correctly.

Collector functions generally do these steps:

  1. parse the input (e.g. minibuffer's content) to get search term and parameters (e.g. count, page, …)
  2. use search term and parameters to build the right target (e.g. URL parameters, local elisp variables, …). Here are some specific examples:
    • URL with parameter for API calls
    • set model and back-end for gptel
    • set filters, counts, … for elfeed
    • set correct paths for grep
  3. run elisp function to get results
    • sync source: takes input argument and returns a list of formatted candidates
    • dynamic source: takes input as well as a callback function and passes a list of formatted arriving candidates to this callback function
    • async source: takes input and returns a list of command line arguments that can be run to collect candidates. Then each line of the result is passed to a transform function for formatting before adding it to the minibuffer candidate list.

Here are some more in=depth examples to understand the differences:

understanding sync v.s. dynamic v.s. async
sync source
(let ((result))
  (setq result (mapcar #'buffer-name (buffer-list)))
result)
dynamic source

Here is an example to understand arrival of results in async processes:

sync url-retrieve Let’s query Wikipedia for "emacs"
(url-retrieve-synchronously "https://wikipedia.org/w/api.php?&action=query&format=json&list=search&formatversion=2&prop=info&inprop=url&srwhat=text&srsearch=emacs")
(let ((result)
      (url "https://wikipedia.org/w/api.php?&action=query&format=json&list=search&formatversion=2&prop=info&inprop=url&srwhat=text&srsearch=emacs"))
  (with-current-buffer (url-retrieve-synchronously url) (setq result (buffer-string)))
result)
async url-retrieve
(let ((result)
      (url "https://wikipedia.org/w/api.php?&action=query&format=json&list=search&formatversion=2&prop=info&inprop=url&srwhat=text&srsearch=emacs"))
  (url-retrieve url (lambda (&rest args) (setq result (buffer-string))))
result)
(let ((result)
      (url "https://wikipedia.org/w/api.php?&action=query&format=json&list=search&formatversion=2&prop=info&inprop=url&srwhat=text&srsearch=emacs"))
  (url-retrieve url (lambda (&rest args) (setq result (buffer-string))))
(sit-for 1)
result)
(let ((result)
      (url "https://wikipedia.org/w/api.php?&action=query&format=json&list=search&formatversion=2&prop=info&inprop=url&srwhat=text&srsearch=emacs"))
  (url-retrieve url (lambda (&rest args) (setq result (buffer-string))))
  (with-timeout (30 (message "url-retrieve timed out!")) (while (not result) (sit-for 0.05)))
result)
Let’s try to make an async collector function
(defun consult-omni--demo-fetch-results (input &rest args)
  (let ((result)
        (url (concat  "https://wikipedia.org/w/api.php?&action=query&format=json&list=search&formatversion=2&prop=info&inprop=url&srwhat=text&srsearch=" input)))
    (url-retrieve url (lambda (&rest args) (setq result (buffer-string))))
(with-timeout (30 (message "url-retrieve timed out!")) (while (not result) (sit-for 0.05)))
(list result))
)
let’s do some clean up and formatting
(defun consult-omni--demo-fetch-results (input &rest args)
  (let ((result)
        (url (concat  "https://wikipedia.org/w/api.php?&action=query&format=json&list=search&formatversion=2&prop=info&inprop=url&srwhat=text&srsearch=" input)))
    (url-retrieve url (lambda (&rest args)
                        (goto-char url-http-end-of-headers)
                        (let* ((data (json-parse-buffer :object-type 'plist))
                               (raw-results (map-nested-elt data '(:query :search))))
                               (setq result (mapcar (lambda (item )
                                                      (let* ((title (propertize (plist-get item :title) 'face 'consult-omni-engine-title-face))
                                                             (snippet (propertize (substring (plist-get item :snippet) 0 50) 'face 'consult-omni-snippet-face))
                                                             (target-url (propertize (concat "https://wikipedia.org/" (string-replace " " "_" title)) 'face 'consult-omni-path-face)))
                                                        (propertize (concat title "\t" snippet "\t" target-url) :source "Demo" :title title :snippet snippet :url target-url)))
                                                    raw-results)))))
(with-timeout (30 (message "url-retrieve timed out!")) (while (not result) (sit-for 0.05)))
result))
But that is not exactly async! Instead of running a timer and waiting for results, we would want the callback function inside url-retrieve to add the arriving items to the minibuffer candidate list when they arrive.
(cl-defun consult-omni--demo-fetch-results (input &rest args &key callback &allow-other-keys)
(let ((url (concat  "https://wikipedia.org/w/api.php?&action=query&format=json&list=search&formatversion=2&prop=info&inprop=url&srwhat=text&srsearch=" input)))
    (url-retrieve url (lambda (&rest args)
                        (goto-char url-http-end-of-headers)
                        (let* ((data (json-parse-buffer :object-type 'plist))
                               (raw-results (map-nested-elt data '(:query :search)))
                               (formatted-results (mapcar (lambda (item )
                                                      (let* ((title (propertize (plist-get item :title) 'face 'consult-omni-engine-title-face))
                                                             (snippet (propertize (substring (plist-get item :snippet) 0 50) 'face 'consult-omni-snippet-face))
                                                             (target-url (propertize (concat "https://wikipedia.org/" (string-replace " " "_" title)) 'face 'consult-omni-path-face)))
                                                        (propertize (concat title "\t" snippet "\t" target-url) :source "Demo" :title title :snippet snippet :url target-url)))
                                                    raw-results)))
(funcall callback formatted-results))))))
(consult-omni-define-source "Demo"
                            :type 'dynamic
                            :request #'consult-omni--demo-fetch-results
                            :on-callback #'consult-omni--demo-callback
                            :on-preview #'consult-omni--demo-preview
                            :on-new #'consult-omni--demo-new
                            :lookup #'consult-omni--lookup-function
                            :preview-key "C-o")

Note that in the case of url-retrieve, the results arrive as one chunk, but for other cases, that may not be the case.

async source
running shell command synchronously
(shell-command-to-string "ls ./projects")
using call-process
(let ((buffer (generate-new-buffer "consult-omni-demo"))
      (result))
(with-current-buffer buffer
  (call-process "ls" nil (current-buffer) nil "./projects")
  (setq result (buffer-string)))
result)
using start-process
(let ((buffer (generate-new-buffer "consult-omni-demo"))
      (result))
(start-process "consult-omni-demo" buffer "ls" "./projects")
(with-current-buffer buffer
(setq result (buffer-string)))
result)
(let ((buffer (generate-new-buffer "consult-omni-demo"))
      (result))
(start-process "consult-omni-demo" buffer "ls" "./projects")
(with-current-buffer buffer
  (with-timeout (30 (message "process timed out!")) (while (or (not result) (string-empty-p result)) (sit-for 0.05)
(setq result (buffer-string)))))
result)
using async type
(defun consult-omni--demo-fetch-results (input &rest args)
(list "ls" "./projects"))
(defun consult-omni--demo-fetch-results (input &rest args)
(list "ls" "./projects"))
(defun consult-omni--demo-transform (files &optional query)
  (delq nil (mapcar (lambda (file) (if (string-match (format ".*%s.*" query) file)
                                       (let ((title (propertize file  'face 'consult-omni-domain-face))
                                             (path (propertize (concat default-directory "/" file) 'face 'consult-omni-path-face)))
                                       (propertize (concat file "\t" path) :source "Demo"))
                                     nil))
                    files)))
(consult-omni-define-source "Demo"
                            :type 'async
                            :request #'consult-omni--demo-fetch-results
                            :transform #'consult-omni--demo-transform
                            :on-callback #'consult-omni--demo-callback
                            :on-preview #'consult-omni--demo-preview
                            :on-new #'consult-omni--demo-new
                            :lookup #'consult-omni--lookup-function
                            :preview-key "C-o")
(let ((result)
        (url (concat  "https://wikipedia.org/w/api.php?&action=query&format=json&list=search&formatversion=2&prop=info&inprop=url&srwhat=text&srsearch=" "emacs")))
    (url-retrieve url (lambda (&rest args)
                        (goto-char url-http-end-of-headers)
                        (let* ((data (json-parse-buffer :object-type 'plist))
                               (raw-results (map-nested-elt data '(:query :search))))
                               (setq result (mapcar (lambda (item )
                                                      (let* ((title (plist-get item :title))
                                                             (snippet (plist-get item :snippet))
                                                             (target-url (concat "https://wikipedia.org/" (string-replace " " "_" title))))
                                                        (propertize (concat title "\t" snippet) :source "Demo" :title title :snippet snippet :url target-url)))
                                                    raw-results)))))

(sit-for 1)
result)
(map-nested-elt my:test '(:query))
(consult-omni--make-url-string consult-omni-wikipedia-api-url  '(("action" . "query")
                         ("format" . "json")
                         ("list" . "search")
                         ("formatversion" . "2")
                         ("prop" . "info")
                         ("inprop" . "url")
                         ("srwhat" . "text")
                         ("srsearch" . "emacs")))

Define helper functions (for example for formatting candidates)

Define functions for formatting candidates, callbacks, previews, …

Need Further Help?

Look at consult-omni-define-source, documentation.

(consult-omni-define-source "name"
                            :type 'sync 'dynamic 'async
                            :request
                            :transform
                            :filter
                            :on-setup
                            :on-preview
                            :on-return
                            :on-exit
                            :state
                            :on-callback
                            :on-new
                            :require-match
                            :interactive 'dynamic 'static 'both
                            :lookup
                            :group
                            :narrow-char ?b
                            :category
                            :search-hist
                            :select-hist
                            :face
                            :annotate
                            :enabled
                            :sort
                            :predicate
                            :preview-key
                            :docstring)

In addition, in consult-omni.org (as the single source of truth in literate programming) provides a good example on how to go about defining new sources.

Clone this wiki locally