|
(require 'slime)
|
|
(require 'cl-lib)
|
|
|
|
(defvar slime-c-p-c-init-undo-stack nil)
|
|
|
|
(define-slime-contrib slime-c-p-c
|
|
"ILISP style Compound Prefix Completion."
|
|
(:authors "Luke Gorrie <luke@synap.se>"
|
|
"Edi Weitz <edi@agharta.de>"
|
|
"Matthias Koeppe <mkoeppe@mail.math.uni-magdeburg.de>"
|
|
"Tobias C. Rittweiler <tcr@freebits.de>")
|
|
(:license "GPL")
|
|
(:slime-dependencies slime-parse slime-editing-commands slime-autodoc)
|
|
(:swank-dependencies swank-c-p-c)
|
|
(:on-load
|
|
(push
|
|
`(progn
|
|
(remove-hook 'slime-completion-at-point-functions
|
|
#'slime-c-p-c-completion-at-point)
|
|
(remove-hook 'slime-connected-hook 'slime-c-p-c-on-connect)
|
|
,@(when (featurep 'slime-repl)
|
|
`((define-key slime-mode-map "\C-c\C-s"
|
|
',(lookup-key slime-mode-map "\C-c\C-s"))
|
|
(define-key slime-repl-mode-map "\C-c\C-s"
|
|
',(lookup-key slime-repl-mode-map "\C-c\C-s")))))
|
|
slime-c-p-c-init-undo-stack)
|
|
(add-hook 'slime-completion-at-point-functions
|
|
#'slime-c-p-c-completion-at-point)
|
|
(define-key slime-mode-map "\C-c\C-s" 'slime-complete-form)
|
|
(when (featurep 'slime-repl)
|
|
(define-key slime-repl-mode-map "\C-c\C-s" 'slime-complete-form)))
|
|
(:on-unload
|
|
(while slime-c-p-c-init-undo-stack
|
|
(eval (pop slime-c-p-c-init-undo-stack)))))
|
|
|
|
(defcustom slime-c-p-c-unambiguous-prefix-p t
|
|
"If true, set point after the unambigous prefix.
|
|
If false, move point to the end of the inserted text."
|
|
:type 'boolean
|
|
:group 'slime-ui)
|
|
|
|
(defcustom slime-complete-symbol*-fancy nil
|
|
"Use information from argument lists for DWIM'ish symbol completion."
|
|
:group 'slime-mode
|
|
:type 'boolean)
|
|
|
|
|
|
;; FIXME: this is the old code to display completions. Remove it once
|
|
;; `slime-complete-symbol*' and `slime-fuzzy-complete-symbol' can be
|
|
;; used together with `completion-at-point'.
|
|
|
|
(defvar slime-completions-buffer-name "*Completions*")
|
|
|
|
;; FIXME: can probably use quit-window instead
|
|
(make-variable-buffer-local
|
|
(defvar slime-complete-saved-window-configuration nil
|
|
"Window configuration before we show the *Completions* buffer.
|
|
This is buffer local in the buffer where the completion is
|
|
performed."))
|
|
|
|
(make-variable-buffer-local
|
|
(defvar slime-completions-window nil
|
|
"The window displaying *Completions* after saving window configuration.
|
|
If this window is no longer active or displaying the completions
|
|
buffer then we can ignore `slime-complete-saved-window-configuration'."))
|
|
|
|
(defun slime-complete-maybe-save-window-configuration ()
|
|
"Maybe save the current window configuration.
|
|
Return true if the configuration was saved."
|
|
(unless (or slime-complete-saved-window-configuration
|
|
(get-buffer-window slime-completions-buffer-name))
|
|
(setq slime-complete-saved-window-configuration
|
|
(current-window-configuration))
|
|
t))
|
|
|
|
(defun slime-complete-delay-restoration ()
|
|
(add-hook 'pre-command-hook
|
|
'slime-complete-maybe-restore-window-configuration
|
|
'append
|
|
'local))
|
|
|
|
(defun slime-complete-forget-window-configuration ()
|
|
(setq slime-complete-saved-window-configuration nil)
|
|
(setq slime-completions-window nil))
|
|
|
|
(defun slime-complete-restore-window-configuration ()
|
|
"Restore the window config if available."
|
|
(remove-hook 'pre-command-hook
|
|
'slime-complete-maybe-restore-window-configuration)
|
|
(when (and slime-complete-saved-window-configuration
|
|
(slime-completion-window-active-p))
|
|
(save-excursion (set-window-configuration
|
|
slime-complete-saved-window-configuration))
|
|
(setq slime-complete-saved-window-configuration nil)
|
|
(when (buffer-live-p slime-completions-buffer-name)
|
|
(kill-buffer slime-completions-buffer-name))))
|
|
|
|
(defun slime-complete-maybe-restore-window-configuration ()
|
|
"Restore the window configuration, if the following command
|
|
terminates a current completion."
|
|
(remove-hook 'pre-command-hook
|
|
'slime-complete-maybe-restore-window-configuration)
|
|
(condition-case err
|
|
(cond ((cl-find last-command-event "()\"'`,# \r\n:")
|
|
(slime-complete-restore-window-configuration))
|
|
((not (slime-completion-window-active-p))
|
|
(slime-complete-forget-window-configuration))
|
|
(t
|
|
(slime-complete-delay-restoration)))
|
|
(error
|
|
;; Because this is called on the pre-command-hook, we mustn't let
|
|
;; errors propagate.
|
|
(message "Error in slime-complete-restore-window-configuration: %S"
|
|
err))))
|
|
|
|
(defun slime-completion-window-active-p ()
|
|
"Is the completion window currently active?"
|
|
(and (window-live-p slime-completions-window)
|
|
(equal (buffer-name (window-buffer slime-completions-window))
|
|
slime-completions-buffer-name)))
|
|
|
|
(defun slime-display-completion-list (completions start end)
|
|
(let ((savedp (slime-complete-maybe-save-window-configuration)))
|
|
(with-output-to-temp-buffer slime-completions-buffer-name
|
|
(display-completion-list completions)
|
|
(with-current-buffer standard-output
|
|
(setq completion-base-position (list start end))
|
|
(set-syntax-table lisp-mode-syntax-table)))
|
|
(when savedp
|
|
(setq slime-completions-window
|
|
(get-buffer-window slime-completions-buffer-name)))))
|
|
|
|
(defun slime-display-or-scroll-completions (completions start end)
|
|
(cond ((and (eq last-command this-command)
|
|
(slime-completion-window-active-p))
|
|
(slime-scroll-completions))
|
|
(t
|
|
(slime-display-completion-list completions start end)))
|
|
(slime-complete-delay-restoration))
|
|
|
|
(defun slime-scroll-completions ()
|
|
(let ((window slime-completions-window))
|
|
(with-current-buffer (window-buffer window)
|
|
(if (pos-visible-in-window-p (point-max) window)
|
|
(set-window-start window (point-min))
|
|
(save-selected-window
|
|
(select-window window)
|
|
(scroll-up))))))
|
|
|
|
(defun slime-minibuffer-respecting-message (format &rest format-args)
|
|
"Display TEXT as a message, without hiding any minibuffer contents."
|
|
(let ((text (format " [%s]" (apply #'format format format-args))))
|
|
(if (minibuffer-window-active-p (minibuffer-window))
|
|
(minibuffer-message text)
|
|
(message "%s" text))))
|
|
|
|
(defun slime-maybe-complete-as-filename ()
|
|
"If point is at a string starting with \", complete it as filename.
|
|
Return nil if point is not at filename."
|
|
(when (save-excursion (re-search-backward "\"[^ \t\n]+\\="
|
|
(max (point-min)
|
|
(- (point) 1000)) t))
|
|
(let ((comint-completion-addsuffix '("/" . "\"")))
|
|
(comint-replace-by-expanded-filename)
|
|
t)))
|
|
|
|
|
|
(defun slime-complete-symbol* ()
|
|
"Expand abbreviations and complete the symbol at point."
|
|
;; NB: It is only the name part of the symbol that we actually want
|
|
;; to complete -- the package prefix, if given, is just context.
|
|
(or (slime-maybe-complete-as-filename)
|
|
(slime-expand-abbreviations-and-complete)))
|
|
|
|
(defun slime-c-p-c-completion-at-point ()
|
|
#'slime-complete-symbol*)
|
|
|
|
;; FIXME: factorize
|
|
(defun slime-expand-abbreviations-and-complete ()
|
|
(let* ((end (move-marker (make-marker) (slime-symbol-end-pos)))
|
|
(beg (move-marker (make-marker) (slime-symbol-start-pos)))
|
|
(prefix (buffer-substring-no-properties beg end))
|
|
(completion-result (slime-contextual-completions beg end))
|
|
(completion-set (cl-first completion-result))
|
|
(completed-prefix (cl-second completion-result)))
|
|
(if (null completion-set)
|
|
(progn (slime-minibuffer-respecting-message
|
|
"Can't find completion for \"%s\"" prefix)
|
|
(ding)
|
|
(slime-complete-restore-window-configuration))
|
|
;; some XEmacs issue makes this distinction necessary
|
|
(cond ((> (length completed-prefix) (- end beg))
|
|
(goto-char end)
|
|
(insert-and-inherit completed-prefix)
|
|
(delete-region beg end)
|
|
(goto-char (+ beg (length completed-prefix))))
|
|
(t nil))
|
|
(cond ((and (member completed-prefix completion-set)
|
|
(slime-length= completion-set 1))
|
|
(slime-minibuffer-respecting-message "Sole completion")
|
|
(when slime-complete-symbol*-fancy
|
|
(slime-complete-symbol*-fancy-bit))
|
|
(slime-complete-restore-window-configuration))
|
|
;; Incomplete
|
|
(t
|
|
(when (member completed-prefix completion-set)
|
|
(slime-minibuffer-respecting-message
|
|
"Complete but not unique"))
|
|
(when slime-c-p-c-unambiguous-prefix-p
|
|
(let ((unambiguous-completion-length
|
|
(cl-loop for c in completion-set
|
|
minimizing (or (cl-mismatch completed-prefix c)
|
|
(length completed-prefix)))))
|
|
(goto-char (+ beg unambiguous-completion-length))))
|
|
(slime-display-or-scroll-completions completion-set
|
|
beg
|
|
(max (point) end)))))))
|
|
|
|
(defun slime-complete-symbol*-fancy-bit ()
|
|
"Do fancy tricks after completing a symbol.
|
|
\(Insert a space or close-paren based on arglist information.)"
|
|
(let ((arglist (slime-retrieve-arglist (slime-symbol-at-point))))
|
|
(unless (eq arglist :not-available)
|
|
(let ((args
|
|
;; Don't intern these symbols
|
|
(let ((obarray (make-vector 10 0)))
|
|
(cdr (read arglist))))
|
|
(function-call-position-p
|
|
(save-excursion
|
|
(backward-sexp)
|
|
(equal (char-before) ?\())))
|
|
(when function-call-position-p
|
|
(if (null args)
|
|
(execute-kbd-macro ")")
|
|
(execute-kbd-macro " ")
|
|
(when (and (slime-background-activities-enabled-p)
|
|
(not (minibuffer-window-active-p (minibuffer-window))))
|
|
(slime-echo-arglist))))))))
|
|
|
|
(cl-defun slime-contextual-completions (beg end)
|
|
"Return a list of completions of the token from BEG to END in the
|
|
current buffer."
|
|
(let ((token (buffer-substring-no-properties beg end)))
|
|
(cond
|
|
((and (< beg (point-max))
|
|
(string= (buffer-substring-no-properties beg (1+ beg)) ":"))
|
|
;; Contextual keyword completion
|
|
(let ((completions
|
|
(slime-completions-for-keyword token
|
|
(save-excursion
|
|
(goto-char beg)
|
|
(slime-parse-form-upto-point)))))
|
|
(when (cl-first completions)
|
|
(cl-return-from slime-contextual-completions completions))
|
|
;; If no matching keyword was found, do regular symbol
|
|
;; completion.
|
|
))
|
|
((and (>= (length token) 2)
|
|
(string= (cl-subseq token 0 2) "#\\"))
|
|
;; Character name completion
|
|
(cl-return-from slime-contextual-completions
|
|
(slime-completions-for-character token))))
|
|
;; Regular symbol completion
|
|
(slime-completions token)))
|
|
|
|
(defun slime-completions (prefix)
|
|
(slime-eval `(swank:completions ,prefix ',(slime-current-package))))
|
|
|
|
(defun slime-completions-for-keyword (prefix buffer-form)
|
|
(slime-eval `(swank:completions-for-keyword ,prefix ',buffer-form)))
|
|
|
|
(defun slime-completions-for-character (prefix)
|
|
(cl-labels ((append-char-syntax (string) (concat "#\\" string)))
|
|
(let ((result (slime-eval `(swank:completions-for-character
|
|
,(cl-subseq prefix 2)))))
|
|
(when (car result)
|
|
(list (mapcar #'append-char-syntax (car result))
|
|
(append-char-syntax (cadr result)))))))
|
|
|
|
|
|
;;; Complete form
|
|
|
|
(defun slime-complete-form ()
|
|
"Complete the form at point.
|
|
This is a superset of the functionality of `slime-insert-arglist'."
|
|
(interactive)
|
|
;; Find the (possibly incomplete) form around point.
|
|
(let ((buffer-form (slime-parse-form-upto-point)))
|
|
(let ((result (slime-eval `(swank:complete-form ',buffer-form))))
|
|
(if (eq result :not-available)
|
|
(error "Could not generate completion for the form `%s'" buffer-form)
|
|
(progn
|
|
(just-one-space (if (looking-back "\\s(" (1- (point)))
|
|
0
|
|
1))
|
|
(save-excursion
|
|
(insert result)
|
|
(let ((slime-close-parens-limit 1))
|
|
(slime-close-all-parens-in-sexp)))
|
|
(save-excursion
|
|
(backward-up-list 1)
|
|
(indent-sexp)))))))
|
|
|
|
(provide 'slime-c-p-c)
|
|
|