branch: externals/javaimp
commit 9bdcbda0f3f4dd799d7bfed5c5b8ce4a722c8d73
Author: Filipp Gunbin <[email protected]>
Commit: Filipp Gunbin <[email protected]>
Add javaimp-beginning-of-defun / javaimp-end-of-defun
---
javaimp-parse.el | 130 +++++++++++++++++++++++++++++++++++++------------------
javaimp-tests.el | 13 +++---
javaimp-util.el | 27 +++++++++++-
javaimp.el | 113 +++++++++++++++++++++++++++++++++++++----------
4 files changed, 210 insertions(+), 73 deletions(-)
diff --git a/javaimp-parse.el b/javaimp-parse.el
index 65a31cb422..bf705b0ed7 100644
--- a/javaimp-parse.el
+++ b/javaimp-parse.el
@@ -215,9 +215,9 @@ is unchanged."
(save-excursion
(funcall parser arg))))
'(javaimp--parse-scope-array
- ;; anonymous-class should be before method/stmt because it
+ ;; anon-class should be before method/stmt because it
;; looks similar, but with "new" in front
- javaimp--parse-scope-anonymous-class
+ javaimp--parse-scope-anon-class
javaimp--parse-scope-class
javaimp--parse-scope-simple-stmt
javaimp--parse-scope-method-or-stmt
@@ -252,10 +252,9 @@ brace.")
(buffer-substring-no-properties
keyword-start keyword-end))
:name (javaimp--parse-substr-before-< (caar
arglist))
- :start keyword-start
- :open-brace brace-pos)))))
+ :start keyword-start)))))
-(defun javaimp--parse-scope-simple-stmt (brace-pos)
+(defun javaimp--parse-scope-simple-stmt (_brace-pos)
"Attempts to parse 'simple-statement' scope."
(and (javaimp--parse-skip-back-until)
(or (and (= (char-before (1- (point))) ?-) ; ->
@@ -267,11 +266,10 @@ brace.")
:name (or (match-string 1)
"lambda")
:start (or (match-beginning 1)
- (- (point) 2))
- :open-brace brace-pos)))
+ (- (point) 2)))))
-(defun javaimp--parse-scope-anonymous-class (brace-pos)
- "Attempts to parse 'anonymous-class' scope."
+(defun javaimp--parse-scope-anon-class (brace-pos)
+ "Attempts to parse 'anon-class' scope."
;; skip arg-list and ws
(when (and (progn
(javaimp--parse-skip-back-until)
@@ -285,10 +283,11 @@ brace.")
(setq start (match-beginning 0)
arglist (javaimp--parse-arglist (match-end 0) end t))
(when (= (length arglist) 1)
- (make-javaimp-scope :type 'anonymous-class
- :name (javaimp--parse-substr-before-< (caar
arglist))
- :start start
- :open-brace brace-pos))))))
+ (make-javaimp-scope :type 'anon-class
+ :name
+ (concat "<anon>"
+ (javaimp--parse-substr-before-< (caar
arglist)))
+ :start start))))))
(defun javaimp--parse-scope-method-or-stmt (brace-pos)
"Attempts to parse 'method' or 'statement' scope."
@@ -332,27 +331,30 @@ brace.")
(cdr arglist-region))))
(concat name "(" (mapconcat #'car args ",") ")"))
name)
- :start (point)
- :open-brace brace-pos))))))))
+ :start (point)))))))))
-(defun javaimp--parse-scope-array (brace-pos)
+(defun javaimp--parse-scope-array (_brace-pos)
"Attempts to parse 'array' scope."
(and (javaimp--parse-skip-back-until)
(member (char-before) '(?, ?\]))
(make-javaimp-scope :type 'array
:name ""
- :start nil
- :open-brace brace-pos)))
+ :start nil)))
(defun javaimp--parse-scopes (count)
- "Attempts to parse COUNT enclosing scopes at point. Returns most
-nested one, with its parents sets accordingly. If COUNT is nil
-then goes all the way up. Examines and sets property
-'javaimp-parse-scope' at each scope's open brace. If neither of
-functions in `javaimp--parse-scope-hook' return non-nil then the
-property value is set to the symbol `unknown'. Additionally, if
-a scope is recognized, but any of its parents is 'unknown', then
-it's set to 'unknown' too."
+ "Attempts to parse COUNT enclosing scopes at point.
+Returns most nested one, with its parents sets accordingly. If
+COUNT is nil then goes all the way up.
+
+Examines and sets property 'javaimp-parse-scope' at each scope's
+open brace. If neither of functions in
+`javaimp--parse-scope-hook' return non-nil then the property
+value is set to the symbol `unknown'. Additionally, if a scope
+is recognized, but any of its parents is 'unknown', then it's set
+to 'unknown' too.
+
+If point is inside of any comment/string then this function does
+nothing."
(let ((state (syntax-ppss))
res)
(unless (syntax-ppss-context state)
@@ -364,9 +366,11 @@ it's set to 'unknown' too."
(when (= (char-after) ?{)
(let ((scope (get-text-property (point) 'javaimp-parse-scope)))
(unless scope
- (setq scope (or (run-hook-with-args-until-success
- 'javaimp--parse-scope-hook (point))
- 'unknown))
+ (setq scope (run-hook-with-args-until-success
+ 'javaimp--parse-scope-hook (point)))
+ (if scope
+ (setf (javaimp-scope-open-brace scope) (point))
+ (setq scope 'unknown))
(put-text-property (point) (1+ (point))
'javaimp-parse-scope scope))
(push scope res)
@@ -377,6 +381,7 @@ it's set to 'unknown' too."
(let (parent reset-tail)
(while res
(if reset-tail
+ ;; overwrite property value with `unknown'
(when (javaimp-scope-p (car res))
(let ((pos (javaimp-scope-open-brace (car res))))
(put-text-property pos (1+ pos) 'javaimp-parse-scope
'unknown)))
@@ -391,10 +396,10 @@ it's set to 'unknown' too."
parent)))
(defun javaimp--parse-all-scopes ()
- "Entry point to the scope parsing. Parses scopes in this buffer
-which are after `javaimp--parse-dirty-pos', if it points
-anywhere. Makes it point nowhere when done."
- (unless javaimp--parse-dirty-pos
+ "Parses all scopes in this buffer which are after
+`javaimp--parse-dirty-pos', if it points anywhere. Makes it
+point nowhere when done."
+ (unless javaimp--parse-dirty-pos ;init on first use
(setq javaimp--parse-dirty-pos (point-min-marker))
(javaimp--parse-setup-buffer))
(when (marker-position javaimp--parse-dirty-pos)
@@ -419,6 +424,29 @@ anywhere. Makes it point nowhere when done."
(setq-local multibyte-syntax-as-symbol t)
(add-hook 'after-change-functions #'javaimp--parse-update-dirty-pos))
+(defun javaimp--parse-enclosing-scope (&optional pred)
+ "Return innermost enclosing scope matching PRED."
+ (with-syntax-table javaimp-syntax-table
+ (let ((state (syntax-ppss)))
+ ;; Move out of any comment/string
+ (when (nth 8 state)
+ (goto-char (nth 8 state))
+ (setq state (syntax-ppss)))
+ (catch 'found
+ (while t
+ (let ((res (save-excursion
+ (javaimp--parse-scopes nil))))
+ (when (and (javaimp-scope-p res)
+ (or (null pred)
+ (funcall pred res)))
+ (throw 'found res))
+ ;; Go up until we get something
+ (if (nth 1 state)
+ (progn
+ (goto-char (nth 1 state))
+ (setq state (syntax-ppss)))
+ (throw 'found nil))))))))
+
(defun javaimp--parse-class-abstract-methods ()
(goto-char (point-max))
(let (res)
@@ -500,14 +528,21 @@ either of symbols `normal' or 'static'."
(cons (and start-pos end-pos (cons start-pos end-pos))
class-alist)))
-(defun javaimp--parse-get-all-scopes (&optional pred parent-pred)
- "Return all scopes in the current buffer, optionally filtering
-them with PRED, and their parents with PARENT-PRED. Neither of
-them should move point."
+(defun javaimp--parse-get-all-scopes (&optional beg end pred parent-pred)
+ "Return all scopes in the current buffer between positions BEG
+and END, both exclusive, optionally filtering them with PRED, and
+their parents with PARENT-PRED. Neither of PRED or PARENT-PRED
+should move point. Note that parents may be outside of region
+given by BEG and END. BEG is the LIMIT argument to
+`previous-single-property-change', END defaults to end of
+accessible portion of the buffer."
(javaimp--parse-all-scopes)
- (let ((pos (point-max))
+ (let ((pos (or end (point-max)))
scope res)
- (while (setq pos (previous-single-property-change pos
'javaimp-parse-scope))
+ (while (and (setq pos (previous-single-property-change
+ pos 'javaimp-parse-scope nil beg))
+ (or (not beg)
+ (/= pos beg)))
(setq scope (get-text-property pos 'javaimp-parse-scope))
(when (and (javaimp-scope-p scope)
(or (null pred)
@@ -518,18 +553,31 @@ them should move point."
(push scope res)))
res))
+(defun javaimp--parse-get-enclosing-scope (&optional pred parent-pred)
+ "Return innermost enclosing scope at point, optionally checking
+it with PRED, and its parents with PARENT-PRED."
+ (save-excursion
+ (javaimp--parse-all-scopes))
+ (when-let ((scope (javaimp--parse-enclosing-scope pred)))
+ (setq scope (javaimp--copy-scope scope))
+ (when parent-pred
+ (javaimp--filter-scope-parents scope parent-pred))
+ scope))
+
(defun javaimp--parse-get-class-abstract-methods ()
(javaimp--parse-all-scopes)
(javaimp--parse-class-abstract-methods))
(defun javaimp--parse-get-interface-abstract-methods ()
(let ((interfaces (javaimp--parse-get-all-scopes
- (lambda (scope)
- (javaimp-test-scope-type scope
+ nil nil
+ (lambda (s)
+ (javaimp-test-scope-type s
'(interface) javaimp--classlike-scope-types)))))
(seq-mapcat #'javaimp--parse-interface-abstract-methods
interfaces)))
+
(defmacro javaimp--parse-without-hook (&rest body)
"Execute BODY, temporarily removing
`javaimp--parse-update-dirty-pos' from `after-change-functions'
diff --git a/javaimp-tests.el b/javaimp-tests.el
index 33b46a68a7..e7dc31314b 100644
--- a/javaimp-tests.el
+++ b/javaimp-tests.el
@@ -102,18 +102,18 @@ extends Bar<? extends Baz<? extends Baz2>> {"
enum "Foo")
))
-(ert-deftest javaimp-test--parse-scope-anonymous-class ()
- (javaimp-test--single-parser #'javaimp--parse-scope-anonymous-class
+(ert-deftest javaimp-test--parse-scope-anon-class ()
+ (javaimp-test--single-parser #'javaimp--parse-scope-anon-class
'(" = new Object < Class1 , Class2 > ( 1 + 1 , baz ) {"
- anonymous-class "Object")
+ anon-class "Object")
`(,(subst-char-in-string
? ?\n
" = new Object < Class1 , Class2 > ( 1 + 1 , baz ) {")
- anonymous-class "Object")
+ anon-class "Object")
'(" = (obj.getField()).new Object<Class1, Class2>(1, baz) {"
- anonymous-class "Object")
+ anon-class "Object")
'(" = obj.new Object<>(1, baz) {"
- anonymous-class "Object")
+ anon-class "Object")
))
(ert-deftest javaimp-test--parse-scope-method-or-stmt ()
@@ -230,6 +230,7 @@ import static some_class.fun_2; // comment
(defun javaimp-test--check-named-scopes ()
(let* ((scopes (javaimp--parse-get-all-scopes
+ nil nil
(lambda (s)
(memq (javaimp-scope-type s) '(class interface enum
method)))
(lambda (s)
diff --git a/javaimp-util.el b/javaimp-util.el
index 0379a9e308..7105323768 100644
--- a/javaimp-util.el
+++ b/javaimp-util.el
@@ -32,7 +32,7 @@
(defconst javaimp--all-scope-types
(append
- '(anonymous-class
+ '(anon-class
array
method
simple-statement
@@ -41,7 +41,7 @@
javaimp--classlike-scope-types))
(defconst javaimp--show-scopes-scope-type-abbrevs
- '((anonymous-class . "ac")
+ '((anon-class . "ac")
(statement . "st")
(simple-statement . "ss")
(array . "ar")
@@ -153,6 +153,29 @@ left."
(setq res (memq (javaimp-scope-type scope) parent-types)))
res))
+(defun javaimp--defun-scope-pred (&optional additional)
+ "Return predicate which matches scopes in
+`javaimp--classlike-scope-types'. ADDITIONAL is a list of scope
+types. If it includes `method', then also method leafs are
+included. If it includes `anon-class', then also leafs and
+parents may be anonymous classes."
+ (let ((leaf-types (append javaimp--classlike-scope-types
+ (when (memq 'method additional) '(method))
+ (when (memq 'anon-class additional)
'(anon-class))))
+ (parent-types (append javaimp--classlike-scope-types
+ (when (memq 'anon-class additional)
'(anon-class)))))
+ (lambda (s)
+ (javaimp-test-scope-type s leaf-types parent-types))))
+
+(defun javaimp--scope-same-parent-pred (parent)
+ (if parent
+ (lambda (s)
+ (and (javaimp-scope-parent s)
+ (= (javaimp-scope-open-brace (javaimp-scope-parent s))
+ (javaimp-scope-open-brace parent))))
+ (lambda (s)
+ (not (javaimp-scope-parent s)))))
+
;; Tree
diff --git a/javaimp.el b/javaimp.el
index 43b9cbd38b..2729ccd278 100644
--- a/javaimp.el
+++ b/javaimp.el
@@ -118,9 +118,15 @@
;; (local-set-key (kbd "C-c o") #'javaimp-organize-imports)
;; (local-set-key (kbd "C-c s") #'javaimp-show-scopes)
;;
-;; To enable Imenu (overriding Imenu support from cc-mode):
+;; To override other functions from cc-mode:
;;
;; (setq imenu-create-index-function #'javaimp-imenu-create-index)
+;;
+;; (setq beginning-of-defun-function #'javaimp-beginning-of-defun)
+;; (setq end-of-defun-function #'javaimp-end-of-defun)
+;; (define-key java-mode-map (kbd "C-M-a") #'beginning-of-defun)
+;; (define-key java-mode-map (kbd "C-M-e") #'end-of-defun)
+;;
;;; Code:
@@ -571,13 +577,10 @@ If there's no such directive, then the last resort is just
(defun javaimp--get-buffer-classes ()
"Return fully-qualified names of all class-like scopes in the
-current buffer."
+current buffer. Anonymous classes are not included."
(let ((package (javaimp--parse-get-package))
(scopes (javaimp--parse-get-all-scopes
- (lambda (scope)
- (javaimp-test-scope-type scope
- javaimp--classlike-scope-types
- javaimp--classlike-scope-types)))))
+ nil nil (javaimp--defun-scope-pred))))
(mapcar (lambda (class)
(if package
(concat package "." class)
@@ -721,19 +724,17 @@ in a major mode hook."
entries)))))
(defun javaimp-imenu--get-forest ()
- (let* ((scopes (javaimp--parse-get-all-scopes
- (lambda (scope)
- (javaimp-test-scope-type scope
- `(,@ javaimp--classlike-scope-types method)
- javaimp--classlike-scope-types))))
+ (let* ((defun-scopes
+ (javaimp--parse-get-all-scopes
+ nil nil (javaimp--defun-scope-pred '(method))))
(methods (seq-filter
(lambda (scope)
(eq (javaimp-scope-type scope) 'method))
- scopes))
+ defun-scopes))
(classes (seq-filter
(lambda (scope)
(not (eq (javaimp-scope-type scope) 'method)))
- scopes))
+ defun-scopes))
(top-classes (seq-filter (lambda (s)
(null (javaimp-scope-parent s)))
classes))
@@ -816,22 +817,15 @@ opening brace."
(define-derived-mode javaimp-show-scopes-mode special-mode "Javaimp Show
Scopes"
(setq next-error-function #'javaimp-show-scopes-next-error))
-(defun javaimp-show-scopes (&optional show-all)
- "Show scopes in *javaimp-scopes* buffer, with clickable text.
-By default, the scopes are only those which appear in
-Imenu (`javaimp-imenu-create-index' is responsible for that), but
-with prefix arg, show all scopes."
- (interactive "P")
+(defun javaimp-show-scopes ()
+ "Show scopes in *javaimp-scopes* buffer."
+ (interactive)
(let ((scopes
(save-excursion
(save-restriction
(widen)
(javaimp--parse-get-all-scopes
- (unless show-all
- (lambda (scope)
- (javaimp-test-scope-type scope
- `(,@ javaimp--classlike-scope-types method)
- javaimp--classlike-scope-types)))))))
+ nil nil (javaimp--defun-scope-pred '(method anon-class))))))
(source-buf (current-buffer))
(source-default-dir default-directory)
(buf (get-buffer-create "*javaimp-scopes*")))
@@ -890,6 +884,77 @@ with prefix arg, show all scopes."
+;; Navigation
+
+(defun javaimp-beginning-of-defun (arg)
+ "Function to be used as `beginning-of-defun-function'."
+ (if (zerop arg)
+ t
+ (when (> arg 0) (setq arg (1- arg)))
+ (when-let* ((tmp (javaimp--get-sibling-defuns))
+ (prev-idx (or (car tmp) -1))
+ (siblings (cdr tmp))
+ (target-idx (- prev-idx arg)))
+ (when (and (>= target-idx 0)
+ (< target-idx (length siblings)))
+ (goto-char (javaimp-scope-open-brace
+ (nth target-idx siblings)))))))
+
+(defun javaimp-end-of-defun ()
+ "Function to be used as `end-of-defun-function'."
+ (ignore-errors
+ (goto-char
+ (scan-lists (point) 1 0))))
+
+(defun javaimp--get-sibling-defuns ()
+ "Return list of the form (PREV-INDEX . SIBLINGS), where SIBLINGS
+is a list of all sibling defun scopes. PREV-INDEX is the index
+of the \"previous\" (relative to point) scope in this list, or
+nil."
+ (save-excursion
+ (save-restriction
+ (widen)
+ (let* ((pos (point))
+ (defun-pred (javaimp--defun-scope-pred '(method anon-class)))
+ (enc (javaimp--parse-get-enclosing-scope defun-pred))
+ (parent
+ (if (and enc (eq (javaimp-scope-type enc) 'method))
+ ;; We're inside a method, and need to look at
+ ;; sibling defuns within same parent (it's ok for
+ ;; parent to be nil)
+ (javaimp-scope-parent enc)
+ ;; We're either inside a type (but not within its
+ ;; nested defuns), or just at top-level. Look at
+ ;; defuns whose parent is enc.
+ enc))
+ (sibling-pred (javaimp--scope-same-parent-pred parent))
+ (siblings
+ (javaimp--parse-get-all-scopes
+ ;; beg/end are not strictly needed, pred is enough, but
+ ;; provide them for effectiveness
+ (and parent (javaimp-scope-open-brace parent))
+ (and parent (ignore-errors
+ (1- (scan-lists
+ (javaimp-scope-open-brace parent) 1 0))))
+ (lambda (s)
+ (and (funcall defun-pred s)
+ (funcall sibling-pred s)))))
+ (prev
+ (if (and enc (eq (javaimp-scope-type enc) 'method))
+ enc
+ ;; try to find previous defun
+ (seq-find (lambda (s)
+ (< (javaimp-scope-open-brace s) pos))
+ (reverse siblings)))))
+ (cons (and prev
+ (seq-position siblings prev
+ (lambda (s1 s2)
+ (= (javaimp-scope-open-brace s1)
+ (javaimp-scope-open-brace s2)))))
+ siblings)))))
+
+
+
;; Misc
(defun javaimp-flush-cache ()