You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
;;; Group
(defgroupconsult-webnil"Consulting search engines and AI assistants":group'convenience:group'minibuffer:group'consult:group'web:group'search:prefix"consult-web-")
customization variables
;;; Customization Variables
(defcustomconsult-web-sources-modules-to-load (list)
"List of source modules/features to load.This variable is a list of symbols;each symbol being a source featue (e.g. consult-web-brave)":type '(repeat:tag"list of source modules/features to load" symbol))
(defcustomconsult-web-default-browse-function#'browse-url"consult-web default function when selecting a link":type '(choice (function:tag"(Default) Browse URL"#'browse-url)
(function:tag"Custom Function")))
(defcustomconsult-web-alternate-browse-function#'eww-browse-url"consult-web default function when selecting a link":type '(choice (function:tag"(Default) EWW"#'eww-browse-url)
(function:tag"Custom Function")))
(defcustomconsult-web-default-preview-function#'eww-browse-url"consult-web default function when previewing a link":type '(choice (function:tag"(Default) EWW"#'eww-browse-url)
(function:tag"Custom Function")))
(defcustomconsult-web-show-previewnil"Should`consult-web' show previews?This turns previews on/off globally for all consult-web sources.":type'boolean)
(defcustomconsult-web-preview-key consult-preview-key
"Preview key for consult-web.This is similar to `consult-preview-key' but explicitly For consult-web.":type '(choice (const :tag"Any Key" Any)
(List :tag"Debounced"
(const :Debounce)
(Float :tag"Seconds"0.1)
(const Any))
(const :tag"No Preview"nil)
(Key :tag"Key")
(repeat:tag"List Of Keys" Key)))
(defcustomconsult-web-default-count5"Number Of search results to retrieve.":type'integer)
(defcustomconsult-web-default-page0"Offset of search results to retrieve.If this is set to N, the first N “pages”(or other first N entities, items for example,depending On the source search engine capabilities)of the search results are omitted and the rest are shown.":type'integer)
(defcustomconsult-web-default-timeout30"Default timeout in seconds for`consult-web--url-retrieve-synchronously.":type'integer)
(defcustomconsult-web-lognil"Default timeout in seconds for`consult-web--url-retrieve-synchronously.":type'boolean)
(defcustomconsult-web-log-buffer-name" *consult-web-log*""String for consult-web-log buffer name":type'string)
(defcustomconsult-web-log-levelnil"How to make logs for consult-web requests?This can be set to nil, info or debugnil: Does not log anythinginfo: Logs urls and response's http headerdebug: Logs urls and the entire http response.When non-nil, information is logged to `consult-web-log-buffer-name'.":type '(choice
(const :tag"No Logging"nil)
(const :tag"Just HTTP Header" info)
(const :tag"Full Response" debug)))
(defcustomconsult-web-group-by:domain"What field to use to group the results in the minibuffer?By default it is set to :domain. but can be any of: :url group by URL :domain group by the domain of the URL :source group by source":type '(radio (const :tag"url path":url)
(const :tag"domain of url path":domain)
(const :tag"name of the search engine or source":source)
(const :tag"custom other field (constant)":any)
(const :tag"do not group"nil)))
(defcustomconsult-web-multi-sources (list)
"List of sources used by `consult-web-multi'.This variable is a list of strings, each string being name of a source.The source name has to be a key from `consult-web-sources-alist'.Sources can be made with the convinient macro `consult-web-define-source'.":type '(choice (repeat:tag"list of source names" string)))
(defcustomconsult-web-omni-sources (list)
"List of sources used by `consult-web-omni'.This variable is a list of strings or symbols; - strings can be name of a source, a key from `consult-web-sources-alist',which can be made with the convinient macro `consult-web-define-source'or by using `consult-web--make-source-from-consult-source'. - symbols can be other consult sources(see `consult-buffer-sources' for example.)":type '(repeat:tag"list of source names" (choice (string symbol))))
(defcustomconsult-web-dynamic-omni-sources (list)
"List of sources used by `consult-web-dynamic-omni'.This variable is a list of strings, each string being name of a source.The source name has to be a key from `consult-web-sources-alist'.Sources can be made with the convinient macro `consult-web-define-source'or by using `consult-web--make-source-from-consult-source'.":type '(choice (repeat:tag"list of source names" string)))
(defcustomconsult-web-scholar-sources (list)
"List of sources used by `consult-web-scholar'.This variable is a list of strings, each string being name of a source.The source name has to be a key from `consult-web-sources-alist'.Sources can be made with the convinient macro `consult-web-define-source'or by using `consult-web--make-source-from-consult-source'.":type '(choice (repeat:tag"list of source names" string)))
(defcustomconsult-web-dynamic-sources (list)
"List of sources used by `consult-web-dynamic'.This variable is a list of strings, each string being name of a source.The source name has to be a key from `consult-web-sources-alist'.Sources can be made with the convinient macro `consult-web-define-source'or by using `consult-web--make-source-from-consult-source'.":type '(choice (repeat:tag"list of source names" string)))
(defcustomconsult-web-highlight-matchest"Should `consult-web' highlight search queries in the minibuffer?":type'boolean)
(defcustomconsult-web-default-interactive-command#'consult-web-multi"Which command should `consult-web' call?":type '(choice (function:tag"(Default) Search with dynamic completion (i.e. `consult-web-dynamic')"#'consult-web-dynamic)
(function:tag"Search without dynamic completion (i.e. `consult-web-multi')"#'consult-web-multi)
(function:tag"Search academic research literature (i.e. `consult-web-scholar')"#'consult-web-scholar)
(function:tag"Custom function")))
(defcustomconsult-web-retrieve-backend#'consult-web-url-retrieve-sync"Which command should `consult-web' use for url requests?":type '(choice (function:tag"(Default) url-retrieve backend"#'consult-web-url-retrieve-sync)
(function:tag"Emacs Request Backend"#'consult-web-request)))
(defcustomconsult-web-default-autosuggest-commandnil"Which command should `consult-web' use for auto suggestion on search input?":type '(choice (function:tag"(default) use brave autosuggestion (i.e. `consult-web-dynamic-brave-autosuggest')"#'consult-web-dynamic-brave-autosuggest)
(function:tag"use google autosuggestion (i.e. `consult-web-dynamic-google-autosuggest')"#'consult-web-dynamic-google-autosuggest)
(function:tag"custom function")))
(defcustomconsult-web-dynamic-input-debounce consult-async-input-debounce
"Input debounce for dynamic commands.The dynamic collection process is started only whenthere has not been new input for consult-web-dynamic-input-debounce seconds. This is similarto `consult-async-input-debounce' butspecifically for consult-web dynamic commands.By default inherits from `consult-async-input-debounce'.":type '(float:tag"delay in seconds"))
(defcustomconsult-web-dynamic-input-throttle consult-async-input-throttle
"Input throttle for dynamic commands.The dynamic collection process is started only every`consult-web-dynamic-input-throttle' seconds. this is similarto `consult-async-input-throttle' but specifically forconsult-web dynamic commands.By default inherits from `consult-async-input-throttle'.":type '(float:tag"delay in seconds"))
(defcustomconsult-web-dynamic-refresh-delay consult-async-refresh-delay
"refreshing delay of the completion ui for dynamic commands.The completion UI is only updated every`consult-web-dynamic-refresh-delay' seconds.This is similar to `consult-async-refresh-delay' but specificallyfor consult-web dynamic commands.By default inherits from `consult-async-refresh-delay'. ":type '(float:tag"delay in seconds"))
other variables
;;; Other Variables
(defvarconsult-web-sources--all-modules-list (list)
"List of all source modules.")
(defvarconsult-web-category'consult-web"Category symbol for the `consult-web' package.")
(defvarconsult-web-scholar-category'consult-web-scholar"Category symbol for the `consult-web' package.")
(defvarconsult-web--selection-history (list)
"History variable that keeps selected items.")
(defvarconsult-web--search-history (list)
"History variable that keeps search terms.")
(defvarconsult-web-sources-alist (list)
"Alist of search engine or ai assistant sources.This is an alist mapping source names to source property lists.This alist is used to define how to process data forma source (e.g. format data) or find what commands to run onselecting candidates from a source, etc.You can use the convinient macro `consult-web-define-source'or the command `consult-web--make-source-from-consult-source'to add to this alist.")
(defvarconsult-web--override-group-bynil"Override grouping in `consult-group' based on user input.This is used in dynamic collection to change grouping.")
(defvarconsult-web--current-sources (list)
"List of sources of the candidates in the current minibuffer.This is used for defining narrow functions(e.g. `consult-web--dynamic-narrow-function'."
)
define faces
;;; Faces
(deffaceconsult-web-default-face
`((t:inherit'default))
"Default face used for listing items in minibuffer.")
(deffaceconsult-web-prompt-face
`((t:inherit'font-lock-variable-use-face))
"The face used for prompts in minibuffer.")
(deffaceconsult-web-engine-source-face
`((t:inherit'font-lock-variable-use-face))
"The face for search engine source types in minibuffer.")
(deffaceconsult-web-ai-source-face
`((t:inherit'font-lock-operator-face))
"The face for AI assistant source types in minibuffer.")
(deffaceconsult-web-files-source-face
`((t:inherit'font-lock-number-face))
"The face for file source types in minibuffer.")
(deffaceconsult-web-notes-source-face
`((t:inherit'font-lock-warning-face))
"The face for notes source types in minibuffer.")
(deffaceconsult-web-scholar-source-face
`((t:inherit'font-lock-function-call-face))
"The face for academic literature source types in minibuffer.")
(deffaceconsult-web-domain-face
`((t:inherit'font-lock-variable-face))
"The face for domain annotation in minibuffer.")
(deffaceconsult-web-path-face
`((t:inherit'font-lock-warning-face))
"The face for path annotation in minibuffer.")
(deffaceconsult-web-source-face
`((t:inherit'font-lock-comment-face))
"The face for source annotation in minibuffer.")
(deffaceconsult-web-highlight-match-face
`((t:inherit'consult-highlight-match))
"Highlight match face for `consult-web'.")
(deffaceconsult-web-preview-match-face
`((t:inherit'consult-preview-match))
"Preview match face in `consult-web' preview buffers.")
Define Backend Functions
general utility
formatting strings
fix string length
set string width
;;; Bakcend Functions
(defunconsult-web--set-string-width (stringwidth&optionalprepend)
"Sets the STRING width to a fixed value, WIDTH.If the STRING is longer than WIDTH, it truncates the STRING and adds ellipsis, \"...\". if the STRING is shorter,it adds whitespace to the STRING.If PREPEND is non-nil, it truncates or adds whitespace from the beginning of STRING, instead of the end."
(let* ((string (format"%s" string))
(w (string-width string)))
(when (< w width)
(if prepend
(setq string (format"%s%s" (make-string (- width w) ?\s) (substring string)))
(setq string (format"%s%s" (substring string) (make-string (- width w) ?\s)))))
(when (> w width)
(if prepend
(setq string (format"...%s" (substring string (- w (- width 3)) w)))
(setq string (format"%s..." (substring string 0 (- width (+ w 3)))))))
string))
justify left
(defunconsult-web--justify-left (stringprefixmaxwidth)
"Sets the width of STRING+PREFIX justified from left.It uses `consult-web--set-string-width' and sets the width of the concatenate of STRING+PREFIX(e.g. `(concat PREFIX STRING)`) within MAXWIDTH.This is used for aligning marginalia info in minibuffer."
(let ((s (string-width string))
(w (string-width prefix)))
(if (> maxwidth w)
(consult-web--set-string-width string (- maxwidth w) t)
string
)
))
highlight match with text-properties
(defunconsult-web--highlight-match (regexpstrignore-case)
"Highlights REGEXP in STR.If a regular expression contains capturing groups, only these are highlighted.If no capturing groups are used, highlight the whole match.Case is ignored, if ignore-case is non-nil.(This is adapted from `consult--highlight-regexps'.)"
(let ((i 0))
(while (and (let ((case-fold-search ignore-case))
(string-match regexp str i))
(> (match-end0) i))
(let ((m (match-data)))
(setq i (cadr m)
m (or (cddr m) m))
(while m
(when (car m)
(add-face-text-property (car m) (cadr m)
'consult-web-highlight-match-facenil str)
)
(setq m (cddr m))))))
str)
highlight match with overlay
(defunconsult-web--overlay-match (match-strbufferignore-case)
"Highlights MATCH-STR in BUFFER using an overlay.If IGNORE-CASE is non-nil, it uses case-insensitive match.This is provided for convinience,if needed in formating candidates or preview buffers."
(with-current-buffer (or (get-buffer buffer) (current-buffer))
(remove-overlays (point-min) (point-max) 'consult-web-overlayt)
(goto-char (point-min))
(let ((case-fold-search ignore-case)
(consult-web-overlays (list)))
(while (search-forward match-str nilt)
(when-let* ((m (match-data))
(beg (car m))
(end (cadr m))
(overlay (make-overlay beg end))
)
(overlay-put overlay 'consult-web-overlayt)
(overlay-put overlay 'face'consult-web-highlight-match-face)
)))))
(defunconsult-web-overlays-toggle (&optionalbuffer)
"Toggles overlay highlights in consult-web view/preview buffers."
(interactive)
(let ((buffer (or buffer (current-buffer))))
(with-current-buffer buffer
(dolist (o (overlays-in (point-min) (point-max)))
(when (overlay-get o 'consult-web-overlay)
(if (and (overlay-get o 'face) (eq (overlay-get o 'face) 'consult-web-highlight-match-face))
(overlay-put o 'facenil)
(overlay-put o 'face'consult-web-highlight-match-face))
)
))))
make url with params
(defunconsult-web--make-url-string (urlparams&optionalignore-keys)
"Adds key value pairs in PARAMS to URL as “&key=val”.PARMAS should be an alist with keys and values to add to the URL.Does not add keys for the key in IGNORE-KEYS list."
(let* ((url (if (equal (substring-no-properties url -1nil) "?")
url
(concat url "?")))
(list (append (list url) (cl-loopfor (key . value) in params
collect
(unless (member key ignore-keys)
(format"&%s=%s" key value))))))
(mapconcat#'identitylist)))
properties to plist
(defunconsult-web-properties-to-plist (string&optionalignore-keys)
"Returns a plist of the text properties of STRING.Ommits keys in IGNORE-KEYs."
(let ((properties (text-properties-at0 string))
(pl nil))
(cl-loopfor k in properties
when (keywordp k)
collect (unless (member k ignore-keys) (push (list k (plist-get properties k)) pl)))
(apply#'append pl)))
hashtable-to-plist
(defunconsult-web-hashtable-to-plist (hashtable&optionalignore-keys)
"Converts a HASHTABLE to a plist.Ommits keys in IGNORE-KEYS."
(let ((pl nil))
(maphash
(lambda (kv)
(unless (member k ignore-keys)
(push (list k v) pl)))
hashtable)
(apply#'append pl)))
expand function in variable
(defunconsult-web-expand-variable-function (var)
"Call the function if VAR is a function"
(if (functionp var)
(funcall var)
var))
url retrieve backend
log
(defunconsult-web--log (string)
"Logs the response from `consult-web-url-retrieve-sync' in `consult-web-log-buffer-name'."
(with-current-buffer (get-buffer-create consult-web-log-buffer-name)
(goto-char (point-min))
(insert"**********************************************\n")
(goto-char (point-min))
(insert (format-time-string"%F - %T%n" (current-time)))
(insert string)
(insert"\n")
(goto-char (point-min))
(insert"\n\n**********************************************\n")))
parse http response
(defunconsult-web--parse-http-response (&optionalbuffer)
"Parse the first header line such as \"HTTP/1.1 200 OK\"."
(with-current-buffer (or buffer (current-buffer))
(save-excursion
(goto-char (point-min))
(when (re-search-forward"\\=[ \t\n]*HTTP/\\(?1:[0-9\\.]+\\) +\\(?2:[0-9]+\\)" url-http-end-of-headers t)
`(:http-version ,(match-string1) :code ,(string-to-number (match-string2)))))))
url retrieve synchronously
(cl-defunconsult-web--url-retrieve-synchronously (url&restsettings &keyparamsheadersparserdatatypeerrorencodingtimeout)
"Retrieves URL synchronously.Passes all the arguments to url-retriev and fetches the results.PARAMS are parameters added to the base url using `consult-web--make-url-string'.HEADERS are headers passed to `url-request-extra-headers'.DATA are http request data passed to `url-request-data'.TYPE is the http request type (e.g. “GET”, “POST”)ERRORENCODINGTIMEOUTPARSER is a function that is executed in the url-retrieve response buffer and the results are returned s the output of this function."
(let* ((url-request-method type)
(url-request-extra-headers headers)
(url-request-data data)
(url-with-params (consult-web--make-url-string url params))
(response-data nil)
(buffer (if timeout
(with-timeout
(timeout
(setf response-data (plist-put response-data :status'timeout))
nil)
(url-retrieve-synchronously url-with-params t))
(url-retrieve-synchronously url-with-params t))
))
(when buffer
(with-current-buffer buffer
(when consult-web-log-level
(save-excursion
(goto-char (point-min))
(cond
((eq consult-web-log-level 'info)
(consult-web--log (format"URL: %s\nRESPONSE: %s" url (buffer-substring (point-min) (pos-eol)))))
((eq consult-web-log-level 'debug)
(consult-web--log (format"URL: %s\n\nRESPONSE-HEADER:\n%s\n\nRESPONSE-BODY: %s\n" url (buffer-substring (point-min) url-http-end-of-headers) (buffer-substring url-http-end-of-headers (point-max))))))
))
(let* ((response-header (buffer-substring (point-min) url-http-end-of-headers))
(response-content (buffer-substring (+ url-http-end-of-headers 1) (point-max)))
(response-status (consult-web--parse-http-response))
)
(delete-region (point-min) (+ url-http-end-of-headers 1))
(when-let ((parsed-data (funcall parser)))
(setf response-data (plist-put response-data :data parsed-data))
)
(when response-header
(setf response-data (plist-put response-data :header response-header)))
(when response-status
(setf response-data (plist-put response-data :status response-status)))
(when response-content
(setf response-data (plist-put response-data :content response-content)))
)))
response-data
))
get the response data
(defunconsult-web--url-response-body (response-data)
"Extracts the response body from `url-retrieve'."
(plist-get response-data :data))
url retrieve sync
(cl-defunconsult-web-url-retrieve-sync (url &keyparamsheadersparserdatatypeerrorencodingtimeout)
"Retrieves URL synchronously.Passes all the arguments to `consult-web--url-retrieve-synchronously' and in trun to `url-retrieve' fetches the results.PARAMS are parameters added to the base url using `consult-web--make-url-string'.HEADERS are headers passed to `url-request-extra-headers'.DATA are http request data passed to `url-request-data'.TYPE is the http request type (e.g. “GET”, “POST”)ERRORENCODINGTIMEOUTPARSER is a function that is executed in the url-retrieve response buffer and the results are returned s the output of this function."
(let ((type (or type "GET"))
(encoding (or encoding 'utf8))
(timeout (or timeout consult-web-default-timeout))
)
(consult-web--url-response-body
(consult-web--url-retrieve-synchronously url :params params :headers headers :parser parser :data data :type type :errorerror:encoding encoding :timeout timeout))))
(cl-defunconsult-web-request (url&restargs &keyparamsheadersdataparserplaceholdererror &allow-other-keys)
"Convinient wrapper for `request'.Passes all the arguments to request and fetches the results *synchronously*.Refer to `request' documents for details."
(unless (functionp'request)
(error"Request backend not available. Either install the package “emacs-request” or change the custom variable `consult-web-retrieve-backend'"))
(let (candidates)
(request
url
:synct:params params
:headers headers
:parser parser
:error (orerror#'consult-web--error-handler)
:data data
:encoding'utf-8:success (cl-function (lambda (&keydata &allow-other-keys)
(setq candidates data))))
candidates))
consult-web backend
thing at point
(defunconsult-web-dynamic--split-thingatpt (thing&optionalsplit-initial)
"Return THING at point.If SPLIT-INITIAL is non-nil, use `consult--async-split-initial' to format the string."
(when-let (str (thing-at-point thing t))
(if split-initial
(consult--async-split-initial str)
str)))
format a single candidate (a.k.a. a hashtable)
simple (non-searchable)
(defunconsult-web--table-to-formatted-candidate-simple (table&optionalface&restargs)
"Returns a formatted candidate for TABLE.TABLE is a hashtable that stores metadata for a consult-web candidate.Returns a cons set of `key . value`;The key is the value of :title key in the TABLE.The value is all the (key value) pairs in the table as a plist."
(let* ((query (gethash:query table))
(title (format"%s" (gethash:title table)))
(title-str (consult-web--set-string-width title (floor (* (frame-width) 0.4))))
(pl (consult-web-hashtable-to-plist table))
)
(apply#'propertize title-str pl)
))
with metadata (searchable)
(defunconsult-web--table-to-formatted-candidate-searchable (table&optionalface&restargs)
"Formats a consult-web candidate.TABLE is a hashtable with metadata for the candidate as (key value) pairs.Returns a string (from :title field in TABLE) with text-properties that conatinall the key value pairs in the table."
(let* ((pl (consult-web-hashtable-to-plist table))
(title (format"%s" (gethash:title table)))
(url (gethash:url table))
(urlobj (if url (url-generic-parse-url url)))
(domain (if (url-p urlobj) (url-domain urlobj)))
(domain (if (stringp domain) (propertize domain 'face'consult-web-domain-face)))
(path (if (url-p urlobj) (url-filename urlobj)))
(path (if (stringp path) (propertize path 'face'consult-web-path-face)))
(source (gethash:source table))
(source (if (stringpsource) (propertizesource'face'consult-web-source-face)))
(query (gethash:query table))
(snippet (gethash:snippet table))
(snippet (if (and snippet (stringp snippet) (> (string-width snippet) 25)) (concat (substring snippet 022) "...") snippet))
(match-str (if (stringp query) (consult--split-escaped (car (consult--command-split query))) nil))
(title-str (consult-web--set-string-width title (floor (* (frame-width) 0.4))))
(title-str (propertize title-str 'face (or face 'consult-web-default-face)))
(extra-args (consult-web-hashtable-to-plist table '(:title:url:search-url:query:source:snippet)))
(str (concat title-str (if domain (concat"\t" domain (if path path))) (if snippet (format"\s\s%s" snippet)) (ifsource (concat"\t"source)) (if extra-args (format"\s\s%s" extra-args))))
(str (apply#'propertize str pl))
)
(if consult-web-highlight-matches
(cond
((listp match-str)
(mapcar (lambda (match) (setq str (consult-web--highlight-match match str t))) match-str))
((stringp match-str)
(setq str (consult-web--highlight-match match-str str t)))))
str))
format all candidates in a list (a.k.a. a list of hashtables)
(defunconsult-web--format-candidates-list (list&optionalformat-funcface)
"Format a LIST of candidates.LIST is a list of hashtables, each representing one candidate.FORMAT-FUNC is a function that is used to format candidates if provided.Returns a list of formatted candidates using either FORMAT-FUNC or otherwise uses default formating for the source retrieved from `consult-web-sources-alist'."
(mapcar (lambda (table)
(let* ((source (gethash:source table))
(format-func (or format-func
(plist-get (cdr (assocsource consult-web-sources-alist)) :format-func)
#'consult-web--table-to-formatted-candidate-searchable))
(face (or face
(plist-get (cdr (assocsource consult-web-sources-alist)) :face)
'consult-web-default-face))
)
(funcall format-func table face))) list))
annotate candidates
(defunconsult-web--annotate-function (cand)
"Annotates each candidate in the minibuffer.This is provided for convinience to be passed as `:annotate' key when making sources using `consult-web-define-source'.For more info on annotation refer to `consult' manual, particularly 'consult--read' and `consult--read-annotate' documentation."
(let* ((url (get-text-property0:url cand))
(urlobj (if url (url-generic-parse-url url)))
(domain (if (url-p urlobj) (url-domain urlobj) nil))
(path (if (url-p urlobj) (url-filename urlobj) nil))
(url-str nil)
(source (get-text-property0:source cand))
(snippet (get-text-property0:snippet cand))
(extra-args (consult-web-properties-to-plist cand '(:url:source:title:search-url:query:snippet:model:backend))))
(if domain (setq domain (propertize domain 'face'consult-web-domain-face)))
(if path (setq path (propertize path 'face'consult-web-path-face)))
(if (and snippet (stringp snippet) (> (string-width snippet) 25)) (setq snippet (concat (substring snippet 022) "...")))
(setq url-str (concat (if domain domain) (if path path)))
(unless (string-empty-p url-str) (setq url url-str))
(when (and url (> (string-width url) (floor (* (frame-width) 0.4))))
(setq url (consult-web--set-string-width url (floor (* (frame-width) 0.4)))))
(concat (if url (format"\s%s" url)) (ifsource (format"\t%s"source)) (if snippet (format"\s\s%s" snippet)) (if extra-args (format"\t%s" extra-args)))
))
group candidates based on a keyword
(defunconsult-web--group-function (group-bycandtransform)
"Group candidates by GROUP-BY keyword.This is passed as GROUP to `consult--read' on candidates and is used to define the grouping for CAND. "
(let* ((group-by (or consult-web--override-group-by group-by consult-web-group-by))
(group-by (if (not (keywordp group-by)) (intern (concat":" (format"%s" group-by))) group-by))
(name (or (if group-by (get-text-property0 group-by cand) "N/A"))))
(cond
((equal group-by :domain)
(when-let* ((url (get-text-property0:url cand))
(urlobj (if url (url-generic-parse-url url) nil))
(domain (if (url-p urlobj) (url-domain urlobj))))
(setq name domain))))
(if transform (substring cand) name)))
narrowing function (for multi-source commands)
single-source narrow
(defunconsult-web--narrow-function (source)
"Make a narrowing (key . value) pair for the SOURCE string.key is the first character, and value is the entire source STRING.For example when “wikipedia” is passed as a source, it returns (w . “wikipedia”)."
`(,(string-to-charsource) .,source)
)
dynamic multi source narrow
(defunconsult-web--dynamic-narrow-function ()
"Dynamically makes a list of (key . value) for all the sources in the current list of candidates using `consult-web--narrow-function'."
(let* ((narrow-pred (lambda (cand)
(if-let ((source (get-text-property0:source (car cand))))
(equal (string-to-charsource) consult--narrow)
)))
(narrow-keys (mapcar (lambda (c) (cons (string-to-char c) c))
consult-web--current-sources)))
`(:Predicate,narrow-pred:keys,narrow-keys)
))
lookup function
(defunconsult-web--lookup-function ()
"Lookup function for `consult-web' minibuffer candidates.This is passed as LOOKUP to `consult--read' on candidates and is used to format the output when a candidate is selected."
(lambda (selcands&restargs)
(let* ((info (or (car (member sel cands)) ""))
(title (get-text-property0:title info))
(url (get-text-property0:url info))
)
(apply#'propertize (or title url "nil") (or (text-properties-at0 info) (list)))
)))
preview
(defunconsult-web--default-url-preview (cand)
"Default function to use for previewing CAND."
(when-let* ((url (cond
((listp cand)
(or (get-text-property0:url (car cand)) (car cand)))
(t
(or (get-text-property0:url cand) cand))))
(buff (funcall consult-web-default-preview-function url)))
(funcall (consult--buffer-preview) 'preview
buff
)
)
)
state
make state
(cl-defunconsult-web--make-state-function (&restargs &keysetuppreviewexitreturn &allow-other-keys)
"Convinient wrapper for `consult-web' to make custom state functions.This can be passed as STATE to `consult--read' on candidates and isused to define actions when setting up, previewing or selecting acandidate. Refer to `consult--read' documentation for more details."
(lambda (actioncand&restargs)
(if cand
(pcase action
('setup
(funcall setup cand))
('preview
(funcall preview cand))
('exit
(funcall exit cand))
('return
(funcall return cand))
)))
)
dynamic state function
(defunconsult-web--dynamic-state-function ()
"State function for `consult-web' minibuffer candidates.This is passed as STATE to `consult--read' on candidates and is usedto define actions that happen when a candidate is previewed orselected.The preview and retrun actions are retrieve from `consult-web-sources-alist'."
(lambda (actioncand&restargs)
(if cand
(let* ((source (get-text-property0:source cand))
(state (plist-get (cdr (assocsource consult-web-sources-alist)) :state))
(preview (plist-get (cdr (assocsource consult-web-sources-alist)) :on-preview))
(return (plist-get (cdr (assocsource consult-web-sources-alist)) :on-return)))
(if state
(funcall state action cand args)
(pcase action
('preview
(if preview (funcall preview cand) (consult-web--default-url-preview cand)))
('return
(if return (funcall return cand) cand))
))
)))
)
callback
(defunconsult-web--default-callback (cand)
"Default CALLBACK for CAND.The CALLBACK is called when a CAND is selected.When making consult-web sources, if a CALLBACK is not provided, thisCALLBACK is used as a fall back."
(if-let ((url (get-text-property0:url cand)))
(funcall consult-web-default-browse-function url)))
(defunconsult-web--extract-opt-pair (optoptsignore-opts)
"Extracts a pair of (OPT . value) from a list OPTS.values is the next element after OPT in OPTS.Excludes keys in IGNORE_OPTS.This i suseful for example to extract key value pairsfrom command-line options in alist of strings"
(let* ((key (cond
((string-match"--.*$" opt)
(intern (concat":" (replace-regexp-in-string"--""" opt))))
((string-match":.*$" opt)
(intern opt))
(tnil)))
(val (or (nth (+ (cl-position opt opts :test'equal) 1) opts) "nil"))
(val (cond
((string-match"--.*$\\|:.*$" val)
nil)
((stringp val)
(intern val)))))
(when (and key (not (member opt ignore-opts)))
(cons key val))
))
(defunconsult-web-dynamic--list-from-sources (sources&optionalformat-funcface&restargs)
"Builds ARGS from user input and collects candidates from allSOURCES."
(pcase-let* ((`(,input,args) (consult-web--split-args args)))
(cond
((and (listp sources))
(apply'append
(cl-loopforsourcein sources
collect
(consult-web--format-candidates-list
(applysource input args)))))
((functionp sources)
(consult-web--format-candidates-list
(apply sources input args) format-func face))
(t
(error"%s is not a consult-web-source!")))))
dynamic collection of results from source(s)
(defunconsult-web-dynamic--collection (sources&optionalformat-funcface&restargs)
"This is a wrapper using `consult--dynamic-collection' and`consult-web-dynamic--list-from-sources'."
(consult--dynamic-collection (apply-partially#'consult-web-dynamic--list-from-sources sources format-func face args)))
(defunconsult-web--source-name (source-name&optionalsuffix)
"Returns a symbol for SOURCE-NAME variable.The variable is consult-web--source-%s (%s=source-name).Adds suffix to the name if provided."
(intern (format"consult-web--source-%s" (concat (replace-regexp-in-string"""-" (downcase source-name)) (if suffix (downcase suffix) nil)))))
make generic docstring for varibale of source
(defunconsult-web--source-generate-docstring (source-name)
"Makes a generic documentation string for SOURCE-NAME.This is used in `consult-web-define-source' macro to make genericdocstrings for variables."
(format"consult-web source for %s.\n\nThis function was defined by the macro `consult-web-define-source'."
(capitalize source-name)))
make a function for source
make a function symbol for source
(defunconsult-web--func-name (source-name&optionalprefixsuffix)
"Make a function symbol for interactive command for SOURCE-NAME.Adds prefix and suffix if non-nil."
(intern (concat"consult-web-" (if prefix prefix) (replace-regexp-in-string"""-" (downcase source-name)) (if suffix suffix))))
make generic doctring for function of source
(defunconsult-web--func-generate-docstring (source-name&optionaldynamic)
"Make a generic documentaion string for an interactive command.This is used to make docstring for function made by `consult-web-define-source'."
(concat"consult-web's " (if dynamic "dynamic ") (format"interactive command to search %s."
(capitalize source-name))))
make a consult–read source list
(defunconsult-web--make-source-list (source-namerequestformatannotatefacenarrow-charstatepreview-keycategorylookupselection-historyinputargs)
"Internal function to make a source for `consult--multi'.Do not use this function directly, use `consult-web-define-source' macroinstead."
`(:name,source-name
,(if (and annotate face) :face)
,(if (and annotate face) (cond
((eq face t)
'consult-web-default-face)
(t face)))
:narrow,narrow-char:state ,(or state #'consult-web--dynamic-state-function)
:category ,(or category 'consult-web)
:history,selection-history:add-history (delqnil
(cl-remove-duplicates
(append (mapcar (lambda (thing) (consult-web-dynamic--split-thingatpt thing))
(list'number'word'sexp'symbol'url'filename'sentence'line)) (list isearch-string))))
:items ,(funcall#'consult-web--format-candidates-list (funcall request input args) format)
:annotate ,(cond
((and annotate (functionp annotate))
annotate)
((eq annotate t)
#'consult-web--annotate-function)
(tnil))
:lookup (if (and lookup (functionp lookup))
lookup
(consult-web--lookup-function))
:preview-key ,(and consult-web-show-preview (or preview-key consult-web-preview-key))
:sortt
)
)
(defunconsult-web--call-dynamic-command (initialno-callbackargssource-namerequestcategoryfacelookupsearch-history-varselection-history-varpreview-key)
"Internal function to make dynamic `consult--read' command.Do not use this function directly, use `consult-web-define-source' macro instead."
(let* ((consult-async-refresh-delay consult-web-dynamic-refresh-delay)
(consult-async-input-throttle consult-web-dynamic-input-throttle)
(consult-async-input-debounce consult-web-dynamic-input-debounce)
(prompt (concat"[" (propertize (format"%s" (consult-web--func-name source-name "dynamic-")) 'face'consult-web-prompt-face) "]"" Search: "))
(collection (consult-web-dynamic--collection (list
request) nil face nil args))
(selected (consult-web-dynamic--internal prompt collection initial category lookup search-history-var preview-key))
(source (get-text-property0:source selected))
(title (get-text-property0:title selected)))
(add-to-history selection-history-var title)
(unless no-callback
(funcall (plist-get (cdr (assocsource consult-web-sources-alist)) :on-callback) selected)
)
selected
))
macro to add a new source
;;; Macros;;;###autoload
(cl-defmacroconsult-web-define-source (source-name&restargs &keyrequestformaton-previewon-returnstateon-callbacklookupdynamicgroupnarrow-charcategorysearch-historyselection-historyfaceannotatepreview-keydocstring &allow-other-keys)
"Macro to make a consult-web-source for SOURCE-NAME.\* Makes- source for `consult-web-multi' and/or `consult-web-dynamic'- interactive commands (static or dynamic) for single source- adds a new row to to `consult-web-sources-alist' with all themetadata as a property list.\* Keyword ArgumentsBrief Description:========== ========== =================================================Keyword Type Explanation========== ========== =================================================REQUEST (function) Fetch results from sourceFORMAT (function) Formats a single candidateON-PREVIEW (function) Preview action in `consult--read'ON-RETURN (function) Return action in `consult--read'STATE (function) STATE passed to `consult--read' (bypasses ON-PREVIEW and ON-RETURN)ON-CALLBACK (function) Function called on selected candidateDYNAMIC (boolean/'both) Whether to make dynamic or non-dynamic commandsGROUP (function) Passed as GROUP to `consult--read'ANNOTATE (function) Passed as ANNOTATE to `consult--read'NARROW-CHAR (char) Ppassed as NARROW to `consult-read'CATEGORY (symbol) Passed as CATEGORY to `consult--read'HISTORY (symbol) Passed as HISTORY to `consult--read'FACE (face) Passed as FACE to `consult--read-multi'PREVIEW-KEY (key) Passed as PREVIEW-KEY to `consult--read'DOCSTRING (string) DOCSTRING for the variable created for SOURCE-NAME===================================================================Detailed Decription:REQUEST is a function that takes at least one string argument, INPUT, which isthe search term, and potentially other arguments. Keyword arguments(e.g. by using `cl-defun') can be passed to this function fromminibuffer prompt using`consult-async' commandline arguments.Examples can be found in the wiki pages of the repo or in“consult-web-sources.el” on the repository webpage or :URL `https://github.com/armindarvish/consult-web/blob/main/consult-web-sources.el'FORMAT takes a hashtable and returns a cons with a propertized string as key and plist property as value. For an example see`consult-web--table-to-formatted-candidate-simple' or `consult-web--table-to-formatted-candidate-searchable'.ON-PREVIEW is used as a function to call on the candidate, when a preview isrequested. It takes one required argument, the candidate. For an example,see `consult-web-default-preview-function'.ON-RETURN is used as a function to call on the candidate, when thecandidate is selected. This is passed to consult built-in statefunction machinery.Note that the output of this function will be returned in the consult-webcommands. In consult-web, ON-CALLBACK is used to call further actions onthis returned value. This allows to separate the return value from thecommands and the action that i run on the selected candidates. Thereforefor most use cases, ON-RETURN can just be `#'identity' to getthe candidate back as it is. But if some transformation is needed,ON-RETURN can be used to transform the selected candidate.STATE is a function that takes no argument and returns a function forconsult--read STATE argument. For an example see`consult-web--dynamic-state-function' that builds state function based on ON-PREVIEW and ON-RETURN. If STATE is non-nil, instead of usingON-PREVIEW and ON-RETURN to make a state function, STATE will be directlyused in consult--read.ON-CALLBACK is the function that is called with one required input argument, the selected candidate. For example, see `consult-web--default-callback'that opens the url of the candidate in the default browser.Other examples can be found in the wiki pages of the repo or in“consult-web-sources.el” on the repository webpage or :URL `https://github.com/armindarvish/consult-web/blob/main/consult-web-sources.el'DYNAMIC can be a bollean (nil or t) or the symbol 'both.If nil only \*non-dynamic\* interactive commands are created in this macro.if t only \*dynamic\* interactive commands are created in this macro.If something else (e.g. 'both) \*Both\* dynamic and non-dynamic commandsare created.GROUP, ANNOTATE, NARROW-CHAR, CATEGORY, and PREVIEW-KEY are passed to`consult--read' or `consult--multi'. See consult's Documentaion for more details.FACE is passed to `consult-multi'. See consult's Documentaion for moredetails.DOCSTRING is used as docstring for the variable consult-web--source-%svariable that this macro creates for %s=SOURCE-NAME."
(if (symbolp source-name) (setq source-name (eval source-name)))
`(progn;; make a function that creates a consult--read source for consult-web-multi
(defun ,(consult-web--source-name source-name "-list") (input &rest args)
,(or docstring (consult-web--source-generate-docstring source-name))
(consult-web--make-source-list ,source-name,request,format,annotate,face,narrow-char,state,preview-key,category,lookup,selection-history input args)
)
;; make a static interactive command consult-web-%s (%s=source-name)
(unless (eq,dynamict)
(defun ,(consult-web--func-name source-name) (&optional input no-callback &rest args)
,(or docstring (consult-web--func-generate-docstring source-name))
(interactive"P")
(consult-web--call-static-command input no-callback args ,request,format,face,state,source-name,category,lookup,selection-history,annotate,preview-key,on-callback)
))
;; make a dynamic interactive command consult-web-dynamic-%s (%s=source-name)
(if,dynamic
(defun ,(consult-web--func-name source-name "dynamic-") (&optional initial no-callback &rest args)
,(or docstring (consult-web--func-generate-docstring source-name t))
(interactive"P")
(consult-web--call-dynamic-command initial no-callback args ,source-name,request,category,face,lookup,search-history,selection-history,preview-key)
))
;; make a variable called consult-web--source-%s (%s=source-name)
(defvar ,(consult-web--source-name source-name) (list))
(setq ,(consult-web--source-name source-name) (cons,source-name
(list:name,source-name:source (consult-web--source-name ,source-name"-list")
:face,face:request-func,request:format-func (or,format#'consult-web--table-to-formatted-candidate-searchable)
:on-preview (or,on-preview#'consult-web--default-url-preview)
:on-return (or,on-return#'identity)
:on-callback (or,on-callback#'consult-web--default-callback)
:state,state:group,group:annotate,annotate:narrow-char,narrow-char:preview-key,preview-key:category (or ',category'consult-web)
:search-history,search-history:selection-history,selection-history:interactive-static (and (functionp (consult-web--func-name ,source-name)) (consult-web--func-name ,source-name))
:interactive-dynamic (and (functionp (consult-web--func-name ,source-name"dynamic-")) (consult-web--func-name ,source-name"dynamic-"))
)))
;; add consult-web--source-%s (%s=source-name) to consult-web-sources-alist
(add-to-list'consult-web-sources-alist ,(consult-web--source-name source-name))
,source-name))
make fetch function for consult sources
;;;###autoload
(cl-defmacroconsult-web--make-fetch-function (source&restargs &keysource-namedocstring &allow-other-keys)
"Make a function for fetching result based on SOURCE.SOURCE is a source for consult (e.g. a plist that is passedto consult--red). See `consult-buffer-sources' for examples.SOURCE-NAME is a string name for SOURCEDOCSTRING is the docstring for the function that is returned."
(let* ((source (if (plistp source) source (evalsource)))
(source-name (substring-no-properties (plist-getsource:name))))
`(progn;; make a function that creates a consult--read source for consult-web-multi
(defun ,(consult-web--source-name source-name "-fetch-results") (input &rest args)
,(or docstring (consult-web--source-generate-docstring source-name))
(let ((results (funcall (plist-get ',source:items)))
(source (substring-no-properties (plist-get ',source:name))))
(cl-loopfor a in results
if (string-match (concat".*" input ".*") a)
collect
(let* ((table (make-hash-table:test'equal))
(title a))
(puthash:title title
table)
(puthash:urlnil
table)
(puthash:query input
table)
(puthash:source (substring-no-propertiessource)
table)
table)))))))
make source for consult-web from consult source
(cl-defunconsult-web--make-source-from-consult-source (consult-source&restargs &keyrequestformaton-previewon-returnstateon-callbackgroupnarrow-charcategorydynamicsearch-historyselection-historyfaceannotatepreview-keydocstring &allow-other-keys)
"Makes a consult-web source from a consult source, CONSULT-SOURCE.All othe input variables are passed to `consult-web-define-source'macro. See `consult-web-define-source' for more details"
(if (boundp consult-source)
(let* ((source (eval consult-source))
(source (if (plistp source) source (evalsource)))
(name (and (plistp source) (substring-no-properties (plist-getsource:name))))
(preview-key (or preview-key (and (plistp source) (plist-getsource:preview-key))))
(narrow-char (or narrow-char (and (plistp source) (plist-getsource:narrow))))
(narrow-char (if (listp narrow-char) (car narrow-char)))
(face (if (member:face args) face (and (plistp source) (plist-getsource:face))))
(state (if (member:state args) state (and (plistp source) (plist-getsource:state))))
(annotate (if (member:annotate args) annotate (and (plistp source) (plist-getsource:annotate))))
(preview-key (or preview-key (and (plistp source) (plist-getsource:preview-key)) consult-web-preview-key))
(group (or group (and (plistp source)(plist-getsource:group))))
(category (or category (and (plistp source) (plist-getsource:category)) 'consult-web)))
(eval (macroexpand
`(consult-web-define-source ,name:docstring,docstring:annotate ',annotate:narrow-char,narrow-char:category ',category:request (or,request (consult-web--make-fetch-function ,source))
:format ',format:face ',face:search-history ',search-history:selection-history ',selection-history:on-preview ',on-preview:on-return ',on-return:on-callback ',on-callback:preview-key,preview-key:group ',group:dynamic ',dynamic))))
(display-warning:warning (format"Consult-web: %s is not available. Make sure `consult-notes' is loaded and set up properly" consult-source)))
)
Frontend Interactive commands
consult-web-multi
interactive
;;; Frontend Interactive Commands
(defunconsult-web-multi (&optionalinputsourcesno-callback&restargs)
"Interactive “multi-source search”INPUT is the initial search query.Searches all sources in SOURCES. if SOURCES is nil`consult-web-multi-sources' is used.If NO-CALLBACK is t, only the selected candidate is returned withoutany callback action."
(interactive"P")
(let* ((input (or input
(and consult-web-default-autosuggest-command (funcall-interactively consult-web-default-autosuggest-command))
(consult-web--read-search-string)))
(sources (or sources consult-web-multi-sources))
(sources (removenil (mapcar (lambda (source) (plist-get (cdr (assocsource consult-web-sources-alist)) :source)) sources)))
(candidates (consult--slow-operation "The web is a big place, allow me a few seconds..." (mapcar (lambda (func) (funcall func input args)) sources)))
(selected (consult--multi candidates
:require-matchnil:prompt (concat"[" (propertize"consult-web-multi"'face'consult-web-prompt-face) "]"" Search: ")
:sortt:annotatenil:category'consult-web:history'consult-web--selection-history
))
(source (get-text-property0:source (car selected)))
)
(unless no-callback
(funcall (plist-get (cdr (assocsource consult-web-sources-alist)) :on-callback) (car selected)))
(car selected)
))
consult-web-dynamic
interactive
(defunconsult-web-dynamic (&optionalinitialsourcesno-callback&restargs)
"Interactive “multi-source dynamic search”INITIAL is the initial search prompt in minibuffer.Searches all sources in SOURCES. if SOURCES is nil`consult-web-dynamic-sources' is used.If NO-CALLBACK is t, only the selected candidate is returned withoutany callback action.This is an interactive command that fetches results form all the sources in `consult-web-dynamic-sources' with dynamic completion meaning that the search term can be dynamically updated by the userand the results are fetched as the user types.Additional commandline arguments can be passed in the minibufferentry similar to `consult-grep' by typing `--` followed by arguments.For example the user can enter:`#consult-web -- -g domain'this will run a search on all the `consult-web-dynamic-sources' forthe term “consult-web” and then groups the results by the “domainof the URL” of the results.Built-in arguments include: -g, --groups, or :groups for grouping (see `consult-web-group-by' and `consult-web--override-group-by'. for more info) -n, --count, or :count is passed as the value for COUNT to any source in `consult-web-dynamic-sources'.If the request function for the source takes a keyword argument for COUNT (e.g. :count value), this is used as the value otherwise it is ignored. -p, --page, or :page is passed as the value for PAGE to any source in `consult-web-dynamic-sources'.If the request function for the source takes a keyword argument for page (e.g. :page value), this is used as the value otherwise it is ignored.Custom arguments can be passed by using “--ARG value” (or “:ARG value”).For example, if the user types the following in the minibuffer:“#how to do web search in emacs? -- --model gpt-4”The term “how to do web search in emacs?” is passed as the searchterm and the “gpt-4” as a keyword argument for :model to everysource in `consult-web-dynamic-sources'. If any request function ofthe sources takes a keyword argument for :model, “gpt-4” isused then.Once the results are fetched, narrowing down can be done by using “#” after the serach term similar to `consult-grep'.For example:“#consult-web#github.com”uses “consult-web” as the search term, and then narrows the choices toresults that have “github.com” in them.For more examples, refer to the official documentation of the repo here:URL `https://github.com/armindarvish/consult-web'.For more details on consult--async functionalities, see `consult-grep'and the official manual of consult, here: URL `https://github.com/minad/consult'."
(interactive"P")
(let* ((consult-async-refresh-delay consult-web-dynamic-refresh-delay)
(consult-async-input-throttle consult-web-dynamic-input-throttle)
(consult-async-input-debounce consult-web-dynamic-input-debounce)
(sources (or sources consult-web-dynamic-sources))
(request-sources (removenil (mapcar (lambda (source)
(plist-get (cdr (assocsource consult-web-sources-alist)) :request-func)) sources)))
(prompt (concat"[" (propertize"consult-web-dynamic"'face'consult-web-prompt-face) "]"" Search: "))
(collection (consult-web-dynamic--collection request-sources nilnil args))
(selected (consult-web-dynamic--internal prompt collection initial 'consult-webnil'consult-web--search-history))
(source (get-text-property0:source selected)))
(unless no-callback
(funcall (plist-get (cdr (assocsource consult-web-sources-alist)) :on-callback) selected))
selected
))
consult-web-scholar
interactive
(defunconsult-web-scholar (&optionalinitialsourcesno-callback&restargs)
"Interactive “multi-source acadmic literature” searchINITIAL is the initial search prompt in minibuffer.Searches all sources in SOURCES. if SOURCES is nil`consult-web-scholar-sources' is used.If NO-CALLBACK is t, only the selected candidate is returned withoutany callback action.This is similar to `consult-web-dynamic', but runs the search on academic literature sources in `consult-web-scholar-sources'.Refer to `consult-web-dynamic' for more details."
(interactive"P")
(let* ((consult-async-refresh-delay consult-web-dynamic-refresh-delay)
(consult-async-input-throttle consult-web-dynamic-input-throttle)
(consult-async-input-debounce consult-web-dynamic-input-debounce)
(sources (or sources consult-web-scholar-sources))
(request-sources (removenil (mapcar (lambda (source)
(plist-get (cdr (assocsource consult-web-sources-alist)) :request-func)) sources)))
(collection (consult-web-dynamic--collection request-sources nilnil args))
(selected (consult-web-dynamic--internal (concat"[" (propertize"consult-web-scholar"'face'consult-web-prompt-face) "]"" Search: ") collection initial 'consult-web-scholarnil'consult-web--search-history))
(source (get-text-property0:source selected)))
(unless no-callback
(funcall (plist-get (cdr (assocsource consult-web-sources-alist)) :on-callback) selected)
)
selected
))
consult-web-omni
concatentate all the sources
(defunconsult-web-omni-get-sources (&optionalinput)
"Returns a flat list of candidates for input.Passes input to sources in `consult-web-omni-sources' and returns aflattend list of sources."
(apply#'append (mapcar (lambda (item) (cond
((stringp item)
(if-let ((func (plist-get (cdr (assoc item consult-web-sources-alist)) :source)))
(list (funcall func input))))
((symbolp item)
(eval item))))
consult-web-omni-sources)))
interactive
(defunconsult-web-omni (&optionalinputsourcesno-callback&restargs)
"Interactive “multi-source omni” search.This is for using combination of web and local sources defined in`consult-web-omni-sources'.Passes INPUT to SOURCES and returns results in minibuffer.If SOURCES is nil, `consult-web-omni-sources' is used.If NO-CALLBACK is t, only the selected candidate is returned withoutany callback action."
(interactive)
(let* ((input (or input (consult-web-dynamic-brave-autosuggest input) ""))
(consult-web-default-count 10)
(sources (or sources (consult-web-omni-get-sources input)))
(selected (consult--multi sources
:prompt"Select: ":history'consult-web--omni-history:add-history (list (thing-at-point'wordt)
"")
:sortt:initial input
))
(source (get-text-property0:source (car selected))))
(unless no-callback
(cond
((andsource (membersource (mapcar#'car consult-web-sources-alist)))
(funcall (plist-get (cdr (assocsource consult-web-sources-alist)) :on-callback) (car selected)))
((and (bufferp (car selected)) (buffer-live-p (car selected)))
(consult--buffer-action (car selected)))
(tnil))
)
(car selected)
))
consult-web-dynamic-omni
interactive
(defunconsult-web-dynamic-omni (&optionalinitialsourcesno-callback&restargs)
"Interactive “multi-source and dynamic omni search”This is for using combination of web and local sources defined in`consult-web-dynamic-omni-sources'.INITIAL is the initial search prompt in minibuffer.Searches all sources in SOURCES. if SOURCES is nil`consult-web-dynamic-omni-sources' is used.If NO-CALLBACK is t, only the selected candidate is returned withoutany callback action.This is a dynamic command and additional arguments can be passed inthe minibuffer. See `consult-web-dynamic' for more details."
(interactive"P")
(let* ((consult-async-refresh-delay consult-web-dynamic-refresh-delay)
(consult-async-input-throttle consult-web-dynamic-input-throttle)
(consult-async-input-debounce consult-web-dynamic-input-debounce)
(sources (or sources consult-web-dynamic-omni-sources))
(request-sources (removenil (mapcar (lambda (source)
(plist-get (cdr (assocsource consult-web-sources-alist)) :request-func)) sources)))
(prompt (concat"[" (propertize"consult-web-dynamic-omni"'face'consult-web-prompt-face) "]"" Search: "))
(collection (consult-web-dynamic--collection request-sources nilnil args))
(selected (consult-web-dynamic--internal prompt collection initial 'consult-webnil'consult-web--search-history))
(source (get-text-property0:source selected)))
(unless no-callback
(funcall (plist-get (cdr (assocsource consult-web-sources-alist)) :on-callback) selected))
selected
))
consult-web
(defunconsult-web (&restargs)
"Wrapper function that calls the function in `consult-web-default-interactive-command'.This is for conviniece to call the favorite consult-web interactive command."
(interactive)
(apply consult-web-default-interactive-command args))
Provide and Footer
;;; provide `consult-web' module
(provide'consult-web)
;;; consult-web.el ends here
(defunconsult-web-sources--load-module (symbol)
"Loads feature SYMBOL"
(require symbol))
(defunconsult-web-sources-load-modules (&optionallist)
"Loads the LIST of symbols.If list is nil, loads `consult-web-sources-modules-to-load'and if that is nil as well, loads `consult-web-sources--all-modules-list'."
(mapcar#'consult-web-sources--load-module (orlist consult-web-sources-modules-to-load consult-web-sources--all-modules-list)))
load the sources
(consult-web-sources-load-modules)
provide and footer
;;; provide `consult-web-sources' module
(provide'consult-web-sources)
;;; consult-web-sources.el ends here
;;; provide `consult-web-chatgpt' module
(provide'consult-web-chatgpt)
(add-to-list'consult-web-sources-modules-to-load'consult-web-chatgpt)
;;; consult-web-chatgpt.el ends here
(defvarconsult-web-bing-search-api-url"https://api.bing.microsoft.com/v7.0/search")
(defcustomconsult-web-bing-search-api-keynil"Key for Bing (Microsoft Azure) search APISee URL `https://www.microsoft.com/en-us/bing/apis/bing-web-search-api' and URL `https://learn.microsoft.com/en-us/bing/search-apis/bing-web-search/search-the-web' for details":group'consult-web:type '(choice (const :tag"API Key" string)
(function:tag"Custom Function")))
(cl-defunconsult-web--bing-fetch-results (input&restargs &keycountpage &allow-other-keys)
"Fetches search results for INPUT from Bing web search api.COUNT is passed as count in query parameters.(* PAGE COUNT) is passed as offset in query paramters.Refer to URL `https://programmablesearchengine.google.com/about/' and `https://developers.google.com/custom-search/' for more info."
(let* ((count (or (and (integerp count) count)
(and count (string-to-number (format"%s" count)))
consult-web-default-count))
(page (or (and (integerp page) page)
(and page (string-to-number (format"%s" page)))
consult-web-default-count))
(count (max count 1))
(page (* page count))
(params `(("q". ,(replace-regexp-in-string"""+" input))
("count". ,(format"%s" count))
("offset". ,(format"%s" page))))
(headers `(("Ocp-Apim-Subscription-Key". ,(consult-web-expand-variable-function consult-web-bing-search-api-key)))))
(funcall consult-web-retrieve-backend
consult-web-bing-search-api-url
:params params
:headers headers
:parser
(lambda ()
(goto-char (point-min))
(let* ((results (json-parse-buffer))
(webpages (gethash"webPages" results))
(search-url (gethash"webSearchUrl" webpages))
(items (gethash"value" webpages)))
(cl-loopfor a across items
collect
(let ((table (make-hash-table:test'equal))
(title (gethash"name" a))
(url (gethash"url" a))
(snippet (gethash"snippet" a)))
(puthash:url url
table)
(puthash:search-url search-url
table)
(puthash:title title
table)
(puthash:source"Bing"
table)
(puthash:query input
table)
(puthash:snippet snippet
table)
table
)
))
))))
(consult-web-define-source "Bing":narrow-char?m:face'consult-web-engine-source-face:request#'consult-web--bing-fetch-results:preview-key consult-web-preview-key
:search-history'consult-web--search-history:selection-history'consult-web--selection-history:dynamic'both
)
provide and footer
;;; provide `consult-web-bing' module
(provide'consult-web-bing)
(add-to-list'consult-web-sources-modules-to-load'consult-web-bing)
;;; consult-web-bing.el ends here
;;; provide `consult-web-brave' module
(provide'consult-web-brave)
(add-to-list'consult-web-sources-modules-to-load'consult-web-brave)
;;; consult-web-brave.el ends here
;;; provide `consult-web-brave-autosuggest' module
(provide'consult-web-brave-autosuggest)
(add-to-list'consult-web-sources-modules-to-load'consult-web-brave-autosuggest)
;;; consult-web-brave-autosuggest.el ends here
(defunconsult-web--line-multi-preview (cand)
"Preview function for consult-web-line-multi."
(let* ((marker (car (get-text-property0:marker cand)))
(query (get-text-property0:query cand)))
(consult--jump marker)
))
define source
(consult-web-define-source "Consult Line Multi":category'consult-location:narrow-char?L:face'consult-web-files-source-face:request#'consult-web--line-multi-fetch-results:format#'consult-web-dynamic--line-multi-format-candidate:preview-key consult-preview-key
:search-history'consult-web--search-history:selection-history'consult-web--selection-history:on-preview#'consult-web--line-multi-preview:on-return#'identity:on-callback#'consult-web--line-multi-preview:dynamic'both
)
provide and footer
;;; provide `consult-web-line-multi' module
(provide'consult-web-line-multi)
(add-to-list'consult-web-sources-modules-to-load'consult-web-line-multi)
;;; consult-web-line-multi.el ends here
(defunconsult-web--consult-buffer-preview (cand)
"Preview function for `consult-web--buffer'."
(if cand
(let* ((title (get-text-property0:title cand)))
(when-let ((buff (get-buffer title)))
(consult--buffer-action buff))
)))
consult-buffer
(cl-loopforsourcein consult-buffer-sources
do (if (symbolpsource) (consult-web--make-source-from-consult-source source:category'consult-web:on-preview#'consult-web--consult-buffer-preview:on-return#'identity:on-callback#'consult--buffer-action:search-history'consult-web--search-history:selection-history'consult-web--selection-history:dynamic'both:preview-key'consult-preview-key
)))
provide and footer
;;; provide `consult-web-buffer' module
(provide'consult-web-buffer)
(add-to-list'consult-web-sources-modules-to-load'consult-web-buffer)
;;; consult-web-buffer.el ends here
(when consult-notes-org-roam-mode
(cl-loopforsourcein '(consult-notes-org-roam--refs consult-notes-org-roam--nodes)
do (consult-web--make-source-from-consult-source source:category'file:face'consult-web-notes-source-face:search-history'consult-web--search-history:selection-history'consult-web--selection-history:on-preview#'consult-web--org-roam-note-preview:on-return#'identity:on-callback#'consult-web--org-roam-note-callback:preview-key'consult-preview-key:dynamic'both)))
provide and footer
;;; provide `consult-web-notes' module
(provide'consult-web-notes)
(add-to-list'consult-web-sources-modules-to-load'consult-web-notes)
;;; consult-web-notes.el ends here
;;; provide `consult-web-duckduckgo' module
(provide'consult-web-duckduckgo)
(add-to-list'consult-web-sources-modules-to-load'consult-web-duckduckgo)
;;; consult-web-duckduckgo.el ends here
(defunconsult-web--elfeed-search-buffer ()
"Get or create buffer for `consult-web-elfeed'"
(get-buffer-create (or consult-web-elfeed-search-buffer-name "*consult-web-elfeed-search*")))
(defunconsult-web--elfeed-search (inputentries)
"Convert elfeed search tnries to hashtables for `consult-web-elfeed'.Returns a list of hashtables, each presenting one elfeed feed."
(cl-loopfor entry in entries
collect (let* ((table (make-hash-table:test'equal))
(title (elfeed-entry-title entry))
(url (elfeed-entry-link entry))
(date (format-time-string"%Y-%m-%d %H:%M" (elfeed-entry-date entry)))
(id (elfeed-entry-id entry))
(tags (elfeed-entry-tags entry))
)
(puthash:title title
table)
(puthash:url url
table)
(puthash:date date
table)
(puthash:tags tags
table)
(puthash:id id
table)
(puthash:source"elfeed"
table)
(puthash:query input
table)
table)))
(cl-defunconsult-web--elfeed-fetch-results (input&restargs &keyfilter &allow-other-keys)
"Return entries matching INPUT in elfeed database.uses INPUT as filter ro find entries in elfeed databse.if FILTER is non-nil, it is used as additional filter parameters."
(cl-letf* (((symbol-function#'elfeed-search-buffer) #'consult-web--elfeed-search-buffer))
(let* ((input (if consult-web-elfeed-default-filter
(concat input "" consult-web-elfeed-default-filter)
input))
(new-filter (if (member:filter args)
(concat input "" (format"%s" filter))
input)))
(setq elfeed-search-filter new-filter)
(elfeed-search-update :force)
(with-current-buffer (consult-web--elfeed-search-buffer)
(elfeed-search-mode)
(save-mark-and-excursion
(goto-char (point-min))
(mark-whole-buffer)
(consult-web--elfeed-search input (elfeed-search-selected))
)))))
(defunconsult-web--elfeed-preview (cand)
"Shows a preview buffer of CAND for `consult-web-elfeed'.Uses `elfeed-show-entry'."
(let* ((id (cond ((listp cand)
(get-text-property0:id (car cand)))
(t
(get-text-property0:id cand))))
(entry (elfeed-db-get-entry id))
(buff (get-buffer-create (elfeed-show--buffer-name entry))))
(with-current-buffer buff
(elfeed-show-mode)
(setq elfeed-show-entry entry)
(elfeed-show-refresh))
(funcall (consult--buffer-preview) 'preview
buff
)))
(consult-web-define-source "elfeed":narrow-char?e:face'elfeed-search-unread-title-face:request#'consult-web--elfeed-fetch-results:format#'consult-web-dynamic--elfeed-format-candidate:on-preview#'consult-web--elfeed-preview:on-return#'identity:on-callback#'consult-web--elfeed-preview:preview-key consult-web-preview-key
:search-history'consult-web--search-history:selection-history'consult-web--selection-history:dynamic'both
)
provide and footer
;;; provide `consult-web-elfeed' module
(provide'consult-web-elfeed)
(add-to-list'consult-web-sources-modules-to-load'consult-web-elfeed)
;;; consult-web-elfeed.el ends here
(defvarconsult-web-google-search-url"https://www.google.com/search")
(defvarconsult-web-google-customsearch-api-url"https://www.googleapis.com/customsearch/v1")
(defcustomconsult-web-google-customsearch-keynil"Key for Google custom search APISee URL `https://developers.google.com/custom-search/' and URL `https://developers.google.com/custom-search/v1/introduction' for details":group'consult-web:type '(choice (const :tag"API Key" string)
(function:tag"Custom Function")))
(defcustomconsult-web-google-customsearch-cxnil"CX for Google custom search APISee URL `https://developers.google.com/custom-search/' and URL `https://developers.google.com/custom-search/v1/introduction' for details":group'consult-web:type '(choice (const :tag"CX String" string)
(function:tag"Custom Function")))
(cl-defunconsult-web--google-fetch-results (input&restargs &keycountpagefilter &allow-other-keys)
"Fetches search results for INPUT from “Google custom search” service.COUNT is passed as num in query parameters.(* PAGE COUNT) is passed as start in query paramters.Refer to URL `https://programmablesearchengine.google.com/about/' and `https://developers.google.com/custom-search/' for more info."
(let* ((count (or (and (integerp count) count)
(and count (string-to-number (format"%s" count)))
consult-web-default-count))
(page (or (and (integerp page) page)
(and page (string-to-number (format"%s" page)))
consult-web-default-page))
(filter (or (and (integerp filter) filter)
(and filter (string-to-number (format"%s" filter)))
1))
(filter (if (member filter '(01)) filter 1))
(count (min count 10))
(page (+ (* page count) 1))
(params `(("q". ,(replace-regexp-in-string"""+" input))
("key". ,(consult-web-expand-variable-function consult-web-google-customsearch-key))
("cx". ,(consult-web-expand-variable-function consult-web-google-customsearch-cx))
("gl"."en")
("filter". ,(format"%s" filter))
("num". ,(format"%s" count))
("start". ,(format"%s" page))))
(headers '(("Accept"."application/json")
("Accept-Encoding"."gzip")
("User-Agent"."consult-web (gzip)"))))
(funcall consult-web-retrieve-backend
consult-web-google-customsearch-api-url
:params params
:headers headers
:parser
(lambda ()
(goto-char (point-min))
(let* ((results (gethash"items" (json-parse-buffer)))
(items (mapcar (lambda (item) `(:url ,(format"%s" (gethash"link" item)) :title ,(format"%s" (gethash"title" item)) :snippet ,(string-trim (format"%s" (gethash"snippet" item))))) results)))
(cl-loopfor a in items
collect
(let ((table (make-hash-table:test'equal)))
(puthash:url
(plist-get a :url) table)
(puthash:search-url (consult-web--make-url-string consult-web-google-search-url params '("key""cx""gl"))
table)
(puthash:title
(plist-get a :title) table)
(puthash:source"Google"
table)
(puthash:query input
table)
(puthash:snippet (plist-get a :snippet) table)
table
)
))))))
(consult-web-define-source "Google":narrow-char?g:face'consult-web-engine-source-face:request#'consult-web--google-fetch-results:preview-key consult-web-preview-key
:search-history'consult-web--search-history:selection-history'consult-web--selection-history:dynamic'both
)
provide and footer
;;; provide `consult-web-google' module
(provide'consult-web-google)
(add-to-list'consult-web-sources-modules-to-load'consult-web-google)
;;; consult-web-google.el ends here
;;; provide `consult-web-google-autosuggest' module
(provide'consult-web-google-autosuggest)
(add-to-list'consult-web-sources-modules-to-load'consult-web-google-autosuggest)
;;; consult-web-google-autosuggest.el ends here
;;; Customization Variables
(defcustomconsult-web-gptel-buffer-name"*consult-web-gptel*""Name for consult-web-gptel buffer.":type '(choice (:tag"A string for buffer name" string)
(:tag"A custom function taking prompt (and other args) as input and returning buffer name string" function)))
(cl-defunconsult-web--gptel-fetch-results (input&restargs &keybackendmodelstream &allow-other-keys)
"Makes cnaidate with INPUT as placeholder for `consult-web-gptel'.This makes a placeholder string “ask gptel: %s” %s=INPUT withmetadata MODEL and BACKEND as text properties, so it can be send to`gptel'."
(unless (featurep'gptel)
(error"consult-web: gptel is not available. Make sure to install and load `gptel'."))
(let* ((table (make-hash-table:test'equal))
(backend (if backend (format"%s" backend) nil))
(backend (and backend (car (seq-filter (lambda (item) (when (string-match (format"%s" backend) item) item)) (mapcar#'car gptel--known-backends)))))
(backend (or backend (and gptel-backend (cl-struct-slot-value (type-of gptel-backend) 'name gptel-backend))))
(backend-struct (cdr (assoc (format"%s" backend) gptel--known-backends)))
(model (if model (format"%s" model)))
(model (or (and model backend-struct (member model (cl-struct-slot-value (type-of backend-struct) 'models backend-struct)) model)
(and model gptel-backend (member model (cl-struct-slot-value (type-of gptel-backend) 'models gptel-backend)) model)
(and backend-struct (car (cl-struct-slot-value (type-of backend-struct) 'models backend-struct)))
(and gptel-backend (car (cl-struct-slot-value (type-of gptel-backend) 'models gptel-backend)))))
(stream (if (member:stream args) stream gptel-stream)))
(puthash:urlnil table)
(puthash:title (concat"ask gptel: " (format"%s" input))
table)
(puthash:source"gptel"
table)
(puthash:query input
table)
(puthash:model model
table)
(puthash:stream stream
table)
(puthash:backend backend
table)
(list table)))
(defunconsult-web--gptel-buffer-name (&optionalquery&restargs)
"Returns a string for `consult-web-gptel' buffer name"
(cond
((functionp consult-web-gptel-buffer-name)
(funcall consult-web-gptel-buffer-name query args))
((stringp consult-web-gptel-buffer-name)
consult-web-gptel-buffer-name)
(t"*consult-web-gptel*")))
(cl-defunconsult-web--gptel-response-preview (query&restargs &keybackendmodelstream &allow-other-key)
"Returns a `gptel' buffer.QUERY is sent to BACKEND using MODEL.If STREAM is non-nil, the response is streamed."
(save-excursion
(with-current-buffer (gptel (consult-web--gptel-buffer-name query args) nilnilnil)
(let* ((backend (if backend (format"%s" backend) nil))
(backend (and backend (car (seq-filter (lambda (item) (when (string-match (format"%s" backend) item) item)) (mapcar#'car gptel--known-backends)))))
(backend (or (and backend (cdr (assoc backend gptel--known-backends)))
gptel-backend))
(model (or (and model (format"%s" model))
(and backend (car (cl-struct-slot-value (type-of backend) 'models backend)))
(and gptel-backend (car (cl-struct-slot-value (type-of gptel-backend) 'models gptel-backend)))
gptel-model))
(stream (if stream tnil))
)
(setq-local gptel-backend backend)
(setq-local gptel-model model)
(setq-local gptel-stream stream)
(erase-buffer)
(insert (gptel-prompt-prefix-string))
(insert (format"%s" query))
(gptel-send)
(current-buffer)))))
(defunconsult-web--gptelbuffer-preview (cand)
"Shows a preview buffer of CAND for `consult-web-gptel'.The preview buffer is from `consult-web--gptel-response-preview'."
(let* ((query (cond ((listp cand)
(get-text-property0:query (car cand)))
(t
(get-text-property0:query cand))))
(backend (cond ((listp cand)
(get-text-property0:backend (car cand)))
(t
(get-text-property0:backend cand))))
(model (cond ((listp cand)
(get-text-property0:model (car cand)))
(t
(get-text-property0:model cand))))
(stream (cond ((listp cand)
(get-text-property0:stream (car cand)))
(t
(get-text-property0:stream cand))))
(buff (consult-web--gptel-response-preview query :model model :backend backend :stream stream)))
(if buff
(funcall (consult--buffer-preview) 'preview
buff
))))
(consult-web-define-source "gptel":narrow-char?G:face'consult-web-ai-source-face:format#'consult-web-dynamic--gptel-format-candidate:request#'consult-web--gptel-fetch-results:on-preview#'consult-web--gptelbuffer-preview:on-return#'identity:on-callback#'consult-web--gptelbuffer-preview:preview-key consult-web-preview-key
:search-history'consult-web--search-history:selection-history'consult-web--selection-history:dynamic'both
)
provide and footer
;;; provide `consult-web-gptel' module
(provide'consult-web-gptel)
(add-to-list'consult-web-sources-modules-to-load'consult-web-gptel)
;;; consult-web-gptel.el ends here
(defvarconsult-web-doiorg-api-url"https://doi.org/api/handles/")
(defvarconsult-web-doiorg-search-url"https://doi.org/")
(defunconsult-web--doi-to-url (doi)
"Converts DOI value to target url"
(let* ((doi (if doi (format"%s" doi)))
(url (concat consult-web-doiorg-api-url doi)))
(funcall consult-web-retrieve-backend
url
:parser
(lambda ()
(goto-char (point-min))
(let* ((content (json-parse-buffer))
(items (gethash"values" content)))
(car (mapcar (lambda (item)
(if-let* ((type (gethash"type" item))
(url (if (equal type "URL") (gethash"value" (gethash"data" item)))))
url
nil)) items)))))))
(cl-defunconsult-web--doiorg-fetch-results (doi&restargs)
"Fetch target url of DOI."
(let* ((table (make-hash-table:test'equal))
(url (consult-web--doi-to-url doi)))
(if url
(progn
(puthash:url url
table)
(puthash:title doi
table)
(puthash:source"doiorg"
table)
(puthash:query doi
table)
))
(list table)))
(defvarconsult-web--doi-search-history (list)
"History variables for search terms when using`consult-web-doi' commands.")
(defvarconsult-web--doi-selection-history (list)
"History variables for selected items when using`consult-web-doi' commands.")
(consult-web-define-source "doiorg":narrow-char?d:face'link:request#'consult-web--doiorg-fetch-results:search-history'consult-web--doi-search-history:selection-history'consult-web--doi-selection-history:dynamict
)
provide and footer
;;; provide `consult-web-doi' module
(provide'consult-web-doi)
(add-to-list'consult-web-sources-modules-to-load'consult-web-doi)
;;; consult-web-doi.el ends here
(cl-defunconsult-web--pubmed-fetch-results (input&restargs &keydatabasecountpage &allow-other-keys)
"Fetches results for INPUT from PubMed using Entrez Utilitiesservice.COUNT and PAGE are passed to `consult-web--pubmed-esearch-fetch-results' and `consult-web--pubmed-esummary-fetch-results'.DATABASE is passed as DB to `consult-web--pubmed-esearch-fetch-results' and `consult-web--pubmed-esummary-fetch-results'."
(let* ((esearch (consult-web--pubmed-esearch-fetch-results input args :db database :count count :page page))
(webenv (plist-get esearch :webenv))
(qk (plist-get esearch :qk)))
(consult-web--pubmed-esummary-fetch-results input :webenv webenv :qk qk :db database :count count :page page args)
))
(consult-web-define-source "PubMed":narrow-char?p:face'consult-web-scholar-source-face:request#'consult-web--pubmed-fetch-results:format#'consult-web-dynamic--pubmed-format-candidate:preview-key consult-web-preview-key
:category'consult-web-scholar:search-history'consult-web--search-history:selection-history'consult-web--selection-history:dynamic'both
)
provide and footer
;;; provide `consult-web-pubmed' module
(provide'consult-web-pubmed)
(add-to-list'consult-web-sources-modules-to-load'consult-web-pubmed)
;;; consult-web-pubmed.el ends here
;;; provide `consult-web-scopus' module
(provide'consult-web-scopus)
(add-to-list'consult-web-sources-modules-to-load'consult-web-scopus)
;;; consult-web-scopus.el ends here
(defvarconsult-web-stackoverflow-search-url"https://stackoverflow.com/search")
(defvarconsult-web-stackoverflow-api-url"https://api.stackexchange.com/2.3/search/advanced")
(defcustomconsult-web-stackexchange-api-keynil"Key for Stack Exchange API.See URL `https://api.stackexchange.com/', and URL `https://stackapps.com/' for more info":group'consult-web:type '(choice (const :tag"API Key" string)
(function:tag"Custom Function")))
(cl-defunconsult-web--stackoverflow-fetch-results (input&restargs &keycountpageordersort &allow-other-keys)
"Fetch search results for INPUT from stackoverflow.COUNT is passed as pagesize in query parameters.PAGE is passed as page in query parameters.ORDER is passed as order in query parameters.SORT is passed as sort in query parameters.See URL `https://api.stackexchange.com/' for more info."
(let* ((count (or (and (integerp count) count)
(and count (string-to-number (format"%s" count)))
consult-web-default-count))
(count (min count 25))
(page (or (and (integerp page) page)
(and page (string-to-number (format"%s" page)))
consult-web-default-page))
(page (max page 1))
(order (if (and order (member (format"%s" order) '("desc""asc"))) (format"%s" order)))
(sort (if (and sort (member (format"%s" sort) '("activity""votes""creation""relevance"))) (format"%s" sort)))
(params `(("order". ,(or order "desc"))
("sort". ,(or sort "relevance"))
("site"."stackoverflow")
("q". ,(replace-regexp-in-string"""+" input))
("pagesize". ,(format"%s" count))
("page". ,(format"%s" page))
("key". ,(consult-web-expand-variable-function consult-web-stackexchange-api-key)))))
(funcall consult-web-retrieve-backend
consult-web-stackoverflow-api-url
:params params
:parser
(lambda ()
(goto-char (point-min))
(let* ((results (gethash"items" (json-parse-buffer)))
(data (mapcar (lambda (item) `(,(format"%s" (gethash"title" item)) ,(format"%s" (gethash"link" item)))) results))
(table (make-hash-table:test'equal)))
(cl-loopfor a in data
collect
(let ((table (make-hash-table:test'equal)))
(puthash:url
(cadr a) table)
(puthash:search-url (concat consult-web-stackoverflow-search-url "?q=" input)
table)
(puthash:title
(car a) table)
(puthash:source"StackOverflow"
table)
(puthash:query input
table)
table
)))
))))
(consult-web-define-source "StackOverflow":narrow-char?s:face'consult-web-engine-source-face:request#'consult-web--stackoverflow-fetch-results:preview-key consult-web-preview-key
:search-history'consult-web--search-history:selection-history'consult-web--selection-history:dynamic'both
)
provide and footer
;;; provide `consult-web-stackoverflow' module
(provide'consult-web-stackoverflow)
(add-to-list'consult-web-sources-modules-to-load'consult-web-stackoverflow)
;;; consult-web-stackoverflow.el ends here
;;; provide `consult-web-wikipedia' module
(provide'consult-web-wikipedia)
(add-to-list'consult-web-sources-modules-to-load'consult-web-wikipedia)
;;; consult-web-wikipedia.el ends here
;;; provide `consult-web-youtube' module
(provide'consult-web-youtube)
(add-to-list'consult-web-sources-modules-to-load'consult-web-youtube)
;;; consult-web-youtube.el ends here