├── README.md └── omnibox.el /README.md: -------------------------------------------------------------------------------- 1 | # Omnibox 2 | 3 | ### Lightweight completion/selection system for Emacs. 4 | 5 | ![omnibox](https://github.com/sebastiencs/omnibox/raw/screenshots/omnibox.png) 6 | [[More screenshots]](https://github.com/sebastiencs/omnibox/tree/screenshots) 7 | 8 | Require Emacs >= 26. 9 | Not compatible with Emacs in terminal 10 | 11 | Usage: 12 | ```el 13 | (use-package omnibox 14 | :config 15 | (global-set-key (kbd "M-x") 'omnibox-M-x) 16 | (omnibox-setup)) 17 | ``` 18 | 19 | It replaces Helm in my daily usage but the package is still under development so don't consider it stable. 20 | -------------------------------------------------------------------------------- /omnibox.el: -------------------------------------------------------------------------------- 1 | ;;; omnibox.el --- Selection package -*- lexical-binding: t -*- 2 | 3 | ;; Copyright (C) 2018 Sebastien Chapuis 4 | 5 | ;; Author: Sebastien Chapuis 6 | ;; URL: https://github.com/sebastiencs/omnibox 7 | ;; Keywords: completion, selection, convenience, frames 8 | ;; Package-Requires: ((emacs "26.1") (dash "2.13") (frame-local "0.0.1")) 9 | ;; Version: 0.0.1 10 | 11 | ;;; License 12 | ;; 13 | ;; This program is free software; you can redistribute it and/or modify 14 | ;; it under the terms of the GNU General Public License as published by 15 | ;; the Free Software Foundation; either version 3, or (at your option) 16 | ;; any later version. 17 | 18 | ;; This program is distributed in the hope that it will be useful, 19 | ;; but WITHOUT ANY WARRANTY; without even the implied warranty of 20 | ;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 21 | ;; GNU General Public License for more details. 22 | 23 | ;; You should have received a copy of the GNU General Public License 24 | ;; along with this program; see the file COPYING. If not, write to 25 | ;; the Free Software Foundation, Inc., 51 Franklin Street, Fifth 26 | ;; Floor, Boston, MA 02110-1301, USA. 27 | 28 | 29 | ;;; Commentary: 30 | ;; 31 | ;; A lightweight completion/selection system for Emacs 32 | ;; 33 | 34 | ;;; Code: 35 | 36 | (require 'icons-in-terminal nil t) 37 | (require 'frame-local) 38 | (require 'dash) 39 | (require 'subr-x) 40 | 41 | (defgroup omnibox nil 42 | "A lightweigth completion/selection system." 43 | :prefix "omnibox-" 44 | :group 'convenience) 45 | 46 | (defface omnibox-modeline 47 | '((((background light)) :foreground "white" :background "black") 48 | (((background dark)) :foreground "black" :background "white")) 49 | "Face to used on the mode line of omnibox." 50 | :group 'omnibox) 51 | 52 | (defface omnibox-selection 53 | '((((background light)) :foreground "white" :background "black") 54 | (((background dark)) :foreground "black" :background "white")) 55 | "Face to used on the mode line of omnibox." 56 | :group 'omnibox) 57 | 58 | (defface omnibox-match 59 | '((((background light)) :foreground "#b00000") 60 | (((background dark)) :foreground "gold1")) 61 | "Face used to highlight matches." 62 | :group 'omnibox) 63 | 64 | (defface omnibox-prompt 65 | '((t :inherit minibuffer-prompt)) 66 | "Face used on the prompt." 67 | :group 'omnibox) 68 | 69 | (defvar omnibox-frame-parameters 70 | `((no-accept-focus . t) 71 | (no-focus-on-map . t) 72 | (width . 90) 73 | (height . 20) 74 | (vertical-scroll-bars . nil) 75 | (horizontal-scroll-bars . nil) 76 | (left-fringe . 0) 77 | (right-fringe . 0) 78 | (menu-bar-lines . 0) 79 | (tool-bar-lines . 0) 80 | (line-spacing . 0) 81 | (unsplittable . t) 82 | (top . 100) 83 | (mouse-wheel-frame . nil) 84 | (no-other-frame . t) 85 | (cursor-type . nil) 86 | (drag-internal-border . t) 87 | (left-fringe . 0) 88 | (right-fringe . 0) 89 | (background-color . "#282C34") 90 | (no-special-glyphs . t))) 91 | 92 | (defvar-local omnibox-ov nil) 93 | (defvar-local omnibox-selection 0) 94 | (defvar-local omnibox-candidates-length 0) 95 | (defvar-local omnibox--extern nil) 96 | 97 | (defmacro omnibox--get (variable) 98 | (let ((var (intern (format "omnibox-%s" variable)))) 99 | `(frame-local-get ',var (frame-parent)))) 100 | 101 | (defmacro omnibox--set (variable value) 102 | (let ((var (intern (format "omnibox-%s" variable)))) 103 | `(frame-local-set ',var ,value (frame-parent)))) 104 | 105 | (defun omnibox--filter-command (item) 106 | (and (commandp item) 107 | (not (get item 'byte-obsolete-info)) 108 | item)) 109 | 110 | (defun omnibox--overlay nil 111 | (or omnibox-ov 112 | (setq omnibox-ov (make-overlay 1 1)))) 113 | 114 | (defun omnibox--make-buffer-name (&optional suffix) 115 | (let ((id (frame-parameter (frame-parent) 'window-id))) 116 | (concat " *Omnibox-" suffix id "*"))) 117 | 118 | (defun omnibox--buffer (&optional suffix) 119 | (get-buffer-create (omnibox--make-buffer-name suffix))) 120 | 121 | (defun omnibox--modeline nil 122 | (let* ((selection (number-to-string (1+ omnibox-selection))) 123 | (length (number-to-string omnibox-candidates-length)) 124 | (state (concat " " selection "/" length " "))) 125 | (concat 126 | (propertize (or (omnibox--get title) " Omnibox ") 'face 'omnibox-modeline 127 | 'display '(raise 0.15)) 128 | (propertize " " 'display `(space :align-to (- right-fringe ,(length state)) :height 1.5)) 129 | (when (> omnibox-candidates-length 0) 130 | (propertize state 'face 'omnibox-modeline 131 | 'display '(raise 0.15)))))) 132 | 133 | (defun omnibox--render-candidate (candidate) 134 | (replace-regexp-in-string "[\n\t]+" " " candidate)) 135 | 136 | (defun omnibox--render-buffer (candidates) 137 | (setq omnibox-selection 0) 138 | (with-current-buffer (omnibox--buffer) 139 | (erase-buffer) 140 | (insert (mapconcat 'omnibox--render-candidate candidates "\n")) 141 | (setq mode-line-format '(:eval (omnibox--modeline)) 142 | truncate-lines t 143 | omnibox-candidates-length (omnibox--get candidates-length) 144 | header-line-format (propertize " " 'display '(space :align-to right-fringe) 'face '(:height 0.3))) 145 | (omnibox--update-line 0) 146 | (current-buffer))) 147 | 148 | (defun omnibox--update-list-buffer nil 149 | (-> (omnibox--get input) 150 | (omnibox--make-candidates) 151 | (omnibox--render-buffer))) 152 | 153 | (defun omnibox--update-input-buffer (&optional string) 154 | (with-current-buffer (omnibox--buffer "input-") 155 | (setq mode-line-format nil 156 | header-line-format nil) 157 | (erase-buffer) 158 | (insert (propertize (or (omnibox--get prompt) "input: ") 'face 'omnibox-prompt) 159 | (or string "") 160 | (propertize " " 'face 'cursor)) 161 | (current-buffer))) 162 | 163 | (defun omnibox--sort (candidates input) 164 | (if (> (omnibox--get input-len) 0) 165 | (-let* ((groups (--group-by (string-prefix-p (string-trim input) it) candidates))) 166 | (-concat 167 | (--sort (< (length it) (length other)) (alist-get t groups)) 168 | (--sort (< (length it) (length other)) (alist-get nil groups)))) 169 | candidates)) 170 | 171 | (defun omnibox--highlight-common (candidate input) 172 | (when (> (omnibox--get input-len) 0) 173 | (setq candidate (copy-sequence candidate)) 174 | (dolist (word (split-string input " " t)) 175 | (-let* ((_match-data (string-match word candidate)) 176 | ((start end) (match-data t))) 177 | (when (and (> end start) (<= end (length candidate))) 178 | (add-face-text-property start end 'omnibox-match nil candidate))))) 179 | candidate) 180 | 181 | (defun omnibox--fetch-candidates (candidates input) 182 | (->> (cond ((and (functionp candidates) (omnibox--get extern)) 183 | (omnibox--generic-completion 184 | (funcall candidates "" (omnibox--get predicate) t) 185 | input)) 186 | ((and (functionp (omnibox--get predicate)) (omnibox--get extern)) 187 | (omnibox--generic-completion candidates input (omnibox--get predicate))) 188 | ((functionp candidates) 189 | (funcall candidates input)) 190 | (t (omnibox--generic-completion candidates input))) 191 | (-take (- 200 (omnibox--get pre-len))))) 192 | 193 | (defun omnibox--make-regexp (input) 194 | (let* ((words (split-string input " " t)) 195 | (n-words (length words))) 196 | (if (<= n-words 1) 197 | (string-trim input) 198 | (concat 199 | "\\(" 200 | (mapconcat 201 | (lambda (list) (mapconcat 'identity list ".*?")) 202 | (if (> n-words 3) (list words (reverse words)) (-permutations words)) 203 | "\\)\\|\\(") 204 | "\\)")))) 205 | 206 | ;;"\\(myword.*oklm.*\\)\\|\\(oklm.*myword\\)" ? 207 | (defun omnibox--generic-completion (candidates input &optional predicate) 208 | (let* ((regexp (->> (string-trim (or input "")) 209 | (omnibox--make-regexp))) 210 | (completion-regexp-list (and regexp (list regexp))) 211 | (case-fold-search completion-ignore-case) 212 | (all (all-completions "" candidates predicate))) 213 | (if (arrayp all) (append all nil) all))) 214 | 215 | (defun omnibox--sort-and-highlight (candidates input) 216 | (-> (--map (omnibox--highlight-common it input) candidates) 217 | (omnibox--sort input))) 218 | 219 | (defun omnibox--make-id (candidate) 220 | (let ((copy (copy-sequence candidate))) 221 | (put-text-property 0 (length candidate) 'omnibox-candidate candidate copy) 222 | copy)) 223 | 224 | (defun omnibox--get-default nil 225 | (when (= (omnibox--get input-len) 0) 226 | (let ((default (omnibox--get default))) 227 | (cond ((null default) nil) 228 | ((consp default) default) 229 | (t (list default)))))) 230 | 231 | (defun omnibox--format-history (history) 232 | (mapcar 233 | (lambda (hist) 234 | (let ((icon (if (fboundp 'icons-in-terminal) 235 | (icons-in-terminal 'oct_clock :foreground "grey") 236 | "H"))) 237 | (concat (propertize hist 238 | 'omnibox-history t 239 | 'omnibox-candidate hist 240 | 'omnibox-icon icon) 241 | (propertize " " 'display '(space :align-to (- right-fringe 2))) 242 | icon))) 243 | history)) 244 | 245 | (defun omnibox--compare-candidates (c1 c2) 246 | (string= (or (get-text-property 0 'omnibox-candidate c1) c1) 247 | (or (get-text-property 0 'omnibox-candidate c2) c2))) 248 | 249 | (defun omnibox--get-history (input) 250 | (-some-> (omnibox--get history) 251 | (omnibox--generic-completion input) 252 | (omnibox--format-history) 253 | (omnibox--sort-and-highlight input))) 254 | 255 | (defun omnibox--get-candidates (input) 256 | (--> (omnibox--get candidates) 257 | (omnibox--fetch-candidates it input) 258 | (mapcar 'omnibox--make-id it) 259 | (omnibox--sort-and-highlight it input))) 260 | 261 | (defun omnibox--merge (default history candidates) 262 | (let ((-compare-fn 'omnibox--compare-candidates)) 263 | (when default 264 | (setq history (-difference history default)) 265 | (setq candidates (-difference candidates default))) 266 | (when history 267 | (setq candidates (-difference candidates history))) 268 | (-concat default history candidates))) 269 | 270 | (defun omnibox--make-candidates (input) 271 | (let* ((default (omnibox--get-default)) 272 | (history (omnibox--get-history input)) 273 | (_ (omnibox--set pre-len (+ (length default) (length history)))) 274 | (candidates (omnibox--get-candidates input)) 275 | (all (omnibox--merge default history candidates))) 276 | (omnibox--set candidates-length (length all)) 277 | all)) 278 | 279 | (defun omnibox--resolve-params (params) 280 | (list 281 | (plist-get params :prompt) 282 | (plist-get params :candidates) 283 | (plist-get params :detail) 284 | (plist-get params :default) 285 | (plist-get params :history) 286 | (plist-get params :title) 287 | (plist-get params :action) 288 | (plist-get params :init) 289 | (plist-get params :require-match))) 290 | 291 | (defvar omnibox-mode-map) 292 | 293 | (defun omnibox--block-and-return nil 294 | (unless (omnibox--get action) 295 | (unwind-protect 296 | (read-from-minibuffer "" nil omnibox-mode-map) 297 | (omnibox--abort) 298 | (when (eq this-command 'omnibox--abort) 299 | (keyboard-quit))) 300 | (omnibox--get selected))) 301 | 302 | (defun omnibox (&rest plist) 303 | "Open the Omnibox. 304 | Supported PLIST keys: 305 | - :prompt 306 | - :candidates 307 | - :detail 308 | - :default 309 | - :history 310 | - :title 311 | - :action 312 | - :init 313 | - :require-match 314 | They will be documented once the package is stable. Most of them are equal 315 | to the ones of `completing-read'." 316 | (-let* (((prompt candidates detail default history title action init require-match) 317 | (omnibox--resolve-params plist))) 318 | (omnibox--set extern omnibox--extern) 319 | (omnibox--set title (or title (omnibox--title))) 320 | (omnibox--set prompt prompt) 321 | (omnibox--set candidates candidates) 322 | (omnibox--set detail detail) 323 | (omnibox--set default default) 324 | (omnibox--set history history) 325 | (omnibox--set input-len (length init)) 326 | (omnibox--set input init) 327 | (omnibox--set action action) 328 | (omnibox--set require-match require-match) 329 | (-> (omnibox--make-candidates (or init "")) 330 | (omnibox--render-buffer) 331 | (omnibox--make-frame)) 332 | (omnibox-mode 1) 333 | (omnibox--block-and-return))) 334 | 335 | (defun omnibox--function-doc (candidate) 336 | (-some--> (intern candidate) 337 | (and (functionp it) it) 338 | (documentation it) 339 | (car (split-string it "\n")))) 340 | 341 | (defun omnibox--variable-doc (candidate) 342 | (-some--> (intern candidate) 343 | (documentation-property it 'variable-documentation) 344 | (car (split-string it "\n")))) 345 | 346 | (defun omnibox--title (&optional command) 347 | (--> (or command this-command "?") 348 | (if (symbolp it) (symbol-name it) it) 349 | (replace-regexp-in-string "^omnibox-" "" it) 350 | (format " Omnibox-%s " it))) 351 | 352 | (defun omnibox-M-x nil 353 | "Select and execute a command." 354 | (interactive) 355 | (omnibox :prompt "M-x: " 356 | :candidates (lambda (input) (omnibox--generic-completion obarray input 'commandp)) 357 | :history extended-command-history 358 | :action (lambda (candidate) 359 | (command-execute (intern candidate) t) 360 | (setq extended-command-history (cons candidate (delete candidate extended-command-history)))) 361 | :detail 'omnibox--function-doc 362 | :require-match t)) 363 | 364 | (defun omnibox-describe-function (prompt _collection &optional 365 | predicate _require-match 366 | _initial-input hist def 367 | _inherit-input-method) 368 | (omnibox :prompt prompt 369 | :candidates (lambda (input) (omnibox--generic-completion obarray input predicate)) 370 | :history hist 371 | :default def 372 | :detail 'omnibox--function-doc)) 373 | 374 | (defun omnibox-describe-variable (prompt _collection &optional 375 | _predicate _require-match 376 | _initial-input hist def 377 | _inherit-input-method) 378 | (omnibox :prompt prompt 379 | :candidates (lambda (input) 380 | (omnibox--generic-completion 381 | obarray input 382 | #'(lambda (v) 383 | (or (get v 'variable-documentation) 384 | (and (boundp v) (not (keywordp v))))))) 385 | :history hist 386 | :default def 387 | :detail 'omnibox--variable-doc)) 388 | 389 | ;; (omnibox :detail "moi" :candidates '("seb" "ok" "coucou") :prompt "seb: ") 390 | 391 | (defun omnibox--make-frame (buffer) 392 | (-if-let* ((frame (omnibox--get frame))) 393 | (progn 394 | (omnibox--update-input-buffer (omnibox--get input)) 395 | (make-frame-visible frame) 396 | (redisplay)) 397 | (let* ((before-make-frame-hook nil) 398 | (after-make-frame-functions nil) 399 | (internal-border (round (* (frame-char-width) 1.2))) 400 | (x (- (/ (frame-pixel-width) 2) 401 | (/ (* 90 (frame-char-width)) 2) 402 | internal-border)) 403 | (frame (make-frame 404 | (append `((left . ,x) 405 | (internal-border-width . ,internal-border) 406 | (default-minibuffer-frame . ,(selected-frame)) 407 | (minibuffer . ,(minibuffer-window)) 408 | (parent-frame . ,(selected-frame))) 409 | omnibox-frame-parameters))) 410 | (window (frame-selected-window frame))) 411 | (set-window-buffer window buffer) 412 | (redirect-frame-focus frame (selected-frame)) 413 | (set-window-dedicated-p window t) 414 | (omnibox--set frame frame) 415 | (with-selected-frame frame 416 | (display-buffer-in-side-window 417 | (omnibox--update-input-buffer (omnibox--get input)) 418 | '((side . top) (window-height . 1)))) 419 | frame))) 420 | 421 | (defun omnibox--candidate-at-point nil 422 | (or (get-text-property (point) 'omnibox-candidate) 423 | (buffer-substring-no-properties (line-beginning-position) 424 | (line-end-position)))) 425 | 426 | (defun omnibox--update-overlay nil 427 | "." 428 | (let ((icon (get-text-property (point) 'omnibox-icon)) 429 | (item (get-text-property (point) 'omnibox-candidate)) 430 | (documentation (or (get-text-property (point) 'omnibox-doc) 431 | (-some--> (omnibox--get detail) 432 | (and (functionp it) it) 433 | (funcall it (omnibox--candidate-at-point))) 434 | ""))) 435 | (when icon 436 | (setq documentation (concat documentation " " icon))) 437 | (setq documentation (concat " " documentation)) 438 | (add-face-text-property 0 (length documentation) 'omnibox-selection nil documentation) 439 | (move-overlay (omnibox--overlay) (line-beginning-position) (line-end-position)) 440 | (overlay-put (omnibox--overlay) 441 | 'face 'omnibox-selection) 442 | (overlay-put (omnibox--overlay) 'display (and icon item)) 443 | (overlay-put (omnibox--overlay) 444 | 'after-string 445 | (concat (propertize " " 'display `(space :align-to (- right-fringe ,(string-width documentation) ,(if icon 1 0)) :height 1.1) 446 | 'face 'omnibox-selection) 447 | documentation 448 | (propertize " " 'display `(space :align-to right-fringe) 449 | 'face 'omnibox-selection))))) 450 | 451 | (defun omnibox--disable-overlays nil 452 | (overlay-put (omnibox--overlay) 'after-string nil) 453 | (overlay-put (omnibox--overlay) 'face nil)) 454 | 455 | (defun omnibox--update-line (selection) 456 | (setq omnibox-selection selection) 457 | (goto-char 1) 458 | (forward-line selection) 459 | (if (= (omnibox--get candidates-length) 0) 460 | (omnibox--disable-overlays) 461 | (omnibox--update-overlay))) 462 | 463 | (defun omnibox--select-return (selected) 464 | (omnibox--set selected selected) 465 | (omnibox--abort) 466 | (-some-> (omnibox--get action) 467 | (funcall selected))) 468 | 469 | (defun omnibox--select nil 470 | "Select to current candidate." 471 | (interactive) 472 | (let ((selected (with-current-buffer (omnibox--buffer) 473 | (omnibox--candidate-at-point))) 474 | (input (omnibox--get input)) 475 | (require-match (omnibox--get require-match)) 476 | empty) 477 | (setq empty (equal selected "")) 478 | (cond 479 | ((and empty (eq require-match t)) 480 | nil) 481 | ((and empty (eq require-match nil)) 482 | (omnibox--select-return input)) 483 | ((memq require-match '(confirm confirm-after-completion)) 484 | (omnibox--select-return (if empty input selected))) 485 | (t (omnibox--select-return selected))))) 486 | 487 | (defun omnibox--change-line (selection) 488 | (with-selected-window (get-buffer-window (omnibox--buffer) t) 489 | (omnibox--update-line selection))) 490 | 491 | (defun omnibox--next nil 492 | "Move to the next candidate." 493 | (interactive) 494 | (setq omnibox-selection (min (1+ omnibox-selection) 495 | (1- (omnibox--get candidates-length)))) 496 | (omnibox--change-line omnibox-selection)) 497 | 498 | (defun omnibox--prev nil 499 | "Move to the previous candidate." 500 | (interactive) 501 | (setq omnibox-selection (max (1- omnibox-selection) 0)) 502 | (omnibox--change-line omnibox-selection)) 503 | 504 | (defun omnibox--hide nil 505 | (-some-> (omnibox--get frame) 506 | (make-frame-invisible))) 507 | 508 | (defun omnibox--abort nil 509 | "Cancel the omnibox." 510 | (interactive) 511 | (when (and (minibufferp) (not (omnibox--get action))) 512 | (exit-minibuffer)) 513 | (omnibox-mode -1) 514 | (omnibox--hide)) 515 | 516 | (defun omnibox--update-input (new-input) 517 | (omnibox--set input new-input) 518 | (omnibox--set input-len (length new-input)) 519 | (omnibox--update-input-buffer new-input) 520 | (omnibox--update-list-buffer)) 521 | 522 | (defun omnibox--insert nil 523 | "Insert a character." 524 | (interactive) 525 | (let* ((char (char-to-string last-command-event)) 526 | (current (or (omnibox--get input) "")) 527 | (new-string (concat current char))) 528 | (omnibox--update-input new-string))) 529 | 530 | (defun omnibox--backward-delete nil 531 | "Delete the previous character." 532 | (interactive) 533 | (-when-let* ((current (omnibox--get input)) 534 | (len (length current)) 535 | (new-string (substring current 0 (max (1- len) 0)))) 536 | (omnibox--update-input new-string))) 537 | 538 | (defun omnibox--try-complete nil 539 | "Try to complete the candidate." 540 | (interactive) 541 | (-some->> (omnibox--make-candidates (omnibox--get input)) 542 | (try-completion (omnibox--get input)) 543 | (substring-no-properties) 544 | (omnibox--update-input))) 545 | 546 | (defun omnibox--make-char-table nil 547 | "." 548 | (let ((my-char-table (make-char-table 'my-char-table))) 549 | (map-char-table (lambda (key val) 550 | (when (eq 'self-insert-command val) 551 | (set-char-table-range my-char-table key 'omnibox--insert))) 552 | (car (cdr global-map))) 553 | my-char-table)) 554 | 555 | (defvar omnibox-mode-map nil 556 | "Keymap for ‘omnibox-mode’.") 557 | (unless omnibox-mode-map 558 | (let ((map (make-sparse-keymap))) 559 | (define-key map "\e\e\e" 'omnibox--abort) 560 | (define-key map (kbd "") 'omnibox--abort) 561 | (define-key map "\C-g" 'omnibox--abort) 562 | (define-key map (kbd "C-n") 'omnibox--next) 563 | (define-key map (kbd "") 'omnibox--next) 564 | (define-key map (kbd "C-p") 'omnibox--prev) 565 | (define-key map (kbd "") 'omnibox--prev) 566 | (define-key map (kbd "RET") 'omnibox--select) 567 | (define-key map (kbd "") 'omnibox--select) 568 | (define-key map (kbd "TAB") 'omnibox--try-complete) 569 | (define-key map (kbd "") 'omnibox--try-complete) 570 | (define-key map (kbd "DEL") 'omnibox--backward-delete) 571 | (define-key map (kbd "") 'omnibox--backward-delete) 572 | (define-key map [t] 'ignore) 573 | (setq map (-snoc map (omnibox--make-char-table))) 574 | (setq omnibox-mode-map map))) 575 | 576 | (define-minor-mode omnibox-mode 577 | "Mode for omnibox." 578 | :init-value nil) 579 | 580 | (defvar omnibox--specialized-functions 581 | '((describe-function . omnibox-describe-function) 582 | (describe-variable . omnibox-describe-variable))) 583 | 584 | (defun omnibox--specialized-function-p (command) 585 | (assq command omnibox--specialized-functions)) 586 | 587 | (defun omnibox--run-specialized-function (command &rest params) 588 | (-> (alist-get command omnibox--specialized-functions) 589 | (apply params))) 590 | 591 | (defun omnibox--completing-read (prompt collection &optional 592 | predicate require-match 593 | initial-input hist def 594 | inherit-input-method) 595 | (if (omnibox--specialized-function-p this-command) 596 | (omnibox--run-specialized-function this-command prompt collection predicate 597 | require-match initial-input 598 | hist def inherit-input-method) 599 | (let ((omnibox--extern t)) 600 | (omnibox--set predicate predicate) 601 | (omnibox :prompt prompt 602 | :candidates collection 603 | :default def 604 | :init initial-input 605 | :require-match require-match)))) 606 | 607 | (defun omnibox--on-complete-region (candidate start end &optional buffer) 608 | (choose-completion-string candidate 609 | (or buffer (current-buffer)) 610 | (list start end))) 611 | 612 | (defun omnibox--completion-in-region (start end collection &optional predicate) 613 | (let ((omnibox--extern t)) 614 | (omnibox--set predicate predicate) 615 | (omnibox :prompt "Complete: " 616 | :candidates collection 617 | :init (buffer-substring-no-properties start end) 618 | :action (lambda (c) (omnibox--on-complete-region c start end (current-buffer)))))) 619 | 620 | (defun omnibox-setup nil 621 | (setq completing-read-function 'omnibox--completing-read 622 | completion-in-region-function 'omnibox--completion-in-region)) 623 | 624 | ;; TODO: 625 | ;; Handle require-match 626 | 627 | 628 | (provide 'omnibox) 629 | ;;; omnibox.el ends here 630 | --------------------------------------------------------------------------------