(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 " "Edi Weitz " "Matthias Koeppe " "Tobias C. Rittweiler ") (: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)