578ed1ba98
Moving all of my Emacs-related files into their own directory at the root of this repository.
304 lines
12 KiB
EmacsLisp
304 lines
12 KiB
EmacsLisp
;;; reason-indent.el --- Indentation functions for ReasonML -*-lexical-binding: t-*-
|
||
|
||
;; Portions Copyright (c) 2015-present, Facebook, Inc. All rights reserved.
|
||
|
||
;;; Commentary:
|
||
|
||
;; Indentation functions for Reason.
|
||
|
||
;;; Code:
|
||
|
||
(defconst reason-re-ident "[[:word:][:multibyte:]_][[:word:][:multibyte:]_[:digit:]]*")
|
||
|
||
(defcustom reason-indent-offset 2
|
||
"Indent Reason code by this number of spaces."
|
||
:type 'integer
|
||
:group 'reason-mode
|
||
:safe #'integerp)
|
||
|
||
(defun reason-looking-back-str (str)
|
||
"Like `looking-back' but for fixed strings rather than regexps.
|
||
Works around some regexp slowness.
|
||
Argument STR string to search for."
|
||
(let ((len (length str)))
|
||
(and (> (point) len)
|
||
(equal str (buffer-substring-no-properties (- (point) len) (point))))))
|
||
|
||
(defun reason-paren-level ()
|
||
"Get the level of nesting inside parentheses."
|
||
(nth 0 (syntax-ppss)))
|
||
|
||
(defun reason-in-str-or-cmnt ()
|
||
"Return whether point is currently inside a string or a comment."
|
||
(nth 8 (syntax-ppss)))
|
||
|
||
(defun reason-rewind-past-str-cmnt ()
|
||
"Rewind past string or comment."
|
||
(goto-char (nth 8 (syntax-ppss))))
|
||
|
||
(defun reason-rewind-irrelevant ()
|
||
"Rewind past irrelevant characters (whitespace of inside comments)."
|
||
(interactive)
|
||
(let ((starting (point)))
|
||
(skip-chars-backward "[:space:]\n")
|
||
(if (reason-looking-back-str "*/") (backward-char))
|
||
(if (reason-in-str-or-cmnt)
|
||
(reason-rewind-past-str-cmnt))
|
||
(if (/= starting (point))
|
||
(reason-rewind-irrelevant))))
|
||
|
||
(defun reason-align-to-expr-after-brace ()
|
||
"Align the expression at point to the expression after the previous brace."
|
||
(save-excursion
|
||
(forward-char)
|
||
;; We don't want to indent out to the open bracket if the
|
||
;; open bracket ends the line
|
||
(when (not (looking-at "[[:blank:]]*\\(?://.*\\)?$"))
|
||
(when (looking-at "[[:space:]]")
|
||
(forward-word 1)
|
||
(backward-word 1))
|
||
(current-column))))
|
||
|
||
(defun reason-align-to-prev-expr ()
|
||
"Align the expression at point to the previous expression."
|
||
(let ((alignment (save-excursion
|
||
(forward-char)
|
||
;; We don't want to indent out to the open bracket if the
|
||
;; open bracket ends the line
|
||
(when (not (looking-at "[[:blank:]]*\\(?://.*\\)?$"))
|
||
(if (looking-at "[[:space:]]")
|
||
(progn
|
||
(forward-word 1)
|
||
(backward-word 1))
|
||
(backward-char))
|
||
(current-column)))))
|
||
(if (not alignment)
|
||
(save-excursion
|
||
(forward-char)
|
||
(forward-line)
|
||
(back-to-indentation)
|
||
(current-column))
|
||
alignment)))
|
||
|
||
;;; Start of a reason binding
|
||
(defvar reason-binding
|
||
(regexp-opt '("let" "type" "module" "fun")))
|
||
|
||
(defun reason-beginning-of-defun (&optional arg)
|
||
"Move backward to the beginning of the current defun.
|
||
|
||
With ARG, move backward multiple defuns. Negative ARG means
|
||
move forward.
|
||
|
||
This is written mainly to be used as `beginning-of-defun-function'.
|
||
Don't move to the beginning of the line. `beginning-of-defun',
|
||
which calls this, does that afterwards."
|
||
(interactive "p")
|
||
(re-search-backward (concat "^\\(" reason-binding "\\)\\_>")
|
||
nil 'move (or arg 1)))
|
||
|
||
(defun reason-end-of-defun ()
|
||
"Move forward to the next end of defun.
|
||
|
||
With argument, do it that many times.
|
||
Negative argument -N means move back to Nth preceding end of defun.
|
||
|
||
Assume that this is called after ‘beginning-of-defun’. So point is
|
||
at the beginning of the defun body.
|
||
|
||
This is written mainly to be used as `end-of-defun-function' for Reason."
|
||
(interactive)
|
||
;; Find the opening brace
|
||
(if (re-search-forward "[{]" nil t)
|
||
(progn
|
||
(goto-char (match-beginning 0))
|
||
;; Go to the closing brace
|
||
(condition-case nil
|
||
(forward-sexp)
|
||
(scan-error
|
||
;; The parentheses are unbalanced; instead of being unable to fontify, just jump to the end of the buffer
|
||
(goto-char (point-max)))))
|
||
;; There is no opening brace, so consider the whole buffer to be one "defun"
|
||
(goto-char (point-max))))
|
||
|
||
(defun reason-rewind-to-beginning-of-current-level-expr ()
|
||
"Rewind to the beginning of the expression on the current level of nesting."
|
||
(interactive)
|
||
(let ((current-level (reason-paren-level)))
|
||
(back-to-indentation)
|
||
(when (looking-at "=>")
|
||
(reason-rewind-irrelevant)
|
||
(back-to-indentation))
|
||
(while (> (reason-paren-level) current-level)
|
||
(backward-up-list)
|
||
(back-to-indentation))))
|
||
|
||
(defun reason-mode-indent-line ()
|
||
"Indent current line."
|
||
(interactive)
|
||
(let ((indent
|
||
(save-excursion
|
||
(back-to-indentation)
|
||
;; Point is now at beginning of current line
|
||
(let* ((level (reason-paren-level))
|
||
(baseline
|
||
;; Our "baseline" is one level out from the indentation of the expression
|
||
;; containing the innermost enclosing opening bracket. That
|
||
;; way if we are within a block that has a different
|
||
;; indentation than this mode would give it, we still indent
|
||
;; the inside of it correctly relative to the outside.
|
||
(if (= 0 level)
|
||
0
|
||
(save-excursion
|
||
(reason-rewind-irrelevant)
|
||
(if (save-excursion
|
||
(reason-rewind-to-beginning-of-current-level-expr)
|
||
(looking-at "<"))
|
||
(progn
|
||
(reason-rewind-to-beginning-of-current-level-expr)
|
||
(current-column))
|
||
(progn
|
||
(backward-up-list)
|
||
(reason-rewind-to-beginning-of-current-level-expr)
|
||
|
||
(cond
|
||
((looking-at "switch")
|
||
(current-column))
|
||
|
||
((looking-at "|")
|
||
(+ (current-column) (* reason-indent-offset 2)))
|
||
|
||
(t
|
||
(let ((current-level (reason-paren-level)))
|
||
(save-excursion
|
||
(while (and (= current-level (reason-paren-level))
|
||
(not (looking-at reason-binding)))
|
||
(reason-rewind-irrelevant)
|
||
(reason-rewind-to-beginning-of-current-level-expr))
|
||
(+ (current-column) reason-indent-offset)))))))))))
|
||
(cond
|
||
;; A function return type is indented to the corresponding function arguments
|
||
((looking-at "=>")
|
||
(+ baseline reason-indent-offset))
|
||
|
||
((reason-in-str-or-cmnt)
|
||
(cond
|
||
;; In the end of the block -- align with star
|
||
((looking-at "*/") (+ baseline 1))
|
||
;; Indent to the following shape:
|
||
;; /* abcd
|
||
;; * asdf
|
||
;; */
|
||
;;
|
||
((looking-at "*") (+ baseline 1))
|
||
;; Indent to the following shape:
|
||
;; /* abcd
|
||
;; asdf
|
||
;; */
|
||
;;
|
||
(t (+ baseline (+ reason-indent-offset 1)))))
|
||
|
||
((looking-at "</") (- baseline reason-indent-offset))
|
||
|
||
;; A closing brace is 1 level unindented
|
||
((looking-at "}\\|)\\|\\]")
|
||
(save-excursion
|
||
(reason-rewind-irrelevant)
|
||
(let ((jsx? (reason-looking-back-str ">")))
|
||
(backward-up-list)
|
||
(reason-rewind-to-beginning-of-current-level-expr)
|
||
(cond
|
||
((looking-at "switch") baseline)
|
||
|
||
(jsx? (current-column))
|
||
|
||
(t (- baseline reason-indent-offset))))))
|
||
|
||
;; Doc comments in /** style with leading * indent to line up the *s
|
||
((and (nth 4 (syntax-ppss)) (looking-at "*"))
|
||
(+ 1 baseline))
|
||
|
||
;; If we're in any other token-tree / sexp, then:
|
||
(t
|
||
(or
|
||
;; If we are inside a pair of braces, with something after the
|
||
;; open brace on the same line and ending with a comma, treat
|
||
;; it as fields and align them.
|
||
(when (> level 0)
|
||
(save-excursion
|
||
(reason-rewind-irrelevant)
|
||
(backward-up-list)
|
||
;; Point is now at the beginning of the containing set of braces
|
||
(reason-align-to-expr-after-brace)))
|
||
|
||
(progn
|
||
(back-to-indentation)
|
||
(cond ((looking-at (regexp-opt '("and" "type")))
|
||
baseline)
|
||
((save-excursion
|
||
(reason-rewind-irrelevant)
|
||
(= (point) 1))
|
||
baseline)
|
||
((save-excursion
|
||
(while (looking-at "|")
|
||
(reason-rewind-irrelevant)
|
||
(back-to-indentation))
|
||
(looking-at (regexp-opt '("type"))))
|
||
(+ baseline reason-indent-offset))
|
||
((looking-at "|\\|/[/*]")
|
||
baseline)
|
||
((and (> level 0)
|
||
(save-excursion
|
||
(reason-rewind-irrelevant)
|
||
(backward-up-list)
|
||
(reason-rewind-to-beginning-of-current-level-expr)
|
||
(looking-at "switch")))
|
||
(+ baseline reason-indent-offset))
|
||
((save-excursion
|
||
(reason-rewind-irrelevant)
|
||
(looking-back "[{;,\\[(]" (- (point) 2)))
|
||
baseline)
|
||
((and
|
||
(save-excursion
|
||
(reason-rewind-irrelevant)
|
||
(reason-rewind-to-beginning-of-current-level-expr)
|
||
(and (looking-at reason-binding)
|
||
(not (progn
|
||
(forward-sexp)
|
||
(forward-sexp)
|
||
(skip-chars-forward "[:space:]\n")
|
||
(looking-at "=")))))
|
||
(not (save-excursion
|
||
(skip-chars-backward "[:space:]\n")
|
||
(reason-looking-back-str "=>"))))
|
||
(save-excursion
|
||
(reason-rewind-irrelevant)
|
||
(backward-sexp)
|
||
(reason-align-to-prev-expr)))
|
||
((save-excursion
|
||
(reason-rewind-irrelevant)
|
||
(looking-back "<\/.*?>" (- (point) 30)))
|
||
baseline)
|
||
(t
|
||
(save-excursion
|
||
(reason-rewind-irrelevant)
|
||
(reason-rewind-to-beginning-of-current-level-expr)
|
||
|
||
(if (looking-at "|")
|
||
baseline
|
||
(+ baseline reason-indent-offset)))))
|
||
;; Point is now at the beginning of the current line
|
||
))))))))
|
||
|
||
(when indent
|
||
;; If we're at the beginning of the line (before or at the current
|
||
;; indentation), jump with the indentation change. Otherwise, save the
|
||
;; excursion so that adding the indentations will leave us at the
|
||
;; equivalent position within the line to where we were before.
|
||
(if (<= (current-column) (current-indentation))
|
||
(indent-line-to indent)
|
||
(save-excursion (indent-line-to indent))))))
|
||
|
||
(provide 'reason-indent)
|
||
|
||
;;; reason-indent.el ends here
|