dotemacs

My Emacs configuration
git clone git://git.entf.net/dotemacs
Log | Files | Refs | LICENSE

sly-completion.el (33882B)


      1 ;;; sly-completion.el --- completion tricks and helpers  -*- lexical-binding: t; -*-
      2 
      3 ;; Copyright (C) 2016  João Távora
      4 
      5 ;; Author: João Távora
      6 ;; Keywords: 
      7 
      8 ;; This program is free software; you can redistribute it and/or modify
      9 ;; it under the terms of the GNU General Public License as published by
     10 ;; the Free Software Foundation, either version 3 of the License, or
     11 ;; (at your option) any later version.
     12 
     13 ;; This program is distributed in the hope that it will be useful,
     14 ;; but WITHOUT ANY WARRANTY; without even the implied warranty of
     15 ;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
     16 ;; GNU General Public License for more details.
     17 
     18 ;; You should have received a copy of the GNU General Public License
     19 ;; along with this program.  If not, see <http://www.gnu.org/licenses/>.
     20 
     21 ;;; Commentary:
     22 
     23 ;; 
     24 
     25 ;;; Code:
     26 ;;;
     27 (require 'cl-lib)
     28 (require 'comint)
     29 (require 'sly-messages "lib/sly-messages")
     30 
     31 
     32 ;;; Something to move to minibuffer.el, maybe
     33 
     34 ;;; Backend completion
     35 
     36 ;; This "completion style" delegates all the work to the completion
     37 ;; table which is then free to implement its own completion style.
     38 ;; Typically this is used to take advantage of some external tool which
     39 ;; already has its own completion system and doesn't give you efficient
     40 ;; access to the prefix completion needed by other completion styles.
     41 
     42 (add-to-list 'completion-styles-alist
     43              '(backend
     44                completion-backend-try-completion
     45                completion-backend-all-completions
     46                "Ad-hoc completion style provided by the completion table"))
     47 
     48 (defun completion--backend-call (op string table pred point)
     49   (when (functionp table)
     50     (let ((res (funcall table string pred (cons op point))))
     51       (when (eq op (car-safe res))
     52         (cdr res)))))
     53 
     54 (defun completion-backend-try-completion (string table pred point)
     55   (completion--backend-call 'try-completion string table pred point))
     56 
     57 (defun completion-backend-all-completions (string table pred point)
     58   (completion--backend-call 'all-completions string table pred point))
     59 
     60 
     61 ;;; Forward declarations (later replace with a `sly-common' lib)
     62 ;;;
     63 (defvar sly-current-thread)
     64 
     65 (declare-function sly-eval "sly" (sexp &optional package
     66                                        cancel-on-input
     67                                        cancel-on-input-retval))
     68 
     69 (declare-function sly-symbol-at-point "sly")
     70 
     71 (declare-function sly-buffer-name "sly")
     72 
     73 (defvar sly-buffer-package)
     74 
     75 (defvar sly-buffer-connection)
     76 
     77 (declare-function sly-connection "sly")
     78 
     79 (declare-function sly-recenter "sly")
     80 
     81 (declare-function sly-symbol-start-pos "sly")
     82 
     83 (declare-function sly-symbol-end-pos "sly")
     84 
     85 (declare-function sly-current-package "sly")
     86 
     87 (declare-function with-displayed-buffer-window "window")
     88 
     89 
     90 ;;; Backward compatibility shim for emacs < 25.
     91 ;;;
     92 (eval-when-compile
     93   (unless (fboundp 'with-displayed-buffer-window)
     94     (defmacro with-displayed-buffer-window (buffer-or-name action quit-function &rest body)
     95       "Show a buffer BUFFER-OR-NAME and evaluate BODY in that buffer.
     96 This construct is like `with-current-buffer-window' but unlike that
     97 displays the buffer specified by BUFFER-OR-NAME before running BODY."
     98       (declare (debug t))
     99       (let ((buffer (make-symbol "buffer"))
    100             (window (make-symbol "window"))
    101             (value (make-symbol "value")))
    102         (macroexp-let2 nil vbuffer-or-name buffer-or-name
    103           (macroexp-let2 nil vaction action
    104             (macroexp-let2 nil vquit-function quit-function
    105               `(let* ((,buffer (temp-buffer-window-setup ,vbuffer-or-name))
    106                       (standard-output ,buffer)
    107                       ,window ,value)
    108                  (with-current-buffer ,buffer
    109                    (setq ,window (temp-buffer-window-show
    110                                   ,buffer
    111                                   ;; Remove window-height when it's handled below.
    112                                   (if (functionp (cdr (assq 'window-height (cdr ,vaction))))
    113                                       (assq-delete-all 'window-height (copy-sequence ,vaction))
    114                                     ,vaction))))
    115 
    116                  (let ((inhibit-read-only t)
    117                        (inhibit-modification-hooks t))
    118                    (setq ,value (progn ,@body)))
    119 
    120                  (set-window-point ,window (point-min))
    121 
    122                  (when (functionp (cdr (assq 'window-height (cdr ,vaction))))
    123                    (ignore-errors
    124                      (funcall (cdr (assq 'window-height (cdr ,vaction))) ,window)))
    125 
    126                  (if (functionp ,vquit-function)
    127                      (funcall ,vquit-function ,window ,value)
    128                    ,value)))))))))
    129 
    130 
    131 
    132 ;;; Customization
    133 ;;;
    134 (defcustom sly-complete-symbol-function 'sly-flex-completions
    135   "Function reponsible for SLY completion.
    136 When called with one argument, a pattern, returns a (possibly
    137 propertized) list of strings the complete that pattern,
    138 collected from the Slynk server."
    139   :type 'function
    140   :group 'sly-ui)
    141 
    142 
    143 (cl-defmacro sly--responsive-eval ((var sexp
    144                                         &optional
    145                                         package
    146                                         input-arrived-retval) &rest body)
    147   "Use `sly-eval' on SEXP, PACKAGE, bind to VAR, run BODY.
    148 If user input arrives in the meantime return INPUT-ARRIVED-RETVAL
    149 immediately."
    150   (declare (indent 1) (debug (sexp &rest form)))
    151   (let ((sym (make-symbol "sly--responsive-eval")))
    152     `(let* ((,sym (make-symbol "sly--responsive-eval-unique"))
    153             (,var (sly-eval ,sexp ,package non-essential ,sym)))
    154        (if (eq ,var ,sym)
    155            ,input-arrived-retval
    156          ,@body))))
    157 
    158 
    159 ;;; Completion calculation
    160 ;;;
    161 (defun sly--completion-request-completions (pattern slyfun)
    162   "Request completions for PATTERN using SLYFUN.
    163 SLYFUN takes two arguments, a pattern and a package."
    164   (when (sly-connected-p)
    165     (let* ((sly-current-thread t))
    166       (sly--responsive-eval
    167           (completions `(,slyfun ,(substring-no-properties pattern)
    168 				 ',(sly-current-package)))
    169 	completions))))
    170 
    171 (defun sly-simple-completions (prefix)
    172   "Return (COMPLETIONS COMMON) where COMPLETIONS complete the PREFIX.
    173 COMPLETIONS is a list of propertized strings.
    174 COMMON a string, the common prefix."
    175   (cl-loop with first-difference-pos = (length prefix)
    176            with (completions common) =
    177            (sly--completion-request-completions prefix 'slynk-completion:simple-completions)
    178            for completion in completions
    179            do (put-text-property first-difference-pos
    180                                  (min (1+ first-difference-pos)
    181                                       (1- (length completion))) 
    182                                  'face
    183                                  'completions-first-difference
    184                                  completion)
    185            collect completion into formatted
    186            finally return (list formatted common)))
    187 
    188 (defun sly-flex-completions (pattern)
    189   "Return (COMPLETIONS NIL) where COMPLETIONS flex-complete PATTERN.
    190 COMPLETIONS is a list of propertized strings."
    191   (cl-loop with (completions _) =
    192            (sly--completion-request-completions pattern 'slynk-completion:flex-completions)
    193            for (completion score chunks classification suggestion) in completions
    194            do
    195            (cl-loop for (pos substring) in chunks
    196                     do (put-text-property pos (+ pos
    197                                                  (length substring))
    198                                           'face
    199                                           'completions-first-difference
    200                                           completion)
    201                     collect `(,pos . ,(+ pos (length substring))) into chunks-2
    202                     finally (put-text-property 0 (length completion)
    203                                                'sly-completion-chunks chunks-2
    204                                                completion))
    205            (add-text-properties 0
    206                                 (length completion)
    207                                 `(sly--annotation
    208                                   ,(format "%s %5.2f%%"
    209                                            classification
    210                                            (* score 100))
    211                                   sly--suggestion
    212                                   ,suggestion)
    213                                 completion)
    214 
    215            collect completion into formatted
    216            finally return (list formatted nil)))
    217 
    218 (defun sly-completion-annotation (completion)
    219   "Grab the annotation of COMPLETION, a string, if any"
    220   (get-text-property 0 'sly--annotation completion))
    221 
    222 ;;; backward-compatibility
    223 (defun sly-fuzzy-completions (pattern)
    224   "This function is obsolete since 1.0.0-beta-2;
    225 use ‘sly-flex-completions’ instead, but notice the updated protocol.
    226 
    227 Returns (COMPLETIONS NIL) where COMPLETIONS flex-complete PATTERN.
    228 
    229 COMPLETIONS is a list of elements of the form (STRING NIL NIL
    230 ANNOTATION) describing each completion possibility."
    231   (let ((new (sly-flex-completions pattern)))
    232     (list (mapcar (lambda (string)
    233 		    (list string nil nil (sly-completion-annotation string)))
    234 		  (car new))
    235 	  (cadr new))))
    236 
    237 (when (boundp 'completion-category-overrides)
    238   (add-to-list 'completion-category-overrides
    239                '(sly-completion (styles . (backend)))))
    240 
    241 (defun sly--completion-function-wrapper (fn)
    242   (let ((cache (make-hash-table :test #'equal)))
    243     (lambda (string pred action)
    244       (cl-labels ((all
    245                    ()
    246                    (let ((probe (gethash string cache :missing)))
    247                      (if (eq probe :missing)
    248                          (puthash string (funcall fn string) cache)
    249                        probe)))
    250                   (try ()
    251                        (let ((all (all)))
    252                          (and (car all)
    253                               (if (and (null (cdr (car all)))
    254                                        (string= string (caar all)))
    255                                   t
    256                                 string)))))
    257         (pcase action
    258           ;; identify this to the custom `sly--completion-in-region-function'
    259           (`sly--identify t)
    260           ;; identify this to other UI's
    261           (`metadata '(metadata
    262                        (display-sort-function . identity)
    263                        (category . sly-completion)))
    264           ;; all completions
    265           (`t (car (all)))
    266           ;; try completion
    267           (`nil (try))
    268           (`(try-completion . ,point)
    269            (cons 'try-completion (cons string point)))
    270           (`(all-completions . ,_point) (cons 'all-completions (car (all))))
    271           (`(boundaries . ,thing)
    272            (completion-boundaries string (all) pred thing))
    273 
    274           ;; boundaries or any other value
    275           (_ nil))))))
    276 
    277 ;; This duplicates a function in sly-parse.el
    278 (defun sly--completion-inside-string-or-comment-p ()
    279   (let ((ppss (syntax-ppss))) (or (nth 3 ppss) (nth 4 ppss))))
    280 
    281 (defun sly--completions-complete-symbol-1 (fn)
    282   (let* ((beg (sly-symbol-start-pos))
    283          (end (sly-symbol-end-pos)))
    284     (list beg end
    285           (sly--completion-function-wrapper fn)
    286           :annotation-function #'sly-completion-annotation
    287           :exit-function (lambda (obj _status)
    288                            (let ((suggestion
    289                                   (get-text-property 0 'sly--suggestion
    290                                                      obj)))
    291                              (when suggestion
    292                                (delete-region (- (point) (length obj)) (point))
    293                                (insert suggestion))))
    294           :company-docsig
    295           (lambda (obj)
    296             (when (sit-for 0.1)
    297               (sly--responsive-eval (arglist `(slynk:operator-arglist
    298                                                ,(substring-no-properties obj)
    299                                                ,(sly-current-package)))
    300                 (or (and arglist
    301                          (sly-autodoc--fontify arglist))
    302                     "no autodoc information"))))
    303           :company-no-cache t
    304           :company-doc-buffer
    305           (lambda (obj)
    306             (when (sit-for 0.1)
    307               (sly--responsive-eval (doc `(slynk:describe-symbol
    308                                            ,(substring-no-properties obj)))
    309                 (when doc
    310                   (with-current-buffer (get-buffer-create " *sly-completion doc*")
    311                     (erase-buffer)
    312                     (insert doc)
    313                     (current-buffer))))))
    314           :company-require-match 'never
    315           :company-match
    316           (lambda (obj)
    317             (get-text-property 0 'sly-completion-chunks obj))
    318           :company-location
    319           (lambda (obj)
    320             (save-window-excursion
    321               (let* ((buffer (sly-edit-definition
    322                               (substring-no-properties obj))))
    323                 (when (buffer-live-p buffer) ; on the safe side
    324                   (cons buffer (with-current-buffer buffer
    325                                  (point)))))))
    326           :company-prefix-length
    327           (and (sly--completion-inside-string-or-comment-p) 0))))
    328 
    329 (defun sly-simple-complete-symbol ()
    330   "Prefix completion on the symbol at point.
    331 Intended to go into `completion-at-point-functions'"
    332   (sly--completions-complete-symbol-1 'sly-simple-completions))
    333 
    334 (defun sly-flex-complete-symbol ()
    335   "\"Flex\" completion on the symbol at point.
    336 Intended to go into `completion-at-point-functions'"
    337   (sly--completions-complete-symbol-1 'sly-flex-completions))
    338 
    339 (defun sly-complete-symbol ()
    340   "Completion on the symbol at point, using `sly-complete-symbol-function'
    341 Intended to go into `completion-at-point-functions'"
    342   (sly--completions-complete-symbol-1 sly-complete-symbol-function))
    343 
    344 (defun sly-complete-filename-maybe ()
    345   (when (nth 3 (syntax-ppss)) (comint-filename-completion)))
    346 
    347 
    348 ;;; Set `completion-at-point-functions' and a few other tricks
    349 ;;;
    350 (defun sly--setup-completion ()
    351   ;; This one can be customized by a SLY user in `sly-mode-hook'
    352   ;;
    353   (setq-local completion-at-point-functions '(sly-complete-filename-maybe
    354                                               sly-complete-symbol))
    355   (add-function :around (local 'completion-in-region-function)
    356                 (lambda (oldfun &rest args)
    357                   (if sly-symbol-completion-mode
    358                       (apply #'sly--completion-in-region-function args)
    359                     (apply oldfun args)))
    360                 '((name . sly--setup-completion))))
    361 
    362 (define-minor-mode sly-symbol-completion-mode "Fancy SLY UI for Lisp symbols" t
    363   :global t)
    364 
    365 (add-hook 'sly-mode-hook 'sly--setup-completion)
    366 
    367 
    368 ;;; TODO: Most of the stuff emulates `completion--in-region' and its
    369 ;;; callees in Emacs's minibuffer.el
    370 ;;; 
    371 (defvar sly--completion-transient-data nil)  ; similar to `completion-in-region--data'
    372 
    373 (defvar sly--completion-transient-completions nil) ; not used
    374 
    375 ;;; TODO: not tested with other functions in `completion-at-point-functions'
    376 ;;; 
    377 (defun sly--completion-in-region-function (beg end function pred)
    378   (cond
    379    ((funcall function nil nil 'sly--identify)
    380     (let* ((pattern (buffer-substring-no-properties beg end))
    381            (all
    382             (all-completions pattern function pred))
    383            (try
    384             (try-completion pattern function pred)))
    385       (setq this-command 'completion-at-point) ; even if we started with `minibuffer-complete'!
    386       (setq sly--completion-transient-completions all)
    387       (cond ((eq try t)
    388              ;; A unique completion
    389              ;;
    390              (choose-completion-string (cl-first all)
    391                                        (current-buffer)
    392                                        (list beg end))
    393              (sly-temp-message 0 2 "Sole completion"))
    394             ;; Incomplete
    395             ((stringp try)
    396              (let ((pattern-overlay (make-overlay beg end nil nil nil)))
    397                (setq sly--completion-transient-data
    398                      `(,pattern-overlay
    399                        ,function
    400                        ,pred))
    401                (overlay-put pattern-overlay 'face 'highlight)
    402                (sly--completion-pop-up-completions-buffer pattern all)
    403                (sly-temp-message 0 2 "Not unique")
    404                (sly--completion-transient-mode 1)))
    405             ((> (length pattern) 0)
    406              (sly-temp-message 0 2 "No completions for %s" pattern)))))
    407    (t
    408     (funcall (default-value 'completion-in-region-function)
    409              beg end function pred))))
    410 
    411 (defvar sly--completion-in-region-overlay
    412   (let ((ov (make-overlay 0 0)))
    413     (overlay-put ov 'face 'highlight)
    414     (delete-overlay ov)
    415     ov)
    416   "Highlights the currently selected completion candidate")
    417 
    418 (defvar sly--completion-display-mode-map
    419   (let ((map (make-sparse-keymap)))
    420     (define-key map [mouse-1] 'sly-choose-completion)
    421     (define-key map [mouse-2] 'sly-choose-completion)
    422     (define-key map [backtab]     'sly-prev-completion)
    423     (define-key map (kbd "q") 'sly-completion-hide-completions)
    424     (define-key map (kbd "C-g") 'sly-completion-hide-completions)
    425     (define-key map (kbd "z") 'sly-completion-hide-completions)
    426     (define-key map [remap previous-line] 'sly-prev-completion)
    427     (define-key map [remap next-line] 'sly-next-completion)
    428     (define-key map [left] 'sly-prev-completion)
    429     (define-key map [right] 'sly-next-completion)
    430     (define-key map (kbd "RET") 'sly-choose-completion)
    431     map)
    432   "Keymap used in the *sly-completions* buffer")
    433 
    434 (define-derived-mode sly--completion-display-mode
    435   fundamental-mode "SLY Completions"
    436   "Major mode for presenting SLY completion results.")
    437 
    438 (defun sly--completion-transient-mode-postch ()
    439   "Determine whether to pop down the *sly completions* buffer."
    440   (unless (or unread-command-events ; Don't pop down the completions in the middle of
    441                                         ; mouse-drag-region/mouse-set-point.
    442               (let ((pattern-ov
    443                      (and sly--completion-transient-data
    444                           (car
    445                            sly--completion-transient-data))))
    446                 (and pattern-ov
    447                      ;; check if we're in the same buffer
    448                      ;;
    449                      (eq (overlay-buffer pattern-ov)
    450                          (current-buffer))
    451                      ;; check if point is somewhere acceptably related
    452                      ;; to the region data that originated the completion
    453                      ;;
    454                      (<= (overlay-start pattern-ov)
    455                          (point)
    456                          (overlay-end pattern-ov)))))
    457     (sly--completion-transient-mode -1)))
    458 
    459 (defvar sly--completion-transient-mode-map
    460   (let ((map (make-sparse-keymap)))
    461     (define-key map (kbd "C-n") 'sly-next-completion)
    462     (define-key map (kbd "C-p") 'sly-prev-completion)
    463     (define-key map (kbd "RET") 'sly-choose-completion)
    464     (define-key map "\t" `(menu-item "" sly-choose-completion
    465                                      :filter (lambda (original)
    466                                                (when (memq last-command
    467                                                            '(completion-at-point
    468                                                              sly-next-completion
    469                                                              sly-prev-completion))
    470                                                  original))))
    471     (define-key map (kbd "C-g") 'sly-quit-completing)
    472     map)
    473   "Keymap used in the buffer originating a *sly-completions* buffer")
    474 
    475 (defvar sly--completion-transient-mode nil
    476   "Explicit `defvar' for `sly--completion-transient-mode'")
    477 
    478 (defun sly--completion-turn-off-transient-mode ()
    479   (if (eq major-mode 'sly--completion-display-mode)
    480       (sly-message "Choosing completions directly in %s" (current-buffer))
    481     (sly-completion-hide-completions)))
    482 
    483 (define-minor-mode sly--completion-transient-mode
    484   "Minor mode when the \"*sly completions*\" buffer is showing"
    485   ;; :lighter " SLY transient completing"
    486   :variable sly--completion-transient-mode
    487   :global t
    488   (remove-hook 'post-command-hook #'sly--completion-transient-mode-postch)
    489   (setq display-buffer-alist
    490         (delq (assq 'sly--completion-transient-mode-display-guard-p display-buffer-alist)
    491               display-buffer-alist))
    492   (setq minor-mode-overriding-map-alist
    493         (delq (assq 'completion-in-region-mode minor-mode-overriding-map-alist)
    494               minor-mode-overriding-map-alist))
    495   (if (null sly--completion-transient-mode)
    496       (sly--completion-turn-off-transient-mode)
    497     (add-hook 'post-command-hook #'sly--completion-transient-mode-postch)
    498     (push `(sly--completion-transient-mode . ,sly--completion-transient-mode-map)
    499           minor-mode-overriding-map-alist)
    500     (push `(sly--completion-transient-mode-display-guard-p
    501             (sly--completion-transient-mode-teardown-before-displaying
    502              . ,display-buffer-alist))
    503           display-buffer-alist)))
    504 
    505 ;; `define-minor-mode' added to `minor-mode-map-alist', but we wanted
    506 ;; `minor-mode-overriding-map-alist' instead, so undo changes to
    507 ;; `minor-mode-map-alist'
    508 ;;
    509 (setq minor-mode-map-alist
    510       (delq (assq 'sly--completion-transient-mode minor-mode-map-alist)
    511             minor-mode-map-alist))
    512 
    513 ;; displaying other buffers with pop-to-buffer while in
    514 ;; `sly--completion-transient-mode' is problematic, because it
    515 ;; dedicates a window. Try some crazy `display-buffer-alist' shit to
    516 ;; prevent that.
    517 ;;
    518 (defun sly--completion-transient-mode-display-guard-p (buffer-name _action)
    519   (not (string-match-p "^*sly-completions*" buffer-name)))
    520 
    521 (defun sly--completion-transient-mode-teardown-before-displaying (_buffer _alist)
    522   (sly--completion-transient-mode -1)
    523   ;; returns nil, hoping some other function in alist will display the
    524   ;; buffer as intended.
    525   nil)
    526 
    527 (defun sly--completion-kill-transient-data ()
    528   (when (overlayp (car sly--completion-transient-data))
    529     (delete-overlay (car sly--completion-transient-data)))
    530   (setq sly--completion-transient-data nil))
    531 
    532 (defun sly-completion-hide-completions ()
    533   (interactive)
    534   (sly--completion-kill-transient-data)
    535   (let* ((buffer (get-buffer (sly-buffer-name :completions)))
    536          (win (and buffer
    537                    (get-buffer-window buffer 0))))
    538     (when win (with-selected-window win (quit-window t)))))
    539 
    540 (defvar sly--completion-reference-buffer nil
    541   "Like `completion-reference-buffer', which see")
    542 
    543 (defmacro sly--completion-with-displayed-buffer-window (buffer
    544                                                         action
    545                                                         quit-function
    546                                                         &rest body)
    547   ;;; WITH-DISPLAYED-BUFFER-WINDOW doesn't work noninteractively
    548   (let ((original-sym (cl-gensym "original-buffer-")))
    549     `(if noninteractive
    550          (let ((,original-sym (current-buffer)))
    551            (display-buffer (get-buffer-create ,buffer) ,action)
    552            (let ((standard-output ,buffer))
    553              (with-current-buffer ,original-sym
    554                ,@body)))
    555        (with-displayed-buffer-window ,buffer ,action ,quit-function
    556                                      ,@body))))
    557 
    558 (defun sly--completion-pop-up-completions-buffer (_pattern completions)
    559   (let ((display-buffer-mark-dedicated 'soft)
    560         (pop-up-windows nil)
    561         completions-buffer first-completion-point)
    562     (sly--completion-with-displayed-buffer-window
    563      (sly-buffer-name :completions)
    564      `((display-buffer--maybe-same-window
    565         display-buffer-reuse-window
    566         display-buffer--maybe-pop-up-frame-or-window
    567         ;; Use `display-buffer-below-selected' for inline completions,
    568         ;; but not in the minibuffer (e.g. in `eval-expression')
    569         ;; for which `display-buffer-at-bottom' is used.
    570         ,(if (eq (selected-window) (minibuffer-window))
    571              'display-buffer-at-bottom
    572            'display-buffer-below-selected))
    573        ,(if temp-buffer-resize-mode
    574             '(window-height . resize-temp-buffer-window)
    575           '(window-height . shrink-window-if-larger-than-buffer))
    576        ,(when temp-buffer-resize-mode
    577           '(preserve-size . (nil . t))))
    578      nil
    579      (sly--completion-transient-mode)
    580      (let ((reference (current-buffer)))
    581        (with-current-buffer standard-output
    582          (sly--completion-display-mode)
    583          (set (make-local-variable 'cursor-type) nil)
    584          (setq sly--completion-reference-buffer reference)
    585          (sly--completion-fill-completions-buffer completions)
    586          (setq completions-buffer standard-output
    587                first-completion-point (point))
    588          (add-hook 'kill-buffer-hook 'sly--completion-kill-transient-data t t))))
    589     (with-current-buffer completions-buffer
    590       (goto-char first-completion-point))))
    591 
    592 (defvar sly--completion-explanation
    593   (concat "Use \\[sly-next-completion] and \\[sly-prev-completion] to navigate completions."
    594           " \\[sly-choose-completion] or [mouse-1] selects a completion."
    595           "\n\nAnnotation flags: (b)oundp (f)boundp (g)eneric-function (c)lass (m)acro (s)pecial-operator\n\n"))
    596 
    597 (defun sly--completion-fill-completions-buffer (completions)
    598   (let ((inhibit-read-only t))
    599     (erase-buffer)
    600     (insert (substitute-command-keys
    601              sly--completion-explanation))
    602     (cl-loop with first = (point)
    603              for completion in completions
    604              for annotation = (or (get-text-property 0 'sly--annotation completion)
    605                                   "")
    606              for start = (point)
    607              do
    608              (cl-loop for (beg . end) in
    609                       (get-text-property 0 'sly-completion-chunks completion)
    610                       do (put-text-property beg
    611                                             end
    612                                             'face
    613                                             'completions-common-part completion))
    614              (insert (propertize completion
    615                                  'mouse-face 'highlight
    616                                  'sly--completion t))
    617              (insert (make-string (max
    618                                    1
    619                                    (- (1- (window-width))
    620                                       (length completion)
    621                                       (length annotation)))
    622                                   ? )
    623                      annotation)
    624              (put-text-property start (point) 'sly--completion completion)
    625              (insert "\n")
    626              finally (goto-char first) (sly-next-completion 0))))
    627 
    628 (defun sly-next-completion (n &optional errorp)
    629   (interactive "p")
    630   (with-current-buffer (sly-buffer-name :completions)
    631     (when (overlay-buffer sly--completion-in-region-overlay)
    632       (goto-char (overlay-start sly--completion-in-region-overlay)))
    633     (forward-line n)
    634     (let* ((end (and (get-text-property (point) 'sly--completion)
    635                      (save-excursion
    636                        (skip-syntax-forward "^\s")
    637                        (point))
    638                      ;; (next-single-char-property-change (point) 'sly--completion)
    639                      ))
    640            (beg (and end
    641                      (previous-single-char-property-change end 'sly--completion))))
    642       (if (and beg end)
    643           (progn
    644             (move-overlay sly--completion-in-region-overlay
    645                           beg end)
    646             (let ((win (get-buffer-window (current-buffer) 0)))
    647               (when win
    648                 (with-selected-window win
    649                   (goto-char beg)
    650                   (sly-recenter beg)))))
    651         (if errorp
    652             (sly-error "No completion at point"))))))
    653 
    654 (defun sly-prev-completion (n)
    655   (interactive "p")
    656   (sly-next-completion (- n)))
    657 
    658 (defun sly-choose-completion (&optional event)
    659   (interactive (list last-nonmenu-event))
    660   ;; In case this is run via the mouse, give temporary modes such as
    661   ;; isearch a chance to turn off.
    662   (run-hooks 'mouse-leave-buffer-hook)
    663   (with-current-buffer (sly-buffer-name :completions)
    664     (when event
    665       (goto-char (posn-point (event-start event)))
    666       (sly-next-completion 0 t))
    667     (let ((completion-text
    668            (buffer-substring-no-properties (overlay-start sly--completion-in-region-overlay)
    669                                            (overlay-end sly--completion-in-region-overlay))))
    670       (unless (buffer-live-p sly--completion-reference-buffer)
    671         (sly-error "Destination buffer is dead"))
    672       (choose-completion-string completion-text
    673                                 sly--completion-reference-buffer
    674                                 (let ((pattern-ov
    675                                        (car sly--completion-transient-data)))
    676                                   (list (overlay-start pattern-ov)
    677                                         (overlay-end pattern-ov))))
    678       (sly--completion-transient-mode -1))))
    679 
    680 (defun sly-quit-completing ()
    681   (interactive)
    682   (when sly--completion-transient-mode
    683     (sly--completion-transient-mode -1))
    684   (keyboard-quit))
    685 
    686 
    687 
    688 ;;;; Minibuffer reading
    689 
    690 (defvar sly-minibuffer-map
    691   (let ((map (make-sparse-keymap)))
    692     (set-keymap-parent map minibuffer-local-map)
    693     (define-key map "\t" 'completion-at-point)
    694     map)
    695   "Minibuffer keymap used for reading CL expressions.")
    696 
    697 
    698 (defvar sly-minibuffer-history '()
    699   "History list of expressions read from the minibuffer.")
    700 
    701 (defvar sly-minibuffer-symbol-history '()
    702   "History list of symbols read from the minibuffer.")
    703 
    704 (defmacro sly--with-sly-minibuffer (&rest body)
    705   `(let* ((minibuffer-setup-hook
    706            (cons (lambda ()
    707                    (set-syntax-table lisp-mode-syntax-table)
    708                    (sly--setup-completion))
    709                  minibuffer-setup-hook))
    710           (sly-buffer-package (sly-current-package))
    711           (sly-buffer-connection (sly-connection)))
    712      ,@body))
    713 
    714 (defvar sly-minibuffer-setup-hook nil
    715   "Setup SLY-specific minibuffer reads.
    716 Used mostly (only?) by `sly-autodoc-mode'.")
    717 
    718 (defun sly-read-from-minibuffer (prompt &optional initial-value history allow-empty keymap)
    719   "Read a string from the minibuffer, prompting with PROMPT.
    720 If INITIAL-VALUE is non-nil, it is inserted into the minibuffer
    721 before reading input.  The result is a string (\"\" if no input
    722 was given and ALLOW-EMPTY is non-nil)."
    723   (sly--with-sly-minibuffer
    724    (cl-loop
    725     with minibuffer-setup-hook = (cons
    726                                   (lambda ()
    727                                     (run-hooks 'sly-minibuffer-setup-hook))
    728                                   minibuffer-setup-hook)
    729     for i from 0
    730     for read = (read-from-minibuffer
    731                 (concat "[sly] " (when (cl-plusp i)
    732                                    "[can't be blank] ")
    733                         prompt)
    734                 (and (zerop i)
    735                      initial-value)
    736                 (or keymap sly-minibuffer-map)
    737                 nil (or history 'sly-minibuffer-history))
    738     when (or (> (length read) 0)
    739              allow-empty)
    740     return read)))
    741 
    742 (defun sly-read-symbol-name (prompt &optional query)
    743   "Either read a symbol name or choose the one at point.
    744 The user is prompted if a prefix argument is in effect, if there is no
    745 symbol at point, or if QUERY is non-nil."
    746   (let* ((sym-at-point (sly-symbol-at-point))
    747          (completion-category-overrides
    748           (cons '(sly-completion (styles . (backend)))
    749                 completion-category-overrides))
    750          (wrapper (sly--completion-function-wrapper sly-complete-symbol-function))
    751          (do-it (lambda () (completing-read prompt wrapper nil nil sym-at-point))))
    752     (cond ((or current-prefix-arg query (not sym-at-point))
    753            (cond (sly-symbol-completion-mode
    754                   (let ((icomplete-mode nil)
    755                         (completing-read-function #'completing-read-default))
    756                     (sly--with-sly-minibuffer (funcall do-it))))
    757                  (t (funcall do-it))))
    758           (t sym-at-point))))
    759 
    760 (defun sly--read-method (prompt-for-generic
    761                          prompt-for-method-within-generic)
    762   "Read triplet (GENERIC-NAME QUALIFIERS SPECIALIZERS) for a method."
    763   (let* ((generic-name (sly-read-symbol-name prompt-for-generic t))
    764          (format-spec (lambda (spec)
    765                         (let ((qualifiers (car spec)))
    766                           (if (null qualifiers)
    767                               (format "%s" (cadr spec))
    768                             (format "%s %s" (string-join qualifiers " ")
    769                                     (cadr spec))))))
    770          (methods-by-formatted-name
    771           (cl-loop for spec in (sly-eval `(slynk:generic-method-specs ,generic-name))
    772                    collect (cons (funcall format-spec spec) spec)))
    773          (context-at-point (sly-parse-context generic-name))
    774          (probe (and (eq :defmethod (car context-at-point))
    775                      (equal generic-name (cadr context-at-point))
    776                      (string-replace
    777                       "'" "" (mapconcat #'prin1-to-string (cddr context-at-point)
    778                                         " "))))
    779          default
    780          (reordered
    781           (cl-loop for e in methods-by-formatted-name
    782                    if (cl-equalp (car e) probe) do (setq default e)
    783                    else collect e into others
    784                    finally (cl-return (if default (cons default others)
    785                                         others)))))
    786     (unless reordered
    787       (sly-user-error "Generic `%s' doesn't have any methods!" generic-name))
    788     (cons generic-name
    789           (cdr (assoc (completing-read
    790                        (concat (format prompt-for-method-within-generic generic-name)
    791                                (if default (format " (default %s)" (car default)))
    792                                ": ")
    793                        (mapcar #'car reordered)
    794                        nil t nil nil (car default))
    795                       reordered)))))
    796 
    797 (provide 'sly-completion)
    798 ;;; sly-completion.el ends here