-
-
Notifications
You must be signed in to change notification settings - Fork 4
YouTube Tutorial
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:
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 candidatesResults 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
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
-
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"))
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:
(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-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))
(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))
(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)))
(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))
(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))
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
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!
Finding Local Files with async commands (video link)
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, …
(consult-grep)
(consult-omni-grep)
(consult-omni-grep "consult -- :g file")
(consult-omni-ripgrep-all "Hello World")
(consult-omni-ripgrep-all "consult")
(consult-omni-ripgrep-all "quantum sensors -- :dir ./examples/pdfs/")
We also have sources for other command-line programs such as find, fd, …
(consult-omni-mdfind "consult")
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-omni-consult-notes-org "macos")
(consult-omni-consult-notes-zettel-roam-nodes "emacs")
(consult-omni-consult-notes-denote "example")
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")
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.
(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 tasks can be made by selecting the minibuffer input itself (calls org-capture
)
(consult-omni-org-agenda "new item")
Calculators (video link)
There is now a source to get results from Emacs’ built-in calculator which uses the calc-eval
function.
(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"))
(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))\"")
(consult-omni-calc "=50degF -- :c degC")
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:
- counsel-linux-apps in swiper by Oleh Krehel
- counsel-osx-app by Boris Buliga
- app-launcher by SebastienWae
- openwith by garberw
(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)
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)
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.
(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)
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)
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:
;; Only load wikipedia source
(setq consult-omni-sources-modules-to-load (list 'consult-omni-wikipedia))
(consult-omni-sources-load-modules)
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-cliconsult-omni-brave
won’t work without an API key
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
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).
(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))))
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)"
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)
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)
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:
(defun consult-omni--demo-fetch-results ()
(mapcar #'buffer-name (buffer-list)))
(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))
))
(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))
))
- 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)
(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")
(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")
For a more general case, you can follow the steps below to make your custom sources:
Define variables that are needed for your source (either for customization or for using in the functions,…)
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:
- parse the input (e.g.
minibuffer's content
) to get search term and parameters (e.g. count, page, …) - 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
- run elisp function to get results
-
sync source
: takes input argument and returns a list of formatted candidates -
dynamic source
: takes inputas well as a callback function
and passes a list of formattedarriving
candidates to thiscallback 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:
(let ((result))
(setq result (mapcar #'buffer-name (buffer-list)))
result)
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)
(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)
(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))
)
(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))
(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.
running shell command synchronously(shell-command-to-string "ls ./projects")
(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)
(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)
(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 functions for formatting candidates, callbacks, previews, …
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.