;;; 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 ""))) (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