Skip to content

Commit

Permalink
Merge pull request #15156 from justbur/julia-mode-indent
Browse files Browse the repository at this point in the history
julia-mode: Indentation fixes
  • Loading branch information
yuyichao committed Feb 22, 2016
2 parents 1b7d009 + 0d4de5a commit 20b33da
Show file tree
Hide file tree
Showing 2 changed files with 180 additions and 47 deletions.
44 changes: 44 additions & 0 deletions contrib/julia-mode-tests.el
Original file line number Diff line number Diff line change
Expand Up @@ -109,6 +109,35 @@ if foo
end
end"))

(ert-deftest julia--test-indent-module-keyword ()
"Module should not increase indentation at any level."
(julia--should-indent
"
module
begin
a = 1
end
end"
"
module
begin
a = 1
end
end")
(julia--should-indent
"
begin
module
foo
end
end"
"
begin
module
foo
end
end"))

(ert-deftest julia--test-indent-function ()
"We should indent function bodies."
(julia--should-indent
Expand Down Expand Up @@ -164,6 +193,21 @@ bar"
foo() =
bar"))

(ert-deftest julia--test-indent-operator ()
"We should increase indent after the first trailing operator
but not again after that."
(julia--should-indent
"
foo() |>
bar |>
baz
qux"
"
foo() |>
bar |>
baz
qux"))

(ert-deftest julia--test-indent-ignores-blank-lines ()
"Blank lines should not affect indentation of non-blank lines."
(julia--should-indent
Expand Down
183 changes: 136 additions & 47 deletions contrib/julia-mode.el
Original file line number Diff line number Diff line change
Expand Up @@ -131,6 +131,71 @@ This function provides equivalent functionality, but makes no efforts to optimis
"\\\\"))
(group "'"))))

(defconst julia-hanging-operator-regexp
;; taken from julia-parser.scm
(concat "^[^#\n]+ "
(regexp-opt
'( ;; conditional
"?"
;; assignment
"=" ":=" "+=" "-=" "*=" "/=" "//=" ".//=" ".*=" "./=" "\\=" ".\\="
"^=" ".^=" "÷=" ".÷=" "%=" ".%=" "|=" "&=" "$=" "=>" "<<=" ">>="
">>>=" "~" ".+=" ".-="
;; arrow
"--" "-->" "" "" "" "" "" "" "" "" "" "" "" "" "" ""
"" "" "" "" "" "" "" "" "" "" "" "" "" "" ""
"" "" "" "" "" "" "" "" "" "" "" "" "" "" ""
"" "" "" "" "" "" "" "" "" "" "" "" "" "" "" ""
"" "" "" "" "" "" "" "" "" "" "" "" "" "" "" ""
"" "" "" "" "" "" "" "" "" "" "" "" "" "" "" ""
"" "" "" "" "" "" "" "" "" "" "⬿" "" "" "" "" ""
"" "" "" "" "" "" "" ""
;; or and and
"&&" "||"
;; comparison
">" "<" ">=" "" "<=" "" "==" "===" "" "!=" "" "!==" "" ".>"
".<" ".>=" ".≥" ".<=" ".≤" ".==" ".!=" ".≠" ".=" ".!" "<:" ">:" ""
"" "" "" "" "" "" "" "" "" "" "" "" "" "" "" "" ""
"" "" "" "" "" "" "" "" "" "" "" "" "" "" "" "" ""
"" "" "" "" "" "" "" "" "" "" "" "" "" "" "" "" ""
"" "" "" "" "" "" "" "" "" "" "" "" "" "" "" "" ""
"" "" "" "" "" "" "" "" "" "" "" "" "" "" "" "" ""
"" "" "" "" "" "" "" "" "" "" "" "" "" "" "" "" ""
"" "" "" "" "" "" "" "" "" "" "" "" "" "" "" "" ""
"" "" "" "" "" "" "" "" "" "" "" "" "" "" "" "" ""
"" "" "" "" "" "" "" "" "" "" "" "" "" "" "" "" ""
"" "" "" "" "" "" "" "" "" "" "" "" "" "" "" "" ""
"" "" "⩿" "" "" "" "" "" "" "" "" "" "" "" "" "" ""
"" "" "" "" "" "" "" "" "" "" "" "" "" "" "" "" ""
"" "" "" "" "" "" "" "" "" "" "" "" "" "" "" "" ""
"" "" "" "" "" "" "" "" "" "" "" "" "" "" "" "⪿" ""
"" "" "" "" "" "" "" "" "" "" "" "" "" "" "" "" ""
"" "" "" "" "" "" "" "" "" "" "" "" "" ""
;; pipe, colon
"|>" "<|" ":" ".."
;; plus
"+" "-" "" "" "" "" ".+" ".-" "++" "|" "" "" "$" "" "±" ""
"" "" "" "" "" "" "" "" "" "" "" "" "" "" "" "" ""
"" "" "" "" "" "" "" "" "" "" "" "" "" "" "" "" ""
"" "" "" "" "" "" "" "" ""
;; bitshift
"<<" ">>" ">>>" ".<<" ".>>" ".>>>"
;; times
"*" "/" "./" "÷" "" "%" "" "" "×" ".%" ".*" "\\"
".\\" "&" "" "" "" "" "" "" "" "" "" "" "" "" "" ""
"" "" "" "" "" "" "" "" "" "" "" "" "" "" "" "⦿" ""
"" "" "" "" "" "" "" "" "" "" "" "" "" "" "" "" ""
"" "" "" "" "" "" "" "" "" "" "" "" "" "" "" "" ""
"" ""
;; rational
"//" ".//"
;; power
"^" ".^" "" "" "" "" "" "" "" "" "" "" "" "" "" ""
"" "" "" "" "" "" "" "" "" "" "" "" "" "" "" ""
;; decl, dot
"::" "."))
(regexp-opt '(" #" " \n" "#" "\n"))))

(defconst julia-triple-quoted-string-regex
;; We deliberately put a group on the first and last delimiter, so
;; we can mark these as string delimiters for font-lock.
Expand Down Expand Up @@ -258,7 +323,11 @@ This function provides equivalent functionality, but makes no efforts to optimis

(defconst julia-block-start-keywords
(list "if" "while" "for" "begin" "try" "function" "type" "let" "macro"
"quote" "do" "immutable"))
"quote" "do" "immutable" "module"))

;; For keywords that begin a block without additional indentation
(defconst julia-block-start-keywords-no-indent
(list "module"))

(defconst julia-block-end-keywords
(list "end" "else" "elseif" "catch" "finally"))
Expand Down Expand Up @@ -383,13 +452,6 @@ beginning of the buffer."
(unless (eq (point) (point-min))
(backward-char)))

(defvar julia-max-paren-lookback 400
"When indenting, don't look back more than this
many characters to see if there are unclosed parens.
This variable has a major effect on indent performance if set too
high.")

(defvar julia-max-block-lookback 5000
"When indenting, don't look back more than this
many characters to see if there are unclosed blocks.
Expand All @@ -402,60 +464,87 @@ high.")
containing paren before point, so we can align succeeding code
with it. Returns nil if we're not within nested parens."
(save-excursion
;; Back up to previous line (beginning-of-line was already called)
(backward-char)
(let ((min-pos (max (- (point) julia-max-paren-lookback)
(point-min)))
(open-count 0))
(while (and (> (point) min-pos)
(not (plusp open-count)))

(when (looking-at (rx (any "[" "]" "(" ")")))
(unless (or (julia-in-string) (julia-in-comment))
(cond ((looking-at (rx (any "[" "(")))
(incf open-count))
((looking-at (rx (any "]" ")")))
(decf open-count)))))

(julia--safe-backward-char))

(if (plusp open-count)
(progn (forward-char 2)
(while (looking-at (rx blank))
(forward-char))
(current-column))
nil))))
(beginning-of-line)
(let ((parser-state (syntax-ppss)))
(cond ((nth 3 parser-state) nil) ;; strings
((= (nth 0 parser-state) 0) nil) ;; top level
(t
(ignore-errors ;; return nil if any of these movements fail
(backward-up-list)
(forward-char)
(skip-syntax-forward " ")
(current-column)))))))

(defun julia-prev-line-skip-blank-or-comment ()
"Move point to beginning of previous line skipping blank lines
and lines including only comments. Returns number of lines moved.
A return of -1 signals that we moved to the first line of
the (possibly narrowed) buffer, so there is nowhere else to go."
(catch 'result
(let ((moved 0) this-move)
(while t
(setq this-move (forward-line -1))
(cond
;; moved into comment or blank
((and (= 0 this-move)
(or (looking-at-p "^\\s-*\\(?:#.*\\)*$")
(julia-in-comment)))
(incf moved))
;; success
((= 0 this-move)
(throw 'result (1+ moved)))
;; on first line and in comment
((and (bobp)
(or (looking-at-p "^\\s-*\\(?:#.*\\)*$")
(julia-in-comment)))
(throw 'result -1))
((bobp)
(throw 'result moved))
(t
(throw 'result 0)))))))

(defun julia-indent-hanging ()
"Calculate indentation for lines that follow \"hanging\"
operators (operators that end the previous line) as defined in
`julia-hanging-operator-regexp'. An assignment operator ending
the previous line increases the indent as do the other operators
unless another operator is found two lines up. Previous line
means previous line after skipping blank lines and lines with
only comments."
(let (prev-indent)
(save-excursion
(when (> (julia-prev-line-skip-blank-or-comment) 0)
(setq prev-indent (current-indentation))
(when (looking-at-p julia-hanging-operator-regexp)
(if (and (> (julia-prev-line-skip-blank-or-comment) 0)
(looking-at-p julia-hanging-operator-regexp))
;; two preceding hanging operators => indent same as line
;; above
prev-indent
;; one preceding hanging operator => increase indent from line
;; above
(+ julia-indent-offset prev-indent)))))))

(defun julia-indent-line ()
"Indent current line of julia code."
(interactive)
(let* ((point-offset (- (current-column) (current-indentation))))
(end-of-line)
(indent-line-to
(or
;; If we're inside an open paren, indent to line up arguments.
(save-excursion
(beginning-of-line)
(ignore-errors (julia-paren-indent)))
;; If the previous line ends in =, increase the indent.
(ignore-errors ; if previous line is (point-min)
(save-excursion
(if (and (not (equal (point-min) (line-beginning-position)))
(progn
(forward-line -1)
(end-of-line) (backward-char 1)
(and (equal (char-after (point)) ?=)
(not (julia-in-comment)))))
(+ julia-indent-offset (current-indentation))
nil)))
(julia-paren-indent)
;; indent due to hanging operators or a line ending in =
(julia-indent-hanging)
;; Indent according to how many nested blocks we are in.
(save-excursion
(beginning-of-line)
(forward-to-indentation 0)
(let ((endtok (julia-at-keyword julia-block-end-keywords))
(last-open-block (julia-last-open-block (- (point) julia-max-block-lookback))))
(max 0 (+ (or last-open-block 0)
(if endtok (- julia-indent-offset) 0)))))))
(if (or endtok
(julia-at-keyword julia-block-start-keywords-no-indent))
(- julia-indent-offset) 0)))))))
;; Point is now at the beginning of indentation, restore it
;; to its original position (relative to indentation).
(when (>= point-offset 0)
Expand Down

0 comments on commit 20b33da

Please sign in to comment.