emacs-elpa-diffs
[Top][All Lists]
Advanced

[Date Prev][Date Next][Thread Prev][Thread Next][Date Index][Thread Index]

[nongnu] elpa/scala-mode c5ab657 160/217: Merge pull request #82 from Iv


From: ELPA Syncer
Subject: [nongnu] elpa/scala-mode c5ab657 160/217: Merge pull request #82 from IvanMalison/support_imenu
Date: Sun, 29 Aug 2021 11:31:04 -0400 (EDT)

branch: elpa/scala-mode
commit c5ab65732c000baf08b50da96a839e7aa6620084
Merge: 34888c0 d4885ac
Author: Heikki Vesalainen <heikki.vesalainen@iki.fi>
Commit: Heikki Vesalainen <heikki.vesalainen@iki.fi>

    Merge pull request #82 from IvanMalison/support_imenu
    
    Support imenu
---
 README.md             |  52 +++++++++++++++++++--
 scala-mode2-imenu.el  | 126 ++++++++++++++++++++++++++++++++++++++++++++++++++
 scala-mode2-syntax.el |  91 ++++++++++++++++++++++++++++++++++++
 scala-mode2.el        |  13 ++++--
 4 files changed, 275 insertions(+), 7 deletions(-)

diff --git a/README.md b/README.md
index df0ec19..3a90d00 100644
--- a/README.md
+++ b/README.md
@@ -428,6 +428,37 @@ Very complex scala files may need the following in your 
emacs init (.emacs, etc)
 (setq max-specpdl-size 5000)
 ```
 
+## `beginning-of-defun` and `end-of-defun`
+
+scala-mode2 defines `scala-syntax:beginning-of-definition` and
+`scala-syntax:end-of-definition` which move the cursor forward and
+backward over class, trait, object, def, val, var, and type definitions. These
+functions are assigned to the buffer local variables
+`beginning-of-defun-function` and `end-of-defun-function` which makes
+it so that the `beginning-of-defun` and `end-of-defun` functions behave
+in a way that is appropriate to scala. These functions are not currently able 
to
+support some of the more advanced scala definition types.
+Multiple assignment to variables e.g.
+
+```scala
+val a, b = (1, 2)
+```
+
+are among the assignment types that are not currently supported. In general
+the only types of definition that these functions are likely to support
+are ones that use only a single, simple (but possibly generic) identifier as 
+its identifer.
+
+## imenu
+
+scala-mode2 supports imenu, a library for accessing locations in documents that
+is included in emacs 24. The custom variable `scala-imenu:should-flatten-index`
+controls whether or not the imenu index will be hierarchical or completely 
flat.
+The current iMenu implementation only goes one level deep i.e. nested classes 
are
+not traversed. scala-mode2's imenu support depends heavily on the
+`scala-syntax:end-of-definition` and `scala-syntax:beginning-of-definition`
+functions, and as such, it shares their limitations.
+
 ## Other features
 - highlights only properly formatted string and character constants
 - indenting a code line removes trailing whitespace
@@ -447,16 +478,30 @@ The indenter thinks the second occurrence of `foo` is the 
body of the while.
 To work around this, terminate the while with a semicolon,
 or put a blank line after it.
 
+As mentioned above,
+`scala-syntax:end-of-definition` `scala-syntax:beginning-of-definition`
+don't properly handle any defintions that don't have a simple, single 
+identifier.
+Its likely that they will stumble when presented with some of the more advanced
+and obscure scala definitions out there.
+
+There also seems to be a strange bug with scala-modes2's `end-of-defun` 
integration
+where two functions are skipped instead of just one. 
`scala-syntax:end-of-definition`
+does not have this problem, so if you find this bug bothering you a lot
+you can bind whatever you normally bind to `end-of-defun` to
+`scala-syntax:end-of-definition` in scala mode to alleviate the issue.
+
 ## Future work
 
 - syntax-begin-function for reliably fontifying elements which span
   multiple lines
-- beginning-of-defun, end-of-defun
-- movement commands to move to previous or next definition (val,
-  var, def, class, trait, object)
 - highlight headings and annotations inside Scaladoc specially (use
   underline for headings)
 - highlight variables in string interpolation (scala 2.10)
+- Improve `end-of-defun` and `beginning-of-defun`. In particular,
+  figure out why `end-of-defun` sometimes skips defintions 
+  even though `scala-syntax:end-of-definition` does not and add
+  support for obscure types of val declarations.
 
 All suggestions and especially pull requests are welcomed in github
 https://github.com/hvesalai/scala-mode2
@@ -477,3 +522,4 @@ Contributors and valuable feedback:
 - Nic Ferrier
 - Tillmann Rendel
 - Jim Powers
+- Ivan Malison
diff --git a/scala-mode2-imenu.el b/scala-mode2-imenu.el
new file mode 100644
index 0000000..5eec53a
--- /dev/null
+++ b/scala-mode2-imenu.el
@@ -0,0 +1,126 @@
+;;; scala-mode-imenu.el - Major mode for editing scala
+;;; Copyright (c) 2014 Heikki Vesalainen
+;;; For information on the License, see the LICENSE file
+
+;;; Code:
+
+(require 'scala-mode2-syntax)
+
+;; Make lambdas proper clousures (only in this file)
+(make-local-variable 'lexical-binding)
+(setq lexical-binding t)
+
+(defcustom scala-imenu:should-flatten-index t
+  "Controls whether or not the imenu index is flattened or hierarchical.")
+(defcustom scala-imenu:build-imenu-candidate
+  'scala-imenu:default-build-imenu-candidate
+  "Controls whether or not the imenu index has definition type information.")
+(defcustom scala-imenu:cleanup-hooks nil
+  "Functions that will be run after the construction of each imenu")
+
+(defun scala-imenu:flatten-list (incoming-list &optional predicate)
+  (when (not predicate) (setq predicate 'listp))
+  (cl-mapcan (lambda (x) (if (funcall predicate x)
+                         (scala-imenu:flatten-list x predicate) (list x))) 
incoming-list))
+
+(defun scala-imenu:flatten-imenu-index (index)
+  (cl-mapcan (lambda (x) (if (listp (cdr x))
+                         (scala-imenu:flatten-imenu-index (cdr x))
+                       (list x))) index))
+
+(defun scala-imenu:create-imenu-index ()
+  (let ((imenu-index (cl-mapcar 'scala-imenu:build-imenu-candidates
+                            (scala-imenu:create-index))))
+    (dolist (cleanup-hook scala-imenu:cleanup-hooks)
+      (funcall cleanup-hook))
+    (if scala-imenu:should-flatten-index
+       (scala-imenu:flatten-imenu-index imenu-index)
+      imenu-index)))
+
+(defun scala-imenu:build-imenu-candidates (member-info &optional parents)
+  (if (listp (car member-info))
+      (let* ((current-member-info (car member-info))
+            (child-member-infos (cdr member-info))
+            (current-member-result 
+             (scala-imenu:destructure-for-build-imenu-candidate
+              current-member-info parents))
+            (current-member-name (car current-member-result)))
+       (if child-member-infos
+           (let ((current-member-members 
+                  (scala-imenu:build-child-members 
+                   (append parents `(,current-member-info))
+                   (cdr member-info))))
+             `(,current-member-name . 
+               ,(cons current-member-result current-member-members)))
+         current-member-result))
+    (scala-imenu:destructure-for-build-imenu-candidate member-info parents)))
+
+(defun scala-imenu:build-child-members (parents child-members)
+  (cl-mapcar (lambda (child) (scala-imenu:build-imenu-candidates
+                          child parents)) child-members))
+
+(defun scala-imenu:destructure-for-build-imenu-candidate (member-info parents)
+  (cl-destructuring-bind (member-name definition-type marker) 
+      member-info (funcall scala-imenu:build-imenu-candidate 
+                          member-name definition-type marker parents)))
+
+
+(defun scala-imenu:default-build-imenu-candidate (member-name definition-type
+                                                             marker parents)
+  (let* ((all-names 
+         (append (cl-mapcar (lambda (parent) (car parent)) parents)
+                 `(,member-name)))
+        (member-string (mapconcat 'identity all-names ".")))
+    `(,(format "(%s)%s" definition-type member-string) . ,marker)))
+
+(defun scala-imenu:create-index ()
+  (let ((class nil) (index nil))
+    (goto-char (point-max))
+    (while (setq class (scala-imenu:parse-nested-from-end))
+      (setq index (cons class index)))
+    index))
+
+(defun scala-imenu:parse-nested-from-end ()
+  (let ((last-point (point)) (class-name nil) (definition-type nil))
+    (scala-syntax:beginning-of-definition)
+    ;; We're done if scala-syntax:beginning-of-definition has no effect.
+    (if (eq (point) last-point) nil
+      (progn (looking-at scala-syntax:all-definition-re)
+            (setq class-name (match-string-no-properties 2))
+            (setq definition-type (match-string-no-properties 1)))
+      `(,`(,class-name ,definition-type ,(point-marker)) . 
+       ,(scala-imenu:nested-members)))))
+
+(defun scala-imenu:parse-nested-from-beginning ()
+  (scala-syntax:end-of-definition)
+  (scala-imenu:parse-nested-from-end))
+
+(defun scala-imenu:nested-members ()
+  (let ((start-point (point)))
+    (save-excursion (scala-syntax:end-of-definition)
+                   ;; This gets us inside of the class definition
+                   ;; It seems like there should be a better way
+                   ;; to do this.
+                   (backward-char)
+                   (scala-imenu:get-nested-members start-point))))
+
+(defvar scala-imenu:nested-definition-types '("class" "object" "trait"))
+
+(defun scala-imenu:get-nested-members (parent-start-point)
+  (scala-syntax:beginning-of-definition)
+  (if (< parent-start-point (point))
+      (cons (scala-imenu:get-member-info-at-point)
+           (scala-imenu:get-nested-members parent-start-point))
+      nil))
+
+(defun scala-imenu:get-member-info-at-point ()
+  (looking-at scala-syntax:all-definition-re)
+  (let* ((member-name (match-string-no-properties 2))
+        (definition-type (match-string-no-properties 1)))
+    (if (member definition-type scala-imenu:nested-definition-types) 
+       (save-excursion (scala-imenu:parse-nested-from-beginning))
+      `(,member-name ,definition-type ,(point-marker)))))
+
+
+(provide 'scala-mode2-imenu)
+;;; scala-mode2-imenu.el ends here
diff --git a/scala-mode2-syntax.el b/scala-mode2-syntax.el
index 1eeb109..80cc8cc 100644
--- a/scala-mode2-syntax.el
+++ b/scala-mode2-syntax.el
@@ -899,4 +899,95 @@ not. A list must be either enclosed in parentheses or 
start with
           (when (looking-at scala-syntax:list-keywords-re)
             (goto-char (match-end 0))))))))
 
+;; Functions to help with finding the beginning and end of scala definitions.
+
+(defconst scala-syntax:modifiers-re 
+  (regexp-opt '("override" "abstract" "final" "sealed" "implicit" "lazy"
+                "private" "protected" "case") 'words))
+
+(defconst scala-syntax:whitespace-delimeted-modifiers-re
+  (concat "\\(?:" scala-syntax:modifiers-re "\\(?: *\\)" "\\)*"))
+
+(defconst scala-syntax:definition-words-re 
+  (mapconcat 'regexp-quote '("class" "object" "trait" "val" "var" "def" 
"type") "\\|"))
+
+(defun scala-syntax:build-definition-re (words-re)
+  (concat " *"
+         scala-syntax:whitespace-delimeted-modifiers-re
+         words-re
+         "\\(?: *\\)"
+         "\\(?2:"
+         scala-syntax:id-re
+         "\\)"))
+
+(defconst scala-syntax:all-definition-re
+  (scala-syntax:build-definition-re
+   (concat "\\(?1:" scala-syntax:definition-words-re "\\)")))
+
+;; Functions to help with beginning and end of definitions.
+
+(defun scala-syntax:backward-sexp-forcing ()
+  (condition-case ex (backward-sexp) ('error (backward-char))))
+
+(defun scala-syntax:forward-sexp-or-next-line ()
+  (interactive)
+  (cond ((looking-at "\n") (next-line) (beginning-of-line))
+       (t (forward-sexp))))
+
+(defun scala-syntax:beginning-of-definition ()
+  "This function may not work properly with certain types of scala definitions.
+For example, no care has been taken to support multiple assignments to vals 
such as
+
+val a, b = (1, 2)
+"
+  (interactive)
+  (let ((found-position
+        (save-excursion
+          (scala-syntax:backward-sexp-forcing)
+          (scala-syntax:movement-function-until-re 
scala-syntax:all-definition-re
+                                                   
'scala-syntax:backward-sexp-forcing))))
+    (when found-position (progn (goto-char found-position) 
(back-to-indentation)))))
+
+(defun scala-syntax:end-of-definition ()
+  "This function may not work properly with certain types of scala definitions.
+For example, no care has been taken to support multiple assignments to vals 
such as
+
+val a, b = (1, 2)
+"
+  (interactive)
+  (re-search-forward scala-syntax:all-definition-re)
+  (scala-syntax:find-brace-equals-or-next)
+  (scala-syntax:handle-brace-equals-or-next))
+
+(defun scala-syntax:find-brace-equals-or-next ()
+  (scala-syntax:go-to-pos
+   (save-excursion
+     (scala-syntax:movement-function-until-cond-function
+      (lambda () (or (looking-at "[[:space:]]*[{=]")
+                    (looking-at scala-syntax:all-definition-re)))
+      (lambda () (condition-case ex (scala-syntax:forward-sexp-or-next-line) 
('error nil)))))))
+
+(defun scala-syntax:handle-brace-equals-or-next ()
+  (cond ((looking-at "[[:space:]]*{") (forward-sexp))
+       ((looking-at "[[:space:]]*=") (scala-syntax:forward-sexp-or-next-line)
+        (scala-syntax:handle-brace-equals-or-next))
+       ((looking-at scala-syntax:all-definition-re) nil)
+       (t (scala-syntax:forward-sexp-or-next-line)
+          (scala-syntax:handle-brace-equals-or-next))))
+
+(defun scala-syntax:movement-function-until-re (re movement-function)
+  (save-excursion
+    (scala-syntax:movement-function-until-cond-function
+     (lambda () (looking-at re)) movement-function)))
+
+(defun scala-syntax:movement-function-until-cond-function (cond-function 
movement-function)
+  (let ((last-point (point)))
+    (if (not (funcall cond-function))
+       (progn (funcall movement-function)
+              (if (equal last-point (point)) nil
+                (scala-syntax:movement-function-until-cond-function
+                 cond-function movement-function))) last-point)))
+
+(defun scala-syntax:go-to-pos (pos) (when pos (goto-char pos)))
+
 (provide 'scala-mode2-syntax)
diff --git a/scala-mode2.el b/scala-mode2.el
index efe2a68..44ab399 100644
--- a/scala-mode2.el
+++ b/scala-mode2.el
@@ -12,6 +12,7 @@
 (require 'scala-mode2-fontlock)
 (require 'scala-mode2-map)
 (require 'scala-mode2-sbt)
+(require 'scala-mode2-imenu)
 
 ;; Tested only for emacs 24
 (unless (<= 24 emacs-major-version)
@@ -108,7 +109,10 @@ When started, runs `scala-mode-hook'.
    'indent-line-function
    'fixup-whitespace
    'delete-indentation
-   'indent-tabs-mode)
+   'indent-tabs-mode
+   'imenu-create-index-function
+   'beginning-of-defun-function
+   'end-of-defun-function)
 
   (add-hook 'syntax-propertize-extend-region-functions
             'scala-syntax:propertize-extend-region)
@@ -142,12 +146,13 @@ When started, runs `scala-mode-hook'.
         fixup-whitespace                'scala-indent:fixup-whitespace
         delete-indentation              'scala-indent:join-line
         indent-tabs-mode                nil
-        )
+       beginning-of-defun-function     #'scala-syntax:beginning-of-definition
+       end-of-defun-function           #'scala-syntax:end-of-definition
+       imenu-create-index-function     #'scala-imenu:create-imenu-index)
   (use-local-map scala-mode-map)
   ;; add indent functionality to some characters
   (scala-mode-map:add-remove-indent-hook)
-  (scala-mode-map:add-self-insert-hooks)
-)
+  (scala-mode-map:add-self-insert-hooks))
 
 ;; Attach .scala files to the scala-mode
 ;;;###autoload



reply via email to

[Prev in Thread] Current Thread [Next in Thread]