You cannot select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
284 lines
13 KiB
EmacsLisp
284 lines
13 KiB
EmacsLisp
;;; phpinspect-eldoc.el --- PHP parsing and completion package -*- lexical-binding: t; -*-
|
|
|
|
;; Copyright (C) 2021-2023 Free Software Foundation, Inc
|
|
|
|
;; Author: Hugo Thunnissen <devel@hugot.nl>
|
|
;; Keywords: php, languages, tools, convenience
|
|
;; Version: 0
|
|
|
|
;; This program is free software; you can redistribute it and/or modify
|
|
;; it under the terms of the GNU General Public License as published by
|
|
;; the Free Software Foundation, either version 3 of the License, or
|
|
;; (at your option) any later version.
|
|
|
|
;; This program is distributed in the hope that it will be useful,
|
|
;; but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
;; GNU General Public License for more details.
|
|
|
|
;; You should have received a copy of the GNU General Public License
|
|
;; along with this program. If not, see <https://www.gnu.org/licenses/>.
|
|
|
|
;;; Commentary:
|
|
|
|
;;; Code:
|
|
(require 'phpinspect-util)
|
|
(require 'phpinspect-meta)
|
|
(require 'phpinspect-token-predicates)
|
|
(require 'phpinspect-resolve)
|
|
(require 'phpinspect-buffer)
|
|
|
|
(eval-when-compile
|
|
(phpinspect--declare-log-group 'eldoc))
|
|
|
|
(defvar phpinspect-eldoc-word-width 14
|
|
"The maximum width of words in eldoc strings.")
|
|
|
|
(cl-defstruct (phpinspect-eldoc-query (:constructor phpinspect-make-eldoc-query))
|
|
(point 0
|
|
:type integer
|
|
:documentation "Position in buffer for which to provide hints")
|
|
(buffer nil
|
|
:type phpinspect-buffer))
|
|
|
|
|
|
(cl-defgeneric phpinspect-eld-strategy-supports (strategy query context)
|
|
"Should return non-nil if STRATEGY should be deployed for QUERY
|
|
and CONTEXT. All strategies must implement this method.")
|
|
|
|
(cl-defgeneric phpinspect-eld-strategy-execute (strategy query context)
|
|
"Should return an object for which `phpinspect-eldoc-string' is implemented.")
|
|
|
|
(cl-defgeneric phpinspect-eldoc-string (response)
|
|
"Should return a string to be displayed by eldoc. This needs to
|
|
be implemented for return values of `phpinspect-eld-strategy-execute'")
|
|
|
|
(cl-defstruct (phpinspect-eld-attribute (:constructor phpinspect-make-eld-attribute))
|
|
"Eldoc strategy for object attributes.")
|
|
|
|
(cl-defmethod phpinspect-eld-strategy-supports
|
|
((_strat phpinspect-eld-attribute) (_q phpinspect-eldoc-query) (rctx phpinspect--resolvecontext))
|
|
(phpinspect-attrib-p (car (last (phpinspect--resolvecontext-subject rctx)))))
|
|
|
|
(cl-defmethod phpinspect-eld-strategy-execute
|
|
((_strat phpinspect-eld-attribute) (_q phpinspect-eldoc-query) (rctx phpinspect--resolvecontext))
|
|
(let ((attrib (car (last (phpinspect--resolvecontext-subject rctx))))
|
|
type-before)
|
|
(setf (phpinspect--resolvecontext-subject rctx) (butlast (phpinspect--resolvecontext-subject rctx)))
|
|
(setq type-before (phpinspect-resolve-type-from-context rctx))
|
|
|
|
(when type-before
|
|
(let ((class (phpinspect-project-get-class-extra-or-create
|
|
(phpinspect--resolvecontext-project rctx)
|
|
type-before))
|
|
(attribute-name (cadadr attrib))
|
|
variable method result)
|
|
(when attribute-name
|
|
(cond ((phpinspect-static-attrib-p attrib)
|
|
(setq variable (phpinspect--class-get-variable class attribute-name))
|
|
|
|
(if (and variable
|
|
(or (phpinspect--variable-static-p variable)
|
|
(phpinspect--variable-const-p variable)))
|
|
(setq result variable)
|
|
(setq method (phpinspect--class-get-static-method
|
|
class (phpinspect-intern-name attribute-name)))
|
|
(when method
|
|
(setq result (phpinspect-make-function-doc :fn method)))))
|
|
((phpinspect-object-attrib-p attrib)
|
|
(setq variable (phpinspect--class-get-variable class attribute-name))
|
|
|
|
(if (and variable
|
|
(phpinspect--variable-vanilla-p variable))
|
|
(setq result variable)
|
|
(setq method (phpinspect--class-get-method
|
|
class (phpinspect-intern-name attribute-name)))
|
|
(when method
|
|
(setq result (phpinspect-make-function-doc :fn method))))))
|
|
result)))))
|
|
|
|
(cl-defstruct (phpinspect-eld-function-args (:constructor phpinspect-make-eld-function-args))
|
|
"Eldoc strategy for function arguments.")
|
|
|
|
(cl-defmethod phpinspect-eld-strategy-supports
|
|
((_strat phpinspect-eld-function-args) (_q phpinspect-eldoc-query) (rctx phpinspect--resolvecontext))
|
|
(let ((parent-token (car (phpinspect--resolvecontext-enclosing-tokens rctx))))
|
|
;; When our subject is inside a list, it is probably an argument of a
|
|
;; function/method call, which is what this strategy provides information for.
|
|
(or (phpinspect-list-p parent-token)
|
|
;; When the last token in our subject is a list, we're either at the end
|
|
;; of a buffer in an incomplete argument list (no closing paren), or in
|
|
;; an empty argument list of a function call.
|
|
(phpinspect-list-p
|
|
(car (last (phpinspect--resolvecontext-subject rctx)))))))
|
|
|
|
(cl-defmethod phpinspect-eld-strategy-execute
|
|
((_strat phpinspect-eld-function-args) (q phpinspect-eldoc-query) (rctx phpinspect--resolvecontext))
|
|
(phpinspect--log "Executing `phpinspect-eld-function-args' strategy")
|
|
(let* ((enclosing-token (car (phpinspect--resolvecontext-enclosing-metadata
|
|
rctx)))
|
|
(left-sibling )
|
|
(statement )
|
|
match-result static arg-list arg-pos)
|
|
|
|
(cond
|
|
;; Subject is a statement
|
|
((and (phpinspect-list-p (car (last (phpinspect--resolvecontext-subject rctx))))
|
|
enclosing-token)
|
|
|
|
(setq left-sibling (phpinspect-meta-find-child-before-recursively
|
|
enclosing-token (phpinspect-eldoc-query-point q))))
|
|
;; Subject is inside an argument list
|
|
((and enclosing-token
|
|
(phpinspect-list-p (phpinspect-meta-token enclosing-token)))
|
|
(setq left-sibling (phpinspect-meta-find-left-sibling enclosing-token)
|
|
statement (list enclosing-token))))
|
|
|
|
(phpinspect--log "Left sibling: %s" (phpinspect-meta-string left-sibling))
|
|
(phpinspect--log "Enclosing parent: %s" (phpinspect-meta-string (phpinspect-meta-parent enclosing-token)))
|
|
|
|
|
|
(while (and left-sibling
|
|
(not (phpinspect-statement-introduction-p (phpinspect-meta-token left-sibling))))
|
|
(unless (phpinspect-comment-p (phpinspect-meta-token left-sibling))
|
|
(push left-sibling statement))
|
|
(setq left-sibling (phpinspect-meta-find-left-sibling left-sibling)))
|
|
|
|
(phpinspect--log "Eldoc statement is: %s" (mapcar #'phpinspect-meta-token statement))
|
|
(phpinspect--log "Enclosing token was: %s" (phpinspect-meta-token enclosing-token))
|
|
|
|
(when enclosing-token
|
|
(cond
|
|
;; Method call
|
|
((setq match-result (phpinspect--match-sequence (last statement 2)
|
|
:f (phpinspect-meta-wrap-token-pred #'phpinspect-attrib-p)
|
|
:f (phpinspect-meta-wrap-token-pred #'phpinspect-list-p)))
|
|
(phpinspect--log "Eldoc context is a method call")
|
|
|
|
(setq arg-list (car (last match-result))
|
|
static (phpinspect-static-attrib-p (phpinspect-meta-token (car match-result)))
|
|
arg-pos (seq-reduce
|
|
(lambda (count meta)
|
|
(if (phpinspect-comma-p (phpinspect-meta-token meta))
|
|
(+ count 1)
|
|
count))
|
|
(phpinspect-meta-find-children-before arg-list (phpinspect-eldoc-query-point q)) 0))
|
|
|
|
;; Set resolvecontext subject to the statement minus the method
|
|
;; name. Point is likely to be at a location inside a method call like
|
|
;; "$a->b->doSomething(". The resulting subject should be "$a->b".
|
|
(setf (phpinspect--resolvecontext-subject rctx)
|
|
(mapcar #'phpinspect-meta-token (butlast statement 2)))
|
|
|
|
|
|
|
|
(when-let* ((type-of-previous-statement
|
|
(phpinspect-resolve-type-from-context rctx))
|
|
(method-name-sym (phpinspect-intern-name (cadadr (phpinspect-meta-token (car match-result)))))
|
|
(class (phpinspect-project-get-class-extra-or-create
|
|
(phpinspect--resolvecontext-project rctx)
|
|
type-of-previous-statement))
|
|
(method (if static
|
|
(phpinspect--class-get-static-method class method-name-sym)
|
|
(phpinspect--class-get-method class method-name-sym))))
|
|
|
|
(when method
|
|
(phpinspect-make-function-doc :fn method :arg-pos arg-pos))))
|
|
((setq match-result (phpinspect--match-sequence (last statement 2)
|
|
:f (phpinspect-meta-wrap-token-pred #'phpinspect-word-p)
|
|
:f (phpinspect-meta-wrap-token-pred #'phpinspect-list-p)))
|
|
(phpinspect--log "Eldoc context is a function call")
|
|
|
|
(setq arg-list (car (last match-result))
|
|
arg-pos (seq-reduce
|
|
(lambda (count meta)
|
|
(if (phpinspect-comma-p (phpinspect-meta-token meta))
|
|
(+ count 1)
|
|
count))
|
|
(phpinspect-meta-find-children-before arg-list (phpinspect-eldoc-query-point q)) 0))
|
|
|
|
(let ((func (phpinspect-project-get-function-or-extra
|
|
(phpinspect--resolvecontext-project rctx)
|
|
(phpinspect-intern-name (cadr (phpinspect-meta-token (car match-result)))))))
|
|
(phpinspect--log "Got past that")
|
|
(when func
|
|
(phpinspect-make-function-doc :fn func :arg-pos arg-pos))))))))
|
|
|
|
(cl-defmethod phpinspect-eldoc-string ((var phpinspect--variable))
|
|
(concat (truncate-string-to-width
|
|
(propertize (concat (if (phpinspect--variable-vanilla-p var) "$" "")
|
|
(phpinspect--variable-name var))
|
|
'face 'font-lock-variable-name-face)
|
|
phpinspect-eldoc-word-width)
|
|
": "
|
|
(propertize (phpinspect--format-type-name (phpinspect--variable-type var))
|
|
'face 'font-lock-type-face)))
|
|
|
|
(cl-defstruct (phpinspect-function-doc (:constructor phpinspect-make-function-doc))
|
|
(fn nil
|
|
:type phpinspect--function)
|
|
(arg-pos nil))
|
|
|
|
(cl-defmethod phpinspect-eldoc-string ((doc phpinspect-function-doc))
|
|
(let ((fn (phpinspect-function-doc-fn doc))
|
|
(arg-pos (phpinspect-function-doc-arg-pos doc))
|
|
(arg-count 0))
|
|
(concat (truncate-string-to-width
|
|
(phpinspect--function-name fn) phpinspect-eldoc-word-width) ": ("
|
|
(mapconcat
|
|
(lambda (arg)
|
|
(let ((doc-string
|
|
(concat "$" (truncate-string-to-width
|
|
(car arg) phpinspect-eldoc-word-width)
|
|
(if (cadr arg) " " "")
|
|
(phpinspect--format-type-name (or (cadr arg) "")))))
|
|
(when (and arg-pos (= arg-count arg-pos))
|
|
(setq doc-string
|
|
(propertize
|
|
doc-string 'face 'eldoc-highlight-function-argument)))
|
|
(setq arg-count (+ arg-count 1))
|
|
doc-string))
|
|
(phpinspect--function-arguments fn)
|
|
", ")
|
|
"): "
|
|
(propertize
|
|
(phpinspect--format-type-name (phpinspect--function-return-type fn))
|
|
'face 'font-lock-type-face))))
|
|
|
|
(defvar phpinspect-eldoc-strategies (list (phpinspect-make-eld-attribute)
|
|
(phpinspect-make-eld-function-args))
|
|
"The eldoc strategies that phpinspect is currently allowed to
|
|
employ. Strategies are queried in the order of this list. See
|
|
also `phpinspect-eldoc-query-execute'.")
|
|
|
|
(cl-defmethod phpinspect-eldoc-query-execute ((query phpinspect-eldoc-query))
|
|
(let* ((buffer (phpinspect-eldoc-query-buffer query))
|
|
(point (phpinspect-eldoc-query-point query))
|
|
(buffer-map (phpinspect-buffer-parse-map buffer))
|
|
(rctx (phpinspect-get-resolvecontext buffer-map point)))
|
|
(phpinspect-buffer-update-project-index buffer)
|
|
(catch 'matched
|
|
(dolist (strategy phpinspect-eldoc-strategies)
|
|
(when (phpinspect-eld-strategy-supports strategy query rctx)
|
|
(phpinspect--log "Found matching eldoc strategy. Executing...")
|
|
(throw 'matched (phpinspect-eld-strategy-execute strategy query rctx)))))))
|
|
|
|
(defun phpinspect-eldoc-function ()
|
|
"An `eldoc-documentation-function` implementation for PHP files.
|
|
|
|
Ignores `eldoc-argument-case` and `eldoc-echo-area-use-multiline-p`.
|
|
|
|
TODO:
|
|
- Respect `eldoc-echo-area-use-multiline-p`
|
|
"
|
|
(catch 'phpinspect-parse-interrupted
|
|
(let ((resp (phpinspect-eldoc-query-execute
|
|
(phpinspect-make-eldoc-query
|
|
:buffer phpinspect-current-buffer
|
|
:point (phpinspect--determine-completion-point)))))
|
|
(when resp
|
|
(phpinspect-eldoc-string resp)))))
|
|
|
|
|
|
(provide 'phpinspect-eldoc)
|