branch: elpa/scala-mode commit f0cc3d409f866c3de6f7ffccb9b0d68487b4e1de Author: Heikki Vesalainen <heikkivesalai...@yahoo.com> Commit: Heikki Vesalainen <heikkivesalai...@yahoo.com>
working on indent rules --- Example.scala | 31 +++++------ scala-mode-indent.el | 141 +++++++++++++++++++++++++++++++++++++++++++-------- scala-mode-map.el | 1 - scala-mode-syntax.el | 14 +++-- scala-mode.el | 23 +++++---- 5 files changed, 159 insertions(+), 51 deletions(-) diff --git a/Example.scala b/Example.scala index fb315c0..c1aa32a 100644 --- a/Example.scala +++ b/Example.scala @@ -19,7 +19,7 @@ def f(s: String, /* */ val x = foo( zot, // indented relative to '/* */' someThing - map (x => x.length) // indented relative to 'someThing' + map (x => x.length) // indented relative to 'someThing' ) val x = @@ -73,7 +73,7 @@ class Foo( // body here } -trait Leijona( x: Int, +trait Leijona( x: Int with Int, y: Int ) extends Kissa // run-one line, acnhor is 'trait' with Harja // ditto @@ -85,9 +85,10 @@ def someThingReallyLong(having: String, aLot: Int, ofParameters: Boolean): } List("foo", "bar") - map ( s => // 'map' indented as run-on, 's =>' start lambda - s.length - ) + map { s => + s.length // 'map' indented as run-on, 's =>' start lambda + toString + } List("foo") map ( s => // start lambda @@ -115,8 +116,8 @@ class Foo( /* */ k: String ) // at indent anchor column -def f( i: String, j: String, - k: String) // indented acording to previous +def f( /* */ i: String, j: String with Bar, + k: String) // indented acording to previous val f = foo(kissa, kala) @@ -245,22 +246,22 @@ While not couraged, should the opening curly bracket of a block be on a new line specified return value and one step from anchor otherwise. */ -class Foo -{ +class Foo { def foo { - println(zot) + zot + foo } def bar = - { + { zot - } + } val zot = - { - "hello" - } + { +"hello" + } } /* diff --git a/scala-mode-indent.el b/scala-mode-indent.el index 64492c0..6bce7cd 100644 --- a/scala-mode-indent.el +++ b/scala-mode-indent.el @@ -4,8 +4,16 @@ (provide 'scala-mode-indent) +(require 'scala-mode-syntax) +(require 'scala-mode-lib) + +(defcustom scala-indent:step 2 + "The number of spaces an indentation step should be. The actual +indentation will be one or two steps depending on context." + :type 'integer + :group 'scala) + (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 @@ -19,27 +27,34 @@ line that is a run-on from a previous line." ; (defconst scala-syntax:mustNotTerminate-re ; scala-syntax:reserved-symbols-unsafe-re -(defun scala-indent:goto-run-on-beginning () +(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 is calculated. If this row is -not a run on, does nothing." - (when (scala-indent:run-on-p) +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." + (if (not (scala-indent:run-on-p point)) + nil + (when point (goto-char point)) (scala-syntax:beginning-of-code-line) - (let ((block-beg (or (nth 1 (syntax-ppss)) (point-min)))) + (let ((block-beg (1+ (or (nth 1 (syntax-ppss)) (1- (point-min)))))) (while (and (scala-indent:run-on-p) (> (point) block-beg)) - (if (scala-syntax:looking-back-token "\\s)" 1) - (backward-list) - (skip-syntax-backward "^)" (max block-beg (line-beginning-position 0)))))) - (unless (= (char-syntax (char-before)) ?\() - (scala-syntax:beginning-of-code-line)))) + ;; 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))) + (point))) (defun scala-indent:list-element-line-p (&optional point) - "Returns t if the current point is in a list. A list is -something that begins with '(' or '[', or 'for {'. A list element -is preceded by ," + "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 ," (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) @@ -52,12 +67,13 @@ is preceded by ," (and (= (char-after list-beg) ?\{) (scala-syntax:looking-back-token "for"))))))) -(defun scala-indent:goto-list-beginning () +(defun scala-indent:goto-list-anchor (&optional point) "Moves back to the point whose column will be used to indent -lists rows. If this row is not a list element, does nothing" - (interactive) - (when (scala-indent:list-element-line-p) - (goto-char (1+ (nth 1 (syntax-ppss)))) +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))) (forward-comment (buffer-size)) (if (= (line-number-at-pos (point)) @@ -66,5 +82,90 @@ lists rows. If this row is not a list element, does nothing" (progn (goto-char block-beg) (skip-syntax-forward " ")) ;; on different line - (back-to-indentation))))) + (back-to-indentation)) + (point)))) + +(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)" + (save-excursion + (when point (goto-char point)) + (beginning-of-line) + (scala-syntax:looking-back-token scala-syntax:body-start-re 2))) + +(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))) + +(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)))) + (if (not block-beg) + nil + (scala-indent:goto-run-on-anchor block-beg)))) + +(defun scala-indent:parentheses-line-p (&optional point) + "" + (save-excursion + (when point (goto-char point)) + (scala-syntax:beginning-of-code-line) + (= (char-syntax (char-after)) ?\())) + +(defun scala-indent: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))) + +(defun scala-indent:apply-indent-rules (rule-indents &optional point) + "Evaluates each rule, until one returns non-nil value. Returns +the sum of the value and the respective indent step, or nil if +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))) + (if anchor + (+ (current-column) (eval indent)) + (scala-indent:apply-indent-rules (cdr rule-indents))))))) + +(defun scala-indent:calculate-indent-for-line (&optional point) + "Calculate the appropriate indent for the current point or the +point 'point'" + (or (scala-indent:apply-indent-rules + `((scala-indent: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)) + point) + 0)) + +(defun scala-indent:indent-line-to (column) + "Indent the line to column and move cursor to the indent +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-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)))) + diff --git a/scala-mode-map.el b/scala-mode-map.el index 1c620af..9ad1758 100644 --- a/scala-mode-map.el +++ b/scala-mode-map.el @@ -18,7 +18,6 @@ (scala-mode-map:define-keys keymap (([backspace] 'backward-delete-char-untabify) - ([(control c)(control r)] 'scala-indent:goto-list-beginning) ; TODO remove ;; ("\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 6c77a7e..e04f9df 100644 --- a/scala-mode-syntax.el +++ b/scala-mode-syntax.el @@ -216,8 +216,8 @@ and the empty line") expression, i.e they cannot be run-on to the previous line even if there is no semi in between.") -(defconst scala-syntax:double-arrow-re - "=>\\|\u21D2") +(defconst scala-syntax:body-start-re + "=>?\\|\u21D2") (defconst scala-syntax:multiLineStringLiteral-start-re "\\(\"\\)\"\"") @@ -274,10 +274,10 @@ if there is no semi in between.") ;; by default all opchars are punctuation, but they will be ;; modified by syntax-propertize-function to be symbol ;; constituents when a part of varid or capitalid - (dolist (char (mapcar 'identity "#%:<=>@!&*+-/?\\^|~\u21D2\u2190")) ;; TODO: Sm, So + (dolist (char (mapcar 'identity "!#%&*+/:<=>?@^|~-\u21D2\u2190")) ;; TODO: Sm, So (modify-syntax-entry char "." syntab)) - ;; what can I say? It's the escape char. + ;; for clarity, the \ is alone here and not in the string above (modify-syntax-entry ?\\ "." syntab) ;; scala strings cannot span lines, so we mark @@ -413,6 +413,7 @@ symbol constituents (syntax 3)" 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)) (if (> (point) eol) eol @@ -453,3 +454,8 @@ empty line. Expects to be outside of comment." (if (= (point) end) nil (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) + (backward-list))) diff --git a/scala-mode.el b/scala-mode.el index 5038b7b..2dc9b18 100644 --- a/scala-mode.el +++ b/scala-mode.el @@ -18,10 +18,9 @@ (format "The Scala mode has been tested only on Emacs version 23.x (and not your Emacs version %s.%s)" emacs-major-version emacs-minor-version))) -;; Attach .scala files to the scala-mode -(add-to-list 'auto-mode-alist '("\\.scala\\'" . scala-mode)) -(modify-coding-system-alist 'file "\\.scala\\'" 'utf-8) - +(defgroup scala nil + "A programming mode for the Scala language 2.9" + :group 'languages) (defmacro scala-mode:make-local-variables (&rest quoted-names) (cons 'progn (mapcar #'(lambda (quoted-name) `(make-local-variable ,quoted-name)) quoted-names))) @@ -62,7 +61,9 @@ When started, runs `scala-mode-hook'. 'comment-end 'comment-start-skip 'comment-column - 'comment-multi-line) + 'comment-multi-line + 'indent-line-function + 'indent-tabs-mode) (add-hook 'syntax-propertize-extend-region-functions 'scala-syntax:propertize-extend-region) @@ -84,14 +85,14 @@ When started, runs `scala-mode-hook'. comment-column 0 comment-multi-line t ;; TODO: comment-indent-function + + indent-line-function 'scala-indent:indent-line + indent-tabs-mode nil ) (use-local-map scala-mode-map) (turn-on-font-lock) ) - - - - - - +;; Attach .scala files to the scala-mode +(add-to-list 'auto-mode-alist '("\\.scala\\'" . scala-mode)) +(modify-coding-system-alist 'file "\\.scala\\'" 'utf-8)