diff options
Diffstat (limited to 'configs/shared/emacs/.emacs.d/elpa/elixir-mode-20180711.1245/elixir-smie.el')
-rw-r--r-- | configs/shared/emacs/.emacs.d/elpa/elixir-mode-20180711.1245/elixir-smie.el | 904 |
1 files changed, 904 insertions, 0 deletions
diff --git a/configs/shared/emacs/.emacs.d/elpa/elixir-mode-20180711.1245/elixir-smie.el b/configs/shared/emacs/.emacs.d/elpa/elixir-mode-20180711.1245/elixir-smie.el new file mode 100644 index 000000000000..c3b524a58175 --- /dev/null +++ b/configs/shared/emacs/.emacs.d/elpa/elixir-mode-20180711.1245/elixir-smie.el @@ -0,0 +1,904 @@ +;;; elixir-smie.el --- Structural syntax support for elixir-mode + +;; Copyright 2011-2015 secondplanet +;; 2013-2015 Samuel Tonini, Matt DeBoard, Andreas Fuchs + +;; This file is not a part of GNU Emacs. + +;; This program is free software; you can redistribute it and/or modify +;; it under the terms of the GNU General Public License as published by +;; the Free Software Foundation; either version 2, or (at your option) +;; any later version. + +;; This program is distributed in the hope that it will be useful, +;; but WITHOUT ANY WARRANTY; without even the implied warranty of +;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +;; GNU General Public License for more details. + +;; You should have received a copy of the GNU General Public License +;; along with this program; if not, write to the Free Software +;; Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. + +;;; Commentary: + +;; Structural syntax support for elixir-mode + +;;; Code: + +(require 'smie) ; Simple minded indentation engine +(require 'cl-lib) ; `cl-flet' + +;; HACK: Patch for Emacs 24.3 smie that fix +;; https://github.com/elixir-lang/emacs-elixir/issues/107. +;; +;; defadvice is used to change the behavior only for elixir-mode. +;; Definition of advice is a definition of corresponding function +;; in Emacs 24.4. +(when (and (= 24 emacs-major-version) + (= 3 emacs-minor-version)) + (defadvice smie-rule-parent (around elixir-mode-patch activate) + (if (not (eq major-mode 'elixir-mode)) + (progn ad-do-it) + (setq ad-return-value + (save-excursion + (goto-char (cadr (smie-indent--parent))) + (cons 'column + (+ (or offset 0) + (smie-indent-virtual))))))) + + (defadvice smie-indent-comment (around elixir-mode-patch activate) + (if (not (eq major-mode 'elixir-mode)) + (progn ad-do-it) + (setq ad-return-value + (and (smie-indent--bolp) + (let ((pos (point))) + (save-excursion + (beginning-of-line) + (and (re-search-forward comment-start-skip (line-end-position) t) + (eq pos (or (match-end 1) (match-beginning 0)))))) + (save-excursion + (forward-comment (point-max)) + (skip-chars-forward " \t\r\n") + (unless + (save-excursion + (let ((next (funcall smie-forward-token-function))) + (or (if (zerop (length next)) + (or (eobp) (eq (car (syntax-after (point))) 5))) + (rassoc next smie-closer-alist)))) + (smie-indent-calculate)))))))) + +(defun elixir-smie-looking-around (back at) + "Check if looking backwards at BACK and forward at AT." + (and (looking-at-p at) (looking-back back))) + +;; Declare variable that we need from the smie package +(defvar smie--parent) + +(defvar elixir-smie-verbose-p nil + "Emit context information about the current syntax state.") + +(defvar elixir-mode-syntax-table + (let ((table (make-syntax-table))) + + ;; Note that ?_ might be better as class "_", but either seems to + ;; work: + (modify-syntax-entry ?| "." table) + (modify-syntax-entry ?- "." table) + (modify-syntax-entry ?+ "." table) + (modify-syntax-entry ?* "." table) + (modify-syntax-entry ?/ "." table) + (modify-syntax-entry ?< "." table) + (modify-syntax-entry ?> "." table) + (modify-syntax-entry ?_ "_" table) + (modify-syntax-entry ?? "w" table) + (modify-syntax-entry ?~ "w" table) + (modify-syntax-entry ?! "_" table) + (modify-syntax-entry ?' "\"'" table) + (modify-syntax-entry ?\" "\"\"" table) + (modify-syntax-entry ?# "<" table) + (modify-syntax-entry ?\n ">" table) + (modify-syntax-entry ?\( "()" table) + (modify-syntax-entry ?\) ")(" table) + (modify-syntax-entry ?\{ "(}" table) + (modify-syntax-entry ?\} "){" table) + (modify-syntax-entry ?\[ "(]" table) + (modify-syntax-entry ?\] ")[" table) + (modify-syntax-entry ?: "_" table) + (modify-syntax-entry ?@ "_" table) + table) + "Elixir mode syntax table.") + +(defconst elixir-smie-grammar + (smie-prec2->grammar + (smie-merge-prec2s + (smie-bnf->prec2 + '((id) + (statements (statement) + (statement ";" statements)) + (statement (non-block-expr "fn" match-statements "end") + (non-block-expr "do" statements "end") + ("if" non-block-expr "do" statements "else" statements "end") + ("if" non-block-expr "do" statements "end") + ("if" non-block-expr "COMMA" "do:" non-block-expr) + ("if" non-block-expr "COMMA" + "do:" non-block-expr "COMMA" + "else:" non-block-expr) + ("try" "do" statements "after" statements "end") + ("try" "do" statements "catch" match-statements "end") + ("try" "do" statements "rescue" match-statements "end") + ("try" "do" statements "end") + ("case" non-block-expr "do" match-statements "end") + ("for" non-block-expr "COMMA" "do:" non-block-expr) + ("for" non-block-expr "COMMA" "into:" non-block-expr "COMMA" "do" statements "end") + ("for" non-block-expr "COMMA" "into:" non-block-expr "COMMA" "do:" non-block-expr) + ("with" non-block-expr "do" statements "else" statements "end") + ("with" non-block-expr "do:" non-block-expr "COMMA" "else:" non-block-expr)) + (non-block-expr (non-block-expr "OP" non-block-expr) + ("(" non-block-expr ")") + ("{" non-block-expr "}") + ("[" non-block-expr "]") + ("STRING")) + (match-statements (match-statement "MATCH-STATEMENT-DELIMITER" + match-statements) + (match-statement)) + (match-statement (non-block-expr "->" statements))) + '((assoc "if" "do:" "else:") + (assoc "COMMA") + (left "OP"))) + + (smie-precs->prec2 + '((left "||") + (left "&&") + (nonassoc "=~" "===" "!==" "==" "!=" "<=" ">=" "<" ">") + (left "+" "-" "<<<" ">>>" "^^^" "~~~" "&&&" "|||") + (left "*" "/")))))) + +(defvar elixir-smie--operator-regexp + (rx (or "<<<" ">>>" "^^^" "~~~" "&&&" "|||" "===" "!==" "==" "!=" "<=" + "=" ">=" "<" ">" "&&" "||" "<>" "++" "--" "//" "/>" "=~" "|>"))) + +(defvar elixir-smie--binary-sequence-regexp + (rx (or "<<" ">>"))) + +(defvar elixir-smie--block-operator-regexp + (rx "->" (0+ nonl))) + +(defvar elixir-smie--oneline-def-operator-regexp + (rx "do:" (0+ nonl))) + +(defvar elixir-smie--spaces-til-eol-regexp + (rx (and (1+ space) eol)) + "Regex representing one or more whitespace characters concluding with eol.") + +(defvar elixir-smie--comment-regexp + (rx (and (0+ space) "#" (0+ not-newline))) + "Regex matching comments.") + +(defvar elixir-smie-indent-basic 2) + +(defmacro elixir-smie-debug (message &rest format-args) + `(progn + (when elixir-smie-verbose-p + (message (format ,message ,@format-args))) + nil)) + +(defun elixir-smie--implicit-semi-p () + (not (or (memq (char-before) '(?\{ ?\[)) + (looking-back elixir-smie--operator-regexp (- (point) 3) t)))) + +(defun elixir-smie-current-line-contains-built-in-keyword-p () + "Return non-nil if the current line contains built in keywords with a `.'" + (save-excursion + (beginning-of-line) + (looking-at ".+\\.\\(case\\|try\\|if\\|rescue\\)"))) + +(defun elixir-smie-last-line-end-with-block-operator-p () + "Return non-nil if the previous line ends with a `->' operator." + (save-excursion + (forward-line -1) + (move-end-of-line 1) + (looking-back elixir-smie--block-operator-regexp (- (point) 3) t))) + +(defun elixir-smie-last-line-start-with-block-operator-p () + (save-excursion + (forward-line -1) + (beginning-of-line) + (looking-at "^\s+->.+$"))) + +(defun elixir-smie-current-line-start-with-pipe-operator-p () + (save-excursion + (beginning-of-line) + (looking-at "^\s*|>.+$"))) + +(defun elixir-smie-last-line-is-assignment-p () + (save-excursion + (forward-line -1) + (beginning-of-line) + (looking-at "^.+=.+$"))) + +(defun elixir-smie-last-line-start-with-pipe-operator-p () + (save-excursion + (forward-line -1) + (beginning-of-line) + (looking-at "^\s*|>.+$"))) + +(defun elixir-smie-line-starts-with-do-colon-p () + (save-excursion + (beginning-of-line) + (looking-at "^\s+do:"))) + +(defun elixir-smie--semi-ends-match () + "Return non-nil if the current line concludes a match block." + (when (not (eobp)) + (save-excursion + ;; Warning: Recursion. + ;; This is easy though. + + ;; 1. If we're at a blank line, move forward a character. This takes us to + ;; the next line. + ;; 2. If we're not at the end of the buffer, call this function again. + ;; (Otherwise, return nil.) + + ;; The point here is that we want to treat blank lines as a single semi- + ;; colon when it comes to detecting the end of match statements. This could + ;; also be handled by a `while' expression or some other looping mechanism. + (cl-flet ((self-call () + (if (< (point) (point-max)) + (elixir-smie--semi-ends-match) + nil))) + (cond + ((and (eolp) (bolp)) + (forward-char) + (self-call)) + ((looking-at elixir-smie--spaces-til-eol-regexp) + (forward-char) + (self-call)) + ;; And if we're NOT on a blank line, move to the end of the line, and see + ;; if we're looking back at a block operator. + (t (move-end-of-line 1) + (and (looking-back elixir-smie--block-operator-regexp) + (not (looking-back ".+fn.+"))))))))) + +(defun elixir-smie--same-line-as-parent (parent-pos child-pos) + "Return non-nil if `child-pos' is on same line as `parent-pos'." + (= (line-number-at-pos parent-pos) (line-number-at-pos child-pos))) + +(defun elixir-smie-forward-token () + (cond + ;; If there is nothing but whitespace between the last token and eol, emit + ;; a semicolon. + ((looking-at elixir-smie--spaces-til-eol-regexp) + (goto-char (match-end 0)) + ";") + ((and (or (looking-at elixir-smie--comment-regexp) + (looking-at "[\n#]")) + (elixir-smie--implicit-semi-p)) + (if (eolp) (forward-char 1) (forward-comment 1)) + ;; Note: `elixir-smie--semi-ends-match' will be called when the point is at + ;; the beginning of a new line. Keep that in mind. + (if (elixir-smie--semi-ends-match) + "MATCH-STATEMENT-DELIMITER" + (if (and (looking-at ".+,$") + (not (> (nth 0 (syntax-ppss)) 0))) + "COMMA" + ";"))) + ((looking-at elixir-smie--block-operator-regexp) + (goto-char (match-end 0)) + "->") + ((looking-at elixir-smie--operator-regexp) + (goto-char (match-end 0)) + "OP") + (t + (let ((token (smie-default-forward-token))) + (unless (or (elixir-smie-empty-string-p token) + (elixir-smie--at-dot-call)) + token))))) + +(defun elixir-smie--at-dot-call () + (and (eq ?w (char-syntax (following-char))) + (eq (char-before) ?.) + (not (eq (char-before (1- (point))) ?.)))) + +(defun elixir-smie-backward-token () + (let ((pos (point))) + (forward-comment (- (point))) + (cond + ((and (> pos (line-end-position)) + (elixir-smie--implicit-semi-p)) + (if (elixir-smie--semi-ends-match) + "MATCH-STATEMENT-DELIMITER" + (if (and (looking-back ",$" (- (point) 3) t) + (not (> (nth 0 (syntax-ppss)) 0))) + "COMMA" + ";"))) + ((looking-back elixir-smie--block-operator-regexp (- (point) 3) t) + (goto-char (match-beginning 0)) + "->") + ((looking-back elixir-smie--binary-sequence-regexp (- (point) 3) t) + (goto-char (match-beginning 0)) + "OP") + ((looking-back elixir-smie--operator-regexp (- (point) 3) t) + (goto-char (match-beginning 0)) + "OP") + (t (let ((token (smie-default-backward-token))) + (unless (or (elixir-smie-empty-string-p token) + (elixir-smie--at-dot-call)) + token)))))) + +(defun verbose-elixir-smie-rules (kind token) + (let ((value (elixir-smie-rules kind token))) + (elixir-smie-debug "%s '%s'; sibling-p:%s parent:%s prev-is-OP:%s hanging:%s == %s" kind token + (ignore-errors (smie-rule-sibling-p)) + (ignore-errors smie--parent) + (ignore-errors (smie-rule-prev-p "OP")) + (ignore-errors (smie-rule-hanging-p)) + value) + value)) + +(defun elixir-smie-rules (kind token) + (pcase (cons kind token) + (`(:list-intro . ";") + -4) + (`(:list-intro . nil) + -4) + (`(:elem . args) + -4) + (`(:before . "COMMA") + (cond + ((and (smie-rule-parent-p "with") + (smie-rule-hanging-p)) + (smie-rule-parent 5)) + ((and (smie-rule-parent-p ";") + (smie-rule-hanging-p)) + (smie-rule-parent)) + ((and (smie-rule-parent-p "COMMA") + (smie-rule-hanging-p)) + (if (save-excursion + (forward-line 1) + (move-beginning-of-line 1) + (looking-at "^.+do:.+$")) + (smie-rule-parent -5) + (smie-rule-parent))) + ((and (smie-rule-parent-p "COMMA") + (not (smie-rule-hanging-p))) + (smie-rule-parent elixir-smie-indent-basic)) + ((smie-rule-parent-p "(") + (smie-rule-parent)) + ((smie-rule-parent-p "if") + (smie-rule-parent)) + ((smie-rule-parent-p "->") + (smie-rule-parent)))) + (`(:after . "COMMA") + (cond + ((and (smie-rule-parent-p ";") + (smie-rule-hanging-p)) + (smie-rule-parent elixir-smie-indent-basic)) + ((and (smie-rule-parent-p "with") + (smie-rule-hanging-p)) + (smie-rule-parent 5)) + ((and (smie-rule-parent-p "{") + (smie-rule-hanging-p)) + (smie-rule-parent elixir-smie-indent-basic)) + ((and (smie-rule-parent-p "[") + (smie-rule-hanging-p)) + 0) + ((smie-rule-parent-p "COMMA") + (smie-rule-parent elixir-smie-indent-basic)) + ((smie-rule-parent-p "->") + (smie-rule-parent elixir-smie-indent-basic)) + (t (smie-rule-parent elixir-smie-indent-basic)))) + (`(:before . "OP") + (cond + ((smie-rule-parent-p "for") + (smie-rule-parent)) + ((and (not (smie-rule-hanging-p)) + (elixir-smie-current-line-start-with-pipe-operator-p) + (elixir-smie-last-line-is-assignment-p)) + (smie-rule-parent)) + ((and (not (smie-rule-hanging-p)) + (elixir-smie-current-line-start-with-pipe-operator-p)) + (cons 'column (elixir-smie--previous-line-indentation))) + ((and (not (smie-rule-hanging-p)) + (smie-rule-prev-p "OP")) + (- elixir-smie-indent-basic)) + ((smie-rule-parent-p "def" "defp" "defmacro" "defmacrop") + (smie-rule-parent)) + (t (smie-rule-parent)))) + (`(:after . "OP") + (cond + ((smie-rule-sibling-p) nil) + ((smie-rule-hanging-p) + (smie-rule-parent elixir-smie-indent-basic)) + ((and (not (smie-rule-sibling-p)) + (not (smie-rule-hanging-p)) + (smie-rule-parent-p "do:")) + (smie-rule-parent)) + ((smie-rule-parent-p ";") + (smie-rule-parent)) + (t (smie-rule-parent (- elixir-smie-indent-basic))))) + (`(:before . "MATCH-STATEMENT-DELIMITER") + (cond + ((and (smie-rule-parent-p "do") + (smie-rule-hanging-p)) + (smie-rule-parent)) + ((and (smie-rule-parent-p "do") + (not (smie-rule-hanging-p))) + (smie-rule-parent elixir-smie-indent-basic)) + ((and (smie-rule-parent-p "fn")) + (smie-rule-parent elixir-smie-indent-basic)) + ;; There is a case when between two line inside a def block + ;; when jumping to the next line and indent, where the cursor + ;; jumps too much in front. + ;; + ;; Example: + ;; def generate_pkg(path, opts) do + ;; name = Path.basename(Path.expand(path)) + ;; + ;; File.mkdir_p!(path) + ;; <- + ;; File.cd! path, fn -> + ;; _generate_pkg(name, opts) + ;; end + ;; end + ((and (smie-rule-parent-p "do") + (not (smie-rule-hanging-p))) + 0) + ((and (not (smie-rule-sibling-p)) + (nth 2 smie--parent) + (smie-rule-hanging-p)) + (smie-rule-parent elixir-smie-indent-basic)) + ((and (not (smie-rule-sibling-p)) + (not (nth 2 smie--parent)) + (smie-rule-hanging-p)) + (smie-rule-parent)))) + (`(:after . "MATCH-STATEMENT-DELIMITER") + (cond + ((and (smie-rule-parent-p "MATCH-STATEMENT-DELIMITER") + (smie-rule-hanging-p) + (smie-rule-sibling-p)) + (smie-rule-parent)) + ((and (smie-rule-parent-p "after") + (smie-rule-hanging-p)) + (smie-rule-parent elixir-smie-indent-basic)) + ;; Correct indentation after a one-line fn definition + ;; Example: + ;; + ;; sum = Enum.reduce(dbms, fn(x, sum) -> x + sum end) + ;; average_dbm = sum / length(addresses) + ((smie-rule-parent-p "fn") + (smie-rule-parent elixir-smie-indent-basic)) + (t + (smie-rule-parent)))) + (`(:before . "fn") + (cond + ((smie-rule-parent-p "(") + (smie-rule-parent)) + (t (smie-rule-parent)))) + (`(:before . "for") + (cond + ((elixir-smie-last-line-end-with-block-operator-p) + (smie-rule-parent elixir-smie-indent-basic)) + ((and (smie-rule-parent-p ";") + (smie-rule-prev-p "OP")) + (smie-rule-parent)) + ((smie-rule-prev-p "OP" "def") + (smie-rule-parent -2)))) + (`(:before . "into:") + (cond + ((smie-rule-parent-p "COMMA") + (smie-rule-parent elixir-smie-indent-basic)))) + (`(:before . "do:") + (cond + ((smie-rule-parent-p "def" "defp" "defmacro" "defmacrop") + (if (save-excursion + (move-beginning-of-line 1) + (looking-at "^\s*do:.+$")) + (smie-rule-parent) + (smie-rule-parent))) + ;; Example + ;; + ;; hi = for i <- list, do: i + ;; # weird spacing now <- Indent + ;; + ;; for i <- list, do: i + ;; IO.puts 'WORKED' <- Indent + ((and (smie-rule-parent-p "for") + (not (smie-rule-hanging-p))) + (smie-rule-parent)) + ((and (smie-rule-parent-p ";") + (not (smie-rule-hanging-p)) + (save-excursion + (move-beginning-of-line 1) + (looking-at "^\s*do:.+$"))) + (if (> (nth 0 (syntax-ppss)) 0) + (smie-rule-parent (- 3)) + (smie-rule-parent elixir-smie-indent-basic))) + ((and (smie-rule-parent-p ";") + (not (smie-rule-hanging-p))) + (if (> (nth 0 (syntax-ppss)) 0) + (smie-rule-parent (- elixir-smie-indent-basic)) + (smie-rule-parent))) + ((and (smie-rule-parent-p "OP") + (not (smie-rule-hanging-p))) + (smie-rule-parent elixir-smie-indent-basic)) + ((and (smie-rule-parent-p "COMMA") + (not (smie-rule-hanging-p))) + (smie-rule-parent elixir-smie-indent-basic)))) + (`(:before . "do") + (cond + ((and (smie-rule-parent-p "for") + (smie-rule-hanging-p)) + (if (save-excursion + (move-beginning-of-line 1) + (looking-at "^.+\sfor\s.+\sdo\s*")) + (smie-rule-parent elixir-smie-indent-basic) + (smie-rule-parent (+ elixir-smie-indent-basic + elixir-smie-indent-basic)))) + ((and (smie-rule-parent-p "case") + (smie-rule-hanging-p)) + (smie-rule-parent elixir-smie-indent-basic)) + ;; There is a case when between two line inside a def block + ;; when jumping to the next line and indent, where the cursor + ;; jumps too much in front. + ;; + ;; Example: + ;; def generate_pkg(path, opts) do + ;; name = Path.basename(Path.expand(path)) + ;; + ;; File.mkdir_p!(path) + ;; <- + ;; File.cd! path, fn -> + ;; _generate_pkg(name, opts) + ;; end + ;; end + ((and (smie-rule-parent-p "def") + (smie-rule-hanging-p)) + (smie-rule-parent elixir-smie-indent-basic)) + (t elixir-smie-indent-basic))) + (`(:before . "end") + (cond + ((smie-rule-parent-p "for") + (smie-rule-parent)) + ((smie-rule-parent-p "(") + (smie-rule-parent)) + (t (smie-rule-parent)))) + (`(:before . "else:") + (cond + ((smie-rule-parent-p ";") + (if (> (nth 0 (syntax-ppss)) 0) + (smie-rule-parent elixir-smie-indent-basic) + (smie-rule-parent))) + ((smie-rule-parent-p "if") + (smie-rule-parent elixir-smie-indent-basic)) + (t (smie-rule-parent)))) + ;; Closing paren on the other line + (`(:before . "(") + (cond + ((smie-rule-parent-p "fn") + (smie-rule-parent elixir-smie-indent-basic)) + ;; Indent parenthesis correctly inside a block + ;; + ;; Example: + ;; + ;; def bar do + ;; () + ;; ..... + ((smie-rule-parent-p "do") + (smie-rule-parent)) + ((smie-rule-parent-p "OP") + (smie-rule-parent)) + ((and (smie-rule-parent-p "with") + (smie-rule-hanging-p)) + (smie-rule-parent)) + ((and (smie-rule-parent-p "with") + (not (smie-rule-hanging-p))) + (smie-rule-parent 3)) + ((smie-rule-parent-p ";") + (smie-rule-parent)) + (t (smie-rule-parent)))) + (`(:before . "[") + (cond + ((smie-rule-hanging-p) + (smie-rule-parent)))) + (`(:before . "{") + (cond + ((smie-rule-parent-p "COMMA") + (smie-rule-parent)) + ;; Example + ;; + ;; case parse do + ;; { [ help: true ], _, _ } + ;; -> :help + ;; { _, [ user, project, count ], _ } + ((and (not (smie-rule-hanging-p)) + (smie-rule-parent-p "do")) + ;; If the last line ends with a block operator `->' + ;; indent two spaces more + ;; + ;; Example + ;; + ;; case File.read("/usr/share/dict/words") do + ;; {:ok, contents} -> + ;; {:something, contents} <- Indent here two spaces + ;; ... + (if (elixir-smie-last-line-end-with-block-operator-p) + (smie-rule-parent elixir-smie-indent-basic))) + ((and (smie-rule-parent-p "MATCH-STATEMENT-DELIMITER") + (not (smie-rule-hanging-p))) + (if (elixir-smie-last-line-end-with-block-operator-p) + (smie-rule-parent elixir-smie-indent-basic) + (if (elixir-smie-last-line-start-with-block-operator-p) + (smie-rule-parent (- elixir-smie-indent-basic)) + (smie-rule-parent)))) + ((and (smie-rule-parent-p "OP") + (smie-rule-hanging-p)) + (smie-rule-parent)) + ((smie-rule-parent-p ";") + (if (save-excursion + (move-end-of-line 1) + (looking-back elixir-smie--block-operator-regexp (- (point) 3) t)) + (smie-rule-parent (- elixir-smie-indent-basic)) + (if (save-excursion + (move-beginning-of-line 1) + (looking-at "^.+->.+$")) + (smie-rule-parent (- elixir-smie-indent-basic)) + (smie-rule-parent)))))) + (`(:after . "{") + (cond + ((smie-rule-hanging-p) + (smie-rule-parent elixir-smie-indent-basic)) + (t elixir-smie-indent-basic))) + (`(:after . "[") + (cond + ((smie-rule-hanging-p) + (smie-rule-parent elixir-smie-indent-basic)) + (t elixir-smie-indent-basic))) + (`(:before . "if") + (cond + ;; Indent when if is inside a `->' block + ;; + ;; Example: + ;; + ;; whatever -> + ;; if true do <- + ;; :foo + ;; end + ;; .... + ((elixir-smie-last-line-end-with-block-operator-p) + (smie-rule-parent elixir-smie-indent-basic)) + ;; Indent if inside else + ;; + ;; Example: + ;; + ;; else + ;; if condition, do: :bar <- + ;; end + ((smie-rule-parent-p "else") + (smie-rule-parent elixir-smie-indent-basic)) + (t (smie-rule-parent)))) + (`(:before . "->") + (cond + ;; Example + ;; + ;; receive do + ;; after + ;; 2000 -> + ;; IO.puts 'hello' + ;; IO.puts 'status 2000 ends' <- Indent second line + ;; { :ok } -> + ;; .... + ((and (smie-rule-parent-p "after") + (not (smie-rule-sibling-p))) + (smie-rule-parent (+ elixir-smie-indent-basic + elixir-smie-indent-basic))) + ;; Example + ;; + ;; case parse do + ;; { [ help: true ], _, _ } + ;; -> :help + ;; ... + ((and (not (smie-rule-hanging-p)) + (smie-rule-parent-p "do")) + elixir-smie-indent-basic) + ((and (not (smie-rule-hanging-p)) + (smie-rule-parent-p "MATCH-STATEMENT-DELIMITER")) + (smie-rule-parent)) + (t (smie-rule-parent elixir-smie-indent-basic)))) + (`(:after . "->") + (cond + ;; This first condition is kind of complicated so I'll try to make this + ;; comment as clear as possible. + + ;; "If `->' is the last thing on the line, and its parent token + ;; is `fn' ..." + ((and (smie-rule-hanging-p) + (smie-rule-parent-p "fn")) + ;; "... and if: + + ;; 1. `smie--parent' is non-nil + ;; 2. the `->' token in question is on the same line as its parent (if + ;; the logic has gotten this far, its parent will be `fn') + + ;; ... then indent the line after the `->' aligned with the + ;; parent, offset by `elixir-smie-indent-basic'." + (if (and smie--parent (elixir-smie--same-line-as-parent + (nth 1 smie--parent) + (point))) + (smie-rule-parent elixir-smie-indent-basic) + elixir-smie-indent-basic)) + ;; Otherwise, if just indent by two. + ((smie-rule-hanging-p) + (cond + ((smie-rule-parent-p "catch" "rescue" "else") + (smie-rule-parent (+ elixir-smie-indent-basic + elixir-smie-indent-basic))) + ((smie-rule-parent-p "do" "try") + (smie-rule-parent elixir-smie-indent-basic)) + ;; Example + ;; + ;; receive do + ;; after + ;; 2000 -> + ;; IO.puts 'hello' <- Indent two spaces + ((and (smie-rule-parent-p "after") + (smie-rule-hanging-p) + (not (smie-rule-sibling-p))) + (smie-rule-parent (+ elixir-smie-indent-basic + elixir-smie-indent-basic))) + (t (smie-rule-parent elixir-smie-indent-basic)))))) + (`(:before . ";") + (cond + ;; Handle cases where built in keywords are used + ;; as function names. + ;; + ;; Example: + ;; + ;; def foo(test) do + ;; test_case = test.case + ;; run(test_case) + ;; end + ((and (smie-rule-parent-p "case" "try" "rescue") + (smie-rule-hanging-p) + (elixir-smie-current-line-contains-built-in-keyword-p)) + (+ (- (cdr (smie-rule-parent))) (+ elixir-smie-indent-basic + elixir-smie-indent-basic))) + ;; There is a case after an one line definition of functions/macros + ;; when an `if' keyword token is involved, where the next block `end' + ;; token will have a `if' as parent and it's hanging. + ;; + ;; Example: + ;; + ;; defmacro my_if(expr, do: if_block), do: if(expr, do: if_block, else: nil) + ;; defmacro my_if(expr, do: if_block, else: else_block) do + ;; ... + ;; end <- parent is `if` + ((and (smie-rule-parent-p "if") + (smie-rule-hanging-p)) + (smie-rule-parent)) + ((and (smie-rule-parent-p "else") + (smie-rule-hanging-p)) + (smie-rule-parent elixir-smie-indent-basic)) + ((smie-rule-parent-p "catch" "def" "defmodule" "defp" "do" "else" + "fn" "if" "rescue" "try" "unless" "defmacro" "defmacrop") + (smie-rule-parent)) + ((smie-rule-parent-p "after") + (smie-rule-parent elixir-smie-indent-basic)) + ;; Example + ;; + ;; case parse do + ;; { [ help: true ], _, _ } + ;; -> :help + ;; { _, [ user, project, count ], _ } + ;; -> { user, project, count } + ;; ... + ((and (smie-rule-parent-p "->") + (smie-rule-hanging-p)) + (smie-rule-parent)) + ((and (smie-rule-parent-p ";") + (smie-rule-hanging-p) + (save-excursion + (move-beginning-of-line 1) + (looking-at "^\s+else:.+$")) + (not (save-excursion + (move-beginning-of-line 1) + (looking-at "^\s+else:.+)$")))) + (smie-rule-parent (- elixir-smie-indent-basic))) + ((and (smie-rule-parent-p ";") + (save-excursion + (move-beginning-of-line 1) + (looking-at "^.+,$"))) + (smie-rule-parent)) + ((and (smie-rule-parent-p ";") + (smie-rule-hanging-p) + (save-excursion + (move-beginning-of-line 1) + (looking-at "^\s+do:.+$")) + (not (save-excursion + (move-beginning-of-line 1) + (looking-at "^\s+do:.+)$")))) + (smie-rule-parent)) + ((elixir-smie-current-line-start-with-pipe-operator-p) + (smie-rule-parent)) + ((smie-rule-parent-p "(") + (smie-rule-parent elixir-smie-indent-basic)))) + (`(:after . ";") + (cond + ((smie-rule-parent-p "def") + (smie-rule-parent)) + ((and (smie-rule-parent-p "if") + (elixir-smie-current-line-contains-built-in-keyword-p)) + (+ (- (cdr (smie-rule-parent))) (+ elixir-smie-indent-basic + elixir-smie-indent-basic))) + ((smie-rule-parent-p "if") + (smie-rule-parent)) + ((smie-rule-parent-p "after") + (smie-rule-parent elixir-smie-indent-basic)) + ((and (smie-rule-parent-p "(") + (boundp 'smie--parent) + (save-excursion + (goto-char (cadr smie--parent)) + (smie-rule-hanging-p))) + (smie-rule-parent elixir-smie-indent-basic)))))) + +(defun elixir-smie--heredoc-at-current-point-p () + "Return non-nil if cursor is at a string." + (save-excursion + (or (and (nth 3 (save-excursion + (let ((pos (point))) + (parse-partial-sexp 1 pos)))) + (nth 8 (save-excursion + (let ((pos (point))) + (parse-partial-sexp 1 pos))))) + (and (looking-at "\"\"\"") + (match-beginning 0))))) + +(defun elixir-smie--previous-line-empty-p () + "Return non-nil if the previous line is blank." + (save-excursion + (forward-line -1) + (move-beginning-of-line 1) + (looking-at "[[:space:]]*$"))) + +(defun elixir-smie--previous-line-indentation () + "Return the indentation of the previous line." + (save-excursion + (forward-line -1) + (current-indentation))) + +;; Add the custom function to handle indentation inside heredoc to the +;; smie-indent-functions list. The indentation function will only be +;; process inside an elixir-mode. +(defun elixir-smie--indent-inside-heredoc () + "Handle indentation inside Elixir heredocs. + +Rules: + 1. If the previous line is empty, indent as the basic indentation + at the beginning of the heredoc. + 2. If the previous line is not empty, indent as the previous line. +" + (if (eq major-mode 'elixir-mode) + (if (elixir-smie--heredoc-at-current-point-p) + (let ((indent + (save-excursion + (when (re-search-backward "^\\(\s+\\)\\(@doc\\|@moduledoc\\|.*\\)\"\"\"" nil t) + (string-width (match-string 1)))))) + (cond + ((elixir-smie--previous-line-empty-p) + (goto-char indent)) + ((and (not (save-excursion (looking-at "\"\"\""))) + (not (elixir-smie--previous-line-empty-p))) + (goto-char (elixir-smie--previous-line-indentation))) + (indent + (goto-char indent))))))) + +(defun elixir-smie-empty-string-p (string) + "Return non-nil if STRING is null, blank or whitespace only." + (or (null string) + (string= string "") + (if (string-match-p "^\s+$" string) t))) + +(add-to-list 'smie-indent-functions 'elixir-smie--indent-inside-heredoc) + +(provide 'elixir-smie) + +;;; elixir-smie.el ends here |