Update of /project/clim-desktop/cvsroot/clim-desktop
In directory clnet:/tmp/cvs-serv22639
Modified Files:
swine.lisp swine-cmds.lisp
Log Message:
Added new position-aware parameter hinting and experimental class
initarg hinting for (make-instance)-forms. Requires recent Swank.
--- /project/clim-desktop/cvsroot/clim-desktop/swine.lisp 2006/03/30 14:38:19 1.2
+++ /project/clim-desktop/cvsroot/clim-desktop/swine.lisp 2006/04/23 15:34:12 1.3
@@ -25,10 +25,23 @@
(in-package :climacs-lisp-syntax)
+;; Convenience functions:
+
(defun buffer-substring (buffer start end)
- "Convenience function."
+ "Return a string of the contents of buffer from `start' to
+`end'."
(coerce (buffer-sequence buffer start end) 'string))
+(defun unlisted (obj)
+ (if (listp obj)
+ (first obj)
+ obj))
+
+(defun listed (obj)
+ (if (listp obj)
+ obj
+ (list obj)))
+
(defun definition-at-mark (mark syntax)
"Return the text of the definition at mark."
(let ((m (clone-mark mark)))
@@ -419,21 +432,24 @@
(show-swine-note-counts notes (second result))
(when notes (show-swine-notes notes (name buffer) "")))))
-(defun split-lambda-list-on-keywords (lambda-list)
- "Return an alist keying lambda list keywords of `lambda-list'
+;;; Parameter hinting code.
+;;; -----------------------
+
+(defun split-arglist-on-keywords (arglist)
+ "Return an alist keying lambda list keywords of `arglist'
to the symbols affected by the keywords."
(let ((sing-result '())
- (env (position '&environment lambda-list)))
+ (env (position '&environment arglist)))
(when env
- (push (list '&environment (elt lambda-list (1+ env))) sing-result)
- (setf lambda-list (remove-if (constantly t) lambda-list :start env :end (+ env 2))))
- (when (eq '&whole (first lambda-list))
- (push (subseq lambda-list 0 2) sing-result)
- (setf lambda-list (cddr lambda-list)))
+ (push (list '&environment (elt arglist (1+ env))) sing-result)
+ (setf arglist (remove-if (constantly t) arglist :start env :end (+ env 2))))
+ (when (eq '&whole (first arglist))
+ (push (subseq arglist 0 2) sing-result)
+ (setf arglist (cddr arglist)))
(do ((llk '(&mandatory &optional &key &allow-other-keys &aux &rest &body))
- (args (if (member (first lambda-list) +cl-lambda-list-keywords+)
- lambda-list
- (cons '&mandatory lambda-list))
+ (args (if (member (first arglist) +cl-arglist-keywords+)
+ arglist
+ (cons '&mandatory arglist))
(cdr args))
(chunk '())
(result '()))
@@ -446,97 +462,410 @@
(setf chunk (list (car args))))
(push (car args) chunk)))))
-(defparameter +cl-lambda-list-keywords+
+(defparameter +cl-arglist-keywords+
'(&whole &optional &rest &key &allow-other-keys &aux &body &environment))
-(defun affected-symbols-in-arglist (arglist index &optional preceeding-arg)
- "Return a list of the symbols of `arglist' that would be
- affected by entering a new argument at position `index'. Index
- 0 is just after the operator and before any
- arguments. `Preceeding-arg' is either nil or a symbol of the
- argument preceeding the one about to be written. Only
- mandatory, &optional, &rest, &body and &key-arguments are
- supported, and complex argument lists from macros may not be
- interpreted correctly."
- (let ((split-arglist (split-lambda-list-on-keywords arglist)))
- (flet ((get-args (keyword)
- (rest (assoc keyword split-arglist))))
- (cond ((> (length (get-args '&mandatory))
- index)
+(defun find-optional-argument-values (arglist provided-args &optional
+ (split-arglist
+ (split-arglist-on-keywords
+ arglist)))
+ "Return an association list mapping symbols of optional or
+ keyword arguments from `arglist' to the specified values in
+ `provided-args'. `Split-arglist' should be either a split
+ arglist or nil, in which case it will be calculated from
+ `arglist'."
+ ;; First we check whether any optional arguments have even been
+ ;; provided.
+ (flet ((get-args (keyword)
+ (rest (assoc keyword split-arglist))))
+ (let* ((mandatory-args-count (length (get-args '&mandatory)))
+ (optional-args-count (length (get-args '&optional)))
+ (keyword-args-count (length (get-args '&key)))
+ (provided-args-count (length provided-args))
+ (nonmandatory-args-count (+ keyword-args-count
+ optional-args-count)))
+ (when (> provided-args-count
+ mandatory-args-count)
+ ;; We have optional arguments.
+ (let (
+ ;; Find the part of the provided arguments that concern
+ ;; optional arguments.
+ (opt-args-values (subseq provided-args
+ mandatory-args-count
+ (min provided-args-count
+ nonmandatory-args-count)))
+ ;; Find the part of the provided arguments that concern
+ ;; keyword arguments.
+ (keyword-args-values (subseq provided-args
+ (min (+ mandatory-args-count
+ optional-args-count)
+ provided-args-count))))
+ (append (mapcar #'cons
+ (get-args '&optional)
+ opt-args-values)
+
+ (loop
+ ;; Loop over the provided keyword symbols and
+ ;; values in the argument list. Note that
+ ;; little checking is done to ensure that the
+ ;; given symbols are valid - this is not a
+ ;; compiler, so extra mappings do not
+ ;; matter.
+ for (keyword value) on keyword-args-values by #'cddr
+ if (keywordp keyword)
+ collect (let ((argument-symbol
+ (unlisted (find (symbol-name keyword)
+ (get-args '&key)
+ :key #'(lambda (arg)
+ (symbol-name (unlisted arg)))
+ :test #'string=))))
+ ;; We have to find the associated
+ ;; symbol in the argument list... ugly.
+ (cons argument-symbol
+ value)))))))))
+
+(defun find-affected-simple-arguments (arglist current-arg-index preceding-arg
+ &optional (split-arglist (split-arglist-on-keywords arglist)))
+ "Find the simple arguments of `arglist' that would be affected
+ if an argument was intered at index `current-arg-index' in the
+ arglist. `Preceding-arg-key' should either be nil or the
+ argument directly preceding point. `Split-arglist' should
+ either be a split arglist or nil, in which case `split-arglist'
+ will be computed from `arglist'. This function returns two
+ values: The primary value is a list of symbols that should be
+ emphasized, the secondary value is a list of symbols that
+ should be highlighted."
+ (flet ((get-args (keyword)
+ (rest (assoc keyword split-arglist))))
+ (let ((mandatory-argument-count (length (get-args '&mandatory))))
+ (cond ((> mandatory-argument-count
+ current-arg-index)
;; We are in the main, mandatory, positional arguments.
- (list (elt (get-args '&mandatory) index)))
+ (let ((relevant-arg (elt (get-args '&mandatory)
+ current-arg-index)))
+ ;; We do not handle complex argument lists here, only
+ ;; pure standard arguments.
+ (unless (and (listp relevant-arg)
+ (< current-arg-index mandatory-argument-count))
+ (values nil (list (unlisted relevant-arg))))))
((> (+ (length (get-args '&optional))
(length (get-args '&mandatory)))
- index)
+ current-arg-index)
;; We are in the &optional arguments.
- (list (elt (get-args '&optional)
- (- index
- (length (get-args '&mandatory))))))
- ((let ((body-or-rest-args (or (get-args '&rest)
+ (values nil
+ (list (unlisted (elt (get-args '&optional)
+ (- current-arg-index
+ (length (get-args '&mandatory))))))))
+ (t
+ (let ((body-or-rest-args (or (get-args '&rest)
(get-args '&body)))
- (key-arg (find (symbol-name preceeding-arg)
+ (key-arg (find (format nil "~A" preceding-arg)
(get-args '&key)
:test #'string=
:key #'(lambda (arg)
- (symbol-name (if (listp arg)
- (first arg)
- arg))))))
+ (symbol-name (unlisted arg))))))
;; We are in the &body, &rest or &key arguments.
- (append (list key-arg)
- body-or-rest-args
- ;; Only highlight the &key
- ;; symbol if we are in a position to add a new
- ;; keyword-value pair, and not just in a position to
- ;; specify a value for a keyword.
- (when (and (null key-arg)
- (get-args '&key))
- '(&key)))))))))
+ (values
+ ;; Only emphasize the &key
+ ;; symbol if we are in a position to add a new
+ ;; keyword-value pair, and not just in a position to
+ ;; specify a value for a keyword.
+ (when (and (null key-arg)
+ (get-args '&key))
+ '(&key))
+ (append (when key-arg
+ (list (unlisted key-arg)))
+ body-or-rest-args))))))))
+
+(defun analyze-arglist-impl (arglist current-arg-indices preceding-arg provided-args)
+ "The implementation for `analyze-arglist'."
+ (let* ((split-arglist (split-arglist-on-keywords arglist))
+ (user-supplied-arg-values (find-optional-argument-values
+ arglist
+ provided-args
+ split-arglist))
+ (mandatory-argument-count
+ (length (rest (assoc '&mandatory split-arglist))))
+ (current-arg-index (or (first current-arg-indices) 0))
+ ret-arglist
+ emphasized-symbols
+ highlighted-symbols)
+ ;; First, we find any standard arguments that should be
+ ;; highlighted or emphasized, more complex, destructuring
+ ;; arguments will be handled specially.
+ (multiple-value-bind (es hs)
+ (find-affected-simple-arguments arglist
+ current-arg-index
+ preceding-arg
+ split-arglist)
+ (setf emphasized-symbols es)
+ (setf highlighted-symbols hs))
+ ;; We loop over the arglist and build a new list, and if we
+ ;; have a default value for a given argument, we insert it into
+ ;; the list. Also, whenever we encounter a list in a mandatory
+ ;; argument position, we assume that it is a destructuring
+ ;; arglist and recursively calls `analyze-arglist' on it
+ ;; to find the arglist and emphasized and highlighted symbols for
+ ;; it.
+ (labels ((generate-arglist (arglist)
+ (loop
+ for arg-element in arglist
+ for arg-name = (unlisted arg-element)
+ for index from 0
+
+ with in-&aux ; If non-NIL, we are in the
+ ; &aux parameters that should
+ ; not be displayed.
+
+ with in-&environment ; If non-NIL, the next
+ ; argument is an &environment
+ ; parameter that should not be
+ ; displayed.
+ if (eq arg-element '&aux)
+ do (setf in-&aux t)
+ else if (eq arg-element '&environment)
+ do (setf in-&environment t)
+ else if (and (listp arg-element)
+ (> mandatory-argument-count
+ index)
+ (not in-&environment)
+ (not in-&aux))
+ collect (multiple-value-bind (arglist
+ sublist-emphasized-symbols
+ sublist-highlighted-symbols)
+ (analyze-arglist arg-element
+ (rest current-arg-indices)
+ preceding-arg
+ (when (< index (length provided-args))
+ (listed (elt provided-args index))))
+ ;; Unless our `current-arg-index' actually
+ ;; refers to this sublist, its highlighted
+ ;; and emphasized symbols are ignored.
+ (if (= index current-arg-index)
+ (if (and (rest current-arg-indices))
+ (setf emphasized-symbols
+ (union (mapcar #'unlisted
+ sublist-emphasized-symbols)
+ emphasized-symbols)
+ highlighted-symbols
+ (union sublist-highlighted-symbols
+ highlighted-symbols))
+ (setf emphasized-symbols
+ (union (mapcar #'unlisted
+ arg-element)
+ emphasized-symbols))))
+ arglist)
+ else if (and (assoc arg-name user-supplied-arg-values)
+ (not in-&environment)
+ (not in-&aux))
+ collect (list arg-name
+ (rest (assoc
+ arg-name
+ user-supplied-arg-values)))
+ else
+ if in-&environment
+ do (setf in-&environment nil)
+ else if (not in-&aux)
+ collect arg-element)))
+ (setf ret-arglist (generate-arglist arglist)))
+ (list ret-arglist emphasized-symbols highlighted-symbols)))
+
+(defun analyze-arglist (arglist current-arg-indices
+ preceding-arg provided-args)
+ "Analyze argument list and provide information for highlighting
+it. `Arglist' is the argument list that is to be analyzed,
+`current-arg-index' is the index where the next argument would be
+written (0 is just after the operator), `preceding-arg' is the
+written argument preceding point and `provided-args' is a list of
+the args already written.
+
+Three values are returned:
+
+* An argument list with values for &optional and &key arguments
+inserted from `provided-args'.
+
+* A list of symbols that should be emphasized.
+
+* A list of symbols that should be highlighted."
+ (apply #'values (analyze-arglist-impl
+ arglist
+ current-arg-indices
+ preceding-arg
+ provided-args)))
+
+(defgeneric arglist-for-form (operator &optional arguments)
+ (:documentation
+ "Return an arglist for `operator'")
+ (:method (operator &optional arguments)
+ (declare (ignore arguments))
+ (swank::arglist operator)))
+
+;; Proof of concept, just to make sure it can be done. We probably
+;; shouldn't use Swank for this. Also, we need a more elegant
+;; interface. Perhaps it could be integrated with the indentation
+;; definition macros, in order to create some sort of
+;; `define-form-traits'-supermacro. That could be cool. Also, that way
+;; various libraries could trivially create a Climacs-extension-file
+;; containing calls to this super-macro that would make Climacs aware
+;; of the libraries indentation- and completion-needs.
+(defmethod arglist-for-form ((operator (eql 'cl:make-instance)) &optional arguments)
+ (let ((arglist (call-next-method)))
+ (if (and (plusp (length arguments))
+ (listp (first arguments))
+ (> (length (first arguments)) 1)
+ (eq (caar arguments) 'cl:quote))
+ (rest (read-from-string (swank::format-arglist-for-echo-area
+ (cons operator arguments)
+ operator)))
+ arglist)))
+
+(defun show-arglist-silent (symbol &optional
+ current-arg-indices
+ preceding-arg arguments)
+ "Display the arglist for `symbol' in the minibuffer, do not
+complain if `symbol' is not bound to a function.
+
+`Current-arg-index' and `preceding-arg' are used to add extra
+information to the arglist display. `Arguments' should be either
+nil or a list of provided arguments in the form housing symbol.
-(defun show-arglist-silent (symbol &optional provided-args-count preceeding-arg)
+Returns NIL if an arglist cannot be displayed."
(when (fboundp symbol)
- (let* ((arglist (swank::arglist symbol))
- (affected-symbols (when provided-args-count
- (affected-symbols-in-arglist
- arglist
- provided-args-count
- preceeding-arg)))
- (arglist-display (apply #'concatenate 'string
- (format nil"(~A" symbol)
- (append (loop for arg in arglist
- for argno from 1
- if (member arg affected-symbols)
- collect (format nil " >~A<" arg)
- else
- collect (format nil " ~A" arg))
- (list ")")))))
- (esa:display-message arglist-display))))
+ (multiple-value-bind (arglist emphasized-symbols highlighted-symbols)
+ (analyze-arglist
+ (arglist-for-form symbol arguments)
+ current-arg-indices
+ preceding-arg
+ arguments)
+ ;; FIXME: This is fairly ugly.
+ (esa:with-minibuffer-stream (minibuffer)
+ (labels ((display-symbol (symbol)
+ (with-text-style
+ (minibuffer
+ `(nil
+ ,(cond ((member symbol
[144 lines skipped]
--- /project/clim-desktop/cvsroot/clim-desktop/swine-cmds.lisp 2006/03/30 14:38:19 1.7
+++ /project/clim-desktop/cvsroot/clim-desktop/swine-cmds.lisp 2006/04/23 15:34:12 1.8
@@ -173,9 +173,16 @@
;; between the mark and the symbol.
(insert-character #\Space)
(backward-object mark)
- (let ((form (form-before syntax (offset mark))))
- (when form
- (show-arglist-for-form mark syntax form)))
+ ;; We must update the syntax in order to reflect any changes to
+ ;; the parse tree our insertion of a space character may have
+ ;; done.
+ (update-syntax (buffer syntax) syntax)
+ ;; Try to find the argument before point, if that is not possibly,
+ ;; find the form that point is in.
+ (let ((immediate-form (or (form-before syntax (offset mark))
+ (form-around syntax (offset mark)))))
+ (when immediate-form
+ (show-arglist-for-form mark syntax (parent immediate-form))))
(forward-object mark)
(clear-completions)))