about summary refs log tree commit diff
path: root/configs/shared/emacs/.emacs.d/elpa/elixir-mode-20180711.1245/elixir-smie.el
diff options
context:
space:
mode:
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.el904
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