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 ()

Reply via email to