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

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

[nongnu] elpa/scala-mode f17fa95 007/217: approaching a functioning inde


From: ELPA Syncer
Subject: [nongnu] elpa/scala-mode f17fa95 007/217: approaching a functioning indent engine
Date: Sun, 29 Aug 2021 11:30:33 -0400 (EDT)

branch: elpa/scala-mode
commit f17fa95b70796aaf4b8c95fb80753675fbfc82cf
Author: Heikki Vesalainen <heikkivesalainen@yahoo.com>
Commit: Heikki Vesalainen <heikkivesalainen@yahoo.com>

    approaching a functioning indent engine
---
 Test.scala           |  67 ++++++++++++---
 scala-mode-indent.el | 232 +++++++++++++++++++++++++++++++++------------------
 scala-mode-lib.el    |  15 ++++
 scala-mode-map.el    |   2 +
 scala-mode-syntax.el |  86 +++++++++++++++----
 5 files changed, 296 insertions(+), 106 deletions(-)

diff --git a/Test.scala b/Test.scala
index 5fb26be..afbdaca 100644
--- a/Test.scala
+++ b/Test.scala
@@ -1,23 +1,64 @@
-/* fooo bar */
 
-"""foo""".r """bar""".r
+{
 
-"""
+  class A
+      extends B {
+    asd
+  }
+
+  someReallyAbsurdyLongLine
+      thatMadeMeWantToCutIt.
+      yeahMoreOfThatLongStuff map {foo =>
+                                     asdasd
+                                         
+    
+  )
+
+  /*
+   * asd asd 
+   */
+  
+  
+  def myVeryLongMethodName(x: String,
+                           y: String)
+                          (x: String): Foo[  Alko 
+                                           + Zugurov
+                                           + Bagurov] =
+    {
+      body
+    }
+
+
+  def myVeryLongMethodName
+      (
+        x: String,
+        y: String
+      )(
+        z: String,
+        b: String
+      )
+  
+  
+  /* fooo bar */
+
+  """foo""".r """bar""".r
+
+  """
 qasduote_asdasd a+_
 'f'
 """, val zot "fo\ro"
 
-__
+  __
 
-asdasd_dfasdf_>> _<<
+  asdasd_dfasdf_>> _<<
 
-val x:[Type <% Foo] => '\u123c'
-val y = "foo a\"sdasd"
-" ", " ", "as\"dasd"
-`asdasd`> asdasdasd 
-"asdasd
+  val x:[Type <% Foo] => '\u123c'
+  val y = "foo a\"sdasd"
+      " ", " ", "as\"dasd"
+      `asdasd`> asdasdasd 
+      "asdasd
 
-"""asdasdasd
+  """asdasdasd
 asdasda
 asdasdasd
 asdasdasd"asda"
@@ -25,7 +66,7 @@ asdasdasdasdasd asd""
 asdasd
 asdasdasdasdasd"""asdasdasd
 
-"""asdasd"""asasdasd
+  """asdasd"""asasdasd
 
-"""asdasdasdasdasdlkjhasd
+  """asdasdasdasdasdlkjhasd
 asdasdkj"""
diff --git a/scala-mode-indent.el b/scala-mode-indent.el
index 6bce7cd..3b18b5b 100644
--- a/scala-mode-indent.el
+++ b/scala-mode-indent.el
@@ -13,119 +13,170 @@ indentation will be one or two steps depending on 
context."
   :type 'integer
   :group 'scala)
 
+(defun scala-indent:backward-sexp-to-beginning-of-line ()
+  "Skip sexps backwards until reaches beginning of line (i.e. the
+point is at the first non whitespace or comment character). It
+does not move outside enclosin list. Returns the current point or
+nil if the beginnig of line could not be reached because of
+enclosing list."
+  (let ((code-beg (scala-lib:point-after 
+                   (scala-syntax:beginning-of-code-line))))
+    (ignore-errors 
+      (while (> (point) code-beg)
+        (scala-syntax:backward-sexp)
+        (when (< (point) code-beg) 
+          ;; moved to previous line, set new target
+          (setq code-beg (scala-lib:point-after 
+                          (scala-syntax:beginning-of-code-line))))))
+    (if (> (point) code-beg)
+        nil
+      (point))))
+
 (defun scala-indent:run-on-p (&optional point) 
+  (interactive)
   "Returns t if the current point (or point at 'point) is on a
 line that is a run-on from a previous line."
   (save-excursion
     (when point (goto-char point))
     (scala-syntax:beginning-of-code-line)
-    (not (or (looking-at scala-syntax:mustNotContinue-re)
-             (scala-syntax:looking-back-empty-line-p)
-             (scala-syntax:looking-back-token 
-              scala-syntax:mustTerminate-re)))))
+    (if (looking-at scala-syntax:mustNotContinue-re)
+        nil
+      (scala-syntax:skip-backward-ignorable)
+      (not (or (bobp)
+               (scala-syntax:looking-back-empty-line-p)
+               (scala-syntax:looking-back-token 
+                scala-syntax:mustTerminate-re))))))
 
 ;    (defconst scala-syntax:mustNotTerminate-re
 ;      scala-syntax:reserved-symbols-unsafe-re
 
 (defun scala-indent:goto-run-on-anchor (&optional point)
   "Moves back to the point whose column will be used as the
-anchor relative to which indenting for currnet point (or point
-'point') is calculated. Returns the new point or nil if point is
-not on a run-on line."
+anchor relative to which indenting for current point (or point
+'point') is calculated. Returns the new point or nil if the point
+is not on a run-on line."
   (if (not (scala-indent:run-on-p point))
       nil
     (when point (goto-char point))
     (scala-syntax:beginning-of-code-line)
-    (let ((block-beg (1+ (or (nth 1 (syntax-ppss)) (1- (point-min))))))
-      (while (and (scala-indent:run-on-p)
-                  (> (point) block-beg))
-        ;; move back all parameter groups, if any
-        (scala-syntax:beginning-of-code-line)
-        (scala-syntax:skip-backward-ignorable)
-        (scala-syntax:backward-parameter-groups)))
-    (back-to-indentation)
-;;;      (when (< (point) block-beg)
-;;;          (goto-char block-beg)))
+    (while (and (scala-indent:run-on-p)
+                (scala-syntax:skip-backward-ignorable)
+                (scala-indent:backward-sexp-to-beginning-of-line)))
     (point)))
 
-(defun scala-indent:list-element-line-p (&optional point)
-  "Returns t if the current point (or point 'point') is in a
-list. A list is something that begins with '(' or '[', or 'for
-{'. A list element is preceded by ,"
+(defun scala-indent:list-p (&optional point)
+  "Returns the start of the list, if the current point (or point
+'point') is in a list, or nil. A list must be either enclosed in
+parentheses or start with 'val', 'var' or 'import'."
   (save-excursion
     ;; first check that the previous line ended with ','
     (when point (goto-char point))
-    (beginning-of-line)
-    (let ((list-beg (nth 1 (syntax-ppss))))
-      (if (not (and (scala-syntax:looking-back-token "," 1)
-                    list-beg))
-          ;; not at list element 2..n (list element 1 is not considered)
-          nil
-        (goto-char list-beg)
-        (or (= (char-after list-beg) ?\()
-            (= (char-after list-beg) ?\[)
-            (and (= (char-after list-beg) ?\{)
-                 (scala-syntax:looking-back-token "for")))))))
+    (scala-syntax:beginning-of-code-line)
+    (if (not (scala-syntax:looking-back-token "," 1))
+        nil
+      (goto-char (match-beginning 0))
+      (ignore-errors ; catches when we get at parentheses
+        (while (not (or (bobp)
+                        (looking-at scala-syntax:list-keywords-re)
+                        (scala-syntax:looking-back-empty-line-p)
+                        (scala-syntax:looking-back-token ";")))
+          (scala-syntax:backward-sexp)))
+      (cond ((= (char-syntax (char-before)) ?\()
+             (point))
+            ((looking-at scala-syntax:list-keywords-re)
+             (goto-char (match-end 0)))))))
 
 (defun scala-indent:goto-list-anchor (&optional point)
   "Moves back to the point whose column will be used to indent
 list rows at current point (or point 'point'). Returns the new
 point or nil if the point is not in a list element > 1."
-  (if (not (scala-indent:list-element-line-p point))
-      nil
-    (goto-char (1+ (nth 1 (syntax-ppss point))))
-    (let ((block-beg (point)))
+  (let ((list-beg (scala-indent:list-p (point))))
+    (if (not list-beg)
+        nil
+      (goto-char list-beg)
+      ;; find the first element of the list
       (forward-comment (buffer-size))
-      (if (= (line-number-at-pos (point))
-             (line-number-at-pos block-beg))
-          ;; on same line as block start
-          (progn (goto-char block-beg)
-                 (skip-syntax-forward " "))
-        ;; on different line
-        (back-to-indentation))
-      (point))))
+      (if (= (line-number-at-pos list-beg) 
+             (line-number-at-pos))
+          (goto-char list-beg)
+        (beginning-of-line))
+
+      ;; align list with first non-whitespace character
+      (skip-syntax-forward " "))))
 
 (defun scala-indent:body-p (&optional point)
-  "Return t if current point (or point 'point) is on a line
-that follows = or => (or it's unicode equivalent)"
+  "Returns the position of equals or double arrow symbol if
+current point (or point 'point) is on a line that follows = or
+=> (or it's unicode equivalent), or nil if not."
   (save-excursion
     (when point (goto-char point))
-    (beginning-of-line)
-    (scala-syntax:looking-back-token scala-syntax:body-start-re 2)))
+    (scala-syntax:beginning-of-code-line)
+    (scala-syntax:looking-back-token scala-syntax:body-start-re 3)))
 
 (defun scala-indent:goto-body-anchor (&optional point)
-  (if (not (scala-indent:body-p point))
-      nil
-    (when point (goto-char point))
-    (beginning-of-line)
-    (goto-char (or (nth 1 (syntax-ppss point)) (point-min)))
-    (beginning-of-line)
-    (point)))
+  (let ((declaration-end (scala-indent:body-p point)))
+    (if (not declaration-end)
+        nil
+      (goto-char declaration-end)
+      (when (scala-indent:backward-sexp-to-beginning-of-line)
+        (scala-indent:goto-run-on-anchor))
+      (point))))
 
 (defun scala-indent:goto-block-anchor (&optional point)
   "Moves back to the point whose column will be used as the
 anchor for calculating block indent for current point (or point
-'point'). Returns point or nil, if not inside a block."
-  (let ((block-beg (nth 1 (syntax-ppss point))))
+'point'). Returns point or (point-min) if not inside a block." 
+  (let ((block-beg (nth 1 (syntax-ppss 
+                           (scala-lib:point-after (beginning-of-line))))))
     (if (not block-beg)
         nil
-      (scala-indent:goto-run-on-anchor block-beg))))
-
-(defun scala-indent:parentheses-line-p (&optional point)
-  ""
+      ;; check if the opening paren is the first on the line,
+      ;; if so, it is the anchor. If not, then go back to the
+      ;; start of the line
+      (goto-char block-beg)
+      (scala-syntax:backward-parameter-groups)
+      (if (= (point) (scala-lib:point-after
+                      (scala-syntax:beginning-of-code-line)))
+          (point)
+        (when (scala-indent:backward-sexp-to-beginning-of-line)
+          (scala-indent:goto-run-on-anchor))
+        (point)))))           
+
+(defun scala-indent:open-parentheses-line-p (&optional point)
+  "Returns the position of the first character of the line,
+if the current point (or point 'point') is on a line that starts
+with a parentheses, or nil if not."
   (save-excursion
     (when point (goto-char point))    
     (scala-syntax:beginning-of-code-line)
-    (= (char-syntax (char-after)) ?\()))
+    (if (looking-at "\\s(") (point) nil)))
 
-(defun scala-indent:parentheses-anchor (&optional point)
+(defun scala-indent:goto-open-parentheses-anchor (&optional point)
   "Moves back to the point whose column will be used as the
 anchor for calculating opening parenthesis indent for the current
 point (or point 'point'). Returns point or nil, if line does not
 start with opening parenthesis."
-  (if (not (scala-indent:parentheses-line-p point))
-      nil
-    (scala-indent:goto-run-on-anchor point)))
+  ;; There are four cases we need to consider:
+  ;; 1. parameters on separate line (who would be so mad?).
+  ;; 2. curry parentheses, i.e. 2..n parentheses groups.
+  ;; 3. value body parentheses (follows '=').
+  ;; 4. non-value body parentheses (follows class, trait, new, def, etc).
+  ;; Of these 1. will be handled by run-on indent rule and 3. should
+  ;; be handled by body indent rule.
+  (let ((parentheses-beg (scala-indent:open-parentheses-line-p point)))
+    (if (not parentheses-beg)
+        nil
+      (goto-char parentheses-beg)
+      (cond 
+       ;; case 2
+       ((scala-syntax:looking-back-token "[])]" 1)
+        (scala-syntax:backward-parameter-groups)
+        (point))
+       ;; case 4
+       ((and (= (char-after) ?\{)
+             (not (scala-syntax:looking-back-token "=" 1)))
+        (scala-indent:goto-run-on-anchor)
+        (point))))))
 
 (defun scala-indent:apply-indent-rules (rule-indents &optional point)
   "Evaluates each rule, until one returns non-nil value. Returns
@@ -134,23 +185,37 @@ nothing was applied."
   (if (not rule-indents)
       nil
     (save-excursion
-      (let* ((rule-indent (car rule-indents))
-             (rule (car rule-indent))
-             (indent (cadr rule-indent))
-             (anchor (funcall rule point)))
+      (let* ((pos (scala-syntax:beginning-of-code-line))
+             (rule-indent (car rule-indents))
+             (rule-statement (car rule-indent))
+             (indent-statement (cadr rule-indent))
+             (anchor (funcall rule-statement point))
+             (indent (if (functionp indent-statement)
+                         (funcall indent-statement pos anchor) 
+                       (eval indent-statement))))
         (if anchor
-            (+ (current-column) (eval indent))
+            (+ (current-column) indent)
           (scala-indent:apply-indent-rules (cdr rule-indents)))))))
 
+(defun scala-indent:resolve-block-step (start anchor)
+  "Resolves the appropriate indent step for block line at position
+'start' relative to the block anchor 'anchor'."
+  (if (= (char-syntax (char-after start)) ?\))
+      0 ;; block close parentheses line up with anchor
+    ;; TODO: case blocks
+    scala-indent:step))
+    
 (defun scala-indent:calculate-indent-for-line (&optional point)
   "Calculate the appropriate indent for the current point or the
-point 'point'"
+point 'point'. Returns the new column, or nil if the indent
+cannot be determined."
   (or (scala-indent:apply-indent-rules
-       `((scala-indent:parentheses-anchor 0)
+       `((scala-indent:goto-open-parentheses-anchor 0)
          (scala-indent:goto-run-on-anchor (* 2 scala-indent:step))
          (scala-indent:goto-list-anchor 0)
          (scala-indent:goto-body-anchor scala-indent:step)
-         (scala-indent:goto-block-anchor scala-indent:step))
+         (scala-indent:goto-block-anchor scala-indent:resolve-block-step)
+     )
        point)
       0))
 
@@ -160,12 +225,21 @@ column, if it was at the left margin."
   (if (<= (current-column) (current-indentation))
       (indent-line-to column)
     (save-excursion (indent-line-to column))))
+
+(defun scala-indent:indent-code-line ()
+  "Indent a line of code. Expect to be outside of any comments or
+strings"
+  (let ((indent (scala-indent:calculate-indent-for-line)))
+    (if indent
+        (scala-indent:indent-line-to indent)
+      (message "No indent rule for current line"))))
     
 (defun scala-indent:indent-line ()
   "Indents the current line."
   (interactive)
-  ;; TODO: do nothing if inside string or comment
-  (let ((indent (scala-indent:calculate-indent-for-line)))
-    (when indent
-      (scala-indent:indent-line-to indent))))
-  
+  (let ((state (save-excursion (syntax-ppss (line-beginning-position)))))
+    (if (not (nth 8 state)) ;; 8 = start pos of comment or string, nil if none
+        (scala-indent:indent-code-line)
+      (scala-indent:indent-line-to (current-indentation))
+      nil
+      )))
diff --git a/scala-mode-lib.el b/scala-mode-lib.el
new file mode 100644
index 0000000..2460609
--- /dev/null
+++ b/scala-mode-lib.el
@@ -0,0 +1,15 @@
+;;; scala-mode-lib.el - Major mode for editing scala, common functions
+;;; Copyright (c) 2012 Heikki Vesalainen
+;;; For information on the License, see the LICENSE file
+
+(provide 'scala-mode-lib)
+
+(defmacro scala-lib:column-after (&rest body)
+  `(save-excursion
+     ,@body
+     (current-column)))
+
+(defmacro scala-lib:point-after (&rest body)
+  `(save-excursion
+     ,@body
+     (point)))
diff --git a/scala-mode-map.el b/scala-mode-map.el
index 9ad1758..fc95277 100644
--- a/scala-mode-map.el
+++ b/scala-mode-map.el
@@ -18,6 +18,8 @@
     (scala-mode-map:define-keys 
      keymap
      (([backspace]                'backward-delete-char-untabify)
+      ((kbd "C-M-b")              'scala-syntax:backward-sexp)
+      ([(control c)(control r)]   'scala-indent:goto-block-anchor)
       ;;       ("\r"                       'scala-newline)
       ([(control c)(control c)]   'comment-region)
       ;;       ("}"                        'scala-electric-brace)
diff --git a/scala-mode-syntax.el b/scala-mode-syntax.el
index e04f9df..645d994 100644
--- a/scala-mode-syntax.el
+++ b/scala-mode-syntax.el
@@ -34,7 +34,7 @@
                                             
scala-syntax:upperAndUnderscore-group 
                                             scala-syntax:lower-group 
                                             scala-syntax:digit-group))
-(defconst scala-syntax:opchar-group "!#%&*+/:<=>?@\\\\\\^|~\\-") ;; TODO: Sm, 
So
+(defconst scala-syntax:opchar-group "!#%&*+/:<=>?@\\\\\\^|~-") ;; TODO: Sm, So
 
 ;; Scala delimiters (Chapter 1), but no quotes
 (defconst scala-syntax:delimiter-group ".,;")
@@ -184,7 +184,8 @@
 (defconst scala-syntax:mustNotTerminate-keywords-re
   (regexp-opt '("catch" "else" "extends" "finally"
                 "forSome" "match" "with" "yield") 'words)
-  "Keywords whiOAch cannot end a expression and are infact a sign of run-on.")
+  "Keywords whiOAch cannot end a expression and are infact a sign
+  of run-on.")
 
 (defconst scala-syntax:mustNotTerminate-re
   (concat "\\(" scala-syntax:mustNotTerminate-keywords-re 
@@ -193,7 +194,7 @@
 and are infact a sign of run-on. Reserved-symbols not included.")
 
 (defconst scala-syntax:mustTerminate-re
-  (concat "\\([,;]\\|=>?\\|\\s(\\|" scala-syntax:empty-line-re "\\)")
+  (concat "\\([,;]\\|=>?\\([ ]\\|$\\)\\|\\s(\\|" scala-syntax:empty-line-re 
"\\)")
   "Symbols that must terminate an expression or start a
 sub-expression, i.e the following expression cannot be a
 run-on. This includes only parenthesis, '=', '=>', ',' and ';'
@@ -211,13 +212,23 @@ and the empty line")
                         "protected" "return" "sealed" "throw"
                         "trait" "try" "type" "val" "var" "case") 
                       'words)
-          "\\|\\s)\\)")
+          "\\|[]})]\\)")
   "Keywords that begin an expression and parenthesis that end an
 expression, i.e they cannot be run-on to the previous line even
 if there is no semi in between.")
 
 (defconst scala-syntax:body-start-re 
-  "=>?\\|\u21D2")
+  "\\(=>?\\|\u21D2\\)\\([ ]\\|$\\)"
+  "A regexp for detecting if a line ends with '=', '=>' or the
+  unicode symbol 'double arrow'")
+
+(defconst scala-syntax:continue-body-keywords-re
+  (regexp-opt '("catch" "else" "finally" "forSome" "match" "yield") 'words)
+  "Keywords which continue a statement, but have their own body")
+
+(defconst scala-syntax:list-keywords-re
+  (regexp-opt '("var" "val" "import") 'words)
+  ("Keywords that can start a list"))
 
 (defconst scala-syntax:multiLineStringLiteral-start-re
   "\\(\"\\)\"\"")
@@ -410,14 +421,21 @@ symbol constituents (syntax 3)"
 (defun scala-syntax:beginning-of-code-line ()
   (interactive)
   "Move to the beginning of code on the line, or to the end of
-the line, if the line is empty"
-  (let ((eol (line-end-position)))
-    (beginning-of-line)
-    ;; TODO: check if we are inside a comment and come out of it
-    (forward-comment (buffer-size))
+the line, if the line is empty. Return the new point.
+Not to be called on a line whose start is inside a comment."
+  (beginning-of-line)
+  (let ((eol (line-end-position))
+        (pos (point)))
+
+    (while (and (forward-comment 1)
+                (< (point) eol))
+      (setq pos (point)))
+    ;; Now we are either on a different line or at eol.
+    ;; Pos is the last point one the starting line.
     (if (> (point) eol)
-        eol
-      (skip-syntax-forward " " eol))))
+        (goto-char pos)
+      (skip-syntax-forward " " eol)
+      (point))))
 
 (defun scala-syntax:looking-back-empty-line-p ()
   "Return t if the previous line is empty"
@@ -456,6 +474,46 @@ empty line. Expects to be outside of comment."
         (if (looking-at re) (point) nil)))))
 
 (defun scala-syntax:backward-parameter-groups ()
-  "Move back over all parameter groups to the start of the first one."
-  (while (scala-syntax:looking-back-token "\\s)" 1)
+  "Move back over all parameter groups to the start of the first
+one."
+  (while (scala-syntax:looking-back-token "[])]" 1)
     (backward-list)))
+
+(defun scala-syntax:looking-back-else-if-p ()
+  ;; TODO: rewrite using (scala-syntax:if-skipped 
(scala:syntax:skip-backward-else-if))
+  (save-excursion
+    (if (and (scala-syntax:looking-back-token "\\s)" 1)
+             (backward-list)
+             (prog1 (scala-syntax:looking-back-token "if")
+               (goto-char (match-beginning 0)))
+             (prog1 (scala-syntax:looking-back-token "else")
+               (goto-char (match-beginning 0))))
+        (point) nil)))
+    
+(defun scala-syntax:backward-sexp () 
+  "Move backward one scala expression. It can be: parameter
+  list (value or type), id, reserved symbol, keyword, block, or
+  literal. Delimiters (.,;) and comments are skipped
+  silently. Position is placed at the beginning of the skipped
+  expression."
+  (interactive)
+  ;; emacs knows how to properly skip: lists, varid, capitalid,
+  ;; strings, symbols, chars, quotedid. What we have to handle here is
+  ;; most of all ids made of op chars
+
+  ;; skip comments, whitespace and scala delimiter chars .,; so we
+  ;; will be at the start of something interesting
+  (forward-comment (- (buffer-size)))
+  (while (> 0 (+ (skip-syntax-backward " ")
+                 (skip-chars-backward scala-syntax:delimiter-group))))
+  
+  (if (not (= (char-syntax (char-before)) ?\.))
+      ;; emacs can handle everything but opchars
+      (backward-sexp)
+    ;; just because some char has punctuation syntax, doesn't mean the
+    ;; position has it (since the propertize function can change
+    ;; things. So... let's first try to handle it as punctuation and
+    ;; if we go no success, then we let emacs try
+    (when (= (skip-syntax-backward ".") 0)
+      (backward-sexp))))
+    



reply via email to

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